From 2aa4a82499d4becd2284cdb482213d541b8804dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 16:29:10 +0200 Subject: Adding upstream version 86.0.1. Signed-off-by: Daniel Baumann --- gfx/layers/AndroidHardwareBuffer.cpp | 510 ++ gfx/layers/AndroidHardwareBuffer.h | 244 + gfx/layers/AnimationHelper.cpp | 713 ++ gfx/layers/AnimationHelper.h | 152 + gfx/layers/AnimationInfo.cpp | 1022 +++ gfx/layers/AnimationInfo.h | 168 + gfx/layers/AnimationStorageData.h | 114 + gfx/layers/AtomicRefCountedWithFinalize.h | 207 + gfx/layers/AxisPhysicsMSDModel.cpp | 78 + gfx/layers/AxisPhysicsMSDModel.h | 83 + gfx/layers/AxisPhysicsModel.cpp | 101 + gfx/layers/AxisPhysicsModel.h | 123 + gfx/layers/BSPTree.cpp | 115 + gfx/layers/BSPTree.h | 136 + gfx/layers/BufferTexture.cpp | 568 ++ gfx/layers/BufferTexture.h | 128 + gfx/layers/BuildConstants.h | 64 + gfx/layers/CanvasDrawEventRecorder.cpp | 517 ++ gfx/layers/CanvasDrawEventRecorder.h | 284 + gfx/layers/CanvasRenderer.cpp | 139 + gfx/layers/CanvasRenderer.h | 153 + gfx/layers/CompositionRecorder.cpp | 107 + gfx/layers/CompositionRecorder.h | 105 + gfx/layers/Compositor.cpp | 616 ++ gfx/layers/Compositor.h | 750 ++ gfx/layers/CompositorAnimationStorage.cpp | 674 ++ gfx/layers/CompositorAnimationStorage.h | 236 + gfx/layers/CompositorTypes.cpp | 54 + gfx/layers/CompositorTypes.h | 321 + gfx/layers/D3D11ShareHandleImage.cpp | 188 + gfx/layers/D3D11ShareHandleImage.h | 94 + gfx/layers/D3D11YCbCrImage.cpp | 430 ++ gfx/layers/D3D11YCbCrImage.h | 88 + gfx/layers/D3D9SurfaceImage.cpp | 267 + gfx/layers/D3D9SurfaceImage.h | 122 + gfx/layers/DMABUFSurfaceImage.cpp | 38 + gfx/layers/DMABUFSurfaceImage.h | 39 + gfx/layers/DirectedGraph.h | 101 + gfx/layers/DirectionUtils.h | 62 + gfx/layers/Effects.cpp | 62 + gfx/layers/Effects.h | 303 + gfx/layers/FrameMetrics.cpp | 313 + gfx/layers/FrameMetrics.h | 1039 +++ gfx/layers/GLImages.cpp | 91 + gfx/layers/GLImages.h | 87 + gfx/layers/GPUVideoImage.h | 98 + gfx/layers/IMFYCbCrImage.cpp | 151 + gfx/layers/IMFYCbCrImage.h | 43 + gfx/layers/IPDLActor.h | 59 + gfx/layers/ImageContainer.cpp | 869 +++ gfx/layers/ImageContainer.h | 887 +++ gfx/layers/ImageDataSerializer.cpp | 338 + gfx/layers/ImageDataSerializer.h | 105 + gfx/layers/ImageLayers.cpp | 61 + gfx/layers/ImageLayers.h | 94 + gfx/layers/ImageTypes.h | 122 + gfx/layers/LayerAttributes.h | 409 + gfx/layers/LayerManager.cpp | 216 + gfx/layers/LayerManager.h | 788 ++ gfx/layers/LayerMetricsWrapper.h | 519 ++ gfx/layers/LayerScope.cpp | 1633 ++++ gfx/layers/LayerScope.h | 68 + gfx/layers/LayerSorter.cpp | 358 + gfx/layers/LayerSorter.h | 22 + gfx/layers/LayerTreeInvalidation.cpp | 820 ++ gfx/layers/LayerTreeInvalidation.h | 79 + gfx/layers/LayerUserData.h | 28 + gfx/layers/Layers.cpp | 2250 ++++++ gfx/layers/Layers.h | 2063 ++++++ gfx/layers/LayersHelpers.cpp | 60 + gfx/layers/LayersHelpers.h | 51 + gfx/layers/LayersTypes.cpp | 170 + gfx/layers/LayersTypes.h | 444 ++ gfx/layers/MacIOSurfaceHelpers.cpp | 185 + gfx/layers/MacIOSurfaceHelpers.h | 30 + gfx/layers/MacIOSurfaceImage.cpp | 150 + gfx/layers/MacIOSurfaceImage.h | 71 + gfx/layers/MemoryPressureObserver.cpp | 77 + gfx/layers/MemoryPressureObserver.h | 59 + gfx/layers/NativeLayer.h | 231 + gfx/layers/NativeLayerCA.h | 396 + gfx/layers/NativeLayerCA.mm | 1069 +++ gfx/layers/OOPCanvasRenderer.h | 53 + gfx/layers/PaintThread.cpp | 272 + gfx/layers/PaintThread.h | 112 + gfx/layers/PersistentBufferProvider.cpp | 543 ++ gfx/layers/PersistentBufferProvider.h | 212 + gfx/layers/ProfilerScreenshots.cpp | 139 + gfx/layers/ProfilerScreenshots.h | 112 + gfx/layers/ReadbackLayer.h | 202 + gfx/layers/ReadbackProcessor.cpp | 175 + gfx/layers/ReadbackProcessor.h | 80 + gfx/layers/RecordedCanvasEventImpl.h | 595 ++ gfx/layers/RenderTrace.cpp | 79 + gfx/layers/RenderTrace.h | 73 + gfx/layers/RepaintRequest.cpp | 26 + gfx/layers/RepaintRequest.h | 312 + gfx/layers/RotatedBuffer.cpp | 477 ++ gfx/layers/RotatedBuffer.h | 418 ++ gfx/layers/SampleTime.cpp | 75 + gfx/layers/SampleTime.h | 69 + gfx/layers/ScreenshotGrabber.cpp | 233 + gfx/layers/ScreenshotGrabber.h | 139 + gfx/layers/ScrollableLayerGuid.cpp | 72 + gfx/layers/ScrollableLayerGuid.h | 80 + gfx/layers/ShareableCanvasRenderer.cpp | 219 + gfx/layers/ShareableCanvasRenderer.h | 62 + gfx/layers/SourceSurfaceSharedData.cpp | 195 + gfx/layers/SourceSurfaceSharedData.h | 329 + gfx/layers/SourceSurfaceVolatileData.cpp | 52 + gfx/layers/SourceSurfaceVolatileData.h | 100 + gfx/layers/SurfacePool.h | 72 + gfx/layers/SurfacePoolCA.h | 287 + gfx/layers/SurfacePoolCA.mm | 436 ++ gfx/layers/SyncObject.cpp | 50 + gfx/layers/SyncObject.h | 75 + gfx/layers/TextureDIB.cpp | 466 ++ gfx/layers/TextureDIB.h | 116 + gfx/layers/TextureSourceProvider.cpp | 88 + gfx/layers/TextureSourceProvider.h | 141 + gfx/layers/TextureSync.cpp | 283 + gfx/layers/TextureSync.h | 49 + gfx/layers/TextureWrapperImage.cpp | 49 + gfx/layers/TextureWrapperImage.h | 37 + gfx/layers/TiledLayerBuffer.h | 214 + gfx/layers/TransactionIdAllocator.h | 93 + gfx/layers/TreeTraversal.h | 276 + gfx/layers/UpdateImageHelper.h | 82 + gfx/layers/ZoomConstraints.cpp | 23 + gfx/layers/ZoomConstraints.h | 68 + gfx/layers/apz/public/APZInputBridge.h | 167 + gfx/layers/apz/public/APZPublicUtils.h | 84 + gfx/layers/apz/public/APZSampler.h | 174 + gfx/layers/apz/public/APZUpdater.h | 235 + gfx/layers/apz/public/CompositorController.h | 35 + gfx/layers/apz/public/GeckoContentController.h | 177 + .../apz/public/GeckoContentControllerTypes.h | 68 + gfx/layers/apz/public/IAPZCTreeManager.h | 165 + gfx/layers/apz/public/MatrixMessage.h | 54 + gfx/layers/apz/public/MetricsSharingController.h | 37 + gfx/layers/apz/src/APZCTreeManager.cpp | 3967 ++++++++++ gfx/layers/apz/src/APZCTreeManager.h | 1042 +++ gfx/layers/apz/src/APZInputBridge.cpp | 215 + gfx/layers/apz/src/APZPublicUtils.cpp | 145 + gfx/layers/apz/src/APZSampler.cpp | 341 + gfx/layers/apz/src/APZUpdater.cpp | 544 ++ gfx/layers/apz/src/APZUtils.cpp | 101 + gfx/layers/apz/src/APZUtils.h | 207 + gfx/layers/apz/src/AndroidAPZ.cpp | 36 + gfx/layers/apz/src/AndroidAPZ.h | 34 + gfx/layers/apz/src/AndroidFlingPhysics.cpp | 216 + gfx/layers/apz/src/AndroidFlingPhysics.h | 45 + gfx/layers/apz/src/AndroidVelocityTracker.cpp | 288 + gfx/layers/apz/src/AndroidVelocityTracker.h | 42 + gfx/layers/apz/src/AsyncDragMetrics.h | 53 + gfx/layers/apz/src/AsyncPanZoomAnimation.h | 101 + gfx/layers/apz/src/AsyncPanZoomController.cpp | 5713 ++++++++++++++ gfx/layers/apz/src/AsyncPanZoomController.h | 1767 +++++ gfx/layers/apz/src/AutoDirWheelDeltaAdjuster.h | 89 + gfx/layers/apz/src/AutoscrollAnimation.cpp | 93 + gfx/layers/apz/src/AutoscrollAnimation.h | 42 + gfx/layers/apz/src/Axis.cpp | 525 ++ gfx/layers/apz/src/Axis.h | 379 + gfx/layers/apz/src/CheckerboardEvent.cpp | 193 + gfx/layers/apz/src/CheckerboardEvent.h | 219 + gfx/layers/apz/src/DesktopFlingPhysics.h | 67 + gfx/layers/apz/src/DragTracker.cpp | 59 + gfx/layers/apz/src/DragTracker.h | 39 + gfx/layers/apz/src/ExpectedGeckoMetrics.cpp | 20 + gfx/layers/apz/src/ExpectedGeckoMetrics.h | 41 + gfx/layers/apz/src/FlingAccelerator.cpp | 128 + gfx/layers/apz/src/FlingAccelerator.h | 59 + gfx/layers/apz/src/FocusState.cpp | 225 + gfx/layers/apz/src/FocusState.h | 175 + gfx/layers/apz/src/FocusTarget.cpp | 229 + gfx/layers/apz/src/FocusTarget.h | 71 + gfx/layers/apz/src/GenericFlingAnimation.h | 202 + gfx/layers/apz/src/GenericScrollAnimation.cpp | 116 + gfx/layers/apz/src/GenericScrollAnimation.h | 59 + gfx/layers/apz/src/GestureEventListener.cpp | 666 ++ gfx/layers/apz/src/GestureEventListener.h | 285 + gfx/layers/apz/src/HitTestingTreeNode.cpp | 517 ++ gfx/layers/apz/src/HitTestingTreeNode.h | 302 + gfx/layers/apz/src/InputBlockState.cpp | 837 +++ gfx/layers/apz/src/InputBlockState.h | 541 ++ gfx/layers/apz/src/InputQueue.cpp | 1018 +++ gfx/layers/apz/src/InputQueue.h | 273 + gfx/layers/apz/src/KeyboardMap.cpp | 170 + gfx/layers/apz/src/KeyboardMap.h | 118 + gfx/layers/apz/src/KeyboardScrollAction.cpp | 37 + gfx/layers/apz/src/KeyboardScrollAction.h | 48 + gfx/layers/apz/src/Overscroll.h | 137 + gfx/layers/apz/src/OverscrollHandoffState.cpp | 178 + gfx/layers/apz/src/OverscrollHandoffState.h | 182 + gfx/layers/apz/src/OvershootDetector.cpp | 69 + gfx/layers/apz/src/OvershootDetector.h | 35 + .../src/PotentialCheckerboardDurationTracker.cpp | 68 + .../apz/src/PotentialCheckerboardDurationTracker.h | 61 + gfx/layers/apz/src/QueuedInput.cpp | 44 + gfx/layers/apz/src/QueuedInput.h | 63 + gfx/layers/apz/src/RecentEventsBuffer.h | 83 + gfx/layers/apz/src/SampledAPZCState.cpp | 95 + gfx/layers/apz/src/SampledAPZCState.h | 61 + gfx/layers/apz/src/SimpleVelocityTracker.cpp | 135 + gfx/layers/apz/src/SimpleVelocityTracker.h | 54 + gfx/layers/apz/src/SmoothMsdScrollAnimation.cpp | 130 + gfx/layers/apz/src/SmoothMsdScrollAnimation.h | 48 + gfx/layers/apz/src/SmoothScrollAnimation.cpp | 46 + gfx/layers/apz/src/SmoothScrollAnimation.h | 37 + gfx/layers/apz/src/WheelScrollAnimation.cpp | 62 + gfx/layers/apz/src/WheelScrollAnimation.h | 30 + gfx/layers/apz/test/gtest/APZCBasicTester.h | 123 + gfx/layers/apz/test/gtest/APZCTreeManagerTester.h | 161 + gfx/layers/apz/test/gtest/APZTestCommon.h | 954 +++ gfx/layers/apz/test/gtest/InputUtils.h | 138 + gfx/layers/apz/test/gtest/TestBasic.cpp | 530 ++ gfx/layers/apz/test/gtest/TestEventRegions.cpp | 410 + .../apz/test/gtest/TestFlingAcceleration.cpp | 255 + gfx/layers/apz/test/gtest/TestGestureDetector.cpp | 877 +++ gfx/layers/apz/test/gtest/TestHitTesting.cpp | 694 ++ gfx/layers/apz/test/gtest/TestInputQueue.cpp | 45 + gfx/layers/apz/test/gtest/TestPanning.cpp | 239 + gfx/layers/apz/test/gtest/TestPinching.cpp | 625 ++ gfx/layers/apz/test/gtest/TestScrollHandoff.cpp | 658 ++ gfx/layers/apz/test/gtest/TestSnapping.cpp | 129 + .../apz/test/gtest/TestSnappingOnMomentum.cpp | 88 + gfx/layers/apz/test/gtest/TestTreeManager.cpp | 306 + gfx/layers/apz/test/gtest/moz.build | 35 + .../test/gtest/mvm/TestMobileViewportManager.cpp | 220 + gfx/layers/apz/test/gtest/mvm/moz.build | 13 + gfx/layers/apz/test/mochitest/.eslintrc.js | 5 + .../apz/test/mochitest/FissionTestHelperChild.jsm | 159 + .../apz/test/mochitest/FissionTestHelperParent.jsm | 104 + .../test/mochitest/apz_test_native_event_utils.js | 1076 +++ gfx/layers/apz/test/mochitest/apz_test_utils.js | 1177 +++ gfx/layers/apz/test/mochitest/browser.ini | 33 + .../browser_test_background_tab_scroll.js | 70 + .../test/mochitest/browser_test_group_fission.js | 154 + .../mochitest/browser_test_reset_scaling_zoom.js | 44 + ...ser_test_scrollbar_in_extension_popup_window.js | 116 + ...ser_test_scrolling_in_extension_popup_window.js | 182 + .../apz/test/mochitest/browser_test_select_zoom.js | 220 + .../mochitest/helper_background_tab_scroll.html | 9 + .../mochitest/helper_basic_doubletap_zoom.html | 61 + .../test/mochitest/helper_basic_onetouchpinch.html | 90 + .../apz/test/mochitest/helper_basic_pan.html | 73 + .../apz/test/mochitest/helper_basic_zoom.html | 71 + .../apz/test/mochitest/helper_bug1162771.html | 131 + .../apz/test/mochitest/helper_bug1271432.html | 573 ++ .../apz/test/mochitest/helper_bug1280013.html | 73 + .../apz/test/mochitest/helper_bug1285070.html | 44 + .../apz/test/mochitest/helper_bug1299195.html | 47 + .../apz/test/mochitest/helper_bug1326290.html | 63 + .../apz/test/mochitest/helper_bug1331693.html | 71 + .../apz/test/mochitest/helper_bug1346632.html | 69 + .../apz/test/mochitest/helper_bug1414336.html | 98 + .../apz/test/mochitest/helper_bug1462961.html | 74 + .../test/mochitest/helper_bug1464568_opacity.html | 66 + .../mochitest/helper_bug1464568_transform.html | 66 + .../apz/test/mochitest/helper_bug1473108.html | 50 + .../apz/test/mochitest/helper_bug1490393-2.html | 65 + .../apz/test/mochitest/helper_bug1490393.html | 64 + .../helper_bug1502010_unconsumed_pan.html | 76 + ...per_bug1506497_touch_action_fixed_on_fixed.html | 96 + .../apz/test/mochitest/helper_bug1509575.html | 71 + ...elper_bug1544966_zoom_on_touch_action_none.html | 89 + .../apz/test/mochitest/helper_bug1550510.html | 66 + .../helper_bug1637113_main_thread_hit_test.html | 70 + .../helper_bug1637135_narrow_viewport.html | 60 + .../helper_bug1638441_fixed_pos_hit_test.html | 67 + .../mochitest/helper_bug1638458_contextmenu.html | 82 + ...elper_bug1648491_no_pointercancel_with_dtc.html | 89 + .../apz/test/mochitest/helper_bug1662800.html | 61 + ...3731_no_pointercancel_on_second_touchstart.html | 82 + .../apz/test/mochitest/helper_bug1669625.html | 74 + .../apz/test/mochitest/helper_bug1674935.html | 76 + ...170_pointercancel_on_touchaction_pinchzoom.html | 75 + .../apz/test/mochitest/helper_bug982141.html | 137 + .../helper_checkerboard_apzforcedisabled.html | 88 + .../helper_checkerboard_no_multiplier.html | 57 + .../mochitest/helper_checkerboard_scrollinfo.html | 91 + .../helper_checkerboard_zoom_during_load.html | 55 + .../helper_checkerboard_zoomoverflowhidden.html | 145 + gfx/layers/apz/test/mochitest/helper_click.html | 41 + .../helper_click_interrupt_animation.html | 85 + gfx/layers/apz/test/mochitest/helper_div_pan.html | 44 + .../apz/test/mochitest/helper_drag_click.html | 44 + .../test/mochitest/helper_drag_root_scrollbar.html | 61 + .../apz/test/mochitest/helper_drag_scroll.html | 633 ++ .../helper_fission_animation_styling_in_oopif.html | 166 + ...ion_animation_styling_in_transformed_oopif.html | 130 + .../apz/test/mochitest/helper_fission_basic.html | 40 + .../apz/test/mochitest/helper_fission_empty.html | 33 + .../helper_fission_event_region_override.html | 84 + .../helper_fission_force_empty_hit_region.html | 82 + ...elper_fission_inactivescroller_under_oopif.html | 88 + .../mochitest/helper_fission_scroll_oopif.html | 157 + .../apz/test/mochitest/helper_fission_tap.html | 87 + ...per_fission_tap_in_nested_iframe_on_zoomed.html | 106 + .../mochitest/helper_fission_tap_on_zoomed.html | 93 + .../apz/test/mochitest/helper_fission_touch.html | 99 + .../test/mochitest/helper_fission_transforms.html | 85 + .../apz/test/mochitest/helper_fission_utils.js | 130 + .../mochitest/helper_fixed_pos_displayport.html | 101 + .../helper_fixed_position_scroll_hittest.html | 51 + .../apz/test/mochitest/helper_fullscreen.html | 53 + .../mochitest/helper_hittest_backface_hidden.html | 67 + .../apz/test/mochitest/helper_hittest_basic.html | 141 + .../mochitest/helper_hittest_checkerboard.html | 59 + .../test/mochitest/helper_hittest_clippath.html | 115 + .../helper_hittest_clipped_fixed_modal.html | 85 + .../mochitest/helper_hittest_deep_scene_stack.html | 57 + ...helper_hittest_fixed_in_scrolled_transform.html | 88 + .../mochitest/helper_hittest_float_bug1434846.html | 56 + .../mochitest/helper_hittest_float_bug1443518.html | 56 + ...helper_hittest_hidden_inactive_scrollframe.html | 52 + .../helper_hittest_hoisted_scrollinfo.html | 84 + ...elper_hittest_nested_transforms_bug1459696.html | 80 + .../helper_hittest_pointerevents_svg.html | 177 + .../apz/test/mochitest/helper_hittest_spam.html | 100 + .../helper_hittest_sticky_bug1478304.html | 58 + .../test/mochitest/helper_hittest_touchaction.html | 364 + .../mochitest/helper_horizontal_checkerboard.html | 65 + gfx/layers/apz/test/mochitest/helper_iframe1.html | 14 + gfx/layers/apz/test/mochitest/helper_iframe2.html | 14 + .../apz/test/mochitest/helper_iframe_pan.html | 50 + .../apz/test/mochitest/helper_iframe_textarea.html | 12 + .../apz/test/mochitest/helper_key_scroll.html | 118 + gfx/layers/apz/test/mochitest/helper_long_tap.html | 108 + .../test/mochitest/helper_minimum_scale_1_0.html | 46 + .../helper_no_scalable_with_initial_scale.html | 48 + .../mochitest/helper_onetouchpinch_nested.html | 103 + .../test/mochitest/helper_overflowhidden_zoom.html | 83 + .../apz/test/mochitest/helper_override_root.html | 60 + .../apz/test/mochitest/helper_override_subdoc.html | 15 + .../helper_overscroll_behavior_bug1425573.html | 44 + .../helper_overscroll_behavior_bug1425603.html | 78 + .../helper_overscroll_behavior_bug1494440.html | 51 + .../helper_scroll_anchoring_smooth_scroll.html | 54 + .../helper_scroll_inactive_perspective.html | 46 + .../mochitest/helper_scroll_inactive_zindex.html | 47 + .../helper_scroll_into_view_bug1516056.html | 73 + .../helper_scroll_into_view_bug1562757.html | 75 + .../mochitest/helper_scroll_on_position_fixed.html | 61 + .../mochitest/helper_scroll_over_scrollbar.html | 49 + .../helper_scroll_snap_no_valid_snap_position.html | 45 + .../helper_scroll_tables_perspective.html | 67 + .../helper_scrollbar_snap_bug1501062.html | 105 + .../test/mochitest/helper_scrollby_bug1531796.html | 36 + .../helper_scrollframe_activation_on_load.html | 69 + .../apz/test/mochitest/helper_scrollto_tap.html | 61 + .../apz/test/mochitest/helper_self_closer.html | 12 + .../test/mochitest/helper_smoothscroll_spam.html | 51 + .../helper_smoothscroll_spam_interleaved.html | 57 + .../apz/test/mochitest/helper_subframe_style.css | 15 + gfx/layers/apz/test/mochitest/helper_tall.html | 504 ++ gfx/layers/apz/test/mochitest/helper_tap.html | 32 + .../test/mochitest/helper_tap_default_passive.html | 79 + .../apz/test/mochitest/helper_tap_fullzoom.html | 33 + .../apz/test/mochitest/helper_tap_passive.html | 64 + .../mochitest/helper_test_reset_scaling_zoom.html | 23 + .../test/mochitest/helper_test_select_zoom.html | 45 + .../apz/test/mochitest/helper_touch_action.html | 123 + .../mochitest/helper_touch_action_complex.html | 137 + .../mochitest/helper_touch_action_regions.html | 288 + ...elper_touch_action_zero_opacity_bug1500864.html | 45 + .../helper_visual_scrollbars_pagescroll.html | 97 + .../mochitest/helper_visual_smooth_scroll.html | 53 + .../helper_visualscroll_clamp_restore.html | 68 + .../mochitest/helper_visualscroll_nonrcd_rsf.html | 88 + .../helper_zoomToFocusedInput_iframe.html | 54 + .../helper_zoomToFocusedInput_multiline.html | 75 + .../helper_zoomToFocusedInput_scroll.html | 53 + .../test/mochitest/helper_zoom_keyboardscroll.html | 70 + .../helper_zoom_out_clamped_scrollpos.html | 85 + .../helper_zoom_out_with_mainthread_clamping.html | 106 + .../apz/test/mochitest/helper_zoom_prevented.html | 75 + .../helper_zoom_restore_position_tabswitch.html | 74 + .../helper_zoom_with_dynamic_toolbar.html | 45 + .../apz/test/mochitest/helper_zoomed_pan.html | 79 + gfx/layers/apz/test/mochitest/mochitest.ini | 113 + gfx/layers/apz/test/mochitest/test_bug1151667.html | 65 + gfx/layers/apz/test/mochitest/test_bug1253683.html | 59 + gfx/layers/apz/test/mochitest/test_bug1277814.html | 105 + .../apz/test/mochitest/test_bug1304689-2.html | 130 + gfx/layers/apz/test/mochitest/test_bug1304689.html | 134 + gfx/layers/apz/test/mochitest/test_bug982141.html | 38 + .../test/mochitest/test_frame_reconstruction.html | 218 + .../apz/test/mochitest/test_group_bug1464568.html | 30 + .../test/mochitest/test_group_checkerboarding.html | 68 + .../test/mochitest/test_group_double_tap_zoom.html | 36 + .../apz/test/mochitest/test_group_fullscreen.html | 33 + .../apz/test/mochitest/test_group_hittest.html | 73 + .../apz/test/mochitest/test_group_keyboard.html | 31 + .../apz/test/mochitest/test_group_mainthread.html | 33 + .../mochitest/test_group_minimum_scale_size.html | 56 + .../apz/test/mochitest/test_group_mouseevents.html | 62 + .../apz/test/mochitest/test_group_overrides.html | 37 + .../test/mochitest/test_group_pointerevents.html | 43 + .../apz/test/mochitest/test_group_scroll_snap.html | 36 + .../test_group_scrollframe_activation.html | 35 + .../test/mochitest/test_group_touchevents-2.html | 67 + .../test/mochitest/test_group_touchevents-3.html | 47 + .../test/mochitest/test_group_touchevents-4.html | 55 + .../test/mochitest/test_group_touchevents-5.html | 45 + .../apz/test/mochitest/test_group_touchevents.html | 55 + .../apz/test/mochitest/test_group_wheelevents.html | 59 + .../apz/test/mochitest/test_group_zoom-2.html | 77 + gfx/layers/apz/test/mochitest/test_group_zoom.html | 86 + .../mochitest/test_group_zoomToFocusedInput.html | 29 + .../test/mochitest/test_interrupted_reflow.html | 718 ++ .../apz/test/mochitest/test_layerization.html | 203 + .../apz/test/mochitest/test_relative_update.html | 92 + .../mochitest/test_scroll_inactive_bug1190112.html | 552 ++ .../test_scroll_inactive_flattened_frame.html | 49 + .../mochitest/test_scroll_subframe_scrollbar.html | 115 + gfx/layers/apz/test/mochitest/test_smoothness.html | 71 + .../test_touch_listeners_impacting_wheel.html | 114 + .../apz/test/mochitest/test_wheel_scroll.html | 104 + .../test/mochitest/test_wheel_transactions.html | 141 + .../apz/test/reftest/async-scrollbar-1-h-ref.html | 8 + .../test/reftest/async-scrollbar-1-h-rtl-ref.html | 9 + .../apz/test/reftest/async-scrollbar-1-h-rtl.html | 13 + .../apz/test/reftest/async-scrollbar-1-h.html | 12 + .../apz/test/reftest/async-scrollbar-1-v-ref.html | 8 + .../test/reftest/async-scrollbar-1-v-rtl-ref.html | 9 + .../apz/test/reftest/async-scrollbar-1-v-rtl.html | 13 + .../apz/test/reftest/async-scrollbar-1-v.html | 12 + .../apz/test/reftest/async-scrollbar-1-vh-ref.html | 8 + .../test/reftest/async-scrollbar-1-vh-rtl-ref.html | 9 + .../apz/test/reftest/async-scrollbar-1-vh-rtl.html | 13 + .../apz/test/reftest/async-scrollbar-1-vh.html | 12 + .../test/reftest/async-scrollbar-zoom-1-ref.html | 8 + .../apz/test/reftest/async-scrollbar-zoom-1.html | 13 + .../test/reftest/async-scrollbar-zoom-2-ref.html | 8 + .../apz/test/reftest/async-scrollbar-zoom-2.html | 13 + .../frame-reconstruction-scroll-clamping-ref.html | 27 + .../frame-reconstruction-scroll-clamping.html | 53 + .../apz/test/reftest/iframe-zoomed-child.html | 12 + gfx/layers/apz/test/reftest/iframe-zoomed-ref.html | 20 + gfx/layers/apz/test/reftest/iframe-zoomed.html | 25 + .../apz/test/reftest/initial-scale-1-ref.html | 9 + gfx/layers/apz/test/reftest/initial-scale-1.html | 9 + .../reftest/pinch-zoom-position-fixed-ref.html | 23 + .../test/reftest/pinch-zoom-position-fixed.html | 37 + .../reftest/pinch-zoom-position-sticky-ref.html | 27 + .../test/reftest/pinch-zoom-position-sticky.html | 30 + gfx/layers/apz/test/reftest/reftest.list | 41 + .../apz/test/reftest/root-scrollbars-1-ref.html | 14 + gfx/layers/apz/test/reftest/root-scrollbars-1.html | 14 + .../apz/test/reftest/scaled-iframe-zoomed-ref.html | 21 + .../apz/test/reftest/scaled-iframe-zoomed.html | 26 + .../reftest/scrollbar-zoom-resolution-1-ref.html | 8 + .../test/reftest/scrollbar-zoom-resolution-1.html | 8 + .../reftest/scrollbar-zoom-resolution-2-ref.html | 8 + .../test/reftest/scrollbar-zoom-resolution-2.html | 8 + gfx/layers/apz/testutil/APZTestData.cpp | 113 + gfx/layers/apz/testutil/APZTestData.h | 219 + gfx/layers/apz/util/APZCCallbackHelper.cpp | 925 +++ gfx/layers/apz/util/APZCCallbackHelper.h | 204 + gfx/layers/apz/util/APZEventState.cpp | 553 ++ gfx/layers/apz/util/APZEventState.h | 124 + gfx/layers/apz/util/APZThreadUtils.cpp | 118 + gfx/layers/apz/util/APZThreadUtils.h | 117 + gfx/layers/apz/util/ActiveElementManager.cpp | 178 + gfx/layers/apz/util/ActiveElementManager.h | 97 + gfx/layers/apz/util/CheckerboardReportService.cpp | 219 + gfx/layers/apz/util/CheckerboardReportService.h | 138 + gfx/layers/apz/util/ChromeProcessController.cpp | 335 + gfx/layers/apz/util/ChromeProcessController.h | 97 + gfx/layers/apz/util/ContentProcessController.cpp | 108 + gfx/layers/apz/util/ContentProcessController.h | 87 + gfx/layers/apz/util/DoubleTapToZoom.cpp | 188 + gfx/layers/apz/util/DoubleTapToZoom.h | 35 + gfx/layers/apz/util/InputAPZContext.cpp | 65 + gfx/layers/apz/util/InputAPZContext.h | 69 + gfx/layers/apz/util/ScrollInputMethods.h | 78 + gfx/layers/apz/util/ScrollLinkedEffectDetector.cpp | 47 + gfx/layers/apz/util/ScrollLinkedEffectDetector.h | 46 + gfx/layers/apz/util/TouchActionHelper.cpp | 106 + gfx/layers/apz/util/TouchActionHelper.h | 39 + gfx/layers/apz/util/TouchCounter.cpp | 74 + gfx/layers/apz/util/TouchCounter.h | 36 + gfx/layers/basic/AutoMaskData.h | 54 + gfx/layers/basic/BasicCanvasLayer.cpp | 77 + gfx/layers/basic/BasicCanvasLayer.h | 44 + gfx/layers/basic/BasicColorLayer.cpp | 75 + gfx/layers/basic/BasicCompositor.cpp | 1252 ++++ gfx/layers/basic/BasicCompositor.h | 240 + gfx/layers/basic/BasicContainerLayer.cpp | 162 + gfx/layers/basic/BasicContainerLayer.h | 103 + gfx/layers/basic/BasicImageLayer.cpp | 108 + gfx/layers/basic/BasicImages.cpp | 180 + gfx/layers/basic/BasicImplData.h | 140 + gfx/layers/basic/BasicLayerManager.cpp | 969 +++ gfx/layers/basic/BasicLayers.h | 243 + gfx/layers/basic/BasicLayersImpl.cpp | 219 + gfx/layers/basic/BasicLayersImpl.h | 145 + gfx/layers/basic/BasicPaintedLayer.cpp | 239 + gfx/layers/basic/BasicPaintedLayer.h | 121 + gfx/layers/basic/MacIOSurfaceTextureHostBasic.cpp | 89 + gfx/layers/basic/MacIOSurfaceTextureHostBasic.h | 87 + gfx/layers/basic/TextureClientX11.cpp | 140 + gfx/layers/basic/TextureClientX11.h | 56 + gfx/layers/basic/TextureHostBasic.cpp | 33 + gfx/layers/basic/TextureHostBasic.h | 38 + gfx/layers/basic/X11BasicCompositor.cpp | 116 + gfx/layers/basic/X11BasicCompositor.h | 66 + gfx/layers/basic/X11TextureSourceBasic.cpp | 48 + gfx/layers/basic/X11TextureSourceBasic.h | 50 + gfx/layers/client/CanvasClient.cpp | 97 + gfx/layers/client/CanvasClient.h | 71 + gfx/layers/client/ClientCanvasLayer.cpp | 41 + gfx/layers/client/ClientCanvasLayer.h | 80 + gfx/layers/client/ClientCanvasRenderer.cpp | 37 + gfx/layers/client/ClientCanvasRenderer.h | 34 + gfx/layers/client/ClientColorLayer.cpp | 61 + gfx/layers/client/ClientContainerLayer.cpp | 31 + gfx/layers/client/ClientContainerLayer.h | 162 + gfx/layers/client/ClientImageLayer.cpp | 152 + gfx/layers/client/ClientLayerManager.cpp | 903 +++ gfx/layers/client/ClientLayerManager.h | 413 ++ gfx/layers/client/ClientPaintedLayer.cpp | 210 + gfx/layers/client/ClientPaintedLayer.h | 123 + gfx/layers/client/ClientReadbackLayer.h | 31 + gfx/layers/client/ClientTiledPaintedLayer.cpp | 653 ++ gfx/layers/client/ClientTiledPaintedLayer.h | 150 + gfx/layers/client/CompositableClient.cpp | 182 + gfx/layers/client/CompositableClient.h | 213 + gfx/layers/client/ContentClient.cpp | 881 +++ gfx/layers/client/ContentClient.h | 362 + gfx/layers/client/GPUVideoTextureClient.cpp | 58 + gfx/layers/client/GPUVideoTextureClient.h | 55 + gfx/layers/client/ImageClient.cpp | 306 + gfx/layers/client/ImageClient.h | 143 + gfx/layers/client/MultiTiledContentClient.cpp | 685 ++ gfx/layers/client/MultiTiledContentClient.h | 199 + gfx/layers/client/SingleTiledContentClient.cpp | 266 + gfx/layers/client/SingleTiledContentClient.h | 128 + gfx/layers/client/TextureClient.cpp | 1934 +++++ gfx/layers/client/TextureClient.h | 932 +++ gfx/layers/client/TextureClientPool.cpp | 314 + gfx/layers/client/TextureClientPool.h | 175 + .../client/TextureClientRecycleAllocator.cpp | 246 + gfx/layers/client/TextureClientRecycleAllocator.h | 133 + gfx/layers/client/TextureClientSharedSurface.cpp | 177 + gfx/layers/client/TextureClientSharedSurface.h | 85 + gfx/layers/client/TextureRecorded.cpp | 105 + gfx/layers/client/TextureRecorded.h | 57 + gfx/layers/client/TiledContentClient.cpp | 734 ++ gfx/layers/client/TiledContentClient.h | 406 + gfx/layers/composite/AsyncCompositionManager.cpp | 1314 ++++ gfx/layers/composite/AsyncCompositionManager.h | 273 + gfx/layers/composite/CanvasLayerComposite.cpp | 141 + gfx/layers/composite/CanvasLayerComposite.h | 72 + gfx/layers/composite/ColorLayerComposite.cpp | 63 + gfx/layers/composite/ColorLayerComposite.h | 62 + gfx/layers/composite/CompositableHost.cpp | 167 + gfx/layers/composite/CompositableHost.h | 278 + gfx/layers/composite/ConsolasFontData.h | 457 ++ gfx/layers/composite/ContainerLayerComposite.cpp | 820 ++ gfx/layers/composite/ContainerLayerComposite.h | 197 + gfx/layers/composite/ContentHost.cpp | 460 ++ gfx/layers/composite/ContentHost.h | 236 + gfx/layers/composite/Diagnostics.cpp | 96 + gfx/layers/composite/Diagnostics.h | 100 + gfx/layers/composite/FPSCounter.cpp | 343 + gfx/layers/composite/FPSCounter.h | 102 + gfx/layers/composite/FontData.h | 304 + gfx/layers/composite/FrameUniformityData.cpp | 145 + gfx/layers/composite/FrameUniformityData.h | 80 + gfx/layers/composite/GPUVideoTextureHost.cpp | 295 + gfx/layers/composite/GPUVideoTextureHost.h | 98 + gfx/layers/composite/ImageComposite.cpp | 385 + gfx/layers/composite/ImageComposite.h | 139 + gfx/layers/composite/ImageHost.cpp | 454 ++ gfx/layers/composite/ImageHost.h | 138 + gfx/layers/composite/ImageLayerComposite.cpp | 209 + gfx/layers/composite/ImageLayerComposite.h | 78 + gfx/layers/composite/LayerManagerComposite.cpp | 1780 +++++ gfx/layers/composite/LayerManagerComposite.h | 729 ++ gfx/layers/composite/LayerManagerCompositeUtils.h | 160 + gfx/layers/composite/PaintCounter.cpp | 79 + gfx/layers/composite/PaintCounter.h | 53 + gfx/layers/composite/PaintedLayerComposite.cpp | 169 + gfx/layers/composite/PaintedLayerComposite.h | 86 + gfx/layers/composite/TextRenderer.cpp | 245 + gfx/layers/composite/TextRenderer.h | 95 + gfx/layers/composite/TextureHost.cpp | 1371 ++++ gfx/layers/composite/TextureHost.h | 1082 +++ gfx/layers/composite/TiledContentHost.cpp | 658 ++ gfx/layers/composite/TiledContentHost.h | 263 + gfx/layers/composite/X11TextureHost.cpp | 103 + gfx/layers/composite/X11TextureHost.h | 65 + gfx/layers/d3d11/BlendShaderConstants.h | 65 + gfx/layers/d3d11/BlendingHelpers.hlslh | 184 + gfx/layers/d3d11/CompositorD3D11.cpp | 1794 +++++ gfx/layers/d3d11/CompositorD3D11.h | 303 + gfx/layers/d3d11/CompositorD3D11.hlsl | 503 ++ gfx/layers/d3d11/DeviceAttachmentsD3D11.cpp | 342 + gfx/layers/d3d11/DeviceAttachmentsD3D11.h | 116 + gfx/layers/d3d11/DiagnosticsD3D11.cpp | 83 + gfx/layers/d3d11/DiagnosticsD3D11.h | 51 + gfx/layers/d3d11/HelpersD3D11.h | 56 + gfx/layers/d3d11/MLGDeviceD3D11.cpp | 2034 +++++ gfx/layers/d3d11/MLGDeviceD3D11.h | 326 + gfx/layers/d3d11/ReadbackManagerD3D11.cpp | 153 + gfx/layers/d3d11/ReadbackManagerD3D11.h | 65 + gfx/layers/d3d11/ShaderDefinitionsD3D11.h | 40 + gfx/layers/d3d11/TextureD3D11.cpp | 1867 +++++ gfx/layers/d3d11/TextureD3D11.h | 600 ++ gfx/layers/d3d11/genshaders.py | 176 + gfx/layers/d3d11/mlgshaders/blend-common.hlsl | 15 + .../d3d11/mlgshaders/blend-ps-generated.hlslh | 540 ++ .../d3d11/mlgshaders/blend-ps-generated.hlslh.tpl | 36 + gfx/layers/d3d11/mlgshaders/blend-ps.hlsl | 13 + gfx/layers/d3d11/mlgshaders/blend-vs.hlsl | 52 + gfx/layers/d3d11/mlgshaders/clear-common.hlsl | 9 + gfx/layers/d3d11/mlgshaders/clear-ps.hlsl | 12 + gfx/layers/d3d11/mlgshaders/clear-vs.hlsl | 30 + gfx/layers/d3d11/mlgshaders/color-common.hlsl | 19 + gfx/layers/d3d11/mlgshaders/color-ps.hlsl | 20 + gfx/layers/d3d11/mlgshaders/color-vs.hlsl | 58 + gfx/layers/d3d11/mlgshaders/common-ps.hlsl | 37 + gfx/layers/d3d11/mlgshaders/common-vs.hlsl | 167 + gfx/layers/d3d11/mlgshaders/common.hlsl | 17 + .../d3d11/mlgshaders/component-alpha-ps.hlsl | 45 + .../d3d11/mlgshaders/diagnostics-common.hlsl | 10 + gfx/layers/d3d11/mlgshaders/diagnostics-ps.hlsl | 13 + gfx/layers/d3d11/mlgshaders/diagnostics-vs.hlsl | 25 + .../d3d11/mlgshaders/mask-combiner-common.hlsl | 10 + gfx/layers/d3d11/mlgshaders/mask-combiner-ps.hlsl | 16 + gfx/layers/d3d11/mlgshaders/mask-combiner-vs.hlsl | 26 + gfx/layers/d3d11/mlgshaders/shaders.manifest | 100 + gfx/layers/d3d11/mlgshaders/test-features-vs.hlsl | 32 + gfx/layers/d3d11/mlgshaders/textured-common.hlsl | 43 + gfx/layers/d3d11/mlgshaders/textured-ps.hlsl | 45 + gfx/layers/d3d11/mlgshaders/textured-vs.hlsl | 49 + gfx/layers/d3d11/mlgshaders/ycbcr-ps.hlsl | 118 + gfx/layers/d3d11/shaders.manifest | 28 + gfx/layers/ipc/APZCTreeManagerChild.cpp | 181 + gfx/layers/ipc/APZCTreeManagerChild.h | 99 + gfx/layers/ipc/APZCTreeManagerParent.cpp | 185 + gfx/layers/ipc/APZCTreeManagerParent.h | 78 + gfx/layers/ipc/APZChild.cpp | 105 + gfx/layers/ipc/APZChild.h | 69 + gfx/layers/ipc/APZInputBridgeChild.cpp | 124 + gfx/layers/ipc/APZInputBridgeChild.h | 44 + gfx/layers/ipc/APZInputBridgeParent.cpp | 128 + gfx/layers/ipc/APZInputBridgeParent.h | 71 + gfx/layers/ipc/CanvasChild.cpp | 325 + gfx/layers/ipc/CanvasChild.h | 151 + gfx/layers/ipc/CanvasThread.cpp | 138 + gfx/layers/ipc/CanvasThread.h | 97 + gfx/layers/ipc/CanvasTranslator.cpp | 534 ++ gfx/layers/ipc/CanvasTranslator.h | 284 + gfx/layers/ipc/CompositableForwarder.cpp | 15 + gfx/layers/ipc/CompositableForwarder.h | 125 + gfx/layers/ipc/CompositableTransactionParent.cpp | 337 + gfx/layers/ipc/CompositableTransactionParent.h | 65 + gfx/layers/ipc/CompositorBench.cpp | 352 + gfx/layers/ipc/CompositorBench.h | 30 + gfx/layers/ipc/CompositorBridgeChild.cpp | 1310 ++++ gfx/layers/ipc/CompositorBridgeChild.h | 427 ++ gfx/layers/ipc/CompositorBridgeParent.cpp | 2990 ++++++++ gfx/layers/ipc/CompositorBridgeParent.h | 904 +++ gfx/layers/ipc/CompositorManagerChild.cpp | 256 + gfx/layers/ipc/CompositorManagerChild.h | 109 + gfx/layers/ipc/CompositorManagerParent.cpp | 335 + gfx/layers/ipc/CompositorManagerParent.h | 88 + gfx/layers/ipc/CompositorThread.cpp | 159 + gfx/layers/ipc/CompositorThread.h | 62 + gfx/layers/ipc/CompositorVsyncScheduler.cpp | 365 + gfx/layers/ipc/CompositorVsyncScheduler.h | 173 + gfx/layers/ipc/CompositorVsyncSchedulerOwner.h | 32 + gfx/layers/ipc/ContentCompositorBridgeParent.cpp | 753 ++ gfx/layers/ipc/ContentCompositorBridgeParent.h | 246 + gfx/layers/ipc/ISurfaceAllocator.cpp | 239 + gfx/layers/ipc/ISurfaceAllocator.h | 287 + gfx/layers/ipc/ImageBridgeChild.cpp | 995 +++ gfx/layers/ipc/ImageBridgeChild.h | 390 + gfx/layers/ipc/ImageBridgeParent.cpp | 796 ++ gfx/layers/ipc/ImageBridgeParent.h | 187 + gfx/layers/ipc/KnowsCompositor.h | 237 + gfx/layers/ipc/LayerAnimationUtils.cpp | 40 + gfx/layers/ipc/LayerAnimationUtils.h | 29 + gfx/layers/ipc/LayerTransactionChild.cpp | 37 + gfx/layers/ipc/LayerTransactionChild.h | 74 + gfx/layers/ipc/LayerTransactionParent.cpp | 1020 +++ gfx/layers/ipc/LayerTransactionParent.h | 236 + gfx/layers/ipc/LayerTreeOwnerTracker.cpp | 68 + gfx/layers/ipc/LayerTreeOwnerTracker.h | 73 + gfx/layers/ipc/LayersMessageUtils.h | 980 +++ gfx/layers/ipc/LayersMessages.ipdlh | 573 ++ gfx/layers/ipc/LayersSurfaces.ipdlh | 204 + gfx/layers/ipc/PAPZ.ipdl | 78 + gfx/layers/ipc/PAPZCTreeManager.ipdl | 86 + gfx/layers/ipc/PAPZInputBridge.ipdl | 82 + gfx/layers/ipc/PCanvas.ipdl | 50 + gfx/layers/ipc/PCompositorBridge.ipdl | 310 + gfx/layers/ipc/PCompositorBridgeTypes.ipdlh | 20 + gfx/layers/ipc/PCompositorManager.ipdl | 92 + gfx/layers/ipc/PImageBridge.ipdl | 71 + gfx/layers/ipc/PLayerTransaction.ipdl | 130 + gfx/layers/ipc/PTexture.ipdl | 40 + gfx/layers/ipc/PUiCompositorController.ipdl | 46 + gfx/layers/ipc/PVideoBridge.ipdl | 33 + gfx/layers/ipc/PWebRenderBridge.ipdl | 106 + gfx/layers/ipc/RefCountedShmem.cpp | 94 + gfx/layers/ipc/RefCountedShmem.h | 48 + gfx/layers/ipc/RemoteContentController.cpp | 425 ++ gfx/layers/ipc/RemoteContentController.h | 115 + gfx/layers/ipc/ShadowLayerUtils.h | 42 + gfx/layers/ipc/ShadowLayerUtilsMac.cpp | 31 + gfx/layers/ipc/ShadowLayerUtilsX11.cpp | 153 + gfx/layers/ipc/ShadowLayerUtilsX11.h | 28 + gfx/layers/ipc/ShadowLayers.cpp | 1074 +++ gfx/layers/ipc/ShadowLayers.h | 481 ++ gfx/layers/ipc/SharedPlanarYCbCrImage.cpp | 179 + gfx/layers/ipc/SharedPlanarYCbCrImage.h | 63 + gfx/layers/ipc/SharedRGBImage.cpp | 156 + gfx/layers/ipc/SharedRGBImage.h | 61 + gfx/layers/ipc/SharedSurfacesChild.cpp | 657 ++ gfx/layers/ipc/SharedSurfacesChild.h | 267 + gfx/layers/ipc/SharedSurfacesMemoryReport.h | 60 + gfx/layers/ipc/SharedSurfacesParent.cpp | 257 + gfx/layers/ipc/SharedSurfacesParent.h | 82 + gfx/layers/ipc/SurfaceDescriptor.h | 71 + gfx/layers/ipc/SynchronousTask.h | 55 + gfx/layers/ipc/TextureForwarder.h | 91 + gfx/layers/ipc/UiCompositorControllerChild.cpp | 322 + gfx/layers/ipc/UiCompositorControllerChild.h | 87 + .../ipc/UiCompositorControllerMessageTypes.h | 32 + gfx/layers/ipc/UiCompositorControllerParent.cpp | 305 + gfx/layers/ipc/UiCompositorControllerParent.h | 84 + gfx/layers/ipc/VideoBridgeChild.cpp | 184 + gfx/layers/ipc/VideoBridgeChild.h | 95 + gfx/layers/ipc/VideoBridgeParent.cpp | 155 + gfx/layers/ipc/VideoBridgeParent.h | 82 + gfx/layers/ipc/VideoBridgeUtils.h | 37 + gfx/layers/ipc/WebRenderMessages.ipdlh | 213 + .../compositor_manager_parent_ipc_libfuzz.cpp | 37 + gfx/layers/ipc/fuzztest/moz.build | 16 + gfx/layers/layerviewer/hide.png | Bin 0 -> 3079 bytes gfx/layers/layerviewer/index.html | 51 + gfx/layers/layerviewer/layerTreeView.js | 972 +++ gfx/layers/layerviewer/noise.png | Bin 0 -> 2118 bytes gfx/layers/layerviewer/show.png | Bin 0 -> 3187 bytes gfx/layers/layerviewer/tree.css | 39 + gfx/layers/mlgpu/BufferCache.cpp | 96 + gfx/layers/mlgpu/BufferCache.h | 82 + gfx/layers/mlgpu/CanvasLayerMLGPU.cpp | 81 + gfx/layers/mlgpu/CanvasLayerMLGPU.h | 57 + gfx/layers/mlgpu/ClearRegionHelper.h | 30 + gfx/layers/mlgpu/ContainerLayerMLGPU.cpp | 242 + gfx/layers/mlgpu/ContainerLayerMLGPU.h | 96 + gfx/layers/mlgpu/FrameBuilder.cpp | 412 + gfx/layers/mlgpu/FrameBuilder.h | 126 + gfx/layers/mlgpu/ImageLayerMLGPU.cpp | 108 + gfx/layers/mlgpu/ImageLayerMLGPU.h | 51 + gfx/layers/mlgpu/LayerMLGPU.cpp | 141 + gfx/layers/mlgpu/LayerMLGPU.h | 161 + gfx/layers/mlgpu/LayerManagerMLGPU.cpp | 588 ++ gfx/layers/mlgpu/LayerManagerMLGPU.h | 145 + gfx/layers/mlgpu/MLGDevice.cpp | 348 + gfx/layers/mlgpu/MLGDevice.h | 481 ++ gfx/layers/mlgpu/MLGDeviceTypes.h | 102 + gfx/layers/mlgpu/MLGPUScreenshotGrabber.cpp | 336 + gfx/layers/mlgpu/MLGPUScreenshotGrabber.h | 59 + gfx/layers/mlgpu/MaskOperation.cpp | 173 + gfx/layers/mlgpu/MaskOperation.h | 89 + gfx/layers/mlgpu/MemoryReportingMLGPU.cpp | 54 + gfx/layers/mlgpu/MemoryReportingMLGPU.h | 26 + gfx/layers/mlgpu/PaintedLayerMLGPU.cpp | 219 + gfx/layers/mlgpu/PaintedLayerMLGPU.h | 100 + gfx/layers/mlgpu/RenderPassMLGPU-inl.h | 67 + gfx/layers/mlgpu/RenderPassMLGPU.cpp | 971 +++ gfx/layers/mlgpu/RenderPassMLGPU.h | 439 ++ gfx/layers/mlgpu/RenderViewMLGPU.cpp | 549 ++ gfx/layers/mlgpu/RenderViewMLGPU.h | 136 + gfx/layers/mlgpu/ShaderDefinitionsMLGPU-inl.h | 79 + gfx/layers/mlgpu/ShaderDefinitionsMLGPU.h | 195 + gfx/layers/mlgpu/SharedBufferMLGPU.cpp | 275 + gfx/layers/mlgpu/SharedBufferMLGPU.h | 273 + gfx/layers/mlgpu/StagingBuffer.cpp | 18 + gfx/layers/mlgpu/StagingBuffer.h | 271 + gfx/layers/mlgpu/TextureSourceProviderMLGPU.cpp | 96 + gfx/layers/mlgpu/TextureSourceProviderMLGPU.h | 56 + gfx/layers/mlgpu/TexturedLayerMLGPU.cpp | 196 + gfx/layers/mlgpu/TexturedLayerMLGPU.h | 90 + gfx/layers/mlgpu/UtilityMLGPU.h | 45 + gfx/layers/moz.build | 664 ++ gfx/layers/opengl/CompositingRenderTargetOGL.cpp | 117 + gfx/layers/opengl/CompositingRenderTargetOGL.h | 212 + gfx/layers/opengl/CompositorOGL.cpp | 2403 ++++++ gfx/layers/opengl/CompositorOGL.h | 529 ++ gfx/layers/opengl/DMABUFTextureClientOGL.cpp | 116 + gfx/layers/opengl/DMABUFTextureClientOGL.h | 64 + gfx/layers/opengl/DMABUFTextureHostOGL.cpp | 233 + gfx/layers/opengl/DMABUFTextureHostOGL.h | 81 + gfx/layers/opengl/EGLImageHelpers.cpp | 58 + gfx/layers/opengl/EGLImageHelpers.h | 28 + gfx/layers/opengl/GLBlitTextureImageHelper.cpp | 283 + gfx/layers/opengl/GLBlitTextureImageHelper.h | 70 + gfx/layers/opengl/MacIOSurfaceTextureClientOGL.cpp | 121 + gfx/layers/opengl/MacIOSurfaceTextureClientOGL.h | 60 + gfx/layers/opengl/MacIOSurfaceTextureHostOGL.cpp | 268 + gfx/layers/opengl/MacIOSurfaceTextureHostOGL.h | 95 + gfx/layers/opengl/OGLShaderConfig.h | 267 + gfx/layers/opengl/OGLShaderProgram.cpp | 1044 +++ gfx/layers/opengl/OGLShaderProgram.h | 412 + gfx/layers/opengl/TextureClientOGL.cpp | 352 + gfx/layers/opengl/TextureClientOGL.h | 161 + gfx/layers/opengl/TextureHostOGL.cpp | 1277 ++++ gfx/layers/opengl/TextureHostOGL.h | 653 ++ gfx/layers/opengl/X11TextureSourceOGL.cpp | 89 + gfx/layers/opengl/X11TextureSourceOGL.h | 64 + gfx/layers/protobuf/LayerScopePacket.pb.cc | 7236 ++++++++++++++++++ gfx/layers/protobuf/LayerScopePacket.pb.h | 7833 ++++++++++++++++++++ gfx/layers/protobuf/LayerScopePacket.proto | 221 + gfx/layers/wr/AsyncImagePipelineManager.cpp | 746 ++ gfx/layers/wr/AsyncImagePipelineManager.h | 276 + gfx/layers/wr/ClipManager.cpp | 424 ++ gfx/layers/wr/ClipManager.h | 150 + gfx/layers/wr/DisplayItemCache.cpp | 197 + gfx/layers/wr/DisplayItemCache.h | 210 + gfx/layers/wr/IpcResourceUpdateQueue.cpp | 498 ++ gfx/layers/wr/IpcResourceUpdateQueue.h | 201 + gfx/layers/wr/OMTAController.cpp | 40 + gfx/layers/wr/OMTAController.h | 44 + gfx/layers/wr/OMTASampler.cpp | 243 + gfx/layers/wr/OMTASampler.h | 151 + gfx/layers/wr/RenderRootStateManager.cpp | 213 + gfx/layers/wr/RenderRootStateManager.h | 100 + gfx/layers/wr/RenderRootTypes.cpp | 108 + gfx/layers/wr/RenderRootTypes.h | 71 + gfx/layers/wr/StackingContextHelper.cpp | 150 + gfx/layers/wr/StackingContextHelper.h | 130 + gfx/layers/wr/WebRenderBridgeChild.cpp | 615 ++ gfx/layers/wr/WebRenderBridgeChild.h | 262 + gfx/layers/wr/WebRenderBridgeParent.cpp | 2596 +++++++ gfx/layers/wr/WebRenderBridgeParent.h | 535 ++ gfx/layers/wr/WebRenderCanvasRenderer.cpp | 81 + gfx/layers/wr/WebRenderCanvasRenderer.h | 54 + gfx/layers/wr/WebRenderCommandBuilder.cpp | 2689 +++++++ gfx/layers/wr/WebRenderCommandBuilder.h | 215 + gfx/layers/wr/WebRenderDrawEventRecorder.cpp | 33 + gfx/layers/wr/WebRenderDrawEventRecorder.h | 49 + gfx/layers/wr/WebRenderImageHost.cpp | 281 + gfx/layers/wr/WebRenderImageHost.h | 102 + gfx/layers/wr/WebRenderLayerManager.cpp | 772 ++ gfx/layers/wr/WebRenderLayerManager.h | 247 + gfx/layers/wr/WebRenderMessageUtils.h | 150 + gfx/layers/wr/WebRenderScrollData.cpp | 371 + gfx/layers/wr/WebRenderScrollData.h | 322 + gfx/layers/wr/WebRenderScrollDataWrapper.h | 424 ++ gfx/layers/wr/WebRenderTextureHost.cpp | 253 + gfx/layers/wr/WebRenderTextureHost.h | 111 + gfx/layers/wr/WebRenderUserData.cpp | 432 ++ gfx/layers/wr/WebRenderUserData.h | 352 + 860 files changed, 208213 insertions(+) create mode 100644 gfx/layers/AndroidHardwareBuffer.cpp create mode 100644 gfx/layers/AndroidHardwareBuffer.h create mode 100644 gfx/layers/AnimationHelper.cpp create mode 100644 gfx/layers/AnimationHelper.h create mode 100644 gfx/layers/AnimationInfo.cpp create mode 100644 gfx/layers/AnimationInfo.h create mode 100644 gfx/layers/AnimationStorageData.h create mode 100644 gfx/layers/AtomicRefCountedWithFinalize.h create mode 100644 gfx/layers/AxisPhysicsMSDModel.cpp create mode 100644 gfx/layers/AxisPhysicsMSDModel.h create mode 100644 gfx/layers/AxisPhysicsModel.cpp create mode 100644 gfx/layers/AxisPhysicsModel.h create mode 100644 gfx/layers/BSPTree.cpp create mode 100644 gfx/layers/BSPTree.h create mode 100644 gfx/layers/BufferTexture.cpp create mode 100644 gfx/layers/BufferTexture.h create mode 100644 gfx/layers/BuildConstants.h create mode 100644 gfx/layers/CanvasDrawEventRecorder.cpp create mode 100644 gfx/layers/CanvasDrawEventRecorder.h create mode 100644 gfx/layers/CanvasRenderer.cpp create mode 100644 gfx/layers/CanvasRenderer.h create mode 100644 gfx/layers/CompositionRecorder.cpp create mode 100644 gfx/layers/CompositionRecorder.h create mode 100644 gfx/layers/Compositor.cpp create mode 100644 gfx/layers/Compositor.h create mode 100644 gfx/layers/CompositorAnimationStorage.cpp create mode 100644 gfx/layers/CompositorAnimationStorage.h create mode 100644 gfx/layers/CompositorTypes.cpp create mode 100644 gfx/layers/CompositorTypes.h create mode 100644 gfx/layers/D3D11ShareHandleImage.cpp create mode 100644 gfx/layers/D3D11ShareHandleImage.h create mode 100644 gfx/layers/D3D11YCbCrImage.cpp create mode 100644 gfx/layers/D3D11YCbCrImage.h create mode 100644 gfx/layers/D3D9SurfaceImage.cpp create mode 100644 gfx/layers/D3D9SurfaceImage.h create mode 100644 gfx/layers/DMABUFSurfaceImage.cpp create mode 100644 gfx/layers/DMABUFSurfaceImage.h create mode 100644 gfx/layers/DirectedGraph.h create mode 100644 gfx/layers/DirectionUtils.h create mode 100644 gfx/layers/Effects.cpp create mode 100644 gfx/layers/Effects.h create mode 100644 gfx/layers/FrameMetrics.cpp create mode 100644 gfx/layers/FrameMetrics.h create mode 100644 gfx/layers/GLImages.cpp create mode 100644 gfx/layers/GLImages.h create mode 100644 gfx/layers/GPUVideoImage.h create mode 100644 gfx/layers/IMFYCbCrImage.cpp create mode 100644 gfx/layers/IMFYCbCrImage.h create mode 100644 gfx/layers/IPDLActor.h create mode 100644 gfx/layers/ImageContainer.cpp create mode 100644 gfx/layers/ImageContainer.h create mode 100644 gfx/layers/ImageDataSerializer.cpp create mode 100644 gfx/layers/ImageDataSerializer.h create mode 100644 gfx/layers/ImageLayers.cpp create mode 100644 gfx/layers/ImageLayers.h create mode 100644 gfx/layers/ImageTypes.h create mode 100644 gfx/layers/LayerAttributes.h create mode 100644 gfx/layers/LayerManager.cpp create mode 100644 gfx/layers/LayerManager.h create mode 100644 gfx/layers/LayerMetricsWrapper.h create mode 100644 gfx/layers/LayerScope.cpp create mode 100644 gfx/layers/LayerScope.h create mode 100644 gfx/layers/LayerSorter.cpp create mode 100644 gfx/layers/LayerSorter.h create mode 100644 gfx/layers/LayerTreeInvalidation.cpp create mode 100644 gfx/layers/LayerTreeInvalidation.h create mode 100644 gfx/layers/LayerUserData.h create mode 100644 gfx/layers/Layers.cpp create mode 100644 gfx/layers/Layers.h create mode 100644 gfx/layers/LayersHelpers.cpp create mode 100644 gfx/layers/LayersHelpers.h create mode 100644 gfx/layers/LayersTypes.cpp create mode 100644 gfx/layers/LayersTypes.h create mode 100644 gfx/layers/MacIOSurfaceHelpers.cpp create mode 100644 gfx/layers/MacIOSurfaceHelpers.h create mode 100644 gfx/layers/MacIOSurfaceImage.cpp create mode 100644 gfx/layers/MacIOSurfaceImage.h create mode 100644 gfx/layers/MemoryPressureObserver.cpp create mode 100644 gfx/layers/MemoryPressureObserver.h create mode 100644 gfx/layers/NativeLayer.h create mode 100644 gfx/layers/NativeLayerCA.h create mode 100644 gfx/layers/NativeLayerCA.mm create mode 100644 gfx/layers/OOPCanvasRenderer.h create mode 100644 gfx/layers/PaintThread.cpp create mode 100644 gfx/layers/PaintThread.h create mode 100644 gfx/layers/PersistentBufferProvider.cpp create mode 100644 gfx/layers/PersistentBufferProvider.h create mode 100644 gfx/layers/ProfilerScreenshots.cpp create mode 100644 gfx/layers/ProfilerScreenshots.h create mode 100644 gfx/layers/ReadbackLayer.h create mode 100644 gfx/layers/ReadbackProcessor.cpp create mode 100644 gfx/layers/ReadbackProcessor.h create mode 100644 gfx/layers/RecordedCanvasEventImpl.h create mode 100644 gfx/layers/RenderTrace.cpp create mode 100644 gfx/layers/RenderTrace.h create mode 100644 gfx/layers/RepaintRequest.cpp create mode 100644 gfx/layers/RepaintRequest.h create mode 100644 gfx/layers/RotatedBuffer.cpp create mode 100644 gfx/layers/RotatedBuffer.h create mode 100644 gfx/layers/SampleTime.cpp create mode 100644 gfx/layers/SampleTime.h create mode 100644 gfx/layers/ScreenshotGrabber.cpp create mode 100644 gfx/layers/ScreenshotGrabber.h create mode 100644 gfx/layers/ScrollableLayerGuid.cpp create mode 100644 gfx/layers/ScrollableLayerGuid.h create mode 100644 gfx/layers/ShareableCanvasRenderer.cpp create mode 100644 gfx/layers/ShareableCanvasRenderer.h create mode 100644 gfx/layers/SourceSurfaceSharedData.cpp create mode 100644 gfx/layers/SourceSurfaceSharedData.h create mode 100644 gfx/layers/SourceSurfaceVolatileData.cpp create mode 100644 gfx/layers/SourceSurfaceVolatileData.h create mode 100644 gfx/layers/SurfacePool.h create mode 100644 gfx/layers/SurfacePoolCA.h create mode 100644 gfx/layers/SurfacePoolCA.mm create mode 100644 gfx/layers/SyncObject.cpp create mode 100644 gfx/layers/SyncObject.h create mode 100644 gfx/layers/TextureDIB.cpp create mode 100644 gfx/layers/TextureDIB.h create mode 100644 gfx/layers/TextureSourceProvider.cpp create mode 100644 gfx/layers/TextureSourceProvider.h create mode 100644 gfx/layers/TextureSync.cpp create mode 100644 gfx/layers/TextureSync.h create mode 100644 gfx/layers/TextureWrapperImage.cpp create mode 100644 gfx/layers/TextureWrapperImage.h create mode 100644 gfx/layers/TiledLayerBuffer.h create mode 100644 gfx/layers/TransactionIdAllocator.h create mode 100644 gfx/layers/TreeTraversal.h create mode 100644 gfx/layers/UpdateImageHelper.h create mode 100644 gfx/layers/ZoomConstraints.cpp create mode 100644 gfx/layers/ZoomConstraints.h create mode 100644 gfx/layers/apz/public/APZInputBridge.h create mode 100644 gfx/layers/apz/public/APZPublicUtils.h create mode 100644 gfx/layers/apz/public/APZSampler.h create mode 100644 gfx/layers/apz/public/APZUpdater.h create mode 100644 gfx/layers/apz/public/CompositorController.h create mode 100644 gfx/layers/apz/public/GeckoContentController.h create mode 100644 gfx/layers/apz/public/GeckoContentControllerTypes.h create mode 100644 gfx/layers/apz/public/IAPZCTreeManager.h create mode 100644 gfx/layers/apz/public/MatrixMessage.h create mode 100644 gfx/layers/apz/public/MetricsSharingController.h create mode 100644 gfx/layers/apz/src/APZCTreeManager.cpp create mode 100644 gfx/layers/apz/src/APZCTreeManager.h create mode 100644 gfx/layers/apz/src/APZInputBridge.cpp create mode 100644 gfx/layers/apz/src/APZPublicUtils.cpp create mode 100644 gfx/layers/apz/src/APZSampler.cpp create mode 100644 gfx/layers/apz/src/APZUpdater.cpp create mode 100644 gfx/layers/apz/src/APZUtils.cpp create mode 100644 gfx/layers/apz/src/APZUtils.h create mode 100644 gfx/layers/apz/src/AndroidAPZ.cpp create mode 100644 gfx/layers/apz/src/AndroidAPZ.h create mode 100644 gfx/layers/apz/src/AndroidFlingPhysics.cpp create mode 100644 gfx/layers/apz/src/AndroidFlingPhysics.h create mode 100644 gfx/layers/apz/src/AndroidVelocityTracker.cpp create mode 100644 gfx/layers/apz/src/AndroidVelocityTracker.h create mode 100644 gfx/layers/apz/src/AsyncDragMetrics.h create mode 100644 gfx/layers/apz/src/AsyncPanZoomAnimation.h create mode 100644 gfx/layers/apz/src/AsyncPanZoomController.cpp create mode 100644 gfx/layers/apz/src/AsyncPanZoomController.h create mode 100644 gfx/layers/apz/src/AutoDirWheelDeltaAdjuster.h create mode 100644 gfx/layers/apz/src/AutoscrollAnimation.cpp create mode 100644 gfx/layers/apz/src/AutoscrollAnimation.h create mode 100644 gfx/layers/apz/src/Axis.cpp create mode 100644 gfx/layers/apz/src/Axis.h create mode 100644 gfx/layers/apz/src/CheckerboardEvent.cpp create mode 100644 gfx/layers/apz/src/CheckerboardEvent.h create mode 100644 gfx/layers/apz/src/DesktopFlingPhysics.h create mode 100644 gfx/layers/apz/src/DragTracker.cpp create mode 100644 gfx/layers/apz/src/DragTracker.h create mode 100644 gfx/layers/apz/src/ExpectedGeckoMetrics.cpp create mode 100644 gfx/layers/apz/src/ExpectedGeckoMetrics.h create mode 100644 gfx/layers/apz/src/FlingAccelerator.cpp create mode 100644 gfx/layers/apz/src/FlingAccelerator.h create mode 100644 gfx/layers/apz/src/FocusState.cpp create mode 100644 gfx/layers/apz/src/FocusState.h create mode 100644 gfx/layers/apz/src/FocusTarget.cpp create mode 100644 gfx/layers/apz/src/FocusTarget.h create mode 100644 gfx/layers/apz/src/GenericFlingAnimation.h create mode 100644 gfx/layers/apz/src/GenericScrollAnimation.cpp create mode 100644 gfx/layers/apz/src/GenericScrollAnimation.h create mode 100644 gfx/layers/apz/src/GestureEventListener.cpp create mode 100644 gfx/layers/apz/src/GestureEventListener.h create mode 100644 gfx/layers/apz/src/HitTestingTreeNode.cpp create mode 100644 gfx/layers/apz/src/HitTestingTreeNode.h create mode 100644 gfx/layers/apz/src/InputBlockState.cpp create mode 100644 gfx/layers/apz/src/InputBlockState.h create mode 100644 gfx/layers/apz/src/InputQueue.cpp create mode 100644 gfx/layers/apz/src/InputQueue.h create mode 100644 gfx/layers/apz/src/KeyboardMap.cpp create mode 100644 gfx/layers/apz/src/KeyboardMap.h create mode 100644 gfx/layers/apz/src/KeyboardScrollAction.cpp create mode 100644 gfx/layers/apz/src/KeyboardScrollAction.h create mode 100644 gfx/layers/apz/src/Overscroll.h create mode 100644 gfx/layers/apz/src/OverscrollHandoffState.cpp create mode 100644 gfx/layers/apz/src/OverscrollHandoffState.h create mode 100644 gfx/layers/apz/src/OvershootDetector.cpp create mode 100644 gfx/layers/apz/src/OvershootDetector.h create mode 100644 gfx/layers/apz/src/PotentialCheckerboardDurationTracker.cpp create mode 100644 gfx/layers/apz/src/PotentialCheckerboardDurationTracker.h create mode 100644 gfx/layers/apz/src/QueuedInput.cpp create mode 100644 gfx/layers/apz/src/QueuedInput.h create mode 100644 gfx/layers/apz/src/RecentEventsBuffer.h create mode 100644 gfx/layers/apz/src/SampledAPZCState.cpp create mode 100644 gfx/layers/apz/src/SampledAPZCState.h create mode 100644 gfx/layers/apz/src/SimpleVelocityTracker.cpp create mode 100644 gfx/layers/apz/src/SimpleVelocityTracker.h create mode 100644 gfx/layers/apz/src/SmoothMsdScrollAnimation.cpp create mode 100644 gfx/layers/apz/src/SmoothMsdScrollAnimation.h create mode 100644 gfx/layers/apz/src/SmoothScrollAnimation.cpp create mode 100644 gfx/layers/apz/src/SmoothScrollAnimation.h create mode 100644 gfx/layers/apz/src/WheelScrollAnimation.cpp create mode 100644 gfx/layers/apz/src/WheelScrollAnimation.h create mode 100644 gfx/layers/apz/test/gtest/APZCBasicTester.h create mode 100644 gfx/layers/apz/test/gtest/APZCTreeManagerTester.h create mode 100644 gfx/layers/apz/test/gtest/APZTestCommon.h create mode 100644 gfx/layers/apz/test/gtest/InputUtils.h create mode 100644 gfx/layers/apz/test/gtest/TestBasic.cpp create mode 100644 gfx/layers/apz/test/gtest/TestEventRegions.cpp create mode 100644 gfx/layers/apz/test/gtest/TestFlingAcceleration.cpp create mode 100644 gfx/layers/apz/test/gtest/TestGestureDetector.cpp create mode 100644 gfx/layers/apz/test/gtest/TestHitTesting.cpp create mode 100644 gfx/layers/apz/test/gtest/TestInputQueue.cpp create mode 100644 gfx/layers/apz/test/gtest/TestPanning.cpp create mode 100644 gfx/layers/apz/test/gtest/TestPinching.cpp create mode 100644 gfx/layers/apz/test/gtest/TestScrollHandoff.cpp create mode 100644 gfx/layers/apz/test/gtest/TestSnapping.cpp create mode 100644 gfx/layers/apz/test/gtest/TestSnappingOnMomentum.cpp create mode 100644 gfx/layers/apz/test/gtest/TestTreeManager.cpp create mode 100644 gfx/layers/apz/test/gtest/moz.build create mode 100644 gfx/layers/apz/test/gtest/mvm/TestMobileViewportManager.cpp create mode 100644 gfx/layers/apz/test/gtest/mvm/moz.build create mode 100644 gfx/layers/apz/test/mochitest/.eslintrc.js create mode 100644 gfx/layers/apz/test/mochitest/FissionTestHelperChild.jsm create mode 100644 gfx/layers/apz/test/mochitest/FissionTestHelperParent.jsm create mode 100644 gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js create mode 100644 gfx/layers/apz/test/mochitest/apz_test_utils.js create mode 100644 gfx/layers/apz/test/mochitest/browser.ini create mode 100644 gfx/layers/apz/test/mochitest/browser_test_background_tab_scroll.js create mode 100644 gfx/layers/apz/test/mochitest/browser_test_group_fission.js create mode 100644 gfx/layers/apz/test/mochitest/browser_test_reset_scaling_zoom.js create mode 100644 gfx/layers/apz/test/mochitest/browser_test_scrollbar_in_extension_popup_window.js create mode 100644 gfx/layers/apz/test/mochitest/browser_test_scrolling_in_extension_popup_window.js create mode 100644 gfx/layers/apz/test/mochitest/browser_test_select_zoom.js create mode 100644 gfx/layers/apz/test/mochitest/helper_background_tab_scroll.html create mode 100644 gfx/layers/apz/test/mochitest/helper_basic_doubletap_zoom.html create mode 100644 gfx/layers/apz/test/mochitest/helper_basic_onetouchpinch.html create mode 100644 gfx/layers/apz/test/mochitest/helper_basic_pan.html create mode 100644 gfx/layers/apz/test/mochitest/helper_basic_zoom.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1162771.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1271432.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1280013.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1285070.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1299195.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1326290.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1331693.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1346632.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1414336.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1462961.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1464568_opacity.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1464568_transform.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1473108.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1490393-2.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1490393.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1502010_unconsumed_pan.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1506497_touch_action_fixed_on_fixed.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1509575.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1544966_zoom_on_touch_action_none.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1550510.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1637113_main_thread_hit_test.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1637135_narrow_viewport.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1638441_fixed_pos_hit_test.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1638458_contextmenu.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1648491_no_pointercancel_with_dtc.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1662800.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1663731_no_pointercancel_on_second_touchstart.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1669625.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1674935.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1682170_pointercancel_on_touchaction_pinchzoom.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug982141.html create mode 100644 gfx/layers/apz/test/mochitest/helper_checkerboard_apzforcedisabled.html create mode 100644 gfx/layers/apz/test/mochitest/helper_checkerboard_no_multiplier.html create mode 100644 gfx/layers/apz/test/mochitest/helper_checkerboard_scrollinfo.html create mode 100644 gfx/layers/apz/test/mochitest/helper_checkerboard_zoom_during_load.html create mode 100644 gfx/layers/apz/test/mochitest/helper_checkerboard_zoomoverflowhidden.html create mode 100644 gfx/layers/apz/test/mochitest/helper_click.html create mode 100644 gfx/layers/apz/test/mochitest/helper_click_interrupt_animation.html create mode 100644 gfx/layers/apz/test/mochitest/helper_div_pan.html create mode 100644 gfx/layers/apz/test/mochitest/helper_drag_click.html create mode 100644 gfx/layers/apz/test/mochitest/helper_drag_root_scrollbar.html create mode 100644 gfx/layers/apz/test/mochitest/helper_drag_scroll.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fission_animation_styling_in_oopif.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fission_animation_styling_in_transformed_oopif.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fission_basic.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fission_empty.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fission_event_region_override.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fission_force_empty_hit_region.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fission_inactivescroller_under_oopif.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fission_scroll_oopif.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fission_tap.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fission_tap_in_nested_iframe_on_zoomed.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fission_tap_on_zoomed.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fission_touch.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fission_transforms.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fission_utils.js create mode 100644 gfx/layers/apz/test/mochitest/helper_fixed_pos_displayport.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fixed_position_scroll_hittest.html create mode 100644 gfx/layers/apz/test/mochitest/helper_fullscreen.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_backface_hidden.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_basic.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_checkerboard.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_clippath.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_clipped_fixed_modal.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_deep_scene_stack.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_fixed_in_scrolled_transform.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_float_bug1434846.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_float_bug1443518.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_hidden_inactive_scrollframe.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_hoisted_scrollinfo.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_nested_transforms_bug1459696.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_pointerevents_svg.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_spam.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_sticky_bug1478304.html create mode 100644 gfx/layers/apz/test/mochitest/helper_hittest_touchaction.html create mode 100644 gfx/layers/apz/test/mochitest/helper_horizontal_checkerboard.html create mode 100644 gfx/layers/apz/test/mochitest/helper_iframe1.html create mode 100644 gfx/layers/apz/test/mochitest/helper_iframe2.html create mode 100644 gfx/layers/apz/test/mochitest/helper_iframe_pan.html create mode 100644 gfx/layers/apz/test/mochitest/helper_iframe_textarea.html create mode 100644 gfx/layers/apz/test/mochitest/helper_key_scroll.html create mode 100644 gfx/layers/apz/test/mochitest/helper_long_tap.html create mode 100644 gfx/layers/apz/test/mochitest/helper_minimum_scale_1_0.html create mode 100644 gfx/layers/apz/test/mochitest/helper_no_scalable_with_initial_scale.html create mode 100644 gfx/layers/apz/test/mochitest/helper_onetouchpinch_nested.html create mode 100644 gfx/layers/apz/test/mochitest/helper_overflowhidden_zoom.html create mode 100644 gfx/layers/apz/test/mochitest/helper_override_root.html create mode 100644 gfx/layers/apz/test/mochitest/helper_override_subdoc.html create mode 100644 gfx/layers/apz/test/mochitest/helper_overscroll_behavior_bug1425573.html create mode 100644 gfx/layers/apz/test/mochitest/helper_overscroll_behavior_bug1425603.html create mode 100644 gfx/layers/apz/test/mochitest/helper_overscroll_behavior_bug1494440.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scroll_anchoring_smooth_scroll.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scroll_inactive_perspective.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scroll_inactive_zindex.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scroll_into_view_bug1516056.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scroll_into_view_bug1562757.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scroll_on_position_fixed.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scroll_over_scrollbar.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scroll_snap_no_valid_snap_position.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scroll_tables_perspective.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scrollbar_snap_bug1501062.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scrollby_bug1531796.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scrollframe_activation_on_load.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scrollto_tap.html create mode 100644 gfx/layers/apz/test/mochitest/helper_self_closer.html create mode 100644 gfx/layers/apz/test/mochitest/helper_smoothscroll_spam.html create mode 100644 gfx/layers/apz/test/mochitest/helper_smoothscroll_spam_interleaved.html create mode 100644 gfx/layers/apz/test/mochitest/helper_subframe_style.css create mode 100644 gfx/layers/apz/test/mochitest/helper_tall.html create mode 100644 gfx/layers/apz/test/mochitest/helper_tap.html create mode 100644 gfx/layers/apz/test/mochitest/helper_tap_default_passive.html create mode 100644 gfx/layers/apz/test/mochitest/helper_tap_fullzoom.html create mode 100644 gfx/layers/apz/test/mochitest/helper_tap_passive.html create mode 100644 gfx/layers/apz/test/mochitest/helper_test_reset_scaling_zoom.html create mode 100644 gfx/layers/apz/test/mochitest/helper_test_select_zoom.html create mode 100644 gfx/layers/apz/test/mochitest/helper_touch_action.html create mode 100644 gfx/layers/apz/test/mochitest/helper_touch_action_complex.html create mode 100644 gfx/layers/apz/test/mochitest/helper_touch_action_regions.html create mode 100644 gfx/layers/apz/test/mochitest/helper_touch_action_zero_opacity_bug1500864.html create mode 100644 gfx/layers/apz/test/mochitest/helper_visual_scrollbars_pagescroll.html create mode 100644 gfx/layers/apz/test/mochitest/helper_visual_smooth_scroll.html create mode 100644 gfx/layers/apz/test/mochitest/helper_visualscroll_clamp_restore.html create mode 100644 gfx/layers/apz/test/mochitest/helper_visualscroll_nonrcd_rsf.html create mode 100644 gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_iframe.html create mode 100644 gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_multiline.html create mode 100644 gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_scroll.html create mode 100644 gfx/layers/apz/test/mochitest/helper_zoom_keyboardscroll.html create mode 100644 gfx/layers/apz/test/mochitest/helper_zoom_out_clamped_scrollpos.html create mode 100644 gfx/layers/apz/test/mochitest/helper_zoom_out_with_mainthread_clamping.html create mode 100644 gfx/layers/apz/test/mochitest/helper_zoom_prevented.html create mode 100644 gfx/layers/apz/test/mochitest/helper_zoom_restore_position_tabswitch.html create mode 100644 gfx/layers/apz/test/mochitest/helper_zoom_with_dynamic_toolbar.html create mode 100644 gfx/layers/apz/test/mochitest/helper_zoomed_pan.html create mode 100644 gfx/layers/apz/test/mochitest/mochitest.ini create mode 100644 gfx/layers/apz/test/mochitest/test_bug1151667.html create mode 100644 gfx/layers/apz/test/mochitest/test_bug1253683.html create mode 100644 gfx/layers/apz/test/mochitest/test_bug1277814.html create mode 100644 gfx/layers/apz/test/mochitest/test_bug1304689-2.html create mode 100644 gfx/layers/apz/test/mochitest/test_bug1304689.html create mode 100644 gfx/layers/apz/test/mochitest/test_bug982141.html create mode 100644 gfx/layers/apz/test/mochitest/test_frame_reconstruction.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_bug1464568.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_checkerboarding.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_double_tap_zoom.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_fullscreen.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_hittest.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_keyboard.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_mainthread.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_minimum_scale_size.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_mouseevents.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_overrides.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_pointerevents.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_scroll_snap.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_scrollframe_activation.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_touchevents-2.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_touchevents-3.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_touchevents-4.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_touchevents-5.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_touchevents.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_wheelevents.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_zoom-2.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_zoom.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_zoomToFocusedInput.html create mode 100644 gfx/layers/apz/test/mochitest/test_interrupted_reflow.html create mode 100644 gfx/layers/apz/test/mochitest/test_layerization.html create mode 100644 gfx/layers/apz/test/mochitest/test_relative_update.html create mode 100644 gfx/layers/apz/test/mochitest/test_scroll_inactive_bug1190112.html create mode 100644 gfx/layers/apz/test/mochitest/test_scroll_inactive_flattened_frame.html create mode 100644 gfx/layers/apz/test/mochitest/test_scroll_subframe_scrollbar.html create mode 100644 gfx/layers/apz/test/mochitest/test_smoothness.html create mode 100644 gfx/layers/apz/test/mochitest/test_touch_listeners_impacting_wheel.html create mode 100644 gfx/layers/apz/test/mochitest/test_wheel_scroll.html create mode 100644 gfx/layers/apz/test/mochitest/test_wheel_transactions.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-h-ref.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-h-rtl-ref.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-h-rtl.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-h.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-v-ref.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-v-rtl-ref.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-v-rtl.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-v.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-vh-ref.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-vh-rtl-ref.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-vh-rtl.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-vh.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-zoom-1-ref.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-zoom-1.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-zoom-2-ref.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-zoom-2.html create mode 100644 gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping-ref.html create mode 100644 gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping.html create mode 100644 gfx/layers/apz/test/reftest/iframe-zoomed-child.html create mode 100644 gfx/layers/apz/test/reftest/iframe-zoomed-ref.html create mode 100644 gfx/layers/apz/test/reftest/iframe-zoomed.html create mode 100644 gfx/layers/apz/test/reftest/initial-scale-1-ref.html create mode 100644 gfx/layers/apz/test/reftest/initial-scale-1.html create mode 100644 gfx/layers/apz/test/reftest/pinch-zoom-position-fixed-ref.html create mode 100644 gfx/layers/apz/test/reftest/pinch-zoom-position-fixed.html create mode 100644 gfx/layers/apz/test/reftest/pinch-zoom-position-sticky-ref.html create mode 100644 gfx/layers/apz/test/reftest/pinch-zoom-position-sticky.html create mode 100644 gfx/layers/apz/test/reftest/reftest.list create mode 100644 gfx/layers/apz/test/reftest/root-scrollbars-1-ref.html create mode 100644 gfx/layers/apz/test/reftest/root-scrollbars-1.html create mode 100644 gfx/layers/apz/test/reftest/scaled-iframe-zoomed-ref.html create mode 100644 gfx/layers/apz/test/reftest/scaled-iframe-zoomed.html create mode 100644 gfx/layers/apz/test/reftest/scrollbar-zoom-resolution-1-ref.html create mode 100644 gfx/layers/apz/test/reftest/scrollbar-zoom-resolution-1.html create mode 100644 gfx/layers/apz/test/reftest/scrollbar-zoom-resolution-2-ref.html create mode 100644 gfx/layers/apz/test/reftest/scrollbar-zoom-resolution-2.html create mode 100644 gfx/layers/apz/testutil/APZTestData.cpp create mode 100644 gfx/layers/apz/testutil/APZTestData.h create mode 100644 gfx/layers/apz/util/APZCCallbackHelper.cpp create mode 100644 gfx/layers/apz/util/APZCCallbackHelper.h create mode 100644 gfx/layers/apz/util/APZEventState.cpp create mode 100644 gfx/layers/apz/util/APZEventState.h create mode 100644 gfx/layers/apz/util/APZThreadUtils.cpp create mode 100644 gfx/layers/apz/util/APZThreadUtils.h create mode 100644 gfx/layers/apz/util/ActiveElementManager.cpp create mode 100644 gfx/layers/apz/util/ActiveElementManager.h create mode 100644 gfx/layers/apz/util/CheckerboardReportService.cpp create mode 100644 gfx/layers/apz/util/CheckerboardReportService.h create mode 100644 gfx/layers/apz/util/ChromeProcessController.cpp create mode 100644 gfx/layers/apz/util/ChromeProcessController.h create mode 100644 gfx/layers/apz/util/ContentProcessController.cpp create mode 100644 gfx/layers/apz/util/ContentProcessController.h create mode 100644 gfx/layers/apz/util/DoubleTapToZoom.cpp create mode 100644 gfx/layers/apz/util/DoubleTapToZoom.h create mode 100644 gfx/layers/apz/util/InputAPZContext.cpp create mode 100644 gfx/layers/apz/util/InputAPZContext.h create mode 100644 gfx/layers/apz/util/ScrollInputMethods.h create mode 100644 gfx/layers/apz/util/ScrollLinkedEffectDetector.cpp create mode 100644 gfx/layers/apz/util/ScrollLinkedEffectDetector.h create mode 100644 gfx/layers/apz/util/TouchActionHelper.cpp create mode 100644 gfx/layers/apz/util/TouchActionHelper.h create mode 100644 gfx/layers/apz/util/TouchCounter.cpp create mode 100644 gfx/layers/apz/util/TouchCounter.h create mode 100644 gfx/layers/basic/AutoMaskData.h create mode 100644 gfx/layers/basic/BasicCanvasLayer.cpp create mode 100644 gfx/layers/basic/BasicCanvasLayer.h create mode 100644 gfx/layers/basic/BasicColorLayer.cpp create mode 100644 gfx/layers/basic/BasicCompositor.cpp create mode 100644 gfx/layers/basic/BasicCompositor.h create mode 100644 gfx/layers/basic/BasicContainerLayer.cpp create mode 100644 gfx/layers/basic/BasicContainerLayer.h create mode 100644 gfx/layers/basic/BasicImageLayer.cpp create mode 100644 gfx/layers/basic/BasicImages.cpp create mode 100644 gfx/layers/basic/BasicImplData.h create mode 100644 gfx/layers/basic/BasicLayerManager.cpp create mode 100644 gfx/layers/basic/BasicLayers.h create mode 100644 gfx/layers/basic/BasicLayersImpl.cpp create mode 100644 gfx/layers/basic/BasicLayersImpl.h create mode 100644 gfx/layers/basic/BasicPaintedLayer.cpp create mode 100644 gfx/layers/basic/BasicPaintedLayer.h create mode 100644 gfx/layers/basic/MacIOSurfaceTextureHostBasic.cpp create mode 100644 gfx/layers/basic/MacIOSurfaceTextureHostBasic.h create mode 100644 gfx/layers/basic/TextureClientX11.cpp create mode 100644 gfx/layers/basic/TextureClientX11.h create mode 100644 gfx/layers/basic/TextureHostBasic.cpp create mode 100644 gfx/layers/basic/TextureHostBasic.h create mode 100644 gfx/layers/basic/X11BasicCompositor.cpp create mode 100644 gfx/layers/basic/X11BasicCompositor.h create mode 100644 gfx/layers/basic/X11TextureSourceBasic.cpp create mode 100644 gfx/layers/basic/X11TextureSourceBasic.h create mode 100644 gfx/layers/client/CanvasClient.cpp create mode 100644 gfx/layers/client/CanvasClient.h create mode 100644 gfx/layers/client/ClientCanvasLayer.cpp create mode 100644 gfx/layers/client/ClientCanvasLayer.h create mode 100644 gfx/layers/client/ClientCanvasRenderer.cpp create mode 100644 gfx/layers/client/ClientCanvasRenderer.h create mode 100644 gfx/layers/client/ClientColorLayer.cpp create mode 100644 gfx/layers/client/ClientContainerLayer.cpp create mode 100644 gfx/layers/client/ClientContainerLayer.h create mode 100644 gfx/layers/client/ClientImageLayer.cpp create mode 100644 gfx/layers/client/ClientLayerManager.cpp create mode 100644 gfx/layers/client/ClientLayerManager.h create mode 100644 gfx/layers/client/ClientPaintedLayer.cpp create mode 100644 gfx/layers/client/ClientPaintedLayer.h create mode 100644 gfx/layers/client/ClientReadbackLayer.h create mode 100644 gfx/layers/client/ClientTiledPaintedLayer.cpp create mode 100644 gfx/layers/client/ClientTiledPaintedLayer.h create mode 100644 gfx/layers/client/CompositableClient.cpp create mode 100644 gfx/layers/client/CompositableClient.h create mode 100644 gfx/layers/client/ContentClient.cpp create mode 100644 gfx/layers/client/ContentClient.h create mode 100644 gfx/layers/client/GPUVideoTextureClient.cpp create mode 100644 gfx/layers/client/GPUVideoTextureClient.h create mode 100644 gfx/layers/client/ImageClient.cpp create mode 100644 gfx/layers/client/ImageClient.h create mode 100644 gfx/layers/client/MultiTiledContentClient.cpp create mode 100644 gfx/layers/client/MultiTiledContentClient.h create mode 100644 gfx/layers/client/SingleTiledContentClient.cpp create mode 100644 gfx/layers/client/SingleTiledContentClient.h create mode 100644 gfx/layers/client/TextureClient.cpp create mode 100644 gfx/layers/client/TextureClient.h create mode 100644 gfx/layers/client/TextureClientPool.cpp create mode 100644 gfx/layers/client/TextureClientPool.h create mode 100644 gfx/layers/client/TextureClientRecycleAllocator.cpp create mode 100644 gfx/layers/client/TextureClientRecycleAllocator.h create mode 100644 gfx/layers/client/TextureClientSharedSurface.cpp create mode 100644 gfx/layers/client/TextureClientSharedSurface.h create mode 100644 gfx/layers/client/TextureRecorded.cpp create mode 100644 gfx/layers/client/TextureRecorded.h create mode 100644 gfx/layers/client/TiledContentClient.cpp create mode 100644 gfx/layers/client/TiledContentClient.h create mode 100644 gfx/layers/composite/AsyncCompositionManager.cpp create mode 100644 gfx/layers/composite/AsyncCompositionManager.h create mode 100644 gfx/layers/composite/CanvasLayerComposite.cpp create mode 100644 gfx/layers/composite/CanvasLayerComposite.h create mode 100644 gfx/layers/composite/ColorLayerComposite.cpp create mode 100644 gfx/layers/composite/ColorLayerComposite.h create mode 100644 gfx/layers/composite/CompositableHost.cpp create mode 100644 gfx/layers/composite/CompositableHost.h create mode 100644 gfx/layers/composite/ConsolasFontData.h create mode 100644 gfx/layers/composite/ContainerLayerComposite.cpp create mode 100644 gfx/layers/composite/ContainerLayerComposite.h create mode 100644 gfx/layers/composite/ContentHost.cpp create mode 100644 gfx/layers/composite/ContentHost.h create mode 100644 gfx/layers/composite/Diagnostics.cpp create mode 100644 gfx/layers/composite/Diagnostics.h create mode 100644 gfx/layers/composite/FPSCounter.cpp create mode 100644 gfx/layers/composite/FPSCounter.h create mode 100644 gfx/layers/composite/FontData.h create mode 100644 gfx/layers/composite/FrameUniformityData.cpp create mode 100644 gfx/layers/composite/FrameUniformityData.h create mode 100644 gfx/layers/composite/GPUVideoTextureHost.cpp create mode 100644 gfx/layers/composite/GPUVideoTextureHost.h create mode 100644 gfx/layers/composite/ImageComposite.cpp create mode 100644 gfx/layers/composite/ImageComposite.h create mode 100644 gfx/layers/composite/ImageHost.cpp create mode 100644 gfx/layers/composite/ImageHost.h create mode 100644 gfx/layers/composite/ImageLayerComposite.cpp create mode 100644 gfx/layers/composite/ImageLayerComposite.h create mode 100644 gfx/layers/composite/LayerManagerComposite.cpp create mode 100644 gfx/layers/composite/LayerManagerComposite.h create mode 100644 gfx/layers/composite/LayerManagerCompositeUtils.h create mode 100644 gfx/layers/composite/PaintCounter.cpp create mode 100644 gfx/layers/composite/PaintCounter.h create mode 100644 gfx/layers/composite/PaintedLayerComposite.cpp create mode 100644 gfx/layers/composite/PaintedLayerComposite.h create mode 100644 gfx/layers/composite/TextRenderer.cpp create mode 100644 gfx/layers/composite/TextRenderer.h create mode 100644 gfx/layers/composite/TextureHost.cpp create mode 100644 gfx/layers/composite/TextureHost.h create mode 100644 gfx/layers/composite/TiledContentHost.cpp create mode 100644 gfx/layers/composite/TiledContentHost.h create mode 100644 gfx/layers/composite/X11TextureHost.cpp create mode 100644 gfx/layers/composite/X11TextureHost.h create mode 100644 gfx/layers/d3d11/BlendShaderConstants.h create mode 100644 gfx/layers/d3d11/BlendingHelpers.hlslh create mode 100644 gfx/layers/d3d11/CompositorD3D11.cpp create mode 100644 gfx/layers/d3d11/CompositorD3D11.h create mode 100644 gfx/layers/d3d11/CompositorD3D11.hlsl create mode 100644 gfx/layers/d3d11/DeviceAttachmentsD3D11.cpp create mode 100644 gfx/layers/d3d11/DeviceAttachmentsD3D11.h create mode 100644 gfx/layers/d3d11/DiagnosticsD3D11.cpp create mode 100644 gfx/layers/d3d11/DiagnosticsD3D11.h create mode 100644 gfx/layers/d3d11/HelpersD3D11.h create mode 100644 gfx/layers/d3d11/MLGDeviceD3D11.cpp create mode 100644 gfx/layers/d3d11/MLGDeviceD3D11.h create mode 100644 gfx/layers/d3d11/ReadbackManagerD3D11.cpp create mode 100644 gfx/layers/d3d11/ReadbackManagerD3D11.h create mode 100644 gfx/layers/d3d11/ShaderDefinitionsD3D11.h create mode 100644 gfx/layers/d3d11/TextureD3D11.cpp create mode 100644 gfx/layers/d3d11/TextureD3D11.h create mode 100644 gfx/layers/d3d11/genshaders.py create mode 100644 gfx/layers/d3d11/mlgshaders/blend-common.hlsl create mode 100644 gfx/layers/d3d11/mlgshaders/blend-ps-generated.hlslh create mode 100644 gfx/layers/d3d11/mlgshaders/blend-ps-generated.hlslh.tpl create mode 100644 gfx/layers/d3d11/mlgshaders/blend-ps.hlsl create mode 100644 gfx/layers/d3d11/mlgshaders/blend-vs.hlsl create mode 100644 gfx/layers/d3d11/mlgshaders/clear-common.hlsl create mode 100644 gfx/layers/d3d11/mlgshaders/clear-ps.hlsl create mode 100644 gfx/layers/d3d11/mlgshaders/clear-vs.hlsl create mode 100644 gfx/layers/d3d11/mlgshaders/color-common.hlsl create mode 100644 gfx/layers/d3d11/mlgshaders/color-ps.hlsl create mode 100644 gfx/layers/d3d11/mlgshaders/color-vs.hlsl create mode 100644 gfx/layers/d3d11/mlgshaders/common-ps.hlsl create mode 100644 gfx/layers/d3d11/mlgshaders/common-vs.hlsl create mode 100644 gfx/layers/d3d11/mlgshaders/common.hlsl create mode 100644 gfx/layers/d3d11/mlgshaders/component-alpha-ps.hlsl create mode 100644 gfx/layers/d3d11/mlgshaders/diagnostics-common.hlsl create mode 100644 gfx/layers/d3d11/mlgshaders/diagnostics-ps.hlsl create mode 100644 gfx/layers/d3d11/mlgshaders/diagnostics-vs.hlsl create mode 100644 gfx/layers/d3d11/mlgshaders/mask-combiner-common.hlsl create mode 100644 gfx/layers/d3d11/mlgshaders/mask-combiner-ps.hlsl create mode 100644 gfx/layers/d3d11/mlgshaders/mask-combiner-vs.hlsl create mode 100644 gfx/layers/d3d11/mlgshaders/shaders.manifest create mode 100644 gfx/layers/d3d11/mlgshaders/test-features-vs.hlsl create mode 100644 gfx/layers/d3d11/mlgshaders/textured-common.hlsl create mode 100644 gfx/layers/d3d11/mlgshaders/textured-ps.hlsl create mode 100644 gfx/layers/d3d11/mlgshaders/textured-vs.hlsl create mode 100644 gfx/layers/d3d11/mlgshaders/ycbcr-ps.hlsl create mode 100644 gfx/layers/d3d11/shaders.manifest create mode 100644 gfx/layers/ipc/APZCTreeManagerChild.cpp create mode 100644 gfx/layers/ipc/APZCTreeManagerChild.h create mode 100644 gfx/layers/ipc/APZCTreeManagerParent.cpp create mode 100644 gfx/layers/ipc/APZCTreeManagerParent.h create mode 100644 gfx/layers/ipc/APZChild.cpp create mode 100644 gfx/layers/ipc/APZChild.h create mode 100644 gfx/layers/ipc/APZInputBridgeChild.cpp create mode 100644 gfx/layers/ipc/APZInputBridgeChild.h create mode 100644 gfx/layers/ipc/APZInputBridgeParent.cpp create mode 100644 gfx/layers/ipc/APZInputBridgeParent.h create mode 100644 gfx/layers/ipc/CanvasChild.cpp create mode 100644 gfx/layers/ipc/CanvasChild.h create mode 100644 gfx/layers/ipc/CanvasThread.cpp create mode 100644 gfx/layers/ipc/CanvasThread.h create mode 100644 gfx/layers/ipc/CanvasTranslator.cpp create mode 100644 gfx/layers/ipc/CanvasTranslator.h create mode 100644 gfx/layers/ipc/CompositableForwarder.cpp create mode 100644 gfx/layers/ipc/CompositableForwarder.h create mode 100644 gfx/layers/ipc/CompositableTransactionParent.cpp create mode 100644 gfx/layers/ipc/CompositableTransactionParent.h create mode 100644 gfx/layers/ipc/CompositorBench.cpp create mode 100644 gfx/layers/ipc/CompositorBench.h create mode 100644 gfx/layers/ipc/CompositorBridgeChild.cpp create mode 100644 gfx/layers/ipc/CompositorBridgeChild.h create mode 100644 gfx/layers/ipc/CompositorBridgeParent.cpp create mode 100644 gfx/layers/ipc/CompositorBridgeParent.h create mode 100644 gfx/layers/ipc/CompositorManagerChild.cpp create mode 100644 gfx/layers/ipc/CompositorManagerChild.h create mode 100644 gfx/layers/ipc/CompositorManagerParent.cpp create mode 100644 gfx/layers/ipc/CompositorManagerParent.h create mode 100644 gfx/layers/ipc/CompositorThread.cpp create mode 100644 gfx/layers/ipc/CompositorThread.h create mode 100644 gfx/layers/ipc/CompositorVsyncScheduler.cpp create mode 100644 gfx/layers/ipc/CompositorVsyncScheduler.h create mode 100644 gfx/layers/ipc/CompositorVsyncSchedulerOwner.h create mode 100644 gfx/layers/ipc/ContentCompositorBridgeParent.cpp create mode 100644 gfx/layers/ipc/ContentCompositorBridgeParent.h create mode 100644 gfx/layers/ipc/ISurfaceAllocator.cpp create mode 100644 gfx/layers/ipc/ISurfaceAllocator.h create mode 100644 gfx/layers/ipc/ImageBridgeChild.cpp create mode 100644 gfx/layers/ipc/ImageBridgeChild.h create mode 100644 gfx/layers/ipc/ImageBridgeParent.cpp create mode 100644 gfx/layers/ipc/ImageBridgeParent.h create mode 100644 gfx/layers/ipc/KnowsCompositor.h create mode 100644 gfx/layers/ipc/LayerAnimationUtils.cpp create mode 100644 gfx/layers/ipc/LayerAnimationUtils.h create mode 100644 gfx/layers/ipc/LayerTransactionChild.cpp create mode 100644 gfx/layers/ipc/LayerTransactionChild.h create mode 100644 gfx/layers/ipc/LayerTransactionParent.cpp create mode 100644 gfx/layers/ipc/LayerTransactionParent.h create mode 100644 gfx/layers/ipc/LayerTreeOwnerTracker.cpp create mode 100644 gfx/layers/ipc/LayerTreeOwnerTracker.h create mode 100644 gfx/layers/ipc/LayersMessageUtils.h create mode 100644 gfx/layers/ipc/LayersMessages.ipdlh create mode 100644 gfx/layers/ipc/LayersSurfaces.ipdlh create mode 100644 gfx/layers/ipc/PAPZ.ipdl create mode 100644 gfx/layers/ipc/PAPZCTreeManager.ipdl create mode 100644 gfx/layers/ipc/PAPZInputBridge.ipdl create mode 100644 gfx/layers/ipc/PCanvas.ipdl create mode 100644 gfx/layers/ipc/PCompositorBridge.ipdl create mode 100644 gfx/layers/ipc/PCompositorBridgeTypes.ipdlh create mode 100644 gfx/layers/ipc/PCompositorManager.ipdl create mode 100644 gfx/layers/ipc/PImageBridge.ipdl create mode 100644 gfx/layers/ipc/PLayerTransaction.ipdl create mode 100644 gfx/layers/ipc/PTexture.ipdl create mode 100644 gfx/layers/ipc/PUiCompositorController.ipdl create mode 100644 gfx/layers/ipc/PVideoBridge.ipdl create mode 100644 gfx/layers/ipc/PWebRenderBridge.ipdl create mode 100644 gfx/layers/ipc/RefCountedShmem.cpp create mode 100644 gfx/layers/ipc/RefCountedShmem.h create mode 100644 gfx/layers/ipc/RemoteContentController.cpp create mode 100644 gfx/layers/ipc/RemoteContentController.h create mode 100644 gfx/layers/ipc/ShadowLayerUtils.h create mode 100644 gfx/layers/ipc/ShadowLayerUtilsMac.cpp create mode 100644 gfx/layers/ipc/ShadowLayerUtilsX11.cpp create mode 100644 gfx/layers/ipc/ShadowLayerUtilsX11.h create mode 100644 gfx/layers/ipc/ShadowLayers.cpp create mode 100644 gfx/layers/ipc/ShadowLayers.h create mode 100644 gfx/layers/ipc/SharedPlanarYCbCrImage.cpp create mode 100644 gfx/layers/ipc/SharedPlanarYCbCrImage.h create mode 100644 gfx/layers/ipc/SharedRGBImage.cpp create mode 100644 gfx/layers/ipc/SharedRGBImage.h create mode 100644 gfx/layers/ipc/SharedSurfacesChild.cpp create mode 100644 gfx/layers/ipc/SharedSurfacesChild.h create mode 100644 gfx/layers/ipc/SharedSurfacesMemoryReport.h create mode 100644 gfx/layers/ipc/SharedSurfacesParent.cpp create mode 100644 gfx/layers/ipc/SharedSurfacesParent.h create mode 100644 gfx/layers/ipc/SurfaceDescriptor.h create mode 100644 gfx/layers/ipc/SynchronousTask.h create mode 100644 gfx/layers/ipc/TextureForwarder.h create mode 100644 gfx/layers/ipc/UiCompositorControllerChild.cpp create mode 100644 gfx/layers/ipc/UiCompositorControllerChild.h create mode 100644 gfx/layers/ipc/UiCompositorControllerMessageTypes.h create mode 100644 gfx/layers/ipc/UiCompositorControllerParent.cpp create mode 100644 gfx/layers/ipc/UiCompositorControllerParent.h create mode 100644 gfx/layers/ipc/VideoBridgeChild.cpp create mode 100644 gfx/layers/ipc/VideoBridgeChild.h create mode 100644 gfx/layers/ipc/VideoBridgeParent.cpp create mode 100644 gfx/layers/ipc/VideoBridgeParent.h create mode 100644 gfx/layers/ipc/VideoBridgeUtils.h create mode 100644 gfx/layers/ipc/WebRenderMessages.ipdlh create mode 100644 gfx/layers/ipc/fuzztest/compositor_manager_parent_ipc_libfuzz.cpp create mode 100644 gfx/layers/ipc/fuzztest/moz.build create mode 100644 gfx/layers/layerviewer/hide.png create mode 100644 gfx/layers/layerviewer/index.html create mode 100644 gfx/layers/layerviewer/layerTreeView.js create mode 100644 gfx/layers/layerviewer/noise.png create mode 100644 gfx/layers/layerviewer/show.png create mode 100644 gfx/layers/layerviewer/tree.css create mode 100644 gfx/layers/mlgpu/BufferCache.cpp create mode 100644 gfx/layers/mlgpu/BufferCache.h create mode 100644 gfx/layers/mlgpu/CanvasLayerMLGPU.cpp create mode 100644 gfx/layers/mlgpu/CanvasLayerMLGPU.h create mode 100644 gfx/layers/mlgpu/ClearRegionHelper.h create mode 100644 gfx/layers/mlgpu/ContainerLayerMLGPU.cpp create mode 100644 gfx/layers/mlgpu/ContainerLayerMLGPU.h create mode 100644 gfx/layers/mlgpu/FrameBuilder.cpp create mode 100644 gfx/layers/mlgpu/FrameBuilder.h create mode 100644 gfx/layers/mlgpu/ImageLayerMLGPU.cpp create mode 100644 gfx/layers/mlgpu/ImageLayerMLGPU.h create mode 100644 gfx/layers/mlgpu/LayerMLGPU.cpp create mode 100644 gfx/layers/mlgpu/LayerMLGPU.h create mode 100644 gfx/layers/mlgpu/LayerManagerMLGPU.cpp create mode 100644 gfx/layers/mlgpu/LayerManagerMLGPU.h create mode 100644 gfx/layers/mlgpu/MLGDevice.cpp create mode 100644 gfx/layers/mlgpu/MLGDevice.h create mode 100644 gfx/layers/mlgpu/MLGDeviceTypes.h create mode 100644 gfx/layers/mlgpu/MLGPUScreenshotGrabber.cpp create mode 100644 gfx/layers/mlgpu/MLGPUScreenshotGrabber.h create mode 100644 gfx/layers/mlgpu/MaskOperation.cpp create mode 100644 gfx/layers/mlgpu/MaskOperation.h create mode 100644 gfx/layers/mlgpu/MemoryReportingMLGPU.cpp create mode 100644 gfx/layers/mlgpu/MemoryReportingMLGPU.h create mode 100644 gfx/layers/mlgpu/PaintedLayerMLGPU.cpp create mode 100644 gfx/layers/mlgpu/PaintedLayerMLGPU.h create mode 100644 gfx/layers/mlgpu/RenderPassMLGPU-inl.h create mode 100644 gfx/layers/mlgpu/RenderPassMLGPU.cpp create mode 100644 gfx/layers/mlgpu/RenderPassMLGPU.h create mode 100644 gfx/layers/mlgpu/RenderViewMLGPU.cpp create mode 100644 gfx/layers/mlgpu/RenderViewMLGPU.h create mode 100644 gfx/layers/mlgpu/ShaderDefinitionsMLGPU-inl.h create mode 100644 gfx/layers/mlgpu/ShaderDefinitionsMLGPU.h create mode 100644 gfx/layers/mlgpu/SharedBufferMLGPU.cpp create mode 100644 gfx/layers/mlgpu/SharedBufferMLGPU.h create mode 100644 gfx/layers/mlgpu/StagingBuffer.cpp create mode 100644 gfx/layers/mlgpu/StagingBuffer.h create mode 100644 gfx/layers/mlgpu/TextureSourceProviderMLGPU.cpp create mode 100644 gfx/layers/mlgpu/TextureSourceProviderMLGPU.h create mode 100644 gfx/layers/mlgpu/TexturedLayerMLGPU.cpp create mode 100644 gfx/layers/mlgpu/TexturedLayerMLGPU.h create mode 100644 gfx/layers/mlgpu/UtilityMLGPU.h create mode 100755 gfx/layers/moz.build create mode 100644 gfx/layers/opengl/CompositingRenderTargetOGL.cpp create mode 100644 gfx/layers/opengl/CompositingRenderTargetOGL.h create mode 100644 gfx/layers/opengl/CompositorOGL.cpp create mode 100644 gfx/layers/opengl/CompositorOGL.h create mode 100644 gfx/layers/opengl/DMABUFTextureClientOGL.cpp create mode 100644 gfx/layers/opengl/DMABUFTextureClientOGL.h create mode 100644 gfx/layers/opengl/DMABUFTextureHostOGL.cpp create mode 100644 gfx/layers/opengl/DMABUFTextureHostOGL.h create mode 100644 gfx/layers/opengl/EGLImageHelpers.cpp create mode 100644 gfx/layers/opengl/EGLImageHelpers.h create mode 100644 gfx/layers/opengl/GLBlitTextureImageHelper.cpp create mode 100644 gfx/layers/opengl/GLBlitTextureImageHelper.h create mode 100644 gfx/layers/opengl/MacIOSurfaceTextureClientOGL.cpp create mode 100644 gfx/layers/opengl/MacIOSurfaceTextureClientOGL.h create mode 100644 gfx/layers/opengl/MacIOSurfaceTextureHostOGL.cpp create mode 100644 gfx/layers/opengl/MacIOSurfaceTextureHostOGL.h create mode 100644 gfx/layers/opengl/OGLShaderConfig.h create mode 100644 gfx/layers/opengl/OGLShaderProgram.cpp create mode 100644 gfx/layers/opengl/OGLShaderProgram.h create mode 100644 gfx/layers/opengl/TextureClientOGL.cpp create mode 100644 gfx/layers/opengl/TextureClientOGL.h create mode 100644 gfx/layers/opengl/TextureHostOGL.cpp create mode 100644 gfx/layers/opengl/TextureHostOGL.h create mode 100644 gfx/layers/opengl/X11TextureSourceOGL.cpp create mode 100644 gfx/layers/opengl/X11TextureSourceOGL.h create mode 100644 gfx/layers/protobuf/LayerScopePacket.pb.cc create mode 100644 gfx/layers/protobuf/LayerScopePacket.pb.h create mode 100644 gfx/layers/protobuf/LayerScopePacket.proto create mode 100644 gfx/layers/wr/AsyncImagePipelineManager.cpp create mode 100644 gfx/layers/wr/AsyncImagePipelineManager.h create mode 100644 gfx/layers/wr/ClipManager.cpp create mode 100644 gfx/layers/wr/ClipManager.h create mode 100644 gfx/layers/wr/DisplayItemCache.cpp create mode 100644 gfx/layers/wr/DisplayItemCache.h create mode 100644 gfx/layers/wr/IpcResourceUpdateQueue.cpp create mode 100644 gfx/layers/wr/IpcResourceUpdateQueue.h create mode 100644 gfx/layers/wr/OMTAController.cpp create mode 100644 gfx/layers/wr/OMTAController.h create mode 100644 gfx/layers/wr/OMTASampler.cpp create mode 100644 gfx/layers/wr/OMTASampler.h create mode 100644 gfx/layers/wr/RenderRootStateManager.cpp create mode 100644 gfx/layers/wr/RenderRootStateManager.h create mode 100644 gfx/layers/wr/RenderRootTypes.cpp create mode 100644 gfx/layers/wr/RenderRootTypes.h create mode 100644 gfx/layers/wr/StackingContextHelper.cpp create mode 100644 gfx/layers/wr/StackingContextHelper.h create mode 100644 gfx/layers/wr/WebRenderBridgeChild.cpp create mode 100644 gfx/layers/wr/WebRenderBridgeChild.h create mode 100644 gfx/layers/wr/WebRenderBridgeParent.cpp create mode 100644 gfx/layers/wr/WebRenderBridgeParent.h create mode 100644 gfx/layers/wr/WebRenderCanvasRenderer.cpp create mode 100644 gfx/layers/wr/WebRenderCanvasRenderer.h create mode 100644 gfx/layers/wr/WebRenderCommandBuilder.cpp create mode 100644 gfx/layers/wr/WebRenderCommandBuilder.h create mode 100644 gfx/layers/wr/WebRenderDrawEventRecorder.cpp create mode 100644 gfx/layers/wr/WebRenderDrawEventRecorder.h create mode 100644 gfx/layers/wr/WebRenderImageHost.cpp create mode 100644 gfx/layers/wr/WebRenderImageHost.h create mode 100644 gfx/layers/wr/WebRenderLayerManager.cpp create mode 100644 gfx/layers/wr/WebRenderLayerManager.h create mode 100644 gfx/layers/wr/WebRenderMessageUtils.h create mode 100644 gfx/layers/wr/WebRenderScrollData.cpp create mode 100644 gfx/layers/wr/WebRenderScrollData.h create mode 100644 gfx/layers/wr/WebRenderScrollDataWrapper.h create mode 100644 gfx/layers/wr/WebRenderTextureHost.cpp create mode 100644 gfx/layers/wr/WebRenderTextureHost.h create mode 100644 gfx/layers/wr/WebRenderUserData.cpp create mode 100644 gfx/layers/wr/WebRenderUserData.h (limited to 'gfx/layers') diff --git a/gfx/layers/AndroidHardwareBuffer.cpp b/gfx/layers/AndroidHardwareBuffer.cpp new file mode 100644 index 0000000000..58a8b35c7e --- /dev/null +++ b/gfx/layers/AndroidHardwareBuffer.cpp @@ -0,0 +1,510 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AndroidHardwareBuffer.h" + +#include + +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/layers/ImageBridgeChild.h" +#include "mozilla/layers/TextureClientSharedSurface.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/UniquePtrExtensions.h" + +namespace mozilla { +namespace layers { + +static uint32_t ToAHardwareBuffer_Format(gfx::SurfaceFormat aFormat) { + switch (aFormat) { + case gfx::SurfaceFormat::R8G8B8A8: + case gfx::SurfaceFormat::B8G8R8A8: + return AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM; + + case gfx::SurfaceFormat::R8G8B8X8: + case gfx::SurfaceFormat::B8G8R8X8: + return AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM; + + case gfx::SurfaceFormat::R5G6B5_UINT16: + return AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM; + + default: + MOZ_ASSERT_UNREACHABLE("Unsupported SurfaceFormat"); + return AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM; + } +} + +StaticAutoPtr AndroidHardwareBufferApi::sInstance; + +/* static */ +void AndroidHardwareBufferApi::Init() { + sInstance = new AndroidHardwareBufferApi(); + if (!sInstance->Load()) { + sInstance = nullptr; + } +} + +/* static */ +void AndroidHardwareBufferApi::Shutdown() { sInstance = nullptr; } + +AndroidHardwareBufferApi::AndroidHardwareBufferApi() {} + +bool AndroidHardwareBufferApi::Load() { + void* handle = dlopen("libandroid.so", RTLD_LAZY | RTLD_LOCAL); + MOZ_ASSERT(handle); + if (!handle) { + gfxCriticalNote << "Failed to load libandroid.so"; + return false; + } + + mAHardwareBuffer_allocate = + (_AHardwareBuffer_allocate)dlsym(handle, "AHardwareBuffer_allocate"); + mAHardwareBuffer_acquire = + (_AHardwareBuffer_acquire)dlsym(handle, "AHardwareBuffer_acquire"); + mAHardwareBuffer_release = + (_AHardwareBuffer_release)dlsym(handle, "AHardwareBuffer_release"); + mAHardwareBuffer_describe = + (_AHardwareBuffer_describe)dlsym(handle, "AHardwareBuffer_describe"); + mAHardwareBuffer_lock = + (_AHardwareBuffer_lock)dlsym(handle, "AHardwareBuffer_lock"); + mAHardwareBuffer_unlock = + (_AHardwareBuffer_unlock)dlsym(handle, "AHardwareBuffer_unlock"); + mAHardwareBuffer_sendHandleToUnixSocket = + (_AHardwareBuffer_sendHandleToUnixSocket)dlsym( + handle, "AHardwareBuffer_sendHandleToUnixSocket"); + mAHardwareBuffer_recvHandleFromUnixSocket = + (_AHardwareBuffer_recvHandleFromUnixSocket)dlsym( + handle, "AHardwareBuffer_recvHandleFromUnixSocket"); + + if (!mAHardwareBuffer_allocate || !mAHardwareBuffer_acquire || + !mAHardwareBuffer_release || !mAHardwareBuffer_describe || + !mAHardwareBuffer_lock || !mAHardwareBuffer_unlock || + !mAHardwareBuffer_sendHandleToUnixSocket || + !mAHardwareBuffer_recvHandleFromUnixSocket) { + gfxCriticalNote << "Failed to load AHardwareBuffer"; + return false; + } + return true; +} + +void AndroidHardwareBufferApi::Allocate(const AHardwareBuffer_Desc* aDesc, + AHardwareBuffer** aOutBuffer) { + mAHardwareBuffer_allocate(aDesc, aOutBuffer); +} + +void AndroidHardwareBufferApi::Acquire(AHardwareBuffer* aBuffer) { + mAHardwareBuffer_acquire(aBuffer); +} + +void AndroidHardwareBufferApi::Release(AHardwareBuffer* aBuffer) { + mAHardwareBuffer_release(aBuffer); +} + +void AndroidHardwareBufferApi::Describe(const AHardwareBuffer* aBuffer, + AHardwareBuffer_Desc* aOutDesc) { + mAHardwareBuffer_describe(aBuffer, aOutDesc); +} + +int AndroidHardwareBufferApi::Lock(AHardwareBuffer* aBuffer, uint64_t aUsage, + int32_t aFence, const ARect* aRect, + void** aOutVirtualAddress) { + return mAHardwareBuffer_lock(aBuffer, aUsage, aFence, aRect, + aOutVirtualAddress); +} + +int AndroidHardwareBufferApi::Unlock(AHardwareBuffer* aBuffer, + int32_t* aFence) { + return mAHardwareBuffer_unlock(aBuffer, aFence); +} + +int AndroidHardwareBufferApi::SendHandleToUnixSocket( + const AHardwareBuffer* aBuffer, int aSocketFd) { + return mAHardwareBuffer_sendHandleToUnixSocket(aBuffer, aSocketFd); +} + +int AndroidHardwareBufferApi::RecvHandleFromUnixSocket( + int aSocketFd, AHardwareBuffer** aOutBuffer) { + return mAHardwareBuffer_recvHandleFromUnixSocket(aSocketFd, aOutBuffer); +} + +/* static */ +uint64_t AndroidHardwareBuffer::GetNextId() { + static std::atomic sNextId = 0; + uint64_t id = ++sNextId; + return id; +} + +/* static */ +already_AddRefed AndroidHardwareBuffer::Create( + gfx::IntSize aSize, gfx::SurfaceFormat aFormat) { + if (!AndroidHardwareBufferApi::Get()) { + return nullptr; + } + + if (aFormat != gfx::SurfaceFormat::R8G8B8A8 && + aFormat != gfx::SurfaceFormat::R8G8B8X8 && + aFormat != gfx::SurfaceFormat::B8G8R8A8 && + aFormat != gfx::SurfaceFormat::B8G8R8X8 && + aFormat != gfx::SurfaceFormat::R5G6B5_UINT16) { + return nullptr; + } + + AHardwareBuffer_Desc desc = {}; + desc.width = aSize.width; + desc.height = aSize.height; + desc.layers = 1; // number of images + desc.usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | + AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN | + AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE | + AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT; + desc.format = ToAHardwareBuffer_Format(aFormat); + + AHardwareBuffer* nativeBuffer = nullptr; + AndroidHardwareBufferApi::Get()->Allocate(&desc, &nativeBuffer); + if (!nativeBuffer) { + return nullptr; + } + + AHardwareBuffer_Desc bufferInfo = {}; + AndroidHardwareBufferApi::Get()->Describe(nativeBuffer, &bufferInfo); + + RefPtr buffer = new AndroidHardwareBuffer( + nativeBuffer, aSize, bufferInfo.stride, aFormat, GetNextId()); + AndroidHardwareBufferManager::Get()->Register(buffer); + return buffer.forget(); +} + +/* static */ +already_AddRefed +AndroidHardwareBuffer::FromFileDescriptor(ipc::FileDescriptor& aFileDescriptor, + uint64_t aBufferId, + gfx::IntSize aSize, + gfx::SurfaceFormat aFormat) { + if (!aFileDescriptor.IsValid()) { + gfxCriticalNote << "AndroidHardwareBuffer invalid FileDescriptor"; + return nullptr; + } + + ipc::FileDescriptor& fileDescriptor = + const_cast(aFileDescriptor); + auto rawFD = fileDescriptor.TakePlatformHandle(); + AHardwareBuffer* nativeBuffer = nullptr; + int ret = AndroidHardwareBufferApi::Get()->RecvHandleFromUnixSocket( + rawFD.get(), &nativeBuffer); + if (ret < 0) { + gfxCriticalNote << "RecvHandleFromUnixSocket failed"; + return nullptr; + } + + AHardwareBuffer_Desc bufferInfo = {}; + AndroidHardwareBufferApi::Get()->Describe(nativeBuffer, &bufferInfo); + + RefPtr buffer = new AndroidHardwareBuffer( + nativeBuffer, aSize, bufferInfo.stride, aFormat, aBufferId); + return buffer.forget(); +} + +AndroidHardwareBuffer::AndroidHardwareBuffer(AHardwareBuffer* aNativeBuffer, + gfx::IntSize aSize, + uint32_t aStride, + gfx::SurfaceFormat aFormat, + uint64_t aId) + : mSize(aSize), + mStride(aStride), + mFormat(aFormat), + mId(aId), + mNativeBuffer(aNativeBuffer), + mIsRegistered(false) { + MOZ_ASSERT(mNativeBuffer); +#ifdef DEBUG + AHardwareBuffer_Desc bufferInfo = {}; + AndroidHardwareBufferApi::Get()->Describe(mNativeBuffer, &bufferInfo); + MOZ_ASSERT(mSize.width == (int32_t)bufferInfo.width); + MOZ_ASSERT(mSize.height == (int32_t)bufferInfo.height); + MOZ_ASSERT(mStride == bufferInfo.stride); + MOZ_ASSERT(ToAHardwareBuffer_Format(mFormat) == bufferInfo.format); +#endif +} + +AndroidHardwareBuffer::~AndroidHardwareBuffer() { + if (mIsRegistered) { + AndroidHardwareBufferManager::Get()->Unregister(this); + } + AndroidHardwareBufferApi::Get()->Release(mNativeBuffer); +} + +int AndroidHardwareBuffer::Lock(uint64_t aUsage, const ARect* aRect, + void** aOutVirtualAddress) { + ipc::FileDescriptor fd = GetAndResetReleaseFence(); + int32_t fenceFd = -1; + ipc::FileDescriptor::UniquePlatformHandle rawFd; + if (fd.IsValid()) { + rawFd = fd.TakePlatformHandle(); + fenceFd = rawFd.get(); + } + return AndroidHardwareBufferApi::Get()->Lock(mNativeBuffer, aUsage, fenceFd, + aRect, aOutVirtualAddress); +} + +int AndroidHardwareBuffer::Unlock() { + int rawFd = -1; + // XXX All tested recent Android devices did not return valid fence. + int ret = AndroidHardwareBufferApi::Get()->Unlock(mNativeBuffer, &rawFd); + if (ret != 0) { + return ret; + } + + ipc::FileDescriptor acquireFenceFd; + // The value -1 indicates that unlocking has already completed before + // the function returned and no further operations are necessary. + if (rawFd >= 0) { + acquireFenceFd = ipc::FileDescriptor(UniqueFileHandle(rawFd)); + } + + if (acquireFenceFd.IsValid()) { + SetAcquireFence(std::move(acquireFenceFd)); + } + return 0; +} + +int AndroidHardwareBuffer::SendHandleToUnixSocket(int aSocketFd) { + return AndroidHardwareBufferApi::Get()->SendHandleToUnixSocket(mNativeBuffer, + aSocketFd); +} + +void AndroidHardwareBuffer::SetLastFwdTransactionId( + uint64_t aFwdTransactionId, bool aUsesImageBridge, + const MonitorAutoLock& aAutoLock) { + if (mTransactionId.isNothing()) { + mTransactionId = + Some(FwdTransactionId(aFwdTransactionId, aUsesImageBridge)); + return; + } + MOZ_RELEASE_ASSERT(mTransactionId.ref().mUsesImageBridge == aUsesImageBridge); + MOZ_RELEASE_ASSERT(mTransactionId.ref().mId <= aFwdTransactionId); + + mTransactionId.ref().mId = aFwdTransactionId; +} + +uint64_t AndroidHardwareBuffer::GetLastFwdTransactionId( + const MonitorAutoLock& aAutoLock) { + if (mTransactionId.isNothing()) { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + return 0; + } + return mTransactionId.ref().mId; +} + +void AndroidHardwareBuffer::SetReleaseFence(ipc::FileDescriptor&& aFenceFd) { + MonitorAutoLock lock(AndroidHardwareBufferManager::Get()->GetMonitor()); + SetReleaseFence(std::move(aFenceFd), lock); +} + +void AndroidHardwareBuffer::SetReleaseFence(ipc::FileDescriptor&& aFenceFd, + const MonitorAutoLock& aAutoLock) { + mReleaseFenceFd = std::move(aFenceFd); +} + +void AndroidHardwareBuffer::SetAcquireFence(ipc::FileDescriptor&& aFenceFd) { + MonitorAutoLock lock(AndroidHardwareBufferManager::Get()->GetMonitor()); + + mAcquireFenceFd = std::move(aFenceFd); +} + +ipc::FileDescriptor AndroidHardwareBuffer::GetAndResetReleaseFence() { + MonitorAutoLock lock(AndroidHardwareBufferManager::Get()->GetMonitor()); + + if (!mReleaseFenceFd.IsValid()) { + return ipc::FileDescriptor(); + } + + return std::move(mReleaseFenceFd); +} + +ipc::FileDescriptor AndroidHardwareBuffer::GetAndResetAcquireFence() { + MonitorAutoLock lock(AndroidHardwareBufferManager::Get()->GetMonitor()); + + if (!mAcquireFenceFd.IsValid()) { + return ipc::FileDescriptor(); + } + + return std::move(mAcquireFenceFd); +} + +ipc::FileDescriptor AndroidHardwareBuffer::GetAcquireFence() { + MonitorAutoLock lock(AndroidHardwareBufferManager::Get()->GetMonitor()); + + if (!mAcquireFenceFd.IsValid()) { + return ipc::FileDescriptor(); + } + + return mAcquireFenceFd; +} + +bool AndroidHardwareBuffer::WaitForBufferOwnership() { + return AndroidHardwareBufferManager::Get()->WaitForBufferOwnership(this); +} + +bool AndroidHardwareBuffer::IsWaitingForBufferOwnership() { + return AndroidHardwareBufferManager::Get()->IsWaitingForBufferOwnership(this); +} + +RefPtr +AndroidHardwareBuffer::GetTextureClientOfSharedSurfaceTextureData( + const layers::SurfaceDescriptor& aDesc, const gfx::SurfaceFormat aFormat, + const gfx::IntSize& aSize, const TextureFlags aFlags, + LayersIPCChannel* aAllocator) { + if (mTextureClientOfSharedSurfaceTextureData) { + return mTextureClientOfSharedSurfaceTextureData; + } + mTextureClientOfSharedSurfaceTextureData = + SharedSurfaceTextureData::CreateTextureClient(aDesc, aFormat, aSize, + aFlags, aAllocator); + return mTextureClientOfSharedSurfaceTextureData; +} + +StaticAutoPtr + AndroidHardwareBufferManager::sInstance; + +/* static */ +void AndroidHardwareBufferManager::Init() { + sInstance = new AndroidHardwareBufferManager(); +} + +/* static */ +void AndroidHardwareBufferManager::Shutdown() { sInstance = nullptr; } + +AndroidHardwareBufferManager::AndroidHardwareBufferManager() + : mMonitor("AndroidHardwareBufferManager.mMonitor") {} + +void AndroidHardwareBufferManager::Register( + RefPtr aBuffer) { + MonitorAutoLock lock(mMonitor); + + aBuffer->mIsRegistered = true; + ThreadSafeWeakPtr weak(aBuffer); + +#ifdef DEBUG + const auto it = mBuffers.find(aBuffer->mId); + MOZ_ASSERT(it == mBuffers.end()); +#endif + mBuffers.emplace(aBuffer->mId, weak); +} + +void AndroidHardwareBufferManager::Unregister(AndroidHardwareBuffer* aBuffer) { + MonitorAutoLock lock(mMonitor); + + const auto it = mBuffers.find(aBuffer->mId); + MOZ_ASSERT(it != mBuffers.end()); + if (it == mBuffers.end()) { + gfxCriticalNote << "AndroidHardwareBuffer id mismatch happened"; + return; + } + mBuffers.erase(it); +} + +already_AddRefed AndroidHardwareBufferManager::GetBuffer( + uint64_t aBufferId) { + MonitorAutoLock lock(mMonitor); + + const auto it = mBuffers.find(aBufferId); + if (it == mBuffers.end()) { + return nullptr; + } + auto buffer = RefPtr(it->second); + return buffer.forget(); +} + +bool AndroidHardwareBufferManager::WaitForBufferOwnership( + AndroidHardwareBuffer* aBuffer) { + MonitorAutoLock lock(mMonitor); + + if (aBuffer->mTransactionId.isNothing()) { + return true; + } + + auto it = mWaitingNotifyNotUsed.find(aBuffer->mId); + if (it == mWaitingNotifyNotUsed.end()) { + return true; + } + + const double waitWarningTimeoutMs = 300; + const double maxTimeoutSec = 3; + auto begin = TimeStamp::Now(); + + bool isWaiting = true; + while (isWaiting) { + TimeDuration timeout = TimeDuration::FromMilliseconds(waitWarningTimeoutMs); + CVStatus status = mMonitor.Wait(timeout); + if (status == CVStatus::Timeout) { + gfxCriticalNoteOnce << "AndroidHardwareBuffer wait is slow"; + } + const auto it = mWaitingNotifyNotUsed.find(aBuffer->mId); + if (it == mWaitingNotifyNotUsed.end()) { + return true; + } + auto now = TimeStamp::Now(); + if ((now - begin).ToSeconds() > maxTimeoutSec) { + isWaiting = false; + gfxCriticalNote << "AndroidHardwareBuffer wait timeout"; + } + } + + return false; +} + +bool AndroidHardwareBufferManager::IsWaitingForBufferOwnership( + AndroidHardwareBuffer* aBuffer) { + MonitorAutoLock lock(mMonitor); + + if (aBuffer->mTransactionId.isNothing()) { + return false; + } + + auto it = mWaitingNotifyNotUsed.find(aBuffer->mId); + if (it == mWaitingNotifyNotUsed.end()) { + return false; + } + return true; +} + +void AndroidHardwareBufferManager::HoldUntilNotifyNotUsed( + uint64_t aBufferId, uint64_t aFwdTransactionId, bool aUsesImageBridge) { + MOZ_ASSERT(NS_IsMainThread() || InImageBridgeChildThread()); + + const auto it = mBuffers.find(aBufferId); + if (it == mBuffers.end()) { + return; + } + auto buffer = RefPtr(it->second); + + MonitorAutoLock lock(mMonitor); + buffer->SetLastFwdTransactionId(aFwdTransactionId, aUsesImageBridge, lock); + mWaitingNotifyNotUsed.emplace(aBufferId, buffer); +} + +void AndroidHardwareBufferManager::NotifyNotUsed(ipc::FileDescriptor&& aFenceFd, + uint64_t aBufferId, + uint64_t aTransactionId, + bool aUsesImageBridge) { + MOZ_ASSERT(InImageBridgeChildThread()); + + MonitorAutoLock lock(mMonitor); + + auto it = mWaitingNotifyNotUsed.find(aBufferId); + if (it != mWaitingNotifyNotUsed.end()) { + if (aTransactionId < it->second->GetLastFwdTransactionId(lock)) { + // Released on host side, but client already requested newer use texture. + return; + } + it->second->SetReleaseFence(std::move(aFenceFd), lock); + mWaitingNotifyNotUsed.erase(it); + mMonitor.NotifyAll(); + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/AndroidHardwareBuffer.h b/gfx/layers/AndroidHardwareBuffer.h new file mode 100644 index 0000000000..00dda502e1 --- /dev/null +++ b/gfx/layers/AndroidHardwareBuffer.h @@ -0,0 +1,244 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_LAYERS_ANDROID_HARDWARE_BUFFER +#define MOZILLA_LAYERS_ANDROID_HARDWARE_BUFFER + +#include +#include +#include + +#include "mozilla/layers/TextureClient.h" +#include "mozilla/gfx/Types.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/Monitor.h" +#include "mozilla/RefPtr.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/ThreadSafeWeakPtr.h" + +namespace mozilla { +namespace layers { + +/** + * AndroidHardwareBufferApi provides apis for managing AHardwareBuffer. + * The apis are supported since Android O(APIVersion 26). + */ +class AndroidHardwareBufferApi final { + public: + static void Init(); + static void Shutdown(); + + static AndroidHardwareBufferApi* Get() { return sInstance; } + + void Allocate(const AHardwareBuffer_Desc* aDesc, + AHardwareBuffer** aOutBuffer); + void Acquire(AHardwareBuffer* aBuffer); + void Release(AHardwareBuffer* aBuffer); + void Describe(const AHardwareBuffer* aBuffer, AHardwareBuffer_Desc* aOutDesc); + int Lock(AHardwareBuffer* aBuffer, uint64_t aUsage, int32_t aFence, + const ARect* aRect, void** aOutVirtualAddress); + int Unlock(AHardwareBuffer* aBuffer, int32_t* aFence); + int SendHandleToUnixSocket(const AHardwareBuffer* aBuffer, int aSocketFd); + int RecvHandleFromUnixSocket(int aSocketFd, AHardwareBuffer** aOutBuffer); + + private: + AndroidHardwareBufferApi(); + bool Load(); + + using _AHardwareBuffer_allocate = int (*)(const AHardwareBuffer_Desc* desc, + AHardwareBuffer** outBuffer); + using _AHardwareBuffer_acquire = void (*)(AHardwareBuffer* buffer); + using _AHardwareBuffer_release = void (*)(AHardwareBuffer* buffer); + using _AHardwareBuffer_describe = void (*)(const AHardwareBuffer* buffer, + AHardwareBuffer_Desc* outDesc); + using _AHardwareBuffer_lock = int (*)(AHardwareBuffer* buffer, uint64_t usage, + int32_t fence, const ARect* rect, + void** outVirtualAddress); + using _AHardwareBuffer_unlock = int (*)(AHardwareBuffer* buffer, + int32_t* fence); + using _AHardwareBuffer_sendHandleToUnixSocket = + int (*)(const AHardwareBuffer* buffer, int socketFd); + using _AHardwareBuffer_recvHandleFromUnixSocket = + int (*)(int socketFd, AHardwareBuffer** outBuffer); + + _AHardwareBuffer_allocate mAHardwareBuffer_allocate = nullptr; + _AHardwareBuffer_acquire mAHardwareBuffer_acquire = nullptr; + _AHardwareBuffer_release mAHardwareBuffer_release = nullptr; + _AHardwareBuffer_describe mAHardwareBuffer_describe = nullptr; + _AHardwareBuffer_lock mAHardwareBuffer_lock = nullptr; + _AHardwareBuffer_unlock mAHardwareBuffer_unlock = nullptr; + _AHardwareBuffer_sendHandleToUnixSocket + mAHardwareBuffer_sendHandleToUnixSocket = nullptr; + _AHardwareBuffer_recvHandleFromUnixSocket + mAHardwareBuffer_recvHandleFromUnixSocket = nullptr; + + static StaticAutoPtr sInstance; +}; + +/** + * AndroidHardwareBuffer is a wrapper of AHardwareBuffer. AHardwareBuffer wraps + * android GraphicBuffer. It is supported since Android O(APIVersion 26). + * The manager is mainly used for release fences delivery from + * host side to client side. + */ +class AndroidHardwareBuffer + : public SupportsThreadSafeWeakPtr { + public: + MOZ_DECLARE_THREADSAFEWEAKREFERENCE_TYPENAME(AndroidHardwareBuffer) + MOZ_DECLARE_REFCOUNTED_TYPENAME(AndroidHardwareBuffer) + + static already_AddRefed Create( + gfx::IntSize aSize, gfx::SurfaceFormat aFormat); + + // This function creates AndroidHardwareBuffer from FileDescriptor. + // The fuction is expected to be called on host side. Client side creates + // the FileDescriptor and it is delivered to host side via ipc. + static already_AddRefed FromFileDescriptor( + ipc::FileDescriptor& aFileDescriptor, uint64_t aBufferId, + gfx::IntSize aSize, gfx::SurfaceFormat aFormat); + + virtual ~AndroidHardwareBuffer(); + + int Lock(uint64_t aUsage, const ARect* aRect, void** aOutVirtualAddress); + int Unlock(); + + int SendHandleToUnixSocket(int aSocketFd); + + AHardwareBuffer* GetNativeBuffer() const { return mNativeBuffer; } + + // Waits until the buffer is no longer used by host side. + // Returns false when wait is aborted by timeout. + bool WaitForBufferOwnership(); + + // Returns true when the buffer is still in use by host side. + bool IsWaitingForBufferOwnership(); + + void SetAcquireFence(ipc::FileDescriptor&& aFenceFd); + + void SetReleaseFence(ipc::FileDescriptor&& aFenceFd); + + ipc::FileDescriptor GetAndResetReleaseFence(); + + ipc::FileDescriptor GetAndResetAcquireFence(); + + ipc::FileDescriptor GetAcquireFence(); + + RefPtr GetTextureClientOfSharedSurfaceTextureData( + const layers::SurfaceDescriptor& aDesc, const gfx::SurfaceFormat aFormat, + const gfx::IntSize& aSize, const TextureFlags aFlags, + LayersIPCChannel* aAllocator); + + const gfx::IntSize mSize; + const uint32_t mStride; + const gfx::SurfaceFormat mFormat; + const uint64_t mId; + + protected: + AndroidHardwareBuffer(AHardwareBuffer* aNativeBuffer, gfx::IntSize aSize, + uint32_t aStride, gfx::SurfaceFormat aFormat, + uint64_t aId); + + void SetLastFwdTransactionId(uint64_t aFwdTransactionId, + bool aUsesImageBridge, + const MonitorAutoLock& aAutoLock); + + uint64_t GetLastFwdTransactionId(const MonitorAutoLock& aAutoLock); + + void SetReleaseFence(ipc::FileDescriptor&& aFenceFd, + const MonitorAutoLock& aAutoLock); + + struct FwdTransactionId { + FwdTransactionId(uint64_t aFwdTransactionId, bool aUsesImageBridge) + : mId(aFwdTransactionId), mUsesImageBridge(aUsesImageBridge) {} + uint64_t mId; + bool mUsesImageBridge; + }; + + AHardwareBuffer* mNativeBuffer; + + // When true, AndroidHardwareBuffer is registered to + // AndroidHardwareBufferManager. + bool mIsRegistered; + + // protected by AndroidHardwareBufferManager::mMonitor + + Maybe mTransactionId; + + // FileDescriptor of release fence. + // Release fence is a fence that is used for waiting until usage/composite of + // AHardwareBuffer is ended. The fence is delivered via ImageBridge. + ipc::FileDescriptor mReleaseFenceFd; + + // FileDescriptor of acquire fence. + // Acquire fence is a fence that is used for waiting until rendering to + // its AHardwareBuffer is completed. + ipc::FileDescriptor mAcquireFenceFd; + + // Only TextureClient of SharedSurfaceTextureData could be here. + // SharedSurfaceTextureData does not own AndroidHardwareBuffer, + // then it does not affect to a lifetime of AndroidHardwareBuffer. + // It is used for reducing SharedSurfaceTextureData re-creation to + // avoid re-allocating file descriptor by + // SharedSurfaceTextureData::Serialize(). + RefPtr mTextureClientOfSharedSurfaceTextureData; + + static uint64_t GetNextId(); + + friend class AndroidHardwareBufferManager; +}; + +/** + * AndroidHardwareBufferManager manages AndroidHardwareBuffers that is + * allocated by client side. + * Host side only uses mMonitor for thread safety of AndroidHardwareBuffer. + */ +class AndroidHardwareBufferManager { + public: + static void Init(); + static void Shutdown(); + + AndroidHardwareBufferManager(); + + static AndroidHardwareBufferManager* Get() { return sInstance; } + + void Register(RefPtr aBuffer); + + void Unregister(AndroidHardwareBuffer* aBuffer); + + already_AddRefed GetBuffer(uint64_t aBufferId); + + bool WaitForBufferOwnership(AndroidHardwareBuffer* aBuffer); + + bool IsWaitingForBufferOwnership(AndroidHardwareBuffer* aBuffer); + + void HoldUntilNotifyNotUsed(uint64_t aBufferId, uint64_t aFwdTransactionId, + bool aUsesImageBridge); + + void NotifyNotUsed(ipc::FileDescriptor&& aFenceFd, uint64_t aBufferId, + uint64_t aTransactionId, bool aUsesImageBridge); + + Monitor& GetMonitor() { return mMonitor; } + + private: + Monitor mMonitor; + std::unordered_map> + mBuffers; + + /** + * Hold AndroidHardwareBuffers that are used by host side via + * CompositorBridgeChild and ImageBridgeChild until end of their usages + * on host side. + */ + std::unordered_map> + mWaitingNotifyNotUsed; + + static StaticAutoPtr sInstance; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/AnimationHelper.cpp b/gfx/layers/AnimationHelper.cpp new file mode 100644 index 0000000000..ee6cc0d3b3 --- /dev/null +++ b/gfx/layers/AnimationHelper.cpp @@ -0,0 +1,713 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AnimationHelper.h" +#include "base/process_util.h" +#include "gfx2DGlue.h" // for ThebesRect +#include "gfxLineSegment.h" // for gfxLineSegment +#include "gfxPoint.h" // for gfxPoint +#include "gfxQuad.h" // for gfxQuad +#include "gfxRect.h" // for gfxRect +#include "gfxUtils.h" // for gfxUtils::TransformToQuad +#include "mozilla/ComputedTimingFunction.h" // for ComputedTimingFunction +#include "mozilla/dom/AnimationEffectBinding.h" // for dom::FillMode +#include "mozilla/dom/KeyframeEffectBinding.h" // for dom::IterationComposite +#include "mozilla/dom/KeyframeEffect.h" // for dom::KeyFrameEffectReadOnly +#include "mozilla/dom/Nullable.h" // for dom::Nullable +#include "mozilla/layers/CompositorThread.h" // for CompositorThreadHolder +#include "mozilla/layers/CompositorAnimationStorage.h" // for CompositorAnimationStorage +#include "mozilla/layers/LayerAnimationUtils.h" // for TimingFunctionToComputedTimingFunction +#include "mozilla/LayerAnimationInfo.h" // for GetCSSPropertiesFor() +#include "mozilla/MotionPathUtils.h" // for ResolveMotionPath() +#include "mozilla/ServoBindings.h" // for Servo_ComposeAnimationSegment, etc +#include "mozilla/StyleAnimationValue.h" // for StyleAnimationValue, etc +#include "nsDeviceContext.h" // for AppUnitsPerCSSPixel +#include "nsDisplayList.h" // for nsDisplayTransform, etc + +namespace mozilla { +namespace layers { + +enum class CanSkipCompose { + IfPossible, + No, +}; +static AnimationHelper::SampleResult SampleAnimationForProperty( + TimeStamp aPreviousFrameTime, TimeStamp aCurrentFrameTime, + const AnimatedValue* aPreviousValue, CanSkipCompose aCanSkipCompose, + nsTArray& aPropertyAnimations, + RefPtr& aAnimationValue) { + MOZ_ASSERT(!aPropertyAnimations.IsEmpty(), "Should have animations"); + bool hasInEffectAnimations = false; +#ifdef DEBUG + // In cases where this function returns a SampleResult::Skipped, we actually + // do populate aAnimationValue in debug mode, so that we can MOZ_ASSERT at the + // call site that the value that would have been computed matches the stored + // value that we end up using. This flag is used to ensure we populate + // aAnimationValue in this scenario. + bool shouldBeSkipped = false; +#endif + // Process in order, since later animations override earlier ones. + for (PropertyAnimation& animation : aPropertyAnimations) { + MOZ_ASSERT( + (!animation.mOriginTime.IsNull() && animation.mStartTime.isSome()) || + animation.mIsNotPlaying, + "If we are playing, we should have an origin time and a start time"); + + // Determine if the animation was play-pending and used a ready time later + // than the previous frame time. + // + // To determine this, _all_ of the following conditions need to hold: + // + // * There was no previous animation value (i.e. this is the first frame for + // the animation since it was sent to the compositor), and + // * The animation is playing, and + // * There is a previous frame time, and + // * The ready time of the animation is ahead of the previous frame time. + // + bool hasFutureReadyTime = false; + if (!aPreviousValue && !animation.mIsNotPlaying && + !aPreviousFrameTime.IsNull()) { + // This is the inverse of the calculation performed in + // AnimationInfo::StartPendingAnimations to calculate the start time of + // play-pending animations. + // Note that we have to calculate (TimeStamp + TimeDuration) last to avoid + // underflow in the middle of the calulation. + const TimeStamp readyTime = + animation.mOriginTime + + (animation.mStartTime.ref() + + animation.mHoldTime.MultDouble(1.0 / animation.mPlaybackRate)); + hasFutureReadyTime = + !readyTime.IsNull() && readyTime > aPreviousFrameTime; + } + // Use the previous vsync time to make main thread animations and compositor + // more closely aligned. + // + // On the first frame where we have animations the previous timestamp will + // not be set so we simply use the current timestamp. As a result we will + // end up painting the first frame twice. That doesn't appear to be + // noticeable, however. + // + // Likewise, if the animation is play-pending, it may have a ready time that + // is *after* |aPreviousFrameTime| (but *before* |aCurrentFrameTime|). + // To avoid flicker we need to use |aCurrentFrameTime| to avoid temporarily + // jumping backwards into the range prior to when the animation starts. + const TimeStamp& timeStamp = + aPreviousFrameTime.IsNull() || hasFutureReadyTime ? aCurrentFrameTime + : aPreviousFrameTime; + + // If the animation is not currently playing, e.g. paused or + // finished, then use the hold time to stay at the same position. + TimeDuration elapsedDuration = + animation.mIsNotPlaying || animation.mStartTime.isNothing() + ? animation.mHoldTime + : (timeStamp - animation.mOriginTime - animation.mStartTime.ref()) + .MultDouble(animation.mPlaybackRate); + + ComputedTiming computedTiming = dom::AnimationEffect::GetComputedTimingAt( + dom::Nullable(elapsedDuration), animation.mTiming, + animation.mPlaybackRate); + + if (computedTiming.mProgress.IsNull()) { + continue; + } + + dom::IterationCompositeOperation iterCompositeOperation = + animation.mIterationComposite; + + // Skip calculation if the progress hasn't changed since the last + // calculation. + // Note that we don't skip calculate this animation if there is another + // animation since the other animation might be 'accumulate' or 'add', or + // might have a missing keyframe (i.e. this animation value will be used in + // the missing keyframe). + // FIXME Bug 1455476: We should do this optimizations for the case where + // the layer has multiple animations and multiple properties. + if (aCanSkipCompose == CanSkipCompose::IfPossible && + !dom::KeyframeEffect::HasComputedTimingChanged( + computedTiming, iterCompositeOperation, + animation.mProgressOnLastCompose, + animation.mCurrentIterationOnLastCompose)) { +#ifdef DEBUG + shouldBeSkipped = true; +#else + return AnimationHelper::SampleResult::Skipped; +#endif + } + + uint32_t segmentIndex = 0; + size_t segmentSize = animation.mSegments.Length(); + PropertyAnimation::SegmentData* segment = animation.mSegments.Elements(); + while (segment->mEndPortion < computedTiming.mProgress.Value() && + segmentIndex < segmentSize - 1) { + ++segment; + ++segmentIndex; + } + + double positionInSegment = + (computedTiming.mProgress.Value() - segment->mStartPortion) / + (segment->mEndPortion - segment->mStartPortion); + + double portion = ComputedTimingFunction::GetPortion( + segment->mFunction, positionInSegment, computedTiming.mBeforeFlag); + + // Like above optimization, skip calculation if the target segment isn't + // changed and if the portion in the segment isn't changed. + // This optimization is needed for CSS animations/transitions with step + // timing functions (e.g. the throbber animation on tabs or frame based + // animations). + // FIXME Bug 1455476: Like the above optimization, we should apply this + // optimizations for multiple animation cases and multiple properties as + // well. + if (aCanSkipCompose == CanSkipCompose::IfPossible && + animation.mSegmentIndexOnLastCompose == segmentIndex && + !animation.mPortionInSegmentOnLastCompose.IsNull() && + animation.mPortionInSegmentOnLastCompose.Value() == portion) { +#ifdef DEBUG + shouldBeSkipped = true; +#else + return AnimationHelper::SampleResult::Skipped; +#endif + } + + AnimationPropertySegment animSegment; + animSegment.mFromKey = 0.0; + animSegment.mToKey = 1.0; + animSegment.mFromValue = AnimationValue(segment->mStartValue); + animSegment.mToValue = AnimationValue(segment->mEndValue); + animSegment.mFromComposite = segment->mStartComposite; + animSegment.mToComposite = segment->mEndComposite; + + // interpolate the property + aAnimationValue = + Servo_ComposeAnimationSegment( + &animSegment, aAnimationValue, + animation.mSegments.LastElement().mEndValue, iterCompositeOperation, + portion, computedTiming.mCurrentIteration) + .Consume(); + +#ifdef DEBUG + if (shouldBeSkipped) { + return AnimationHelper::SampleResult::Skipped; + } +#endif + + hasInEffectAnimations = true; + animation.mProgressOnLastCompose = computedTiming.mProgress; + animation.mCurrentIterationOnLastCompose = computedTiming.mCurrentIteration; + animation.mSegmentIndexOnLastCompose = segmentIndex; + animation.mPortionInSegmentOnLastCompose.SetValue(portion); + } + + return hasInEffectAnimations ? AnimationHelper::SampleResult::Sampled + : AnimationHelper::SampleResult::None; +} + +AnimationHelper::SampleResult AnimationHelper::SampleAnimationForEachNode( + TimeStamp aPreviousFrameTime, TimeStamp aCurrentFrameTime, + const AnimatedValue* aPreviousValue, + nsTArray& aPropertyAnimationGroups, + nsTArray>& aAnimationValues /* out */) { + MOZ_ASSERT(!aPropertyAnimationGroups.IsEmpty(), + "Should be called with animation data"); + MOZ_ASSERT(aAnimationValues.IsEmpty(), + "Should be called with empty aAnimationValues"); + + nsTArray> nonAnimatingValues; + for (PropertyAnimationGroup& group : aPropertyAnimationGroups) { + // Initialize animation value with base style. + RefPtr currValue = group.mBaseStyle; + + CanSkipCompose canSkipCompose = + aPreviousValue && aPropertyAnimationGroups.Length() == 1 && + group.mAnimations.Length() == 1 + ? CanSkipCompose::IfPossible + : CanSkipCompose::No; + + MOZ_ASSERT( + !group.mAnimations.IsEmpty() || + nsCSSPropertyIDSet::TransformLikeProperties().HasProperty( + group.mProperty), + "Only transform-like properties can have empty PropertyAnimation list"); + + // For properties which are not animating (i.e. their values are always the + // same), we store them in a different array, and then merge them into the + // final result (a.k.a. aAnimationValues) because we shouldn't take them + // into account for SampleResult. (In other words, these properties + // shouldn't affect the optimization.) + if (group.mAnimations.IsEmpty()) { + nonAnimatingValues.AppendElement(std::move(currValue)); + continue; + } + + SampleResult result = SampleAnimationForProperty( + aPreviousFrameTime, aCurrentFrameTime, aPreviousValue, canSkipCompose, + group.mAnimations, currValue); + + // FIXME: Bug 1455476: Do optimization for multiple properties. For now, + // the result is skipped only if the property count == 1. + if (result == SampleResult::Skipped) { +#ifdef DEBUG + aAnimationValues.AppendElement(std::move(currValue)); +#endif + return SampleResult::Skipped; + } + + if (result != SampleResult::Sampled) { + continue; + } + + // Insert the interpolation result into the output array. + MOZ_ASSERT(currValue); + aAnimationValues.AppendElement(std::move(currValue)); + } + + SampleResult rv = + aAnimationValues.IsEmpty() ? SampleResult::None : SampleResult::Sampled; + if (rv == SampleResult::Sampled) { + aAnimationValues.AppendElements(std::move(nonAnimatingValues)); + } + return rv; +} + +static dom::FillMode GetAdjustedFillMode(const Animation& aAnimation) { + // Adjust fill mode so that if the main thread is delayed in clearing + // this animation we don't introduce flicker by jumping back to the old + // underlying value. + auto fillMode = static_cast(aAnimation.fillMode()); + float playbackRate = aAnimation.playbackRate(); + switch (fillMode) { + case dom::FillMode::None: + if (playbackRate > 0) { + fillMode = dom::FillMode::Forwards; + } else if (playbackRate < 0) { + fillMode = dom::FillMode::Backwards; + } + break; + case dom::FillMode::Backwards: + if (playbackRate > 0) { + fillMode = dom::FillMode::Both; + } + break; + case dom::FillMode::Forwards: + if (playbackRate < 0) { + fillMode = dom::FillMode::Both; + } + break; + default: + break; + } + return fillMode; +} + +#ifdef DEBUG +static bool HasTransformLikeAnimations(const AnimationArray& aAnimations) { + nsCSSPropertyIDSet transformSet = + nsCSSPropertyIDSet::TransformLikeProperties(); + + for (const Animation& animation : aAnimations) { + if (animation.isNotAnimating()) { + continue; + } + + if (transformSet.HasProperty(animation.property())) { + return true; + } + } + + return false; +} +#endif + +AnimationStorageData AnimationHelper::ExtractAnimations( + const LayersId& aLayersId, const AnimationArray& aAnimations) { + AnimationStorageData storageData; + storageData.mLayersId = aLayersId; + + nsCSSPropertyID prevID = eCSSProperty_UNKNOWN; + PropertyAnimationGroup* currData = nullptr; + DebugOnly currBaseStyle = nullptr; + + for (const Animation& animation : aAnimations) { + // Animations with same property are grouped together, so we can just + // check if the current property is the same as the previous one for + // knowing this is a new group. + if (prevID != animation.property()) { + // Got a different group, we should create a different array. + currData = storageData.mAnimation.AppendElement(); + currData->mProperty = animation.property(); + if (animation.transformData()) { + MOZ_ASSERT(!storageData.mTransformData, + "Only one entry has TransformData"); + storageData.mTransformData = animation.transformData(); + } + + prevID = animation.property(); + + // Reset the debug pointer. + currBaseStyle = nullptr; + } + + MOZ_ASSERT(currData); + if (animation.baseStyle().type() != Animatable::Tnull_t) { + MOZ_ASSERT(!currBaseStyle || *currBaseStyle == animation.baseStyle(), + "Should be the same base style"); + + currData->mBaseStyle = AnimationValue::FromAnimatable( + animation.property(), animation.baseStyle()); + currBaseStyle = &animation.baseStyle(); + } + + // If this layers::Animation sets isNotAnimating to true, it only has + // base style and doesn't have any animation information, so we can skip + // the rest steps. (And so its PropertyAnimationGroup::mAnimation will be + // an empty array.) + if (animation.isNotAnimating()) { + MOZ_ASSERT(nsCSSPropertyIDSet::TransformLikeProperties().HasProperty( + animation.property()), + "Only transform-like properties could set this true"); + + if (animation.property() == eCSSProperty_offset_path) { + MOZ_ASSERT(currData->mBaseStyle, + "Fixed offset-path should have base style"); + MOZ_ASSERT(HasTransformLikeAnimations(aAnimations)); + + AnimationValue value{currData->mBaseStyle}; + const StyleOffsetPath& offsetPath = value.GetOffsetPathProperty(); + if (offsetPath.IsPath()) { + MOZ_ASSERT(!storageData.mCachedMotionPath, + "Only one offset-path: path() is set"); + + RefPtr builder = + MotionPathUtils::GetCompositorPathBuilder(); + storageData.mCachedMotionPath = + MotionPathUtils::BuildPath(offsetPath.AsPath(), builder); + } + } + + continue; + } + + PropertyAnimation* propertyAnimation = + currData->mAnimations.AppendElement(); + + propertyAnimation->mOriginTime = animation.originTime(); + propertyAnimation->mStartTime = animation.startTime(); + propertyAnimation->mHoldTime = animation.holdTime(); + propertyAnimation->mPlaybackRate = animation.playbackRate(); + propertyAnimation->mIterationComposite = + static_cast( + animation.iterationComposite()); + propertyAnimation->mIsNotPlaying = animation.isNotPlaying(); + propertyAnimation->mTiming = + TimingParams{animation.duration(), + animation.delay(), + animation.endDelay(), + animation.iterations(), + animation.iterationStart(), + static_cast(animation.direction()), + GetAdjustedFillMode(animation), + AnimationUtils::TimingFunctionToComputedTimingFunction( + animation.easingFunction())}; + + nsTArray& segmentData = + propertyAnimation->mSegments; + for (const AnimationSegment& segment : animation.segments()) { + segmentData.AppendElement(PropertyAnimation::SegmentData{ + AnimationValue::FromAnimatable(animation.property(), + segment.startState()), + AnimationValue::FromAnimatable(animation.property(), + segment.endState()), + AnimationUtils::TimingFunctionToComputedTimingFunction( + segment.sampleFn()), + segment.startPortion(), segment.endPortion(), + static_cast(segment.startComposite()), + static_cast(segment.endComposite())}); + } + } + +#ifdef DEBUG + // Sanity check that the grouped animation data is correct by looking at the + // property set. + if (!storageData.mAnimation.IsEmpty()) { + nsCSSPropertyIDSet seenProperties; + for (const auto& group : storageData.mAnimation) { + nsCSSPropertyID id = group.mProperty; + + MOZ_ASSERT(!seenProperties.HasProperty(id), "Should be a new property"); + seenProperties.AddProperty(id); + } + + MOZ_ASSERT( + seenProperties.IsSubsetOf(LayerAnimationInfo::GetCSSPropertiesFor( + DisplayItemType::TYPE_TRANSFORM)) || + seenProperties.IsSubsetOf(LayerAnimationInfo::GetCSSPropertiesFor( + DisplayItemType::TYPE_OPACITY)) || + seenProperties.IsSubsetOf(LayerAnimationInfo::GetCSSPropertiesFor( + DisplayItemType::TYPE_BACKGROUND_COLOR)), + "The property set of output should be the subset of transform-like " + "properties, opacity, or background_color."); + + if (seenProperties.IsSubsetOf(LayerAnimationInfo::GetCSSPropertiesFor( + DisplayItemType::TYPE_TRANSFORM))) { + MOZ_ASSERT(storageData.mTransformData, "Should have TransformData"); + } + + if (seenProperties.HasProperty(eCSSProperty_offset_path)) { + MOZ_ASSERT(storageData.mTransformData, "Should have TransformData"); + MOZ_ASSERT(storageData.mTransformData->motionPathData(), + "Should have MotionPathData"); + } + } +#endif + + return storageData; +} + +uint64_t AnimationHelper::GetNextCompositorAnimationsId() { + static uint32_t sNextId = 0; + ++sNextId; + + uint32_t procId = static_cast(base::GetCurrentProcId()); + uint64_t nextId = procId; + nextId = nextId << 32 | sNextId; + return nextId; +} + +gfx::Matrix4x4 AnimationHelper::ServoAnimationValueToMatrix4x4( + const nsTArray>& aValues, + const TransformData& aTransformData, gfx::Path* aCachedMotionPath) { + using nsStyleTransformMatrix::TransformReferenceBox; + + // This is a bit silly just to avoid the transform list copy from the + // animation transform list. + auto noneTranslate = StyleTranslate::None(); + auto noneRotate = StyleRotate::None(); + auto noneScale = StyleScale::None(); + const StyleTransform noneTransform; + + const StyleTranslate* translate = nullptr; + const StyleRotate* rotate = nullptr; + const StyleScale* scale = nullptr; + const StyleTransform* transform = nullptr; + const StyleOffsetPath* path = nullptr; + const StyleLengthPercentage* distance = nullptr; + const StyleOffsetRotate* offsetRotate = nullptr; + const StylePositionOrAuto* anchor = nullptr; + + for (const auto& value : aValues) { + MOZ_ASSERT(value); + nsCSSPropertyID id = Servo_AnimationValue_GetPropertyId(value); + switch (id) { + case eCSSProperty_transform: + MOZ_ASSERT(!transform); + transform = Servo_AnimationValue_GetTransform(value); + break; + case eCSSProperty_translate: + MOZ_ASSERT(!translate); + translate = Servo_AnimationValue_GetTranslate(value); + break; + case eCSSProperty_rotate: + MOZ_ASSERT(!rotate); + rotate = Servo_AnimationValue_GetRotate(value); + break; + case eCSSProperty_scale: + MOZ_ASSERT(!scale); + scale = Servo_AnimationValue_GetScale(value); + break; + case eCSSProperty_offset_path: + MOZ_ASSERT(!path); + path = Servo_AnimationValue_GetOffsetPath(value); + break; + case eCSSProperty_offset_distance: + MOZ_ASSERT(!distance); + distance = Servo_AnimationValue_GetOffsetDistance(value); + break; + case eCSSProperty_offset_rotate: + MOZ_ASSERT(!offsetRotate); + offsetRotate = Servo_AnimationValue_GetOffsetRotate(value); + break; + case eCSSProperty_offset_anchor: + MOZ_ASSERT(!anchor); + anchor = Servo_AnimationValue_GetOffsetAnchor(value); + break; + default: + MOZ_ASSERT_UNREACHABLE("Unsupported transform-like property"); + } + } + + TransformReferenceBox refBox(nullptr, aTransformData.bounds()); + Maybe motion = MotionPathUtils::ResolveMotionPath( + path, distance, offsetRotate, anchor, aTransformData.motionPathData(), + refBox, aCachedMotionPath); + + // We expect all our transform data to arrive in device pixels + gfx::Point3D transformOrigin = aTransformData.transformOrigin(); + nsDisplayTransform::FrameTransformProperties props( + translate ? *translate : noneTranslate, rotate ? *rotate : noneRotate, + scale ? *scale : noneScale, transform ? *transform : noneTransform, + motion, transformOrigin); + + return nsDisplayTransform::GetResultingTransformMatrix( + props, refBox, aTransformData.appUnitsPerDevPixel()); +} + +static uint8_t CollectOverflowedSideLines(const gfxQuad& aPrerenderedQuad, + SideBits aOverflowSides, + gfxLineSegment sideLines[4]) { + uint8_t count = 0; + + if (aOverflowSides & SideBits::eTop) { + sideLines[count] = gfxLineSegment(aPrerenderedQuad.mPoints[0], + aPrerenderedQuad.mPoints[1]); + count++; + } + if (aOverflowSides & SideBits::eRight) { + sideLines[count] = gfxLineSegment(aPrerenderedQuad.mPoints[1], + aPrerenderedQuad.mPoints[2]); + count++; + } + if (aOverflowSides & SideBits::eBottom) { + sideLines[count] = gfxLineSegment(aPrerenderedQuad.mPoints[2], + aPrerenderedQuad.mPoints[3]); + count++; + } + if (aOverflowSides & SideBits::eLeft) { + sideLines[count] = gfxLineSegment(aPrerenderedQuad.mPoints[3], + aPrerenderedQuad.mPoints[0]); + count++; + } + + return count; +} + +enum RegionBits : uint8_t { + Inside = 0, + Left = (1 << 0), + Right = (1 << 1), + Bottom = (1 << 2), + Top = (1 << 3), +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(RegionBits); + +static RegionBits GetRegionBitsForPoint(double aX, double aY, + const gfxRect& aClip) { + RegionBits result = RegionBits::Inside; + if (aX < aClip.X()) { + result |= RegionBits::Left; + } else if (aX > aClip.XMost()) { + result |= RegionBits::Right; + } + + if (aY < aClip.Y()) { + result |= RegionBits::Bottom; + } else if (aY > aClip.YMost()) { + result |= RegionBits::Top; + } + return result; +}; + +// https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm +static bool LineSegmentIntersectsClip(double aX0, double aY0, double aX1, + double aY1, const gfxRect& aClip) { + RegionBits b0 = GetRegionBitsForPoint(aX0, aY0, aClip); + RegionBits b1 = GetRegionBitsForPoint(aX1, aY1, aClip); + + while (true) { + if (!(b0 | b1)) { + // Completely inside. + return true; + } + + if (b0 & b1) { + // Completely outside. + return false; + } + + double x, y; + // Choose an outside point. + RegionBits outsidePointBits = b1 > b0 ? b1 : b0; + if (outsidePointBits & RegionBits::Top) { + x = aX0 + (aX1 - aX0) * (aClip.YMost() - aY0) / (aY1 - aY0); + y = aClip.YMost(); + } else if (outsidePointBits & RegionBits::Bottom) { + x = aX0 + (aX1 - aX0) * (aClip.Y() - aY0) / (aY1 - aY0); + y = aClip.Y(); + } else if (outsidePointBits & RegionBits::Right) { + y = aY0 + (aY1 - aY0) * (aClip.XMost() - aX0) / (aX1 - aX0); + x = aClip.XMost(); + } else if (outsidePointBits & RegionBits::Left) { + y = aY0 + (aY1 - aY0) * (aClip.X() - aX0) / (aX1 - aX0); + x = aClip.X(); + } + + if (outsidePointBits == b0) { + aX0 = x; + aY0 = y; + b0 = GetRegionBitsForPoint(aX0, aY0, aClip); + } else { + aX1 = x; + aY1 = y; + b1 = GetRegionBitsForPoint(aX1, aY1, aClip); + } + } + MOZ_ASSERT_UNREACHABLE(); + return false; +} + +// static +bool AnimationHelper::ShouldBeJank(const LayoutDeviceRect& aPrerenderedRect, + SideBits aOverflowSides, + const gfx::Matrix4x4& aTransform, + const ParentLayerRect& aClipRect) { + if (aClipRect.IsEmpty()) { + return false; + } + + gfxQuad prerenderedQuad = gfxUtils::TransformToQuad( + ThebesRect(aPrerenderedRect.ToUnknownRect()), aTransform); + + gfxLineSegment sideLines[4]; + uint8_t overflowSideCount = + CollectOverflowedSideLines(prerenderedQuad, aOverflowSides, sideLines); + + gfxRect clipRect = ThebesRect(aClipRect.ToUnknownRect()); + for (uint8_t j = 0; j < overflowSideCount; j++) { + if (LineSegmentIntersectsClip(sideLines[j].mStart.x, sideLines[j].mStart.y, + sideLines[j].mEnd.x, sideLines[j].mEnd.y, + clipRect)) { + return true; + } + } + + // With step timing functions there are cases the transform jumps to a + // position where the partial pre-render area is totally outside of the clip + // rect without any intersection of the partial pre-render area and the clip + // rect happened in previous compositions but there remains visible area of + // the entire transformed area. + // + // So now all four points of the transformed partial pre-render rect are + // outside of the clip rect, if all these four points are in either side of + // the clip rect, we consider it's jank so that on the main-thread we will + // either a) rebuild the up-to-date display item if there remains visible area + // or b) no longer rebuild the display item if it's totally outside of the + // clip rect. + // + // Note that RegionBits::Left and Right are mutually exclusive, + // RegionBits::Top and Bottom are also mutually exclusive, so if there remains + // any bits, it means all four points are in the same side. + return GetRegionBitsForPoint(prerenderedQuad.mPoints[0].x, + prerenderedQuad.mPoints[0].y, clipRect) & + GetRegionBitsForPoint(prerenderedQuad.mPoints[1].x, + prerenderedQuad.mPoints[1].y, clipRect) & + GetRegionBitsForPoint(prerenderedQuad.mPoints[2].x, + prerenderedQuad.mPoints[2].y, clipRect) & + GetRegionBitsForPoint(prerenderedQuad.mPoints[3].x, + prerenderedQuad.mPoints[3].y, clipRect); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/AnimationHelper.h b/gfx/layers/AnimationHelper.h new file mode 100644 index 0000000000..f5a57b9c4d --- /dev/null +++ b/gfx/layers/AnimationHelper.h @@ -0,0 +1,152 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_AnimationHelper_h +#define mozilla_layers_AnimationHelper_h + +#include "mozilla/dom/Nullable.h" +#include "mozilla/ComputedTimingFunction.h" // for ComputedTimingFunction +#include "mozilla/layers/AnimationStorageData.h" +#include "mozilla/layers/LayersMessages.h" // for TransformData, etc +#include "mozilla/webrender/WebRenderTypes.h" // for RenderRoot +#include "mozilla/TimeStamp.h" // for TimeStamp +#include "mozilla/TimingParams.h" +#include "mozilla/Types.h" // for SideBits +#include "X11UndefineNone.h" +#include + +namespace mozilla { +namespace layers { +class Animation; +class CompositorAnimationStorage; +struct AnimatedValue; + +typedef nsTArray AnimationArray; + +/** + * This utility class allows reusing code between the webrender and + * non-webrender compositor-side implementations. It provides + * utility functions for sampling animations at particular timestamps. + */ +class AnimationHelper { + public: + enum class SampleResult { None, Skipped, Sampled }; + + /** + * Sample animations based on a given time stamp for a element(layer) with + * its animation data. + * Generally |aPreviousFrameTime| is used for the sampling if it's + * supplied to make the animation more in sync with other animations on the + * main-thread. But in the case where the animation just started at the time + * when the animation was sent to the compositor, |aCurrentFrameTime| is used + * for sampling instead to avoid flicker. + * + * Returns SampleResult::None if none of the animations are producing a result + * (e.g. they are in the delay phase with no backwards fill), + * SampleResult::Skipped if the animation output did not change since the last + * call of this function, + * SampleResult::Sampled if the animation output was updated. + * + * Using the same example from ExtractAnimations (below): + * + * Input |aPropertyAnimationGroups| (ignoring the base animation style): + * + * [ + * Group A: [ { rotate, Animation A }, { rotate, Animation B } ], + * Group B: [ { scale, Animation B } ], + * Group C: [ { transform, Animation A }, { transform, Animation B } ], + * ] + * + * For each property group, this function interpolates each animation in turn, + * using the result of interpolating one animation as input for the next such + * that it reduces each property group to a single output value: + * + * [ + * { rotate, RawServoAnimationValue }, + * { scale, RawServoAnimationValue }, + * { transform, RawServoAnimationValue }, + * ] + * + * For transform animations, the caller (SampleAnimations) will combine the + * result of the various transform properties into a final matrix. + */ + static SampleResult SampleAnimationForEachNode( + TimeStamp aPreviousFrameTime, TimeStamp aCurrentFrameTime, + const AnimatedValue* aPreviousValue, + nsTArray& aPropertyAnimationGroups, + nsTArray>& aAnimationValues); + + /** + * Extract organized animation data by property into an array of + * PropertyAnimationGroup objects. + * + * For example, suppose we have the following animations: + * + * Animation A: [ transform, rotate ] + * Animation B: [ rotate, scale ] + * Animation C: [ transform ] + * Animation D: [ opacity ] + * + * When we go to send transform-like properties to the compositor, we + * sort them as follows: + * + * [ + * { rotate: Animation A (rotate segments only) }, + * { rotate: Animation B ( " " ) }, + * { scale: Animation B (scale segments only) }, + * { transform: Animation A (transform segments only) }, + * { transform: Animation C ( " " ) }, + * ] + * + * In this function, we group these animations together by property producing + * output such as the following: + * + * [ + * [ { rotate, Animation A }, { rotate, Animation B } ], + * [ { scale, Animation B } ], + * [ { transform, Animation A }, { transform, Animation B } ], + * ] + * + * In the process of grouping these animations, we also convert their values + * from the rather compact representation we use for transferring across the + * IPC boundary into something we can readily use for sampling. + */ + static AnimationStorageData ExtractAnimations( + const LayersId& aLayersId, const AnimationArray& aAnimations); + + /** + * Get a unique id to represent the compositor animation between child + * and parent side. This id will be used as a key to store animation + * data in the CompositorAnimationStorage per compositor. + * Each layer on the content side calls this when it gets new animation + * data. + */ + static uint64_t GetNextCompositorAnimationsId(); + + /** + * Convert an array of animation values into a matrix given the corresponding + * transform parameters. |aValue| must be a transform-like value + * (e.g. transform, translate etc.). + */ + static gfx::Matrix4x4 ServoAnimationValueToMatrix4x4( + const nsTArray>& aValue, + const TransformData& aTransformData, gfx::Path* aCachedMotionPath); + + /** + * Returns true if |aPrerenderedRect| transformed by |aTransform| were + * composited in |aClipRect| there appears area which wasn't pre-rendered + * on the main-thread. I.e. checkerboarding. + */ + static bool ShouldBeJank(const LayoutDeviceRect& aPrerenderedRect, + SideBits aOverflowedSides, + const gfx::Matrix4x4& aTransform, + const ParentLayerRect& aClipRect); +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_AnimationHelper_h diff --git a/gfx/layers/AnimationInfo.cpp b/gfx/layers/AnimationInfo.cpp new file mode 100644 index 0000000000..278271fa69 --- /dev/null +++ b/gfx/layers/AnimationInfo.cpp @@ -0,0 +1,1022 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AnimationInfo.h" +#include "Layers.h" +#include "mozilla/LayerAnimationInfo.h" +#include "mozilla/layers/WebRenderLayerManager.h" +#include "mozilla/layers/AnimationHelper.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/dom/Animation.h" +#include "mozilla/dom/CSSTransition.h" +#include "mozilla/dom/KeyframeEffect.h" +#include "mozilla/EffectSet.h" +#include "mozilla/PresShell.h" +#include "mozilla/StaticPrefs_layout.h" +#include "nsIContent.h" +#include "nsLayoutUtils.h" +#include "nsStyleTransformMatrix.h" +#include "PuppetWidget.h" + +namespace mozilla { +namespace layers { + +using TransformReferenceBox = nsStyleTransformMatrix::TransformReferenceBox; + +AnimationInfo::AnimationInfo() : mCompositorAnimationsId(0), mMutated(false) {} + +AnimationInfo::~AnimationInfo() = default; + +void AnimationInfo::EnsureAnimationsId() { + if (!mCompositorAnimationsId) { + mCompositorAnimationsId = AnimationHelper::GetNextCompositorAnimationsId(); + } +} + +Animation* AnimationInfo::AddAnimation() { + MOZ_ASSERT(!CompositorThreadHolder::IsInCompositorThread()); + // Here generates a new id when the first animation is added and + // this id is used to represent the animations in this layer. + EnsureAnimationsId(); + + MOZ_ASSERT(!mPendingAnimations, "should have called ClearAnimations first"); + + Animation* anim = mAnimations.AppendElement(); + + mMutated = true; + + return anim; +} + +Animation* AnimationInfo::AddAnimationForNextTransaction() { + MOZ_ASSERT(!CompositorThreadHolder::IsInCompositorThread()); + MOZ_ASSERT(mPendingAnimations, + "should have called ClearAnimationsForNextTransaction first"); + + Animation* anim = mPendingAnimations->AppendElement(); + + return anim; +} + +void AnimationInfo::ClearAnimations() { + mPendingAnimations = nullptr; + + if (mAnimations.IsEmpty() && mStorageData.IsEmpty()) { + return; + } + + mAnimations.Clear(); + mStorageData.Clear(); + + mMutated = true; +} + +void AnimationInfo::ClearAnimationsForNextTransaction() { + // Ensure we have a non-null mPendingAnimations to mark a future clear. + if (!mPendingAnimations) { + mPendingAnimations = MakeUnique(); + } + + mPendingAnimations->Clear(); +} + +void AnimationInfo::SetCompositorAnimations( + const LayersId& aLayersId, + const CompositorAnimations& aCompositorAnimations) { + mCompositorAnimationsId = aCompositorAnimations.id(); + + mStorageData = AnimationHelper::ExtractAnimations( + aLayersId, aCompositorAnimations.animations()); +} + +bool AnimationInfo::StartPendingAnimations(const TimeStamp& aReadyTime) { + bool updated = false; + for (size_t animIdx = 0, animEnd = mAnimations.Length(); animIdx < animEnd; + animIdx++) { + Animation& anim = mAnimations[animIdx]; + + // If the animation is doing an async update of its playback rate, then we + // want to match whatever its current time would be at *aReadyTime*. + if (!std::isnan(anim.previousPlaybackRate()) && anim.startTime().isSome() && + !anim.originTime().IsNull() && !anim.isNotPlaying()) { + TimeDuration readyTime = aReadyTime - anim.originTime(); + anim.holdTime() = dom::Animation::CurrentTimeFromTimelineTime( + readyTime, anim.startTime().ref(), anim.previousPlaybackRate()); + // Make start time null so that we know to update it below. + anim.startTime() = Nothing(); + } + + // If the animation is play-pending, resolve the start time. + if (anim.startTime().isNothing() && !anim.originTime().IsNull() && + !anim.isNotPlaying()) { + TimeDuration readyTime = aReadyTime - anim.originTime(); + anim.startTime() = Some(dom::Animation::StartTimeFromTimelineTime( + readyTime, anim.holdTime(), anim.playbackRate())); + updated = true; + } + } + return updated; +} + +void AnimationInfo::TransferMutatedFlagToLayer(Layer* aLayer) { + if (mMutated) { + aLayer->Mutated(); + mMutated = false; + } +} + +bool AnimationInfo::ApplyPendingUpdatesForThisTransaction() { + if (mPendingAnimations) { + mAnimations = std::move(*mPendingAnimations); + mPendingAnimations = nullptr; + return true; + } + + return false; +} + +bool AnimationInfo::HasTransformAnimation() const { + const nsCSSPropertyIDSet& transformSet = + LayerAnimationInfo::GetCSSPropertiesFor(DisplayItemType::TYPE_TRANSFORM); + for (uint32_t i = 0; i < mAnimations.Length(); i++) { + if (transformSet.HasProperty(mAnimations[i].property())) { + return true; + } + } + return false; +} + +/* static */ +Maybe AnimationInfo::GetGenerationFromFrame( + nsIFrame* aFrame, DisplayItemType aDisplayItemKey) { + MOZ_ASSERT(aFrame->IsPrimaryFrame() || + nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(aFrame)); + + layers::Layer* layer = + FrameLayerBuilder::GetDedicatedLayer(aFrame, aDisplayItemKey); + if (layer) { + return layer->GetAnimationInfo().GetAnimationGeneration(); + } + + // In case of continuation, KeyframeEffectReadOnly uses its first frame, + // whereas nsDisplayItem uses its last continuation, so we have to use the + // last continuation frame here. + if (nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(aFrame)) { + aFrame = nsLayoutUtils::LastContinuationOrIBSplitSibling(aFrame); + } + RefPtr animationData = + GetWebRenderUserData(aFrame, + (uint32_t)aDisplayItemKey); + if (animationData) { + return animationData->GetAnimationInfo().GetAnimationGeneration(); + } + + return Nothing(); +} + +/* static */ +void AnimationInfo::EnumerateGenerationOnFrame( + const nsIFrame* aFrame, const nsIContent* aContent, + const CompositorAnimatableDisplayItemTypes& aDisplayItemTypes, + AnimationGenerationCallback aCallback) { + if (XRE_IsContentProcess()) { + if (nsIWidget* widget = nsContentUtils::WidgetForContent(aContent)) { + // In case of child processes, we might not have yet created the layer + // manager. That means there is no animation generation we have, thus + // we call the callback function with |Nothing()| for the generation. + // + // Note that we need to use nsContentUtils::WidgetForContent() instead of + // BrowserChild::GetFrom(aFrame->PresShell())->WebWidget() because in the + // case of child popup content PuppetWidget::mBrowserChild is the same as + // the parent's one, which means mBrowserChild->IsLayersConnected() check + // in PuppetWidget::GetLayerManager queries the parent state, it results + // the assertion in the function failure. + if (widget->GetOwningBrowserChild() && + !static_cast(widget)->HasLayerManager()) { + for (auto displayItem : LayerAnimationInfo::sDisplayItemTypes) { + aCallback(Nothing(), displayItem); + } + return; + } + } + } + + RefPtr layerManager = + nsContentUtils::LayerManagerForContent(aContent); + + if (layerManager && + layerManager->GetBackendType() == layers::LayersBackend::LAYERS_WR) { + // In case of continuation, nsDisplayItem uses its last continuation, so we + // have to use the last continuation frame here. + if (nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(aFrame)) { + aFrame = nsLayoutUtils::LastContinuationOrIBSplitSibling(aFrame); + } + + for (auto displayItem : LayerAnimationInfo::sDisplayItemTypes) { + // For transform animations, the animation is on the primary frame but + // |aFrame| is the style frame. + const nsIFrame* frameToQuery = + displayItem == DisplayItemType::TYPE_TRANSFORM + ? nsLayoutUtils::GetPrimaryFrameFromStyleFrame(aFrame) + : aFrame; + RefPtr animationData = + GetWebRenderUserData(frameToQuery, + (uint32_t)displayItem); + Maybe generation; + if (animationData) { + generation = animationData->GetAnimationInfo().GetAnimationGeneration(); + } + aCallback(generation, displayItem); + } + return; + } + + FrameLayerBuilder::EnumerateGenerationForDedicatedLayers(aFrame, aCallback); +} + +static StyleTransformOperation ResolveTranslate( + TransformReferenceBox& aRefBox, const LengthPercentage& aX, + const LengthPercentage& aY = LengthPercentage::Zero(), + const Length& aZ = Length{0}) { + float x = nsStyleTransformMatrix::ProcessTranslatePart( + aX, &aRefBox, &TransformReferenceBox::Width); + float y = nsStyleTransformMatrix::ProcessTranslatePart( + aY, &aRefBox, &TransformReferenceBox::Height); + return StyleTransformOperation::Translate3D( + LengthPercentage::FromPixels(x), LengthPercentage::FromPixels(y), aZ); +} + +static StyleTranslate ResolveTranslate(const StyleTranslate& aValue, + TransformReferenceBox& aRefBox) { + if (aValue.IsTranslate()) { + const auto& t = aValue.AsTranslate(); + float x = nsStyleTransformMatrix::ProcessTranslatePart( + t._0, &aRefBox, &TransformReferenceBox::Width); + float y = nsStyleTransformMatrix::ProcessTranslatePart( + t._1, &aRefBox, &TransformReferenceBox::Height); + return StyleTranslate::Translate(LengthPercentage::FromPixels(x), + LengthPercentage::FromPixels(y), t._2); + } + + MOZ_ASSERT(aValue.IsNone()); + return StyleTranslate::None(); +} + +static StyleTransform ResolveTransformOperations( + const StyleTransform& aTransform, TransformReferenceBox& aRefBox) { + auto convertMatrix = [](const gfx::Matrix4x4& aM) { + return StyleTransformOperation::Matrix3D(StyleGenericMatrix3D{ + aM._11, aM._12, aM._13, aM._14, aM._21, aM._22, aM._23, aM._24, aM._31, + aM._32, aM._33, aM._34, aM._41, aM._42, aM._43, aM._44}); + }; + + Vector result; + MOZ_RELEASE_ASSERT( + result.initCapacity(aTransform.Operations().Length()), + "Allocating vector of transform operations should be successful."); + + for (const StyleTransformOperation& op : aTransform.Operations()) { + switch (op.tag) { + case StyleTransformOperation::Tag::TranslateX: + result.infallibleAppend(ResolveTranslate(aRefBox, op.AsTranslateX())); + break; + case StyleTransformOperation::Tag::TranslateY: + result.infallibleAppend(ResolveTranslate( + aRefBox, LengthPercentage::Zero(), op.AsTranslateY())); + break; + case StyleTransformOperation::Tag::TranslateZ: + result.infallibleAppend( + ResolveTranslate(aRefBox, LengthPercentage::Zero(), + LengthPercentage::Zero(), op.AsTranslateZ())); + break; + case StyleTransformOperation::Tag::Translate: { + const auto& translate = op.AsTranslate(); + result.infallibleAppend( + ResolveTranslate(aRefBox, translate._0, translate._1)); + break; + } + case StyleTransformOperation::Tag::Translate3D: { + const auto& translate = op.AsTranslate3D(); + result.infallibleAppend(ResolveTranslate(aRefBox, translate._0, + translate._1, translate._2)); + break; + } + case StyleTransformOperation::Tag::InterpolateMatrix: { + gfx::Matrix4x4 matrix; + nsStyleTransformMatrix::ProcessInterpolateMatrix(matrix, op, aRefBox); + result.infallibleAppend(convertMatrix(matrix)); + break; + } + case StyleTransformOperation::Tag::AccumulateMatrix: { + gfx::Matrix4x4 matrix; + nsStyleTransformMatrix::ProcessAccumulateMatrix(matrix, op, aRefBox); + result.infallibleAppend(convertMatrix(matrix)); + break; + } + case StyleTransformOperation::Tag::RotateX: + case StyleTransformOperation::Tag::RotateY: + case StyleTransformOperation::Tag::RotateZ: + case StyleTransformOperation::Tag::Rotate: + case StyleTransformOperation::Tag::Rotate3D: + case StyleTransformOperation::Tag::ScaleX: + case StyleTransformOperation::Tag::ScaleY: + case StyleTransformOperation::Tag::ScaleZ: + case StyleTransformOperation::Tag::Scale: + case StyleTransformOperation::Tag::Scale3D: + case StyleTransformOperation::Tag::SkewX: + case StyleTransformOperation::Tag::SkewY: + case StyleTransformOperation::Tag::Skew: + case StyleTransformOperation::Tag::Matrix: + case StyleTransformOperation::Tag::Matrix3D: + case StyleTransformOperation::Tag::Perspective: + result.infallibleAppend(op); + break; + default: + MOZ_ASSERT_UNREACHABLE("Function not handled yet!"); + } + } + + auto transform = StyleTransform{ + StyleOwnedSlice(std::move(result))}; + MOZ_ASSERT(!transform.HasPercent()); + MOZ_ASSERT(transform.Operations().Length() == + aTransform.Operations().Length()); + return transform; +} + +static TimingFunction ToTimingFunction( + const Maybe& aCTF) { + if (aCTF.isNothing()) { + return TimingFunction(null_t()); + } + + if (aCTF->HasSpline()) { + const SMILKeySpline* spline = aCTF->GetFunction(); + return TimingFunction(CubicBezierFunction( + static_cast(spline->X1()), static_cast(spline->Y1()), + static_cast(spline->X2()), static_cast(spline->Y2()))); + } + + return TimingFunction(StepFunction( + aCTF->GetSteps().mSteps, static_cast(aCTF->GetSteps().mPos))); +} + +// FIXME: Bug 1489392: We don't have to normalize the path here if we accept +// the spec issue which would like to normalize svg paths at computed time. +static StyleOffsetPath NormalizeOffsetPath(const StyleOffsetPath& aOffsetPath) { + if (aOffsetPath.IsPath()) { + return StyleOffsetPath::Path( + MotionPathUtils::NormalizeSVGPathData(aOffsetPath.AsPath())); + } + return StyleOffsetPath(aOffsetPath); +} + +static void SetAnimatable(nsCSSPropertyID aProperty, + const AnimationValue& aAnimationValue, + nsIFrame* aFrame, TransformReferenceBox& aRefBox, + layers::Animatable& aAnimatable) { + MOZ_ASSERT(aFrame); + + if (aAnimationValue.IsNull()) { + aAnimatable = null_t(); + return; + } + + switch (aProperty) { + case eCSSProperty_background_color: { + // We don't support color animation on the compositor yet so that we can + // resolve currentColor at this moment. + nscolor foreground = + aFrame->Style()->GetVisitedDependentColor(&nsStyleText::mColor); + aAnimatable = aAnimationValue.GetColor(foreground); + break; + } + case eCSSProperty_opacity: + aAnimatable = aAnimationValue.GetOpacity(); + break; + case eCSSProperty_rotate: + aAnimatable = aAnimationValue.GetRotateProperty(); + break; + case eCSSProperty_scale: + aAnimatable = aAnimationValue.GetScaleProperty(); + break; + case eCSSProperty_translate: + aAnimatable = + ResolveTranslate(aAnimationValue.GetTranslateProperty(), aRefBox); + break; + case eCSSProperty_transform: + aAnimatable = ResolveTransformOperations( + aAnimationValue.GetTransformProperty(), aRefBox); + break; + case eCSSProperty_offset_path: + aAnimatable = + NormalizeOffsetPath(aAnimationValue.GetOffsetPathProperty()); + break; + case eCSSProperty_offset_distance: + aAnimatable = aAnimationValue.GetOffsetDistanceProperty(); + break; + case eCSSProperty_offset_rotate: + aAnimatable = aAnimationValue.GetOffsetRotateProperty(); + break; + case eCSSProperty_offset_anchor: + aAnimatable = aAnimationValue.GetOffsetAnchorProperty(); + break; + default: + MOZ_ASSERT_UNREACHABLE("Unsupported property"); + } +} + +void AnimationInfo::AddAnimationForProperty( + nsIFrame* aFrame, const AnimationProperty& aProperty, + dom::Animation* aAnimation, const Maybe& aTransformData, + Send aSendFlag) { + MOZ_ASSERT(aAnimation->GetEffect(), + "Should not be adding an animation without an effect"); + MOZ_ASSERT(!aAnimation->GetCurrentOrPendingStartTime().IsNull() || + !aAnimation->IsPlaying() || + (aAnimation->GetTimeline() && + aAnimation->GetTimeline()->TracksWallclockTime()), + "If the animation has an unresolved start time it should either" + " be static (so we don't need a start time) or else have a" + " timeline capable of converting TimeStamps (so we can calculate" + " one later"); + + layers::Animation* animation = (aSendFlag == Send::NextTransaction) + ? AddAnimationForNextTransaction() + : AddAnimation(); + + const TimingParams& timing = aAnimation->GetEffect()->SpecifiedTiming(); + + // If we are starting a new transition that replaces an existing transition + // running on the compositor, it is possible that the animation on the + // compositor will have advanced ahead of the main thread. If we use as + // the starting point of the new transition, the current value of the + // replaced transition as calculated on the main thread using the refresh + // driver time, the new transition will jump when it starts. Instead, we + // re-calculate the starting point of the new transition by applying the + // current TimeStamp to the parameters of the replaced transition. + // + // We need to do this here, rather than when we generate the new transition, + // since after generating the new transition other requestAnimationFrame + // callbacks may run that introduce further lag between the main thread and + // the compositor. + dom::CSSTransition* cssTransition = aAnimation->AsCSSTransition(); + if (cssTransition) { + cssTransition->UpdateStartValueFromReplacedTransition(); + } + + animation->originTime() = + !aAnimation->GetTimeline() + ? TimeStamp() + : aAnimation->GetTimeline()->ToTimeStamp(TimeDuration()); + + dom::Nullable startTime = + aAnimation->GetCurrentOrPendingStartTime(); + if (startTime.IsNull()) { + animation->startTime() = Nothing(); + } else { + animation->startTime() = Some(startTime.Value()); + } + + animation->holdTime() = aAnimation->GetCurrentTimeAsDuration().Value(); + + const ComputedTiming computedTiming = + aAnimation->GetEffect()->GetComputedTiming(); + animation->delay() = timing.Delay(); + animation->endDelay() = timing.EndDelay(); + animation->duration() = computedTiming.mDuration; + animation->iterations() = static_cast(computedTiming.mIterations); + animation->iterationStart() = + static_cast(computedTiming.mIterationStart); + animation->direction() = static_cast(timing.Direction()); + animation->fillMode() = static_cast(computedTiming.mFill); + animation->property() = aProperty.mProperty; + animation->playbackRate() = + static_cast(aAnimation->CurrentOrPendingPlaybackRate()); + animation->previousPlaybackRate() = + aAnimation->HasPendingPlaybackRate() + ? static_cast(aAnimation->PlaybackRate()) + : std::numeric_limits::quiet_NaN(); + animation->transformData() = aTransformData; + animation->easingFunction() = ToTimingFunction(timing.TimingFunction()); + animation->iterationComposite() = static_cast( + aAnimation->GetEffect()->AsKeyframeEffect()->IterationComposite()); + animation->isNotPlaying() = !aAnimation->IsPlaying(); + animation->isNotAnimating() = false; + + TransformReferenceBox refBox(aFrame); + + // If the animation is additive or accumulates, we need to pass its base value + // to the compositor. + + AnimationValue baseStyle = + aAnimation->GetEffect()->AsKeyframeEffect()->BaseStyle( + aProperty.mProperty); + if (!baseStyle.IsNull()) { + SetAnimatable(aProperty.mProperty, baseStyle, aFrame, refBox, + animation->baseStyle()); + } else { + animation->baseStyle() = null_t(); + } + + for (uint32_t segIdx = 0; segIdx < aProperty.mSegments.Length(); segIdx++) { + const AnimationPropertySegment& segment = aProperty.mSegments[segIdx]; + + AnimationSegment* animSegment = animation->segments().AppendElement(); + SetAnimatable(aProperty.mProperty, segment.mFromValue, aFrame, refBox, + animSegment->startState()); + SetAnimatable(aProperty.mProperty, segment.mToValue, aFrame, refBox, + animSegment->endState()); + + animSegment->startPortion() = segment.mFromKey; + animSegment->endPortion() = segment.mToKey; + animSegment->startComposite() = + static_cast(segment.mFromComposite); + animSegment->endComposite() = static_cast(segment.mToComposite); + animSegment->sampleFn() = ToTimingFunction(segment.mTimingFunction); + } +} + +// Let's use an example to explain this function: +// +// We have 4 playing animations (without any !important rule or transition): +// Animation A: [ transform, rotate ]. +// Animation B: [ rotate, scale ]. +// Animation C: [ transform, margin-left ]. +// Animation D: [ opacity, margin-left ]. +// +// Normally, GetAnimationsForCompositor(|transform-like properties|) returns: +// [ Animation A, Animation B, Animation C ], which is the first argument of +// this function. +// +// In this function, we want to re-organize the list as (Note: don't care +// the order of properties): +// [ +// { rotate: [ Animation A, Animation B ] }, +// { scale: [ Animation B ] }, +// { transform: [ Animation A, Animation C ] }, +// ] +// +// Therefore, AddAnimationsForProperty() will append each animation property +// into AnimationInfo, as a final list of layers::Animation: +// [ +// { rotate: Animation A }, +// { rotate: Animation B }, +// { scale: Animation B }, +// { transform: Animation A }, +// { transform: Animation C }, +// ] +// +// And then, for each transaction, we send this list to the compositor thread. +static HashMap>> +GroupAnimationsByProperty(const nsTArray>& aAnimations, + const nsCSSPropertyIDSet& aPropertySet) { + HashMap>> groupedAnims; + for (const RefPtr& anim : aAnimations) { + const dom::KeyframeEffect* effect = anim->GetEffect()->AsKeyframeEffect(); + MOZ_ASSERT(effect); + for (const AnimationProperty& property : effect->Properties()) { + if (!aPropertySet.HasProperty(property.mProperty)) { + continue; + } + + auto animsForPropertyPtr = groupedAnims.lookupForAdd(property.mProperty); + if (!animsForPropertyPtr) { + DebugOnly rv = + groupedAnims.add(animsForPropertyPtr, property.mProperty, + nsTArray>()); + MOZ_ASSERT(rv, "Should have enough memory"); + } + animsForPropertyPtr->value().AppendElement(anim); + } + } + return groupedAnims; +} + +bool AnimationInfo::AddAnimationsForProperty( + nsIFrame* aFrame, const EffectSet* aEffects, + const nsTArray>& aCompositorAnimations, + const Maybe& aTransformData, nsCSSPropertyID aProperty, + Send aSendFlag, LayerManager* aLayerManager) { + bool addedAny = false; + // Add from first to last (since last overrides) + for (dom::Animation* anim : aCompositorAnimations) { + if (!anim->IsRelevant()) { + continue; + } + + dom::KeyframeEffect* keyframeEffect = + anim->GetEffect() ? anim->GetEffect()->AsKeyframeEffect() : nullptr; + MOZ_ASSERT(keyframeEffect, + "A playing animation should have a keyframe effect"); + const AnimationProperty* property = + keyframeEffect->GetEffectiveAnimationOfProperty(aProperty, *aEffects); + if (!property) { + continue; + } + + // Note that if the property is overridden by !important rules, + // GetEffectiveAnimationOfProperty returns null instead. + // This is what we want, since if we have animations overridden by + // !important rules, we don't want to send them to the compositor. + MOZ_ASSERT( + anim->CascadeLevel() != EffectCompositor::CascadeLevel::Animations || + !aEffects->PropertiesWithImportantRules().HasProperty(aProperty), + "GetEffectiveAnimationOfProperty already tested the property " + "is not overridden by !important rules"); + + // Don't add animations that are pending if their timeline does not + // track wallclock time. This is because any pending animations on layers + // will have their start time updated with the current wallclock time. + // If we can't convert that wallclock time back to an equivalent timeline + // time, we won't be able to update the content animation and it will end + // up being out of sync with the layer animation. + // + // Currently this only happens when the timeline is driven by a refresh + // driver under test control. In this case, the next time the refresh + // driver is advanced it will trigger any pending animations. + if (anim->Pending() && + (anim->GetTimeline() && !anim->GetTimeline()->TracksWallclockTime())) { + continue; + } + + AddAnimationForProperty(aFrame, *property, anim, aTransformData, aSendFlag); + keyframeEffect->SetIsRunningOnCompositor(aProperty, true); + addedAny = true; + if (aTransformData && aTransformData->partialPrerenderData() && + aLayerManager) { + aLayerManager->AddPartialPrerenderedAnimation(GetCompositorAnimationsId(), + anim); + } + } + return addedAny; +} + +// Returns which pre-rendered area's sides are overflowed from the pre-rendered +// rect. +// +// We don't need to make jank animations when we are going to composite the +// area where there is no overflowed area even if it's outside of the +// pre-rendered area. +static SideBits GetOverflowedSides(const nsRect& aOverflow, + const nsRect& aPartialPrerenderArea) { + SideBits sides = SideBits::eNone; + if (aOverflow.X() < aPartialPrerenderArea.X()) { + sides |= SideBits::eLeft; + } + if (aOverflow.Y() < aPartialPrerenderArea.Y()) { + sides |= SideBits::eTop; + } + if (aOverflow.XMost() > aPartialPrerenderArea.XMost()) { + sides |= SideBits::eRight; + } + if (aOverflow.YMost() > aPartialPrerenderArea.YMost()) { + sides |= SideBits::eBottom; + } + return sides; +} + +static std::pair +GetClipRectAndTransformForPartialPrerender( + const nsIFrame* aFrame, int32_t aDevPixelsToAppUnits, + const nsIFrame* aClipFrame, const nsIScrollableFrame* aScrollFrame) { + MOZ_ASSERT(aClipFrame); + + gfx::Matrix4x4 transformInClip = + nsLayoutUtils::GetTransformToAncestor(RelativeTo{aFrame->GetParent()}, + RelativeTo{aClipFrame}) + .GetMatrix(); + if (aScrollFrame) { + transformInClip.PostTranslate( + LayoutDevicePoint::FromAppUnits(aScrollFrame->GetScrollPosition(), + aDevPixelsToAppUnits) + .ToUnknownPoint()); + } + + // We don't necessarily use nsLayoutUtils::CalculateCompositionSizeForFrame + // since this is a case where we don't use APZ at all. + return std::make_pair( + LayoutDeviceRect::FromAppUnits(aScrollFrame + ? aScrollFrame->GetScrollPortRect() + : aClipFrame->GetRectRelativeToSelf(), + aDevPixelsToAppUnits) * + LayoutDeviceToLayerScale2D() * LayerToParentLayerScale(), + transformInClip); +} + +static PartialPrerenderData GetPartialPrerenderData( + const nsIFrame* aFrame, const nsDisplayItem* aItem) { + const nsRect& partialPrerenderedRect = aItem->GetUntransformedPaintRect(); + nsRect overflow = aFrame->InkOverflowRectRelativeToSelf(); + + ScrollableLayerGuid::ViewID scrollId = ScrollableLayerGuid::NULL_SCROLL_ID; + + const nsIFrame* clipFrame = + nsLayoutUtils::GetNearestOverflowClipFrame(aFrame->GetParent()); + const nsIScrollableFrame* scrollFrame = do_QueryFrame(clipFrame); + + if (!clipFrame) { + // If there is no suitable clip frame in the same document, use the + // root one. + scrollFrame = aFrame->PresShell()->GetRootScrollFrameAsScrollable(); + if (scrollFrame) { + clipFrame = do_QueryFrame(scrollFrame); + } else { + // If there is no root scroll frame, use the viewport frame. + clipFrame = aFrame->PresShell()->GetRootFrame(); + } + } + + // If the scroll frame is asyncronously scrollable, try to find the scroll id. + if (scrollFrame && + !scrollFrame->GetScrollStyles().IsHiddenInBothDirections() && + nsLayoutUtils::AsyncPanZoomEnabled(aFrame)) { + const bool isInPositionFixed = + nsLayoutUtils::IsInPositionFixedSubtree(aFrame); + const ActiveScrolledRoot* asr = aItem->GetActiveScrolledRoot(); + const nsIFrame* asrScrollableFrame = + asr ? do_QueryFrame(asr->mScrollableFrame) : nullptr; + if (!isInPositionFixed && asr && + aFrame->PresContext() == asrScrollableFrame->PresContext()) { + scrollId = asr->GetViewId(); + MOZ_ASSERT(clipFrame == asrScrollableFrame); + } else { + // Use the root scroll id in the same document if the target frame is in + // position:fixed subtree or there is no ASR or the ASR is in a different + // ancestor document. + scrollId = + nsLayoutUtils::ScrollIdForRootScrollFrame(aFrame->PresContext()); + MOZ_ASSERT(clipFrame == aFrame->PresShell()->GetRootScrollFrame()); + } + } + + int32_t devPixelsToAppUnits = aFrame->PresContext()->AppUnitsPerDevPixel(); + + auto [clipRect, transformInClip] = GetClipRectAndTransformForPartialPrerender( + aFrame, devPixelsToAppUnits, clipFrame, scrollFrame); + + return PartialPrerenderData{ + LayoutDeviceRect::FromAppUnits(partialPrerenderedRect, + devPixelsToAppUnits), + GetOverflowedSides(overflow, partialPrerenderedRect), + scrollId, + clipRect, + transformInClip, + LayoutDevicePoint()}; // will be set by caller. +} + +enum class AnimationDataType { + WithMotionPath, + WithoutMotionPath, +}; +static Maybe CreateAnimationData( + nsIFrame* aFrame, nsDisplayItem* aItem, DisplayItemType aType, + layers::LayersBackend aLayersBackend, AnimationDataType aDataType, + const Maybe& aPosition) { + if (aType != DisplayItemType::TYPE_TRANSFORM) { + return Nothing(); + } + + // XXX Performance here isn't ideal for SVG. We'd prefer to avoid resolving + // the dimensions of refBox. That said, we only get here if there are CSS + // animations or transitions on this element, and that is likely to be a + // lot rarer than transforms on SVG (the frequency of which drives the need + // for TransformReferenceBox). + TransformReferenceBox refBox(aFrame); + const nsRect bounds(0, 0, refBox.Width(), refBox.Height()); + + // all data passed directly to the compositor should be in dev pixels + int32_t devPixelsToAppUnits = aFrame->PresContext()->AppUnitsPerDevPixel(); + float scale = devPixelsToAppUnits; + gfx::Point3D offsetToTransformOrigin = + nsDisplayTransform::GetDeltaToTransformOrigin(aFrame, refBox, scale); + nsPoint origin; + if (aLayersBackend == layers::LayersBackend::LAYERS_WR) { + // leave origin empty, because we are sending it separately on the + // stacking context that we are pushing to WR, and WR will automatically + // include it when picking up the animated transform values + } else if (aItem) { + // This branch is for display items to leverage the cache of + // nsDisplayListBuilder. + origin = aItem->ToReferenceFrame(); + } else { + // This branch is running for restyling. + // Animations are animated at the coordination of the reference + // frame outside, not the given frame itself. The given frame + // is also reference frame too, so the parent's reference frame + // are used. + nsIFrame* referenceFrame = nsLayoutUtils::GetReferenceFrame( + nsLayoutUtils::GetCrossDocParentFrame(aFrame)); + origin = aFrame->GetOffsetToCrossDoc(referenceFrame); + } + + Maybe motionPathData; + if (aDataType == AnimationDataType::WithMotionPath) { + const StyleTransformOrigin& styleOrigin = + aFrame->StyleDisplay()->mTransformOrigin; + CSSPoint motionPathOrigin = nsStyleTransformMatrix::Convert2DPosition( + styleOrigin.horizontal, styleOrigin.vertical, refBox); + CSSPoint anchorAdjustment = + MotionPathUtils::ComputeAnchorPointAdjustment(*aFrame); + + motionPathData = Some(layers::MotionPathData( + motionPathOrigin, anchorAdjustment, RayReferenceData(aFrame))); + } + + Maybe partialPrerenderData; + if (aItem && static_cast(aItem)->IsPartialPrerender()) { + partialPrerenderData = Some(GetPartialPrerenderData(aFrame, aItem)); + + if (aLayersBackend == layers::LayersBackend::LAYERS_WR) { + MOZ_ASSERT(aPosition); + partialPrerenderData->position() = *aPosition; + } + } + + return Some(TransformData(origin, offsetToTransformOrigin, bounds, + devPixelsToAppUnits, motionPathData, + partialPrerenderData)); +} + +void AnimationInfo::AddNonAnimatingTransformLikePropertiesStyles( + const nsCSSPropertyIDSet& aNonAnimatingProperties, nsIFrame* aFrame, + Send aSendFlag) { + auto appendFakeAnimation = [this, aSendFlag](nsCSSPropertyID aProperty, + Animatable&& aBaseStyle) { + layers::Animation* animation = (aSendFlag == Send::NextTransaction) + ? AddAnimationForNextTransaction() + : AddAnimation(); + animation->property() = aProperty; + animation->baseStyle() = std::move(aBaseStyle); + animation->easingFunction() = null_t(); + animation->isNotAnimating() = true; + }; + + const nsStyleDisplay* display = aFrame->StyleDisplay(); + // A simple optimization. We don't need to send offset-* properties if we + // don't have offset-path and offset-position. + // FIXME: Bug 1559232: Add offset-position here. + bool hasMotion = + !display->mOffsetPath.IsNone() || + !aNonAnimatingProperties.HasProperty(eCSSProperty_offset_path); + + for (nsCSSPropertyID id : aNonAnimatingProperties) { + switch (id) { + case eCSSProperty_transform: + if (!display->mTransform.IsNone()) { + TransformReferenceBox refBox(aFrame); + appendFakeAnimation( + id, ResolveTransformOperations(display->mTransform, refBox)); + } + break; + case eCSSProperty_translate: + if (!display->mTranslate.IsNone()) { + TransformReferenceBox refBox(aFrame); + appendFakeAnimation(id, + ResolveTranslate(display->mTranslate, refBox)); + } + break; + case eCSSProperty_rotate: + if (!display->mRotate.IsNone()) { + appendFakeAnimation(id, display->mRotate); + } + break; + case eCSSProperty_scale: + if (!display->mScale.IsNone()) { + appendFakeAnimation(id, display->mScale); + } + break; + case eCSSProperty_offset_path: + if (!display->mOffsetPath.IsNone()) { + appendFakeAnimation(id, NormalizeOffsetPath(display->mOffsetPath)); + } + break; + case eCSSProperty_offset_distance: + if (hasMotion && !display->mOffsetDistance.IsDefinitelyZero()) { + appendFakeAnimation(id, display->mOffsetDistance); + } + break; + case eCSSProperty_offset_rotate: + if (hasMotion && (!display->mOffsetRotate.auto_ || + display->mOffsetRotate.angle.ToDegrees() != 0.0)) { + appendFakeAnimation(id, display->mOffsetRotate); + } + break; + case eCSSProperty_offset_anchor: + if (hasMotion && !display->mOffsetAnchor.IsAuto()) { + appendFakeAnimation(id, display->mOffsetAnchor); + } + break; + default: + MOZ_ASSERT_UNREACHABLE("Unsupported transform-like properties"); + } + } +} + +void AnimationInfo::AddAnimationsForDisplayItem( + nsIFrame* aFrame, nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem, + DisplayItemType aType, LayerManager* aLayerManager, + const Maybe& aPosition) { + Send sendFlag = !aBuilder ? Send::NextTransaction : Send::Immediate; + if (sendFlag == Send::NextTransaction) { + ClearAnimationsForNextTransaction(); + } else { + ClearAnimations(); + } + + // Update the animation generation on the layer. We need to do this before + // any early returns since even if we don't add any animations to the + // layer, we still need to mark it as up-to-date with regards to animations. + // Otherwise, in RestyleManager we'll notice the discrepancy between the + // animation generation numbers and update the layer indefinitely. + EffectSet* effects = EffectSet::GetEffectSetForFrame(aFrame, aType); + uint64_t animationGeneration = + effects ? effects->GetAnimationGeneration() : 0; + SetAnimationGeneration(animationGeneration); + if (!effects || effects->IsEmpty()) { + return; + } + + EffectCompositor::ClearIsRunningOnCompositor(aFrame, aType); + const nsCSSPropertyIDSet& propertySet = + LayerAnimationInfo::GetCSSPropertiesFor(aType); + const nsTArray> matchedAnimations = + EffectCompositor::GetAnimationsForCompositor(aFrame, propertySet); + if (matchedAnimations.IsEmpty()) { + return; + } + + // If the frame is not prerendered, bail out. + // Do this check only during layer construction; during updating the + // caller is required to check it appropriately. + if (aItem && !aItem->CanUseAsyncAnimations(aBuilder)) { + // EffectCompositor needs to know that we refused to run this animation + // asynchronously so that it will not throttle the main thread + // animation. + aFrame->SetProperty(nsIFrame::RefusedAsyncAnimationProperty(), true); + return; + } + + const HashMap>> + compositorAnimations = + GroupAnimationsByProperty(matchedAnimations, propertySet); + Maybe transformData = + CreateAnimationData(aFrame, aItem, aType, aLayerManager->GetBackendType(), + compositorAnimations.has(eCSSProperty_offset_path) || + !aFrame->StyleDisplay()->mOffsetPath.IsNone() + ? AnimationDataType::WithMotionPath + : AnimationDataType::WithoutMotionPath, + aPosition); + // Bug 1424900: Drop this pref check after shipping individual transforms. + // Bug 1582554: Drop this pref check after shipping motion path. + const bool hasMultipleTransformLikeProperties = + (StaticPrefs::layout_css_individual_transform_enabled() || + StaticPrefs::layout_css_motion_path_enabled()) && + aType == DisplayItemType::TYPE_TRANSFORM; + nsCSSPropertyIDSet nonAnimatingProperties = + nsCSSPropertyIDSet::TransformLikeProperties(); + for (auto iter = compositorAnimations.iter(); !iter.done(); iter.next()) { + // Note: We can skip offset-* if there is no offset-path/offset-position + // animations and styles. However, it should be fine and may be better to + // send these information to the compositor because 1) they are simple data + // structure, 2) AddAnimationsForProperty() marks these animations as + // running on the composiror, so CanThrottle() returns true for them, and + // we avoid running these animations on the main thread. + bool added = AddAnimationsForProperty(aFrame, effects, iter.get().value(), + transformData, iter.get().key(), + sendFlag, aLayerManager); + if (added && transformData) { + // Only copy TransformLikeMetaData in the first animation property. + transformData.reset(); + } + + if (hasMultipleTransformLikeProperties && added) { + nonAnimatingProperties.RemoveProperty(iter.get().key()); + } + } + + // If some transform-like properties have animations, but others not, and + // those non-animating transform-like properties have non-none + // transform/translate/rotate/scale styles or non-initial value for motion + // path properties, we also pass their styles into the compositor, so the + // final transform matrix (on the compositor) could take them into account. + if (hasMultipleTransformLikeProperties && + // For these cases we don't need to send the property style values to + // the compositor: + // 1. No property has running animations on the compositor. (i.e. All + // properties should be handled by main thread) + // 2. All properties have running animations on the compositor. + // (i.e. Those running animations should override the styles.) + !nonAnimatingProperties.Equals( + nsCSSPropertyIDSet::TransformLikeProperties()) && + !nonAnimatingProperties.IsEmpty()) { + AddNonAnimatingTransformLikePropertiesStyles(nonAnimatingProperties, aFrame, + sendFlag); + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/AnimationInfo.h b/gfx/layers/AnimationInfo.h new file mode 100644 index 0000000000..d47a48106e --- /dev/null +++ b/gfx/layers/AnimationInfo.h @@ -0,0 +1,168 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_ANIMATIONINFO_H +#define GFX_ANIMATIONINFO_H + +#include "nsCSSPropertyIDSet.h" +#include "nsDisplayItemTypes.h" +#include "mozilla/Array.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/FunctionRef.h" +#include "mozilla/layers/AnimationStorageData.h" +#include "mozilla/layers/LayersMessages.h" // for TransformData + +struct RawServoAnimationValue; +class nsIContent; +class nsIFrame; +class nsDisplayListBuilder; +class nsDisplayItem; + +namespace mozilla { + +class EffectSet; +struct AnimationProperty; + +namespace dom { +class Animation; +} // namespace dom + +namespace gfx { +class Path; +} // namespace gfx + +namespace layers { + +class Animation; +class CompositorAnimations; +class Layer; +class LayerManager; +struct CompositorAnimationData; +struct PropertyAnimationGroup; + +class AnimationInfo final { + typedef nsTArray AnimationArray; + + public: + AnimationInfo(); + ~AnimationInfo(); + + // Ensure that this AnimationInfo has a valid (non-zero) animations id. This + // value is unique across layers. + void EnsureAnimationsId(); + + // Call AddAnimation to add a new animation to this layer from layout code. + // Caller must fill in all the properties of the returned animation. + // A later animation overrides an earlier one. + Animation* AddAnimation(); + + // These are a parallel to AddAnimation and clearAnimations, except + // they add pending animations that apply only when the next + // transaction is begun. (See also + // SetBaseTransformForNextTransaction.) + Animation* AddAnimationForNextTransaction(); + + void SetAnimationGeneration(uint64_t aCount) { + mAnimationGeneration = Some(aCount); + } + Maybe GetAnimationGeneration() const { + return mAnimationGeneration; + } + + // ClearAnimations clears animations on this layer. + void ClearAnimations(); + void ClearAnimationsForNextTransaction(); + void SetCompositorAnimations( + const LayersId& aLayersId, + const CompositorAnimations& aCompositorAnimations); + bool StartPendingAnimations(const TimeStamp& aReadyTime); + void TransferMutatedFlagToLayer(Layer* aLayer); + + uint64_t GetCompositorAnimationsId() { return mCompositorAnimationsId; } + // Note: We don't set mAnimations on the compositor thread, so this will + // always return an empty array on the compositor thread. + AnimationArray& GetAnimations() { return mAnimations; } + nsTArray& GetPropertyAnimationGroups() { + return mStorageData.mAnimation; + } + const Maybe& GetTransformData() const { + return mStorageData.mTransformData; + } + const LayersId& GetLayersId() const { return mStorageData.mLayersId; } + bool ApplyPendingUpdatesForThisTransaction(); + bool HasTransformAnimation() const; + + gfx::Path* CachedMotionPath() { return mStorageData.mCachedMotionPath; } + + // In case of continuation, |aFrame| must be the first or the last + // continuation frame, otherwise this function might return Nothing(). + static Maybe GetGenerationFromFrame( + nsIFrame* aFrame, DisplayItemType aDisplayItemKey); + + using CompositorAnimatableDisplayItemTypes = + Array; + using AnimationGenerationCallback = FunctionRef& aGeneration, DisplayItemType aDisplayItemType)>; + // Enumerates animation generations on |aFrame| for the given display item + // types and calls |aCallback| with the animation generation. + // + // The enumeration stops if |aCallback| returns false. + static void EnumerateGenerationOnFrame( + const nsIFrame* aFrame, const nsIContent* aContent, + const CompositorAnimatableDisplayItemTypes& aDisplayItemTypes, + AnimationGenerationCallback); + + void AddAnimationsForDisplayItem( + nsIFrame* aFrame, nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem, + DisplayItemType aType, LayerManager* aLayerManager, + const Maybe& aPosition = Nothing()); + + private: + enum class Send { + NextTransaction, + Immediate, + }; + void AddAnimationForProperty(nsIFrame* aFrame, + const AnimationProperty& aProperty, + dom::Animation* aAnimation, + const Maybe& aTransformData, + Send aSendFlag); + + bool AddAnimationsForProperty( + nsIFrame* aFrame, const EffectSet* aEffects, + const nsTArray>& aCompositorAnimations, + const Maybe& aTransformData, nsCSSPropertyID aProperty, + Send aSendFlag, LayerManager* aLayerManager); + + void AddNonAnimatingTransformLikePropertiesStyles( + const nsCSSPropertyIDSet& aNonAnimatingProperties, nsIFrame* aFrame, + Send aSendFlag); + + protected: + // mAnimations (and mPendingAnimations) are only set on the main thread. + // + // Once the animations are received on the compositor thread/process we + // use AnimationHelper::ExtractAnimations to transform the rather compact + // representation of animation data we transfer into something we can more + // readily use for sampling and then store it in mPropertyAnimationGroups + // (below) or CompositorAnimationStorage.mAnimations for WebRender. + AnimationArray mAnimations; + UniquePtr mPendingAnimations; + + uint64_t mCompositorAnimationsId; + // The extracted data produced by AnimationHelper::ExtractAnimations(). + AnimationStorageData mStorageData; + // If this layer is used for OMTA, then this counter is used to ensure we + // stay in sync with the animation manager + Maybe mAnimationGeneration; + bool mMutated; +}; + +} // namespace layers +} // namespace mozilla + +#endif // GFX_ANIMATIONINFO_H diff --git a/gfx/layers/AnimationStorageData.h b/gfx/layers/AnimationStorageData.h new file mode 100644 index 0000000000..47e9981d2d --- /dev/null +++ b/gfx/layers/AnimationStorageData.h @@ -0,0 +1,114 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_AnimationStorageData_h +#define mozilla_layers_AnimationStorageData_h + +#include "mozilla/dom/Nullable.h" +#include "mozilla/ComputedTimingFunction.h" // for ComputedTimingFunction +#include "mozilla/layers/LayersMessages.h" // for TransformData, etc +#include "mozilla/layers/LayersTypes.h" // for LayersId +#include "mozilla/TimeStamp.h" // for TimeStamp +#include "mozilla/TimingParams.h" +#include "X11UndefineNone.h" + +namespace mozilla { + +namespace dom { +enum class CompositeOperation : uint8_t; +enum class IterationCompositeOperation : uint8_t; +}; // namespace dom + +namespace layers { + +struct PropertyAnimation { + struct SegmentData { + RefPtr mStartValue; + RefPtr mEndValue; + Maybe mFunction; + float mStartPortion; + float mEndPortion; + dom::CompositeOperation mStartComposite; + dom::CompositeOperation mEndComposite; + }; + nsTArray mSegments; + TimingParams mTiming; + + // These two variables correspond to the variables of the same name in + // KeyframeEffectReadOnly and are used for the same purpose: to skip composing + // animations whose progress has not changed. + dom::Nullable mProgressOnLastCompose; + uint64_t mCurrentIterationOnLastCompose = 0; + // These two variables are used for a similar optimization above but are + // applied to the timing function in each keyframe. + uint32_t mSegmentIndexOnLastCompose = 0; + dom::Nullable mPortionInSegmentOnLastCompose; + + TimeStamp mOriginTime; + Maybe mStartTime; + TimeDuration mHoldTime; + float mPlaybackRate; + dom::IterationCompositeOperation mIterationComposite; + bool mIsNotPlaying; + + void ResetLastCompositionValues() { + mCurrentIterationOnLastCompose = 0; + mSegmentIndexOnLastCompose = 0; + mProgressOnLastCompose.SetNull(); + mPortionInSegmentOnLastCompose.SetNull(); + } +}; + +struct PropertyAnimationGroup { + nsCSSPropertyID mProperty; + + nsTArray mAnimations; + RefPtr mBaseStyle; + + bool IsEmpty() const { return mAnimations.IsEmpty(); } + void Clear() { + mAnimations.Clear(); + mBaseStyle = nullptr; + } + void ResetLastCompositionValues() { + for (PropertyAnimation& animation : mAnimations) { + animation.ResetLastCompositionValues(); + } + } +}; + +struct AnimationStorageData { + // Each entry in the array represents an animation list for one property. For + // transform-like properties (e.g. transform, rotate etc.), there may be + // multiple entries depending on how many transform-like properties we have. + nsTArray mAnimation; + Maybe mTransformData; + // For motion path. We cached the gfx path for optimization. + RefPtr mCachedMotionPath; + // This is used to communicate with the main-thread. E.g. to tell the fact + // that this animation needs to be pre-rendered again on the main-thread, etc. + LayersId mLayersId; + + AnimationStorageData() = default; + AnimationStorageData(AnimationStorageData&& aOther) = default; + AnimationStorageData& operator=(AnimationStorageData&& aOther) = default; + + // Avoid any copy because mAnimation could be a large array. + AnimationStorageData(const AnimationStorageData& aOther) = delete; + AnimationStorageData& operator=(const AnimationStorageData& aOther) = delete; + + bool IsEmpty() const { return mAnimation.IsEmpty(); } + void Clear() { + mAnimation.Clear(); + mTransformData.reset(); + mCachedMotionPath = nullptr; + } +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_AnimationStorageData_h diff --git a/gfx/layers/AtomicRefCountedWithFinalize.h b/gfx/layers/AtomicRefCountedWithFinalize.h new file mode 100644 index 0000000000..caae189212 --- /dev/null +++ b/gfx/layers/AtomicRefCountedWithFinalize.h @@ -0,0 +1,207 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_ATOMICREFCOUNTEDWITHFINALIZE_H_ +#define MOZILLA_ATOMICREFCOUNTEDWITHFINALIZE_H_ + +#include "mozilla/RefPtr.h" +#include "mozilla/Likely.h" +#include "MainThreadUtils.h" +#include "base/message_loop.h" +#include "base/task.h" +#include "mozilla/gfx/Logging.h" + +#define ADDREF_MANUALLY(obj) \ + (obj)->AddRefManually(__FUNCTION__, __FILE__, __LINE__) +#define RELEASE_MANUALLY(obj) \ + (obj)->ReleaseManually(__FUNCTION__, __FILE__, __LINE__) + +namespace mozilla { + +template +class StaticRefPtr; + +namespace gl { +template +class RefSet; + +template +class RefQueue; +} // namespace gl + +template +class AtomicRefCountedWithFinalize { + protected: + explicit AtomicRefCountedWithFinalize(const char* aName) + : mRecycleCallback(nullptr), + mClosure(nullptr), + mRefCount(0) +#ifdef DEBUG + , + mSpew(false), + mManualAddRefs(0), + mManualReleases(0) +#endif +#ifdef NS_BUILD_REFCNT_LOGGING + , + mName(aName) +#endif + { + } + + ~AtomicRefCountedWithFinalize() { + if (mRefCount >= 0) { + gfxCriticalError() << "Deleting referenced object? " << mRefCount; + } + } + + public: + // Mark user classes that are considered flawless. + template + friend class ::mozilla::StaticRefPtr; + + template + friend struct mozilla::RefPtrTraits; + + template + friend class ::mozilla::gl::RefSet; + + template + friend class ::mozilla::gl::RefQueue; + + // friend class mozilla::gl::SurfaceFactory; + + void AddRefManually(const char* funcName, const char* fileName, + uint32_t lineNum) { +#ifdef DEBUG + uint32_t count = ++mManualAddRefs; + if (mSpew) { + printf_stderr("AddRefManually() #%u in %s at %s:%u\n", count, funcName, + fileName, lineNum); + } +#else + (void)funcName; + (void)fileName; + (void)lineNum; +#endif + AddRef(); + } + + void ReleaseManually(const char* funcName, const char* fileName, + uint32_t lineNum) { +#ifdef DEBUG + uint32_t count = ++mManualReleases; + if (mSpew) { + printf_stderr("ReleaseManually() #%u in %s at %s:%u\n", count, funcName, + fileName, lineNum); + } +#else + (void)funcName; + (void)fileName; + (void)lineNum; +#endif + Release(); + } + + private: + void AddRef() { + MOZ_ASSERT(mRefCount >= 0, "AddRef() during/after Finalize()/dtor."); +#ifdef NS_BUILD_REFCNT_LOGGING + int currCount = ++mRefCount; + NS_LOG_ADDREF(this, currCount, mName, sizeof(*this)); +#else + ++mRefCount; +#endif + } + + void Release() { + MOZ_ASSERT(mRefCount > 0, "Release() during/after Finalize()/dtor."); + // Read mRecycleCallback early so that it does not get set to + // deleted memory, if the object is goes away. See bug 994903. + // This saves us in the case where there is no callback, so that + // we can do the "else if" below. + RecycleCallback recycleCallback = mRecycleCallback; + int currCount = --mRefCount; + if (currCount < 0) { + gfxCriticalError() << "Invalid reference count release" << currCount; + ++mRefCount; + return; + } +#ifdef NS_BUILD_REFCNT_LOGGING + NS_LOG_RELEASE(this, currCount, mName); +#endif + + if (0 == currCount) { + mRefCount = detail::DEAD; + MOZ_ASSERT(IsDead()); + + // Recycle listeners must call ClearRecycleCallback + // before releasing their strong reference. + if (mRecycleCallback) { + gfxCriticalError() << "About to release with valid callback"; + mRecycleCallback = nullptr; + } + + MOZ_ASSERT(mManualAddRefs == mManualReleases); + + T* derived = static_cast(this); + derived->Finalize(); + delete derived; + } else if (1 == currCount && recycleCallback) { + // There is nothing enforcing this in the code, except how the callers + // are being careful to never let the reference count go down if there + // is a callback. + MOZ_ASSERT(!IsDead()); + T* derived = static_cast(this); + recycleCallback(derived, mClosure); + } + } + + public: + typedef void (*RecycleCallback)(T* aObject, void* aClosure); + /** + * Set a callback responsible for recycling this object + * before it is finalized. + */ + void SetRecycleCallback(RecycleCallback aCallback, void* aClosure) { + MOZ_ASSERT(!IsDead()); + mRecycleCallback = aCallback; + mClosure = aClosure; + } + void ClearRecycleCallback() { + MOZ_ASSERT(!IsDead()); + SetRecycleCallback(nullptr, nullptr); + } + + bool HasRecycleCallback() const { + MOZ_ASSERT(!IsDead()); + return !!mRecycleCallback; + } + + bool IsDead() const { return mRefCount < 0; } + + bool HasOneRef() const { return mRefCount == 1; } + + private: + RecycleCallback mRecycleCallback; + void* mClosure; + Atomic mRefCount; +#ifdef DEBUG + public: + bool mSpew; + + private: + Atomic mManualAddRefs; + Atomic mManualReleases; +#endif +#ifdef NS_BUILD_REFCNT_LOGGING + const char* mName; +#endif +}; + +} // namespace mozilla + +#endif diff --git a/gfx/layers/AxisPhysicsMSDModel.cpp b/gfx/layers/AxisPhysicsMSDModel.cpp new file mode 100644 index 0000000000..40d516f78b --- /dev/null +++ b/gfx/layers/AxisPhysicsMSDModel.cpp @@ -0,0 +1,78 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AxisPhysicsMSDModel.h" +#include // for sqrt and fabs + +namespace mozilla { +namespace layers { + +/** + * Constructs an AxisPhysicsMSDModel with initial values for state. + * + * @param aInitialPosition sets the initial position of the simulated spring, + * in AppUnits. + * @param aInitialDestination sets the resting position of the simulated spring, + * in AppUnits. + * @param aInitialVelocity sets the initial velocity of the simulated spring, + * in AppUnits / second. Critically-damped and over-damped systems are + * guaranteed not to overshoot aInitialDestination if this is set to 0; + * however, it is possible to overshoot and oscillate if not set to 0 or + * the system is under-damped. + * @param aSpringConstant sets the strength of the simulated spring. Greater + * values of mSpringConstant result in a stiffer / stronger spring. + * @param aDampingRatio controls the amount of dampening force and determines + * if the system is under-damped, critically-damped, or over-damped. + */ +AxisPhysicsMSDModel::AxisPhysicsMSDModel(double aInitialPosition, + double aInitialDestination, + double aInitialVelocity, + double aSpringConstant, + double aDampingRatio) + : AxisPhysicsModel(aInitialPosition, aInitialVelocity), + mDestination(aInitialDestination), + mSpringConstant(aSpringConstant), + mSpringConstantSqrtXTwo(sqrt(mSpringConstant) * 2.0), + mDampingRatio(aDampingRatio) {} + +AxisPhysicsMSDModel::~AxisPhysicsMSDModel() = default; + +double AxisPhysicsMSDModel::Acceleration(const State& aState) { + // Simulate a Mass-Damper-Spring Model; assume a unit mass + + // Hooke’s Law: http://en.wikipedia.org/wiki/Hooke%27s_law + double spring_force = (mDestination - aState.p) * mSpringConstant; + double damp_force = -aState.v * mDampingRatio * mSpringConstantSqrtXTwo; + + return spring_force + damp_force; +} + +double AxisPhysicsMSDModel::GetDestination() const { return mDestination; } + +void AxisPhysicsMSDModel::SetDestination(double aDestination) { + mDestination = aDestination; +} + +bool AxisPhysicsMSDModel::IsFinished(double aSmallestVisibleIncrement) { + // In order to satisfy the condition of reaching the destination, the distance + // between the simulation position and the destination must be less than + // aSmallestVisibleIncrement while the speed is simultaneously less than + // finishVelocity. This enables an under-damped system to overshoot the + // destination when desired without prematurely triggering the finished state. + // If finishVelocity is set too low, the animation may end long after + // oscillation has finished, resulting in unnecessary processing. + // If set too high, the animation may prematurely terminate when expected + // to overshoot the destination in an under-damped system. + // aSmallestVisibleIncrement * 2 was selected through experimentation that + // revealed that a critically damped system will terminate within 100ms. + const double finishVelocity = aSmallestVisibleIncrement * 2; + + return fabs(mDestination - GetPosition()) < aSmallestVisibleIncrement && + fabs(GetVelocity()) <= finishVelocity; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/AxisPhysicsMSDModel.h b/gfx/layers/AxisPhysicsMSDModel.h new file mode 100644 index 0000000000..e624293c4b --- /dev/null +++ b/gfx/layers/AxisPhysicsMSDModel.h @@ -0,0 +1,83 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_AxisPhysicsMSDModel_h +#define mozilla_layers_AxisPhysicsMSDModel_h + +#include "AxisPhysicsModel.h" + +namespace mozilla { +namespace layers { + +/** + * AxisPhysicsMSDModel encapsulates a 1-dimensional MSD (Mass-Spring-Damper) + * model. A unit mass is assumed. + */ +class AxisPhysicsMSDModel : public AxisPhysicsModel { + public: + AxisPhysicsMSDModel(double aInitialPosition, double aInitialDestination, + double aInitialVelocity, double aSpringConstant, + double aDampingRatio); + + virtual ~AxisPhysicsMSDModel(); + + /** + * Gets the raw destination of this axis at this moment. + */ + double GetDestination() const; + + /** + * Sets the raw destination of this axis at this moment. + */ + void SetDestination(double aDestination); + + /** + * Returns true when the position is close to the destination and the + * velocity is low. + */ + bool IsFinished(double aSmallestVisibleIncrement); + + protected: + double Acceleration(const State& aState) override; + + private: + /** + * mDestination represents the target position and the resting position of + * the simulated spring. + */ + double mDestination; + + /** + * Greater values of mSpringConstant result in a stiffer / stronger spring. + */ + double mSpringConstant; + + /** + * mSpringConstantSqrtTimesTwo is calculated from mSpringConstant to reduce + * calculations performed in the inner loop. + */ + double mSpringConstantSqrtXTwo; + + /** + * Damping Ratio: http://en.wikipedia.org/wiki/Damping_ratio + * + * When mDampingRatio < 1.0, this is an under damped system. + * - Overshoots destination and oscillates with the amplitude gradually + * decreasing to zero. + * + * When mDampingRatio == 1.0, this is a critically damped system. + * - Reaches destination as quickly as possible without oscillating. + * + * When mDampingRatio > 1.0, this is an over damped system. + * - Reaches destination (exponentially decays) without oscillating. + */ + double mDampingRatio; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/AxisPhysicsModel.cpp b/gfx/layers/AxisPhysicsModel.cpp new file mode 100644 index 0000000000..dc0cba3110 --- /dev/null +++ b/gfx/layers/AxisPhysicsModel.cpp @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AxisPhysicsModel.h" + +namespace mozilla { +namespace layers { + +/** + * The simulation is advanced forward in time with a fixed time step to ensure + * that it remains deterministic given variable framerates. To determine the + * position at any variable time, two samples are interpolated. + * + * kFixedtimestep is set to 120hz in order to ensure that every frame in a + * common 60hz refresh rate display will have at least one physics simulation + * sample. More accuracy can be obtained by reducing kFixedTimestep to smaller + * intervals, such as 240hz or 1000hz, at the cost of more CPU cycles. If + * kFixedTimestep is increased to much longer intervals, interpolation will + * become less effective at reducing temporal jitter and the simulation will + * lose accuracy. + */ +const double AxisPhysicsModel::kFixedTimestep = 1.0 / 120.0; // 120hz + +/** + * Constructs an AxisPhysicsModel with initial values for state. + * + * @param aInitialPosition sets the initial position of the simulation, + * in AppUnits. + * @param aInitialVelocity sets the initial velocity of the simulation, + * in AppUnits / second. + */ +AxisPhysicsModel::AxisPhysicsModel(double aInitialPosition, + double aInitialVelocity) + : mProgress(1.0), + mPrevState(aInitialPosition, aInitialVelocity), + mNextState(aInitialPosition, aInitialVelocity) {} + +AxisPhysicsModel::~AxisPhysicsModel() = default; + +double AxisPhysicsModel::GetVelocity() const { + return LinearInterpolate(mPrevState.v, mNextState.v, mProgress); +} + +double AxisPhysicsModel::GetPosition() const { + return LinearInterpolate(mPrevState.p, mNextState.p, mProgress); +} + +void AxisPhysicsModel::SetVelocity(double aVelocity) { + mNextState.v = aVelocity; + mNextState.p = GetPosition(); + mProgress = 1.0; +} + +void AxisPhysicsModel::SetPosition(double aPosition) { + mNextState.v = GetVelocity(); + mNextState.p = aPosition; + mProgress = 1.0; +} + +void AxisPhysicsModel::Simulate(const TimeDuration& aDeltaTime) { + for (mProgress += aDeltaTime.ToSeconds() / kFixedTimestep; mProgress > 1.0; + mProgress -= 1.0) { + Integrate(kFixedTimestep); + } +} + +void AxisPhysicsModel::Integrate(double aDeltaTime) { + mPrevState = mNextState; + + // RK4 (Runge-Kutta method) Integration + // http://en.wikipedia.org/wiki/Runge%E2%80%93Kutta_methods + Derivative a = Evaluate(mNextState, 0.0, Derivative()); + Derivative b = Evaluate(mNextState, aDeltaTime * 0.5, a); + Derivative c = Evaluate(mNextState, aDeltaTime * 0.5, b); + Derivative d = Evaluate(mNextState, aDeltaTime, c); + + double dpdt = 1.0 / 6.0 * (a.dp + 2.0 * (b.dp + c.dp) + d.dp); + double dvdt = 1.0 / 6.0 * (a.dv + 2.0 * (b.dv + c.dv) + d.dv); + + mNextState.p += dpdt * aDeltaTime; + mNextState.v += dvdt * aDeltaTime; +} + +AxisPhysicsModel::Derivative AxisPhysicsModel::Evaluate( + const State& aInitState, double aDeltaTime, const Derivative& aDerivative) { + State state(aInitState.p + aDerivative.dp * aDeltaTime, + aInitState.v + aDerivative.dv * aDeltaTime); + + return Derivative(state.v, Acceleration(state)); +} + +double AxisPhysicsModel::LinearInterpolate(double aV1, double aV2, + double aBlend) { + return aV1 * (1.0 - aBlend) + aV2 * aBlend; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/AxisPhysicsModel.h b/gfx/layers/AxisPhysicsModel.h new file mode 100644 index 0000000000..4aa6a69c11 --- /dev/null +++ b/gfx/layers/AxisPhysicsModel.h @@ -0,0 +1,123 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_AxisPhysicsModel_h +#define mozilla_layers_AxisPhysicsModel_h + +#include // for int32_t +#include "mozilla/TimeStamp.h" // for TimeDuration + +namespace mozilla { +namespace layers { + +/** + * AxisPhysicsModel encapsulates a generic 1-dimensional physically-based motion + * model. + * + * It performs frame-rate independent interpolation and RK4 integration for + * smooth animation with stable, deterministic behavior. + * Implementations are expected to subclass and override the Acceleration() + * method. + */ +class AxisPhysicsModel { + public: + AxisPhysicsModel(double aInitialPosition, double aInitialVelocity); + virtual ~AxisPhysicsModel(); + + /** + * Advance the physics simulation. + * |aDelta| is the time since the last sample. + */ + void Simulate(const TimeDuration& aDeltaTime); + + /** + * Gets the raw velocity of this axis at this moment. + */ + double GetVelocity() const; + + /** + * Sets the raw velocity of this axis at this moment. + */ + void SetVelocity(double aVelocity); + + /** + * Gets the raw position of this axis at this moment. + */ + double GetPosition() const; + + /** + * Sets the raw position of this axis at this moment. + */ + void SetPosition(double aPosition); + + protected: + struct State { + State(double ap, double av) : p(ap), v(av){}; + double p; // Position + double v; // Velocity + }; + + struct Derivative { + Derivative() : dp(0.0), dv(0.0){}; + Derivative(double aDp, double aDv) : dp(aDp), dv(aDv){}; + double dp; // dp / delta time = Position + double dv; // dv / delta time = Velocity + }; + + /** + * Acceleration must be overridden and return the number of + * axis-position-units / second that should be added or removed from the + * velocity. + */ + virtual double Acceleration(const State& aState) = 0; + + private: + /** + * Duration of fixed delta time step (seconds) + */ + static const double kFixedTimestep; + + /** + * 0.0 - 1.0 value indicating progress between current and next simulation + * sample. Normalized to units of kFixedTimestep duration. + */ + double mProgress; + + /** + * Sample of simulation state as it existed + * (1.0 - mProgress) * kFixedTimestep seconds in the past. + */ + State mPrevState; + + /** + * Sample of simulation state as it will be in mProgress * kFixedTimestep + * seconds in the future. + */ + State mNextState; + + /** + * Perform RK4 (Runge-Kutta method) Integration to calculate the next + * simulation sample. + */ + void Integrate(double aDeltaTime); + + /** + * Apply delta velocity and position represented by aDerivative over + * aDeltaTime seconds, calculate new acceleration, and return new deltas. + */ + Derivative Evaluate(const State& aInitState, double aDeltaTime, + const Derivative& aDerivative); + + /** + * Helper function for performing linear interpolation (lerp) of double's + */ + static double LinearInterpolate(double aV1, double aV2, double aBlend); +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/BSPTree.cpp b/gfx/layers/BSPTree.cpp new file mode 100644 index 0000000000..d181b05e80 --- /dev/null +++ b/gfx/layers/BSPTree.cpp @@ -0,0 +1,115 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "BSPTree.h" +#include "mozilla/gfx/Polygon.h" + +namespace mozilla { +namespace layers { + +void BSPTree::BuildDrawOrder(BSPTreeNode* aNode, + nsTArray& aLayers) const { + const gfx::Point4D& normal = aNode->First().GetNormal(); + + BSPTreeNode* front = aNode->front; + BSPTreeNode* back = aNode->back; + + // Since the goal is to return the draw order from back to front, we reverse + // the traversal order if the current polygon is facing towards the camera. + const bool reverseOrder = normal.z > 0.0f; + + if (reverseOrder) { + std::swap(front, back); + } + + if (front) { + BuildDrawOrder(front, aLayers); + } + + for (LayerPolygon& layer : aNode->layers) { + MOZ_ASSERT(layer.geometry); + + if (layer.geometry->GetPoints().Length() >= 3) { + aLayers.AppendElement(std::move(layer)); + } + } + + if (back) { + BuildDrawOrder(back, aLayers); + } +} + +void BSPTree::BuildTree(BSPTreeNode* aRoot, std::list& aLayers) { + MOZ_ASSERT(!aLayers.empty()); + + aRoot->layers.push_back(std::move(aLayers.front())); + aLayers.pop_front(); + + if (aLayers.empty()) { + return; + } + + const gfx::Polygon& plane = aRoot->First(); + MOZ_ASSERT(!plane.IsEmpty()); + + const gfx::Point4D& planeNormal = plane.GetNormal(); + const gfx::Point4D& planePoint = plane.GetPoints()[0]; + + std::list backLayers, frontLayers; + for (LayerPolygon& layerPolygon : aLayers) { + const nsTArray& geometry = layerPolygon.geometry->GetPoints(); + + // Calculate the plane-point distances for the polygon classification. + size_t pos = 0, neg = 0; + nsTArray distances = CalculatePointPlaneDistances( + geometry, planeNormal, planePoint, pos, neg); + + // Back polygon + if (pos == 0 && neg > 0) { + backLayers.push_back(std::move(layerPolygon)); + } + // Front polygon + else if (pos > 0 && neg == 0) { + frontLayers.push_back(std::move(layerPolygon)); + } + // Coplanar polygon + else if (pos == 0 && neg == 0) { + aRoot->layers.push_back(std::move(layerPolygon)); + } + // Polygon intersects with the splitting plane. + else if (pos > 0 && neg > 0) { + nsTArray backPoints, frontPoints; + // Clip the polygon against the plane. We reuse the previously calculated + // distances to find the plane-edge intersections. + ClipPointsWithPlane(geometry, planeNormal, distances, backPoints, + frontPoints); + + const gfx::Point4D& normal = layerPolygon.geometry->GetNormal(); + Layer* layer = layerPolygon.layer; + + if (backPoints.Length() >= 3) { + backLayers.emplace_back(layer, std::move(backPoints), normal); + } + + if (frontPoints.Length() >= 3) { + frontLayers.emplace_back(layer, std::move(frontPoints), normal); + } + } + } + + if (!backLayers.empty()) { + aRoot->back = new (mPool) BSPTreeNode(mListPointers); + BuildTree(aRoot->back, backLayers); + } + + if (!frontLayers.empty()) { + aRoot->front = new (mPool) BSPTreeNode(mListPointers); + BuildTree(aRoot->front, frontLayers); + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/BSPTree.h b/gfx/layers/BSPTree.h new file mode 100644 index 0000000000..3a3a4ca466 --- /dev/null +++ b/gfx/layers/BSPTree.h @@ -0,0 +1,136 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_LAYERS_BSPTREE_H +#define MOZILLA_LAYERS_BSPTREE_H + +#include +#include + +#include "mozilla/ArenaAllocator.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/gfx/Polygon.h" +#include "nsTArray.h" + +namespace mozilla { +namespace layers { + +class Layer; + +/** + * Represents a layer that might have a non-rectangular geometry. + */ +struct LayerPolygon { + explicit LayerPolygon(Layer* aLayer) : layer(aLayer) {} + + LayerPolygon(Layer* aLayer, gfx::Polygon&& aGeometry) + : layer(aLayer), geometry(Some(std::move(aGeometry))) {} + + LayerPolygon(Layer* aLayer, nsTArray&& aPoints, + const gfx::Point4D& aNormal) + : layer(aLayer) { + geometry.emplace(std::move(aPoints), aNormal); + } + + Layer* layer; + Maybe geometry; +}; + +/** + * Allocate BSPTreeNodes from a memory arena to improve performance with + * complex scenes. + * The arena size of 4096 bytes was selected as an arbitrary power of two. + * Depending on the platform, this size accommodates roughly 100 BSPTreeNodes. + */ +typedef mozilla::ArenaAllocator<4096, 8> BSPTreeArena; + +/** + * Aliases the container type used to store layers within BSPTreeNodes. + */ +typedef std::list LayerList; + +/** + * Represents a node in a BSP tree. The node contains at least one layer with + * associated geometry that is used as a splitting plane, and at most two child + * nodes that represent the splitting planes that further subdivide the space. + */ +struct BSPTreeNode { + explicit BSPTreeNode(nsTArray& aListPointers) + : front(nullptr), back(nullptr) { + // Store the layer list pointer to free memory when BSPTree is destroyed. + aListPointers.AppendElement(&layers); + } + + const gfx::Polygon& First() const { + MOZ_ASSERT(!layers.empty()); + MOZ_ASSERT(layers.front().geometry); + return *layers.front().geometry; + } + + static void* operator new(size_t aSize, BSPTreeArena& mPool) { + return mPool.Allocate(aSize); + } + + BSPTreeNode* front; + BSPTreeNode* back; + LayerList layers; +}; + +/** + * BSPTree class takes a list of layers as an input and uses binary space + * partitioning algorithm to create a tree structure that can be used for + * depth sorting. + + * Sources for more information: + * https://en.wikipedia.org/wiki/Binary_space_partitioning + * ftp://ftp.sgi.com/other/bspfaq/faq/bspfaq.html + */ +class BSPTree final { + public: + /** + * The constructor modifies layers in the given list. + */ + explicit BSPTree(std::list& aLayers) { + MOZ_ASSERT(!aLayers.empty()); + + mRoot = new (mPool) BSPTreeNode(mListPointers); + BuildTree(mRoot, aLayers); + } + + ~BSPTree() { + for (LayerList* listPtr : mListPointers) { + listPtr->~LayerList(); + } + } + + /** + * Builds and returns the back-to-front draw order for the created BSP tree. + */ + nsTArray GetDrawOrder() const { + nsTArray layers; + BuildDrawOrder(mRoot, layers); + return layers; + } + + private: + BSPTreeArena mPool; + BSPTreeNode* mRoot; + nsTArray mListPointers; + + /** + * BuildDrawOrder and BuildTree are called recursively. The depth of the + * recursion depends on the amount of polygons and their intersections. + */ + void BuildDrawOrder(BSPTreeNode* aNode, + nsTArray& aLayers) const; + + void BuildTree(BSPTreeNode* aRoot, std::list& aLayers); +}; + +} // namespace layers +} // namespace mozilla + +#endif /* MOZILLA_LAYERS_BSPTREE_H */ diff --git a/gfx/layers/BufferTexture.cpp b/gfx/layers/BufferTexture.cpp new file mode 100644 index 0000000000..6021ada134 --- /dev/null +++ b/gfx/layers/BufferTexture.cpp @@ -0,0 +1,568 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "BufferTexture.h" + +#include + +#include "libyuv.h" +#include "mozilla/fallible.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/layers/CompositableForwarder.h" +#include "mozilla/layers/ISurfaceAllocator.h" +#include "mozilla/layers/ImageDataSerializer.h" +#include "mozilla/layers/TextureForwarder.h" + +#ifdef MOZ_WIDGET_GTK +# include "gfxPlatformGtk.h" +#endif + +using mozilla::ipc::IShmemAllocator; + +namespace mozilla { +namespace layers { + +class MemoryTextureData : public BufferTextureData { + public: + static MemoryTextureData* Create(gfx::IntSize aSize, + gfx::SurfaceFormat aFormat, + gfx::BackendType aMoz2DBackend, + LayersBackend aLayersBackend, + TextureFlags aFlags, + TextureAllocationFlags aAllocFlags, + IShmemAllocator* aAllocator); + + virtual TextureData* CreateSimilar( + LayersIPCChannel* aAllocator, LayersBackend aLayersBackend, + TextureFlags aFlags = TextureFlags::DEFAULT, + TextureAllocationFlags aAllocFlags = ALLOC_DEFAULT) const override; + + virtual bool Serialize(SurfaceDescriptor& aOutDescriptor) override; + + virtual void Deallocate(LayersIPCChannel*) override; + + MemoryTextureData(const BufferDescriptor& aDesc, + gfx::BackendType aMoz2DBackend, uint8_t* aBuffer, + size_t aBufferSize) + : BufferTextureData(aDesc, aMoz2DBackend), + mBuffer(aBuffer), + mBufferSize(aBufferSize) { + MOZ_ASSERT(aBuffer); + MOZ_ASSERT(aBufferSize); + } + + virtual uint8_t* GetBuffer() override { return mBuffer; } + + virtual size_t GetBufferSize() override { return mBufferSize; } + + protected: + uint8_t* mBuffer; + size_t mBufferSize; +}; + +class ShmemTextureData : public BufferTextureData { + public: + static ShmemTextureData* Create(gfx::IntSize aSize, + gfx::SurfaceFormat aFormat, + gfx::BackendType aMoz2DBackend, + LayersBackend aLayersBackend, + TextureFlags aFlags, + TextureAllocationFlags aAllocFlags, + IShmemAllocator* aAllocator); + + virtual TextureData* CreateSimilar( + LayersIPCChannel* aAllocator, LayersBackend aLayersBackend, + TextureFlags aFlags = TextureFlags::DEFAULT, + TextureAllocationFlags aAllocFlags = ALLOC_DEFAULT) const override; + + virtual bool Serialize(SurfaceDescriptor& aOutDescriptor) override; + + virtual void Deallocate(LayersIPCChannel* aAllocator) override; + + ShmemTextureData(const BufferDescriptor& aDesc, + gfx::BackendType aMoz2DBackend, mozilla::ipc::Shmem aShmem) + : BufferTextureData(aDesc, aMoz2DBackend), mShmem(aShmem) { + MOZ_ASSERT(mShmem.Size()); + } + + virtual uint8_t* GetBuffer() override { return mShmem.get(); } + + virtual size_t GetBufferSize() override { return mShmem.Size(); } + + protected: + mozilla::ipc::Shmem mShmem; +}; + +static bool UsingX11Compositor() { +#ifdef MOZ_WIDGET_GTK + return gfx::gfxVars::UseXRender(); +#endif + return false; +} + +bool ComputeHasIntermediateBuffer(gfx::SurfaceFormat aFormat, + LayersBackend aLayersBackend, + bool aSupportsTextureDirectMapping) { + if (aSupportsTextureDirectMapping) { + return false; + } + + return aLayersBackend != LayersBackend::LAYERS_BASIC || + UsingX11Compositor() || aFormat == gfx::SurfaceFormat::UNKNOWN; +} + +BufferTextureData* BufferTextureData::Create( + gfx::IntSize aSize, gfx::SurfaceFormat aFormat, + gfx::BackendType aMoz2DBackend, LayersBackend aLayersBackend, + TextureFlags aFlags, TextureAllocationFlags aAllocFlags, + mozilla::ipc::IShmemAllocator* aAllocator, bool aIsSameProcess) { + if (!aAllocator || aIsSameProcess) { + return MemoryTextureData::Create(aSize, aFormat, aMoz2DBackend, + aLayersBackend, aFlags, aAllocFlags, + aAllocator); + } else { + return ShmemTextureData::Create(aSize, aFormat, aMoz2DBackend, + aLayersBackend, aFlags, aAllocFlags, + aAllocator); + } +} + +BufferTextureData* BufferTextureData::CreateInternal( + LayersIPCChannel* aAllocator, const BufferDescriptor& aDesc, + gfx::BackendType aMoz2DBackend, int32_t aBufferSize, + TextureFlags aTextureFlags) { + if (!aAllocator || aAllocator->IsSameProcess()) { + uint8_t* buffer = new (fallible) uint8_t[aBufferSize]; + if (!buffer) { + return nullptr; + } + + GfxMemoryImageReporter::DidAlloc(buffer); + + return new MemoryTextureData(aDesc, aMoz2DBackend, buffer, aBufferSize); + } else { + ipc::Shmem shm; + if (!aAllocator->AllocUnsafeShmem(aBufferSize, OptimalShmemType(), &shm)) { + return nullptr; + } + + return new ShmemTextureData(aDesc, aMoz2DBackend, shm); + } +} + +BufferTextureData* BufferTextureData::CreateForYCbCr( + KnowsCompositor* aAllocator, const gfx::IntRect& aDisplay, + const gfx::IntSize& aYSize, uint32_t aYStride, + const gfx::IntSize& aCbCrSize, uint32_t aCbCrStride, StereoMode aStereoMode, + gfx::ColorDepth aColorDepth, gfx::YUVColorSpace aYUVColorSpace, + gfx::ColorRange aColorRange, TextureFlags aTextureFlags) { + uint32_t bufSize = ImageDataSerializer::ComputeYCbCrBufferSize( + aYSize, aYStride, aCbCrSize, aCbCrStride); + if (bufSize == 0) { + return nullptr; + } + + uint32_t yOffset; + uint32_t cbOffset; + uint32_t crOffset; + ImageDataSerializer::ComputeYCbCrOffsets(aYStride, aYSize.height, aCbCrStride, + aCbCrSize.height, yOffset, cbOffset, + crOffset); + + bool supportsTextureDirectMapping = + aAllocator->SupportsTextureDirectMapping() && + aAllocator->GetMaxTextureSize() > + std::max(aYSize.width, + std::max(aYSize.height, + std::max(aCbCrSize.width, aCbCrSize.height))); + + bool hasIntermediateBuffer = + aAllocator + ? ComputeHasIntermediateBuffer(gfx::SurfaceFormat::YUV, + aAllocator->GetCompositorBackendType(), + supportsTextureDirectMapping) + : true; + + YCbCrDescriptor descriptor = + YCbCrDescriptor(aDisplay, aYSize, aYStride, aCbCrSize, aCbCrStride, + yOffset, cbOffset, crOffset, aStereoMode, aColorDepth, + aYUVColorSpace, aColorRange, hasIntermediateBuffer); + + return CreateInternal( + aAllocator ? aAllocator->GetTextureForwarder() : nullptr, descriptor, + gfx::BackendType::NONE, bufSize, aTextureFlags); +} + +void BufferTextureData::FillInfo(TextureData::Info& aInfo) const { + aInfo.size = GetSize(); + aInfo.format = GetFormat(); + aInfo.hasSynchronization = false; + aInfo.canExposeMappedData = true; + + if (mDescriptor.type() == BufferDescriptor::TYCbCrDescriptor) { + aInfo.hasIntermediateBuffer = + mDescriptor.get_YCbCrDescriptor().hasIntermediateBuffer(); + } else { + aInfo.hasIntermediateBuffer = + mDescriptor.get_RGBDescriptor().hasIntermediateBuffer(); + } + + switch (aInfo.format) { + case gfx::SurfaceFormat::YUV: + case gfx::SurfaceFormat::UNKNOWN: + aInfo.supportsMoz2D = false; + break; + default: + aInfo.supportsMoz2D = true; + } +} + +gfx::IntSize BufferTextureData::GetSize() const { + return ImageDataSerializer::SizeFromBufferDescriptor(mDescriptor); +} + +gfx::IntRect BufferTextureData::GetPictureRect() const { + return ImageDataSerializer::RectFromBufferDescriptor(mDescriptor); +} + +Maybe BufferTextureData::GetCbCrSize() const { + return ImageDataSerializer::CbCrSizeFromBufferDescriptor(mDescriptor); +} + +Maybe BufferTextureData::GetYStride() const { + return ImageDataSerializer::YStrideFromBufferDescriptor(mDescriptor); +} + +Maybe BufferTextureData::GetCbCrStride() const { + return ImageDataSerializer::CbCrStrideFromBufferDescriptor(mDescriptor); +} + +Maybe BufferTextureData::GetYUVColorSpace() const { + return ImageDataSerializer::YUVColorSpaceFromBufferDescriptor(mDescriptor); +} + +Maybe BufferTextureData::GetColorDepth() const { + return ImageDataSerializer::ColorDepthFromBufferDescriptor(mDescriptor); +} + +Maybe BufferTextureData::GetStereoMode() const { + return ImageDataSerializer::StereoModeFromBufferDescriptor(mDescriptor); +} + +gfx::SurfaceFormat BufferTextureData::GetFormat() const { + return ImageDataSerializer::FormatFromBufferDescriptor(mDescriptor); +} + +already_AddRefed BufferTextureData::BorrowDrawTarget() { + if (mDescriptor.type() != BufferDescriptor::TRGBDescriptor) { + return nullptr; + } + + const RGBDescriptor& rgb = mDescriptor.get_RGBDescriptor(); + + uint32_t stride = ImageDataSerializer::GetRGBStride(rgb); + RefPtr dt; + if (gfx::Factory::DoesBackendSupportDataDrawtarget(mMoz2DBackend)) { + dt = gfx::Factory::CreateDrawTargetForData( + mMoz2DBackend, GetBuffer(), rgb.size(), stride, rgb.format(), true); + } + if (!dt) { + // Fall back to supported platform backend. Note that mMoz2DBackend + // does not match the draw target type. + dt = gfxPlatform::CreateDrawTargetForData(GetBuffer(), rgb.size(), stride, + rgb.format(), true); + } + + if (!dt) { + gfxCriticalNote << "BorrowDrawTarget failure, original backend " + << (int)mMoz2DBackend; + } + + return dt.forget(); +} + +bool BufferTextureData::BorrowMappedData(MappedTextureData& aData) { + if (GetFormat() == gfx::SurfaceFormat::YUV) { + return false; + } + + gfx::IntSize size = GetSize(); + + aData.data = GetBuffer(); + aData.size = size; + aData.format = GetFormat(); + aData.stride = + ImageDataSerializer::ComputeRGBStride(aData.format, size.width); + + return true; +} + +bool BufferTextureData::BorrowMappedYCbCrData(MappedYCbCrTextureData& aMap) { + if (mDescriptor.type() != BufferDescriptor::TYCbCrDescriptor) { + return false; + } + + const YCbCrDescriptor& desc = mDescriptor.get_YCbCrDescriptor(); + + uint8_t* data = GetBuffer(); + auto ySize = desc.ySize(); + auto cbCrSize = desc.cbCrSize(); + + aMap.stereoMode = desc.stereoMode(); + aMap.metadata = nullptr; + uint32_t bytesPerPixel = + BytesPerPixel(SurfaceFormatForColorDepth(desc.colorDepth())); + + aMap.y.data = data + desc.yOffset(); + aMap.y.size = ySize; + aMap.y.stride = desc.yStride(); + aMap.y.skip = 0; + aMap.y.bytesPerPixel = bytesPerPixel; + + aMap.cb.data = data + desc.cbOffset(); + aMap.cb.size = cbCrSize; + aMap.cb.stride = desc.cbCrStride(); + aMap.cb.skip = 0; + aMap.cb.bytesPerPixel = bytesPerPixel; + + aMap.cr.data = data + desc.crOffset(); + aMap.cr.size = cbCrSize; + aMap.cr.stride = desc.cbCrStride(); + aMap.cr.skip = 0; + aMap.cr.bytesPerPixel = bytesPerPixel; + + return true; +} + +bool BufferTextureData::UpdateFromSurface(gfx::SourceSurface* aSurface) { + if (mDescriptor.type() != BufferDescriptor::TRGBDescriptor) { + return false; + } + const RGBDescriptor& rgb = mDescriptor.get_RGBDescriptor(); + + uint32_t stride = ImageDataSerializer::GetRGBStride(rgb); + RefPtr surface = + gfx::Factory::CreateWrappingDataSourceSurface(GetBuffer(), stride, + rgb.size(), rgb.format()); + + if (!surface) { + gfxCriticalError() << "Failed to get serializer as surface!"; + return false; + } + + RefPtr srcSurf = aSurface->GetDataSurface(); + + if (!srcSurf) { + gfxCriticalError() << "Failed to GetDataSurface in UpdateFromSurface (BT)."; + return false; + } + + if (surface->GetSize() != srcSurf->GetSize() || + surface->GetFormat() != srcSurf->GetFormat()) { + gfxCriticalError() << "Attempt to update texture client from a surface " + "with a different size or format (BT)! This: " + << surface->GetSize() << " " << surface->GetFormat() + << " Other: " << aSurface->GetSize() << " " + << aSurface->GetFormat(); + return false; + } + + gfx::DataSourceSurface::MappedSurface sourceMap; + gfx::DataSourceSurface::MappedSurface destMap; + if (!srcSurf->Map(gfx::DataSourceSurface::READ, &sourceMap)) { + gfxCriticalError() + << "Failed to map source surface for UpdateFromSurface (BT)."; + return false; + } + + if (!surface->Map(gfx::DataSourceSurface::WRITE, &destMap)) { + srcSurf->Unmap(); + gfxCriticalError() + << "Failed to map destination surface for UpdateFromSurface."; + return false; + } + + for (int y = 0; y < srcSurf->GetSize().height; y++) { + memcpy(destMap.mData + destMap.mStride * y, + sourceMap.mData + sourceMap.mStride * y, + srcSurf->GetSize().width * BytesPerPixel(srcSurf->GetFormat())); + } + + srcSurf->Unmap(); + surface->Unmap(); + + return true; +} + +void BufferTextureData::SetDescriptor(BufferDescriptor&& aDescriptor) { + MOZ_ASSERT(mDescriptor.type() == BufferDescriptor::TYCbCrDescriptor); + MOZ_ASSERT(mDescriptor.get_YCbCrDescriptor().ySize() == gfx::IntSize()); + mDescriptor = std::move(aDescriptor); +} + +bool MemoryTextureData::Serialize(SurfaceDescriptor& aOutDescriptor) { + MOZ_ASSERT(GetFormat() != gfx::SurfaceFormat::UNKNOWN); + if (GetFormat() == gfx::SurfaceFormat::UNKNOWN) { + return false; + } + + uintptr_t ptr = reinterpret_cast(mBuffer); + aOutDescriptor = SurfaceDescriptorBuffer(mDescriptor, MemoryOrShmem(ptr)); + + return true; +} + +static bool InitBuffer(uint8_t* buf, size_t bufSize, gfx::SurfaceFormat aFormat, + TextureAllocationFlags aAllocFlags, bool aAlreadyZero) { + if (!buf) { + gfxDebug() << "BufferTextureData: Failed to allocate " << bufSize + << " bytes"; + return false; + } + + if ((aAllocFlags & ALLOC_CLEAR_BUFFER) || + (aAllocFlags & ALLOC_CLEAR_BUFFER_BLACK)) { + if (aFormat == gfx::SurfaceFormat::B8G8R8X8) { + // Even though BGRX was requested, XRGB_UINT32 is what is meant, + // so use 0xFF000000 to put alpha in the right place. + libyuv::ARGBRect(buf, bufSize, 0, 0, bufSize / sizeof(uint32_t), 1, + 0xFF000000); + } else if (!aAlreadyZero) { + memset(buf, 0, bufSize); + } + } + + if (aAllocFlags & ALLOC_CLEAR_BUFFER_WHITE) { + memset(buf, 0xFF, bufSize); + } + + return true; +} + +MemoryTextureData* MemoryTextureData::Create(gfx::IntSize aSize, + gfx::SurfaceFormat aFormat, + gfx::BackendType aMoz2DBackend, + LayersBackend aLayersBackend, + TextureFlags aFlags, + TextureAllocationFlags aAllocFlags, + IShmemAllocator* aAllocator) { + // Should have used CreateForYCbCr. + MOZ_ASSERT(aFormat != gfx::SurfaceFormat::YUV); + + if (aSize.width <= 0 || aSize.height <= 0) { + gfxDebug() << "Asking for buffer of invalid size " << aSize.width << "x" + << aSize.height; + return nullptr; + } + + uint32_t bufSize = ImageDataSerializer::ComputeRGBBufferSize(aSize, aFormat); + if (!bufSize) { + return nullptr; + } + + uint8_t* buf = new (fallible) uint8_t[bufSize]; + if (!InitBuffer(buf, bufSize, aFormat, aAllocFlags, false)) { + return nullptr; + } + + bool hasIntermediateBuffer = ComputeHasIntermediateBuffer( + aFormat, aLayersBackend, aAllocFlags & ALLOC_ALLOW_DIRECT_MAPPING); + + GfxMemoryImageReporter::DidAlloc(buf); + + BufferDescriptor descriptor = + RGBDescriptor(aSize, aFormat, hasIntermediateBuffer); + + return new MemoryTextureData(descriptor, aMoz2DBackend, buf, bufSize); +} + +void MemoryTextureData::Deallocate(LayersIPCChannel*) { + MOZ_ASSERT(mBuffer); + GfxMemoryImageReporter::WillFree(mBuffer); + delete[] mBuffer; + mBuffer = nullptr; +} + +TextureData* MemoryTextureData::CreateSimilar( + LayersIPCChannel* aAllocator, LayersBackend aLayersBackend, + TextureFlags aFlags, TextureAllocationFlags aAllocFlags) const { + return MemoryTextureData::Create(GetSize(), GetFormat(), mMoz2DBackend, + aLayersBackend, aFlags, aAllocFlags, + aAllocator); +} + +bool ShmemTextureData::Serialize(SurfaceDescriptor& aOutDescriptor) { + MOZ_ASSERT(GetFormat() != gfx::SurfaceFormat::UNKNOWN); + if (GetFormat() == gfx::SurfaceFormat::UNKNOWN) { + return false; + } + + aOutDescriptor = + SurfaceDescriptorBuffer(mDescriptor, MemoryOrShmem(std::move(mShmem))); + + return true; +} + +ShmemTextureData* ShmemTextureData::Create(gfx::IntSize aSize, + gfx::SurfaceFormat aFormat, + gfx::BackendType aMoz2DBackend, + LayersBackend aLayersBackend, + TextureFlags aFlags, + TextureAllocationFlags aAllocFlags, + IShmemAllocator* aAllocator) { + MOZ_ASSERT(aAllocator); + // Should have used CreateForYCbCr. + MOZ_ASSERT(aFormat != gfx::SurfaceFormat::YUV); + + if (!aAllocator) { + return nullptr; + } + + if (aSize.width <= 0 || aSize.height <= 0) { + gfxDebug() << "Asking for buffer of invalid size " << aSize.width << "x" + << aSize.height; + return nullptr; + } + + uint32_t bufSize = ImageDataSerializer::ComputeRGBBufferSize(aSize, aFormat); + if (!bufSize) { + return nullptr; + } + + mozilla::ipc::Shmem shm; + if (!aAllocator->AllocUnsafeShmem(bufSize, OptimalShmemType(), &shm)) { + return nullptr; + } + + uint8_t* buf = shm.get(); + if (!InitBuffer(buf, bufSize, aFormat, aAllocFlags, true)) { + return nullptr; + } + + bool hasIntermediateBuffer = ComputeHasIntermediateBuffer( + aFormat, aLayersBackend, aAllocFlags & ALLOC_ALLOW_DIRECT_MAPPING); + + BufferDescriptor descriptor = + RGBDescriptor(aSize, aFormat, hasIntermediateBuffer); + + return new ShmemTextureData(descriptor, aMoz2DBackend, shm); +} + +TextureData* ShmemTextureData::CreateSimilar( + LayersIPCChannel* aAllocator, LayersBackend aLayersBackend, + TextureFlags aFlags, TextureAllocationFlags aAllocFlags) const { + return ShmemTextureData::Create(GetSize(), GetFormat(), mMoz2DBackend, + aLayersBackend, aFlags, aAllocFlags, + aAllocator); +} + +void ShmemTextureData::Deallocate(LayersIPCChannel* aAllocator) { + aAllocator->DeallocShmem(mShmem); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/BufferTexture.h b/gfx/layers/BufferTexture.h new file mode 100644 index 0000000000..4bd3f36757 --- /dev/null +++ b/gfx/layers/BufferTexture.h @@ -0,0 +1,128 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_LAYERS_BUFFERETEXTURE +#define MOZILLA_LAYERS_BUFFERETEXTURE + +#include "mozilla/RefPtr.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Types.h" +#include "mozilla/ipc/SharedMemory.h" +#include "mozilla/layers/TextureClient.h" + +namespace mozilla { +namespace layers { + +bool ComputeHasIntermediateBuffer(gfx::SurfaceFormat aFormat, + LayersBackend aLayersBackend, + bool aSupportsTextureDirectMapping); + +class BufferTextureData : public TextureData { + public: + // ShmemAllocator needs to implement IShmemAllocator and IsSameProcess, + // as done in LayersIPCChannel and ISurfaceAllocator. + template + static BufferTextureData* Create(gfx::IntSize aSize, + gfx::SurfaceFormat aFormat, + gfx::BackendType aMoz2DBackend, + LayersBackend aLayersBackend, + TextureFlags aFlags, + TextureAllocationFlags aAllocFlags, + ShmemAllocator aAllocator); + + static BufferTextureData* CreateForYCbCr( + KnowsCompositor* aAllocator, const gfx::IntRect& aDisplay, + const gfx::IntSize& aYSize, uint32_t aYStride, + const gfx::IntSize& aCbCrSize, uint32_t aCbCrStride, + StereoMode aStereoMode, gfx::ColorDepth aColorDepth, + gfx::YUVColorSpace aYUVColorSpace, gfx::ColorRange aColorRange, + TextureFlags aTextureFlags); + + bool Lock(OpenMode aMode) override { return true; } + + void Unlock() override {} + + void FillInfo(TextureData::Info& aInfo) const override; + + already_AddRefed BorrowDrawTarget() override; + + bool BorrowMappedData(MappedTextureData& aMap) override; + + bool BorrowMappedYCbCrData(MappedYCbCrTextureData& aMap) override; + + // use TextureClient's default implementation + bool UpdateFromSurface(gfx::SourceSurface* aSurface) override; + + BufferTextureData* AsBufferTextureData() override { return this; } + + // Don't use this. + void SetDescriptor(BufferDescriptor&& aDesc); + + Maybe GetCbCrSize() const; + + Maybe GetYStride() const; + + Maybe GetCbCrStride() const; + + Maybe GetYUVColorSpace() const; + + Maybe GetColorDepth() const; + + Maybe GetStereoMode() const; + + protected: + gfx::IntSize GetSize() const; + gfx::IntRect GetPictureRect() const; + + gfx::SurfaceFormat GetFormat() const; + + static BufferTextureData* Create( + gfx::IntSize aSize, gfx::SurfaceFormat aFormat, + gfx::BackendType aMoz2DBackend, LayersBackend aLayersBackend, + TextureFlags aFlags, TextureAllocationFlags aAllocFlags, + mozilla::ipc::IShmemAllocator* aAllocator, bool aIsSameProcess); + + static BufferTextureData* CreateInternal(LayersIPCChannel* aAllocator, + const BufferDescriptor& aDesc, + gfx::BackendType aMoz2DBackend, + int32_t aBufferSize, + TextureFlags aTextureFlags); + + virtual uint8_t* GetBuffer() = 0; + virtual size_t GetBufferSize() = 0; + + BufferTextureData(const BufferDescriptor& aDescriptor, + gfx::BackendType aMoz2DBackend) + : mDescriptor(aDescriptor), mMoz2DBackend(aMoz2DBackend) {} + + BufferDescriptor mDescriptor; + gfx::BackendType mMoz2DBackend; +}; + +template +inline BufferTextureData* BufferTextureData::Create( + gfx::IntSize aSize, gfx::SurfaceFormat aFormat, + gfx::BackendType aMoz2DBackend, LayersBackend aLayersBackend, + TextureFlags aFlags, TextureAllocationFlags aAllocFlags, + ShmemAllocator aAllocator) { + return Create(aSize, aFormat, aMoz2DBackend, aLayersBackend, aFlags, + aAllocFlags, aAllocator, aAllocator->IsSameProcess()); +} + +// nullptr allocator specialization +template <> +inline BufferTextureData* BufferTextureData::Create( + gfx::IntSize aSize, gfx::SurfaceFormat aFormat, + gfx::BackendType aMoz2DBackend, LayersBackend aLayersBackend, + TextureFlags aFlags, TextureAllocationFlags aAllocFlags, std::nullptr_t) { + return Create(aSize, aFormat, aMoz2DBackend, aLayersBackend, aFlags, + aAllocFlags, nullptr, true); +} + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/BuildConstants.h b/gfx/layers/BuildConstants.h new file mode 100644 index 0000000000..4478ce8da1 --- /dev/null +++ b/gfx/layers/BuildConstants.h @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef BUILD_CONSTANTS_H_ +#define BUILD_CONSTANTS_H_ + +/** + * Why not just use ifdefs? + * ifdefs tend to result in code that compiles on one platform but not another. + * Given the number of build and platform configurations we have, it's best to + * aim to compile the same on as many platforms as possible, and let the + * compiler see the constexprs and handle dead-code elision itself. + */ + +namespace mozilla { + +constexpr bool kIsDebug = +#ifdef DEBUG + true; +#else + false; +#endif + +constexpr bool kIsWindows = +#ifdef XP_WIN + true; +#else + false; +#endif + +constexpr bool kIsMacOS = +#ifdef XP_MACOSX + true; +#else + false; +#endif + +constexpr bool kIsX11 = +#ifdef MOZ_X11 + true; +#else + false; +#endif + +constexpr bool kIsWayland = +#ifdef MOZ_WAYLAND + true; +#else + false; +#endif + +constexpr bool kIsAndroid = +#ifdef MOZ_WIDGET_ANDROID + true; +#else + false; +#endif + +} // namespace mozilla + +#endif // BUILD_CONSTANTS_H_ diff --git a/gfx/layers/CanvasDrawEventRecorder.cpp b/gfx/layers/CanvasDrawEventRecorder.cpp new file mode 100644 index 0000000000..193ee63c2c --- /dev/null +++ b/gfx/layers/CanvasDrawEventRecorder.cpp @@ -0,0 +1,517 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "CanvasDrawEventRecorder.h" + +#include + +#include "nsThreadUtils.h" + +namespace mozilla { +namespace layers { + +static const int32_t kCheckpointEventType = -1; +static const uint32_t kMaxSpinCount = 200; + +static const TimeDuration kTimeout = TimeDuration::FromMilliseconds(100); +static const int32_t kTimeoutRetryCount = 50; + +static const uint32_t kCacheLineSize = 64; +static const uint32_t kStreamSize = 64 * 1024; +static const uint32_t kShmemSize = kStreamSize + (2 * kCacheLineSize); + +static_assert((static_cast(UINT32_MAX) + 1) % kStreamSize == 0, + "kStreamSize must be a power of two."); + +bool CanvasEventRingBuffer::InitWriter( + base::ProcessId aOtherPid, ipc::SharedMemoryBasic::Handle* aReadHandle, + CrossProcessSemaphoreHandle* aReaderSem, + CrossProcessSemaphoreHandle* aWriterSem, + UniquePtr aWriterServices) { + mSharedMemory = MakeAndAddRef(); + if (NS_WARN_IF(!mSharedMemory->Create(kShmemSize)) || + NS_WARN_IF(!mSharedMemory->Map(kShmemSize))) { + return false; + } + + if (NS_WARN_IF(!mSharedMemory->ShareToProcess(aOtherPid, aReadHandle))) { + return false; + } + + mSharedMemory->CloseHandle(); + + mBuf = static_cast(mSharedMemory->memory()); + mBufPos = mBuf; + mAvailable = kStreamSize; + + static_assert(sizeof(ReadFooter) <= kCacheLineSize, + "ReadFooter must fit in kCacheLineSize."); + mRead = reinterpret_cast(mBuf + kStreamSize); + mRead->count = 0; + mRead->returnCount = 0; + mRead->state = State::Processing; + + static_assert(sizeof(WriteFooter) <= kCacheLineSize, + "WriteFooter must fit in kCacheLineSize."); + mWrite = reinterpret_cast(mBuf + kStreamSize + kCacheLineSize); + mWrite->count = 0; + mWrite->returnCount = 0; + mWrite->requiredDifference = 0; + mWrite->state = State::Processing; + + mReaderSemaphore.reset( + CrossProcessSemaphore::Create("SharedMemoryStreamParent", 0)); + *aReaderSem = mReaderSemaphore->ShareToProcess(aOtherPid); + mReaderSemaphore->CloseHandle(); + if (!IsHandleValid(*aReaderSem)) { + return false; + } + mWriterSemaphore.reset( + CrossProcessSemaphore::Create("SharedMemoryStreamChild", 0)); + *aWriterSem = mWriterSemaphore->ShareToProcess(aOtherPid); + mWriterSemaphore->CloseHandle(); + if (!IsHandleValid(*aWriterSem)) { + return false; + } + + mWriterServices = std::move(aWriterServices); + + mGood = true; + return true; +} + +bool CanvasEventRingBuffer::InitReader( + const ipc::SharedMemoryBasic::Handle& aReadHandle, + const CrossProcessSemaphoreHandle& aReaderSem, + const CrossProcessSemaphoreHandle& aWriterSem, + UniquePtr aReaderServices) { + mSharedMemory = MakeAndAddRef(); + if (NS_WARN_IF(!mSharedMemory->SetHandle( + aReadHandle, ipc::SharedMemory::RightsReadWrite)) || + NS_WARN_IF(!mSharedMemory->Map(kShmemSize))) { + return false; + } + + mSharedMemory->CloseHandle(); + + mBuf = static_cast(mSharedMemory->memory()); + mRead = reinterpret_cast(mBuf + kStreamSize); + mWrite = reinterpret_cast(mBuf + kStreamSize + kCacheLineSize); + mReaderSemaphore.reset(CrossProcessSemaphore::Create(aReaderSem)); + mReaderSemaphore->CloseHandle(); + mWriterSemaphore.reset(CrossProcessSemaphore::Create(aWriterSem)); + mWriterSemaphore->CloseHandle(); + + mReaderServices = std::move(aReaderServices); + + mGood = true; + return true; +} + +bool CanvasEventRingBuffer::WaitForAndRecalculateAvailableSpace() { + if (!good()) { + return false; + } + + uint32_t bufPos = mOurCount % kStreamSize; + uint32_t maxToWrite = kStreamSize - bufPos; + mAvailable = std::min(maxToWrite, WaitForBytesToWrite()); + if (!mAvailable) { + mBufPos = nullptr; + return false; + } + + mBufPos = mBuf + bufPos; + return true; +} + +void CanvasEventRingBuffer::write(const char* const aData, const size_t aSize) { + const char* curDestPtr = aData; + size_t remainingToWrite = aSize; + if (remainingToWrite > mAvailable) { + if (!WaitForAndRecalculateAvailableSpace()) { + return; + } + } + + if (remainingToWrite <= mAvailable) { + memcpy(mBufPos, curDestPtr, remainingToWrite); + UpdateWriteTotalsBy(remainingToWrite); + return; + } + + do { + memcpy(mBufPos, curDestPtr, mAvailable); + IncrementWriteCountBy(mAvailable); + curDestPtr += mAvailable; + remainingToWrite -= mAvailable; + if (!WaitForAndRecalculateAvailableSpace()) { + return; + } + } while (remainingToWrite > mAvailable); + + memcpy(mBufPos, curDestPtr, remainingToWrite); + UpdateWriteTotalsBy(remainingToWrite); +} + +void CanvasEventRingBuffer::IncrementWriteCountBy(uint32_t aCount) { + mOurCount += aCount; + mWrite->count = mOurCount; + if (mRead->state != State::Processing) { + CheckAndSignalReader(); + } +} + +void CanvasEventRingBuffer::UpdateWriteTotalsBy(uint32_t aCount) { + IncrementWriteCountBy(aCount); + mBufPos += aCount; + mAvailable -= aCount; +} + +bool CanvasEventRingBuffer::WaitForAndRecalculateAvailableData() { + if (!good()) { + return false; + } + + uint32_t bufPos = mOurCount % kStreamSize; + uint32_t maxToRead = kStreamSize - bufPos; + mAvailable = std::min(maxToRead, WaitForBytesToRead()); + if (!mAvailable) { + SetIsBad(); + mBufPos = nullptr; + return false; + } + + mBufPos = mBuf + bufPos; + return true; +} + +void CanvasEventRingBuffer::read(char* const aOut, const size_t aSize) { + char* curSrcPtr = aOut; + size_t remainingToRead = aSize; + if (remainingToRead > mAvailable) { + if (!WaitForAndRecalculateAvailableData()) { + return; + } + } + + if (remainingToRead <= mAvailable) { + memcpy(curSrcPtr, mBufPos, remainingToRead); + UpdateReadTotalsBy(remainingToRead); + return; + } + + do { + memcpy(curSrcPtr, mBufPos, mAvailable); + IncrementReadCountBy(mAvailable); + curSrcPtr += mAvailable; + remainingToRead -= mAvailable; + if (!WaitForAndRecalculateAvailableData()) { + return; + } + } while (remainingToRead > mAvailable); + + memcpy(curSrcPtr, mBufPos, remainingToRead); + UpdateReadTotalsBy(remainingToRead); +} + +void CanvasEventRingBuffer::IncrementReadCountBy(uint32_t aCount) { + mOurCount += aCount; + mRead->count = mOurCount; + if (mWrite->state != State::Processing) { + CheckAndSignalWriter(); + } +} + +void CanvasEventRingBuffer::UpdateReadTotalsBy(uint32_t aCount) { + IncrementReadCountBy(aCount); + mBufPos += aCount; + mAvailable -= aCount; +} + +void CanvasEventRingBuffer::CheckAndSignalReader() { + do { + switch (mRead->state) { + case State::Processing: + case State::Failed: + return; + case State::AboutToWait: + // The reader is making a decision about whether to wait. So, we must + // wait until it has decided to avoid races. Check if the reader is + // closed to avoid hangs. + if (mWriterServices->ReaderClosed()) { + return; + } + continue; + case State::Waiting: + if (mRead->count != mOurCount) { + // We have to use compareExchange here because the reader can change + // from Waiting to Stopped. + if (mRead->state.compareExchange(State::Waiting, State::Processing)) { + mReaderSemaphore->Signal(); + return; + } + + MOZ_ASSERT(mRead->state == State::Stopped); + continue; + } + return; + case State::Stopped: + if (mRead->count != mOurCount) { + mRead->state = State::Processing; + mWriterServices->ResumeReader(); + } + return; + default: + MOZ_ASSERT_UNREACHABLE("Invalid waiting state."); + return; + } + } while (true); +} + +bool CanvasEventRingBuffer::HasDataToRead() { + return (mWrite->count != mOurCount); +} + +bool CanvasEventRingBuffer::StopIfEmpty() { + // Double-check that the writer isn't waiting. + CheckAndSignalWriter(); + mRead->state = State::AboutToWait; + if (HasDataToRead()) { + mRead->state = State::Processing; + return false; + } + + mRead->state = State::Stopped; + return true; +} + +bool CanvasEventRingBuffer::WaitForDataToRead(TimeDuration aTimeout, + int32_t aRetryCount) { + uint32_t spinCount = kMaxSpinCount; + do { + if (HasDataToRead()) { + return true; + } + } while (--spinCount != 0); + + // Double-check that the writer isn't waiting. + CheckAndSignalWriter(); + mRead->state = State::AboutToWait; + if (HasDataToRead()) { + mRead->state = State::Processing; + return true; + } + + mRead->state = State::Waiting; + do { + if (mReaderSemaphore->Wait(Some(aTimeout))) { + MOZ_RELEASE_ASSERT(HasDataToRead()); + return true; + } + + if (mReaderServices->WriterClosed()) { + // Something has gone wrong on the writing side, just return false so + // that we can hopefully recover. + return false; + } + } while (aRetryCount-- > 0); + + // We have to use compareExchange here because the writer can change our + // state if we are waiting. signaled + if (!mRead->state.compareExchange(State::Waiting, State::Stopped)) { + MOZ_RELEASE_ASSERT(HasDataToRead()); + MOZ_RELEASE_ASSERT(mRead->state == State::Processing); + // The writer has just signaled us, so consume it before returning + MOZ_ALWAYS_TRUE(mReaderSemaphore->Wait()); + return true; + } + + return false; +} + +int32_t CanvasEventRingBuffer::ReadNextEvent() { + int32_t nextEvent; + ReadElement(*this, nextEvent); + while (nextEvent == kCheckpointEventType && good()) { + ReadElement(*this, nextEvent); + } + + return nextEvent; +} + +uint32_t CanvasEventRingBuffer::CreateCheckpoint() { + WriteElement(*this, kCheckpointEventType); + return mOurCount; +} + +bool CanvasEventRingBuffer::WaitForCheckpoint(uint32_t aCheckpoint) { + return WaitForReadCount(aCheckpoint, kTimeout); +} + +void CanvasEventRingBuffer::CheckAndSignalWriter() { + do { + switch (mWrite->state) { + case State::Processing: + return; + case State::AboutToWait: + // The writer is making a decision about whether to wait. So, we must + // wait until it has decided to avoid races. Check if the writer is + // closed to avoid hangs. + if (mReaderServices->WriterClosed()) { + return; + } + continue; + case State::Waiting: + if (mWrite->count - mOurCount <= mWrite->requiredDifference) { + mWrite->state = State::Processing; + mWriterSemaphore->Signal(); + } + return; + default: + MOZ_ASSERT_UNREACHABLE("Invalid waiting state."); + return; + } + } while (true); +} + +bool CanvasEventRingBuffer::WaitForReadCount(uint32_t aReadCount, + TimeDuration aTimeout) { + uint32_t requiredDifference = mOurCount - aReadCount; + uint32_t spinCount = kMaxSpinCount; + do { + if (mOurCount - mRead->count <= requiredDifference) { + return true; + } + } while (--spinCount != 0); + + // Double-check that the reader isn't waiting. + CheckAndSignalReader(); + mWrite->state = State::AboutToWait; + if (mOurCount - mRead->count <= requiredDifference) { + mWrite->state = State::Processing; + return true; + } + + mWrite->requiredDifference = requiredDifference; + mWrite->state = State::Waiting; + + // Wait unless we detect the reading side has closed. + while (!mWriterServices->ReaderClosed() && mRead->state != State::Failed) { + if (mWriterSemaphore->Wait(Some(aTimeout))) { + MOZ_ASSERT(mOurCount - mRead->count <= requiredDifference); + return true; + } + } + + // Either the reader has failed or we're stopping writing for some other + // reason (e.g. shutdown), so mark us as failed so the reader is aware. + mWrite->state = State::Failed; + mGood = false; + return false; +} + +uint32_t CanvasEventRingBuffer::WaitForBytesToWrite() { + uint32_t streamFullReadCount = mOurCount - kStreamSize; + if (!WaitForReadCount(streamFullReadCount + 1, kTimeout)) { + return 0; + } + + return mRead->count - streamFullReadCount; +} + +uint32_t CanvasEventRingBuffer::WaitForBytesToRead() { + if (!WaitForDataToRead(kTimeout, kTimeoutRetryCount)) { + return 0; + } + + return mWrite->count - mOurCount; +} + +void CanvasEventRingBuffer::ReturnWrite(const char* aData, size_t aSize) { + uint32_t writeCount = mRead->returnCount; + uint32_t bufPos = writeCount % kStreamSize; + uint32_t bufRemaining = kStreamSize - bufPos; + uint32_t availableToWrite = + std::min(bufRemaining, (mWrite->returnCount + kStreamSize - writeCount)); + while (availableToWrite < aSize) { + if (availableToWrite) { + memcpy(mBuf + bufPos, aData, availableToWrite); + writeCount += availableToWrite; + mRead->returnCount = writeCount; + bufPos = writeCount % kStreamSize; + bufRemaining = kStreamSize - bufPos; + aData += availableToWrite; + aSize -= availableToWrite; + } else if (mReaderServices->WriterClosed()) { + return; + } + + availableToWrite = std::min( + bufRemaining, (mWrite->returnCount + kStreamSize - writeCount)); + } + + memcpy(mBuf + bufPos, aData, aSize); + writeCount += aSize; + mRead->returnCount = writeCount; +} + +void CanvasEventRingBuffer::ReturnRead(char* aOut, size_t aSize) { + // First wait for the event returning the data to be read. + WaitForCheckpoint(mOurCount); + uint32_t readCount = mWrite->returnCount; + + // If the event sending back data fails to play then it will ReturnWrite + // nothing. So, wait until something has been written or the reader has + // stopped processing. + while (readCount == mRead->returnCount) { + // We recheck the count, because the other side can write all the data and + // started waiting in between these two lines. + if (mRead->state != State::Processing && readCount == mRead->returnCount) { + return; + } + } + + uint32_t bufPos = readCount % kStreamSize; + uint32_t bufRemaining = kStreamSize - bufPos; + uint32_t availableToRead = + std::min(bufRemaining, (mRead->returnCount - readCount)); + while (availableToRead < aSize) { + if (availableToRead) { + memcpy(aOut, mBuf + bufPos, availableToRead); + readCount += availableToRead; + mWrite->returnCount = readCount; + bufPos = readCount % kStreamSize; + bufRemaining = kStreamSize - bufPos; + aOut += availableToRead; + aSize -= availableToRead; + } else if (mWriterServices->ReaderClosed()) { + return; + } + + availableToRead = std::min(bufRemaining, (mRead->returnCount - readCount)); + } + + memcpy(aOut, mBuf + bufPos, aSize); + readCount += aSize; + mWrite->returnCount = readCount; +} + +void CanvasDrawEventRecorder::RecordSourceSurfaceDestruction(void* aSurface) { + // We must only record things on the main thread and surfaces that have been + // recorded can sometimes be destroyed off the main thread. + if (NS_IsMainThread()) { + DrawEventRecorderPrivate::RecordSourceSurfaceDestruction(aSurface); + return; + } + + NS_DispatchToMainThread(NewRunnableMethod( + "DrawEventRecorderPrivate::RecordSourceSurfaceDestruction", this, + &DrawEventRecorderPrivate::RecordSourceSurfaceDestruction, aSurface)); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/CanvasDrawEventRecorder.h b/gfx/layers/CanvasDrawEventRecorder.h new file mode 100644 index 0000000000..7fb69669ec --- /dev/null +++ b/gfx/layers/CanvasDrawEventRecorder.h @@ -0,0 +1,284 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_CanvasDrawEventRecorder_h +#define mozilla_layers_CanvasDrawEventRecorder_h + +#include "mozilla/gfx/DrawEventRecorder.h" +#include "mozilla/ipc/CrossProcessSemaphore.h" +#include "mozilla/ipc/SharedMemoryBasic.h" + +namespace mozilla { +namespace layers { + +class CanvasEventRingBuffer final : public gfx::EventRingBuffer { + public: + /** + * WriterServices allows consumers of CanvasEventRingBuffer to provide + * functions required by the write side of a CanvasEventRingBuffer without + * introducing unnecessary dependencies on IPC code. + */ + class WriterServices { + public: + virtual ~WriterServices() = default; + + /** + * @returns true if the reader of the CanvasEventRingBuffer has permanently + * stopped processing, otherwise returns false. + */ + virtual bool ReaderClosed() = 0; + + /** + * Causes the reader to resume processing when it is in a stopped state. + */ + virtual void ResumeReader() = 0; + }; + + /** + * ReaderServices allows consumers of CanvasEventRingBuffer to provide + * functions required by the read side of a CanvasEventRingBuffer without + * introducing unnecessary dependencies on IPC code. + */ + class ReaderServices { + public: + virtual ~ReaderServices() = default; + + /** + * @returns true if the writer of the CanvasEventRingBuffer has permanently + * stopped processing, otherwise returns false. + */ + virtual bool WriterClosed() = 0; + }; + + CanvasEventRingBuffer() {} + + /** + * Initialize the write side of a CanvasEventRingBuffer returning handles to + * the shared memory for the buffer and the two semaphores for waiting in the + * reader and the writer. + * + * @param aOtherPid process ID to share the handles to + * @param aReadHandle handle to the shared memory for the buffer + * @param aReaderSem reading blocked semaphore + * @param aWriterSem writing blocked semaphore + * @param aWriterServices provides functions required by the writer + * @returns true if initialization succeeds + */ + bool InitWriter(base::ProcessId aOtherPid, + ipc::SharedMemoryBasic::Handle* aReadHandle, + CrossProcessSemaphoreHandle* aReaderSem, + CrossProcessSemaphoreHandle* aWriterSem, + UniquePtr aWriterServices); + + /** + * Initialize the read side of a CanvasEventRingBuffer. + * + * @param aReadHandle handle to the shared memory for the buffer + * @param aReaderSem reading blocked semaphore + * @param aWriterSem writing blocked semaphore + * @param aReaderServices provides functions required by the reader + * @returns true if initialization succeeds + */ + bool InitReader(const ipc::SharedMemoryBasic::Handle& aReadHandle, + const CrossProcessSemaphoreHandle& aReaderSem, + const CrossProcessSemaphoreHandle& aWriterSem, + UniquePtr aReaderServices); + + bool good() const final { return mGood; } + + bool WriterFailed() const { return mWrite->state == State::Failed; } + + void SetIsBad() final { + mGood = false; + mRead->state = State::Failed; + } + + void write(const char* const aData, const size_t aSize) final; + + bool HasDataToRead(); + + /* + * This will put the reader into a stopped state if there is no more data to + * read. If this returns false the caller is responsible for continuing + * translation at a later point. If it returns false the writer will start the + * translation again when more data is written. + * + * @returns true if stopped + */ + bool StopIfEmpty(); + + /* + * Waits for data to become available. This will wait for aTimeout duration + * aRetryCount number of times, checking to see if the other side is closed in + * between each one. + * + * @param aTimeout duration to wait + * @param aRetryCount number of times to retry + * @returns true if data is available to read. + */ + bool WaitForDataToRead(TimeDuration aTimeout, int32_t aRetryCount); + + int32_t ReadNextEvent(); + + void read(char* const aOut, const size_t aSize) final; + + /** + * Writes a checkpoint event to the buffer. + * + * @returns the write count after the checkpoint has been written + */ + uint32_t CreateCheckpoint(); + + /** + * Waits until the given checkpoint has been read from the buffer. + * + * @params aCheckpoint the checkpoint to wait for + * @params aTimeout duration to wait while reader is not active + * @returns true if the checkpoint was reached, false if the reader is closed + * or we timeout. + */ + bool WaitForCheckpoint(uint32_t aCheckpoint); + + /** + * Used to send data back to the writer. This is done through the same shared + * memory so the writer must wait and read the response after it has submitted + * the event that uses this. + * + * @param aData the data to be written back to the writer + * @param aSize the number of chars to write + */ + void ReturnWrite(const char* aData, size_t aSize); + + /** + * Used to read data sent back from the reader via ReturnWrite. This is done + * through the same shared memory so the writer must wait until all expected + * data is read before writing new events to the buffer. + * + * @param aOut the pointer to read into + * @param aSize the number of chars to read + */ + void ReturnRead(char* aOut, size_t aSize); + + protected: + bool WaitForAndRecalculateAvailableSpace() final; + void UpdateWriteTotalsBy(uint32_t aCount) final; + + private: + enum class State : uint32_t { + Processing, + + /** + * This is the important state to make sure the other side signals or starts + * us as soon as data or space is available. We set AboutToWait first and + * then re-check the condition. If we went straight to Waiting or Stopped + * then in between the last check and setting the state, the other side + * could have used all available data or space and never have signaled us + * because it didn't know we were about to wait, causing a deadlock. + * While we are in this state, the other side must wait until we resolve the + * AboutToWait state to one of the other states and then signal or start us + * if it needs to. + */ + AboutToWait, + Waiting, + Stopped, + Failed, + }; + + struct ReadFooter { + Atomic count; + Atomic returnCount; + Atomic state; + }; + + struct WriteFooter { + Atomic count; + Atomic returnCount; + Atomic requiredDifference; + Atomic state; + }; + + CanvasEventRingBuffer(const CanvasEventRingBuffer&) = delete; + void operator=(const CanvasEventRingBuffer&) = delete; + + void IncrementWriteCountBy(uint32_t aCount); + + bool WaitForReadCount(uint32_t aReadCount, TimeDuration aTimeout); + + bool WaitForAndRecalculateAvailableData(); + + void UpdateReadTotalsBy(uint32_t aCount); + void IncrementReadCountBy(uint32_t aCount); + + void CheckAndSignalReader(); + + void CheckAndSignalWriter(); + + uint32_t WaitForBytesToWrite(); + + uint32_t WaitForBytesToRead(); + + RefPtr mSharedMemory; + UniquePtr mReaderSemaphore; + UniquePtr mWriterSemaphore; + UniquePtr mWriterServices; + UniquePtr mReaderServices; + char* mBuf = nullptr; + uint32_t mOurCount = 0; + WriteFooter* mWrite = nullptr; + ReadFooter* mRead = nullptr; + bool mGood = false; +}; + +class CanvasDrawEventRecorder final : public gfx::DrawEventRecorderPrivate { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(CanvasDrawEventRecorder, final) + explicit CanvasDrawEventRecorder(){}; + + bool Init(base::ProcessId aOtherPid, ipc::SharedMemoryBasic::Handle* aHandle, + CrossProcessSemaphoreHandle* aReaderSem, + CrossProcessSemaphoreHandle* aWriterSem, + UniquePtr aWriterServices) { + return mOutputStream.InitWriter(aOtherPid, aHandle, aReaderSem, aWriterSem, + std::move(aWriterServices)); + } + + void RecordEvent(const gfx::RecordedEvent& aEvent) final { + if (!mOutputStream.good()) { + return; + } + + aEvent.RecordToStream(mOutputStream); + } + + void RecordSourceSurfaceDestruction(void* aSurface) final; + + void Flush() final {} + + void ReturnRead(char* aOut, size_t aSize) { + mOutputStream.ReturnRead(aOut, aSize); + } + + uint32_t CreateCheckpoint() { return mOutputStream.CreateCheckpoint(); } + + /** + * Waits until the given checkpoint has been read by the translator. + * + * @params aCheckpoint the checkpoint to wait for + * @returns true if the checkpoint was reached, false if the reader is closed + * or we timeout. + */ + bool WaitForCheckpoint(uint32_t aCheckpoint) { + return mOutputStream.WaitForCheckpoint(aCheckpoint); + } + + private: + CanvasEventRingBuffer mOutputStream; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_CanvasDrawEventRecorder_h diff --git a/gfx/layers/CanvasRenderer.cpp b/gfx/layers/CanvasRenderer.cpp new file mode 100644 index 0000000000..561d8dc14c --- /dev/null +++ b/gfx/layers/CanvasRenderer.cpp @@ -0,0 +1,139 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "CanvasRenderer.h" + +#include "BuildConstants.h" +#include "ipc/KnowsCompositor.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/StaticPrefs_webgl.h" +#include "nsICanvasRenderingContextInternal.h" +#include "PersistentBufferProvider.h" +#include "WebGLTypes.h" + +namespace mozilla { +namespace layers { + +CanvasRendererData::CanvasRendererData() = default; +CanvasRendererData::~CanvasRendererData() = default; + +// - + +BorrowedSourceSurface::BorrowedSourceSurface( + PersistentBufferProvider* const returnTo, + const RefPtr surf) + : mReturnTo(returnTo), mSurf(surf) {} + +BorrowedSourceSurface::~BorrowedSourceSurface() { + if (mReturnTo) { + auto forgettable = mSurf; + mReturnTo->ReturnSnapshot(forgettable.forget()); + } +} + +// - + +CanvasRenderer::CanvasRenderer() { MOZ_COUNT_CTOR(CanvasRenderer); } + +CanvasRenderer::~CanvasRenderer() { MOZ_COUNT_DTOR(CanvasRenderer); } + +void CanvasRenderer::Initialize(const CanvasRendererData& aData) { + mData = aData; +} + +bool CanvasRenderer::IsDataValid(const CanvasRendererData& aData) const { + return mData.GetContext() == aData.GetContext(); +} + +std::shared_ptr CanvasRenderer::BorrowSnapshot( + const bool requireAlphaPremult) const { + const auto context = mData.GetContext(); + if (!context) return nullptr; + RefPtr provider = context->GetBufferProvider(); + + RefPtr ss; + + if (provider) { + ss = provider->BorrowSnapshot(); + } + if (!ss) { + provider = nullptr; + ss = context->GetFrontBufferSnapshot(requireAlphaPremult); + } + if (!ss) return nullptr; + + return std::make_shared(provider, ss); +} + +void CanvasRenderer::FirePreTransactionCallback() const { + if (!mData.mDoPaintCallbacks) return; + const auto context = mData.GetContext(); + if (!context) return; + context->OnBeforePaintTransaction(); +} + +void CanvasRenderer::FireDidTransactionCallback() const { + if (!mData.mDoPaintCallbacks) return; + const auto context = mData.GetContext(); + if (!context) return; + context->OnDidPaintTransaction(); +} + +TextureType TexTypeForWebgl(KnowsCompositor* const knowsCompositor) { + if (!knowsCompositor) return TextureType::Unknown; + const auto layersBackend = knowsCompositor->GetCompositorBackendType(); + + switch (layersBackend) { + case LayersBackend::LAYERS_CLIENT: + MOZ_CRASH("Unexpected LayersBackend::LAYERS_CLIENT"); + case LayersBackend::LAYERS_LAST: + MOZ_CRASH("Unexpected LayersBackend::LAYERS_LAST"); + + case LayersBackend::LAYERS_NONE: + case LayersBackend::LAYERS_BASIC: + return TextureType::Unknown; + + case LayersBackend::LAYERS_D3D11: + case LayersBackend::LAYERS_OPENGL: + case LayersBackend::LAYERS_WR: + break; + } + + if (kIsWindows) { + if (knowsCompositor->SupportsD3D11()) { + return TextureType::D3D11; + } + return TextureType::Unknown; + } + if (kIsMacOS) { + return TextureType::MacIOSurface; + } + if (kIsWayland) { + if (knowsCompositor->UsingSoftwareWebRender()) { + return TextureType::Unknown; + } + return TextureType::DMABUF; + } + if (kIsX11) { + if (knowsCompositor->UsingSoftwareWebRender()) { + return TextureType::Unknown; + } + return TextureType::X11; + } + if (kIsAndroid) { + if (gfx::gfxVars::UseAHardwareBufferSharedSurface()) { + return TextureType::AndroidHardwareBuffer; + } + if (StaticPrefs::webgl_enable_surface_texture()) { + return TextureType::AndroidNativeWindow; + } + } + + return TextureType::Unknown; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/CanvasRenderer.h b/gfx/layers/CanvasRenderer.h new file mode 100644 index 0000000000..ca3ae088cc --- /dev/null +++ b/gfx/layers/CanvasRenderer.h @@ -0,0 +1,153 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_CANVASRENDERER_H +#define GFX_CANVASRENDERER_H + +#include // for weak_ptr +#include // for uint32_t +#include "GLContextTypes.h" // for GLContext +#include "gfxContext.h" // for gfxContext, etc +#include "gfxTypes.h" +#include "gfxPlatform.h" // for gfxImageFormat +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/Preferences.h" // for Preferences +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/gfx/2D.h" // for DrawTarget +#include "mozilla/mozalloc.h" // for operator delete, etc +#include "mozilla/WeakPtr.h" // for WeakPtr +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc + +class nsICanvasRenderingContextInternal; + +namespace mozilla { +namespace layers { + +class ClientCanvasRenderer; +class KnowsCompositor; +class PersistentBufferProvider; +class WebRenderCanvasRendererAsync; + +TextureType TexTypeForWebgl(KnowsCompositor*); + +struct CanvasRendererData final { + CanvasRendererData(); + ~CanvasRendererData(); + + std::weak_ptr + mContext; // weak_ptr to ptr (bug 1635644) + + // The size of the canvas content + gfx::IntSize mSize = {0, 0}; + + bool mDoPaintCallbacks = false; + bool mIsOpaque = true; + bool mIsAlphaPremult = true; + + gl::OriginPos mOriginPos = gl::OriginPos::TopLeft; + + nsICanvasRenderingContextInternal* GetContext() const { + const auto ptrToPtr = mContext.lock(); + if (!ptrToPtr) return nullptr; + return *ptrToPtr; + } +}; + +// Based class which used for canvas rendering. There are many derived classes +// for different purposes. such as: +// +// ShareableCanvasRenderer provides IPC capabilities that allow sending canvas +// content over IPC. This is pure virtual class because the IPC handling is +// different in different LayerManager. So that we have following classes +// inherit ShareableCanvasRenderer. +// +// ClientCanvasRenderer inherits ShareableCanvasRenderer and be used in +// ClientCanvasLayer. +// WebRenderCanvasRenderer inherits ShareableCanvasRenderer and provides all +// functionality that WebRender uses. +// WebRenderCanvasRendererAsync inherits WebRenderCanvasRenderer and be used in +// layers-free mode of WebRender. +// +// class diagram: +// +// +--------------+ +// |CanvasRenderer| +// +-------+------+ +// ^ +// | +// +-----------+-----------+ +// |ShareableCanvasRenderer| +// +-----+-----------------+ +// ^ ^ +// +-------------+ +-------+ +// | | +// +--------------------+ +---------+-------------+ +// |ClientCanvasRenderer| |WebRenderCanvasRenderer| +// +--------------------+ +-----------+-----------+ +// ^ +// | +// +-------------+--------------+ +// |WebRenderCanvasRendererAsync| +// +----------------------------+ + +class BorrowedSourceSurface final { + public: + const WeakPtr mReturnTo; + const RefPtr mSurf; /// non-null + + BorrowedSourceSurface(PersistentBufferProvider*, RefPtr); + ~BorrowedSourceSurface(); +}; + +// - + +class CanvasRenderer : public RefCounted { + friend class CanvasRendererSourceSurface; + + public: + MOZ_DECLARE_REFCOUNTED_TYPENAME(CanvasRenderer) + + private: + bool mDirty = false; + + protected: + CanvasRendererData mData; + + public: + explicit CanvasRenderer(); + virtual ~CanvasRenderer(); + + public: + virtual void Initialize(const CanvasRendererData&); + virtual bool IsDataValid(const CanvasRendererData&) const; + + virtual void ClearCachedResources() {} + virtual void DisconnectClient() {} + + const gfx::IntSize& GetSize() const { return mData.mSize; } + bool IsOpaque() const { return mData.mIsOpaque; } + bool YIsDown() const { return mData.mOriginPos == gl::OriginPos::TopLeft; } + + void SetDirty() { mDirty = true; } + void ResetDirty() { mDirty = false; } + bool IsDirty() const { return mDirty; } + + virtual ClientCanvasRenderer* AsClientCanvasRenderer() { return nullptr; } + virtual WebRenderCanvasRendererAsync* AsWebRenderCanvasRendererAsync() { + return nullptr; + } + + std::shared_ptr BorrowSnapshot( + bool requireAlphaPremult = true) const; + + void FirePreTransactionCallback() const; + void FireDidTransactionCallback() const; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/CompositionRecorder.cpp b/gfx/layers/CompositionRecorder.cpp new file mode 100644 index 0000000000..f4bef815e7 --- /dev/null +++ b/gfx/layers/CompositionRecorder.cpp @@ -0,0 +1,107 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "CompositionRecorder.h" +#include "gfxUtils.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/gfxVars.h" +#include "nsIInputStream.h" +#include "nsIBinaryOutputStream.h" +#include "nsIObjectOutputStream.h" +#include "prtime.h" + +#include +#include +#include "stdio.h" +#ifdef XP_WIN +# include "direct.h" +#else +# include +# include "sys/stat.h" +#endif + +using namespace mozilla::gfx; + +namespace mozilla { +namespace layers { + +CompositionRecorder::CompositionRecorder(TimeStamp aRecordingStart) + : mRecordingStart(aRecordingStart) {} + +void CompositionRecorder::RecordFrame(RecordedFrame* aFrame) { + mCollectedFrames.AppendElement(aFrame); +} + +void CompositionRecorder::WriteCollectedFrames() { + // The directory has the format of + // "[LayersWindowRecordingPath]/windowrecording-[mRecordingStartTime as unix + // timestamp]". We need mRecordingStart as a unix timestamp here because we + // want the consumer of these files to be able to compute an absolute + // timestamp of each screenshot, so that we can align screenshots with timed + // data from other sources, such as Gecko profiler information. The time of + // each screenshot is part of the screenshot's filename, expressed as + // milliseconds *relative to mRecordingStart*. We want to compute the number + // of milliseconds between midnight 1 January 1970 UTC and mRecordingStart, + // unfortunately, mozilla::TimeStamp does not have a built-in way of doing + // that. However, PR_Now() returns the number of microseconds since midnight 1 + // January 1970 UTC. We call PR_Now() and TimeStamp::NowUnfuzzed() very + // closely to each other so that they return their representation of "the same + // time", and then compute (Now - (Now - mRecordingStart)). + std::stringstream str; + nsCString recordingStartTime; + TimeDuration delta = TimeStamp::NowUnfuzzed() - mRecordingStart; + recordingStartTime.AppendFloat( + static_cast(PR_Now() / 1000.0 - delta.ToMilliseconds())); + str << gfxVars::LayersWindowRecordingPath() << "windowrecording-" + << recordingStartTime; + +#ifdef XP_WIN + _mkdir(str.str().c_str()); +#else + mkdir(str.str().c_str(), 0777); +#endif + + uint32_t i = 1; + for (RefPtr& frame : mCollectedFrames) { + RefPtr surf = frame->GetSourceSurface(); + std::stringstream filename; + filename << str.str() << "/frame-" << i << "-" + << uint32_t( + (frame->GetTimeStamp() - mRecordingStart).ToMilliseconds()) + << ".png"; + gfxUtils::WriteAsPNG(surf, filename.str().c_str()); + i++; + } + mCollectedFrames.Clear(); +} + +CollectedFrames CompositionRecorder::GetCollectedFrames() { + nsTArray frames; + + TimeDuration delta = TimeStamp::NowUnfuzzed() - mRecordingStart; + double recordingStart = PR_Now() / 1000.0 - delta.ToMilliseconds(); + + for (RefPtr& frame : mCollectedFrames) { + nsCString buffer; + + RefPtr surf = frame->GetSourceSurface(); + double offset = (frame->GetTimeStamp() - mRecordingStart).ToMilliseconds(); + + gfxUtils::EncodeSourceSurface(surf, ImageType::PNG, u""_ns, + gfxUtils::eDataURIEncode, nullptr, &buffer); + + frames.EmplaceBack(offset, std::move(buffer)); + } + + mCollectedFrames.Clear(); + + return CollectedFrames(recordingStart, std::move(frames)); +} + +void CompositionRecorder::ClearCollectedFrames() { mCollectedFrames.Clear(); } + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/CompositionRecorder.h b/gfx/layers/CompositionRecorder.h new file mode 100644 index 0000000000..59c6bafa82 --- /dev/null +++ b/gfx/layers/CompositionRecorder.h @@ -0,0 +1,105 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_CompositionRecorder_h +#define mozilla_layers_CompositionRecorder_h + +#include "mozilla/RefPtr.h" +#include "mozilla/TimeStamp.h" +#include "nsISupportsImpl.h" +#include "nsTArray.h" +#include "nsString.h" + +namespace mozilla { + +namespace gfx { +class DataSourceSurface; +} + +namespace layers { + +/** + * A captured frame from a |LayerManager|. + */ +class RecordedFrame { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RecordedFrame) + + // The resulting DataSourceSurface must not be kept alive beyond the lifetime + // of the RecordedFrame object, since it may refer to data owned by the frame. + virtual already_AddRefed GetSourceSurface() = 0; + TimeStamp GetTimeStamp() { return mTimeStamp; } + + protected: + virtual ~RecordedFrame() = default; + RecordedFrame(const TimeStamp& aTimeStamp) : mTimeStamp(aTimeStamp) {} + + private: + TimeStamp mTimeStamp; +}; + +/** + * A recorded frame that has been encoded into a data: URI. + */ +struct CollectedFrame { + CollectedFrame(double aTimeOffset, nsCString&& aDataUri) + : mTimeOffset(aTimeOffset), mDataUri(std::move(aDataUri)) {} + + double mTimeOffset; + nsCString mDataUri; +}; + +/** + * All of the frames collected during a composition recording session. + */ +struct CollectedFrames { + CollectedFrames(double aRecordingStart, nsTArray&& aFrames) + : mRecordingStart(aRecordingStart), mFrames(std::move(aFrames)) {} + + double mRecordingStart; + nsTArray mFrames; +}; + +/** + * A recorder for composited frames. + * + * This object collects frames sent to it by a |LayerManager| and writes them + * out as a series of images until recording has finished. + * + * If GPU-accelerated rendering is used, the frames will not be mapped into + * memory until |WriteCollectedFrames| is called. + */ +class CompositionRecorder { + public: + explicit CompositionRecorder(TimeStamp aRecordingStart); + + /** + * Record a composited frame. + */ + void RecordFrame(RecordedFrame* aFrame); + + /** + * Write out the collected frames as a series of timestamped images. + */ + void WriteCollectedFrames(); + + /** + * Return the collected frames as an array of their timestamps and contents. + */ + CollectedFrames GetCollectedFrames(); + + protected: + void ClearCollectedFrames(); + + private: + nsTArray> mCollectedFrames; + TimeStamp mRecordingStart; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_CompositionRecorder_h diff --git a/gfx/layers/Compositor.cpp b/gfx/layers/Compositor.cpp new file mode 100644 index 0000000000..80fbb97c87 --- /dev/null +++ b/gfx/layers/Compositor.cpp @@ -0,0 +1,616 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/Compositor.h" +#include "mozilla/layers/CompositionRecorder.h" +#include "base/message_loop.h" // for MessageLoop +#include "mozilla/gfx/Types.h" +#include "mozilla/layers/CompositorBridgeParent.h" // for CompositorBridgeParent +#include "mozilla/layers/Diagnostics.h" +#include "mozilla/layers/Effects.h" // for Effect, EffectChain, etc +#include "mozilla/layers/TextureClient.h" +#include "mozilla/layers/TextureHost.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/mozalloc.h" // for operator delete, etc +#include "GeckoProfiler.h" +#include "gfx2DGlue.h" +#include "nsAppRunner.h" +#include "LayersHelpers.h" + +namespace mozilla { + +namespace layers { + +class CompositorRecordedFrame final : public RecordedFrame { + public: + CompositorRecordedFrame(const TimeStamp& aTimeStamp, + RefPtr&& aBuffer) + : RecordedFrame(aTimeStamp), mBuffer(aBuffer) {} + + virtual already_AddRefed GetSourceSurface() override { + if (mSurface) { + return do_AddRef(mSurface); + } + + gfx::IntSize size = mBuffer->GetSize(); + + mSurface = gfx::Factory::CreateDataSourceSurface( + size, gfx::SurfaceFormat::B8G8R8A8, + /* aZero = */ false); + + if (!mBuffer->MapAndCopyInto(mSurface, size)) { + mSurface = nullptr; + return nullptr; + } + + return do_AddRef(mSurface); + } + + private: + RefPtr mBuffer; + RefPtr mSurface; +}; + +Compositor::Compositor(widget::CompositorWidget* aWidget, + CompositorBridgeParent* aParent) + : mDiagnosticTypes(DiagnosticTypes::NO_DIAGNOSTIC), + mParent(aParent), + mPixelsPerFrame(0), + mPixelsFilled(0), + mScreenRotation(ROTATION_0), + mWidget(aWidget), + mIsDestroyed(false) +#if defined(MOZ_WIDGET_ANDROID) + // If the default color isn't white for Fennec, there is a black + // flash before the first page of a tab is loaded. + , + mClearColor(ToDeviceColor(sRGBColor::OpaqueWhite())), + mDefaultClearColor(ToDeviceColor(sRGBColor::OpaqueWhite())) +#else + , + mClearColor(gfx::DeviceColor()), + mDefaultClearColor(gfx::DeviceColor()) +#endif +{ +} + +Compositor::~Compositor() { ReadUnlockTextures(); } + +void Compositor::Destroy() { + mWidget = nullptr; + + TextureSourceProvider::Destroy(); + mIsDestroyed = true; +} + +void Compositor::EndFrame() { + ReadUnlockTextures(); + mLastCompositionEndTime = TimeStamp::Now(); +} + +bool Compositor::ShouldDrawDiagnostics(DiagnosticFlags aFlags) { + if ((aFlags & DiagnosticFlags::TILE) && + !(mDiagnosticTypes & DiagnosticTypes::TILE_BORDERS)) { + return false; + } + if ((aFlags & DiagnosticFlags::BIGIMAGE) && + !(mDiagnosticTypes & DiagnosticTypes::BIGIMAGE_BORDERS)) { + return false; + } + if (mDiagnosticTypes == DiagnosticTypes::NO_DIAGNOSTIC) { + return false; + } + return true; +} + +void Compositor::DrawDiagnostics(DiagnosticFlags aFlags, + const nsIntRegion& aVisibleRegion, + const gfx::IntRect& aClipRect, + const gfx::Matrix4x4& aTransform, + uint32_t aFlashCounter) { + if (!ShouldDrawDiagnostics(aFlags)) { + return; + } + + if (aVisibleRegion.GetNumRects() > 1) { + for (auto iter = aVisibleRegion.RectIter(); !iter.Done(); iter.Next()) { + DrawDiagnostics(aFlags | DiagnosticFlags::REGION_RECT, + IntRectToRect(iter.Get()), aClipRect, aTransform, + aFlashCounter); + } + } + + DrawDiagnostics(aFlags, IntRectToRect(aVisibleRegion.GetBounds()), aClipRect, + aTransform, aFlashCounter); +} + +void Compositor::DrawDiagnostics(DiagnosticFlags aFlags, + const gfx::Rect& aVisibleRect, + const gfx::IntRect& aClipRect, + const gfx::Matrix4x4& aTransform, + uint32_t aFlashCounter) { + if (!ShouldDrawDiagnostics(aFlags)) { + return; + } + + DrawDiagnosticsInternal(aFlags, aVisibleRect, aClipRect, aTransform, + aFlashCounter); +} + +void Compositor::DrawDiagnosticsInternal(DiagnosticFlags aFlags, + const gfx::Rect& aVisibleRect, + const gfx::IntRect& aClipRect, + const gfx::Matrix4x4& aTransform, + uint32_t aFlashCounter) { +#ifdef ANDROID + int lWidth = 10; +#else + int lWidth = 2; +#endif + + // Technically it is sRGB but it is just for debugging. + gfx::DeviceColor color; + if (aFlags & DiagnosticFlags::CONTENT) { + color = gfx::DeviceColor(0.0f, 1.0f, 0.0f, 1.0f); // green + if (aFlags & DiagnosticFlags::COMPONENT_ALPHA) { + color = gfx::DeviceColor(0.0f, 1.0f, 1.0f, 1.0f); // greenish blue + } + } else if (aFlags & DiagnosticFlags::IMAGE) { + if (aFlags & DiagnosticFlags::NV12) { + color = gfx::DeviceColor(1.0f, 1.0f, 0.0f, 1.0f); // yellow + } else if (aFlags & DiagnosticFlags::YCBCR) { + color = gfx::DeviceColor(1.0f, 0.55f, 0.0f, 1.0f); // orange + } else { + color = gfx::DeviceColor(1.0f, 0.0f, 0.0f, 1.0f); // red + } + } else if (aFlags & DiagnosticFlags::COLOR) { + color = gfx::DeviceColor(0.0f, 0.0f, 1.0f, 1.0f); // blue + } else if (aFlags & DiagnosticFlags::CONTAINER) { + color = gfx::DeviceColor(0.8f, 0.0f, 0.8f, 1.0f); // purple + } + + // make tile borders a bit more transparent to keep layer borders readable. + if (aFlags & DiagnosticFlags::TILE || aFlags & DiagnosticFlags::BIGIMAGE || + aFlags & DiagnosticFlags::REGION_RECT) { + lWidth = 1; + color.r *= 0.7f; + color.g *= 0.7f; + color.b *= 0.7f; + color.a = color.a * 0.5f; + } else { + color.a = color.a * 0.7f; + } + + if (mDiagnosticTypes & DiagnosticTypes::FLASH_BORDERS) { + float flash = (float)aFlashCounter / (float)DIAGNOSTIC_FLASH_COUNTER_MAX; + color.r *= flash; + color.g *= flash; + color.b *= flash; + } + + SlowDrawRect(aVisibleRect, color, aClipRect, aTransform, lWidth); +} + +static void UpdateTextureCoordinates(gfx::TexturedTriangle& aTriangle, + const gfx::Rect& aRect, + const gfx::Rect& aIntersection, + const gfx::Rect& aTextureCoords) { + // Calculate the relative offset of the intersection within the layer. + float dx = (aIntersection.X() - aRect.X()) / aRect.Width(); + float dy = (aIntersection.Y() - aRect.Y()) / aRect.Height(); + + // Update the texture offset. + float x = aTextureCoords.X() + dx * aTextureCoords.Width(); + float y = aTextureCoords.Y() + dy * aTextureCoords.Height(); + + // Scale the texture width and height. + float w = aTextureCoords.Width() * aIntersection.Width() / aRect.Width(); + float h = aTextureCoords.Height() * aIntersection.Height() / aRect.Height(); + + static const auto Clamp = [](float& f) { + if (f >= 1.0f) f = 1.0f; + if (f <= 0.0f) f = 0.0f; + }; + + auto UpdatePoint = [&](const gfx::Point& p, gfx::Point& t) { + t.x = x + (p.x - aIntersection.X()) / aIntersection.Width() * w; + t.y = y + (p.y - aIntersection.Y()) / aIntersection.Height() * h; + + Clamp(t.x); + Clamp(t.y); + }; + + UpdatePoint(aTriangle.p1, aTriangle.textureCoords.p1); + UpdatePoint(aTriangle.p2, aTriangle.textureCoords.p2); + UpdatePoint(aTriangle.p3, aTriangle.textureCoords.p3); +} + +void Compositor::DrawGeometry(const gfx::Rect& aRect, + const gfx::IntRect& aClipRect, + const EffectChain& aEffectChain, + gfx::Float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::Rect& aVisibleRect, + const Maybe& aGeometry) { + if (aRect.IsEmpty()) { + return; + } + + if (!aGeometry || !SupportsLayerGeometry()) { + DrawQuad(aRect, aClipRect, aEffectChain, aOpacity, aTransform, + aVisibleRect); + return; + } + + // Cull completely invisible polygons. + if (aRect.Intersect(aGeometry->BoundingBox()).IsEmpty()) { + return; + } + + const gfx::Polygon clipped = aGeometry->ClipPolygon(aRect); + + // Cull polygons with no area. + if (clipped.IsEmpty()) { + return; + } + + DrawPolygon(clipped, aRect, aClipRect, aEffectChain, aOpacity, aTransform, + aVisibleRect); +} + +void Compositor::DrawTriangles( + const nsTArray& aTriangles, const gfx::Rect& aRect, + const gfx::IntRect& aClipRect, const EffectChain& aEffectChain, + gfx::Float aOpacity, const gfx::Matrix4x4& aTransform, + const gfx::Rect& aVisibleRect) { + for (const gfx::TexturedTriangle& triangle : aTriangles) { + DrawTriangle(triangle, aClipRect, aEffectChain, aOpacity, aTransform, + aVisibleRect); + } +} + +nsTArray GenerateTexturedTriangles( + const gfx::Polygon& aPolygon, const gfx::Rect& aRect, + const gfx::Rect& aTexRect) { + nsTArray texturedTriangles; + + gfx::Rect layerRects[4]; + gfx::Rect textureRects[4]; + size_t rects = + DecomposeIntoNoRepeatRects(aRect, aTexRect, &layerRects, &textureRects); + for (size_t i = 0; i < rects; ++i) { + const gfx::Rect& rect = layerRects[i]; + const gfx::Rect& texRect = textureRects[i]; + const gfx::Polygon clipped = aPolygon.ClipPolygon(rect); + + if (clipped.IsEmpty()) { + continue; + } + + for (const gfx::Triangle& triangle : clipped.ToTriangles()) { + const gfx::Rect intersection = rect.Intersect(triangle.BoundingBox()); + + // Cull completely invisible triangles. + if (intersection.IsEmpty()) { + continue; + } + + MOZ_ASSERT(rect.Width() > 0.0f && rect.Height() > 0.0f); + MOZ_ASSERT(intersection.Width() > 0.0f && intersection.Height() > 0.0f); + + // Since the texture was created for non-split geometry, we need to + // update the texture coordinates to account for the split. + gfx::TexturedTriangle t(triangle); + UpdateTextureCoordinates(t, rect, intersection, texRect); + texturedTriangles.AppendElement(std::move(t)); + } + } + + return texturedTriangles; +} + +nsTArray TexturedTrianglesToVertexArray( + const nsTArray& aTriangles) { + const auto VertexFromPoints = [](const gfx::Point& p, const gfx::Point& t) { + return TexturedVertex{{p.x, p.y}, {t.x, t.y}}; + }; + + nsTArray vertices; + + for (const gfx::TexturedTriangle& t : aTriangles) { + vertices.AppendElement(VertexFromPoints(t.p1, t.textureCoords.p1)); + vertices.AppendElement(VertexFromPoints(t.p2, t.textureCoords.p2)); + vertices.AppendElement(VertexFromPoints(t.p3, t.textureCoords.p3)); + } + + return vertices; +} + +void Compositor::DrawPolygon(const gfx::Polygon& aPolygon, + const gfx::Rect& aRect, + const gfx::IntRect& aClipRect, + const EffectChain& aEffectChain, + gfx::Float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::Rect& aVisibleRect) { + nsTArray texturedTriangles; + + TexturedEffect* texturedEffect = + aEffectChain.mPrimaryEffect->AsTexturedEffect(); + + if (texturedEffect) { + texturedTriangles = GenerateTexturedTriangles( + aPolygon, aRect, texturedEffect->mTextureCoords); + } else { + for (const gfx::Triangle& triangle : aPolygon.ToTriangles()) { + texturedTriangles.AppendElement(gfx::TexturedTriangle(triangle)); + } + } + + if (texturedTriangles.IsEmpty()) { + // Nothing to render. + return; + } + + DrawTriangles(texturedTriangles, aRect, aClipRect, aEffectChain, aOpacity, + aTransform, aVisibleRect); +} + +void Compositor::SlowDrawRect(const gfx::Rect& aRect, + const gfx::DeviceColor& aColor, + const gfx::IntRect& aClipRect, + const gfx::Matrix4x4& aTransform, + int aStrokeWidth) { + // TODO This should draw a rect using a single draw call but since + // this is only used for debugging overlays it's not worth optimizing ATM. + float opacity = 1.0f; + EffectChain effects; + + effects.mPrimaryEffect = new EffectSolidColor(aColor); + // left + this->DrawQuad(gfx::Rect(aRect.X(), aRect.Y(), aStrokeWidth, aRect.Height()), + aClipRect, effects, opacity, aTransform); + // top + this->DrawQuad(gfx::Rect(aRect.X() + aStrokeWidth, aRect.Y(), + aRect.Width() - 2 * aStrokeWidth, aStrokeWidth), + aClipRect, effects, opacity, aTransform); + // right + this->DrawQuad(gfx::Rect(aRect.XMost() - aStrokeWidth, aRect.Y(), + aStrokeWidth, aRect.Height()), + aClipRect, effects, opacity, aTransform); + // bottom + this->DrawQuad( + gfx::Rect(aRect.X() + aStrokeWidth, aRect.YMost() - aStrokeWidth, + aRect.Width() - 2 * aStrokeWidth, aStrokeWidth), + aClipRect, effects, opacity, aTransform); +} + +void Compositor::FillRect(const gfx::Rect& aRect, + const gfx::DeviceColor& aColor, + const gfx::IntRect& aClipRect, + const gfx::Matrix4x4& aTransform) { + float opacity = 1.0f; + EffectChain effects; + + effects.mPrimaryEffect = new EffectSolidColor(aColor); + this->DrawQuad(aRect, aClipRect, effects, opacity, aTransform); +} + +static float WrapTexCoord(float v) { + // This should return values in range [0, 1.0) + return v - floorf(v); +} + +static void SetRects(size_t n, decomposedRectArrayT* aLayerRects, + decomposedRectArrayT* aTextureRects, float x0, float y0, + float x1, float y1, float tx0, float ty0, float tx1, + float ty1, bool flip_y) { + if (flip_y) { + std::swap(ty0, ty1); + } + (*aLayerRects)[n] = gfx::Rect(x0, y0, x1 - x0, y1 - y0); + (*aTextureRects)[n] = gfx::Rect(tx0, ty0, tx1 - tx0, ty1 - ty0); +} + +#ifdef DEBUG +static inline bool FuzzyEqual(float a, float b) { + return fabs(a - b) < 0.0001f; +} +static inline bool FuzzyLTE(float a, float b) { return a <= b + 0.0001f; } +#endif + +size_t DecomposeIntoNoRepeatRects(const gfx::Rect& aRect, + const gfx::Rect& aTexCoordRect, + decomposedRectArrayT* aLayerRects, + decomposedRectArrayT* aTextureRects) { + gfx::Rect texCoordRect = aTexCoordRect; + + // If the texture should be flipped, it will have negative height. Detect that + // here and compensate for it. We will flip each rect as we emit it. + bool flipped = false; + if (texCoordRect.Height() < 0) { + flipped = true; + texCoordRect.MoveByY(texCoordRect.Height()); + texCoordRect.SetHeight(-texCoordRect.Height()); + } + + // Wrap the texture coordinates so they are within [0,1] and cap width/height + // at 1. We rely on this below. + texCoordRect = gfx::Rect(gfx::Point(WrapTexCoord(texCoordRect.X()), + WrapTexCoord(texCoordRect.Y())), + gfx::Size(std::min(texCoordRect.Width(), 1.0f), + std::min(texCoordRect.Height(), 1.0f))); + + NS_ASSERTION( + texCoordRect.X() >= 0.0f && texCoordRect.X() <= 1.0f && + texCoordRect.Y() >= 0.0f && texCoordRect.Y() <= 1.0f && + texCoordRect.Width() >= 0.0f && texCoordRect.Width() <= 1.0f && + texCoordRect.Height() >= 0.0f && texCoordRect.Height() <= 1.0f && + texCoordRect.XMost() >= 0.0f && texCoordRect.XMost() <= 2.0f && + texCoordRect.YMost() >= 0.0f && texCoordRect.YMost() <= 2.0f, + "We just wrapped the texture coordinates, didn't we?"); + + // Get the top left and bottom right points of the rectangle. Note that + // tl.x/tl.y are within [0,1] but br.x/br.y are within [0,2]. + gfx::Point tl = texCoordRect.TopLeft(); + gfx::Point br = texCoordRect.BottomRight(); + + NS_ASSERTION(tl.x >= 0.0f && tl.x <= 1.0f && tl.y >= 0.0f && tl.y <= 1.0f && + br.x >= tl.x && br.x <= 2.0f && br.y >= tl.y && + br.y <= 2.0f && FuzzyLTE(br.x - tl.x, 1.0f) && + FuzzyLTE(br.y - tl.y, 1.0f), + "Somehow generated invalid texture coordinates"); + + // Then check if we wrap in either the x or y axis. + bool xwrap = br.x > 1.0f; + bool ywrap = br.y > 1.0f; + + // If xwrap is false, the texture will be sampled from tl.x .. br.x. + // If xwrap is true, then it will be split into tl.x .. 1.0, and + // 0.0 .. WrapTexCoord(br.x). Same for the Y axis. The destination + // rectangle is also split appropriately, according to the calculated + // xmid/ymid values. + if (!xwrap && !ywrap) { + SetRects(0, aLayerRects, aTextureRects, aRect.X(), aRect.Y(), aRect.XMost(), + aRect.YMost(), tl.x, tl.y, br.x, br.y, flipped); + return 1; + } + + // If we are dealing with wrapping br.x and br.y are greater than 1.0 so + // wrap them here as well. + br = gfx::Point(xwrap ? WrapTexCoord(br.x) : br.x, + ywrap ? WrapTexCoord(br.y) : br.y); + + // If we wrap around along the x axis, we will draw first from + // tl.x .. 1.0 and then from 0.0 .. br.x (which we just wrapped above). + // The same applies for the Y axis. The midpoints we calculate here are + // only valid if we actually wrap around. + GLfloat xmid = + aRect.X() + (1.0f - tl.x) / texCoordRect.Width() * aRect.Width(); + GLfloat ymid = + aRect.Y() + (1.0f - tl.y) / texCoordRect.Height() * aRect.Height(); + + // Due to floating-point inaccuracy, we have to use XMost()-x and YMost()-y + // to calculate width and height, respectively, to ensure that size will + // remain consistent going from absolute to relative and back again. + NS_ASSERTION( + !xwrap || (xmid >= aRect.X() && xmid <= aRect.XMost() && + FuzzyEqual((xmid - aRect.X()) + (aRect.XMost() - xmid), + aRect.XMost() - aRect.X())), + "xmid should be within [x,XMost()] and the wrapped rect should have the " + "same width"); + NS_ASSERTION( + !ywrap || (ymid >= aRect.Y() && ymid <= aRect.YMost() && + FuzzyEqual((ymid - aRect.Y()) + (aRect.YMost() - ymid), + aRect.YMost() - aRect.Y())), + "ymid should be within [y,YMost()] and the wrapped rect should have the " + "same height"); + + if (!xwrap && ywrap) { + SetRects(0, aLayerRects, aTextureRects, aRect.X(), aRect.Y(), aRect.XMost(), + ymid, tl.x, tl.y, br.x, 1.0f, flipped); + SetRects(1, aLayerRects, aTextureRects, aRect.X(), ymid, aRect.XMost(), + aRect.YMost(), tl.x, 0.0f, br.x, br.y, flipped); + return 2; + } + + if (xwrap && !ywrap) { + SetRects(0, aLayerRects, aTextureRects, aRect.X(), aRect.Y(), xmid, + aRect.YMost(), tl.x, tl.y, 1.0f, br.y, flipped); + SetRects(1, aLayerRects, aTextureRects, xmid, aRect.Y(), aRect.XMost(), + aRect.YMost(), 0.0f, tl.y, br.x, br.y, flipped); + return 2; + } + + SetRects(0, aLayerRects, aTextureRects, aRect.X(), aRect.Y(), xmid, ymid, + tl.x, tl.y, 1.0f, 1.0f, flipped); + SetRects(1, aLayerRects, aTextureRects, xmid, aRect.Y(), aRect.XMost(), ymid, + 0.0f, tl.y, br.x, 1.0f, flipped); + SetRects(2, aLayerRects, aTextureRects, aRect.X(), ymid, xmid, aRect.YMost(), + tl.x, 0.0f, 1.0f, br.y, flipped); + SetRects(3, aLayerRects, aTextureRects, xmid, ymid, aRect.XMost(), + aRect.YMost(), 0.0f, 0.0f, br.x, br.y, flipped); + return 4; +} + +gfx::IntRect Compositor::ComputeBackdropCopyRect( + const gfx::Rect& aRect, const gfx::IntRect& aClipRect, + const gfx::Matrix4x4& aTransform, gfx::Matrix4x4* aOutTransform, + gfx::Rect* aOutLayerQuad) { + // Compute the clip. + RefPtr currentRenderTarget = + GetCurrentRenderTarget(); + gfx::IntPoint rtOffset = currentRenderTarget->GetOrigin(); + gfx::IntSize rtSize = currentRenderTarget->GetSize(); + + return layers::ComputeBackdropCopyRect(aRect, aClipRect, aTransform, + gfx::IntRect(rtOffset, rtSize), + aOutTransform, aOutLayerQuad); +} + +gfx::IntRect Compositor::ComputeBackdropCopyRect( + const gfx::Triangle& aTriangle, const gfx::IntRect& aClipRect, + const gfx::Matrix4x4& aTransform, gfx::Matrix4x4* aOutTransform, + gfx::Rect* aOutLayerQuad) { + gfx::Rect boundingBox = aTriangle.BoundingBox(); + return ComputeBackdropCopyRect(boundingBox, aClipRect, aTransform, + aOutTransform, aOutLayerQuad); +} + +void Compositor::SetInvalid() { mParent = nullptr; } + +bool Compositor::IsValid() const { return !!mParent; } + +void Compositor::UnlockAfterComposition(TextureHost* aTexture) { + TextureSourceProvider::UnlockAfterComposition(aTexture); + + // If this is being called after we shutdown the compositor, we must finish + // read unlocking now to prevent a cycle. + if (IsDestroyed()) { + ReadUnlockTextures(); + } +} + +bool Compositor::NotifyNotUsedAfterComposition(TextureHost* aTextureHost) { + if (IsDestroyed() || AsBasicCompositor()) { + return false; + } + return TextureSourceProvider::NotifyNotUsedAfterComposition(aTextureHost); +} + +void Compositor::GetFrameStats(GPUStats* aStats) { + aStats->mInvalidPixels = mPixelsPerFrame; + aStats->mPixelsFilled = mPixelsFilled; +} + +already_AddRefed Compositor::RecordFrame( + const TimeStamp& aTimeStamp) { + RefPtr renderTarget = GetWindowRenderTarget(); + if (!renderTarget) { + return nullptr; + } + + RefPtr buffer = + CreateAsyncReadbackBuffer(renderTarget->GetSize()); + + if (!ReadbackRenderTarget(renderTarget, buffer)) { + return nullptr; + } + + return MakeAndAddRef(aTimeStamp, std::move(buffer)); +} + +bool Compositor::ShouldRecordFrames() const { +#ifdef MOZ_GECKO_PROFILER + if (profiler_feature_active(ProfilerFeature::Screenshots)) { + return true; + } +#endif + return mRecordFrames; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/Compositor.h b/gfx/layers/Compositor.h new file mode 100644 index 0000000000..5e7a8372a6 --- /dev/null +++ b/gfx/layers/Compositor.h @@ -0,0 +1,750 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_COMPOSITOR_H +#define MOZILLA_GFX_COMPOSITOR_H + +#include "Units.h" // for ScreenPoint +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/RefPtr.h" // for already_AddRefed, RefCounted +#include "mozilla/gfx/2D.h" // for DrawTarget +#include "mozilla/gfx/MatrixFwd.h" // for Matrix, Matrix4x4 +#include "mozilla/gfx/Point.h" // for IntSize, Point +#include "mozilla/gfx/Polygon.h" // for Polygon +#include "mozilla/gfx/Rect.h" // for Rect, IntRect +#include "mozilla/gfx/Types.h" // for Float +#include "mozilla/gfx/Triangle.h" // for Triangle, TexturedTriangle +#include "mozilla/layers/CompositorTypes.h" // for DiagnosticTypes, etc +#include "mozilla/layers/LayersTypes.h" // for LayersBackend +#include "mozilla/layers/SurfacePool.h" // for SurfacePoolHandle +#include "mozilla/layers/TextureSourceProvider.h" +#include "mozilla/widget/CompositorWidget.h" +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "nsRegion.h" +#include +#include "mozilla/WidgetUtils.h" + +/** + * Different elements of a web pages are rendered into separate "layers" before + * they are flattened into the final image that is brought to the screen. + * See Layers.h for more informations about layers and why we use retained + * structures. + * Most of the documentation for layers is directly in the source code in the + * form of doc comments. An overview can also be found in the the wiki: + * https://wiki.mozilla.org/Gecko:Overview#Graphics + * + * + * # Main interfaces and abstractions + * + * - Layer, ShadowableLayer and LayerComposite + * (see Layers.h and ipc/ShadowLayers.h) + * - CompositableClient and CompositableHost + * (client/CompositableClient.h composite/CompositableHost.h) + * - TextureClient and TextureHost + * (client/TextureClient.h composite/TextureHost.h) + * - TextureSource + * (composite/TextureHost.h) + * - Forwarders + * (ipc/CompositableForwarder.h ipc/ShadowLayers.h) + * - Compositor + * (this file) + * - IPDL protocols + * (.ipdl files under the gfx/layers/ipc directory) + * + * The *Client and Shadowable* classes are always used on the content thread. + * Forwarders are always used on the content thread. + * The *Host and Shadow* classes are always used on the compositor thread. + * Compositors, TextureSource, and Effects are always used on the compositor + * thread. + * Most enums and constants are declared in LayersTypes.h and CompositorTypes.h. + * + * + * # Texture transfer + * + * Most layer classes own a Compositable plus some extra information like + * transforms and clip rects. They are platform independent. + * Compositable classes manipulate Texture objects and are reponsible for + * things like tiling, buffer rotation or double buffering. Compositables + * are also platform-independent. Examples of compositable classes are: + * - ImageClient + * - CanvasClient + * - ContentHost + * - etc. + * Texture classes (TextureClient and TextureHost) are thin abstractions over + * platform-dependent texture memory. They are maniplulated by compositables + * and don't know about buffer rotations and such. The purposes of TextureClient + * and TextureHost are to synchronize, serialize and deserialize texture data. + * TextureHosts provide access to TextureSources that are views on the + * Texture data providing the necessary api for Compositor backend to composite + * them. + * + * Compositable and Texture clients and hosts are created using factory methods. + * They should only be created by using their constructor in exceptional + * circumstances. The factory methods are located: + * TextureClient - CompositableClient::CreateTextureClient + * TextureHost - TextureHost::CreateTextureHost, which calls a + * platform-specific function, e.g., + * CreateTextureHostOGL CompositableClient - in the appropriate subclass, e.g., + * CanvasClient::CreateCanvasClient + * CompositableHost - CompositableHost::Create + * + * + * # IPDL + * + * If off-main-thread compositing (OMTC) is enabled, compositing is performed + * in a dedicated thread. In some setups compositing happens in a dedicated + * process. Documentation may refer to either the compositor thread or the + * compositor process. + * See explanations in ShadowLayers.h. + * + * + * # Backend implementations + * + * Compositor backends like OpenGL or flavours of D3D live in their own + * directory under gfx/layers/. To add a new backend, implement at least the + * following interfaces: + * - Compositor (ex. CompositorOGL) + * - TextureHost (ex. SurfaceTextureHost) + * Depending on the type of data that needs to be serialized, you may need to + * add specific TextureClient implementations. + */ + +class nsIWidget; + +namespace mozilla { +namespace gfx { +class DrawTarget; +class DataSourceSurface; +} // namespace gfx + +namespace layers { + +struct Effect; +struct EffectChain; +class Image; +class Layer; +class TextureSource; +class DataTextureSource; +class CompositingRenderTarget; +class CompositorBridgeParent; +class LayerManagerComposite; +class NativeLayer; +class CompositorOGL; +class CompositorD3D11; +class BasicCompositor; +class TextureReadLock; +struct GPUStats; +class AsyncReadbackBuffer; +class RecordedFrame; + +enum SurfaceInitMode { INIT_MODE_NONE, INIT_MODE_CLEAR }; + +/** + * Common interface for compositor backends. + * + * Compositor provides a cross-platform interface to a set of operations for + * compositing quads. Compositor knows nothing about the layer tree. It must be + * told everything about each composited quad - contents, location, transform, + * opacity, etc. + * + * In theory it should be possible for different widgets to use the same + * compositor. In practice, we use one compositor per window. + * + * # Usage + * + * For an example of a user of Compositor, see LayerManagerComposite. + * + * Initialization: create a Compositor object, call Initialize(). + * + * Destruction: destroy any resources associated with the compositor, call + * Destroy(), delete the Compositor object. + * + * Composition: + * call BeginFrame, + * for each quad to be composited: + * call MakeCurrent if necessary (not necessary if no other context has been + * made current), + * take care of any texture upload required to composite the quad, this step + * is backend-dependent, + * construct an EffectChain for the quad, + * call DrawQuad, + * call EndFrame. + * + * By default, the compositor will render to the screen if BeginFrameForWindow + * is called. To render to a target, call BeginFrameForTarget or + * or SetRenderTarget, the latter with a target created + * by CreateRenderTarget or CreateRenderTargetFromSource. + * + * The target and viewport methods can be called before any DrawQuad call and + * affect any subsequent DrawQuad calls. + */ +class Compositor : public TextureSourceProvider { + protected: + virtual ~Compositor(); + + public: + explicit Compositor(widget::CompositorWidget* aWidget, + CompositorBridgeParent* aParent = nullptr); + + virtual bool Initialize(nsCString* const out_failureReason) = 0; + void Destroy() override; + bool IsDestroyed() const { return mIsDestroyed; } + + /** + * Request a texture host identifier that may be used for creating textures + * across process or thread boundaries that are compatible with this + * compositor. + */ + virtual TextureFactoryIdentifier GetTextureFactoryIdentifier() = 0; + + /** + * Properties of the compositor. + */ + virtual bool CanUseCanvasLayerForSize(const gfx::IntSize& aSize) = 0; + + typedef uint32_t MakeCurrentFlags; + static const MakeCurrentFlags ForceMakeCurrent = 0x1; + /** + * Make this compositor's rendering context the current context for the + * underlying graphics API. This may be a global operation, depending on the + * API. Our context will remain the current one until someone else changes it. + * + * Clients of the compositor should call this at the start of the compositing + * process, it might be required by texture uploads etc. + * + * If aFlags == ForceMakeCurrent then we will (re-)set our context on the + * underlying API even if it is already the current context. + */ + virtual void MakeCurrent(MakeCurrentFlags aFlags = 0) = 0; + + /** + * Creates a Surface that can be used as a rendering target by this + * compositor. + */ + virtual already_AddRefed CreateRenderTarget( + const gfx::IntRect& aRect, SurfaceInitMode aInit) = 0; + + /** + * Creates a Surface that can be used as a rendering target by this + * compositor, and initializes the surface by copying from aSource. + * If aSource is null, then the current screen buffer is used as source. + * + * aSourcePoint specifies the point in aSource to copy data from. + */ + virtual already_AddRefed + CreateRenderTargetFromSource(const gfx::IntRect& aRect, + const CompositingRenderTarget* aSource, + const gfx::IntPoint& aSourcePoint) = 0; + + /** + * Grab a snapshot of aSource and store it in aDest, so that the pixels can + * be read on the CPU by mapping aDest at some point in the future. + * aSource and aDest must have the same size. + * If this is a GPU compositor, this call must not block on the GPU. + * Returns whether the operation was successful. + */ + virtual bool ReadbackRenderTarget(CompositingRenderTarget* aSource, + AsyncReadbackBuffer* aDest) { + return false; + } + + /** + * Create an AsyncReadbackBuffer of the specified size. Can return null. + */ + virtual already_AddRefed CreateAsyncReadbackBuffer( + const gfx::IntSize& aSize) { + return nullptr; + } + + /** + * Draw a part of aSource into the current render target. + * Scaling is done with linear filtering. + * Returns whether the operation was successful. + */ + virtual bool BlitRenderTarget(CompositingRenderTarget* aSource, + const gfx::IntSize& aSourceSize, + const gfx::IntSize& aDestSize) { + return false; + } + + /** + * Sets the given surface as the target for subsequent calls to DrawQuad. + * Passing null as aSurface sets the screen as the target. + */ + virtual void SetRenderTarget(CompositingRenderTarget* aSurface) = 0; + + /** + * Returns the current target for rendering. Will return null if we are + * rendering to the screen. + */ + virtual already_AddRefed GetCurrentRenderTarget() + const = 0; + + /** + * Returns a render target which contains the entire window's drawing. + * On platforms where no such render target is used during compositing (e.g. + * with buffered BasicCompositor, where only the invalid area is drawn to a + * render target), this will return null. + */ + virtual already_AddRefed GetWindowRenderTarget() + const { + return nullptr; + } + + /** + * Mostly the compositor will pull the size from a widget and this method will + * be ignored, but compositor implementations are free to use it if they like. + */ + virtual void SetDestinationSurfaceSize(const gfx::IntSize& aSize) = 0; + + void DrawGeometry(const gfx::Rect& aRect, const gfx::IntRect& aClipRect, + const EffectChain& aEffectChain, gfx::Float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::Rect& aVisibleRect, + const Maybe& aGeometry); + + void DrawGeometry(const gfx::Rect& aRect, const gfx::IntRect& aClipRect, + const EffectChain& aEffectChain, gfx::Float aOpacity, + const gfx::Matrix4x4& aTransform, + const Maybe& aGeometry) { + DrawGeometry(aRect, aClipRect, aEffectChain, aOpacity, aTransform, aRect, + aGeometry); + } + + /** + * Tell the compositor to draw a quad. What to do draw and how it is + * drawn is specified by aEffectChain. aRect is the quad to draw, in user + * space. aTransform transforms from user space to screen space. If texture + * coords are required, these will be in the primary effect in the effect + * chain. aVisibleRect is used to determine which edges should be antialiased, + * without applying the effect to the inner edges of a tiled layer. + */ + virtual void DrawQuad(const gfx::Rect& aRect, const gfx::IntRect& aClipRect, + const EffectChain& aEffectChain, gfx::Float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::Rect& aVisibleRect) = 0; + + /** + * Overload of DrawQuad, with aVisibleRect defaulted to the value of aRect. + * Use this when you are drawing a single quad that is not part of a tiled + * layer. + */ + void DrawQuad(const gfx::Rect& aRect, const gfx::IntRect& aClipRect, + const EffectChain& aEffectChain, gfx::Float aOpacity, + const gfx::Matrix4x4& aTransform) { + DrawQuad(aRect, aClipRect, aEffectChain, aOpacity, aTransform, aRect); + } + + virtual void DrawTriangle(const gfx::TexturedTriangle& aTriangle, + const gfx::IntRect& aClipRect, + const EffectChain& aEffectChain, + gfx::Float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::Rect& aVisibleRect) { + MOZ_CRASH( + "Compositor::DrawTriangle is not implemented for the current " + "platform!"); + } + + virtual bool SupportsLayerGeometry() const { return false; } + + /** + * Draw an unfilled solid color rect. Typically used for debugging overlays. + */ + void SlowDrawRect(const gfx::Rect& aRect, const gfx::DeviceColor& color, + const gfx::IntRect& aClipRect = gfx::IntRect(), + const gfx::Matrix4x4& aTransform = gfx::Matrix4x4(), + int aStrokeWidth = 1); + + /** + * Draw a solid color filled rect. This is a simple DrawQuad helper. + */ + void FillRect(const gfx::Rect& aRect, const gfx::DeviceColor& color, + const gfx::IntRect& aClipRect = gfx::IntRect(), + const gfx::Matrix4x4& aTransform = gfx::Matrix4x4()); + + void SetClearColor(const gfx::DeviceColor& aColor) { mClearColor = aColor; } + + void SetDefaultClearColor(const gfx::DeviceColor& aColor) { + mDefaultClearColor = aColor; + } + + void SetClearColorToDefault() { mClearColor = mDefaultClearColor; } + + /* + * Clear aRect on current render target. + */ + virtual void ClearRect(const gfx::Rect& aRect) = 0; + + /** + * Start a new frame for rendering to the window. + * Needs to be paired with a call to EndFrame() if the return value is not + * Nothing(). + * + * aInvalidRegion is the invalid region of the window. + * aClipRect is the clip rect for all drawing (optional). + * aRenderBounds is the bounding rect for rendering. + * aOpaqueRegion is the area that contains opaque content. + * All coordinates are in window space. + * + * Returns the non-empty render bounds actually used by the compositor in + * window space, or Nothing() if composition should be aborted. + */ + virtual Maybe BeginFrameForWindow( + const nsIntRegion& aInvalidRegion, const Maybe& aClipRect, + const gfx::IntRect& aRenderBounds, const nsIntRegion& aOpaqueRegion) = 0; + + /** + * Start a new frame for rendering to a DrawTarget. Rendering can happen + * directly into the DrawTarget, or it can happen in an offscreen GPU buffer + * and read back into the DrawTarget in EndFrame, or it can happen inside the + * window and read back into the DrawTarget in EndFrame. + * Needs to be paired with a call to EndFrame() if the return value is not + * Nothing(). + * + * aInvalidRegion is the invalid region in the target. + * aClipRect is the clip rect for all drawing (optional). + * aRenderBounds is the bounding rect for rendering. + * aOpaqueRegion is the area that contains opaque content. + * aTarget is the DrawTarget which should contain the rendering after + * EndFrame() has been called. + * aTargetBounds are the DrawTarget's bounds. + * All coordinates are in window space. + * + * Returns the non-empty render bounds actually used by the compositor in + * window space, or Nothing() if composition should be aborted. + * + * If BeginFrame succeeds, the compositor keeps a reference to aTarget until + * EndFrame is called. + */ + virtual Maybe BeginFrameForTarget( + const nsIntRegion& aInvalidRegion, const Maybe& aClipRect, + const gfx::IntRect& aRenderBounds, const nsIntRegion& aOpaqueRegion, + gfx::DrawTarget* aTarget, const gfx::IntRect& aTargetBounds) = 0; + + /** + * Start a new frame for rendering to one or more native layers. Needs to be + * paired with a call to EndFrame(). + * + * This puts the compositor in a state where offscreen rendering is allowed. + * Rendering an actual native layer is only possible via a call to + * BeginRenderingToNativeLayer(), after BeginFrameForNativeLayers() has run. + * + * The following is true for the entire time between + * BeginFrameForNativeLayers() and EndFrame(), even outside pairs of calls to + * Begin/EndRenderingToNativeLayer(): + * - GetCurrentRenderTarget() will return something non-null. + * - CreateRenderTarget() and SetRenderTarget() can be called, in order to + * facilitate offscreen rendering. + * The render target that this method sets as the current render target is not + * useful. Do not render to it. It exists so that calls of the form + * SetRenderTarget(previousTarget) do not crash. + * + * Do not call on platforms that do not support native layers. + */ + virtual void BeginFrameForNativeLayers() = 0; + + /** + * Start rendering into aNativeLayer. + * Needs to be paired with a call to EndRenderingToNativeLayer() if the return + * value is not Nothing(). + * + * Must be called between BeginFrameForNativeLayers() and EndFrame(). + * + * aInvalidRegion is the invalid region in the native layer. + * aClipRect is the clip rect for all drawing (optional). + * aOpaqueRegion is the area that contains opaque content. + * aNativeLayer is the native layer. + * All coordinates, including aNativeLayer->GetRect(), are in window space. + * + * Returns the non-empty layer rect, or Nothing() if rendering to this layer + * should be skipped. + * + * If BeginRenderingToNativeLayer succeeds, the compositor keeps a reference + * to aNativeLayer until EndRenderingToNativeLayer is called. + * + * Do not call on platforms that do not support native layers. + */ + virtual Maybe BeginRenderingToNativeLayer( + const nsIntRegion& aInvalidRegion, const Maybe& aClipRect, + const nsIntRegion& aOpaqueRegion, NativeLayer* aNativeLayer) = 0; + + /** + * Stop rendering to the native layer and submit the rendering as the layer's + * new content. + * + * Do not call on platforms that do not support native layers. + */ + virtual void EndRenderingToNativeLayer() = 0; + + /** + * Notification that we've finished issuing draw commands for normal + * layers (as opposed to the diagnostic overlay which comes after). + * This is called between BeginFrame* and EndFrame, and it's called before + * GetWindowRenderTarget() is called for the purposes of screenshot capturing. + * That next call to GetWindowRenderTarget() expects up-to-date contents for + * the current frame. + * When rendering to native layers, this should be called for every layer, + * between BeginRenderingToNativeLayer and EndRenderingToNativeLayer, at a + * time at which the current render target is the one that + * BeginRenderingToNativeLayer has put in place. + * When not rendering to native layers, this should be called at a time when + * the current render target is the one that BeginFrameForWindow put in place. + */ + virtual void NormalDrawingDone() {} + + /** + * Flush the current frame to the screen and tidy up. + * + * Derived class overriding this should call Compositor::EndFrame. + */ + virtual void EndFrame(); + + virtual void CancelFrame(bool aNeedFlush = true) { ReadUnlockTextures(); } + + virtual void WaitForGPU() {} + + virtual RefPtr GetSurfacePoolHandle() { return nullptr; } + + /** + * Whether textures created by this compositor can receive partial updates. + */ + virtual bool SupportsPartialTextureUpdate() = 0; + + void SetDiagnosticTypes(DiagnosticTypes aDiagnostics) { + mDiagnosticTypes = aDiagnostics; + } + + DiagnosticTypes GetDiagnosticTypes() const { return mDiagnosticTypes; } + + void DrawDiagnostics(DiagnosticFlags aFlags, const gfx::Rect& visibleRect, + const gfx::IntRect& aClipRect, + const gfx::Matrix4x4& transform, + uint32_t aFlashCounter = DIAGNOSTIC_FLASH_COUNTER_MAX); + + void DrawDiagnostics(DiagnosticFlags aFlags, const nsIntRegion& visibleRegion, + const gfx::IntRect& aClipRect, + const gfx::Matrix4x4& transform, + uint32_t aFlashCounter = DIAGNOSTIC_FLASH_COUNTER_MAX); + +#ifdef MOZ_DUMP_PAINTING + virtual const char* Name() const = 0; +#endif // MOZ_DUMP_PAINTING + + virtual LayersBackend GetBackendType() const = 0; + + virtual CompositorD3D11* AsCompositorD3D11() { return nullptr; } + + Compositor* AsCompositor() override { return this; } + + TimeStamp GetLastCompositionEndTime() const override { + return mLastCompositionEndTime; + } + + void UnlockAfterComposition(TextureHost* aTexture) override; + bool NotifyNotUsedAfterComposition(TextureHost* aTextureHost) override; + + /** + * Notify the compositor that composition is being paused. This allows the + * compositor to temporarily release any resources. + * Between calling Pause and Resume, compositing may fail. + */ + virtual void Pause() {} + /** + * Notify the compositor that composition is being resumed. The compositor + * regain any resources it requires for compositing. + * Returns true if succeeded. + */ + virtual bool Resume() { return true; } + + /** + * Call before rendering begins to ensure the compositor is ready to + * composite. Returns false if rendering should be aborted. + */ + virtual bool Ready() { return true; } + + virtual void ForcePresent() {} + + virtual bool IsPendingComposite() { return false; } + + virtual void FinishPendingComposite() {} + + widget::CompositorWidget* GetWidget() const { return mWidget; } + + // Return statistics for the most recent frame we computed statistics for. + virtual void GetFrameStats(GPUStats* aStats); + + ScreenRotation GetScreenRotation() const { return mScreenRotation; } + void SetScreenRotation(ScreenRotation aRotation) { + mScreenRotation = aRotation; + } + + // A stale Compositor has no CompositorBridgeParent; it will not process + // frames and should not be used. + void SetInvalid(); + bool IsValid() const override; + CompositorBridgeParent* GetCompositorBridgeParent() const { return mParent; } + + /** + * Request the compositor to allow recording its frames. + * + * This is a noop on |CompositorOGL|. + */ + virtual void RequestAllowFrameRecording(bool aWillRecord) { + mRecordFrames = aWillRecord; + } + + /** + * Record the current frame for readback by the |CompositionRecorder|. + * + * If this compositor does not support this feature, a null pointer is + * returned instead. + */ + already_AddRefed RecordFrame(const TimeStamp& aTimeStamp); + + protected: + void DrawDiagnosticsInternal(DiagnosticFlags aFlags, + const gfx::Rect& aVisibleRect, + const gfx::IntRect& aClipRect, + const gfx::Matrix4x4& transform, + uint32_t aFlashCounter); + + bool ShouldDrawDiagnostics(DiagnosticFlags); + + /** + * Given a layer rect, clip, and transform, compute the area of the backdrop + * that needs to be copied for mix-blending. The output transform translates + * from 0..1 space into the backdrop rect space. + * + * The transformed layer quad is also optionally returned - this is the same + * as the result rect, before rounding. + */ + gfx::IntRect ComputeBackdropCopyRect(const gfx::Rect& aRect, + const gfx::IntRect& aClipRect, + const gfx::Matrix4x4& aTransform, + gfx::Matrix4x4* aOutTransform, + gfx::Rect* aOutLayerQuad = nullptr); + + gfx::IntRect ComputeBackdropCopyRect(const gfx::Triangle& aTriangle, + const gfx::IntRect& aClipRect, + const gfx::Matrix4x4& aTransform, + gfx::Matrix4x4* aOutTransform, + gfx::Rect* aOutLayerQuad = nullptr); + + virtual void DrawTriangles(const nsTArray& aTriangles, + const gfx::Rect& aRect, + const gfx::IntRect& aClipRect, + const EffectChain& aEffectChain, + gfx::Float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::Rect& aVisibleRect); + + virtual void DrawPolygon(const gfx::Polygon& aPolygon, const gfx::Rect& aRect, + const gfx::IntRect& aClipRect, + const EffectChain& aEffectChain, gfx::Float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::Rect& aVisibleRect); + + /** + * Whether or not the compositor should be prepared to record frames. While + * this returns true, compositors are expected to maintain a full window + * render target that they return from GetWindowRenderTarget() between + * NormalDrawingDone() and EndFrame(). + * + * This will be true when either we are recording a profile with screenshots + * enabled or the |LayerManagerComposite| has requested us to record frames + * for the |CompositionRecorder|. + */ + bool ShouldRecordFrames() const; + + /** + * Last Composition end time. + */ + TimeStamp mLastCompositionEndTime; + + DiagnosticTypes mDiagnosticTypes; + CompositorBridgeParent* mParent; + + /** + * We keep track of the total number of pixels filled as we composite the + * current frame. This value is an approximation and is not accurate, + * especially in the presence of transforms. + */ + size_t mPixelsPerFrame; + size_t mPixelsFilled; + + ScreenRotation mScreenRotation; + + widget::CompositorWidget* mWidget; + + bool mIsDestroyed; + + gfx::DeviceColor mClearColor; + gfx::DeviceColor mDefaultClearColor; + + bool mRecordFrames = false; + + private: + static LayersBackend sBackend; +}; + +// Returns the number of rects. (Up to 4) +typedef gfx::Rect decomposedRectArrayT[4]; +size_t DecomposeIntoNoRepeatRects(const gfx::Rect& aRect, + const gfx::Rect& aTexCoordRect, + decomposedRectArrayT* aLayerRects, + decomposedRectArrayT* aTextureRects); + +static inline bool BlendOpIsMixBlendMode(gfx::CompositionOp aOp) { + switch (aOp) { + case gfx::CompositionOp::OP_MULTIPLY: + case gfx::CompositionOp::OP_SCREEN: + case gfx::CompositionOp::OP_OVERLAY: + case gfx::CompositionOp::OP_DARKEN: + case gfx::CompositionOp::OP_LIGHTEN: + case gfx::CompositionOp::OP_COLOR_DODGE: + case gfx::CompositionOp::OP_COLOR_BURN: + case gfx::CompositionOp::OP_HARD_LIGHT: + case gfx::CompositionOp::OP_SOFT_LIGHT: + case gfx::CompositionOp::OP_DIFFERENCE: + case gfx::CompositionOp::OP_EXCLUSION: + case gfx::CompositionOp::OP_HUE: + case gfx::CompositionOp::OP_SATURATION: + case gfx::CompositionOp::OP_COLOR: + case gfx::CompositionOp::OP_LUMINOSITY: + return true; + default: + return false; + } +} + +class AsyncReadbackBuffer { + public: + NS_INLINE_DECL_REFCOUNTING(AsyncReadbackBuffer) + + gfx::IntSize GetSize() const { return mSize; } + virtual bool MapAndCopyInto(gfx::DataSourceSurface* aSurface, + const gfx::IntSize& aReadSize) const = 0; + + protected: + explicit AsyncReadbackBuffer(const gfx::IntSize& aSize) : mSize(aSize) {} + virtual ~AsyncReadbackBuffer() = default; + + gfx::IntSize mSize; +}; + +struct TexturedVertex { + float position[2]; + float texCoords[2]; +}; + +nsTArray TexturedTrianglesToVertexArray( + const nsTArray& aTriangles); + +} // namespace layers +} // namespace mozilla + +#endif /* MOZILLA_GFX_COMPOSITOR_H */ diff --git a/gfx/layers/CompositorAnimationStorage.cpp b/gfx/layers/CompositorAnimationStorage.cpp new file mode 100644 index 0000000000..0a004cfa9e --- /dev/null +++ b/gfx/layers/CompositorAnimationStorage.cpp @@ -0,0 +1,674 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "CompositorAnimationStorage.h" + +#include "AnimationHelper.h" +#include "mozilla/gfx/MatrixFwd.h" +#include "mozilla/layers/APZSampler.h" // for APZSampler +#include "mozilla/layers/CompositorBridgeParent.h" // for CompositorBridgeParent +#include "mozilla/layers/CompositorThread.h" // for CompositorThreadHolder +#include "mozilla/layers/LayerManagerComposite.h" // for LayerComposite, etc +#include "mozilla/layers/LayerMetricsWrapper.h" // for LayerMetricsWrapper +#include "mozilla/layers/OMTAController.h" // for OMTAController +#include "mozilla/ScopeExit.h" +#include "mozilla/ServoStyleConsts.h" +#include "mozilla/webrender/WebRenderTypes.h" // for ToWrTransformProperty, etc +#include "nsDeviceContext.h" // for AppUnitsPerCSSPixel +#include "nsDisplayList.h" // for nsDisplayTransform, etc +#include "nsLayoutUtils.h" +#include "TreeTraversal.h" // for ForEachNode, BreadthFirstSearch + +namespace mozilla { +namespace layers { + +using gfx::Matrix4x4; + +void CompositorAnimationStorage::Clear() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + // This function should only be called via the non Webrender version of + // SampleAnimations. + mLock.AssertCurrentThreadOwns(); + + mAnimatedValues.Clear(); + mAnimations.clear(); +} + +void CompositorAnimationStorage::ClearById(const uint64_t& aId) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + MutexAutoLock lock(mLock); + + mAnimatedValues.Remove(aId); + mAnimations.erase(aId); +} + +bool CompositorAnimationStorage::HasAnimations() const { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + MutexAutoLock lock(mLock); + + return !mAnimations.empty(); +} + +AnimatedValue* CompositorAnimationStorage::GetAnimatedValue( + const uint64_t& aId) const { + mLock.AssertCurrentThreadOwns(); + + return mAnimatedValues.Get(aId); +} + +OMTAValue CompositorAnimationStorage::GetOMTAValue(const uint64_t& aId) const { + MutexAutoLock lock(mLock); + + OMTAValue omtaValue = mozilla::null_t(); + auto animatedValue = GetAnimatedValue(aId); + if (!animatedValue) { + return omtaValue; + } + + animatedValue->Value().match( + [&](const AnimationTransform& aTransform) { + gfx::Matrix4x4 transform = aTransform.mFrameTransform; + const TransformData& data = aTransform.mData; + float scale = data.appUnitsPerDevPixel(); + gfx::Point3D transformOrigin = data.transformOrigin(); + + // Undo the rebasing applied by + // nsDisplayTransform::GetResultingTransformMatrixInternal + transform.ChangeBasis(-transformOrigin); + + // Convert to CSS pixels (this undoes the operations performed by + // nsStyleTransformMatrix::ProcessTranslatePart which is called from + // nsDisplayTransform::GetResultingTransformMatrix) + double devPerCss = double(scale) / double(AppUnitsPerCSSPixel()); + transform._41 *= devPerCss; + transform._42 *= devPerCss; + transform._43 *= devPerCss; + omtaValue = transform; + }, + [&](const float& aOpacity) { omtaValue = aOpacity; }, + [&](const nscolor& aColor) { omtaValue = aColor; }); + return omtaValue; +} + +void CompositorAnimationStorage::SetAnimatedValueForWebRender( + uint64_t aId, AnimatedValue* aPreviousValue, + const gfx::Matrix4x4& aFrameTransform, const TransformData& aData) { + mLock.AssertCurrentThreadOwns(); + + if (!aPreviousValue) { + MOZ_ASSERT(!mAnimatedValues.Contains(aId)); + mAnimatedValues.Put(aId, MakeUnique(gfx::Matrix4x4(), + aFrameTransform, aData)); + return; + } + MOZ_ASSERT(aPreviousValue->Is()); + MOZ_ASSERT(aPreviousValue == GetAnimatedValue(aId)); + + aPreviousValue->SetTransformForWebRender(aFrameTransform, aData); +} + +void CompositorAnimationStorage::SetAnimatedValue( + uint64_t aId, AnimatedValue* aPreviousValue, + const gfx::Matrix4x4& aTransformInDevSpace, + const gfx::Matrix4x4& aFrameTransform, const TransformData& aData) { + mLock.AssertCurrentThreadOwns(); + + if (!aPreviousValue) { + MOZ_ASSERT(!mAnimatedValues.Contains(aId)); + mAnimatedValues.Put(aId, MakeUnique(aTransformInDevSpace, + aFrameTransform, aData)); + return; + } + MOZ_ASSERT(aPreviousValue->Is()); + MOZ_ASSERT(aPreviousValue == GetAnimatedValue(aId)); + + aPreviousValue->SetTransform(aTransformInDevSpace, aFrameTransform, aData); +} + +void CompositorAnimationStorage::SetAnimatedValue(uint64_t aId, + AnimatedValue* aPreviousValue, + nscolor aColor) { + mLock.AssertCurrentThreadOwns(); + + if (!aPreviousValue) { + MOZ_ASSERT(!mAnimatedValues.Contains(aId)); + mAnimatedValues.Put(aId, MakeUnique(aColor)); + return; + } + + MOZ_ASSERT(aPreviousValue->Is()); + MOZ_ASSERT(aPreviousValue == GetAnimatedValue(aId)); + aPreviousValue->SetColor(aColor); +} + +void CompositorAnimationStorage::SetAnimatedValue(uint64_t aId, + AnimatedValue* aPreviousValue, + float aOpacity) { + mLock.AssertCurrentThreadOwns(); + + if (!aPreviousValue) { + MOZ_ASSERT(!mAnimatedValues.Contains(aId)); + mAnimatedValues.Put(aId, MakeUnique(aOpacity)); + return; + } + + MOZ_ASSERT(aPreviousValue->Is()); + MOZ_ASSERT(aPreviousValue == GetAnimatedValue(aId)); + aPreviousValue->SetOpacity(aOpacity); +} + +void CompositorAnimationStorage::SetAnimations(uint64_t aId, + const LayersId& aLayersId, + const AnimationArray& aValue) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + MutexAutoLock lock(mLock); + + mAnimations[aId] = std::make_unique( + AnimationHelper::ExtractAnimations(aLayersId, aValue)); + + // If there is the last animated value, then we need to store the id to remove + // the value if the new animation doesn't produce any animated data (i.e. in + // the delay phase) when we sample this new animation. + if (mAnimatedValues.Contains(aId)) { + mNewAnimations.insert(aId); + } +} + +// Returns clip rect in the scroll frame's coordinate space. +static ParentLayerRect GetClipRectForPartialPrerender( + const LayersId aLayersId, const PartialPrerenderData& aPartialPrerenderData, + const RefPtr& aSampler) { + if (aSampler && + aPartialPrerenderData.scrollId() != ScrollableLayerGuid::NULL_SCROLL_ID) { + return aSampler->GetCompositionBounds(aLayersId, + aPartialPrerenderData.scrollId()); + } + + return aPartialPrerenderData.clipRect(); +} + +bool CompositorAnimationStorage::SampleAnimations( + const OMTAController* aOMTAController, TimeStamp aPreviousFrameTime, + TimeStamp aCurrentFrameTime) { + MutexAutoLock lock(mLock); + + bool isAnimating = false; + auto cleanup = MakeScopeExit([&] { mNewAnimations.clear(); }); + + // Do nothing if there are no compositor animations + if (mAnimations.empty()) { + return isAnimating; + } + + std::unordered_map, LayersId::HashFn> janked; + + RefPtr apzSampler = mCompositorBridge->GetAPZSampler(); + + for (const auto& iter : mAnimations) { + const auto& animationStorageData = iter.second; + if (animationStorageData->mAnimation.IsEmpty()) { + continue; + } + + isAnimating = true; + AutoTArray, 1> animationValues; + AnimatedValue* previousValue = GetAnimatedValue(iter.first); + AnimationHelper::SampleResult sampleResult = + AnimationHelper::SampleAnimationForEachNode( + aPreviousFrameTime, aCurrentFrameTime, previousValue, + animationStorageData->mAnimation, animationValues); + + if (sampleResult != AnimationHelper::SampleResult::Sampled) { + if (mNewAnimations.find(iter.first) != mNewAnimations.end()) { + mAnimatedValues.Remove(iter.first); + } + continue; + } + + const PropertyAnimationGroup& lastPropertyAnimationGroup = + animationStorageData->mAnimation.LastElement(); + + // Store the AnimatedValue + switch (lastPropertyAnimationGroup.mProperty) { + case eCSSProperty_background_color: { + SetAnimatedValue(iter.first, previousValue, + Servo_AnimationValue_GetColor(animationValues[0], + NS_RGBA(0, 0, 0, 0))); + break; + } + case eCSSProperty_opacity: { + MOZ_ASSERT(animationValues.Length() == 1); + SetAnimatedValue(iter.first, previousValue, + Servo_AnimationValue_GetOpacity(animationValues[0])); + break; + } + case eCSSProperty_rotate: + case eCSSProperty_scale: + case eCSSProperty_translate: + case eCSSProperty_transform: + case eCSSProperty_offset_path: + case eCSSProperty_offset_distance: + case eCSSProperty_offset_rotate: + case eCSSProperty_offset_anchor: { + MOZ_ASSERT(animationStorageData->mTransformData); + + const TransformData& transformData = + *animationStorageData->mTransformData; + MOZ_ASSERT(transformData.origin() == nsPoint()); + + gfx::Matrix4x4 frameTransform = + AnimationHelper::ServoAnimationValueToMatrix4x4( + animationValues, transformData, + animationStorageData->mCachedMotionPath); + + if (const Maybe& partialPrerenderData = + transformData.partialPrerenderData()) { + gfx::Matrix4x4 transform = frameTransform; + transform.PostTranslate( + partialPrerenderData->position().ToUnknownPoint()); + + gfx::Matrix4x4 transformInClip = + partialPrerenderData->transformInClip(); + if (apzSampler && partialPrerenderData->scrollId() != + ScrollableLayerGuid::NULL_SCROLL_ID) { + AsyncTransform asyncTransform = + apzSampler->GetCurrentAsyncTransform( + animationStorageData->mLayersId, + partialPrerenderData->scrollId(), LayoutAndVisual); + transformInClip.PostTranslate( + asyncTransform.mTranslation.ToUnknownPoint()); + } + transformInClip = transform * transformInClip; + + ParentLayerRect clipRect = + GetClipRectForPartialPrerender(animationStorageData->mLayersId, + *partialPrerenderData, apzSampler); + if (AnimationHelper::ShouldBeJank( + partialPrerenderData->rect(), + partialPrerenderData->overflowedSides(), transformInClip, + clipRect)) { + if (previousValue) { + frameTransform = previousValue->Transform().mFrameTransform; + } + janked[animationStorageData->mLayersId].AppendElement(iter.first); + } + } + + SetAnimatedValueForWebRender(iter.first, previousValue, frameTransform, + transformData); + break; + } + default: + MOZ_ASSERT_UNREACHABLE("Unhandled animated property"); + } + } + + if (!janked.empty() && aOMTAController) { + aOMTAController->NotifyJankedAnimations(std::move(janked)); + } + + return isAnimating; +} + +WrAnimations CompositorAnimationStorage::CollectWebRenderAnimations() const { + MutexAutoLock lock(mLock); + + WrAnimations animations; + + for (auto iter = mAnimatedValues.ConstIter(); !iter.Done(); iter.Next()) { + AnimatedValue* value = iter.UserData(); + value->Value().match( + [&](const AnimationTransform& aTransform) { + animations.mTransformArrays.AppendElement(wr::ToWrTransformProperty( + iter.Key(), aTransform.mFrameTransform)); + }, + [&](const float& aOpacity) { + animations.mOpacityArrays.AppendElement( + wr::ToWrOpacityProperty(iter.Key(), aOpacity)); + }, + [&](const nscolor& aColor) { + animations.mColorArrays.AppendElement(wr::ToWrColorProperty( + iter.Key(), ToDeviceColor(gfx::sRGBColor::FromABGR(aColor)))); + }); + } + + return animations; +} + +static gfx::Matrix4x4 FrameTransformToTransformInDevice( + const gfx::Matrix4x4& aFrameTransform, Layer* aLayer, + const TransformData& aTransformData) { + gfx::Matrix4x4 transformInDevice = aFrameTransform; + // If our parent layer is a perspective layer, then the offset into reference + // frame coordinates is already on that layer. If not, then we need to ask + // for it to be added here. + if (!aLayer->GetParent() || + !aLayer->GetParent()->GetTransformIsPerspective()) { + nsLayoutUtils::PostTranslate( + transformInDevice, aTransformData.origin(), + aTransformData.appUnitsPerDevPixel(), + aLayer->GetContentFlags() & Layer::CONTENT_SNAP_TO_GRID); + } + + if (ContainerLayer* c = aLayer->AsContainerLayer()) { + transformInDevice.PostScale(c->GetInheritedXScale(), + c->GetInheritedYScale(), 1); + } + + return transformInDevice; +} + +static Matrix4x4 GetTransformForPartialPrerender( + Layer* aLayer, const LayersId aLayersId, + const ScrollableLayerGuid::ViewID& aScrollId, + const RefPtr& aSampler) { + MOZ_ASSERT(aLayer); + + ParentLayerPoint translationByApz; + Matrix4x4 transform; + + for (Layer* layer = aLayer; layer; layer = layer->GetParent()) { + if (layer->AsRefLayer()) { + MOZ_ASSERT(layer->AsRefLayer()->GetReferentId() == aLayersId); + break; + } + + // Accumulate static transforms. + if (layer != aLayer) { + Matrix4x4 baseTransform = layer->GetBaseTransform(); + if (ContainerLayer* container = layer->AsContainerLayer()) { + baseTransform.PostScale(container->GetPreXScale(), + container->GetPreYScale(), 1); + } + transform *= baseTransform; + } + + if (!layer->GetIsStickyPosition() && !layer->GetIsFixedPosition()) { + bool hasSameScrollId = false; + for (uint32_t i = 0; i < layer->GetScrollMetadataCount(); i++) { + // Factor APZ translation if there exists. + if (aSampler) { + LayerMetricsWrapper wrapper = LayerMetricsWrapper(layer, i); + AsyncTransform asyncTransform = + aSampler->GetCurrentAsyncTransform(wrapper, LayoutAndVisual); + translationByApz += asyncTransform.mTranslation; + } + if (layer->GetFrameMetrics(i).GetScrollId() == aScrollId) { + hasSameScrollId = true; + } + } + + if (hasSameScrollId) { + break; + } + } else { + // Bug 1642547: Fix for position:sticky layers. + } + } + + transform.PostTranslate(translationByApz.ToUnknownPoint()); + + return transform; +} + +bool CompositorAnimationStorage::ApplyAnimatedValue( + CompositorBridgeParent* aCompositorBridge, Layer* aLayer, + nsCSSPropertyID aProperty, AnimatedValue* aPreviousValue, + const nsTArray>& aValues) { + mLock.AssertCurrentThreadOwns(); + + MOZ_ASSERT(!aValues.IsEmpty()); + + bool janked = false; + HostLayer* layerCompositor = aLayer->AsHostLayer(); + switch (aProperty) { + case eCSSProperty_background_color: { + MOZ_ASSERT(aValues.Length() == 1); + // We don't support 'color' animations on the compositor yet so we never + // meet currentColor on the compositor. + nscolor color = + Servo_AnimationValue_GetColor(aValues[0], NS_RGBA(0, 0, 0, 0)); + aLayer->AsColorLayer()->SetColor(gfx::ToDeviceColor(color)); + SetAnimatedValue(aLayer->GetCompositorAnimationsId(), aPreviousValue, + color); + + layerCompositor->SetShadowOpacity(aLayer->GetOpacity()); + layerCompositor->SetShadowOpacitySetByAnimation(false); + layerCompositor->SetShadowBaseTransform(aLayer->GetBaseTransform()); + layerCompositor->SetShadowTransformSetByAnimation(false); + break; + } + case eCSSProperty_opacity: { + MOZ_ASSERT(aValues.Length() == 1); + float opacity = Servo_AnimationValue_GetOpacity(aValues[0]); + layerCompositor->SetShadowOpacity(opacity); + layerCompositor->SetShadowOpacitySetByAnimation(true); + SetAnimatedValue(aLayer->GetCompositorAnimationsId(), aPreviousValue, + opacity); + + layerCompositor->SetShadowBaseTransform(aLayer->GetBaseTransform()); + layerCompositor->SetShadowTransformSetByAnimation(false); + break; + } + case eCSSProperty_rotate: + case eCSSProperty_scale: + case eCSSProperty_translate: + case eCSSProperty_transform: + case eCSSProperty_offset_path: + case eCSSProperty_offset_distance: + case eCSSProperty_offset_rotate: + case eCSSProperty_offset_anchor: { + MOZ_ASSERT(aLayer->GetTransformData()); + const TransformData& transformData = *aLayer->GetTransformData(); + gfx::Matrix4x4 frameTransform = + AnimationHelper::ServoAnimationValueToMatrix4x4( + aValues, transformData, aLayer->CachedMotionPath()); + + gfx::Matrix4x4 transform = FrameTransformToTransformInDevice( + frameTransform, aLayer, transformData); + if (const Maybe& partialPrerenderData = + transformData.partialPrerenderData()) { + Matrix4x4 transformInClip = GetTransformForPartialPrerender( + aLayer, aLayer->GetAnimationLayersId(), + partialPrerenderData->scrollId(), + aCompositorBridge->GetAPZSampler()); + transformInClip = transform * transformInClip; + ParentLayerRect clipRect = GetClipRectForPartialPrerender( + aLayer->GetAnimationLayersId(), *partialPrerenderData, + aCompositorBridge->GetAPZSampler()); + if (AnimationHelper::ShouldBeJank( + partialPrerenderData->rect(), + partialPrerenderData->overflowedSides(), transformInClip, + clipRect)) { + // It's possible that we don't have the previous value and we don't + // either have enough area to composite in the first composition, + // e.g. a translate animation with a step timing function. In such + // cases we use the base transform value which was calculated on the + // main-thread as a fallback value. + transform = aPreviousValue + ? aPreviousValue->Transform().mTransformInDevSpace + : aLayer->GetBaseTransform(); + janked = true; + } + } + + layerCompositor->SetShadowBaseTransform(transform); + layerCompositor->SetShadowTransformSetByAnimation(true); + SetAnimatedValue(aLayer->GetCompositorAnimationsId(), aPreviousValue, + transform, frameTransform, transformData); + + layerCompositor->SetShadowOpacity(aLayer->GetOpacity()); + layerCompositor->SetShadowOpacitySetByAnimation(false); + break; + } + default: + MOZ_ASSERT_UNREACHABLE("Unhandled animated property"); + } + return !janked; +} + +bool CompositorAnimationStorage::SampleAnimations( + Layer* aRoot, CompositorBridgeParent* aCompositorBridge, + TimeStamp aPreviousFrameTime, TimeStamp aCurrentFrameTime) { + MutexAutoLock lock(mLock); + + bool isAnimating = false; + + auto autoClearAnimationStorage = MakeScopeExit([&] { + if (!isAnimating) { + // Clean up the CompositorAnimationStorage because + // there are no active animations running + Clear(); + } + }); + + std::unordered_map, LayersId::HashFn> janked; + + ForEachNode(aRoot, [&](Layer* layer) { + auto& propertyAnimationGroups = layer->GetPropertyAnimationGroups(); + if (propertyAnimationGroups.IsEmpty()) { + return; + } + + isAnimating = true; + AnimatedValue* previousValue = + GetAnimatedValue(layer->GetCompositorAnimationsId()); + + AutoTArray, 1> animationValues; + AnimationHelper::SampleResult sampleResult = + AnimationHelper::SampleAnimationForEachNode( + aPreviousFrameTime, aCurrentFrameTime, previousValue, + propertyAnimationGroups, animationValues); + + const PropertyAnimationGroup& lastPropertyAnimationGroup = + propertyAnimationGroups.LastElement(); + + switch (sampleResult) { + case AnimationHelper::SampleResult::Sampled: + // We assume all transform like properties (on the same frame) live in + // a single same layer, so using the transform data of the last element + // should be fine. + if (!ApplyAnimatedValue(aCompositorBridge, layer, + lastPropertyAnimationGroup.mProperty, + previousValue, animationValues)) { + // Reset the last composition values in cases of jank so that we will + // never mis-compare in a sanity check in the case of + // SampleResult::Skipped below in this function. + // + // An example; + // a translateX(0px) -> translateX(100px) animation with step(2,start) + // and if the animation janked at translateX(50px) and in a later + // frame if the calculated transform value is going to be still + // translateX(50px) (i.e. at the same timing portion calculated by the + // step timing function), we skip sampling. That's correct ideally. + // But we have an assertion to do a sanity check for the skip sampling + // case that the check compares the calculated value translateX(50px) + // with the previous composited value. In this case the previous + // composited value is translateX(0px). + // + // NOTE: Ideally we shouldn't update the last composition values when + // we met janks, but it's quite hard to tell whether the jank will + // happen or not when we calculate each transform like properties' + // value (i.e. when we set the last composition value) since janks are + // caused by a result of the combinations of all transform like + // properties (e.g. `transform: translateX(50px)` and + // `translate: -50px` results `translateX(0px)`. + for (PropertyAnimationGroup& group : propertyAnimationGroups) { + group.ResetLastCompositionValues(); + } + janked[layer->GetAnimationLayersId()].AppendElement( + layer->GetCompositorAnimationsId()); + } + break; + case AnimationHelper::SampleResult::Skipped: + switch (lastPropertyAnimationGroup.mProperty) { + case eCSSProperty_background_color: + case eCSSProperty_opacity: { + if (lastPropertyAnimationGroup.mProperty == eCSSProperty_opacity) { + MOZ_ASSERT( + layer->AsHostLayer()->GetShadowOpacitySetByAnimation()); +#ifdef DEBUG + // Disable this assertion until the root cause is fixed in bug + // 1459775. + // MOZ_ASSERT(FuzzyEqualsMultiplicative( + // Servo_AnimationValue_GetOpacity(animationValue), + // *(GetAnimationOpacity(layer->GetCompositorAnimationsId())))); +#endif + } + // Even if opacity or background-color animation value has + // unchanged, we have to set the shadow base transform value + // here since the value might have been changed by APZC. + HostLayer* layerCompositor = layer->AsHostLayer(); + layerCompositor->SetShadowBaseTransform(layer->GetBaseTransform()); + layerCompositor->SetShadowTransformSetByAnimation(false); + break; + } + case eCSSProperty_rotate: + case eCSSProperty_scale: + case eCSSProperty_translate: + case eCSSProperty_transform: + case eCSSProperty_offset_path: + case eCSSProperty_offset_distance: + case eCSSProperty_offset_rotate: + case eCSSProperty_offset_anchor: { + MOZ_ASSERT( + layer->AsHostLayer()->GetShadowTransformSetByAnimation()); + MOZ_ASSERT(previousValue); + MOZ_ASSERT(layer->GetTransformData()); +#ifdef DEBUG + gfx::Matrix4x4 frameTransform = + AnimationHelper::ServoAnimationValueToMatrix4x4( + animationValues, *layer->GetTransformData(), + layer->CachedMotionPath()); + gfx::Matrix4x4 transformInDevice = + FrameTransformToTransformInDevice(frameTransform, layer, + *layer->GetTransformData()); + MOZ_ASSERT(previousValue->Transform() + .mTransformInDevSpace.FuzzyEqualsMultiplicative( + transformInDevice)); +#endif + // In the case of transform we have to set the unchanged + // transform value again because APZC might have modified the + // previous shadow base transform value. + HostLayer* layerCompositor = layer->AsHostLayer(); + layerCompositor->SetShadowBaseTransform( + // FIXME: Bug 1459775: It seems possible that we somehow try + // to sample animations and skip it even if the previous value + // has been discarded from the animation storage when we enable + // layer tree cache. So for the safety, in the case where we + // have no previous animation value, we set non-animating value + // instead. + previousValue ? previousValue->Transform().mTransformInDevSpace + : layer->GetBaseTransform()); + break; + } + default: + MOZ_ASSERT_UNREACHABLE("Unsupported properties"); + break; + } + break; + case AnimationHelper::SampleResult::None: { + HostLayer* layerCompositor = layer->AsHostLayer(); + layerCompositor->SetShadowBaseTransform(layer->GetBaseTransform()); + layerCompositor->SetShadowTransformSetByAnimation(false); + layerCompositor->SetShadowOpacity(layer->GetOpacity()); + layerCompositor->SetShadowOpacitySetByAnimation(false); + break; + } + default: + break; + } + }); + + if (!janked.empty()) { + aCompositorBridge->NotifyJankedAnimations(janked); + } + + return isAnimating; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/CompositorAnimationStorage.h b/gfx/layers/CompositorAnimationStorage.h new file mode 100644 index 0000000000..9122be89ca --- /dev/null +++ b/gfx/layers/CompositorAnimationStorage.h @@ -0,0 +1,236 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_CompositorAnimationStorage_h +#define mozilla_layers_CompositorAnimationStorage_h + +#include "mozilla/layers/AnimationStorageData.h" +#include "mozilla/layers/LayersMessages.h" // for TransformData, etc +#include "mozilla/webrender/webrender_ffi.h" +#include "mozilla/Variant.h" +#include "nsClassHashtable.h" +#include "X11UndefineNone.h" +#include +#include +#include + +namespace mozilla { +namespace layers { +class Animation; +class CompositorBridgeParent; +class Layer; +class OMTAController; + +typedef nsTArray AnimationArray; + +struct AnimationTransform { + /* + * This transform is calculated from sampleanimation in device pixel + * and used for layers (i.e. non WebRender) + */ + gfx::Matrix4x4 mTransformInDevSpace; + /* + * This transform is calculated from frame used for WebRender and used by + * getOMTAStyle() for OMTA testing. + */ + gfx::Matrix4x4 mFrameTransform; + TransformData mData; +}; + +struct AnimatedValue final { + typedef Variant AnimatedValueType; + + const AnimatedValueType& Value() const { return mValue; } + const AnimationTransform& Transform() const { + return mValue.as(); + } + const float& Opacity() const { return mValue.as(); } + const nscolor& Color() const { return mValue.as(); } + template + bool Is() const { + return mValue.is(); + } + + AnimatedValue(const gfx::Matrix4x4& aTransformInDevSpace, + const gfx::Matrix4x4& aFrameTransform, + const TransformData& aData) + : mValue(AsVariant(AnimationTransform{aTransformInDevSpace, + aFrameTransform, aData})) {} + + explicit AnimatedValue(const float& aValue) : mValue(AsVariant(aValue)) {} + + explicit AnimatedValue(nscolor aValue) : mValue(AsVariant(aValue)) {} + + void SetTransformForWebRender(const gfx::Matrix4x4& aFrameTransform, + const TransformData& aData) { + MOZ_ASSERT(mValue.is()); + AnimationTransform& previous = mValue.as(); + previous.mFrameTransform = aFrameTransform; + if (previous.mData != aData) { + previous.mData = aData; + } + } + void SetTransform(const gfx::Matrix4x4& aTransformInDevSpace, + const gfx::Matrix4x4& aFrameTransform, + const TransformData& aData) { + MOZ_ASSERT(mValue.is()); + AnimationTransform& previous = mValue.as(); + previous.mTransformInDevSpace = aTransformInDevSpace; + previous.mFrameTransform = aFrameTransform; + if (previous.mData != aData) { + previous.mData = aData; + } + } + void SetOpacity(float aOpacity) { + MOZ_ASSERT(mValue.is()); + mValue.as() = aOpacity; + } + void SetColor(nscolor aColor) { + MOZ_ASSERT(mValue.is()); + mValue.as() = aColor; + } + + private: + AnimatedValueType mValue; +}; + +struct WrAnimations { + nsTArray mOpacityArrays; + nsTArray mTransformArrays; + nsTArray mColorArrays; +}; + +// CompositorAnimationStorage stores the animations and animated values +// keyed by a CompositorAnimationsId. The "animations" are a representation of +// an entire animation over time, while the "animated values" are values sampled +// from the animations at a particular point in time. +// +// There is one CompositorAnimationStorage per CompositorBridgeParent (i.e. +// one per browser window), and the CompositorAnimationsId key is unique within +// a particular CompositorAnimationStorage instance. +// +// Each layer which has animations gets a CompositorAnimationsId key, and reuses +// that key during its lifetime. Likewise, in layers-free webrender, a display +// item that is animated (e.g. nsDisplayTransform) gets a CompositorAnimationsId +// key and reuses that key (it persists the key via the frame user-data +// mechanism). +class CompositorAnimationStorage final { + typedef nsClassHashtable AnimatedValueTable; + typedef std::unordered_map> + AnimationsTable; + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CompositorAnimationStorage) + public: + explicit CompositorAnimationStorage(CompositorBridgeParent* aCompositorBridge) + : mLock("CompositorAnimationStorage::mLock"), + mCompositorBridge(aCompositorBridge) {} + + OMTAValue GetOMTAValue(const uint64_t& aId) const; + + /** + * Collect all animations in this class as WebRender type properties. + */ + WrAnimations CollectWebRenderAnimations() const; + + /** + * Set the animations based on the unique id + */ + void SetAnimations(uint64_t aId, const LayersId& aLayersId, + const AnimationArray& aAnimations); + + /** + * Sample animation based the given timestamps and store them in this + * CompositorAnimationStorage. The animated values after sampling will be + * stored in CompositorAnimationStorage as well. + * + * Returns true if there is any animation. + * Note that even if there are only in-delay phase animations (i.e. not + * visually effective), this function returns true to ensure we composite + * again on the next tick. + * + * Note: This is called only by WebRender. + */ + bool SampleAnimations(const OMTAController* aOMTAController, + TimeStamp aPreviousFrameTime, + TimeStamp aCurrentFrameTime); + + /** + * Non WebRender version of above SampleAnimations. + * + * Note: This is called only by non WebRender. + */ + bool SampleAnimations(Layer* aRoot, CompositorBridgeParent* aCompositorBridge, + TimeStamp aPreviousFrameTime, + TimeStamp aCurrentFrameTime); + + bool HasAnimations() const; + + /** + * Clear AnimatedValues and Animations data + */ + void ClearById(const uint64_t& aId); + + private: + ~CompositorAnimationStorage() = default; + + /** + * Return the animated value if a given id can map to its animated value + */ + AnimatedValue* GetAnimatedValue(const uint64_t& aId) const; + + /** + * Set the animation transform based on the unique id and also + * set up |aFrameTransform| and |aData| for OMTA testing. + * If |aPreviousValue| is not null, the animation transform replaces the value + * in the |aPreviousValue|. + * NOTE: |aPreviousValue| should be the value for the |aId|. + */ + void SetAnimatedValue(uint64_t aId, AnimatedValue* aPreviousValue, + const gfx::Matrix4x4& aTransformInDevSpace, + const gfx::Matrix4x4& aFrameTransform, + const TransformData& aData); + + /** + * This is for the WebRender version of above SetAnimatedValue. + * In the case of WebRender we don't need to have |aTransformInDevSpace| + * separately because it's same as |aFrameTransform|. + */ + void SetAnimatedValueForWebRender(uint64_t aId, AnimatedValue* aPreviousValue, + const gfx::Matrix4x4& aFrameTransform, + const TransformData& aData); + + /** + * Similar to above but for opacity. + */ + void SetAnimatedValue(uint64_t aId, AnimatedValue* aPreviousValue, + float aOpacity); + + /** + * Similar to above but for color. + */ + void SetAnimatedValue(uint64_t aId, AnimatedValue* aPreviousValue, + nscolor aColor); + + bool ApplyAnimatedValue( + CompositorBridgeParent* aCompositorBridge, Layer* aLayer, + nsCSSPropertyID aProperty, AnimatedValue* aPreviousValue, + const nsTArray>& aValues); + + void Clear(); + + private: + AnimatedValueTable mAnimatedValues; + AnimationsTable mAnimations; + std::unordered_set mNewAnimations; + mutable Mutex mLock; + // CompositorBridgeParent owns this CompositorAnimationStorage instance. + CompositorBridgeParent* MOZ_NON_OWNING_REF mCompositorBridge; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_CompositorAnimationStorage_h diff --git a/gfx/layers/CompositorTypes.cpp b/gfx/layers/CompositorTypes.cpp new file mode 100644 index 0000000000..9f03a63df6 --- /dev/null +++ b/gfx/layers/CompositorTypes.cpp @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "CompositorTypes.h" + +#include + +namespace mozilla { +namespace layers { + +std::ostream& operator<<(std::ostream& aStream, const TextureFlags& aFlags) { + if (aFlags == TextureFlags::NO_FLAGS) { + aStream << "NoFlags"; + } else { +#define AppendFlag(test) \ + { \ + if (!!(aFlags & (test))) { \ + if (previous) { \ + aStream << "|"; \ + } \ + aStream << #test; \ + previous = true; \ + } \ + } + bool previous = false; + AppendFlag(TextureFlags::USE_NEAREST_FILTER); + AppendFlag(TextureFlags::ORIGIN_BOTTOM_LEFT); + AppendFlag(TextureFlags::DISALLOW_BIGIMAGE); + AppendFlag(TextureFlags::RB_SWAPPED); + AppendFlag(TextureFlags::NON_PREMULTIPLIED); + AppendFlag(TextureFlags::RECYCLE); + AppendFlag(TextureFlags::DEALLOCATE_CLIENT); + AppendFlag(TextureFlags::DEALLOCATE_SYNC); + AppendFlag(TextureFlags::DEALLOCATE_MAIN_THREAD); + AppendFlag(TextureFlags::IMMUTABLE); + AppendFlag(TextureFlags::IMMEDIATE_UPLOAD); + AppendFlag(TextureFlags::COMPONENT_ALPHA); + AppendFlag(TextureFlags::INVALID_COMPOSITOR); + AppendFlag(TextureFlags::RGB_FROM_YCBCR); + AppendFlag(TextureFlags::SNAPSHOT); + AppendFlag(TextureFlags::NON_BLOCKING_READ_LOCK); + AppendFlag(TextureFlags::BLOCKING_READ_LOCK); + AppendFlag(TextureFlags::WAIT_HOST_USAGE_END); + AppendFlag(TextureFlags::IS_OPAQUE); +#undef AppendFlag + } + return aStream; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/CompositorTypes.h b/gfx/layers/CompositorTypes.h new file mode 100644 index 0000000000..faf725667d --- /dev/null +++ b/gfx/layers/CompositorTypes.h @@ -0,0 +1,321 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_LAYERS_COMPOSITORTYPES_H +#define MOZILLA_LAYERS_COMPOSITORTYPES_H + +#include +#include // for uint32_t +#include // for int32_t +#include "LayersTypes.h" // for LayersBackend, etc +#include "nsXULAppAPI.h" // for GeckoProcessType, etc +#include "mozilla/gfx/Types.h" +#include "mozilla/EnumSet.h" + +#include "mozilla/TypedEnumBits.h" + +namespace mozilla { +namespace layers { + +/** + * Flags used by texture clients and texture hosts. These are passed from client + * side to host side when textures and compositables are created. Usually set + * by the compositableCient, they may be modified by either the compositable or + * texture clients. + */ +enum class TextureFlags : uint32_t { + NO_FLAGS = 0, + // Use nearest-neighbour texture filtering (as opposed to linear filtering). + USE_NEAREST_FILTER = 1 << 0, + // The compositor assumes everything is origin-top-left by default. + ORIGIN_BOTTOM_LEFT = 1 << 1, + // Force the texture to be represented using a single tile (note that this + // means tiled textures, not tiled layers). + DISALLOW_BIGIMAGE = 1 << 2, + // The buffer will be treated as if the RB bytes are swapped. + // This is useful for rendering using Cairo/Thebes, because there is no + // BGRX Android pixel format, and so we have to do byte swapping. + // + // For example, if the GraphicBuffer has an Android pixel format of + // PIXEL_FORMAT_RGBA_8888 and isRBSwapped is true, when it is sampled + // (for example, with GL), a BGRA shader should be used. + RB_SWAPPED = 1 << 3, + // Data in this texture has not been alpha-premultiplied. + // XXX - Apparently only used with ImageClient/Host + NON_PREMULTIPLIED = 1 << 4, + // The TextureClient should be recycled with recycle callback when no longer + // in used. When the texture is used in host side, ref count of TextureClient + // is transparently added by ShadowLayerForwarder or ImageBridgeChild. + RECYCLE = 1 << 5, + // If DEALLOCATE_CLIENT is set, the shared data is deallocated on the + // client side and requires some extra synchronizaion to ensure race-free + // deallocation. + // The default behaviour is to deallocate on the host side. + DEALLOCATE_CLIENT = 1 << 6, + DEALLOCATE_SYNC = 1 << 6, // XXX - make it a separate flag. + DEALLOCATE_MAIN_THREAD = 1 << 8, + // After being shared ith the compositor side, an immutable texture is never + // modified, it can only be read. It is safe to not Lock/Unlock immutable + // textures. + IMMUTABLE = 1 << 9, + // The contents of the texture must be uploaded or copied immediately + // during the transaction, because the producer may want to write + // to it again. + IMMEDIATE_UPLOAD = 1 << 10, + // The texture is part of a component-alpha pair + COMPONENT_ALPHA = 1 << 11, + // The texture is being allocated for a compositor that no longer exists. + // This flag is only used in the parent process. + INVALID_COMPOSITOR = 1 << 12, + // The texture was created by converting from YCBCR to RGB + RGB_FROM_YCBCR = 1 << 13, + // The texture is used for snapshot. + SNAPSHOT = 1 << 14, + // Enable a non blocking read lock. + NON_BLOCKING_READ_LOCK = 1 << 15, + // Enable a blocking read lock. + BLOCKING_READ_LOCK = 1 << 16, + // Keep TextureClient alive when host side is used + WAIT_HOST_USAGE_END = 1 << 17, + // The texture is guaranteed to have alpha 1.0 everywhere; some backends + // have trouble with RGBX/BGRX formats, so we use RGBA/BGRA but set this + // hint when we know alpha is opaque (eg. WebGL) + IS_OPAQUE = 1 << 18, + + // OR union of all valid bits + ALL_BITS = (1 << 19) - 1, + // the default flags + DEFAULT = NO_FLAGS +}; +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(TextureFlags) + +std::ostream& operator<<(std::ostream& aStream, const TextureFlags& aFlags); + +static inline bool TextureRequiresLocking(TextureFlags aFlags) { + // If we're not double buffered, or uploading + // within a transaction, then we need to support + // locking correctly. + return !(aFlags & (TextureFlags::IMMEDIATE_UPLOAD | TextureFlags::IMMUTABLE)); +} + +/** + * The type of debug diagnostic to enable. + */ +enum class DiagnosticTypes : uint8_t { + NO_DIAGNOSTIC = 0, + TILE_BORDERS = 1 << 0, + LAYER_BORDERS = 1 << 1, + BIGIMAGE_BORDERS = 1 << 2, + FLASH_BORDERS = 1 << 3, + ALL_BITS = (1 << 4) - 1 +}; +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(DiagnosticTypes) + +#define DIAGNOSTIC_FLASH_COUNTER_MAX 100 + +/** + * Information about the object that is being diagnosed. + */ +enum class DiagnosticFlags : uint16_t { + NO_DIAGNOSTIC = 0, + IMAGE = 1 << 0, + CONTENT = 1 << 1, + CANVAS = 1 << 2, + COLOR = 1 << 3, + CONTAINER = 1 << 4, + TILE = 1 << 5, + BIGIMAGE = 1 << 6, + COMPONENT_ALPHA = 1 << 7, + REGION_RECT = 1 << 8, + NV12 = 1 << 9, + YCBCR = 1 << 10 +}; +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(DiagnosticFlags) + +/** + * See gfx/layers/Effects.h + */ +enum class EffectTypes : uint8_t { + MASK, + BLEND_MODE, + COLOR_MATRIX, + MAX_SECONDARY, // sentinel for the count of secondary effect types + RGB, + YCBCR, + NV12, + COMPONENT_ALPHA, + SOLID_COLOR, + RENDER_TARGET, + MAX // sentinel for the count of all effect types +}; + +/** + * How the Compositable should manage textures. + */ +enum class CompositableType : uint8_t { + UNKNOWN, + CONTENT_TILED, // tiled painted layer + IMAGE, // image with single buffering + IMAGE_BRIDGE, // ImageBridge protocol + CONTENT_SINGLE, // painted layer interface, single buffering + CONTENT_DOUBLE, // painted layer interface, double buffering + COUNT +}; + +#ifdef XP_WIN +typedef void* SyncHandle; +#else +typedef uintptr_t SyncHandle; +#endif // XP_WIN + +/** + * Sent from the compositor to the content-side LayerManager, includes + * properties of the compositor and should (in the future) include information + * about what kinds of buffer and texture clients to create. + */ +struct TextureFactoryIdentifier { + LayersBackend mParentBackend; + WebRenderBackend mWebRenderBackend; + WebRenderCompositor mWebRenderCompositor; + GeckoProcessType mParentProcessType; + int32_t mMaxTextureSize; + bool mSupportsTextureDirectMapping; + bool mCompositorUseANGLE; + bool mCompositorUseDComp; + bool mUseCompositorWnd; + bool mSupportsTextureBlitting; + bool mSupportsPartialUploads; + bool mSupportsComponentAlpha; + bool mUsingAdvancedLayers; + SyncHandle mSyncHandle; + + explicit TextureFactoryIdentifier( + LayersBackend aLayersBackend = LayersBackend::LAYERS_NONE, + GeckoProcessType aParentProcessType = GeckoProcessType_Default, + int32_t aMaxTextureSize = 4096, + bool aSupportsTextureDirectMapping = false, + bool aCompositorUseANGLE = false, bool aCompositorUseDComp = false, + bool aUseCompositorWnd = false, bool aSupportsTextureBlitting = false, + bool aSupportsPartialUploads = false, bool aSupportsComponentAlpha = true, + SyncHandle aSyncHandle = 0) + : mParentBackend(aLayersBackend), + mWebRenderBackend(WebRenderBackend::HARDWARE), + mWebRenderCompositor(WebRenderCompositor::DRAW), + mParentProcessType(aParentProcessType), + mMaxTextureSize(aMaxTextureSize), + mSupportsTextureDirectMapping(aSupportsTextureDirectMapping), + mCompositorUseANGLE(aCompositorUseANGLE), + mCompositorUseDComp(aCompositorUseDComp), + mUseCompositorWnd(aUseCompositorWnd), + mSupportsTextureBlitting(aSupportsTextureBlitting), + mSupportsPartialUploads(aSupportsPartialUploads), + mSupportsComponentAlpha(aSupportsComponentAlpha), + mUsingAdvancedLayers(false), + mSyncHandle(aSyncHandle) {} + + explicit TextureFactoryIdentifier( + WebRenderBackend aWebRenderBackend, + WebRenderCompositor aWebRenderCompositor, + GeckoProcessType aParentProcessType = GeckoProcessType_Default, + int32_t aMaxTextureSize = 4096, + bool aSupportsTextureDirectMapping = false, + bool aCompositorUseANGLE = false, bool aCompositorUseDComp = false, + bool aUseCompositorWnd = false, bool aSupportsTextureBlitting = false, + bool aSupportsPartialUploads = false, bool aSupportsComponentAlpha = true, + SyncHandle aSyncHandle = 0) + : mParentBackend(LayersBackend::LAYERS_WR), + mWebRenderBackend(aWebRenderBackend), + mWebRenderCompositor(aWebRenderCompositor), + mParentProcessType(aParentProcessType), + mMaxTextureSize(aMaxTextureSize), + mSupportsTextureDirectMapping(aSupportsTextureDirectMapping), + mCompositorUseANGLE(aCompositorUseANGLE), + mCompositorUseDComp(aCompositorUseDComp), + mUseCompositorWnd(aUseCompositorWnd), + mSupportsTextureBlitting(aSupportsTextureBlitting), + mSupportsPartialUploads(aSupportsPartialUploads), + mSupportsComponentAlpha(aSupportsComponentAlpha), + mUsingAdvancedLayers(false), + mSyncHandle(aSyncHandle) {} + + bool operator==(const TextureFactoryIdentifier& aOther) const { + return mParentBackend == aOther.mParentBackend && + mWebRenderBackend == aOther.mWebRenderBackend && + mWebRenderCompositor == aOther.mWebRenderCompositor && + mParentProcessType == aOther.mParentProcessType && + mMaxTextureSize == aOther.mMaxTextureSize && + mSupportsTextureDirectMapping == + aOther.mSupportsTextureDirectMapping && + mCompositorUseANGLE == aOther.mCompositorUseANGLE && + mCompositorUseDComp == aOther.mCompositorUseDComp && + mUseCompositorWnd == aOther.mUseCompositorWnd && + mSupportsTextureBlitting == aOther.mSupportsTextureBlitting && + mSupportsPartialUploads == aOther.mSupportsPartialUploads && + mSupportsComponentAlpha == aOther.mSupportsComponentAlpha && + mUsingAdvancedLayers == aOther.mUsingAdvancedLayers && + mSyncHandle == aOther.mSyncHandle; + } +}; + +/** + * Information required by the compositor from the content-side for creating or + * using compositables and textures. + * XXX - TextureInfo is a bad name: this information is useful for the + * compositable, not the Texture. And ith new Textures, only the compositable + * type is really useful. This may (should) be removed in the near future. + */ +struct TextureInfo { + CompositableType mCompositableType; + TextureFlags mTextureFlags; + + TextureInfo() + : mCompositableType(CompositableType::UNKNOWN), + mTextureFlags(TextureFlags::NO_FLAGS) {} + + explicit TextureInfo(CompositableType aType, + TextureFlags aTextureFlags = TextureFlags::DEFAULT) + : mCompositableType(aType), mTextureFlags(aTextureFlags) {} + + bool operator==(const TextureInfo& aOther) const { + return mCompositableType == aOther.mCompositableType && + mTextureFlags == aOther.mTextureFlags; + } +}; + +/** + * How a SurfaceDescriptor will be opened. + * + * See ShadowLayerForwarder::OpenDescriptor for example. + */ +enum class OpenMode : uint8_t { + OPEN_NONE = 0, + OPEN_READ = 0x1, + OPEN_WRITE = 0x2, + // This is only used in conjunction with OMTP to indicate that the DrawTarget + // that is being borrowed will be painted asynchronously, and so will outlive + // the write lock. + OPEN_ASYNC = 0x04, + + OPEN_READ_WRITE = OPEN_READ | OPEN_WRITE, + OPEN_READ_WRITE_ASYNC = OPEN_READ | OPEN_WRITE | OPEN_ASYNC, + OPEN_READ_ASYNC = OPEN_READ | OPEN_ASYNC, + OPEN_READ_ONLY = OPEN_READ, + OPEN_WRITE_ONLY = OPEN_WRITE, +}; +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(OpenMode) + +// The kinds of mask texture a shader can support +// We rely on the items in this enum being sequential +enum class MaskType : uint8_t { + MaskNone = 0, // no mask layer + Mask, // mask layer + NumMaskTypes +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/D3D11ShareHandleImage.cpp b/gfx/layers/D3D11ShareHandleImage.cpp new file mode 100644 index 0000000000..9acd76c509 --- /dev/null +++ b/gfx/layers/D3D11ShareHandleImage.cpp @@ -0,0 +1,188 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "D3D11ShareHandleImage.h" +#include +#include "DXVA2Manager.h" +#include "WMF.h" +#include "d3d11.h" +#include "gfxImageSurface.h" +#include "gfxWindowsPlatform.h" +#include "mozilla/StaticPrefs_media.h" +#include "mozilla/gfx/DeviceManagerDx.h" +#include "mozilla/layers/CompositableClient.h" +#include "mozilla/layers/CompositableForwarder.h" +#include "mozilla/layers/TextureClient.h" +#include "mozilla/layers/TextureD3D11.h" + +namespace mozilla { +namespace layers { + +using namespace gfx; + +D3D11ShareHandleImage::D3D11ShareHandleImage(const gfx::IntSize& aSize, + const gfx::IntRect& aRect, + gfx::YUVColorSpace aColorSpace, + gfx::ColorRange aColorRange) + : Image(nullptr, ImageFormat::D3D11_SHARE_HANDLE_TEXTURE), + mSize(aSize), + mPictureRect(aRect), + mYUVColorSpace(aColorSpace), + mColorRange(aColorRange) {} + +bool D3D11ShareHandleImage::AllocateTexture(D3D11RecycleAllocator* aAllocator, + ID3D11Device* aDevice) { + if (aAllocator) { + mTextureClient = + aAllocator->CreateOrRecycleClient(mYUVColorSpace, mColorRange, mSize); + if (mTextureClient) { + D3D11TextureData* textureData = GetData(); + MOZ_DIAGNOSTIC_ASSERT(textureData, "Wrong TextureDataType"); + mTexture = textureData->GetD3D11Texture(); + return true; + } + return false; + } else { + MOZ_ASSERT(aDevice); + CD3D11_TEXTURE2D_DESC newDesc( + DXGI_FORMAT_B8G8R8A8_UNORM, mSize.width, mSize.height, 1, 1, + D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE); + newDesc.MiscFlags = D3D11_RESOURCE_MISC_SHARED; + + HRESULT hr = + aDevice->CreateTexture2D(&newDesc, nullptr, getter_AddRefs(mTexture)); + return SUCCEEDED(hr); + } +} + +gfx::IntSize D3D11ShareHandleImage::GetSize() const { return mSize; } + +TextureClient* D3D11ShareHandleImage::GetTextureClient( + KnowsCompositor* aKnowsCompositor) { + return mTextureClient; +} + +already_AddRefed +D3D11ShareHandleImage::GetAsSourceSurface() { + RefPtr src = GetTexture(); + if (!src) { + gfxWarning() << "Cannot readback from shared texture because no texture is " + "available."; + return nullptr; + } + + return gfx::Factory::CreateBGRA8DataSourceSurfaceForD3D11Texture(src); +} + +ID3D11Texture2D* D3D11ShareHandleImage::GetTexture() const { return mTexture; } + +class MOZ_RAII D3D11TextureClientAllocationHelper + : public ITextureClientAllocationHelper { + public: + D3D11TextureClientAllocationHelper(gfx::SurfaceFormat aFormat, + gfx::YUVColorSpace aColorSpace, + gfx::ColorRange aColorRange, + const gfx::IntSize& aSize, + TextureAllocationFlags aAllocFlags, + ID3D11Device* aDevice, + TextureFlags aTextureFlags) + : ITextureClientAllocationHelper(aFormat, aSize, BackendSelector::Content, + aTextureFlags, aAllocFlags), + mYUVColorSpace(aColorSpace), + mColorRange(aColorRange), + mDevice(aDevice) {} + + bool IsCompatible(TextureClient* aTextureClient) override { + D3D11TextureData* textureData = + aTextureClient->GetInternalData()->AsD3D11TextureData(); + if (!textureData || aTextureClient->GetFormat() != mFormat || + aTextureClient->GetSize() != mSize) { + return false; + } + // TODO: Should we also check for change in the allocation flags if RGBA? + return (aTextureClient->GetFormat() != gfx::SurfaceFormat::NV12 && + aTextureClient->GetFormat() != gfx::SurfaceFormat::P010 && + aTextureClient->GetFormat() != gfx::SurfaceFormat::P016) || + (textureData->GetYUVColorSpace() == mYUVColorSpace && + textureData->GetColorRange() == mColorRange && + textureData->GetTextureAllocationFlags() == mAllocationFlags); + } + + already_AddRefed Allocate( + KnowsCompositor* aAllocator) override { + D3D11TextureData* data = + D3D11TextureData::Create(mSize, mFormat, mAllocationFlags, mDevice); + if (!data) { + return nullptr; + } + data->SetYUVColorSpace(mYUVColorSpace); + data->SetColorRange(mColorRange); + return MakeAndAddRef(data, mTextureFlags, + aAllocator->GetTextureForwarder()); + } + + private: + const gfx::YUVColorSpace mYUVColorSpace; + const gfx::ColorRange mColorRange; + const RefPtr mDevice; +}; + +D3D11RecycleAllocator::D3D11RecycleAllocator( + KnowsCompositor* aAllocator, ID3D11Device* aDevice, + gfx::SurfaceFormat aPreferredFormat) + : TextureClientRecycleAllocator(aAllocator), + mDevice(aDevice), + mCanUseNV12(StaticPrefs::media_wmf_use_nv12_format() && + gfx::DeviceManagerDx::Get()->CanUseNV12()), + mCanUseP010(StaticPrefs::media_wmf_use_nv12_format() && + gfx::DeviceManagerDx::Get()->CanUseP010()), + mCanUseP016(StaticPrefs::media_wmf_use_nv12_format() && + gfx::DeviceManagerDx::Get()->CanUseP016()) { + SetPreferredSurfaceFormat(aPreferredFormat); +} + +void D3D11RecycleAllocator::SetPreferredSurfaceFormat( + gfx::SurfaceFormat aPreferredFormat) { + if ((aPreferredFormat == gfx::SurfaceFormat::NV12 && mCanUseNV12) || + (aPreferredFormat == gfx::SurfaceFormat::P010 && mCanUseP010) || + (aPreferredFormat == gfx::SurfaceFormat::P016 && mCanUseP016)) { + mUsableSurfaceFormat = aPreferredFormat; + return; + } + // We can't handle the native source format, set it to BGRA which will + // force the caller to convert it later. + mUsableSurfaceFormat = gfx::SurfaceFormat::B8G8R8A8; +} + +already_AddRefed D3D11RecycleAllocator::CreateOrRecycleClient( + gfx::YUVColorSpace aColorSpace, gfx::ColorRange aColorRange, + const gfx::IntSize& aSize) { + // When CompositorDevice or ContentDevice is updated, + // we could not reuse old D3D11Textures. It could cause video flickering. + RefPtr device = gfx::DeviceManagerDx::Get()->GetImageDevice(); + if (!!mImageDevice && mImageDevice != device) { + ShrinkToMinimumSize(); + } + mImageDevice = device; + + TextureAllocationFlags allocFlags = TextureAllocationFlags::ALLOC_DEFAULT; + if (StaticPrefs::media_wmf_use_sync_texture_AtStartup() || + mDevice == DeviceManagerDx::Get()->GetCompositorDevice()) { + // If our device is the compositor device, we don't need any synchronization + // in practice. + allocFlags = TextureAllocationFlags::ALLOC_MANUAL_SYNCHRONIZATION; + } + + D3D11TextureClientAllocationHelper helper( + mUsableSurfaceFormat, aColorSpace, aColorRange, aSize, allocFlags, + mDevice, layers::TextureFlags::DEFAULT); + + RefPtr textureClient = CreateOrRecycle(helper); + return textureClient.forget(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/D3D11ShareHandleImage.h b/gfx/layers/D3D11ShareHandleImage.h new file mode 100644 index 0000000000..76ce6d1058 --- /dev/null +++ b/gfx/layers/D3D11ShareHandleImage.h @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_D311_SHARE_HANDLE_IMAGE_H +#define GFX_D311_SHARE_HANDLE_IMAGE_H + +#include "ImageContainer.h" +#include "d3d11.h" +#include "mozilla/Atomics.h" +#include "mozilla/RefPtr.h" +#include "mozilla/gfx/Types.h" +#include "mozilla/layers/TextureClient.h" +#include "mozilla/layers/TextureClientRecycleAllocator.h" +#include "mozilla/layers/TextureD3D11.h" + +namespace mozilla { +namespace gl { +class GLBlitHelper; +} +namespace layers { + +class D3D11RecycleAllocator final : public TextureClientRecycleAllocator { + public: + D3D11RecycleAllocator(KnowsCompositor* aAllocator, ID3D11Device* aDevice, + gfx::SurfaceFormat aPreferredFormat); + + already_AddRefed CreateOrRecycleClient( + gfx::YUVColorSpace aColorSpace, gfx::ColorRange aColorRange, + const gfx::IntSize& aSize); + + void SetPreferredSurfaceFormat(gfx::SurfaceFormat aPreferredFormat); + + private: + const RefPtr mDevice; + const bool mCanUseNV12; + const bool mCanUseP010; + const bool mCanUseP016; + /** + * Used for checking if CompositorDevice/ContentDevice is updated. + */ + RefPtr mImageDevice; + gfx::SurfaceFormat mUsableSurfaceFormat; +}; + +// Image class that wraps a ID3D11Texture2D. This class copies the image +// passed into SetData(), so that it can be accessed from other D3D devices. +// This class also manages the synchronization of the copy, to ensure the +// resource is ready to use. +class D3D11ShareHandleImage final : public Image { + public: + D3D11ShareHandleImage(const gfx::IntSize& aSize, const gfx::IntRect& aRect, + gfx::YUVColorSpace aColorSpace, + gfx::ColorRange aColorRange); + virtual ~D3D11ShareHandleImage() = default; + + bool AllocateTexture(D3D11RecycleAllocator* aAllocator, + ID3D11Device* aDevice); + + gfx::IntSize GetSize() const override; + already_AddRefed GetAsSourceSurface() override; + TextureClient* GetTextureClient(KnowsCompositor* aKnowsCompositor) override; + gfx::IntRect GetPictureRect() const override { return mPictureRect; } + + Maybe GetDesc() override { return GetDescFromTexClient(); } + + ID3D11Texture2D* GetTexture() const; + + gfx::YUVColorSpace GetYUVColorSpace() const { return mYUVColorSpace; } + gfx::ColorRange GetColorRange() const { return mColorRange; } + + private: + friend class gl::GLBlitHelper; + D3D11TextureData* GetData() const { + if (!mTextureClient) { + return nullptr; + } + return mTextureClient->GetInternalData()->AsD3D11TextureData(); + } + + gfx::IntSize mSize; + gfx::IntRect mPictureRect; + gfx::YUVColorSpace mYUVColorSpace; + gfx::ColorRange mColorRange; + RefPtr mTextureClient; + RefPtr mTexture; +}; + +} // namespace layers +} // namespace mozilla + +#endif // GFX_D3DSURFACEIMAGE_H diff --git a/gfx/layers/D3D11YCbCrImage.cpp b/gfx/layers/D3D11YCbCrImage.cpp new file mode 100644 index 0000000000..40148f791a --- /dev/null +++ b/gfx/layers/D3D11YCbCrImage.cpp @@ -0,0 +1,430 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "D3D11YCbCrImage.h" + +#include "YCbCrUtils.h" +#include "gfx2DGlue.h" +#include "mozilla/gfx/DeviceManagerDx.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/layers/CompositableClient.h" +#include "mozilla/layers/CompositableForwarder.h" +#include "mozilla/layers/TextureD3D11.h" + +using namespace mozilla::gfx; + +namespace mozilla { +namespace layers { + +D3D11YCbCrImage::D3D11YCbCrImage() + : Image(NULL, ImageFormat::D3D11_YCBCR_IMAGE) {} + +D3D11YCbCrImage::~D3D11YCbCrImage() {} + +bool D3D11YCbCrImage::SetData(KnowsCompositor* aAllocator, + ImageContainer* aContainer, + const PlanarYCbCrData& aData) { + mPictureRect = IntRect(aData.mPicX, aData.mPicY, aData.mPicSize.width, + aData.mPicSize.height); + mYSize = aData.mYSize; + mCbCrSize = aData.mCbCrSize; + mColorDepth = aData.mColorDepth; + mColorSpace = aData.mYUVColorSpace; + mColorRange = aData.mColorRange; + + D3D11YCbCrRecycleAllocator* allocator = + aContainer->GetD3D11YCbCrRecycleAllocator(aAllocator); + if (!allocator) { + return false; + } + + RefPtr device = gfx::DeviceManagerDx::Get()->GetImageDevice(); + if (!device) { + return false; + } + + { + DXGIYCbCrTextureAllocationHelper helper(aData, TextureFlags::DEFAULT, + device); + mTextureClient = allocator->CreateOrRecycle(helper); + } + + if (!mTextureClient) { + return false; + } + + DXGIYCbCrTextureData* data = + mTextureClient->GetInternalData()->AsDXGIYCbCrTextureData(); + + ID3D11Texture2D* textureY = data->GetD3D11Texture(0); + ID3D11Texture2D* textureCb = data->GetD3D11Texture(1); + ID3D11Texture2D* textureCr = data->GetD3D11Texture(2); + + RefPtr mt; + HRESULT hr = device->QueryInterface((ID3D10Multithread**)getter_AddRefs(mt)); + + if (FAILED(hr) || !mt) { + gfxCriticalError() << "Multithread safety interface not supported. " << hr; + return false; + } + + if (!mt->GetMultithreadProtected()) { + gfxCriticalError() << "Device used not marked as multithread-safe."; + return false; + } + + D3D11MTAutoEnter mtAutoEnter(mt.forget()); + + RefPtr ctx; + device->GetImmediateContext(getter_AddRefs(ctx)); + if (!ctx) { + gfxCriticalError() << "Failed to get immediate context."; + return false; + } + + AutoLockD3D11Texture lockY(textureY); + AutoLockD3D11Texture lockCb(textureCb); + AutoLockD3D11Texture lockCr(textureCr); + + ctx->UpdateSubresource(textureY, 0, nullptr, aData.mYChannel, aData.mYStride, + aData.mYStride * aData.mYSize.height); + ctx->UpdateSubresource(textureCb, 0, nullptr, aData.mCbChannel, + aData.mCbCrStride, + aData.mCbCrStride * aData.mCbCrSize.height); + ctx->UpdateSubresource(textureCr, 0, nullptr, aData.mCrChannel, + aData.mCbCrStride, + aData.mCbCrStride * aData.mCbCrSize.height); + + return true; +} + +IntSize D3D11YCbCrImage::GetSize() const { return mPictureRect.Size(); } + +TextureClient* D3D11YCbCrImage::GetTextureClient( + KnowsCompositor* aKnowsCompositor) { + return mTextureClient; +} + +const DXGIYCbCrTextureData* D3D11YCbCrImage::GetData() const { + if (!mTextureClient) return nullptr; + + return mTextureClient->GetInternalData()->AsDXGIYCbCrTextureData(); +} + +already_AddRefed D3D11YCbCrImage::GetAsSourceSurface() { + if (!mTextureClient) { + gfxWarning() + << "GetAsSourceSurface() called on uninitialized D3D11YCbCrImage."; + return nullptr; + } + + gfx::IntSize size(mPictureRect.Size()); + gfx::SurfaceFormat format = + gfx::ImageFormatToSurfaceFormat(gfxVars::OffscreenFormat()); + HRESULT hr; + + PlanarYCbCrData data; + + DXGIYCbCrTextureData* dxgiData = + mTextureClient->GetInternalData()->AsDXGIYCbCrTextureData(); + + if (!dxgiData) { + gfxCriticalError() << "Failed to get texture client internal data."; + return nullptr; + } + + RefPtr texY = dxgiData->GetD3D11Texture(0); + RefPtr texCb = dxgiData->GetD3D11Texture(1); + RefPtr texCr = dxgiData->GetD3D11Texture(2); + RefPtr softTexY, softTexCb, softTexCr; + D3D11_TEXTURE2D_DESC desc; + + RefPtr dev; + texY->GetDevice(getter_AddRefs(dev)); + + if (!dev || dev != gfx::DeviceManagerDx::Get()->GetImageDevice()) { + gfxCriticalError() << "D3D11Device is obsoleted"; + return nullptr; + } + + RefPtr mt; + hr = dev->QueryInterface((ID3D10Multithread**)getter_AddRefs(mt)); + + if (FAILED(hr) || !mt) { + gfxCriticalError() << "Multithread safety interface not supported."; + return nullptr; + } + + if (!mt->GetMultithreadProtected()) { + gfxCriticalError() << "Device used not marked as multithread-safe."; + return nullptr; + } + + D3D11MTAutoEnter mtAutoEnter(mt.forget()); + + texY->GetDesc(&desc); + desc.BindFlags = 0; + desc.MiscFlags = 0; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + desc.Usage = D3D11_USAGE_STAGING; + + dev->CreateTexture2D(&desc, nullptr, getter_AddRefs(softTexY)); + + texCb->GetDesc(&desc); + desc.BindFlags = 0; + desc.MiscFlags = 0; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + desc.Usage = D3D11_USAGE_STAGING; + + dev->CreateTexture2D(&desc, nullptr, getter_AddRefs(softTexCb)); + + texCr->GetDesc(&desc); + desc.BindFlags = 0; + desc.MiscFlags = 0; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + desc.Usage = D3D11_USAGE_STAGING; + + dev->CreateTexture2D(&desc, nullptr, getter_AddRefs(softTexCr)); + + RefPtr ctx; + dev->GetImmediateContext(getter_AddRefs(ctx)); + if (!ctx) { + gfxCriticalError() << "Failed to get immediate context."; + return nullptr; + } + + { + AutoLockD3D11Texture lockY(texY); + AutoLockD3D11Texture lockCb(texCb); + AutoLockD3D11Texture lockCr(texCr); + ctx->CopyResource(softTexY, texY); + ctx->CopyResource(softTexCb, texCb); + ctx->CopyResource(softTexCr, texCr); + } + + D3D11_MAPPED_SUBRESOURCE mapY, mapCb, mapCr; + RefPtr surface; + mapY.pData = mapCb.pData = mapCr.pData = nullptr; + + hr = ctx->Map(softTexY, 0, D3D11_MAP_READ, 0, &mapY); + if (FAILED(hr)) { + gfxCriticalError() << "Failed to map Y plane (" << hr << ")"; + return nullptr; + } + hr = ctx->Map(softTexCb, 0, D3D11_MAP_READ, 0, &mapCb); + if (FAILED(hr)) { + gfxCriticalError() << "Failed to map Y plane (" << hr << ")"; + return nullptr; + } + hr = ctx->Map(softTexCr, 0, D3D11_MAP_READ, 0, &mapCr); + if (FAILED(hr)) { + gfxCriticalError() << "Failed to map Y plane (" << hr << ")"; + return nullptr; + } + + MOZ_ASSERT(mapCb.RowPitch == mapCr.RowPitch); + + data.mPicX = mPictureRect.X(); + data.mPicY = mPictureRect.Y(); + data.mPicSize = mPictureRect.Size(); + data.mStereoMode = StereoMode::MONO; + data.mColorDepth = mColorDepth; + data.mYUVColorSpace = mColorSpace; + data.mColorRange = mColorRange; + data.mYSkip = data.mCbSkip = data.mCrSkip = 0; + data.mYSize = mYSize; + data.mCbCrSize = mCbCrSize; + data.mYChannel = static_cast(mapY.pData); + data.mYStride = mapY.RowPitch; + data.mCbChannel = static_cast(mapCb.pData); + data.mCrChannel = static_cast(mapCr.pData); + data.mCbCrStride = mapCb.RowPitch; + + gfx::GetYCbCrToRGBDestFormatAndSize(data, format, size); + if (size.width > PlanarYCbCrImage::MAX_DIMENSION || + size.height > PlanarYCbCrImage::MAX_DIMENSION) { + gfxCriticalError() << "Illegal image dest width or height"; + return nullptr; + } + + surface = gfx::Factory::CreateDataSourceSurface(size, format); + if (!surface) { + gfxCriticalError() << "Failed to create DataSourceSurface for image: " + << size << " " << format; + return nullptr; + } + + DataSourceSurface::ScopedMap mapping(surface, DataSourceSurface::WRITE); + if (!mapping.IsMapped()) { + gfxCriticalError() << "Failed to map DataSourceSurface for D3D11YCbCrImage"; + return nullptr; + } + + gfx::ConvertYCbCrToRGB(data, format, size, mapping.GetData(), + mapping.GetStride()); + + ctx->Unmap(softTexY, 0); + ctx->Unmap(softTexCb, 0); + ctx->Unmap(softTexCr, 0); + + return surface.forget(); +} + +class AutoCheckLockD3D11Texture final { + public: + explicit AutoCheckLockD3D11Texture(ID3D11Texture2D* aTexture) + : mIsLocked(false) { + aTexture->QueryInterface((IDXGIKeyedMutex**)getter_AddRefs(mMutex)); + if (!mMutex) { + // If D3D11Texture does not have keyed mutex, we think that the + // D3D11Texture could be locked. + mIsLocked = true; + return; + } + + // Test to see if the keyed mutex has been released + HRESULT hr = mMutex->AcquireSync(0, 0); + if (hr == S_OK || hr == WAIT_ABANDONED) { + mIsLocked = true; + // According to Microsoft documentation: + // WAIT_ABANDONED - The shared surface and keyed mutex are no longer in a + // consistent state. If AcquireSync returns this value, you should release + // and recreate both the keyed mutex and the shared surface + // So even if we do get WAIT_ABANDONED, the keyed mutex will have to be + // released. + mSyncAcquired = true; + } + } + + ~AutoCheckLockD3D11Texture() { + if (!mSyncAcquired) { + return; + } + HRESULT hr = mMutex->ReleaseSync(0); + if (FAILED(hr)) { + NS_WARNING("Failed to unlock the texture"); + } + } + + bool IsLocked() const { return mIsLocked; } + + private: + bool mIsLocked; + bool mSyncAcquired = false; + RefPtr mMutex; +}; + +DXGIYCbCrTextureAllocationHelper::DXGIYCbCrTextureAllocationHelper( + const PlanarYCbCrData& aData, TextureFlags aTextureFlags, + ID3D11Device* aDevice) + : ITextureClientAllocationHelper(gfx::SurfaceFormat::YUV, aData.mPicSize, + BackendSelector::Content, aTextureFlags, + ALLOC_DEFAULT), + mData(aData), + mDevice(aDevice) {} + +bool DXGIYCbCrTextureAllocationHelper::IsCompatible( + TextureClient* aTextureClient) { + MOZ_ASSERT(aTextureClient->GetFormat() == gfx::SurfaceFormat::YUV); + + DXGIYCbCrTextureData* dxgiData = + aTextureClient->GetInternalData()->AsDXGIYCbCrTextureData(); + if (!dxgiData || aTextureClient->GetSize() != mData.mPicSize || + dxgiData->GetYSize() != mData.mYSize || + dxgiData->GetCbCrSize() != mData.mCbCrSize || + dxgiData->GetColorDepth() != mData.mColorDepth || + dxgiData->GetYUVColorSpace() != mData.mYUVColorSpace) { + return false; + } + + ID3D11Texture2D* textureY = dxgiData->GetD3D11Texture(0); + ID3D11Texture2D* textureCb = dxgiData->GetD3D11Texture(1); + ID3D11Texture2D* textureCr = dxgiData->GetD3D11Texture(2); + + RefPtr device; + textureY->GetDevice(getter_AddRefs(device)); + if (!device || device != gfx::DeviceManagerDx::Get()->GetImageDevice()) { + return false; + } + + // Test to see if the keyed mutex has been released. + // If D3D11Texture failed to lock, do not recycle the DXGIYCbCrTextureData. + + AutoCheckLockD3D11Texture lockY(textureY); + AutoCheckLockD3D11Texture lockCr(textureCr); + AutoCheckLockD3D11Texture lockCb(textureCb); + + if (!lockY.IsLocked() || !lockCr.IsLocked() || !lockCb.IsLocked()) { + return false; + } + + return true; +} + +already_AddRefed DXGIYCbCrTextureAllocationHelper::Allocate( + KnowsCompositor* aAllocator) { + CD3D11_TEXTURE2D_DESC newDesc(mData.mColorDepth == gfx::ColorDepth::COLOR_8 + ? DXGI_FORMAT_R8_UNORM + : DXGI_FORMAT_R16_UNORM, + mData.mYSize.width, mData.mYSize.height, 1, 1); + // WebRender requests keyed mutex + if (mDevice == gfx::DeviceManagerDx::Get()->GetCompositorDevice() && + !gfxVars::UseWebRender()) { + newDesc.MiscFlags = D3D11_RESOURCE_MISC_SHARED; + } else { + newDesc.MiscFlags = D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX; + } + + RefPtr mt; + HRESULT hr = mDevice->QueryInterface((ID3D10Multithread**)getter_AddRefs(mt)); + + if (FAILED(hr) || !mt) { + gfxCriticalError() << "Multithread safety interface not supported. " << hr; + return nullptr; + } + + if (!mt->GetMultithreadProtected()) { + gfxCriticalError() << "Device used not marked as multithread-safe."; + return nullptr; + } + + D3D11MTAutoEnter mtAutoEnter(mt.forget()); + + RefPtr textureY; + hr = mDevice->CreateTexture2D(&newDesc, nullptr, getter_AddRefs(textureY)); + NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr); + + newDesc.Width = mData.mCbCrSize.width; + newDesc.Height = mData.mCbCrSize.height; + + RefPtr textureCb; + hr = mDevice->CreateTexture2D(&newDesc, nullptr, getter_AddRefs(textureCb)); + NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr); + + RefPtr textureCr; + hr = mDevice->CreateTexture2D(&newDesc, nullptr, getter_AddRefs(textureCr)); + NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr); + + TextureForwarder* forwarder = + aAllocator ? aAllocator->GetTextureForwarder() : nullptr; + + return TextureClient::CreateWithData( + DXGIYCbCrTextureData::Create(textureY, textureCb, textureCr, + mData.mPicSize, mData.mYSize, + mData.mCbCrSize, mData.mColorDepth, + mData.mYUVColorSpace, mData.mColorRange), + mTextureFlags, forwarder); +} + +already_AddRefed D3D11YCbCrRecycleAllocator::Allocate( + SurfaceFormat aFormat, IntSize aSize, BackendSelector aSelector, + TextureFlags aTextureFlags, TextureAllocationFlags aAllocFlags) { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + return nullptr; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/D3D11YCbCrImage.h b/gfx/layers/D3D11YCbCrImage.h new file mode 100644 index 0000000000..6d92ace79c --- /dev/null +++ b/gfx/layers/D3D11YCbCrImage.h @@ -0,0 +1,88 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_D3D11_YCBCR_IMAGE_H +#define GFX_D3D11_YCBCR_IMAGE_H + +#include "d3d11.h" +#include "mozilla/layers/TextureClientRecycleAllocator.h" +#include "mozilla/Maybe.h" +#include "ImageContainer.h" + +namespace mozilla { +namespace gl { +class GLBlitHelper; +} +namespace layers { + +class ImageContainer; +class DXGIYCbCrTextureClient; +class DXGIYCbCrTextureData; + +class MOZ_RAII DXGIYCbCrTextureAllocationHelper + : public ITextureClientAllocationHelper { + public: + DXGIYCbCrTextureAllocationHelper(const PlanarYCbCrData& aData, + TextureFlags aTextureFlags, + ID3D11Device* aDevice); + + bool IsCompatible(TextureClient* aTextureClient) override; + + already_AddRefed Allocate( + KnowsCompositor* aAllocator) override; + + protected: + const PlanarYCbCrData& mData; + RefPtr mDevice; +}; + +class D3D11YCbCrRecycleAllocator : public TextureClientRecycleAllocator { + public: + explicit D3D11YCbCrRecycleAllocator(KnowsCompositor* aKnowsCompositor) + : TextureClientRecycleAllocator(aKnowsCompositor) {} + + protected: + already_AddRefed Allocate( + gfx::SurfaceFormat aFormat, gfx::IntSize aSize, BackendSelector aSelector, + TextureFlags aTextureFlags, TextureAllocationFlags aAllocFlags) override; +}; + +class D3D11YCbCrImage : public Image { + friend class gl::GLBlitHelper; + + public: + D3D11YCbCrImage(); + virtual ~D3D11YCbCrImage(); + + // Copies the surface into a sharable texture's surface, and initializes + // the image. + bool SetData(KnowsCompositor* aAllocator, ImageContainer* aContainer, + const PlanarYCbCrData& aData); + + gfx::IntSize GetSize() const override; + + already_AddRefed GetAsSourceSurface() override; + + TextureClient* GetTextureClient(KnowsCompositor* aKnowsCompositor) override; + + gfx::IntRect GetPictureRect() const override { return mPictureRect; } + + private: + const DXGIYCbCrTextureData* GetData() const; + + gfx::IntSize mYSize; + gfx::IntSize mCbCrSize; + gfx::IntRect mPictureRect; + gfx::ColorDepth mColorDepth; + gfx::YUVColorSpace mColorSpace; + gfx::ColorRange mColorRange; + RefPtr mTextureClient; +}; + +} // namespace layers +} // namespace mozilla + +#endif // GFX_D3D11_YCBCR_IMAGE_H diff --git a/gfx/layers/D3D9SurfaceImage.cpp b/gfx/layers/D3D9SurfaceImage.cpp new file mode 100644 index 0000000000..a88c002117 --- /dev/null +++ b/gfx/layers/D3D9SurfaceImage.cpp @@ -0,0 +1,267 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "D3D9SurfaceImage.h" +#include "gfx2DGlue.h" +#include "gfxWindowsPlatform.h" +#include "mozilla/layers/CompositableClient.h" +#include "mozilla/layers/CompositableForwarder.h" +#include "mozilla/layers/ImageBridgeChild.h" +#include "mozilla/gfx/Types.h" + +namespace mozilla { +namespace layers { + +DXGID3D9TextureData::DXGID3D9TextureData(gfx::SurfaceFormat aFormat, + IDirect3DTexture9* aTexture, + HANDLE aHandle, + IDirect3DDevice9* aDevice) + : mDevice(aDevice), mTexture(aTexture), mFormat(aFormat), mHandle(aHandle) { + MOZ_COUNT_CTOR(DXGID3D9TextureData); +} + +DXGID3D9TextureData::~DXGID3D9TextureData() { + gfxWindowsPlatform::sD3D9SharedTextures -= mDesc.Width * mDesc.Height * 4; + MOZ_COUNT_DTOR(DXGID3D9TextureData); +} + +// static +DXGID3D9TextureData* DXGID3D9TextureData::Create(gfx::IntSize aSize, + gfx::SurfaceFormat aFormat, + TextureFlags aFlags, + IDirect3DDevice9* aDevice) { + AUTO_PROFILER_LABEL("DXGID3D9TextureData::Create", GRAPHICS); + MOZ_ASSERT(aFormat == gfx::SurfaceFormat::B8G8R8A8); + if (aFormat != gfx::SurfaceFormat::B8G8R8A8) { + return nullptr; + } + + RefPtr texture; + HANDLE shareHandle = nullptr; + HRESULT hr = aDevice->CreateTexture( + aSize.width, aSize.height, 1, D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8, + D3DPOOL_DEFAULT, getter_AddRefs(texture), &shareHandle); + if (FAILED(hr) || !shareHandle) { + return nullptr; + } + + D3DSURFACE_DESC surfaceDesc; + hr = texture->GetLevelDesc(0, &surfaceDesc); + if (FAILED(hr)) { + return nullptr; + } + DXGID3D9TextureData* data = + new DXGID3D9TextureData(aFormat, texture, shareHandle, aDevice); + data->mDesc = surfaceDesc; + + gfxWindowsPlatform::sD3D9SharedTextures += aSize.width * aSize.height * 4; + return data; +} + +void DXGID3D9TextureData::FillInfo(TextureData::Info& aInfo) const { + aInfo.size = GetSize(); + aInfo.format = mFormat; + aInfo.supportsMoz2D = false; + aInfo.canExposeMappedData = false; + aInfo.hasIntermediateBuffer = false; + aInfo.hasSynchronization = false; +} + +already_AddRefed DXGID3D9TextureData::GetD3D9Surface() + const { + RefPtr textureSurface; + HRESULT hr = mTexture->GetSurfaceLevel(0, getter_AddRefs(textureSurface)); + NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr); + + return textureSurface.forget(); +} + +bool DXGID3D9TextureData::Serialize(SurfaceDescriptor& aOutDescriptor) { + // In reality, with D3D9 we will only ever deal with RGBA textures. + bool isYUV = mFormat == SurfaceFormat::NV12 || + mFormat == SurfaceFormat::P010 || mFormat == SurfaceFormat::P016; + aOutDescriptor = SurfaceDescriptorD3D10( + (WindowsHandle)(mHandle), mFormat, GetSize(), + isYUV ? gfx::YUVColorSpace::BT601 : gfx::YUVColorSpace::UNKNOWN, + gfx::ColorRange::LIMITED); + return true; +} + +D3D9SurfaceImage::D3D9SurfaceImage() + : Image(nullptr, ImageFormat::D3D9_RGB32_TEXTURE), + mSize(0, 0), + mShareHandle(0), + mValid(true) {} + +D3D9SurfaceImage::~D3D9SurfaceImage() {} + +HRESULT +D3D9SurfaceImage::AllocateAndCopy(D3D9RecycleAllocator* aAllocator, + IDirect3DSurface9* aSurface, + const gfx::IntRect& aRegion) { + NS_ENSURE_TRUE(aSurface, E_POINTER); + HRESULT hr; + RefPtr surface = aSurface; + + RefPtr device; + hr = surface->GetDevice(getter_AddRefs(device)); + NS_ENSURE_TRUE(SUCCEEDED(hr), E_FAIL); + + RefPtr d3d9; + hr = device->GetDirect3D(getter_AddRefs(d3d9)); + NS_ENSURE_TRUE(SUCCEEDED(hr), E_FAIL); + + D3DSURFACE_DESC desc; + surface->GetDesc(&desc); + // Ensure we can convert the textures format to RGB conversion + // in StretchRect. Fail if we can't. + hr = d3d9->CheckDeviceFormatConversion(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, + desc.Format, D3DFMT_A8R8G8B8); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + // DXVA surfaces aren't created sharable, so we need to copy the surface + // to a sharable texture to that it's accessible to the layer manager's + // device. + if (aAllocator) { + mTextureClient = aAllocator->CreateOrRecycleClient( + gfx::SurfaceFormat::B8G8R8A8, aRegion.Size()); + if (!mTextureClient) { + return E_FAIL; + } + + DXGID3D9TextureData* texData = + static_cast(mTextureClient->GetInternalData()); + mTexture = texData->GetD3D9Texture(); + mShareHandle = texData->GetShareHandle(); + mDesc = texData->GetDesc(); + } else { + hr = device->CreateTexture(aRegion.Size().width, aRegion.Size().height, 1, + D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8, + D3DPOOL_DEFAULT, getter_AddRefs(mTexture), + &mShareHandle); + if (FAILED(hr) || !mShareHandle) { + return E_FAIL; + } + + hr = mTexture->GetLevelDesc(0, &mDesc); + if (FAILED(hr)) { + return E_FAIL; + } + } + + // Copy the image onto the texture, preforming YUV -> RGB conversion if + // necessary. + RefPtr textureSurface = GetD3D9Surface(); + if (!textureSurface) { + return E_FAIL; + } + + RECT src = {aRegion.X(), aRegion.Y(), aRegion.XMost(), aRegion.YMost()}; + hr = + device->StretchRect(surface, &src, textureSurface, nullptr, D3DTEXF_NONE); + NS_ENSURE_TRUE(SUCCEEDED(hr), hr); + + mSize = aRegion.Size(); + return S_OK; +} + +already_AddRefed D3D9SurfaceImage::GetD3D9Surface() const { + RefPtr textureSurface; + HRESULT hr = mTexture->GetSurfaceLevel(0, getter_AddRefs(textureSurface)); + NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr); + return textureSurface.forget(); +} + +HANDLE +D3D9SurfaceImage::GetShareHandle() const { return mShareHandle; } + +gfx::IntSize D3D9SurfaceImage::GetSize() const { return mSize; } + +TextureClient* D3D9SurfaceImage::GetTextureClient( + KnowsCompositor* aKnowsCompositor) { + MOZ_ASSERT(mTextureClient); + MOZ_ASSERT(mTextureClient->GetAllocator() == + aKnowsCompositor->GetTextureForwarder()); + return mTextureClient; +} + +already_AddRefed D3D9SurfaceImage::GetAsSourceSurface() { + if (!mTexture) { + return nullptr; + } + + HRESULT hr; + RefPtr surface = + gfx::Factory::CreateDataSourceSurface(mSize, + gfx::SurfaceFormat::B8G8R8X8); + if (NS_WARN_IF(!surface)) { + return nullptr; + } + + // Readback the texture from GPU memory into system memory, so that + // we can copy it into the Cairo image. This is expensive. + RefPtr textureSurface = GetD3D9Surface(); + if (!textureSurface) { + return nullptr; + } + + RefPtr device; + hr = textureSurface->GetDevice(getter_AddRefs(device)); + NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr); + + RefPtr systemMemorySurface; + hr = device->CreateOffscreenPlainSurface( + mSize.width, mSize.height, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, + getter_AddRefs(systemMemorySurface), 0); + NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr); + + hr = device->GetRenderTargetData(textureSurface, systemMemorySurface); + NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr); + + D3DLOCKED_RECT rect; + hr = systemMemorySurface->LockRect(&rect, nullptr, 0); + NS_ENSURE_TRUE(SUCCEEDED(hr), nullptr); + + gfx::DataSourceSurface::MappedSurface mappedSurface; + if (!surface->Map(gfx::DataSourceSurface::WRITE, &mappedSurface)) { + systemMemorySurface->UnlockRect(); + return nullptr; + } + + const unsigned char* src = (const unsigned char*)(rect.pBits); + const unsigned srcPitch = rect.Pitch; + for (int y = 0; y < mSize.height; y++) { + memcpy(mappedSurface.mData + mappedSurface.mStride * y, + (unsigned char*)(src) + srcPitch * y, mSize.width * 4); + } + + systemMemorySurface->UnlockRect(); + surface->Unmap(); + + return surface.forget(); +} + +already_AddRefed D3D9RecycleAllocator::Allocate( + gfx::SurfaceFormat aFormat, gfx::IntSize aSize, BackendSelector aSelector, + TextureFlags aTextureFlags, TextureAllocationFlags aAllocFlags) { + TextureData* data = + DXGID3D9TextureData::Create(aSize, aFormat, aTextureFlags, mDevice); + if (!data) { + return nullptr; + } + + return MakeAndAddRef(data, aTextureFlags, + mKnowsCompositor->GetTextureForwarder()); +} + +already_AddRefed D3D9RecycleAllocator::CreateOrRecycleClient( + gfx::SurfaceFormat aFormat, const gfx::IntSize& aSize) { + return CreateOrRecycle(aFormat, aSize, BackendSelector::Content, + TextureFlags::DEFAULT); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/D3D9SurfaceImage.h b/gfx/layers/D3D9SurfaceImage.h new file mode 100644 index 0000000000..183d5bb585 --- /dev/null +++ b/gfx/layers/D3D9SurfaceImage.h @@ -0,0 +1,122 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_D3DSURFACEIMAGE_H +#define GFX_D3DSURFACEIMAGE_H + +#include "mozilla/RefPtr.h" +#include "ImageContainer.h" +#include "d3d9.h" +#include "mozilla/layers/TextureClientRecycleAllocator.h" + +namespace mozilla { +namespace layers { + +class TextureClient; + +class D3D9RecycleAllocator : public TextureClientRecycleAllocator { + public: + D3D9RecycleAllocator(KnowsCompositor* aAllocator, IDirect3DDevice9* aDevice) + : TextureClientRecycleAllocator(aAllocator), mDevice(aDevice) {} + + already_AddRefed CreateOrRecycleClient( + gfx::SurfaceFormat aFormat, const gfx::IntSize& aSize); + + protected: + already_AddRefed Allocate( + gfx::SurfaceFormat aFormat, gfx::IntSize aSize, BackendSelector aSelector, + TextureFlags aTextureFlags, TextureAllocationFlags aAllocFlags) override; + + RefPtr mDevice; +}; + +/** + * Wraps a D3D9 texture, shared with the compositor though DXGI. + * At the moment it is only used with D3D11 compositing, and the corresponding + * TextureHost is DXGITextureHostD3D11. + */ +class DXGID3D9TextureData : public TextureData { + public: + static DXGID3D9TextureData* Create(gfx::IntSize aSize, + gfx::SurfaceFormat aFormat, + TextureFlags aFlags, + IDirect3DDevice9* aDevice); + + virtual ~DXGID3D9TextureData(); + + void FillInfo(TextureData::Info& aInfo) const override; + + bool Lock(OpenMode) override { return true; } + + void Unlock() override {} + + bool Serialize(SurfaceDescriptor& aOutDescriptor) override; + + void Deallocate(LayersIPCChannel* aAllocator) override {} + + IDirect3DDevice9* GetD3D9Device() { return mDevice; } + IDirect3DTexture9* GetD3D9Texture() { return mTexture; } + HANDLE GetShareHandle() const { return mHandle; } + already_AddRefed GetD3D9Surface() const; + + const D3DSURFACE_DESC& GetDesc() const { return mDesc; } + + gfx::IntSize GetSize() const { + return gfx::IntSize(mDesc.Width, mDesc.Height); + } + + protected: + DXGID3D9TextureData(gfx::SurfaceFormat aFormat, IDirect3DTexture9* aTexture, + HANDLE aHandle, IDirect3DDevice9* aDevice); + + RefPtr mDevice; + RefPtr mTexture; + gfx::SurfaceFormat mFormat; + HANDLE mHandle; + D3DSURFACE_DESC mDesc; +}; + +// Image class that wraps a IDirect3DSurface9. This class copies the image +// passed into SetData(), so that it can be accessed from other D3D devices. +// This class also manages the synchronization of the copy, to ensure the +// resource is ready to use. +class D3D9SurfaceImage : public Image { + public: + D3D9SurfaceImage(); + virtual ~D3D9SurfaceImage(); + + HRESULT AllocateAndCopy(D3D9RecycleAllocator* aAllocator, + IDirect3DSurface9* aSurface, + const gfx::IntRect& aRegion); + + // Returns the description of the shared surface. + gfx::IntSize GetSize() const override; + + already_AddRefed GetAsSourceSurface() override; + + TextureClient* GetTextureClient(KnowsCompositor* aKnowsCompositor) override; + + already_AddRefed GetD3D9Surface() const; + + HANDLE GetShareHandle() const; + + bool IsValid() const override { return mValid; } + + void Invalidate() { mValid = false; } + + private: + gfx::IntSize mSize; + RefPtr mTextureClient; + RefPtr mTexture; + HANDLE mShareHandle; + D3DSURFACE_DESC mDesc; + bool mValid; +}; + +} // namespace layers +} // namespace mozilla + +#endif // GFX_D3DSURFACEIMAGE_H diff --git a/gfx/layers/DMABUFSurfaceImage.cpp b/gfx/layers/DMABUFSurfaceImage.cpp new file mode 100644 index 0000000000..86e37fb999 --- /dev/null +++ b/gfx/layers/DMABUFSurfaceImage.cpp @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DMABUFSurfaceImage.h" +#include "mozilla/widget/DMABufSurface.h" +#include "mozilla/layers/CompositableClient.h" +#include "mozilla/layers/CompositableForwarder.h" +#include "mozilla/layers/DMABUFTextureClientOGL.h" +#include "mozilla/layers/TextureForwarder.h" + +using namespace mozilla; +using namespace mozilla::layers; +using namespace mozilla::gfx; + +DMABUFSurfaceImage::DMABUFSurfaceImage(DMABufSurface* aSurface) + : Image(nullptr, ImageFormat::DMABUF), mSurface(aSurface) { + mSurface->GlobalRefAdd(); +} + +DMABUFSurfaceImage::~DMABUFSurfaceImage() { mSurface->GlobalRefRelease(); } + +TextureClient* DMABUFSurfaceImage::GetTextureClient( + KnowsCompositor* aKnowsCompositor) { + if (!mTextureClient) { + BackendType backend = BackendType::NONE; + mTextureClient = TextureClient::CreateWithData( + DMABUFTextureData::Create(mSurface, backend), TextureFlags::DEFAULT, + aKnowsCompositor->GetTextureForwarder()); + } + return mTextureClient; +} + +gfx::IntSize DMABUFSurfaceImage::GetSize() const { + return gfx::IntSize::Truncate(mSurface->GetWidth(), mSurface->GetHeight()); +} diff --git a/gfx/layers/DMABUFSurfaceImage.h b/gfx/layers/DMABUFSurfaceImage.h new file mode 100644 index 0000000000..93fa8bb868 --- /dev/null +++ b/gfx/layers/DMABUFSurfaceImage.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef SURFACE_DMABUF_H +#define SURFACE_DMABUF_H + +#include "ImageContainer.h" + +class DMABufSurface; + +namespace mozilla { +namespace layers { + +class TextureClient; + +class DMABUFSurfaceImage : public Image { + public: + explicit DMABUFSurfaceImage(DMABufSurface* aSurface); + ~DMABUFSurfaceImage(); + + DMABufSurface* GetSurface() { return mSurface; } + gfx::IntSize GetSize() const override; + already_AddRefed GetAsSourceSurface() override { + return nullptr; + } + TextureClient* GetTextureClient(KnowsCompositor* aKnowsCompositor) override; + + private: + RefPtr mSurface; + RefPtr mTextureClient; +}; + +} // namespace layers +} // namespace mozilla + +#endif // SURFACE_DMABUF_H diff --git a/gfx/layers/DirectedGraph.h b/gfx/layers/DirectedGraph.h new file mode 100644 index 0000000000..1891b14355 --- /dev/null +++ b/gfx/layers/DirectedGraph.h @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_DIRECTEDGRAPH_H +#define GFX_DIRECTEDGRAPH_H + +#include "gfxTypes.h" +#include "nsTArray.h" + +namespace mozilla { +namespace layers { + +template +class DirectedGraph { + public: + class Edge { + public: + Edge(T aFrom, T aTo) : mFrom(aFrom), mTo(aTo) {} + + bool operator==(const Edge& aOther) const { + return mFrom == aOther.mFrom && mTo == aOther.mTo; + } + + T mFrom; + T mTo; + }; + + class RemoveEdgesToComparator { + public: + bool Equals(const Edge& a, T const& b) const { return a.mTo == b; } + }; + + /** + * Add a new edge to the graph. + */ + void AddEdge(Edge aEdge) { + NS_ASSERTION(!mEdges.Contains(aEdge), "Adding a duplicate edge!"); + mEdges.AppendElement(aEdge); + } + + void AddEdge(T aFrom, T aTo) { AddEdge(Edge(aFrom, aTo)); } + + /** + * Get the list of edges. + */ + const nsTArray& GetEdgeList() const { return mEdges; } + + /** + * Remove the given edge from the graph. + */ + void RemoveEdge(Edge aEdge) { mEdges.RemoveElement(aEdge); } + + /** + * Remove all edges going into aNode. + */ + void RemoveEdgesTo(T aNode) { + RemoveEdgesToComparator c; + while (mEdges.RemoveElement(aNode, c)) { + } + } + + /** + * Get the number of edges going into aNode. + */ + unsigned int NumEdgesTo(T aNode) { + unsigned int count = 0; + for (unsigned int i = 0; i < mEdges.Length(); i++) { + if (mEdges.ElementAt(i).mTo == aNode) { + count++; + } + } + return count; + } + + /** + * Get the list of all edges going from aNode + */ + void GetEdgesFrom(T aNode, nsTArray& aResult) { + for (unsigned int i = 0; i < mEdges.Length(); i++) { + if (mEdges.ElementAt(i).mFrom == aNode) { + aResult.AppendElement(mEdges.ElementAt(i)); + } + } + } + + /** + * Get the total number of edges. + */ + unsigned int GetEdgeCount() { return mEdges.Length(); } + + private: + nsTArray mEdges; +}; + +} // namespace layers +} // namespace mozilla + +#endif // GFX_DIRECTEDGRAPH_H diff --git a/gfx/layers/DirectionUtils.h b/gfx/layers/DirectionUtils.h new file mode 100644 index 0000000000..84d9cb6969 --- /dev/null +++ b/gfx/layers/DirectionUtils.h @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_DIRECTIONUTILS_H +#define GFX_DIRECTIONUTILS_H + +#include "LayersTypes.h" // for ScrollDirection +#include "Units.h" // for Coord, Point, and Rect types + +namespace mozilla { +namespace layers { + +template +CoordOf GetAxisStart(ScrollDirection aDir, + const PointOrRect& aValue) { + if (aDir == ScrollDirection::eHorizontal) { + return aValue.X(); + } else { + return aValue.Y(); + } +} + +template +CoordOf GetAxisEnd(ScrollDirection aDir, const Rect& aValue) { + if (aDir == ScrollDirection::eHorizontal) { + return aValue.XMost(); + } else { + return aValue.YMost(); + } +} + +template +CoordOf GetAxisLength(ScrollDirection aDir, const Rect& aValue) { + if (aDir == ScrollDirection::eHorizontal) { + return aValue.Width(); + } else { + return aValue.Height(); + } +} + +template +float GetAxisScale(ScrollDirection aDir, + const gfx::ScaleFactors2D& aValue) { + if (aDir == ScrollDirection::eHorizontal) { + return aValue.xScale; + } else { + return aValue.yScale; + } +} + +inline ScrollDirection GetPerpendicularDirection(ScrollDirection aDir) { + return aDir == ScrollDirection::eHorizontal ? ScrollDirection::eVertical + : ScrollDirection::eHorizontal; +} + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_DIRECTIONUTILS_H */ diff --git a/gfx/layers/Effects.cpp b/gfx/layers/Effects.cpp new file mode 100644 index 0000000000..2f21feb3ea --- /dev/null +++ b/gfx/layers/Effects.cpp @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Effects.h" +#include "nsAString.h" +#include "nsPrintfCString.h" // for nsPrintfCString +#include "nsString.h" // for nsAutoCString + +using namespace mozilla::layers; + +void TexturedEffect::PrintInfo(std::stringstream& aStream, + const char* aPrefix) { + aStream << aPrefix; + aStream << nsPrintfCString("%s (0x%p)", Name(), this).get() + << " [texture-coords=" << mTextureCoords << "]"; + + if (mPremultiplied) { + aStream << " [premultiplied]"; + } else { + aStream << " [not-premultiplied]"; + } + + aStream << " [filter=" << mSamplingFilter << "]"; +} + +void EffectMask::PrintInfo(std::stringstream& aStream, const char* aPrefix) { + aStream << aPrefix << nsPrintfCString("EffectMask (0x%p)", this).get() + << " [size=" << mSize << "]" + << " [mask-transform=" << mMaskTransform << "]"; +} + +void EffectRenderTarget::PrintInfo(std::stringstream& aStream, + const char* aPrefix) { + TexturedEffect::PrintInfo(aStream, aPrefix); + aStream << nsPrintfCString(" [render-target=%p]", mRenderTarget.get()).get(); +} + +void EffectSolidColor::PrintInfo(std::stringstream& aStream, + const char* aPrefix) { + aStream << aPrefix; + aStream << nsPrintfCString("EffectSolidColor (0x%p) [color=%x]", this, + mColor.ToABGR()) + .get(); +} + +void EffectBlendMode::PrintInfo(std::stringstream& aStream, + const char* aPrefix) { + aStream << aPrefix; + aStream << nsPrintfCString("EffectBlendMode (0x%p) [blendmode=%i]", this, + (int)mBlendMode) + .get(); +} + +void EffectColorMatrix::PrintInfo(std::stringstream& aStream, + const char* aPrefix) { + aStream << aPrefix; + aStream << nsPrintfCString("EffectColorMatrix (0x%p)", this).get() + << " [matrix=" << mColorMatrix << "]"; +} diff --git a/gfx/layers/Effects.h b/gfx/layers/Effects.h new file mode 100644 index 0000000000..b138fefcc7 --- /dev/null +++ b/gfx/layers/Effects.h @@ -0,0 +1,303 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_LAYERS_EFFECTS_H +#define MOZILLA_LAYERS_EFFECTS_H + +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/RefPtr.h" // for RefPtr, already_AddRefed, etc +#include "mozilla/gfx/Matrix.h" // for Matrix4x4 +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/gfx/Rect.h" // for Rect +#include "mozilla/gfx/Types.h" // for SamplingFilter, etc +#include "mozilla/layers/CompositorTypes.h" // for EffectTypes, etc +#include "mozilla/layers/TextureHost.h" // for CompositingRenderTarget, etc +#include "mozilla/mozalloc.h" // for operator delete, etc +#include "nscore.h" // for nsACString +#include "mozilla/EnumeratedArray.h" + +namespace mozilla { +namespace layers { + +/** + * Effects and effect chains are used by the compositor API (see Compositor.h). + * An effect chain represents a rendering method, for example some shader and + * the data required for that shader to run. An effect is some component of the + * chain and its data. + * + * An effect chain consists of a primary effect - how the 'texture' memory + * should be interpreted (RGBA, BGRX, YCBCR, etc.) - and any number of secondary + * effects + * - any way in which rendering can be changed, e.g., applying a mask layer. + * + * During the rendering process, an effect chain is created by the layer being + * rendered and the primary effect is added by the compositable host. Secondary + * effects may be added by the layer or compositable. The effect chain is passed + * to the compositor by the compositable host as a parameter to DrawQuad. + */ + +struct TexturedEffect; + +struct Effect { + NS_INLINE_DECL_REFCOUNTING(Effect) + + explicit Effect(EffectTypes aType) : mType(aType) {} + + EffectTypes mType; + + virtual TexturedEffect* AsTexturedEffect() { return nullptr; } + virtual void PrintInfo(std::stringstream& aStream, const char* aPrefix) = 0; + + protected: + virtual ~Effect() = default; +}; + +// Render from a texture +struct TexturedEffect : public Effect { + TexturedEffect(EffectTypes aType, TextureSource* aTexture, + bool aPremultiplied, gfx::SamplingFilter aSamplingFilter) + : Effect(aType), + mTextureCoords(0, 0, 1.0f, 1.0f), + mTexture(aTexture), + mPremultiplied(aPremultiplied), + mSamplingFilter(aSamplingFilter) {} + + TexturedEffect* AsTexturedEffect() override { return this; } + virtual const char* Name() = 0; + void PrintInfo(std::stringstream& aStream, const char* aPrefix) override; + + gfx::Rect mTextureCoords; + TextureSource* mTexture; + bool mPremultiplied; + gfx::SamplingFilter mSamplingFilter; +}; + +// Support an alpha mask. +struct EffectMask : public Effect { + EffectMask(TextureSource* aMaskTexture, gfx::IntSize aSize, + const gfx::Matrix4x4& aMaskTransform) + : Effect(EffectTypes::MASK), + mMaskTexture(aMaskTexture), + mSize(aSize), + mMaskTransform(aMaskTransform) {} + + void PrintInfo(std::stringstream& aStream, const char* aPrefix) override; + + TextureSource* mMaskTexture; + gfx::IntSize mSize; + gfx::Matrix4x4 mMaskTransform; +}; + +struct EffectBlendMode : public Effect { + explicit EffectBlendMode(gfx::CompositionOp aBlendMode) + : Effect(EffectTypes::BLEND_MODE), mBlendMode(aBlendMode) {} + + virtual const char* Name() { return "EffectBlendMode"; } + void PrintInfo(std::stringstream& aStream, const char* aPrefix) override; + + gfx::CompositionOp mBlendMode; +}; + +// Render to a render target rather than the screen. +struct EffectRenderTarget : public TexturedEffect { + explicit EffectRenderTarget(CompositingRenderTarget* aRenderTarget) + : TexturedEffect(EffectTypes::RENDER_TARGET, aRenderTarget, true, + gfx::SamplingFilter::LINEAR), + mRenderTarget(aRenderTarget) {} + + const char* Name() override { return "EffectRenderTarget"; } + void PrintInfo(std::stringstream& aStream, const char* aPrefix) override; + + RefPtr mRenderTarget; + + protected: + EffectRenderTarget(EffectTypes aType, CompositingRenderTarget* aRenderTarget) + : TexturedEffect(aType, aRenderTarget, true, gfx::SamplingFilter::LINEAR), + mRenderTarget(aRenderTarget) {} +}; + +// Render to a render target rather than the screen. +struct EffectColorMatrix : public Effect { + explicit EffectColorMatrix(gfx::Matrix5x4 aMatrix) + : Effect(EffectTypes::COLOR_MATRIX), mColorMatrix(aMatrix) {} + + void PrintInfo(std::stringstream& aStream, const char* aPrefix) override; + const gfx::Matrix5x4 mColorMatrix; +}; + +struct EffectRGB : public TexturedEffect { + EffectRGB(TextureSource* aTexture, bool aPremultiplied, + gfx::SamplingFilter aSamplingFilter, bool aFlipped = false) + : TexturedEffect(EffectTypes::RGB, aTexture, aPremultiplied, + aSamplingFilter) {} + + const char* Name() override { return "EffectRGB"; } +}; + +struct EffectYCbCr : public TexturedEffect { + EffectYCbCr(TextureSource* aSource, gfx::YUVColorSpace aYUVColorSpace, + gfx::ColorRange aColorRange, gfx::ColorDepth aColorDepth, + gfx::SamplingFilter aSamplingFilter) + : TexturedEffect(EffectTypes::YCBCR, aSource, false, aSamplingFilter), + mYUVColorSpace(aYUVColorSpace), + mColorRange(aColorRange), + mColorDepth(aColorDepth) {} + + const char* Name() override { return "EffectYCbCr"; } + + gfx::YUVColorSpace mYUVColorSpace; + gfx::ColorRange mColorRange; + gfx::ColorDepth mColorDepth; +}; + +struct EffectNV12 : public EffectYCbCr { + EffectNV12(TextureSource* aSource, gfx::YUVColorSpace aYUVColorSpace, + gfx::ColorRange aColorRange, gfx::ColorDepth aColorDepth, + gfx::SamplingFilter aSamplingFilter) + : EffectYCbCr(aSource, aYUVColorSpace, aColorRange, aColorDepth, + aSamplingFilter) { + mType = EffectTypes::NV12; + } + + const char* Name() override { return "EffectNV12"; } +}; + +struct EffectComponentAlpha : public TexturedEffect { + EffectComponentAlpha(TextureSource* aOnBlack, TextureSource* aOnWhite, + gfx::SamplingFilter aSamplingFilter) + : TexturedEffect(EffectTypes::COMPONENT_ALPHA, nullptr, false, + aSamplingFilter), + mOnBlack(aOnBlack), + mOnWhite(aOnWhite) {} + + const char* Name() override { return "EffectComponentAlpha"; } + + TextureSource* mOnBlack; + TextureSource* mOnWhite; +}; + +struct EffectSolidColor : public Effect { + explicit EffectSolidColor(const gfx::DeviceColor& aColor) + : Effect(EffectTypes::SOLID_COLOR), mColor(aColor) {} + + void PrintInfo(std::stringstream& aStream, const char* aPrefix) override; + + gfx::DeviceColor mColor; +}; + +struct EffectChain { + EffectChain() : mLayerRef(nullptr) {} + explicit EffectChain(void* aLayerRef) : mLayerRef(aLayerRef) {} + + RefPtr mPrimaryEffect; + EnumeratedArray> + mSecondaryEffects; + void* mLayerRef; //!< For LayerScope logging +}; + +/** + * Create a Textured effect corresponding to aFormat and using + * aSource as the (first) texture source. + * + * Note that aFormat can be different form aSource->GetFormat if, we are + * creating an effect that takes several texture sources (like with YCBCR + * where aFormat would be FORMAT_YCBCR and each texture source would be + * a one-channel A8 texture) + */ +inline already_AddRefed CreateTexturedEffect( + gfx::SurfaceFormat aFormat, TextureSource* aSource, + const gfx::SamplingFilter aSamplingFilter, bool isAlphaPremultiplied) { + MOZ_ASSERT(aSource); + RefPtr result; + switch (aFormat) { + case gfx::SurfaceFormat::B8G8R8A8: + case gfx::SurfaceFormat::B8G8R8X8: + case gfx::SurfaceFormat::R8G8B8X8: + case gfx::SurfaceFormat::R5G6B5_UINT16: + case gfx::SurfaceFormat::R8G8B8A8: + result = new EffectRGB(aSource, isAlphaPremultiplied, aSamplingFilter); + break; + case gfx::SurfaceFormat::YUV: + case gfx::SurfaceFormat::NV12: + case gfx::SurfaceFormat::P010: + case gfx::SurfaceFormat::P016: + MOZ_ASSERT_UNREACHABLE( + "gfx::SurfaceFormat::YUV/NV12/P010/P016 is invalid"); + break; + default: + NS_WARNING("unhandled program type"); + break; + } + + return result.forget(); +} + +inline already_AddRefed CreateTexturedEffect( + TextureHost* aHost, TextureSource* aSource, + const gfx::SamplingFilter aSamplingFilter, bool isAlphaPremultiplied) { + MOZ_ASSERT(aHost); + MOZ_ASSERT(aSource); + + RefPtr result; + + switch (aHost->GetReadFormat()) { + case gfx::SurfaceFormat::YUV: + MOZ_ASSERT(aHost->GetYUVColorSpace() != gfx::YUVColorSpace::UNKNOWN); + result = new EffectYCbCr(aSource, aHost->GetYUVColorSpace(), + aHost->GetColorRange(), aHost->GetColorDepth(), + aSamplingFilter); + break; + case gfx::SurfaceFormat::NV12: + case gfx::SurfaceFormat::P010: + case gfx::SurfaceFormat::P016: + result = new EffectNV12(aSource, aHost->GetYUVColorSpace(), + aHost->GetColorRange(), aHost->GetColorDepth(), + aSamplingFilter); + break; + default: + result = CreateTexturedEffect(aHost->GetReadFormat(), aSource, + aSamplingFilter, isAlphaPremultiplied); + break; + } + return result.forget(); +} + +/** + * Create a textured effect based on aSource format and the presence of + * aSourceOnWhite. + * + * aSourceOnWhite can be null. + */ +inline already_AddRefed CreateTexturedEffect( + TextureSource* aSource, TextureSource* aSourceOnWhite, + const gfx::SamplingFilter aSamplingFilter, bool isAlphaPremultiplied) { + MOZ_ASSERT(aSource); + if (aSourceOnWhite) { + MOZ_ASSERT(aSource->GetFormat() == gfx::SurfaceFormat::R8G8B8X8 || + aSource->GetFormat() == gfx::SurfaceFormat::B8G8R8X8); + MOZ_ASSERT(aSource->GetFormat() == aSourceOnWhite->GetFormat()); + return MakeAndAddRef(aSource, aSourceOnWhite, + aSamplingFilter); + } + + return CreateTexturedEffect(aSource->GetFormat(), aSource, aSamplingFilter, + isAlphaPremultiplied); +} + +/** + * Create a textured effect based on aSource format. + * + * This version excudes the possibility of component alpha. + */ +inline already_AddRefed CreateTexturedEffect( + TextureSource* aTexture, const gfx::SamplingFilter aSamplingFilter) { + return CreateTexturedEffect(aTexture, nullptr, aSamplingFilter, true); +} + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/FrameMetrics.cpp b/gfx/layers/FrameMetrics.cpp new file mode 100644 index 0000000000..b433a754f8 --- /dev/null +++ b/gfx/layers/FrameMetrics.cpp @@ -0,0 +1,313 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FrameMetrics.h" + +#include + +#include "gfxUtils.h" +#include "nsStyleConsts.h" +#include "nsStyleStruct.h" +#include "mozilla/WritingModes.h" +#include "mozilla/gfx/Types.h" + +namespace mozilla { +namespace layers { + +const ScrollableLayerGuid::ViewID ScrollableLayerGuid::NULL_SCROLL_ID = 0; + +std::ostream& operator<<(std::ostream& aStream, const FrameMetrics& aMetrics) { + aStream << "{ [cb=" << aMetrics.GetCompositionBounds() + << "] [sr=" << aMetrics.GetScrollableRect() + << "] [s=" << aMetrics.GetVisualScrollOffset(); + if (aMetrics.GetVisualScrollUpdateType() != FrameMetrics::eNone) { + aStream << "] [vd=" << aMetrics.GetVisualDestination(); + } + aStream << "] [dp=" << aMetrics.GetDisplayPort() + << "] [cdp=" << aMetrics.GetCriticalDisplayPort() + << "] [rcs=" << aMetrics.GetRootCompositionSize() + << "] [v=" << aMetrics.GetLayoutViewport() + << nsPrintfCString("] [z=(ld=%.3f r=%.3f", + aMetrics.GetDevPixelsPerCSSPixel().scale, + aMetrics.GetPresShellResolution()) + .get() + << " cr=" << aMetrics.GetCumulativeResolution() + << " z=" << aMetrics.GetZoom() + << " er=" << aMetrics.GetExtraResolution() << " )] [u=(" + << (int)aMetrics.GetVisualScrollUpdateType() << " " + << aMetrics.GetScrollGeneration() + << ")] scrollId=" << aMetrics.GetScrollId(); + if (aMetrics.IsRootContent()) { + aStream << " [rcd]"; + } + aStream << " }"; + return aStream; +} + +void FrameMetrics::RecalculateLayoutViewportOffset() { + // For subframes, the visual and layout viewports coincide, so just + // keep the layout viewport offset in sync with the visual one. + if (!mIsRootContent) { + mLayoutViewport.MoveTo(GetVisualScrollOffset()); + return; + } + // For the root, the two viewports can diverge, but the layout + // viewport needs to keep enclosing the visual viewport. + KeepLayoutViewportEnclosingVisualViewport(GetVisualViewport(), + mScrollableRect, mLayoutViewport); +} + +/* static */ +void FrameMetrics::KeepLayoutViewportEnclosingVisualViewport( + const CSSRect& aVisualViewport, const CSSRect& aScrollableRect, + CSSRect& aLayoutViewport) { + // If the visual viewport is contained within the layout viewport, we don't + // need to make any adjustments, so we can exit early. + // + // Additionally, if the composition bounds changes (due to an orientation + // change, window resize, etc.), it may take a few frames for aLayoutViewport + // to update and during that time, the visual viewport may be larger than the + // layout viewport. In such situations, we take an early exit if the visual + // viewport contains the layout viewport. + if (aLayoutViewport.Contains(aVisualViewport) || + aVisualViewport.Contains(aLayoutViewport)) { + return; + } + + // If visual viewport size is greater than the layout viewport, move the + // layout viewport such that it remains inside the visual viewport. Otherwise, + // move the layout viewport such that the visual viewport is contained + // inside the layout viewport. + if ((aLayoutViewport.Width() < aVisualViewport.Width() && + !FuzzyEqualsMultiplicative(aLayoutViewport.Width(), + aVisualViewport.Width())) || + (aLayoutViewport.Height() < aVisualViewport.Height() && + !FuzzyEqualsMultiplicative(aLayoutViewport.Height(), + aVisualViewport.Height()))) { + if (aLayoutViewport.X() < aVisualViewport.X()) { + // layout viewport moves right + aLayoutViewport.MoveToX(aVisualViewport.X()); + } else if (aVisualViewport.XMost() < aLayoutViewport.XMost()) { + // layout viewport moves left + aLayoutViewport.MoveByX(aVisualViewport.XMost() - + aLayoutViewport.XMost()); + } + if (aLayoutViewport.Y() < aVisualViewport.Y()) { + // layout viewport moves down + aLayoutViewport.MoveToY(aVisualViewport.Y()); + } else if (aVisualViewport.YMost() < aLayoutViewport.YMost()) { + // layout viewport moves up + aLayoutViewport.MoveByY(aVisualViewport.YMost() - + aLayoutViewport.YMost()); + } + } else { + if (aVisualViewport.X() < aLayoutViewport.X()) { + aLayoutViewport.MoveToX(aVisualViewport.X()); + } else if (aLayoutViewport.XMost() < aVisualViewport.XMost()) { + aLayoutViewport.MoveByX(aVisualViewport.XMost() - + aLayoutViewport.XMost()); + } + if (aVisualViewport.Y() < aLayoutViewport.Y()) { + aLayoutViewport.MoveToY(aVisualViewport.Y()); + } else if (aLayoutViewport.YMost() < aVisualViewport.YMost()) { + aLayoutViewport.MoveByY(aVisualViewport.YMost() - + aLayoutViewport.YMost()); + } + } + + // Regardless of any adjustment above, the layout viewport is not allowed + // to go outside the scrollable rect. + aLayoutViewport = aLayoutViewport.MoveInsideAndClamp(aScrollableRect); +} + +void FrameMetrics::ApplyScrollUpdateFrom(const ScrollPositionUpdate& aUpdate) { + // In applying a main-thread scroll update, try to preserve the relative + // offset between the visual and layout viewports. + CSSPoint relativeOffset = GetVisualScrollOffset() - GetLayoutScrollOffset(); + MOZ_ASSERT(IsRootContent() || relativeOffset == CSSPoint()); + // We need to set the two offsets together, otherwise a subsequent + // RecalculateLayoutViewportOffset() could see divergent layout and + // visual offsets. + SetLayoutScrollOffset(aUpdate.GetDestination()); + ClampAndSetVisualScrollOffset(aUpdate.GetDestination() + relativeOffset); +} + +CSSPoint FrameMetrics::ApplyRelativeScrollUpdateFrom( + const ScrollPositionUpdate& aUpdate) { + MOZ_ASSERT(aUpdate.GetType() == ScrollUpdateType::Relative); + CSSPoint origin = GetVisualScrollOffset(); + CSSPoint delta = (aUpdate.GetDestination() - aUpdate.GetSource()); + ClampAndSetVisualScrollOffset(origin + delta); + return GetVisualScrollOffset() - origin; +} + +CSSPoint FrameMetrics::ApplyPureRelativeScrollUpdateFrom( + const ScrollPositionUpdate& aUpdate) { + MOZ_ASSERT(aUpdate.GetType() == ScrollUpdateType::PureRelative); + CSSPoint origin = GetVisualScrollOffset(); + ClampAndSetVisualScrollOffset(origin + aUpdate.GetDelta()); + return GetVisualScrollOffset() - origin; +} + +void FrameMetrics::UpdatePendingScrollInfo(const ScrollPositionUpdate& aInfo) { + // We only get this "pending scroll info" for paint-skip transactions, + // but PureRelative position updates always trigger a full paint, so + // we should never enter this code with a PureRelative update type. For + // the other types, the destination field on the ScrollPositionUpdate will + // tell us the final layout scroll position on the main thread. + MOZ_ASSERT(aInfo.GetType() != ScrollUpdateType::PureRelative); + + // In applying a main-thread scroll update, try to preserve the relative + // offset between the visual and layout viewports. + CSSPoint relativeOffset = GetVisualScrollOffset() - GetLayoutScrollOffset(); + MOZ_ASSERT(IsRootContent() || relativeOffset == CSSPoint()); + + SetLayoutScrollOffset(aInfo.GetDestination()); + ClampAndSetVisualScrollOffset(aInfo.GetDestination() + relativeOffset); + mScrollGeneration = aInfo.GetGeneration(); +} + +ScrollSnapInfo::ScrollSnapInfo() + : mScrollSnapStrictnessX(StyleScrollSnapStrictness::None), + mScrollSnapStrictnessY(StyleScrollSnapStrictness::None) {} + +bool ScrollSnapInfo::HasScrollSnapping() const { + return mScrollSnapStrictnessY != StyleScrollSnapStrictness::None || + mScrollSnapStrictnessX != StyleScrollSnapStrictness::None; +} + +bool ScrollSnapInfo::HasSnapPositions() const { + return (!mSnapPositionX.IsEmpty() && + mScrollSnapStrictnessX != StyleScrollSnapStrictness::None) || + (!mSnapPositionY.IsEmpty() && + mScrollSnapStrictnessY != StyleScrollSnapStrictness::None); +} + +void ScrollSnapInfo::InitializeScrollSnapStrictness( + WritingMode aWritingMode, const nsStyleDisplay* aDisplay) { + if (aDisplay->mScrollSnapType.strictness == StyleScrollSnapStrictness::None) { + return; + } + + mScrollSnapStrictnessX = StyleScrollSnapStrictness::None; + mScrollSnapStrictnessY = StyleScrollSnapStrictness::None; + + switch (aDisplay->mScrollSnapType.axis) { + case StyleScrollSnapAxis::X: + mScrollSnapStrictnessX = aDisplay->mScrollSnapType.strictness; + break; + case StyleScrollSnapAxis::Y: + mScrollSnapStrictnessY = aDisplay->mScrollSnapType.strictness; + break; + case StyleScrollSnapAxis::Block: + if (aWritingMode.IsVertical()) { + mScrollSnapStrictnessX = aDisplay->mScrollSnapType.strictness; + } else { + mScrollSnapStrictnessY = aDisplay->mScrollSnapType.strictness; + } + break; + case StyleScrollSnapAxis::Inline: + if (aWritingMode.IsVertical()) { + mScrollSnapStrictnessY = aDisplay->mScrollSnapType.strictness; + } else { + mScrollSnapStrictnessX = aDisplay->mScrollSnapType.strictness; + } + break; + case StyleScrollSnapAxis::Both: + mScrollSnapStrictnessX = aDisplay->mScrollSnapType.strictness; + mScrollSnapStrictnessY = aDisplay->mScrollSnapType.strictness; + break; + } +} + +std::ostream& operator<<(std::ostream& aStream, + const OverscrollBehavior& aBehavior) { + switch (aBehavior) { + case OverscrollBehavior::Auto: { + aStream << "auto"; + break; + } + case OverscrollBehavior::Contain: { + aStream << "contain"; + break; + } + case OverscrollBehavior::None: { + aStream << "none"; + break; + } + } + return aStream; +} + +OverscrollBehaviorInfo::OverscrollBehaviorInfo() + : mBehaviorX(OverscrollBehavior::Auto), + mBehaviorY(OverscrollBehavior::Auto) {} + +static OverscrollBehavior ToOverscrollBehavior( + StyleOverscrollBehavior aBehavior) { + switch (aBehavior) { + case StyleOverscrollBehavior::Auto: + return OverscrollBehavior::Auto; + case StyleOverscrollBehavior::Contain: + return OverscrollBehavior::Contain; + case StyleOverscrollBehavior::None: + return OverscrollBehavior::None; + } + MOZ_ASSERT_UNREACHABLE("Invalid overscroll behavior"); + return OverscrollBehavior::Auto; +} + +OverscrollBehaviorInfo OverscrollBehaviorInfo::FromStyleConstants( + StyleOverscrollBehavior aBehaviorX, StyleOverscrollBehavior aBehaviorY) { + OverscrollBehaviorInfo result; + result.mBehaviorX = ToOverscrollBehavior(aBehaviorX); + result.mBehaviorY = ToOverscrollBehavior(aBehaviorY); + return result; +} + +bool OverscrollBehaviorInfo::operator==( + const OverscrollBehaviorInfo& aOther) const { + return mBehaviorX == aOther.mBehaviorX && mBehaviorY == aOther.mBehaviorY; +} + +std::ostream& operator<<(std::ostream& aStream, + const OverscrollBehaviorInfo& aInfo) { + if (aInfo.mBehaviorX == aInfo.mBehaviorY) { + aStream << aInfo.mBehaviorX; + } else { + aStream << "{ x=" << aInfo.mBehaviorX << ", y=" << aInfo.mBehaviorY << " }"; + } + return aStream; +} + +std::ostream& operator<<(std::ostream& aStream, + const ScrollMetadata& aMetadata) { + aStream << "{ [metrics=" << aMetadata.GetMetrics() + << "] [color=" << aMetadata.GetBackgroundColor(); + if (aMetadata.GetScrollParentId() != ScrollableLayerGuid::NULL_SCROLL_ID) { + aStream << "] [scrollParent=" << aMetadata.GetScrollParentId(); + } + if (aMetadata.HasScrollClip()) { + aStream << "] [clip=" << aMetadata.ScrollClip().GetClipRect(); + } + if (aMetadata.HasMaskLayer()) { + aStream << "] [mask=" << aMetadata.ScrollClip().GetMaskLayerIndex().value(); + } + aStream << "] [overscroll=" << aMetadata.GetOverscrollBehavior() << "] [" + << aMetadata.GetScrollUpdates().Length() << " scrollupdates" + << "] }"; + return aStream; +} + +void ScrollMetadata::SetBackgroundColor( + const gfx::sRGBColor& aBackgroundColor) { + mBackgroundColor = gfx::ToDeviceColor(aBackgroundColor); +} + +StaticAutoPtr ScrollMetadata::sNullMetadata; + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/FrameMetrics.h b/gfx/layers/FrameMetrics.h new file mode 100644 index 0000000000..e48dda39e6 --- /dev/null +++ b/gfx/layers/FrameMetrics.h @@ -0,0 +1,1039 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_FRAMEMETRICS_H +#define GFX_FRAMEMETRICS_H + +#include // for uint8_t, uint32_t, uint64_t +#include + +#include "Units.h" // for CSSRect, CSSPixel, etc +#include "mozilla/DefineEnum.h" // for MOZ_DEFINE_ENUM +#include "mozilla/HashFunctions.h" // for HashGeneric +#include "mozilla/Maybe.h" +#include "mozilla/gfx/BasePoint.h" // for BasePoint +#include "mozilla/gfx/Rect.h" // for RoundedIn +#include "mozilla/gfx/ScaleFactor.h" // for ScaleFactor +#include "mozilla/gfx/Logging.h" // for Log +#include "mozilla/layers/LayersTypes.h" // for ScrollDirection +#include "mozilla/layers/ScrollableLayerGuid.h" // for ScrollableLayerGuid +#include "mozilla/ScrollPositionUpdate.h" // for ScrollPositionUpdate +#include "mozilla/StaticPtr.h" // for StaticAutoPtr +#include "mozilla/TimeStamp.h" // for TimeStamp +#include "nsDataHashtable.h" // for nsDataHashtable +#include "nsString.h" +#include "PLDHashTable.h" // for PLDHashNumber + +struct nsStyleDisplay; +namespace mozilla { +enum class StyleScrollSnapStrictness : uint8_t; +enum class StyleOverscrollBehavior : uint8_t; +class WritingMode; +} // namespace mozilla + +namespace IPC { +template +struct ParamTraits; +} // namespace IPC + +namespace mozilla { +namespace layers { + +/** + * Metrics about a scroll frame that are sent to the compositor and used + * by APZ. + * + * This is used for two main purposes: + * + * (1) Sending information about a scroll frame to the compositor and APZ + * as part of a layers or WebRender transaction. + * (2) Storing information about a scroll frame in APZ that persists + * between transactions. + * + * TODO: Separate these two uses into two distinct structures. + * + * A related class, RepaintRequest, is used for sending information about a + * scroll frame back from the compositor to the main thread when requesting + * a repaint of the scroll frame's contents. + */ +struct FrameMetrics { + friend struct IPC::ParamTraits; + friend std::ostream& operator<<(std::ostream& aStream, + const FrameMetrics& aMetrics); + + typedef ScrollableLayerGuid::ViewID ViewID; + + public: + // clang-format off + MOZ_DEFINE_ENUM_WITH_BASE_AT_CLASS_SCOPE( + ScrollOffsetUpdateType, uint8_t, ( + eNone, // The default; the scroll offset was not updated + eMainThread, // The scroll offset was updated by the main thread. + eRestore // The scroll offset was updated by the main thread, but + // as a restore from history or after a frame + // reconstruction. In this case, APZ can ignore the + // offset change if the user has done an APZ scroll + // already. + )); + // clang-format on + + FrameMetrics() + : mScrollId(ScrollableLayerGuid::NULL_SCROLL_ID), + mPresShellResolution(1), + mCompositionBounds(0, 0, 0, 0), + mDisplayPort(0, 0, 0, 0), + mCriticalDisplayPort(0, 0, 0, 0), + mScrollableRect(0, 0, 0, 0), + mCumulativeResolution(), + mDevPixelsPerCSSPixel(1), + mScrollOffset(0, 0), + mZoom(), + mRootCompositionSize(0, 0), + mPresShellId(-1), + mLayoutViewport(0, 0, 0, 0), + mExtraResolution(), + mPaintRequestTime(), + mVisualDestination(0, 0), + mVisualScrollUpdateType(eNone), + mCompositionSizeWithoutDynamicToolbar(), + mIsRootContent(false), + mIsScrollInfoLayer(false) {} + + // Default copy ctor and operator= are fine + + bool operator==(const FrameMetrics& aOther) const { + // Put mScrollId at the top since it's the most likely one to fail. + return mScrollId == aOther.mScrollId && + mPresShellResolution == aOther.mPresShellResolution && + mCompositionBounds.IsEqualEdges(aOther.mCompositionBounds) && + mDisplayPort.IsEqualEdges(aOther.mDisplayPort) && + mCriticalDisplayPort.IsEqualEdges(aOther.mCriticalDisplayPort) && + mScrollableRect.IsEqualEdges(aOther.mScrollableRect) && + mCumulativeResolution == aOther.mCumulativeResolution && + mDevPixelsPerCSSPixel == aOther.mDevPixelsPerCSSPixel && + mScrollOffset == aOther.mScrollOffset && + // don't compare mZoom + mScrollGeneration == aOther.mScrollGeneration && + mRootCompositionSize == aOther.mRootCompositionSize && + mPresShellId == aOther.mPresShellId && + mLayoutViewport.IsEqualEdges(aOther.mLayoutViewport) && + mExtraResolution == aOther.mExtraResolution && + mPaintRequestTime == aOther.mPaintRequestTime && + mVisualDestination == aOther.mVisualDestination && + mVisualScrollUpdateType == aOther.mVisualScrollUpdateType && + mIsRootContent == aOther.mIsRootContent && + mIsScrollInfoLayer == aOther.mIsScrollInfoLayer && + mFixedLayerMargins == aOther.mFixedLayerMargins && + mCompositionSizeWithoutDynamicToolbar == + aOther.mCompositionSizeWithoutDynamicToolbar; + } + + bool operator!=(const FrameMetrics& aOther) const { + return !operator==(aOther); + } + + bool IsScrollable() const { + return mScrollId != ScrollableLayerGuid::NULL_SCROLL_ID; + } + + CSSToScreenScale2D DisplayportPixelsPerCSSPixel() const { + // Note: use 'mZoom * ParentLayerToLayerScale(1.0f)' as the CSS-to-Layer + // scale instead of LayersPixelsPerCSSPixel(), because displayport + // calculations are done in the context of a repaint request, where we ask + // Layout to repaint at a new resolution that includes any async zoom. Until + // this repaint request is processed, LayersPixelsPerCSSPixel() does not yet + // include the async zoom, but it will when the displayport is interpreted + // for the repaint. + return mZoom * ParentLayerToLayerScale(1.0f) / mExtraResolution; + } + + CSSToLayerScale2D LayersPixelsPerCSSPixel() const { + return mDevPixelsPerCSSPixel * mCumulativeResolution; + } + + // Get the amount by which this frame has been zoomed since the last repaint. + LayerToParentLayerScale GetAsyncZoom() const { + // The async portion of the zoom should be the same along the x and y + // axes. + return (mZoom / LayersPixelsPerCSSPixel()).ToScaleFactor(); + } + + // Ensure the scrollableRect is at least as big as the compositionBounds + // because the scrollableRect can be smaller if the content is not large + // and the scrollableRect hasn't been updated yet. + // We move the scrollableRect up because we don't know if we can move it + // down. i.e. we know that scrollableRect can go back as far as zero. + // but we don't know how much further ahead it can go. + CSSRect GetExpandedScrollableRect() const { + CSSRect scrollableRect = mScrollableRect; + CSSSize compSize = CalculateCompositedSizeInCssPixels(); + if (scrollableRect.Width() < compSize.width) { + scrollableRect.SetRectX( + std::max(0.f, scrollableRect.X() - + (compSize.width - scrollableRect.Width())), + compSize.width); + } + + if (scrollableRect.Height() < compSize.height) { + scrollableRect.SetRectY( + std::max(0.f, scrollableRect.Y() - + (compSize.height - scrollableRect.Height())), + compSize.height); + } + + return scrollableRect; + } + + CSSSize CalculateCompositedSizeInCssPixels() const { + if (GetZoom() == CSSToParentLayerScale2D(0, 0)) { + return CSSSize(); // avoid division by zero + } + return mCompositionBounds.Size() / GetZoom(); + } + + /* + * Calculate the composition bounds of this frame in the CSS pixels of + * the content surrounding the scroll frame. (This can be thought of as + * "parent CSS" pixels). + * Note that it does not make sense to ask for the composition bounds in the + * CSS pixels of the scrolled content (that is, regular CSS pixels), + * because the origin of the composition bounds is not meaningful in that + * coordinate space. (The size is, use CalculateCompositedSizeInCssPixels() + * for that.) + */ + CSSRect CalculateCompositionBoundsInCssPixelsOfSurroundingContent() const { + if (GetZoom() == CSSToParentLayerScale2D(0, 0)) { + return CSSRect(); // avoid division by zero + } + // The CSS pixels of the scrolled content and the CSS pixels of the + // surrounding content only differ if the scrolled content is rendered + // at a higher resolution, and the difference is the resolution. + return mCompositionBounds / GetZoom() * CSSToCSSScale{mPresShellResolution}; + } + + CSSSize CalculateBoundedCompositedSizeInCssPixels() const { + CSSSize size = CalculateCompositedSizeInCssPixels(); + size.width = std::min(size.width, mRootCompositionSize.width); + size.height = std::min(size.height, mRootCompositionSize.height); + return size; + } + + CSSRect CalculateScrollRange() const { + CSSSize scrollPortSize = CalculateCompositedSizeInCssPixels(); + CSSRect scrollRange = mScrollableRect; + scrollRange.SetWidth( + std::max(scrollRange.Width() - scrollPortSize.width, 0.0f)); + scrollRange.SetHeight( + std::max(scrollRange.Height() - scrollPortSize.height, 0.0f)); + return scrollRange; + } + + void ScrollBy(const CSSPoint& aPoint) { + SetVisualScrollOffset(GetVisualScrollOffset() + aPoint); + } + + void ZoomBy(float aScale) { ZoomBy(gfxSize(aScale, aScale)); } + + void ZoomBy(const gfxSize& aScale) { + mZoom.xScale *= aScale.width; + mZoom.yScale *= aScale.height; + } + + /* + * Compares an APZ frame metrics with an incoming content frame metrics + * to see if APZ has a scroll offset that has not been incorporated into + * the content frame metrics. + */ + bool HasPendingScroll(const FrameMetrics& aContentFrameMetrics) const { + return GetVisualScrollOffset() != + aContentFrameMetrics.GetVisualScrollOffset(); + } + + void ApplyScrollUpdateFrom(const ScrollPositionUpdate& aUpdate); + + /** + * Applies the relative scroll offset update contained in aOther to the + * scroll offset contained in this. The scroll delta is clamped to the + * scrollable region. + * + * @returns The clamped scroll offset delta that was applied + */ + CSSPoint ApplyRelativeScrollUpdateFrom(const ScrollPositionUpdate& aUpdate); + + CSSPoint ApplyPureRelativeScrollUpdateFrom( + const ScrollPositionUpdate& aUpdate); + + void UpdatePendingScrollInfo(const ScrollPositionUpdate& aInfo); + + public: + void SetPresShellResolution(float aPresShellResolution) { + mPresShellResolution = aPresShellResolution; + } + + float GetPresShellResolution() const { return mPresShellResolution; } + + void SetCompositionBounds(const ParentLayerRect& aCompositionBounds) { + mCompositionBounds = aCompositionBounds; + } + + const ParentLayerRect& GetCompositionBounds() const { + return mCompositionBounds; + } + + void SetDisplayPort(const CSSRect& aDisplayPort) { + mDisplayPort = aDisplayPort; + } + + const CSSRect& GetDisplayPort() const { return mDisplayPort; } + + void SetCriticalDisplayPort(const CSSRect& aCriticalDisplayPort) { + mCriticalDisplayPort = aCriticalDisplayPort; + } + + const CSSRect& GetCriticalDisplayPort() const { return mCriticalDisplayPort; } + + void SetCumulativeResolution( + const LayoutDeviceToLayerScale2D& aCumulativeResolution) { + mCumulativeResolution = aCumulativeResolution; + } + + const LayoutDeviceToLayerScale2D& GetCumulativeResolution() const { + return mCumulativeResolution; + } + + void SetDevPixelsPerCSSPixel( + const CSSToLayoutDeviceScale& aDevPixelsPerCSSPixel) { + mDevPixelsPerCSSPixel = aDevPixelsPerCSSPixel; + } + + const CSSToLayoutDeviceScale& GetDevPixelsPerCSSPixel() const { + return mDevPixelsPerCSSPixel; + } + + void SetIsRootContent(bool aIsRootContent) { + mIsRootContent = aIsRootContent; + } + + bool IsRootContent() const { return mIsRootContent; } + + // Set scroll offset, first clamping to the scroll range. + void ClampAndSetVisualScrollOffset(const CSSPoint& aScrollOffset) { + SetVisualScrollOffset(CalculateScrollRange().ClampPoint(aScrollOffset)); + } + + CSSPoint GetLayoutScrollOffset() const { return mLayoutViewport.TopLeft(); } + void SetLayoutScrollOffset(const CSSPoint& aLayoutScrollOffset) { + mLayoutViewport.MoveTo(aLayoutScrollOffset); + } + + const CSSPoint& GetVisualScrollOffset() const { return mScrollOffset; } + void SetVisualScrollOffset(const CSSPoint& aVisualScrollOffset) { + mScrollOffset = aVisualScrollOffset; + } + + void SetZoom(const CSSToParentLayerScale2D& aZoom) { mZoom = aZoom; } + + const CSSToParentLayerScale2D& GetZoom() const { return mZoom; } + + void SetScrollGeneration(const ScrollGeneration& aScrollGeneration) { + mScrollGeneration = aScrollGeneration; + } + + ScrollGeneration GetScrollGeneration() const { return mScrollGeneration; } + + ViewID GetScrollId() const { return mScrollId; } + + void SetScrollId(ViewID scrollId) { mScrollId = scrollId; } + + void SetRootCompositionSize(const CSSSize& aRootCompositionSize) { + mRootCompositionSize = aRootCompositionSize; + } + + const CSSSize& GetRootCompositionSize() const { return mRootCompositionSize; } + + uint32_t GetPresShellId() const { return mPresShellId; } + + void SetPresShellId(uint32_t aPresShellId) { mPresShellId = aPresShellId; } + + void SetLayoutViewport(const CSSRect& aLayoutViewport) { + mLayoutViewport = aLayoutViewport; + } + + const CSSRect& GetLayoutViewport() const { return mLayoutViewport; } + + CSSRect GetVisualViewport() const { + return CSSRect(GetVisualScrollOffset(), + CalculateCompositedSizeInCssPixels()); + } + + void SetExtraResolution(const ScreenToLayerScale2D& aExtraResolution) { + mExtraResolution = aExtraResolution; + } + + const ScreenToLayerScale2D& GetExtraResolution() const { + return mExtraResolution; + } + + const CSSRect& GetScrollableRect() const { return mScrollableRect; } + + void SetScrollableRect(const CSSRect& aScrollableRect) { + mScrollableRect = aScrollableRect; + } + + // If the frame is in vertical-RTL writing mode(E.g. "writing-mode: + // vertical-rl" in CSS), or if it's in horizontal-RTL writing-mode(E.g. + // "writing-mode: horizontal-tb; direction: rtl;" in CSS), then this function + // returns true. From the representation perspective, frames whose horizontal + // contents start at rightside also cause their horizontal scrollbars, if any, + // initially start at rightside. So we can also learn about the initial side + // of the horizontal scrollbar for the frame by calling this function. + bool IsHorizontalContentRightToLeft() const { return mScrollableRect.x < 0; } + + void SetPaintRequestTime(const TimeStamp& aTime) { + mPaintRequestTime = aTime; + } + const TimeStamp& GetPaintRequestTime() const { return mPaintRequestTime; } + + void SetIsScrollInfoLayer(bool aIsScrollInfoLayer) { + mIsScrollInfoLayer = aIsScrollInfoLayer; + } + bool IsScrollInfoLayer() const { return mIsScrollInfoLayer; } + + void SetVisualDestination(const CSSPoint& aVisualDestination) { + mVisualDestination = aVisualDestination; + } + const CSSPoint& GetVisualDestination() const { return mVisualDestination; } + + void SetVisualScrollUpdateType(ScrollOffsetUpdateType aUpdateType) { + mVisualScrollUpdateType = aUpdateType; + } + ScrollOffsetUpdateType GetVisualScrollUpdateType() const { + return mVisualScrollUpdateType; + } + + // Determine if the visual viewport is outside of the layout viewport and + // adjust the x,y-offset in mLayoutViewport accordingly. This is necessary to + // allow APZ to async-scroll the layout viewport. + // + // This is a no-op if mIsRootContent is false. + void RecalculateLayoutViewportOffset(); + + void SetFixedLayerMargins(const ScreenMargin& aFixedLayerMargins) { + mFixedLayerMargins = aFixedLayerMargins; + } + const ScreenMargin& GetFixedLayerMargins() const { + return mFixedLayerMargins; + } + + void SetCompositionSizeWithoutDynamicToolbar(const ParentLayerSize& aSize) { + MOZ_ASSERT(mIsRootContent); + mCompositionSizeWithoutDynamicToolbar = aSize; + } + const ParentLayerSize& GetCompositionSizeWithoutDynamicToolbar() const { + MOZ_ASSERT(mIsRootContent); + return mCompositionSizeWithoutDynamicToolbar; + } + + // Helper function for RecalculateViewportOffset(). Exposed so that + // APZC can perform the operation on other copies of the layout + // and visual viewport rects (e.g. the "effective" ones used to implement + // the frame delay). + // Modifies |aLayoutViewport| to continue enclosing |aVisualViewport| + // if possible. + // The layout viewport needs to remain clamped to the scrollable rect, + // and we pass in the scrollable rect so this function can maintain that + // constraint. + static void KeepLayoutViewportEnclosingVisualViewport( + const CSSRect& aVisualViewport, const CSSRect& aScrollableRect, + CSSRect& aLayoutViewport); + + private: + // A ID assigned to each scrollable frame, unique within each LayersId.. + ViewID mScrollId; + + // The pres-shell resolution that has been induced on the document containing + // this scroll frame as a result of zooming this scroll frame (whether via + // user action, or choosing an initial zoom level on page load). This can + // only be different from 1.0 for frames that are zoomable, which currently + // is just the root content document's root scroll frame + // (mIsRootContent = true). + // This is a plain float rather than a ScaleFactor because in and of itself + // it does not convert between any coordinate spaces for which we have names. + float mPresShellResolution; + + // This is the area within the widget that we're compositing to. It is in the + // layer coordinates of the scrollable content's parent layer. + // + // The size of the composition bounds corresponds to the size of the scroll + // frame's scroll port (but in a coordinate system where the size does not + // change during zooming). + // + // The origin of the composition bounds is relative to the layer tree origin. + // Unlike the scroll port's origin, it does not change during scrolling of + // the scrollable layer to which it is associated. However, it may change due + // to scrolling of ancestor layers. + // + // This value is provided by Gecko at layout/paint time. + ParentLayerRect mCompositionBounds; + + // The area of a scroll frame's contents that has been painted, relative to + // GetLayoutScrollOffset(). + // + // Should not be larger than GetExpandedScrollableRect(). + // + // To pre-render a margin of 100 CSS pixels around the scroll port, + // { x = -100, y = - 100, + // width = scrollPort.width + 200, height = scrollPort.height + 200 } + // where scrollPort = CalculateCompositedSizeInCssPixels(). + CSSRect mDisplayPort; + + // If non-empty, the area of a frame's contents that is considered critical + // to paint. Area outside of this area (i.e. area inside mDisplayPort, but + // outside of mCriticalDisplayPort) is considered low-priority, and may be + // painted with lower precision, or not painted at all. + // + // The same restrictions for mDisplayPort apply here. + CSSRect mCriticalDisplayPort; + + // The scrollable bounds of a frame. This is determined by reflow. + // Ordinarily the x and y will be 0 and the width and height will be the + // size of the element being scrolled. However for RTL pages or elements + // the x value may be negative. + // + // For scrollable frames that are overflow:hidden the x and y are usually + // set to the value of the current scroll offset, and the width and height + // will match the composition bounds width and height. In effect this reduces + // the scrollable range to 0. + // + // This is in the same coordinate space as |mScrollOffset|, but a different + // coordinate space than |mDisplayPort|. Note also that this coordinate + // system is understood by window.scrollTo(). + CSSRect mScrollableRect; + + // The cumulative resolution that the current frame has been painted at. + // This is the product of the pres-shell resolutions of the document + // containing this scroll frame and its ancestors, and any css-driven + // resolution. This information is provided by Gecko at layout/paint time. + // Note that this is allowed to have different x- and y-scales, but only + // for subframes (mIsRootContent = false). (The same applies to other scales + // that "inherit" the 2D-ness of this one, such as mZoom.) + LayoutDeviceToLayerScale2D mCumulativeResolution; + + // The conversion factor between CSS pixels and device pixels for this frame. + // This can vary based on a variety of things, such as reflowing-zoom. + CSSToLayoutDeviceScale mDevPixelsPerCSSPixel; + + // The position of the top-left of the scroll frame's scroll port, relative + // to the scrollable content's origin. + // + // This is in the same coordinate space as |mScrollableRect|, but a different + // coordinate space than |mDisplayPort|. + // + // It is required that the rect: + // { x = mScrollOffset.x, y = mScrollOffset.y, + // width = scrollPort.width, + // height = scrollPort.height } + // (where scrollPort = CalculateCompositedSizeInCssPixels()) + // be within |mScrollableRect|. + CSSPoint mScrollOffset; + + // The "user zoom". Content is painted by gecko at mCumulativeResolution * + // mDevPixelsPerCSSPixel, but will be drawn to the screen at mZoom. In the + // steady state, the two will be the same, but during an async zoom action the + // two may diverge. This information is initialized in Gecko but updated in + // the APZC. + CSSToParentLayerScale2D mZoom; + + // The scroll generation counter used to acknowledge the scroll offset update. + ScrollGeneration mScrollGeneration; + + // The size of the root scrollable's composition bounds, but in local CSS + // pixels. + CSSSize mRootCompositionSize; + + // A display port expressed as layer margins that apply to the rect of what + // is drawn of the scrollable element. + ScreenMargin mDisplayPortMargins; + + uint32_t mPresShellId; + + // For a root scroll frame (RSF), the document's layout viewport + // (sometimes called "CSS viewport" in older code). + // + // Its size is the dimensions we're using to constrain the element + // of the document (i.e. the initial containing block (ICB) size). + // + // Its origin is the RSF's layout scroll position, i.e. the scroll position + // exposed to web content via window.scrollX/Y. + // + // Note that only the root content document's RSF has a layout viewport + // that's distinct from the visual viewport. For an iframe RSF, the two + // are the same. + // + // For a scroll frame that is not an RSF, this metric is meaningless and + // invalid. + CSSRect mLayoutViewport; + + // The extra resolution at which content in this scroll frame is drawn beyond + // that necessary to draw one Layer pixel per Screen pixel. + ScreenToLayerScale2D mExtraResolution; + + // The time at which the APZC last requested a repaint for this scroll frame. + TimeStamp mPaintRequestTime; + + // These fields are used when the main thread wants to set a visual viewport + // offset that's distinct from the layout viewport offset. + // In this case, mVisualScrollUpdateType is set to eMainThread, and + // mVisualDestination is set to desired visual destination (relative + // to the document, like mScrollOffset). + CSSPoint mVisualDestination; + ScrollOffsetUpdateType mVisualScrollUpdateType; + + // 'fixed layer margins' on the main-thread. This is only used for the + // root-content scroll frame. + ScreenMargin mFixedLayerMargins; + + // Similar to mCompositionBounds.Size() but not including the dynamic toolbar + // height. + // If we are not using a dynamic toolbar, this has the same value as + // mCompositionBounds.Size(). + ParentLayerSize mCompositionSizeWithoutDynamicToolbar; + + // Whether or not this is the root scroll frame for the root content document. + bool mIsRootContent : 1; + + // True if this scroll frame is a scroll info layer. A scroll info layer is + // not layerized and its content cannot be truly async-scrolled, but its + // metrics are still sent to and updated by the compositor, with the updates + // being reflected on the next paint rather than the next composite. + bool mIsScrollInfoLayer : 1; + + // WARNING!!!! + // + // When adding a new field: + // + // - First, consider whether the field can be added to ScrollMetadata + // instead. If so, prefer that. + // + // - Otherwise, the following places should be updated to include them + // (as needed): + // FrameMetrics::operator == + // AsyncPanZoomController::NotifyLayersUpdated + // The ParamTraits specialization in LayersMessageUtils.h + // + // Please add new fields above this comment. +}; + +struct ScrollSnapInfo { + ScrollSnapInfo(); + + bool operator==(const ScrollSnapInfo& aOther) const { + return mScrollSnapStrictnessX == aOther.mScrollSnapStrictnessX && + mScrollSnapStrictnessY == aOther.mScrollSnapStrictnessY && + mSnapPositionX == aOther.mSnapPositionX && + mSnapPositionY == aOther.mSnapPositionY && + mXRangeWiderThanSnapport == aOther.mXRangeWiderThanSnapport && + mYRangeWiderThanSnapport == aOther.mYRangeWiderThanSnapport && + mSnapportSize == aOther.mSnapportSize; + } + + bool HasScrollSnapping() const; + bool HasSnapPositions() const; + + void InitializeScrollSnapStrictness(WritingMode aWritingMode, + const nsStyleDisplay* aDisplay); + + // The scroll frame's scroll-snap-type. + StyleScrollSnapStrictness mScrollSnapStrictnessX; + StyleScrollSnapStrictness mScrollSnapStrictnessY; + + // The scroll positions corresponding to scroll-snap-align values. + CopyableTArray mSnapPositionX; + CopyableTArray mSnapPositionY; + + struct ScrollSnapRange { + ScrollSnapRange() = default; + + ScrollSnapRange(nscoord aStart, nscoord aEnd) + : mStart(aStart), mEnd(aEnd) {} + + nscoord mStart; + nscoord mEnd; + bool operator==(const ScrollSnapRange& aOther) const { + return mStart == aOther.mStart && mEnd == aOther.mEnd; + } + + // Returns true if |aPoint| is a valid snap position in this range. + bool IsValid(nscoord aPoint, nscoord aSnapportSize) const { + MOZ_ASSERT(mEnd - mStart > aSnapportSize); + return mStart <= aPoint && aPoint <= mEnd - aSnapportSize; + } + }; + // An array of the range that the target element is larger than the snapport + // on the axis. + // Snap positions in this range will be valid snap positions in the case where + // the distance between the closest snap position and the second closest snap + // position is still larger than the snapport size. + // See https://drafts.csswg.org/css-scroll-snap-1/#snap-overflow + // + // Note: This range contains scroll-margin values. + CopyableTArray mXRangeWiderThanSnapport; + CopyableTArray mYRangeWiderThanSnapport; + + // Note: This snapport size has been already deflated by scroll-padding. + nsSize mSnapportSize; +}; + +// clang-format off +MOZ_DEFINE_ENUM_CLASS_WITH_BASE( + OverscrollBehavior, uint8_t, ( + Auto, + Contain, + None +)); +// clang-format on + +std::ostream& operator<<(std::ostream& aStream, + const OverscrollBehavior& aBehavior); + +struct OverscrollBehaviorInfo { + OverscrollBehaviorInfo(); + + // Construct from StyleOverscrollBehavior values. + static OverscrollBehaviorInfo FromStyleConstants( + StyleOverscrollBehavior aBehaviorX, StyleOverscrollBehavior aBehaviorY); + + bool operator==(const OverscrollBehaviorInfo& aOther) const; + friend std::ostream& operator<<(std::ostream& aStream, + const OverscrollBehaviorInfo& aInfo); + + OverscrollBehavior mBehaviorX; + OverscrollBehavior mBehaviorY; +}; + +/** + * A clip that applies to a layer, that may be scrolled by some of the + * scroll frames associated with the layer. + */ +struct LayerClip { + friend struct IPC::ParamTraits; + + public: + LayerClip() : mClipRect(), mMaskLayerIndex() {} + + explicit LayerClip(const ParentLayerIntRect& aClipRect) + : mClipRect(aClipRect), mMaskLayerIndex() {} + + bool operator==(const LayerClip& aOther) const { + return mClipRect == aOther.mClipRect && + mMaskLayerIndex == aOther.mMaskLayerIndex; + } + + void SetClipRect(const ParentLayerIntRect& aClipRect) { + mClipRect = aClipRect; + } + const ParentLayerIntRect& GetClipRect() const { return mClipRect; } + + void SetMaskLayerIndex(const Maybe& aIndex) { + mMaskLayerIndex = aIndex; + } + const Maybe& GetMaskLayerIndex() const { return mMaskLayerIndex; } + + private: + ParentLayerIntRect mClipRect; + + // Optionally, specifies a mask layer that's part of the clip. + // This is an index into the MetricsMaskLayers array on the Layer. + Maybe mMaskLayerIndex; +}; + +typedef Maybe MaybeLayerClip; // for passing over IPDL + +/** + * Metadata about a scroll frame that's sent to the compositor during a layers + * or WebRender transaction, and also stored by APZ between transactions. + * This includes the scroll frame's FrameMetrics, as well as other metadata. + * We don't put the other metadata into FrameMetrics to avoid FrameMetrics + * becoming too bloated (as a FrameMetrics is e.g. stored in memory shared + * with the content process). + */ +struct ScrollMetadata { + friend struct IPC::ParamTraits; + friend std::ostream& operator<<(std::ostream& aStream, + const ScrollMetadata& aMetadata); + + typedef ScrollableLayerGuid::ViewID ViewID; + + public: + static StaticAutoPtr + sNullMetadata; // We sometimes need an empty metadata + + ScrollMetadata() + : mMetrics(), + mSnapInfo(), + mScrollParentId(ScrollableLayerGuid::NULL_SCROLL_ID), + mBackgroundColor(), + mContentDescription(), + mLineScrollAmount(0, 0), + mPageScrollAmount(0, 0), + mScrollClip(), + mHasScrollgrab(false), + mIsLayersIdRoot(false), + mIsAutoDirRootContentRTL(false), + mForceDisableApz(false), + mResolutionUpdated(false), + mIsRDMTouchSimulationActive(false), + mDidContentGetPainted(true), + mOverscrollBehavior() {} + + bool operator==(const ScrollMetadata& aOther) const { + return mMetrics == aOther.mMetrics && mSnapInfo == aOther.mSnapInfo && + mScrollParentId == aOther.mScrollParentId && + mBackgroundColor == aOther.mBackgroundColor && + // don't compare mContentDescription + mLineScrollAmount == aOther.mLineScrollAmount && + mPageScrollAmount == aOther.mPageScrollAmount && + mScrollClip == aOther.mScrollClip && + mHasScrollgrab == aOther.mHasScrollgrab && + mIsLayersIdRoot == aOther.mIsLayersIdRoot && + mIsAutoDirRootContentRTL == aOther.mIsAutoDirRootContentRTL && + mForceDisableApz == aOther.mForceDisableApz && + mResolutionUpdated == aOther.mResolutionUpdated && + mIsRDMTouchSimulationActive == aOther.mIsRDMTouchSimulationActive && + mDidContentGetPainted == aOther.mDidContentGetPainted && + mDisregardedDirection == aOther.mDisregardedDirection && + mOverscrollBehavior == aOther.mOverscrollBehavior && + mScrollUpdates == aOther.mScrollUpdates; + } + + bool operator!=(const ScrollMetadata& aOther) const { + return !operator==(aOther); + } + + bool IsDefault() const { + ScrollMetadata def; + + def.mMetrics.SetPresShellId(mMetrics.GetPresShellId()); + return (def == *this); + } + + FrameMetrics& GetMetrics() { return mMetrics; } + const FrameMetrics& GetMetrics() const { return mMetrics; } + + void SetSnapInfo(ScrollSnapInfo&& aSnapInfo) { + mSnapInfo = std::move(aSnapInfo); + } + const ScrollSnapInfo& GetSnapInfo() const { return mSnapInfo; } + + ViewID GetScrollParentId() const { return mScrollParentId; } + + void SetScrollParentId(ViewID aParentId) { mScrollParentId = aParentId; } + const gfx::DeviceColor& GetBackgroundColor() const { + return mBackgroundColor; + } + void SetBackgroundColor(const gfx::sRGBColor& aBackgroundColor); + const nsCString& GetContentDescription() const { return mContentDescription; } + void SetContentDescription(const nsCString& aContentDescription) { + mContentDescription = aContentDescription; + } + const LayoutDeviceIntSize& GetLineScrollAmount() const { + return mLineScrollAmount; + } + void SetLineScrollAmount(const LayoutDeviceIntSize& size) { + mLineScrollAmount = size; + } + const LayoutDeviceIntSize& GetPageScrollAmount() const { + return mPageScrollAmount; + } + void SetPageScrollAmount(const LayoutDeviceIntSize& size) { + mPageScrollAmount = size; + } + + void SetScrollClip(const Maybe& aScrollClip) { + mScrollClip = aScrollClip; + } + const Maybe& GetScrollClip() const { return mScrollClip; } + bool HasScrollClip() const { return mScrollClip.isSome(); } + const LayerClip& ScrollClip() const { return mScrollClip.ref(); } + LayerClip& ScrollClip() { return mScrollClip.ref(); } + + bool HasMaskLayer() const { + return HasScrollClip() && ScrollClip().GetMaskLayerIndex(); + } + Maybe GetClipRect() const { + return mScrollClip.isSome() ? Some(mScrollClip->GetClipRect()) : Nothing(); + } + + void SetHasScrollgrab(bool aHasScrollgrab) { + mHasScrollgrab = aHasScrollgrab; + } + bool GetHasScrollgrab() const { return mHasScrollgrab; } + void SetIsLayersIdRoot(bool aValue) { mIsLayersIdRoot = aValue; } + bool IsLayersIdRoot() const { return mIsLayersIdRoot; } + void SetIsAutoDirRootContentRTL(bool aValue) { + mIsAutoDirRootContentRTL = aValue; + } + bool IsAutoDirRootContentRTL() const { return mIsAutoDirRootContentRTL; } + void SetForceDisableApz(bool aForceDisable) { + mForceDisableApz = aForceDisable; + } + bool IsApzForceDisabled() const { return mForceDisableApz; } + void SetResolutionUpdated(bool aUpdated) { mResolutionUpdated = aUpdated; } + bool IsResolutionUpdated() const { return mResolutionUpdated; } + + void SetIsRDMTouchSimulationActive(bool aValue) { + mIsRDMTouchSimulationActive = aValue; + } + bool GetIsRDMTouchSimulationActive() const { + return mIsRDMTouchSimulationActive; + } + + bool DidContentGetPainted() const { return mDidContentGetPainted; } + + private: + // For use in IPC only + void SetDidContentGetPainted(bool aValue) { mDidContentGetPainted = aValue; } + + public: + // For more details about the concept of a disregarded direction, refer to the + // code which defines mDisregardedDirection. + Maybe GetDisregardedDirection() const { + return mDisregardedDirection; + } + void SetDisregardedDirection(const Maybe& aValue) { + mDisregardedDirection = aValue; + } + + void SetOverscrollBehavior( + const OverscrollBehaviorInfo& aOverscrollBehavior) { + mOverscrollBehavior = aOverscrollBehavior; + } + const OverscrollBehaviorInfo& GetOverscrollBehavior() const { + return mOverscrollBehavior; + } + + void SetScrollUpdates(const nsTArray& aUpdates) { + mScrollUpdates = aUpdates; + } + + const nsTArray& GetScrollUpdates() const { + return mScrollUpdates; + } + + void UpdatePendingScrollInfo(nsTArray&& aUpdates) { + MOZ_ASSERT(!aUpdates.IsEmpty()); + mMetrics.UpdatePendingScrollInfo(aUpdates.LastElement()); + + mDidContentGetPainted = false; + mScrollUpdates.Clear(); + mScrollUpdates.AppendElements(std::move(aUpdates)); + } + + private: + FrameMetrics mMetrics; + + // Information used to determine where to snap to for a given scroll. + ScrollSnapInfo mSnapInfo; + + // The ViewID of the scrollable frame to which overscroll should be handed + // off. + ViewID mScrollParentId; + + // The background color to use when overscrolling. + gfx::DeviceColor mBackgroundColor; + + // A description of the content element corresponding to this frame. + // This is empty unless this is a scrollable layer and the + // apz.printtree pref is turned on. + nsCString mContentDescription; + + // The value of GetLineScrollAmount(), for scroll frames. + LayoutDeviceIntSize mLineScrollAmount; + + // The value of GetPageScrollAmount(), for scroll frames. + LayoutDeviceIntSize mPageScrollAmount; + + // A clip to apply when compositing the layer bearing this ScrollMetadata, + // after applying any transform arising from scrolling this scroll frame. + // Note that, unlike most other fields of ScrollMetadata, this is allowed + // to differ between different layers scrolled by the same scroll frame. + // TODO: Group the fields of ScrollMetadata into sub-structures to separate + // fields with this property better. + Maybe mScrollClip; + + // Whether or not this frame is for an element marked 'scrollgrab'. + bool mHasScrollgrab : 1; + + // Whether these framemetrics are for the root scroll frame (root element if + // we don't have a root scroll frame) for its layers id. + bool mIsLayersIdRoot : 1; + + // The AutoDirRootContent is the element in an HTML document, or the + // root scrollframe if there is no body. This member variable indicates + // whether this element's content in the horizontal direction starts from + // right to left (e.g. it's true either if "writing-mode: vertical-rl", or + // "writing-mode: horizontal-tb; direction: rtl" in CSS). + // When we do auto-dir scrolling (@see mozilla::WheelDeltaAdjustmentStrategy + // or refer to bug 1358017 for details), setting a pref can make the code use + // the writing mode of this root element instead of the target scrollframe, + // and so we need to know if the writing mode is RTL or not. + bool mIsAutoDirRootContentRTL : 1; + + // Whether or not the compositor should actually do APZ-scrolling on this + // scrollframe. + bool mForceDisableApz : 1; + + // Whether the pres shell resolution stored in mMetrics reflects a change + // originated by the main thread. + bool mResolutionUpdated : 1; + + // Whether or not RDM and touch simulation are active for this document. + // It's important to note that if RDM is active then this field will be + // true for the content document but NOT the chrome document containing + // the browser UI and RDM controls. + bool mIsRDMTouchSimulationActive : 1; + + // Whether this metadata is part of a transaction that also repainted the + // content (i.e. updated the displaylist or textures). This gets set to false + // for "paint-skip" transactions, where the main thread doesn't repaint but + // instead requests APZ to update the compositor scroll offset instead. APZ + // needs to be able to distinguish these paint-skip transactions so that it + // can use the correct transforms. + bool mDidContentGetPainted : 1; + + // The disregarded direction means the direction which is disregarded anyway, + // even if the scroll frame overflows in that direction and the direction is + // specified as scrollable. This could happen in some scenarios, for instance, + // a single-line text control frame should disregard wheel scroll in + // its block-flow direction even if it overflows in that direction. + Maybe mDisregardedDirection; + + // The overscroll behavior for this scroll frame. + OverscrollBehaviorInfo mOverscrollBehavior; + + // The ordered list of scroll position updates for this scroll frame since + // the last transaction. + CopyableTArray mScrollUpdates; + + // WARNING!!!! + // + // When adding new fields to ScrollMetadata, the following places should be + // updated to include them (as needed): + // 1. ScrollMetadata::operator == + // 2. AsyncPanZoomController::NotifyLayersUpdated + // 3. The ParamTraits specialization in LayersMessageUtils.h + // + // Please add new fields above this comment. +}; + +typedef nsDataHashtable> + ScrollUpdatesMap; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_FRAMEMETRICS_H */ diff --git a/gfx/layers/GLImages.cpp b/gfx/layers/GLImages.cpp new file mode 100644 index 0000000000..daec5f6519 --- /dev/null +++ b/gfx/layers/GLImages.cpp @@ -0,0 +1,91 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GLImages.h" +#include "GLContext.h" +#include "GLContextProvider.h" +#include "ScopedGLHelpers.h" +#include "GLImages.h" +#include "GLBlitHelper.h" +#include "GLReadTexImageHelper.h" +#include "GLLibraryEGL.h" +#include "mozilla/gfx/Logging.h" + +using namespace mozilla; +using namespace mozilla::gl; + +namespace mozilla { +namespace layers { + +static RefPtr sSnapshotContext; + +already_AddRefed GLImage::GetAsSourceSurface() { + MOZ_ASSERT(NS_IsMainThread(), "Should be on the main thread"); + + if (!sSnapshotContext) { + nsCString discardFailureId; + sSnapshotContext = GLContextProvider::CreateHeadless({}, &discardFailureId); + if (!sSnapshotContext) { + NS_WARNING("Failed to create snapshot GLContext"); + return nullptr; + } + } + + sSnapshotContext->MakeCurrent(); + ScopedTexture scopedTex(sSnapshotContext); + ScopedBindTexture boundTex(sSnapshotContext, scopedTex.Texture()); + + gfx::IntSize size = GetSize(); + sSnapshotContext->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGBA, + size.width, size.height, 0, LOCAL_GL_RGBA, + LOCAL_GL_UNSIGNED_BYTE, nullptr); + + ScopedFramebufferForTexture autoFBForTex(sSnapshotContext, + scopedTex.Texture()); + if (!autoFBForTex.IsComplete()) { + gfxCriticalError() + << "GetAsSourceSurface: ScopedFramebufferForTexture failed."; + return nullptr; + } + + const gl::OriginPos destOrigin = gl::OriginPos::TopLeft; + { + const ScopedBindFramebuffer bindFB(sSnapshotContext, autoFBForTex.FB()); + if (!sSnapshotContext->BlitHelper()->BlitImageToFramebuffer(this, size, + destOrigin)) { + return nullptr; + } + } + + RefPtr source = + gfx::Factory::CreateDataSourceSurface(size, gfx::SurfaceFormat::B8G8R8A8); + if (NS_WARN_IF(!source)) { + return nullptr; + } + + ScopedBindFramebuffer bind(sSnapshotContext, autoFBForTex.FB()); + ReadPixelsIntoDataSurface(sSnapshotContext, source); + return source.forget(); +} + +#ifdef MOZ_WIDGET_ANDROID +SurfaceTextureImage::SurfaceTextureImage(AndroidSurfaceTextureHandle aHandle, + const gfx::IntSize& aSize, + bool aContinuous, + gl::OriginPos aOriginPos, + bool aHasAlpha /* = true */) + : GLImage(ImageFormat::SURFACE_TEXTURE), + mHandle(aHandle), + mSize(aSize), + mContinuous(aContinuous), + mOriginPos(aOriginPos), + mHasAlpha(aHasAlpha) { + MOZ_ASSERT(mHandle); +} +#endif + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/GLImages.h b/gfx/layers/GLImages.h new file mode 100644 index 0000000000..f82f3219ed --- /dev/null +++ b/gfx/layers/GLImages.h @@ -0,0 +1,87 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_GLIMAGES_H +#define GFX_GLIMAGES_H + +#include "GLContextTypes.h" +#include "GLTypes.h" +#include "ImageContainer.h" // for Image +#include "ImageTypes.h" // for ImageFormat::SHARED_GLTEXTURE +#include "nsCOMPtr.h" // for already_AddRefed +#include "mozilla/gfx/Point.h" // for IntSize + +#ifdef MOZ_WIDGET_ANDROID +# include "AndroidSurfaceTexture.h" +#endif + +namespace mozilla { +namespace layers { + +class GLImage : public Image { + public: + explicit GLImage(ImageFormat aFormat) : Image(nullptr, aFormat) {} + + already_AddRefed GetAsSourceSurface() override; + + GLImage* AsGLImage() override { return this; } +}; + +#ifdef MOZ_WIDGET_ANDROID + +class SurfaceTextureImage : public GLImage { + public: + class SetCurrentCallback { + public: + virtual void operator()(void) = 0; + virtual ~SetCurrentCallback() {} + }; + + SurfaceTextureImage(AndroidSurfaceTextureHandle aHandle, + const gfx::IntSize& aSize, bool aContinuous, + gl::OriginPos aOriginPos, bool aHasAlpha = true); + + gfx::IntSize GetSize() const override { return mSize; } + AndroidSurfaceTextureHandle GetHandle() const { return mHandle; } + bool GetContinuous() const { return mContinuous; } + gl::OriginPos GetOriginPos() const { return mOriginPos; } + bool GetHasAlpha() const { return mHasAlpha; } + + already_AddRefed GetAsSourceSurface() override { + // We can implement this, but currently don't want to because it will cause + // the SurfaceTexture to be permanently bound to the snapshot readback + // context. + return nullptr; + } + + SurfaceTextureImage* AsSurfaceTextureImage() override { return this; } + + void RegisterSetCurrentCallback(UniquePtr aCallback) { + mSetCurrentCallback = std::move(aCallback); + } + + void OnSetCurrent() { + if (mSetCurrentCallback) { + (*mSetCurrentCallback)(); + mSetCurrentCallback.reset(); + } + } + + private: + AndroidSurfaceTextureHandle mHandle; + gfx::IntSize mSize; + bool mContinuous; + gl::OriginPos mOriginPos; + const bool mHasAlpha; + UniquePtr mSetCurrentCallback; +}; + +#endif // MOZ_WIDGET_ANDROID + +} // namespace layers +} // namespace mozilla + +#endif // GFX_GLIMAGES_H diff --git a/gfx/layers/GPUVideoImage.h b/gfx/layers/GPUVideoImage.h new file mode 100644 index 0000000000..535fd34da1 --- /dev/null +++ b/gfx/layers/GPUVideoImage.h @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_GPU_VIDEO_IMAGE_H +#define GFX_GPU_VIDEO_IMAGE_H + +#include "mozilla/RefPtr.h" +#include "ImageContainer.h" +#include "mozilla/layers/GPUVideoTextureClient.h" +#include "mozilla/layers/CompositableClient.h" +#include "mozilla/layers/ImageBridgeChild.h" + +namespace mozilla { +namespace gl { +class GLBlitHelper; +} +namespace layers { + +class IGPUVideoSurfaceManager { + protected: + virtual ~IGPUVideoSurfaceManager() = default; + + public: + NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING + + virtual already_AddRefed Readback( + const SurfaceDescriptorGPUVideo& aSD) = 0; + virtual void DeallocateSurfaceDescriptor( + const SurfaceDescriptorGPUVideo& aSD) = 0; +}; + +// Represents an animated Image that is known to the GPU process. +class GPUVideoImage final : public Image { + friend class gl::GLBlitHelper; + + public: + GPUVideoImage(IGPUVideoSurfaceManager* aManager, + const SurfaceDescriptorGPUVideo& aSD, const gfx::IntSize& aSize) + : Image(nullptr, ImageFormat::GPU_VIDEO), mSize(aSize) { + // Create the TextureClient immediately since the GPUVideoTextureData + // is responsible for deallocating the SurfaceDescriptor. + // + // Use the RECYCLE texture flag, since it's likely that our 'real' + // TextureData (in the decoder thread of the GPU process) is using + // it too, and we want to make sure we don't send the delete message + // until we've stopped being used on the compositor. + mTextureClient = TextureClient::CreateWithData( + new GPUVideoTextureData(aManager, aSD, aSize), TextureFlags::RECYCLE, + ImageBridgeChild::GetSingleton().get()); + } + + virtual ~GPUVideoImage() = default; + + gfx::IntSize GetSize() const override { return mSize; } + + Maybe GetDesc() override { + return GetDescFromTexClient(mTextureClient); + } + + private: + GPUVideoTextureData* GetData() const { + if (!mTextureClient) { + return nullptr; + } + TextureData* data = mTextureClient->GetInternalData(); + if (!data) { + return nullptr; + } + return data->AsGPUVideoTextureData(); + } + + public: + already_AddRefed GetAsSourceSurface() override { + GPUVideoTextureData* data = GetData(); + if (!data) { + return nullptr; + } + return data->GetAsSourceSurface(); + } + + TextureClient* GetTextureClient(KnowsCompositor* aKnowsCompositor) override { + MOZ_ASSERT(aKnowsCompositor == ImageBridgeChild::GetSingleton(), + "Must only use GPUVideo on ImageBridge"); + return mTextureClient; + } + + private: + gfx::IntSize mSize; + RefPtr mTextureClient; +}; + +} // namespace layers +} // namespace mozilla + +#endif // GFX_GPU_VIDEO_IMAGE_H diff --git a/gfx/layers/IMFYCbCrImage.cpp b/gfx/layers/IMFYCbCrImage.cpp new file mode 100644 index 0000000000..2753287c92 --- /dev/null +++ b/gfx/layers/IMFYCbCrImage.cpp @@ -0,0 +1,151 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "IMFYCbCrImage.h" +#include "mozilla/gfx/DeviceManagerDx.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/gfx/Types.h" +#include "mozilla/layers/TextureD3D11.h" +#include "mozilla/layers/CompositableClient.h" +#include "mozilla/layers/CompositableForwarder.h" +#include "mozilla/layers/D3D11YCbCrImage.h" +#include "mozilla/layers/TextureClient.h" +#include "d3d9.h" + +namespace mozilla { +namespace layers { + +IMFYCbCrImage::IMFYCbCrImage(IMFMediaBuffer* aBuffer, IMF2DBuffer* a2DBuffer, + KnowsCompositor* aKnowsCompositor, + ImageContainer* aContainer) + : RecyclingPlanarYCbCrImage(nullptr), + mBuffer(aBuffer), + m2DBuffer(a2DBuffer) { + mAllocator = aContainer->GetD3D11YCbCrRecycleAllocator(aKnowsCompositor); +} + +IMFYCbCrImage::~IMFYCbCrImage() { + if (m2DBuffer) { + m2DBuffer->Unlock2D(); + } else { + mBuffer->Unlock(); + } +} + +/* static */ +bool IMFYCbCrImage::CopyDataToTexture(const Data& aData, ID3D11Device* aDevice, + DXGIYCbCrTextureData* aTextureData) { + MOZ_ASSERT(aTextureData); + + HRESULT hr; + RefPtr mt; + + hr = aDevice->QueryInterface((ID3D10Multithread**)getter_AddRefs(mt)); + if (FAILED(hr)) { + return false; + } + + if (!mt->GetMultithreadProtected()) { + return false; + } + + if (!gfx::DeviceManagerDx::Get()->CanInitializeKeyedMutexTextures()) { + return false; + } + + ID3D11Texture2D* textureY = aTextureData->GetD3D11Texture(0); + ID3D11Texture2D* textureCb = aTextureData->GetD3D11Texture(1); + ID3D11Texture2D* textureCr = aTextureData->GetD3D11Texture(2); + + D3D11MTAutoEnter mtAutoEnter(mt.forget()); + + RefPtr ctx; + aDevice->GetImmediateContext(getter_AddRefs(ctx)); + if (!ctx) { + gfxCriticalError() << "Failed to get immediate context."; + return false; + } + + // The documentation here seems to suggest using the immediate mode context + // on more than one thread is not allowed: + // https://msdn.microsoft.com/en-us/library/windows/desktop/ff476891(v=vs.85).aspx + // The Debug Layer seems to imply it is though. When the ID3D10Multithread + // layer is on. The Enter/Leave of the critical section shouldn't even be + // required but were added for extra security. + + { + AutoLockD3D11Texture lockY(textureY); + AutoLockD3D11Texture lockCr(textureCr); + AutoLockD3D11Texture lockCb(textureCb); + D3D11MTAutoEnter mtAutoEnter(mt.forget()); + + D3D11_BOX box; + box.front = box.top = box.left = 0; + box.back = 1; + box.right = aData.mYSize.width; + box.bottom = aData.mYSize.height; + ctx->UpdateSubresource(textureY, 0, &box, aData.mYChannel, aData.mYStride, + 0); + + box.right = aData.mCbCrSize.width; + box.bottom = aData.mCbCrSize.height; + ctx->UpdateSubresource(textureCb, 0, &box, aData.mCbChannel, + aData.mCbCrStride, 0); + ctx->UpdateSubresource(textureCr, 0, &box, aData.mCrChannel, + aData.mCbCrStride, 0); + } + + return true; +} + +TextureClient* IMFYCbCrImage::GetD3D11TextureClient( + KnowsCompositor* aKnowsCompositor) { + if (!mAllocator) { + return nullptr; + } + + RefPtr device = gfx::DeviceManagerDx::Get()->GetImageDevice(); + if (!device) { + return nullptr; + } + + { + DXGIYCbCrTextureAllocationHelper helper(mData, TextureFlags::DEFAULT, + device); + mTextureClient = mAllocator->CreateOrRecycle(helper); + } + + if (!mTextureClient) { + return nullptr; + } + + DXGIYCbCrTextureData* data = + mTextureClient->GetInternalData()->AsDXGIYCbCrTextureData(); + + if (!CopyDataToTexture(mData, device, data)) { + // Failed to copy data + mTextureClient = nullptr; + return nullptr; + } + + return mTextureClient; +} + +TextureClient* IMFYCbCrImage::GetTextureClient( + KnowsCompositor* aKnowsCompositor) { + if (mTextureClient) { + return mTextureClient; + } + + RefPtr device = gfx::DeviceManagerDx::Get()->GetImageDevice(); + if (!device || !aKnowsCompositor->SupportsD3D11()) { + return nullptr; + } + return GetD3D11TextureClient(aKnowsCompositor); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/IMFYCbCrImage.h b/gfx/layers/IMFYCbCrImage.h new file mode 100644 index 0000000000..e1df0b2099 --- /dev/null +++ b/gfx/layers/IMFYCbCrImage.h @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_IMFYCBCRIMAGE_H +#define GFX_IMFYCBCRIMAGE_H + +#include "mozilla/layers/TextureD3D11.h" +#include "mozilla/RefPtr.h" +#include "ImageContainer.h" +#include "mfidl.h" + +namespace mozilla { +namespace layers { + +class IMFYCbCrImage : public RecyclingPlanarYCbCrImage { + public: + IMFYCbCrImage(IMFMediaBuffer* aBuffer, IMF2DBuffer* a2DBuffer, + KnowsCompositor* aKnowsCompositor, ImageContainer* aContainer); + + bool IsValid() const override { return true; } + + TextureClient* GetTextureClient(KnowsCompositor* aKnowsCompositor) override; + + protected: + TextureClient* GetD3D11TextureClient(KnowsCompositor* aKnowsCompositor); + static bool CopyDataToTexture(const Data& aData, ID3D11Device* aDevice, + DXGIYCbCrTextureData* aTextureData); + + virtual ~IMFYCbCrImage(); + + RefPtr mBuffer; + RefPtr m2DBuffer; + RefPtr mAllocator; + RefPtr mTextureClient; +}; + +} // namespace layers +} // namespace mozilla + +#endif // GFX_D3DSURFACEIMAGE_H diff --git a/gfx/layers/IPDLActor.h b/gfx/layers/IPDLActor.h new file mode 100644 index 0000000000..b5eb04d0dc --- /dev/null +++ b/gfx/layers/IPDLActor.h @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_LAYERS_IPDLACTOR_H +#define MOZILLA_LAYERS_IPDLACTOR_H + +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/layers/CompositableForwarder.h" +#include "mozilla/Unused.h" +#include "gfxPlatform.h" + +namespace mozilla { +namespace layers { + +/// A base class to facilitate the deallocation of IPDL actors. +/// +/// Implements the parent side of the simple deallocation handshake. +/// Override the Destroy method rather than the ActorDestroy method. +template +class ParentActor : public Protocol { + public: + ParentActor() : mDestroyed(false) {} + + ~ParentActor() { MOZ_ASSERT(mDestroyed); } + + bool CanSend() const { return !mDestroyed; } + + // Override this rather than ActorDestroy + virtual void Destroy() {} + + mozilla::ipc::IPCResult RecvDestroy() final { + DestroyIfNeeded(); + Unused << Protocol::Send__delete__(this); + return IPC_OK(); + } + + typedef ipc::IProtocol::ActorDestroyReason Why; + + void ActorDestroy(Why) override { DestroyIfNeeded(); } + + protected: + void DestroyIfNeeded() { + if (!mDestroyed) { + Destroy(); + mDestroyed = true; + } + } + + private: + bool mDestroyed; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/ImageContainer.cpp b/gfx/layers/ImageContainer.cpp new file mode 100644 index 0000000000..bd4ad42f94 --- /dev/null +++ b/gfx/layers/ImageContainer.cpp @@ -0,0 +1,869 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ImageContainer.h" + +#include // for memcpy, memset + +#include "GeckoProfiler.h" +#include "GLImages.h" // for SurfaceTextureImage +#include "YCbCrUtils.h" // for YCbCr conversions +#include "gfx2DGlue.h" +#include "gfxPlatform.h" // for gfxPlatform +#include "gfxUtils.h" // for gfxUtils +#include "libyuv.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/RefPtr.h" // for already_AddRefed +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/ipc/CrossProcessMutex.h" // for CrossProcessMutex, etc +#include "mozilla/layers/CompositorTypes.h" +#include "mozilla/layers/ImageBridgeChild.h" // for ImageBridgeChild +#include "mozilla/layers/ImageClient.h" // for ImageClient +#include "mozilla/layers/ImageDataSerializer.h" // for SurfaceDescriptorBuffer +#include "mozilla/layers/LayersMessages.h" +#include "mozilla/layers/SharedPlanarYCbCrImage.h" +#include "mozilla/layers/SharedRGBImage.h" +#include "mozilla/layers/SharedSurfacesChild.h" // for SharedSurfacesAnimation +#include "mozilla/layers/TextureClientRecycleAllocator.h" +#include "nsProxyRelease.h" +#include "nsISupportsUtils.h" // for NS_IF_ADDREF + +#ifdef XP_MACOSX +# include "MacIOSurfaceImage.h" +# include "mozilla/gfx/QuartzSupport.h" +#endif + +#ifdef XP_WIN +# include + +# include "gfxWindowsPlatform.h" +# include "mozilla/gfx/DeviceManagerDx.h" +# include "mozilla/layers/D3D11YCbCrImage.h" +#endif + +namespace mozilla::layers { + +using namespace mozilla::gfx; +using namespace mozilla::ipc; + +Atomic Image::sSerialCounter(0); + +Atomic ImageContainer::sGenerationCounter(0); + +static void CopyPlane(uint8_t* aDst, const uint8_t* aSrc, + const gfx::IntSize& aSize, int32_t aStride, + int32_t aSkip); + +RefPtr ImageFactory::CreatePlanarYCbCrImage( + const gfx::IntSize& aScaleHint, BufferRecycleBin* aRecycleBin) { + return new RecyclingPlanarYCbCrImage(aRecycleBin); +} + +BufferRecycleBin::BufferRecycleBin() + : mLock("mozilla.layers.BufferRecycleBin.mLock") + // This member is only valid when the bin is not empty and will be + // properly initialized in RecycleBuffer, but initializing it here avoids + // static analysis noise. + , + mRecycledBufferSize(0) {} + +void BufferRecycleBin::RecycleBuffer(UniquePtr aBuffer, + uint32_t aSize) { + MutexAutoLock lock(mLock); + + if (!mRecycledBuffers.IsEmpty() && aSize != mRecycledBufferSize) { + mRecycledBuffers.Clear(); + } + mRecycledBufferSize = aSize; + mRecycledBuffers.AppendElement(std::move(aBuffer)); +} + +UniquePtr BufferRecycleBin::GetBuffer(uint32_t aSize) { + MutexAutoLock lock(mLock); + + if (mRecycledBuffers.IsEmpty() || mRecycledBufferSize != aSize) { + return UniquePtr(new (fallible) uint8_t[aSize]); + } + + return mRecycledBuffers.PopLastElement(); +} + +void BufferRecycleBin::ClearRecycledBuffers() { + MutexAutoLock lock(mLock); + if (!mRecycledBuffers.IsEmpty()) { + mRecycledBuffers.Clear(); + } + mRecycledBufferSize = 0; +} + +ImageContainerListener::ImageContainerListener(ImageContainer* aImageContainer) + : mLock("mozilla.layers.ImageContainerListener.mLock"), + mImageContainer(aImageContainer) {} + +ImageContainerListener::~ImageContainerListener() = default; + +void ImageContainerListener::NotifyComposite( + const ImageCompositeNotification& aNotification) { + MutexAutoLock lock(mLock); + if (mImageContainer) { + mImageContainer->NotifyComposite(aNotification); + } +} + +void ImageContainerListener::NotifyDropped(uint32_t aDropped) { + MutexAutoLock lock(mLock); + if (mImageContainer) { + mImageContainer->NotifyDropped(aDropped); + } +} + +void ImageContainerListener::ClearImageContainer() { + MutexAutoLock lock(mLock); + mImageContainer = nullptr; +} + +void ImageContainerListener::DropImageClient() { + MutexAutoLock lock(mLock); + if (mImageContainer) { + mImageContainer->DropImageClient(); + } +} + +already_AddRefed ImageContainer::GetImageClient() { + RecursiveMutexAutoLock mon(mRecursiveMutex); + EnsureImageClient(); + RefPtr imageClient = mImageClient; + return imageClient.forget(); +} + +void ImageContainer::DropImageClient() { + RecursiveMutexAutoLock mon(mRecursiveMutex); + if (mImageClient) { + mImageClient->ClearCachedResources(); + mImageClient = nullptr; + } +} + +void ImageContainer::EnsureImageClient() { + // If we're not forcing a new ImageClient, then we can skip this if we don't + // have an existing ImageClient, or if the existing one belongs to an IPC + // actor that is still open. + if (!mIsAsync) { + return; + } + if (mImageClient && + mImageClient->GetForwarder()->GetLayersIPCActor()->IPCOpen()) { + return; + } + + RefPtr imageBridge = ImageBridgeChild::GetSingleton(); + if (imageBridge) { + mImageClient = + imageBridge->CreateImageClient(CompositableType::IMAGE, this); + if (mImageClient) { + mAsyncContainerHandle = mImageClient->GetAsyncHandle(); + } else { + // It's okay to drop the async container handle since the ImageBridgeChild + // is going to die anyway. + mAsyncContainerHandle = CompositableHandle(); + } + } +} + +SharedSurfacesAnimation* ImageContainer::EnsureSharedSurfacesAnimation() { + if (!mSharedAnimation) { + mSharedAnimation = new SharedSurfacesAnimation(); + } + return mSharedAnimation; +} + +ImageContainer::ImageContainer(Mode flag) + : mRecursiveMutex("ImageContainer.mRecursiveMutex"), + mGenerationCounter(++sGenerationCounter), + mPaintCount(0), + mDroppedImageCount(0), + mImageFactory(new ImageFactory()), + mRecycleBin(new BufferRecycleBin()), + mIsAsync(flag == ASYNCHRONOUS), + mCurrentProducerID(-1) { + if (flag == ASYNCHRONOUS) { + mNotifyCompositeListener = new ImageContainerListener(this); + EnsureImageClient(); + } +} + +ImageContainer::ImageContainer(const CompositableHandle& aHandle) + : mRecursiveMutex("ImageContainer.mRecursiveMutex"), + mGenerationCounter(++sGenerationCounter), + mPaintCount(0), + mDroppedImageCount(0), + mImageFactory(nullptr), + mRecycleBin(nullptr), + mIsAsync(true), + mAsyncContainerHandle(aHandle), + mCurrentProducerID(-1) { + MOZ_ASSERT(mAsyncContainerHandle); +} + +ImageContainer::~ImageContainer() { + if (mNotifyCompositeListener) { + mNotifyCompositeListener->ClearImageContainer(); + } + if (mAsyncContainerHandle) { + if (RefPtr imageBridge = + ImageBridgeChild::GetSingleton()) { + imageBridge->ForgetImageContainer(mAsyncContainerHandle); + } + } + if (mSharedAnimation) { + mSharedAnimation->Destroy(); + } +} + +Maybe Image::GetDesc() { return {}; } + +Maybe Image::GetDescFromTexClient( + TextureClient* const forTc) { + RefPtr tc = forTc; + if (!forTc) { + tc = GetTextureClient(nullptr); + } + + const auto& tcd = tc->GetInternalData(); + + SurfaceDescriptor ret; + if (!tcd->Serialize(ret)) { + return {}; + } + return Some(ret); +} + +RefPtr ImageContainer::CreatePlanarYCbCrImage() { + RecursiveMutexAutoLock lock(mRecursiveMutex); + EnsureImageClient(); + if (mImageClient && mImageClient->AsImageClientSingle()) { + return new SharedPlanarYCbCrImage(mImageClient); + } + if (mRecycleAllocator) { + return new SharedPlanarYCbCrImage(mRecycleAllocator); + } + return mImageFactory->CreatePlanarYCbCrImage(mScaleHint, mRecycleBin); +} + +RefPtr ImageContainer::CreateSharedRGBImage() { + RecursiveMutexAutoLock lock(mRecursiveMutex); + EnsureImageClient(); + if (mImageClient && mImageClient->AsImageClientSingle()) { + return new SharedRGBImage(mImageClient); + } + if (mRecycleAllocator) { + return new SharedRGBImage(mRecycleAllocator); + } + return nullptr; +} + +void ImageContainer::SetCurrentImageInternal( + const nsTArray& aImages) { + RecursiveMutexAutoLock lock(mRecursiveMutex); + + mGenerationCounter = ++sGenerationCounter; + + if (!aImages.IsEmpty()) { + NS_ASSERTION(mCurrentImages.IsEmpty() || + mCurrentImages[0].mProducerID != aImages[0].mProducerID || + mCurrentImages[0].mFrameID <= aImages[0].mFrameID, + "frame IDs shouldn't go backwards"); + if (aImages[0].mProducerID != mCurrentProducerID) { + mCurrentProducerID = aImages[0].mProducerID; + } + } + + nsTArray newImages; + + for (uint32_t i = 0; i < aImages.Length(); ++i) { + NS_ASSERTION(aImages[i].mImage, "image can't be null"); + NS_ASSERTION(!aImages[i].mTimeStamp.IsNull() || aImages.Length() == 1, + "Multiple images require timestamps"); + if (i > 0) { + NS_ASSERTION(aImages[i].mTimeStamp >= aImages[i - 1].mTimeStamp, + "Timestamps must not decrease"); + NS_ASSERTION(aImages[i].mFrameID > aImages[i - 1].mFrameID, + "FrameIDs must increase"); + NS_ASSERTION(aImages[i].mProducerID == aImages[i - 1].mProducerID, + "ProducerIDs must be the same"); + } + OwningImage* img = newImages.AppendElement(); + img->mImage = aImages[i].mImage; + img->mTimeStamp = aImages[i].mTimeStamp; + img->mFrameID = aImages[i].mFrameID; + img->mProducerID = aImages[i].mProducerID; + for (const auto& oldImg : mCurrentImages) { + if (oldImg.mFrameID == img->mFrameID && + oldImg.mProducerID == img->mProducerID) { + img->mComposited = oldImg.mComposited; + break; + } + } + } + + mCurrentImages = std::move(newImages); +} + +void ImageContainer::ClearImagesFromImageBridge() { + RecursiveMutexAutoLock lock(mRecursiveMutex); + SetCurrentImageInternal(nsTArray()); +} + +void ImageContainer::SetCurrentImages(const nsTArray& aImages) { + AUTO_PROFILER_LABEL("ImageContainer::SetCurrentImages", GRAPHICS); + MOZ_ASSERT(!aImages.IsEmpty()); + RecursiveMutexAutoLock lock(mRecursiveMutex); + if (mIsAsync) { + if (RefPtr imageBridge = + ImageBridgeChild::GetSingleton()) { + imageBridge->UpdateImageClient(this); + } + } + SetCurrentImageInternal(aImages); +} + +void ImageContainer::ClearAllImages() { + if (mImageClient) { + // Let ImageClient release all TextureClients. This doesn't return + // until ImageBridge has called ClearCurrentImageFromImageBridge. + if (RefPtr imageBridge = + ImageBridgeChild::GetSingleton()) { + imageBridge->FlushAllImages(mImageClient, this); + } + return; + } + + RecursiveMutexAutoLock lock(mRecursiveMutex); + SetCurrentImageInternal(nsTArray()); +} + +void ImageContainer::ClearCachedResources() { + RecursiveMutexAutoLock lock(mRecursiveMutex); + if (mImageClient && mImageClient->AsImageClientSingle()) { + if (!mImageClient->HasTextureClientRecycler()) { + return; + } + mImageClient->GetTextureClientRecycler()->ShrinkToMinimumSize(); + return; + } + return mRecycleBin->ClearRecycledBuffers(); +} + +void ImageContainer::SetCurrentImageInTransaction(Image* aImage) { + AutoTArray images; + images.AppendElement(NonOwningImage(aImage)); + SetCurrentImagesInTransaction(images); +} + +void ImageContainer::SetCurrentImagesInTransaction( + const nsTArray& aImages) { + NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); + NS_ASSERTION(!mImageClient, + "Should use async image transfer with ImageBridge."); + + SetCurrentImageInternal(aImages); +} + +bool ImageContainer::IsAsync() const { return mIsAsync; } + +CompositableHandle ImageContainer::GetAsyncContainerHandle() { + NS_ASSERTION(IsAsync(), + "Shared image ID is only relevant to async ImageContainers"); + NS_ASSERTION(mAsyncContainerHandle, "Should have a shared image ID"); + RecursiveMutexAutoLock mon(mRecursiveMutex); + EnsureImageClient(); + return mAsyncContainerHandle; +} + +bool ImageContainer::HasCurrentImage() { + RecursiveMutexAutoLock lock(mRecursiveMutex); + + return !mCurrentImages.IsEmpty(); +} + +void ImageContainer::GetCurrentImages(nsTArray* aImages, + uint32_t* aGenerationCounter) { + RecursiveMutexAutoLock lock(mRecursiveMutex); + + *aImages = mCurrentImages.Clone(); + if (aGenerationCounter) { + *aGenerationCounter = mGenerationCounter; + } +} + +gfx::IntSize ImageContainer::GetCurrentSize() { + RecursiveMutexAutoLock lock(mRecursiveMutex); + + if (mCurrentImages.IsEmpty()) { + return gfx::IntSize(0, 0); + } + + return mCurrentImages[0].mImage->GetSize(); +} + +void ImageContainer::NotifyComposite( + const ImageCompositeNotification& aNotification) { + RecursiveMutexAutoLock lock(mRecursiveMutex); + + // An image composition notification is sent the first time a particular + // image is composited by an ImageHost. Thus, every time we receive such + // a notification, a new image has been painted. + ++mPaintCount; + + if (aNotification.producerID() == mCurrentProducerID) { + for (auto& img : mCurrentImages) { + if (img.mFrameID == aNotification.frameID()) { + img.mComposited = true; + } + } + } + + if (!aNotification.imageTimeStamp().IsNull()) { + mPaintDelay = aNotification.firstCompositeTimeStamp() - + aNotification.imageTimeStamp(); + } +} + +void ImageContainer::NotifyDropped(uint32_t aDropped) { + mDroppedImageCount += aDropped; +} + +void ImageContainer::EnsureRecycleAllocatorForRDD( + KnowsCompositor* aKnowsCompositor) { + MOZ_ASSERT(!mIsAsync); + MOZ_ASSERT(!mImageClient); + MOZ_ASSERT(XRE_IsRDDProcess()); + + if (mRecycleAllocator && + aKnowsCompositor == mRecycleAllocator->GetKnowsCompositor()) { + return; + } + + if (!StaticPrefs::layers_recycle_allocator_rdd_AtStartup()) { + return; + } + + static const uint32_t MAX_POOLED_VIDEO_COUNT = 5; + + mRecycleAllocator = + new layers::TextureClientRecycleAllocator(aKnowsCompositor); + mRecycleAllocator->SetMaxPoolSize(MAX_POOLED_VIDEO_COUNT); +} + +#ifdef XP_WIN +D3D11YCbCrRecycleAllocator* ImageContainer::GetD3D11YCbCrRecycleAllocator( + KnowsCompositor* aKnowsCompositor) { + if (mD3D11YCbCrRecycleAllocator && + aKnowsCompositor == mD3D11YCbCrRecycleAllocator->GetKnowsCompositor()) { + return mD3D11YCbCrRecycleAllocator; + } + + if (!aKnowsCompositor->SupportsD3D11() || + !gfx::DeviceManagerDx::Get()->GetImageDevice()) { + return nullptr; + } + + mD3D11YCbCrRecycleAllocator = + new D3D11YCbCrRecycleAllocator(aKnowsCompositor); + return mD3D11YCbCrRecycleAllocator; +} +#endif + +#ifdef XP_MACOSX +MacIOSurfaceRecycleAllocator* +ImageContainer::GetMacIOSurfaceRecycleAllocator() { + if (!mMacIOSurfaceRecycleAllocator) { + mMacIOSurfaceRecycleAllocator = new MacIOSurfaceRecycleAllocator(); + } + + return mMacIOSurfaceRecycleAllocator; +} +#endif + +PlanarYCbCrImage::PlanarYCbCrImage() + : Image(nullptr, ImageFormat::PLANAR_YCBCR), + mOffscreenFormat(SurfaceFormat::UNKNOWN), + mBufferSize(0) {} + +nsresult PlanarYCbCrImage::BuildSurfaceDescriptorBuffer( + SurfaceDescriptorBuffer& aSdBuffer) { + const PlanarYCbCrData* pdata = GetData(); + MOZ_ASSERT(pdata, "must have PlanarYCbCrData"); + MOZ_ASSERT(pdata->mYSkip == 0 && pdata->mCbSkip == 0 && pdata->mCrSkip == 0, + "YCbCrDescriptor doesn't hold skip values"); + + uint32_t yOffset; + uint32_t cbOffset; + uint32_t crOffset; + ImageDataSerializer::ComputeYCbCrOffsets( + pdata->mYStride, pdata->mYSize.height, pdata->mCbCrStride, + pdata->mCbCrSize.height, yOffset, cbOffset, crOffset); + + aSdBuffer.desc() = YCbCrDescriptor( + pdata->GetPictureRect(), pdata->mYSize, pdata->mYStride, pdata->mCbCrSize, + pdata->mCbCrStride, yOffset, cbOffset, crOffset, pdata->mStereoMode, + pdata->mColorDepth, pdata->mYUVColorSpace, pdata->mColorRange, + /*hasIntermediateBuffer*/ false); + + uint8_t* buffer = nullptr; + const MemoryOrShmem& memOrShmem = aSdBuffer.data(); + switch (memOrShmem.type()) { + case MemoryOrShmem::Tuintptr_t: + buffer = reinterpret_cast(memOrShmem.get_uintptr_t()); + break; + case MemoryOrShmem::TShmem: + buffer = memOrShmem.get_Shmem().get(); + break; + default: + MOZ_ASSERT(false, "Unknown MemoryOrShmem type"); + } + MOZ_ASSERT(buffer, "no valid buffer available to copy image data"); + if (!buffer) { + return NS_ERROR_INVALID_ARG; + } + + CopyPlane(buffer + yOffset, pdata->mYChannel, pdata->mYSize, pdata->mYStride, + pdata->mYSkip); + CopyPlane(buffer + cbOffset, pdata->mCbChannel, pdata->mCbCrSize, + pdata->mCbCrStride, pdata->mCbSkip); + CopyPlane(buffer + crOffset, pdata->mCrChannel, pdata->mCbCrSize, + pdata->mCbCrStride, pdata->mCrSkip); + return NS_OK; +} + +RecyclingPlanarYCbCrImage::~RecyclingPlanarYCbCrImage() { + if (mBuffer) { + mRecycleBin->RecycleBuffer(std::move(mBuffer), mBufferSize); + } +} + +size_t RecyclingPlanarYCbCrImage::SizeOfExcludingThis( + MallocSizeOf aMallocSizeOf) const { + // Ignoring: + // - mData - just wraps mBuffer + // - Surfaces should be reported under gfx-surfaces-*: + // - mSourceSurface + // - Base class: + // - mImplData is not used + // Not owned: + // - mRecycleBin + size_t size = aMallocSizeOf(mBuffer.get()); + + // Could add in the future: + // - mBackendData (from base class) + + return size; +} + +UniquePtr RecyclingPlanarYCbCrImage::AllocateBuffer(uint32_t aSize) { + return mRecycleBin->GetBuffer(aSize); +} + +static void CopyPlane(uint8_t* aDst, const uint8_t* aSrc, + const gfx::IntSize& aSize, int32_t aStride, + int32_t aSkip) { + int32_t height = aSize.height; + int32_t width = aSize.width; + + MOZ_RELEASE_ASSERT(width <= aStride); + + if (!aSkip) { + // Fast path: planar input. + memcpy(aDst, aSrc, height * aStride); + } else { + for (int y = 0; y < height; ++y) { + const uint8_t* src = aSrc; + uint8_t* dst = aDst; + // Slow path + for (int x = 0; x < width; ++x) { + *dst++ = *src++; + src += aSkip; + } + aSrc += aStride; + aDst += aStride; + } + } +} + +bool RecyclingPlanarYCbCrImage::CopyData(const Data& aData) { + // update buffer size + // Use uint32_t throughout to match AllocateBuffer's param and mBufferSize + const auto checkedSize = + CheckedInt(aData.mCbCrStride) * aData.mCbCrSize.height * 2 + + CheckedInt(aData.mYStride) * aData.mYSize.height; + + if (!checkedSize.isValid()) return false; + + const auto size = checkedSize.value(); + + // get new buffer + mBuffer = AllocateBuffer(size); + if (!mBuffer) return false; + + // update buffer size + mBufferSize = size; + + mData = aData; + mData.mYChannel = mBuffer.get(); + mData.mCbChannel = mData.mYChannel + mData.mYStride * mData.mYSize.height; + mData.mCrChannel = + mData.mCbChannel + mData.mCbCrStride * mData.mCbCrSize.height; + mData.mYSkip = mData.mCbSkip = mData.mCrSkip = 0; + + CopyPlane(mData.mYChannel, aData.mYChannel, aData.mYSize, aData.mYStride, + aData.mYSkip); + CopyPlane(mData.mCbChannel, aData.mCbChannel, aData.mCbCrSize, + aData.mCbCrStride, aData.mCbSkip); + CopyPlane(mData.mCrChannel, aData.mCrChannel, aData.mCbCrSize, + aData.mCbCrStride, aData.mCrSkip); + + mSize = aData.mPicSize; + mOrigin = gfx::IntPoint(aData.mPicX, aData.mPicY); + return true; +} + +gfxImageFormat PlanarYCbCrImage::GetOffscreenFormat() const { + return mOffscreenFormat == SurfaceFormat::UNKNOWN ? gfxVars::OffscreenFormat() + : mOffscreenFormat; +} + +bool PlanarYCbCrImage::AdoptData(const Data& aData) { + mData = aData; + mSize = aData.mPicSize; + mOrigin = gfx::IntPoint(aData.mPicX, aData.mPicY); + return true; +} + +already_AddRefed PlanarYCbCrImage::GetAsSourceSurface() { + if (mSourceSurface) { + RefPtr surface(mSourceSurface); + return surface.forget(); + } + + gfx::IntSize size(mSize); + gfx::SurfaceFormat format = + gfx::ImageFormatToSurfaceFormat(GetOffscreenFormat()); + gfx::GetYCbCrToRGBDestFormatAndSize(mData, format, size); + if (mSize.width > PlanarYCbCrImage::MAX_DIMENSION || + mSize.height > PlanarYCbCrImage::MAX_DIMENSION) { + NS_ERROR("Illegal image dest width or height"); + return nullptr; + } + + RefPtr surface = + gfx::Factory::CreateDataSourceSurface(size, format); + if (NS_WARN_IF(!surface)) { + return nullptr; + } + + DataSourceSurface::ScopedMap mapping(surface, DataSourceSurface::WRITE); + if (NS_WARN_IF(!mapping.IsMapped())) { + return nullptr; + } + + gfx::ConvertYCbCrToRGB(mData, format, size, mapping.GetData(), + mapping.GetStride()); + + mSourceSurface = surface; + + return surface.forget(); +} + +PlanarYCbCrImage::~PlanarYCbCrImage() { + NS_ReleaseOnMainThread("PlanarYCbCrImage::mSourceSurface", + mSourceSurface.forget()); +} + +NVImage::NVImage() : Image(nullptr, ImageFormat::NV_IMAGE), mBufferSize(0) {} + +NVImage::~NVImage() { + NS_ReleaseOnMainThread("NVImage::mSourceSurface", mSourceSurface.forget()); +} + +IntSize NVImage::GetSize() const { return mSize; } + +IntRect NVImage::GetPictureRect() const { return mData.GetPictureRect(); } + +already_AddRefed NVImage::GetAsSourceSurface() { + if (mSourceSurface) { + RefPtr surface(mSourceSurface); + return surface.forget(); + } + + // Convert the current NV12 or NV21 data to YUV420P so that we can follow the + // logics in PlanarYCbCrImage::GetAsSourceSurface(). + const int bufferLength = mData.mYSize.height * mData.mYStride + + mData.mCbCrSize.height * mData.mCbCrSize.width * 2; + UniquePtr buffer(new uint8_t[bufferLength]); + + Data aData = mData; + aData.mCbCrStride = aData.mCbCrSize.width; + aData.mCbSkip = 0; + aData.mCrSkip = 0; + aData.mYChannel = buffer.get(); + aData.mCbChannel = aData.mYChannel + aData.mYSize.height * aData.mYStride; + aData.mCrChannel = + aData.mCbChannel + aData.mCbCrSize.height * aData.mCbCrStride; + + if (mData.mCbChannel < mData.mCrChannel) { // NV12 + libyuv::NV12ToI420(mData.mYChannel, mData.mYStride, mData.mCbChannel, + mData.mCbCrStride, aData.mYChannel, aData.mYStride, + aData.mCbChannel, aData.mCbCrStride, aData.mCrChannel, + aData.mCbCrStride, aData.mYSize.width, + aData.mYSize.height); + } else { // NV21 + libyuv::NV21ToI420(mData.mYChannel, mData.mYStride, mData.mCrChannel, + mData.mCbCrStride, aData.mYChannel, aData.mYStride, + aData.mCbChannel, aData.mCbCrStride, aData.mCrChannel, + aData.mCbCrStride, aData.mYSize.width, + aData.mYSize.height); + } + + // The logics in PlanarYCbCrImage::GetAsSourceSurface(). + gfx::IntSize size(mSize); + gfx::SurfaceFormat format = gfx::ImageFormatToSurfaceFormat( + gfxPlatform::GetPlatform()->GetOffscreenFormat()); + gfx::GetYCbCrToRGBDestFormatAndSize(aData, format, size); + if (mSize.width > PlanarYCbCrImage::MAX_DIMENSION || + mSize.height > PlanarYCbCrImage::MAX_DIMENSION) { + NS_ERROR("Illegal image dest width or height"); + return nullptr; + } + + RefPtr surface = + gfx::Factory::CreateDataSourceSurface(size, format); + if (NS_WARN_IF(!surface)) { + return nullptr; + } + + DataSourceSurface::ScopedMap mapping(surface, DataSourceSurface::WRITE); + if (NS_WARN_IF(!mapping.IsMapped())) { + return nullptr; + } + + gfx::ConvertYCbCrToRGB(aData, format, size, mapping.GetData(), + mapping.GetStride()); + + mSourceSurface = surface; + + return surface.forget(); +} + +bool NVImage::IsValid() const { return !!mBufferSize; } + +uint32_t NVImage::GetBufferSize() const { return mBufferSize; } + +NVImage* NVImage::AsNVImage() { return this; }; + +bool NVImage::SetData(const Data& aData) { + MOZ_ASSERT(aData.mCbSkip == 1 && aData.mCrSkip == 1); + MOZ_ASSERT((int)std::abs(aData.mCbChannel - aData.mCrChannel) == 1); + + // Calculate buffer size + // Use uint32_t throughout to match AllocateBuffer's param and mBufferSize + const auto checkedSize = + CheckedInt(aData.mYSize.height) * aData.mYStride + + CheckedInt(aData.mCbCrSize.height) * aData.mCbCrStride; + + if (!checkedSize.isValid()) return false; + + const auto size = checkedSize.value(); + + // Allocate a new buffer. + mBuffer = AllocateBuffer(size); + if (!mBuffer) { + return false; + } + + // Update mBufferSize. + mBufferSize = size; + + // Update mData. + mData = aData; + mData.mYChannel = mBuffer.get(); + mData.mCbChannel = mData.mYChannel + (aData.mCbChannel - aData.mYChannel); + mData.mCrChannel = mData.mYChannel + (aData.mCrChannel - aData.mYChannel); + + // Update mSize. + mSize = aData.mPicSize; + + // Copy the input data into mBuffer. + // This copies the y-channel and the interleaving CbCr-channel. + memcpy(mData.mYChannel, aData.mYChannel, mBufferSize); + + return true; +} + +const NVImage::Data* NVImage::GetData() const { return &mData; } + +UniquePtr NVImage::AllocateBuffer(uint32_t aSize) { + UniquePtr buffer(new uint8_t[aSize]); + return buffer; +} + +SourceSurfaceImage::SourceSurfaceImage(const gfx::IntSize& aSize, + gfx::SourceSurface* aSourceSurface) + : Image(nullptr, ImageFormat::CAIRO_SURFACE), + mSize(aSize), + mSourceSurface(aSourceSurface), + mTextureFlags(TextureFlags::DEFAULT) {} + +SourceSurfaceImage::SourceSurfaceImage(gfx::SourceSurface* aSourceSurface) + : Image(nullptr, ImageFormat::CAIRO_SURFACE), + mSize(aSourceSurface->GetSize()), + mSourceSurface(aSourceSurface), + mTextureFlags(TextureFlags::DEFAULT) {} + +SourceSurfaceImage::~SourceSurfaceImage() { + NS_ReleaseOnMainThread("SourceSurfaceImage::mSourceSurface", + mSourceSurface.forget()); +} + +TextureClient* SourceSurfaceImage::GetTextureClient( + KnowsCompositor* aKnowsCompositor) { + if (!aKnowsCompositor) { + return nullptr; + } + + auto entry = mTextureClients.LookupForAdd(aKnowsCompositor->GetSerial()); + if (entry) { + return entry.Data(); + } + + RefPtr textureClient; + RefPtr surface = GetAsSourceSurface(); + MOZ_ASSERT(surface); + if (surface) { + // gfx::BackendType::NONE means default to content backend + textureClient = TextureClient::CreateFromSurface( + aKnowsCompositor, surface, BackendSelector::Content, mTextureFlags, + ALLOC_DEFAULT); + } + if (textureClient) { + textureClient->SyncWithObject(aKnowsCompositor->GetSyncObject()); + entry.OrInsert([&textureClient]() { return textureClient; }); + return textureClient; + } + + // Remove the speculatively added entry. + entry.OrRemove(); + return nullptr; +} + +ImageContainer::ProducerID ImageContainer::AllocateProducerID() { + // Callable on all threads. + static Atomic sProducerID(0u); + return ++sProducerID; +} + +} // namespace mozilla::layers diff --git a/gfx/layers/ImageContainer.h b/gfx/layers/ImageContainer.h new file mode 100644 index 0000000000..a7f9fcffc0 --- /dev/null +++ b/gfx/layers/ImageContainer.h @@ -0,0 +1,887 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_IMAGECONTAINER_H +#define GFX_IMAGECONTAINER_H + +#include // for int32_t, uint32_t, uint8_t, uint64_t +#include "ImageTypes.h" // for ImageFormat, etc +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Assertions.h" // for MOZ_ASSERT_HELPER2 +#include "mozilla/Mutex.h" // for Mutex +#include "mozilla/RecursiveMutex.h" // for RecursiveMutex, etc +#include "mozilla/ThreadSafeWeakPtr.h" +#include "mozilla/TimeStamp.h" // for TimeStamp +#include "mozilla/gfx/Point.h" // For IntSize +#include "mozilla/gfx/Rect.h" +#include "mozilla/gfx/Types.h" // For ColorDepth +#include "mozilla/layers/LayersTypes.h" // for LayersBackend, etc +#include "mozilla/layers/CompositorTypes.h" +#include "mozilla/mozalloc.h" // for operator delete, etc +#include "nsDebug.h" // for NS_ASSERTION +#include "nsISupportsImpl.h" // for Image::Release, etc +#include "nsTArray.h" // for nsTArray +#include "mozilla/Atomics.h" +#include "mozilla/gfx/2D.h" +#include "nsDataHashtable.h" +#include "mozilla/EnumeratedArray.h" +#include "mozilla/UniquePtr.h" +#include "MediaInfo.h" + +#ifdef XP_WIN +struct ID3D10Texture2D; +struct ID3D10Device; +struct ID3D10ShaderResourceView; +#endif + +typedef void* HANDLE; + +namespace mozilla { + +namespace layers { + +class ImageClient; +class ImageCompositeNotification; +class ImageContainer; +class ImageContainerChild; +class SharedPlanarYCbCrImage; +class SharedSurfacesAnimation; +class SurfaceDescriptor; +class PlanarYCbCrImage; +class TextureClient; +class TextureClientRecycleAllocator; +class KnowsCompositor; +class NVImage; +#ifdef XP_WIN +class D3D11YCbCrRecycleAllocator; +#endif +#ifdef XP_MACOSX +class MacIOSurfaceRecycleAllocator; +#endif +class SurfaceDescriptorBuffer; + +struct ImageBackendData { + virtual ~ImageBackendData() = default; + + protected: + ImageBackendData() = default; +}; + +/* Forward declarations for Image derivatives. */ +class GLImage; +class SharedRGBImage; +#ifdef MOZ_WIDGET_ANDROID +class SurfaceTextureImage; +#elif defined(XP_MACOSX) +class MacIOSurfaceImage; +#elif MOZ_WAYLAND +class DMABUFSurfaceImage; +#endif + +/** + * A class representing a buffer of pixel data. The data can be in one + * of various formats including YCbCr. + * + * Create an image using an ImageContainer. Fill the image with data, and + * then call ImageContainer::SetImage to display it. An image must not be + * modified after calling SetImage. Image implementations do not need to + * perform locking; when filling an Image, the Image client is responsible + * for ensuring only one thread accesses the Image at a time, and after + * SetImage the image is immutable. + * + * When resampling an Image, only pixels within the buffer should be + * sampled. For example, cairo images should be sampled in EXTEND_PAD mode. + */ +class Image { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Image) + + public: + ImageFormat GetFormat() const { return mFormat; } + void* GetImplData() const { return mImplData; } + + virtual gfx::IntSize GetSize() const = 0; + virtual gfx::IntPoint GetOrigin() const { return gfx::IntPoint(0, 0); } + virtual gfx::IntRect GetPictureRect() const { + return gfx::IntRect(GetOrigin().x, GetOrigin().y, GetSize().width, + GetSize().height); + } + + ImageBackendData* GetBackendData(LayersBackend aBackend) { + return mBackendData[aBackend].get(); + } + void SetBackendData(LayersBackend aBackend, ImageBackendData* aData) { + mBackendData[aBackend] = mozilla::WrapUnique(aData); + } + + int32_t GetSerial() const { return mSerial; } + + virtual already_AddRefed GetAsSourceSurface() = 0; + + virtual bool IsValid() const { return true; } + + /** + * For use with the TextureForwarder only (so that the later can + * synchronize the TextureClient with the TextureHost). + */ + virtual TextureClient* GetTextureClient(KnowsCompositor* aKnowsCompositor) { + return nullptr; + } + + /* Access to derived classes. */ + virtual GLImage* AsGLImage() { return nullptr; } +#ifdef MOZ_WIDGET_ANDROID + virtual SurfaceTextureImage* AsSurfaceTextureImage() { return nullptr; } +#endif +#ifdef XP_MACOSX + virtual MacIOSurfaceImage* AsMacIOSurfaceImage() { return nullptr; } +#endif + virtual PlanarYCbCrImage* AsPlanarYCbCrImage() { return nullptr; } +#ifdef MOZ_WAYLAND + virtual DMABUFSurfaceImage* AsDMABUFSurfaceImage() { return nullptr; } +#endif + + virtual NVImage* AsNVImage() { return nullptr; } + + virtual Maybe GetDesc(); + + protected: + Maybe GetDescFromTexClient( + TextureClient* tcOverride = nullptr); + + Image(void* aImplData, ImageFormat aFormat) + : mImplData(aImplData), mSerial(++sSerialCounter), mFormat(aFormat) {} + + // Protected destructor, to discourage deletion outside of Release(): + virtual ~Image() = default; + + mozilla::EnumeratedArray> + mBackendData; + + void* mImplData; + int32_t mSerial; + ImageFormat mFormat; + + static mozilla::Atomic sSerialCounter; +}; + +/** + * A RecycleBin is owned by an ImageContainer. We store buffers in it that we + * want to recycle from one image to the next.It's a separate object from + * ImageContainer because images need to store a strong ref to their RecycleBin + * and we must avoid creating a reference loop between an ImageContainer and + * its active image. + */ +class BufferRecycleBin final { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BufferRecycleBin) + + // typedef mozilla::gl::GLContext GLContext; + + public: + BufferRecycleBin(); + + void RecycleBuffer(mozilla::UniquePtr aBuffer, uint32_t aSize); + // Returns a recycled buffer of the right size, or allocates a new buffer. + mozilla::UniquePtr GetBuffer(uint32_t aSize); + virtual void ClearRecycledBuffers(); + + private: + typedef mozilla::Mutex Mutex; + + // Private destructor, to discourage deletion outside of Release(): + ~BufferRecycleBin() = default; + + // This protects mRecycledBuffers, mRecycledBufferSize, mRecycledTextures + // and mRecycledTextureSizes + Mutex mLock; + + // We should probably do something to prune this list on a timer so we don't + // eat excess memory while video is paused... + nsTArray> mRecycledBuffers; + // This is only valid if mRecycledBuffers is non-empty + uint32_t mRecycledBufferSize; +}; + +/** + * A class that manages Image creation for a LayerManager. The only reason + * we need a separate class here is that LayerManagers aren't threadsafe + * (because layers can only be used on the main thread) and we want to + * be able to create images from any thread, to facilitate video playback + * without involving the main thread, for example. + * Different layer managers can implement child classes of this making it + * possible to create layer manager specific images. + * This class is not meant to be used directly but rather can be set on an + * image container. This is usually done by the layer system internally and + * not explicitly by users. For PlanarYCbCr or Cairo images the default + * implementation will creates images whose data lives in system memory, for + * MacIOSurfaces the default implementation will be a simple MacIOSurface + * wrapper. + */ + +class ImageFactory { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ImageFactory) + protected: + friend class ImageContainer; + + ImageFactory() = default; + virtual ~ImageFactory() = default; + + virtual RefPtr CreatePlanarYCbCrImage( + const gfx::IntSize& aScaleHint, BufferRecycleBin* aRecycleBin); +}; + +// Used to notify ImageContainer::NotifyComposite() +class ImageContainerListener final { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ImageContainerListener) + + public: + explicit ImageContainerListener(ImageContainer* aImageContainer); + + void NotifyComposite(const ImageCompositeNotification& aNotification); + void NotifyDropped(uint32_t aDropped); + void ClearImageContainer(); + void DropImageClient(); + + private: + typedef mozilla::Mutex Mutex; + + ~ImageContainerListener(); + + Mutex mLock; + ImageContainer* mImageContainer; +}; + +/** + * A class that manages Images for an ImageLayer. The only reason + * we need a separate class here is that ImageLayers aren't threadsafe + * (because layers can only be used on the main thread) and we want to + * be able to set the current Image from any thread, to facilitate + * video playback without involving the main thread, for example. + * + * An ImageContainer can operate in one of these modes: + * 1) Normal. Triggered by constructing the ImageContainer with + * DISABLE_ASYNC or when compositing is happening on the main thread. + * SetCurrentImages changes ImageContainer state but nothing is sent to the + * compositor until the next layer transaction. + * 2) Asynchronous. Initiated by constructing the ImageContainer with + * ENABLE_ASYNC when compositing is happening on the main thread. + * SetCurrentImages sends a message through the ImageBridge to the compositor + * thread to update the image, without going through the main thread or + * a layer transaction. + * The ImageContainer uses a shared memory block containing a cross-process + * mutex to communicate with the compositor thread. SetCurrentImage + * synchronously updates the shared state to point to the new image and the old + * image is immediately released (not true in Normal or Asynchronous modes). + */ +class ImageContainer final : public SupportsThreadSafeWeakPtr { + friend class ImageContainerChild; + + public: + MOZ_DECLARE_THREADSAFEWEAKREFERENCE_TYPENAME(ImageContainer) + MOZ_DECLARE_REFCOUNTED_TYPENAME(ImageContainer) + + enum Mode { SYNCHRONOUS = 0x0, ASYNCHRONOUS = 0x01 }; + + static const uint64_t sInvalidAsyncContainerId = 0; + + explicit ImageContainer(ImageContainer::Mode flag = SYNCHRONOUS); + + /** + * Create ImageContainer just to hold another ASYNCHRONOUS ImageContainer's + * async container ID. + * @param aAsyncContainerID async container ID for which we are a proxy + */ + explicit ImageContainer(const CompositableHandle& aHandle); + + ~ImageContainer(); + + typedef ContainerFrameID FrameID; + typedef ContainerProducerID ProducerID; + + RefPtr CreatePlanarYCbCrImage(); + + // Factory methods for shared image types. + RefPtr CreateSharedRGBImage(); + + struct NonOwningImage { + explicit NonOwningImage(Image* aImage = nullptr, + TimeStamp aTimeStamp = TimeStamp(), + FrameID aFrameID = 0, ProducerID aProducerID = 0) + : mImage(aImage), + mTimeStamp(aTimeStamp), + mFrameID(aFrameID), + mProducerID(aProducerID) {} + Image* mImage; + TimeStamp mTimeStamp; + FrameID mFrameID; + ProducerID mProducerID; + }; + /** + * Set aImages as the list of timestamped to display. The Images must have + * been created by this ImageContainer. + * Can be called on any thread. This method takes mRecursiveMutex + * when accessing thread-shared state. + * aImages must be non-empty. The first timestamp in the list may be + * null but the others must not be, and the timestamps must increase. + * Every element of aImages must have non-null mImage. + * mFrameID can be zero, in which case you won't get meaningful + * painted/dropped frame counts. Otherwise you should use a unique and + * increasing ID for each decoded and submitted frame (but it's OK to + * pass the same frame to SetCurrentImages). + * mProducerID is a unique ID for the stream of images. A change in the + * mProducerID means changing to a new mFrameID namespace. All frames in + * aImages must have the same mProducerID. + * + * The Image data must not be modified after this method is called! + * Note that this must not be called if ENABLE_ASYNC has not been set. + * + * The implementation calls CurrentImageChanged() while holding + * mRecursiveMutex. + * + * If this ImageContainer has an ImageClient for async video: + * Schedule a task to send the image to the compositor using the + * PImageBridge protcol without using the main thread. + */ + void SetCurrentImages(const nsTArray& aImages); + + /** + * Clear all images. Let ImageClient release all TextureClients. + */ + void ClearAllImages(); + + /** + * Clear any resources that are not immediately necessary. This may be called + * in low-memory conditions. + */ + void ClearCachedResources(); + + /** + * Clear the current images. + * This function is expect to be called only from a CompositableClient + * that belongs to ImageBridgeChild. Created to prevent dead lock. + * See Bug 901224. + */ + void ClearImagesFromImageBridge(); + + /** + * Set an Image as the current image to display. The Image must have + * been created by this ImageContainer. + * Must be called on the main thread, within a layers transaction. + * + * This method takes mRecursiveMutex + * when accessing thread-shared state. + * aImage can be null. While it's null, nothing will be painted. + * + * The Image data must not be modified after this method is called! + * Note that this must not be called if ENABLE_ASYNC been set. + * + * You won't get meaningful painted/dropped counts when using this method. + */ + void SetCurrentImageInTransaction(Image* aImage); + void SetCurrentImagesInTransaction(const nsTArray& aImages); + + /** + * Returns true if this ImageContainer uses the ImageBridge IPDL protocol. + * + * Can be called from any thread. + */ + bool IsAsync() const; + + /** + * If this ImageContainer uses ImageBridge, returns the ID associated to + * this container, for use in the ImageBridge protocol. + * Returns 0 if this ImageContainer does not use ImageBridge. Note that + * 0 is always an invalid ID for asynchronous image containers. + * + * Can be called from any thread. + */ + CompositableHandle GetAsyncContainerHandle(); + + /** + * Returns if the container currently has an image. + * Can be called on any thread. This method takes mRecursiveMutex + * when accessing thread-shared state. + */ + bool HasCurrentImage(); + + struct OwningImage { + OwningImage() : mFrameID(0), mProducerID(0), mComposited(false) {} + RefPtr mImage; + TimeStamp mTimeStamp; + FrameID mFrameID; + ProducerID mProducerID; + bool mComposited; + }; + /** + * Copy the current Image list to aImages. + * This has to add references since otherwise there are race conditions + * where the current image is destroyed before the caller can add + * a reference. + * Can be called on any thread. + * May return an empty list to indicate there is no current image. + * If aGenerationCounter is non-null, sets *aGenerationCounter to a value + * that's unique for this ImageContainer state. + */ + void GetCurrentImages(nsTArray* aImages, + uint32_t* aGenerationCounter = nullptr); + + /** + * Returns the size of the image in pixels. + * Can be called on any thread. This method takes mRecursiveMutex when + * accessing thread-shared state. + */ + gfx::IntSize GetCurrentSize(); + + /** + * Sets a size that the image is expected to be rendered at. + * This is a hint for image backends to optimize scaling. + * Default implementation in this class is to ignore the hint. + * Can be called on any thread. This method takes mRecursiveMutex + * when accessing thread-shared state. + */ + void SetScaleHint(const gfx::IntSize& aScaleHint) { mScaleHint = aScaleHint; } + + const gfx::IntSize& GetScaleHint() const { return mScaleHint; } + + void SetTransformHint(const gfx::Matrix& aTransformHint) { + mTransformHint = aTransformHint; + } + + const gfx::Matrix& GetTransformHint() const { return mTransformHint; } + + void SetRotation(VideoInfo::Rotation aRotation) { mRotation = aRotation; } + + VideoInfo::Rotation GetRotation() const { return mRotation; } + + void SetImageFactory(ImageFactory* aFactory) { + RecursiveMutexAutoLock lock(mRecursiveMutex); + mImageFactory = aFactory ? aFactory : new ImageFactory(); + } + + ImageFactory* GetImageFactory() const { return mImageFactory; } + + void EnsureRecycleAllocatorForRDD(KnowsCompositor* aKnowsCompositor); + +#ifdef XP_WIN + D3D11YCbCrRecycleAllocator* GetD3D11YCbCrRecycleAllocator( + KnowsCompositor* aKnowsCompositor); +#endif + +#ifdef XP_MACOSX + MacIOSurfaceRecycleAllocator* GetMacIOSurfaceRecycleAllocator(); +#endif + + /** + * Returns the delay between the last composited image's presentation + * timestamp and when it was first composited. It's possible for the delay + * to be negative if the first image in the list passed to SetCurrentImages + * has a presentation timestamp greater than "now". + * Returns 0 if the composited image had a null timestamp, or if no + * image has been composited yet. + */ + TimeDuration GetPaintDelay() { + RecursiveMutexAutoLock lock(mRecursiveMutex); + return mPaintDelay; + } + + /** + * Returns the number of images which have been contained in this container + * and painted at least once. Can be called from any thread. + */ + uint32_t GetPaintCount() { + RecursiveMutexAutoLock lock(mRecursiveMutex); + return mPaintCount; + } + + /** + * An entry in the current image list "expires" when the entry has an + * non-null timestamp, and in a SetCurrentImages call the new image list is + * non-empty, the timestamp of the first new image is non-null and greater + * than the timestamp associated with the image, and the first new image's + * frameID is not the same as the entry's. + * Every expired image that is never composited is counted as dropped. + */ + uint32_t GetDroppedImageCount() { return mDroppedImageCount; } + + void NotifyComposite(const ImageCompositeNotification& aNotification); + void NotifyDropped(uint32_t aDropped); + + ImageContainerListener* GetImageContainerListener() { + return mNotifyCompositeListener; + } + + /** + * Get the ImageClient associated with this container. Returns only after + * validating, and it will recreate the image client if that fails. + * Returns nullptr if not applicable. + */ + already_AddRefed GetImageClient(); + + /** + * Main thread only. + */ + static ProducerID AllocateProducerID(); + + void DropImageClient(); + + SharedSurfacesAnimation* GetSharedSurfacesAnimation() const { + return mSharedAnimation; + } + + SharedSurfacesAnimation* EnsureSharedSurfacesAnimation(); + + private: + typedef mozilla::RecursiveMutex RecursiveMutex; + + void SetCurrentImageInternal(const nsTArray& aImages); + + // This is called to ensure we have an active image, this may not be true + // when we're storing image information in a RemoteImageData structure. + // NOTE: If we have remote data mRemoteDataMutex should be locked when + // calling this function! + void EnsureActiveImage(); + + void EnsureImageClient(); + + // RecursiveMutex to protect thread safe access to the "current + // image", and any other state which is shared between threads. + RecursiveMutex mRecursiveMutex; + + RefPtr mRecycleAllocator; + +#ifdef XP_WIN + RefPtr mD3D11YCbCrRecycleAllocator; +#endif +#ifdef XP_MACOSX + RefPtr mMacIOSurfaceRecycleAllocator; +#endif + + nsTArray mCurrentImages; + + // Updates every time mActiveImage changes + uint32_t mGenerationCounter; + + // Number of contained images that have been painted at least once. It's up + // to the ImageContainer implementation to ensure accesses to this are + // threadsafe. + uint32_t mPaintCount; + + // See GetPaintDelay. Accessed only with mRecursiveMutex held. + TimeDuration mPaintDelay; + + // See GetDroppedImageCount. + mozilla::Atomic mDroppedImageCount; + + // This is the image factory used by this container, layer managers using + // this container can set an alternative image factory that will be used to + // create images for this container. + RefPtr mImageFactory; + + gfx::IntSize mScaleHint; + + gfx::Matrix mTransformHint; + + VideoInfo::Rotation mRotation = VideoInfo::Rotation::kDegree_0; + + RefPtr mRecycleBin; + + // This member points to an ImageClient if this ImageContainer was + // sucessfully created with ENABLE_ASYNC, or points to null otherwise. + // 'unsuccessful' in this case only means that the ImageClient could not + // be created, most likely because off-main-thread compositing is not enabled. + // In this case the ImageContainer is perfectly usable, but it will forward + // frames to the compositor through transactions in the main thread rather + // than asynchronusly using the ImageBridge IPDL protocol. + RefPtr mImageClient; + + RefPtr mSharedAnimation; + + bool mIsAsync; + CompositableHandle mAsyncContainerHandle; + + // ProducerID for last current image(s) + ProducerID mCurrentProducerID; + + RefPtr mNotifyCompositeListener; + + static mozilla::Atomic sGenerationCounter; +}; + +class AutoLockImage { + public: + explicit AutoLockImage(ImageContainer* aContainer) { + aContainer->GetCurrentImages(&mImages); + } + + bool HasImage() const { return !mImages.IsEmpty(); } + Image* GetImage() const { + return mImages.IsEmpty() ? nullptr : mImages[0].mImage.get(); + } + + Image* GetImage(TimeStamp aTimeStamp) const { + if (mImages.IsEmpty()) { + return nullptr; + } + + MOZ_ASSERT(!aTimeStamp.IsNull()); + uint32_t chosenIndex = 0; + + while (chosenIndex + 1 < mImages.Length() && + mImages[chosenIndex + 1].mTimeStamp <= aTimeStamp) { + ++chosenIndex; + } + + return mImages[chosenIndex].mImage.get(); + } + + private: + AutoTArray mImages; +}; + +struct PlanarYCbCrData { + // Luminance buffer + uint8_t* mYChannel = nullptr; + int32_t mYStride = 0; + gfx::IntSize mYSize = gfx::IntSize(0, 0); + int32_t mYSkip = 0; + // Chroma buffers + uint8_t* mCbChannel = nullptr; + uint8_t* mCrChannel = nullptr; + int32_t mCbCrStride = 0; + gfx::IntSize mCbCrSize = gfx::IntSize(0, 0); + int32_t mCbSkip = 0; + int32_t mCrSkip = 0; + // Picture region + uint32_t mPicX = 0; + uint32_t mPicY = 0; + gfx::IntSize mPicSize = gfx::IntSize(0, 0); + StereoMode mStereoMode = StereoMode::MONO; + gfx::ColorDepth mColorDepth = gfx::ColorDepth::COLOR_8; + gfx::YUVColorSpace mYUVColorSpace = gfx::YUVColorSpace::UNKNOWN; + gfx::ColorRange mColorRange = gfx::ColorRange::LIMITED; + + gfx::IntRect GetPictureRect() const { + return gfx::IntRect(mPicX, mPicY, mPicSize.width, mPicSize.height); + } +}; + +// This type is currently only used for AVIF and therefore makes some +// AVIF-specific assumptions (e.g., Alpha's bpc and stride is equal to Y's one) +struct PlanarYCbCrAData : PlanarYCbCrData { + uint8_t* mAlphaChannel = nullptr; + gfx::IntSize mAlphaSize = gfx::IntSize(0, 0); + bool mPremultipliedAlpha = false; + + bool hasAlpha() { return mAlphaChannel; } +}; + +/****** Image subtypes for the different formats ******/ + +/** + * We assume that the image data is in the REC 470M color space (see + * Theora specification, section 4.3.1). + * + * The YCbCr format can be: + * + * 4:4:4 - CbCr width/height are the same as Y. + * 4:2:2 - CbCr width is half that of Y. Height is the same. + * 4:2:0 - CbCr width and height is half that of Y. + * + * The color format is detected based on the height/width ratios + * defined above. + * + * The Image that is rendered is the picture region defined by + * mPicX, mPicY and mPicSize. The size of the rendered image is + * mPicSize, not mYSize or mCbCrSize. + * + * mYSkip, mCbSkip, mCrSkip are added to support various output + * formats from hardware decoder. They are per-pixel skips in the + * source image. + * + * For example when image width is 640, mYStride is 670, mYSkip is 2, + * the mYChannel buffer looks like: + * + * |<----------------------- mYStride ----------------------------->| + * |<----------------- mYSize.width --------------->| + * 0 3 6 9 12 15 18 21 639 669 + * |----------------------------------------------------------------| + * |Y___Y___Y___Y___Y___Y___Y___Y... |%%%%%%%%%%%%%%%| + * |Y___Y___Y___Y___Y___Y___Y___Y... |%%%%%%%%%%%%%%%| + * |Y___Y___Y___Y___Y___Y___Y___Y... |%%%%%%%%%%%%%%%| + * | |<->| + * mYSkip + */ +class PlanarYCbCrImage : public Image { + public: + typedef PlanarYCbCrData Data; + + enum { MAX_DIMENSION = 16384 }; + + virtual ~PlanarYCbCrImage(); + + /** + * This makes a copy of the data buffers, in order to support functioning + * in all different layer managers. + */ + virtual bool CopyData(const Data& aData) = 0; + + /** + * This doesn't make a copy of the data buffers. + */ + virtual bool AdoptData(const Data& aData); + + /** + * Ask this Image to not convert YUV to RGB during SetData, and make + * the original data available through GetData. This is optional, + * and not all PlanarYCbCrImages will support it. + */ + virtual void SetDelayedConversion(bool aDelayed) {} + + /** + * Grab the original YUV data. This is optional. + */ + virtual const Data* GetData() const { return &mData; } + + /** + * Return the number of bytes of heap memory used to store this image. + */ + uint32_t GetDataSize() const { return mBufferSize; } + + bool IsValid() const override { return !!mBufferSize; } + + gfx::IntSize GetSize() const override { return mSize; } + + gfx::IntPoint GetOrigin() const override { return mOrigin; } + + PlanarYCbCrImage(); + + virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); + } + + virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const = 0; + + PlanarYCbCrImage* AsPlanarYCbCrImage() override { return this; } + + /** + * Build a SurfaceDescriptorBuffer with this image. The provided + * SurfaceDescriptorBuffer must already have a valid MemoryOrShmem set + * with a capacity large enough to hold |GetDataSize|. + */ + virtual nsresult BuildSurfaceDescriptorBuffer( + SurfaceDescriptorBuffer& aSdBuffer); + + protected: + already_AddRefed GetAsSourceSurface() override; + + void SetOffscreenFormat(gfxImageFormat aFormat) { + mOffscreenFormat = aFormat; + } + gfxImageFormat GetOffscreenFormat() const; + + Data mData; + gfx::IntPoint mOrigin; + gfx::IntSize mSize; + gfxImageFormat mOffscreenFormat; + RefPtr mSourceSurface; + uint32_t mBufferSize; +}; + +class RecyclingPlanarYCbCrImage : public PlanarYCbCrImage { + public: + explicit RecyclingPlanarYCbCrImage(BufferRecycleBin* aRecycleBin) + : mRecycleBin(aRecycleBin) {} + virtual ~RecyclingPlanarYCbCrImage(); + bool CopyData(const Data& aData) override; + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override; + + protected: + /** + * Return a buffer to store image data in. + */ + mozilla::UniquePtr AllocateBuffer(uint32_t aSize); + + RefPtr mRecycleBin; + mozilla::UniquePtr mBuffer; +}; + +/** + * NVImage is used to store YUV420SP_NV12 and YUV420SP_NV21 data natively, which + * are not supported by PlanarYCbCrImage. (PlanarYCbCrImage only stores YUV444P, + * YUV422P and YUV420P, it converts YUV420SP_NV12 and YUV420SP_NV21 data into + * YUV420P in its PlanarYCbCrImage::SetData() method.) + * + * PlanarYCbCrData is able to express all the YUV family and so we keep use it + * in NVImage. + */ +class NVImage final : public Image { + typedef PlanarYCbCrData Data; + + public: + NVImage(); + virtual ~NVImage(); + + // Methods inherited from layers::Image. + gfx::IntSize GetSize() const override; + gfx::IntRect GetPictureRect() const override; + already_AddRefed GetAsSourceSurface() override; + bool IsValid() const override; + NVImage* AsNVImage() override; + + // Methods mimic layers::PlanarYCbCrImage. + bool SetData(const Data& aData); + const Data* GetData() const; + uint32_t GetBufferSize() const; + + protected: + /** + * Return a buffer to store image data in. + */ + mozilla::UniquePtr AllocateBuffer(uint32_t aSize); + + mozilla::UniquePtr mBuffer; + uint32_t mBufferSize; + gfx::IntSize mSize; + Data mData; + RefPtr mSourceSurface; +}; + +/** + * Currently, the data in a SourceSurfaceImage surface is treated as being in + * the device output color space. This class is very simple as all backends have + * to know about how to deal with drawing a cairo image. + */ +class SourceSurfaceImage final : public Image { + public: + already_AddRefed GetAsSourceSurface() override { + RefPtr surface(mSourceSurface); + return surface.forget(); + } + + void SetTextureFlags(TextureFlags aTextureFlags) { + mTextureFlags = aTextureFlags; + } + TextureClient* GetTextureClient(KnowsCompositor* aKnowsCompositor) override; + + gfx::IntSize GetSize() const override { return mSize; } + + SourceSurfaceImage(const gfx::IntSize& aSize, + gfx::SourceSurface* aSourceSurface); + explicit SourceSurfaceImage(gfx::SourceSurface* aSourceSurface); + virtual ~SourceSurfaceImage(); + + private: + gfx::IntSize mSize; + RefPtr mSourceSurface; + nsDataHashtable> mTextureClients; + TextureFlags mTextureFlags; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/ImageDataSerializer.cpp b/gfx/layers/ImageDataSerializer.cpp new file mode 100644 index 0000000000..804680a9e8 --- /dev/null +++ b/gfx/layers/ImageDataSerializer.cpp @@ -0,0 +1,338 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ImageDataSerializer.h" + +#include "YCbCrUtils.h" // for YCbCr conversions +#include "gfx2DGlue.h" // for SurfaceFormatToImageFormat +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/gfx/2D.h" // for DataSourceSurface, Factory +#include "mozilla/gfx/Logging.h" // for gfxDebug +#include "mozilla/gfx/Tools.h" // for GetAlignedStride, etc +#include "mozilla/gfx/Types.h" +#include "mozilla/mozalloc.h" // for operator delete, etc + +namespace mozilla { +namespace layers { +namespace ImageDataSerializer { + +using namespace gfx; + +int32_t ComputeRGBStride(SurfaceFormat aFormat, int32_t aWidth) { +#ifdef XP_MACOSX + // Some drivers require an alignment of 32 bytes for efficient texture upload. + return GetAlignedStride<32>(aWidth, BytesPerPixel(aFormat)); +#else + return GetAlignedStride<4>(aWidth, BytesPerPixel(aFormat)); +#endif +} + +int32_t GetRGBStride(const RGBDescriptor& aDescriptor) { + return ComputeRGBStride(aDescriptor.format(), aDescriptor.size().width); +} + +uint32_t ComputeRGBBufferSize(IntSize aSize, SurfaceFormat aFormat) { + MOZ_ASSERT(aSize.height >= 0 && aSize.width >= 0); + + // This takes care of checking whether there could be overflow + // with enough margin for the metadata. + if (!gfx::Factory::AllowedSurfaceSize(aSize)) { + return 0; + } + + // Note we're passing height instad of the bpp parameter, but the end + // result is the same - and the bpp was already taken care of in the + // ComputeRGBStride function. + int32_t bufsize = GetAlignedStride<16>(ComputeRGBStride(aFormat, aSize.width), + aSize.height); + + if (bufsize < 0) { + // This should not be possible thanks to Factory::AllowedSurfaceSize + return 0; + } + + return bufsize; +} + +// Minimum required shmem size in bytes +uint32_t ComputeYCbCrBufferSize(const gfx::IntSize& aYSize, int32_t aYStride, + const gfx::IntSize& aCbCrSize, + int32_t aCbCrStride) { + MOZ_ASSERT(aYSize.height >= 0 && aYSize.width >= 0); + + if (aYSize.height < 0 || aYSize.width < 0 || aCbCrSize.height < 0 || + aCbCrSize.width < 0 || + !gfx::Factory::AllowedSurfaceSize(IntSize(aYStride, aYSize.height)) || + !gfx::Factory::AllowedSurfaceSize( + IntSize(aCbCrStride, aCbCrSize.height))) { + return 0; + } + + // Overflow checks are performed in AllowedSurfaceSize + return GetAlignedStride<4>(aYSize.height, aYStride) + + 2 * GetAlignedStride<4>(aCbCrSize.height, aCbCrStride); +} + +uint32_t ComputeYCbCrBufferSize(const gfx::IntSize& aYSize, int32_t aYStride, + const gfx::IntSize& aCbCrSize, + int32_t aCbCrStride, uint32_t aYOffset, + uint32_t aCbOffset, uint32_t aCrOffset) { + MOZ_ASSERT(aYSize.height >= 0 && aYSize.width >= 0); + + if (aYSize.height < 0 || aYSize.width < 0 || aCbCrSize.height < 0 || + aCbCrSize.width < 0 || + !gfx::Factory::AllowedSurfaceSize(IntSize(aYStride, aYSize.height)) || + !gfx::Factory::AllowedSurfaceSize( + IntSize(aCbCrStride, aCbCrSize.height))) { + return 0; + } + + uint32_t yLength = GetAlignedStride<4>(aYStride, aYSize.height); + uint32_t cbCrLength = GetAlignedStride<4>(aCbCrStride, aCbCrSize.height); + if (yLength == 0 || cbCrLength == 0) { + return 0; + } + + CheckedInt yEnd = aYOffset; + yEnd += yLength; + CheckedInt cbEnd = aCbOffset; + cbEnd += cbCrLength; + CheckedInt crEnd = aCrOffset; + crEnd += cbCrLength; + + if (!yEnd.isValid() || !cbEnd.isValid() || !crEnd.isValid() || + yEnd.value() > aCbOffset || cbEnd.value() > aCrOffset) { + return 0; + } + + return crEnd.value(); +} + +uint32_t ComputeYCbCrBufferSize(uint32_t aBufferSize) { + return GetAlignedStride<4>(aBufferSize, 1); +} + +void ComputeYCbCrOffsets(int32_t yStride, int32_t yHeight, int32_t cbCrStride, + int32_t cbCrHeight, uint32_t& outYOffset, + uint32_t& outCbOffset, uint32_t& outCrOffset) { + outYOffset = 0; + outCbOffset = outYOffset + GetAlignedStride<4>(yStride, yHeight); + outCrOffset = outCbOffset + GetAlignedStride<4>(cbCrStride, cbCrHeight); +} + +gfx::SurfaceFormat FormatFromBufferDescriptor( + const BufferDescriptor& aDescriptor) { + switch (aDescriptor.type()) { + case BufferDescriptor::TRGBDescriptor: + return aDescriptor.get_RGBDescriptor().format(); + case BufferDescriptor::TYCbCrDescriptor: + return gfx::SurfaceFormat::YUV; + default: + MOZ_CRASH("GFX: FormatFromBufferDescriptor"); + } +} + +gfx::IntSize SizeFromBufferDescriptor(const BufferDescriptor& aDescriptor) { + switch (aDescriptor.type()) { + case BufferDescriptor::TRGBDescriptor: + return aDescriptor.get_RGBDescriptor().size(); + case BufferDescriptor::TYCbCrDescriptor: { + return aDescriptor.get_YCbCrDescriptor().display().Size(); + } + default: + MOZ_CRASH("GFX: SizeFromBufferDescriptor"); + } +} + +gfx::IntRect RectFromBufferDescriptor(const BufferDescriptor& aDescriptor) { + switch (aDescriptor.type()) { + case BufferDescriptor::TRGBDescriptor: { + auto size = aDescriptor.get_RGBDescriptor().size(); + return gfx::IntRect(0, 0, size.Width(), size.Height()); + } + case BufferDescriptor::TYCbCrDescriptor: + return aDescriptor.get_YCbCrDescriptor().display(); + default: + MOZ_CRASH("GFX: RectFromBufferDescriptor"); + } +} + +Maybe CbCrSizeFromBufferDescriptor( + const BufferDescriptor& aDescriptor) { + switch (aDescriptor.type()) { + case BufferDescriptor::TRGBDescriptor: + return Nothing(); + case BufferDescriptor::TYCbCrDescriptor: + return Some(aDescriptor.get_YCbCrDescriptor().cbCrSize()); + default: + MOZ_CRASH("GFX: CbCrSizeFromBufferDescriptor"); + } +} + +Maybe YStrideFromBufferDescriptor( + const BufferDescriptor& aDescriptor) { + switch (aDescriptor.type()) { + case BufferDescriptor::TRGBDescriptor: + return Nothing(); + case BufferDescriptor::TYCbCrDescriptor: + return Some(aDescriptor.get_YCbCrDescriptor().yStride()); + default: + MOZ_CRASH("GFX: YStrideFromBufferDescriptor"); + } +} + +Maybe CbCrStrideFromBufferDescriptor( + const BufferDescriptor& aDescriptor) { + switch (aDescriptor.type()) { + case BufferDescriptor::TRGBDescriptor: + return Nothing(); + case BufferDescriptor::TYCbCrDescriptor: + return Some(aDescriptor.get_YCbCrDescriptor().cbCrStride()); + default: + MOZ_CRASH("GFX: CbCrStrideFromBufferDescriptor"); + } +} + +Maybe YUVColorSpaceFromBufferDescriptor( + const BufferDescriptor& aDescriptor) { + switch (aDescriptor.type()) { + case BufferDescriptor::TRGBDescriptor: + return Nothing(); + case BufferDescriptor::TYCbCrDescriptor: + return Some(aDescriptor.get_YCbCrDescriptor().yUVColorSpace()); + default: + MOZ_CRASH("GFX: YUVColorSpaceFromBufferDescriptor"); + } +} + +Maybe ColorDepthFromBufferDescriptor( + const BufferDescriptor& aDescriptor) { + switch (aDescriptor.type()) { + case BufferDescriptor::TRGBDescriptor: + return Nothing(); + case BufferDescriptor::TYCbCrDescriptor: + return Some(aDescriptor.get_YCbCrDescriptor().colorDepth()); + default: + MOZ_CRASH("GFX: ColorDepthFromBufferDescriptor"); + } +} + +Maybe ColorRangeFromBufferDescriptor( + const BufferDescriptor& aDescriptor) { + switch (aDescriptor.type()) { + case BufferDescriptor::TRGBDescriptor: + return Nothing(); + case BufferDescriptor::TYCbCrDescriptor: + return Some(aDescriptor.get_YCbCrDescriptor().colorRange()); + default: + MOZ_CRASH("GFX: YUVFullRangeFromBufferDescriptor"); + } +} + +Maybe StereoModeFromBufferDescriptor( + const BufferDescriptor& aDescriptor) { + switch (aDescriptor.type()) { + case BufferDescriptor::TRGBDescriptor: + return Nothing(); + case BufferDescriptor::TYCbCrDescriptor: + return Some(aDescriptor.get_YCbCrDescriptor().stereoMode()); + default: + MOZ_CRASH("GFX: StereoModeFromBufferDescriptor"); + } +} + +uint8_t* GetYChannel(uint8_t* aBuffer, const YCbCrDescriptor& aDescriptor) { + return aBuffer + aDescriptor.yOffset(); +} + +uint8_t* GetCbChannel(uint8_t* aBuffer, const YCbCrDescriptor& aDescriptor) { + return aBuffer + aDescriptor.cbOffset(); +} + +uint8_t* GetCrChannel(uint8_t* aBuffer, const YCbCrDescriptor& aDescriptor) { + return aBuffer + aDescriptor.crOffset(); +} + +already_AddRefed DataSourceSurfaceFromYCbCrDescriptor( + uint8_t* aBuffer, const YCbCrDescriptor& aDescriptor, + gfx::DataSourceSurface* aSurface) { + const gfx::IntRect display = aDescriptor.display(); + const gfx::IntSize size = display.Size(); + RefPtr result; + if (aSurface) { + MOZ_ASSERT(aSurface->GetSize() == size); + MOZ_ASSERT(aSurface->GetFormat() == gfx::SurfaceFormat::B8G8R8X8); + if (aSurface->GetSize() == size && + aSurface->GetFormat() == gfx::SurfaceFormat::B8G8R8X8) { + result = aSurface; + } + } + + if (!result) { + result = + Factory::CreateDataSourceSurface(size, gfx::SurfaceFormat::B8G8R8X8); + } + if (NS_WARN_IF(!result)) { + return nullptr; + } + + DataSourceSurface::MappedSurface map; + if (NS_WARN_IF(!result->Map(DataSourceSurface::MapType::WRITE, &map))) { + return nullptr; + } + + layers::PlanarYCbCrData ycbcrData; + ycbcrData.mYChannel = GetYChannel(aBuffer, aDescriptor); + ycbcrData.mYStride = aDescriptor.yStride(); + ycbcrData.mYSize = aDescriptor.ySize(); + ycbcrData.mCbChannel = GetCbChannel(aBuffer, aDescriptor); + ycbcrData.mCrChannel = GetCrChannel(aBuffer, aDescriptor); + ycbcrData.mCbCrStride = aDescriptor.cbCrStride(); + ycbcrData.mCbCrSize = aDescriptor.cbCrSize(); + ycbcrData.mPicSize = size; + ycbcrData.mPicX = display.X(); + ycbcrData.mPicY = display.Y(); + ycbcrData.mYUVColorSpace = aDescriptor.yUVColorSpace(); + ycbcrData.mColorDepth = aDescriptor.colorDepth(); + + gfx::ConvertYCbCrToRGB(ycbcrData, gfx::SurfaceFormat::B8G8R8X8, size, + map.mData, map.mStride); + + result->Unmap(); + return result.forget(); +} + +void ConvertAndScaleFromYCbCrDescriptor(uint8_t* aBuffer, + const YCbCrDescriptor& aDescriptor, + const gfx::SurfaceFormat& aDestFormat, + const gfx::IntSize& aDestSize, + unsigned char* aDestBuffer, + int32_t aStride) { + MOZ_ASSERT(aBuffer); + + const gfx::IntRect display = aDescriptor.display(); + + layers::PlanarYCbCrData ycbcrData; + ycbcrData.mYChannel = GetYChannel(aBuffer, aDescriptor); + ycbcrData.mYStride = aDescriptor.yStride(); + ycbcrData.mYSize = aDescriptor.ySize(); + ycbcrData.mCbChannel = GetCbChannel(aBuffer, aDescriptor); + ycbcrData.mCrChannel = GetCrChannel(aBuffer, aDescriptor); + ycbcrData.mCbCrStride = aDescriptor.cbCrStride(); + ycbcrData.mCbCrSize = aDescriptor.cbCrSize(); + ycbcrData.mPicSize = display.Size(); + ycbcrData.mPicX = display.X(); + ycbcrData.mPicY = display.Y(); + ycbcrData.mYUVColorSpace = aDescriptor.yUVColorSpace(); + ycbcrData.mColorDepth = aDescriptor.colorDepth(); + + gfx::ConvertYCbCrToRGB(ycbcrData, aDestFormat, aDestSize, aDestBuffer, + aStride); +} + +} // namespace ImageDataSerializer +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ImageDataSerializer.h b/gfx/layers/ImageDataSerializer.h new file mode 100644 index 0000000000..d402e0c8df --- /dev/null +++ b/gfx/layers/ImageDataSerializer.h @@ -0,0 +1,105 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_LAYERS_BLOBSURFACE_H +#define GFX_LAYERS_BLOBSURFACE_H + +#include // for uint8_t, uint32_t +#include "mozilla/Attributes.h" // for MOZ_STACK_CLASS +#include "mozilla/RefPtr.h" // for already_AddRefed +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/gfx/Rect.h" // for IntRect +#include "mozilla/gfx/Types.h" // for SurfaceFormat +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor + +namespace mozilla { +namespace gfx { +class DataSourceSurface; +class DrawTarget; +} // namespace gfx +} // namespace mozilla + +namespace mozilla { +namespace layers { + +namespace ImageDataSerializer { + +// RGB + +int32_t ComputeRGBStride(gfx::SurfaceFormat aFormat, int32_t aWidth); + +int32_t GetRGBStride(const RGBDescriptor& aDescriptor); + +uint32_t ComputeRGBBufferSize(gfx::IntSize aSize, gfx::SurfaceFormat aFormat); + +// YCbCr + +/// This function is meant as a helper to know how much shared memory we need +/// to allocate in a shmem in order to place a shared YCbCr image blob of +/// given dimensions. +uint32_t ComputeYCbCrBufferSize(const gfx::IntSize& aYSize, int32_t aYStride, + const gfx::IntSize& aCbCrSize, + int32_t aCbCrStride); +uint32_t ComputeYCbCrBufferSize(const gfx::IntSize& aYSize, int32_t aYStride, + const gfx::IntSize& aCbCrSize, + int32_t aCbCrStride, uint32_t aYOffset, + uint32_t aCbOffset, uint32_t aCrOffset); +uint32_t ComputeYCbCrBufferSize(uint32_t aBufferSize); + +void ComputeYCbCrOffsets(int32_t yStride, int32_t yHeight, int32_t cbCrStride, + int32_t cbCrHeight, uint32_t& outYOffset, + uint32_t& outCbOffset, uint32_t& outCrOffset); + +gfx::SurfaceFormat FormatFromBufferDescriptor( + const BufferDescriptor& aDescriptor); + +gfx::IntSize SizeFromBufferDescriptor(const BufferDescriptor& aDescriptor); + +gfx::IntRect RectFromBufferDescriptor(const BufferDescriptor& aDescriptor); + +Maybe CbCrSizeFromBufferDescriptor( + const BufferDescriptor& aDescriptor); + +Maybe YStrideFromBufferDescriptor(const BufferDescriptor& aDescriptor); + +Maybe CbCrStrideFromBufferDescriptor( + const BufferDescriptor& aDescriptor); + +Maybe YUVColorSpaceFromBufferDescriptor( + const BufferDescriptor& aDescriptor); + +Maybe ColorDepthFromBufferDescriptor( + const BufferDescriptor& aDescriptor); + +Maybe ColorRangeFromBufferDescriptor( + const BufferDescriptor& aDescriptor); + +Maybe StereoModeFromBufferDescriptor( + const BufferDescriptor& aDescriptor); + +uint8_t* GetYChannel(uint8_t* aBuffer, const YCbCrDescriptor& aDescriptor); + +uint8_t* GetCbChannel(uint8_t* aBuffer, const YCbCrDescriptor& aDescriptor); + +uint8_t* GetCrChannel(uint8_t* aBuffer, const YCbCrDescriptor& aDescriptor); + +already_AddRefed DataSourceSurfaceFromYCbCrDescriptor( + uint8_t* aBuffer, const YCbCrDescriptor& aDescriptor, + gfx::DataSourceSurface* aSurface = nullptr); + +void ConvertAndScaleFromYCbCrDescriptor(uint8_t* aBuffer, + const YCbCrDescriptor& aDescriptor, + const gfx::SurfaceFormat& aDestFormat, + const gfx::IntSize& aDestSize, + unsigned char* aDestBuffer, + int32_t aStride); + +} // namespace ImageDataSerializer + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/ImageLayers.cpp b/gfx/layers/ImageLayers.cpp new file mode 100644 index 0000000000..f58ec1e0ba --- /dev/null +++ b/gfx/layers/ImageLayers.cpp @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ImageLayers.h" +#include "ImageContainer.h" // for ImageContainer +#include "gfxRect.h" // for gfxRect +#include "nsDebug.h" // for NS_ASSERTION +#include "nsISupportsImpl.h" // for ImageContainer::Release, etc +#include "gfx2DGlue.h" + +namespace mozilla { +namespace layers { + +ImageLayer::ImageLayer(LayerManager* aManager, void* aImplData) + : Layer(aManager, aImplData), + mSamplingFilter(gfx::SamplingFilter::GOOD), + mScaleMode(ScaleMode::SCALE_NONE) {} + +ImageLayer::~ImageLayer() = default; + +void ImageLayer::SetContainer(ImageContainer* aContainer) { + mContainer = aContainer; +} + +void ImageLayer::ComputeEffectiveTransforms( + const gfx::Matrix4x4& aTransformToSurface) { + gfx::Matrix4x4 local = GetLocalTransform(); + + // Snap image edges to pixel boundaries + gfxRect sourceRect(0, 0, 0, 0); + if (mContainer) { + sourceRect.SizeTo(gfx::SizeDouble(mContainer->GetCurrentSize())); + } + // Snap our local transform first, and snap the inherited transform as well. + // This makes our snapping equivalent to what would happen if our content + // was drawn into a PaintedLayer (gfxContext would snap using the local + // transform, then we'd snap again when compositing the PaintedLayer). + mEffectiveTransform = SnapTransform(local, sourceRect, nullptr) * + SnapTransformTranslation(aTransformToSurface, nullptr); + + if (mScaleMode != ScaleMode::SCALE_NONE && !sourceRect.IsZeroArea()) { + NS_ASSERTION(mScaleMode == ScaleMode::STRETCH, + "No other scalemodes than stretch and none supported yet."); + local.PreScale(mScaleToSize.width / sourceRect.Width(), + mScaleToSize.height / sourceRect.Height(), 1.0); + + mEffectiveTransformForBuffer = + SnapTransform(local, sourceRect, nullptr) * + SnapTransformTranslation(aTransformToSurface, nullptr); + } else { + mEffectiveTransformForBuffer = mEffectiveTransform; + } + + ComputeEffectiveTransformForMaskLayers(aTransformToSurface); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ImageLayers.h b/gfx/layers/ImageLayers.h new file mode 100644 index 0000000000..90e59fc25b --- /dev/null +++ b/gfx/layers/ImageLayers.h @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_IMAGELAYER_H +#define GFX_IMAGELAYER_H + +#include "Layers.h" // for Layer, etc +#include "mozilla/gfx/BaseSize.h" // for BaseSize +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/layers/LayersTypes.h" +#include "nscore.h" // for nsACString + +namespace mozilla { +namespace layers { + +class ImageContainer; + +namespace layerscope { +class LayersPacket; +} // namespace layerscope + +/** + * A Layer which renders an Image. + */ +class ImageLayer : public Layer { + public: + /** + * CONSTRUCTION PHASE ONLY + * Set the ImageContainer. aContainer must have the same layer manager + * as this layer. + */ + virtual void SetContainer(ImageContainer* aContainer); + + /** + * CONSTRUCTION PHASE ONLY + * Set the filter used to resample this image if necessary. + */ + void SetSamplingFilter(gfx::SamplingFilter aSamplingFilter) { + if (mSamplingFilter != aSamplingFilter) { + MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) Filter", this)); + mSamplingFilter = aSamplingFilter; + Mutated(); + } + } + + /** + * CONSTRUCTION PHASE ONLY + * Set the size to scale the image to and the mode at which to scale. + */ + void SetScaleToSize(const gfx::IntSize& aSize, ScaleMode aMode) { + if (mScaleToSize != aSize || mScaleMode != aMode) { + mScaleToSize = aSize; + mScaleMode = aMode; + Mutated(); + } + } + + ImageContainer* GetContainer() { return mContainer; } + gfx::SamplingFilter GetSamplingFilter() { return mSamplingFilter; } + const gfx::IntSize& GetScaleToSize() { return mScaleToSize; } + ScaleMode GetScaleMode() { return mScaleMode; } + + MOZ_LAYER_DECL_NAME("ImageLayer", TYPE_IMAGE) + + void ComputeEffectiveTransforms( + const gfx::Matrix4x4& aTransformToSurface) override; + + const gfx::Matrix4x4& GetEffectiveTransformForBuffer() const override { + return mEffectiveTransformForBuffer; + } + + ImageLayer* AsImageLayer() override { return this; } + + protected: + ImageLayer(LayerManager* aManager, void* aImplData); + virtual ~ImageLayer(); + void PrintInfo(std::stringstream& aStream, const char* aPrefix) override; + void DumpPacket(layerscope::LayersPacket* aPacket, + const void* aParent) override; + + RefPtr mContainer; + gfx::SamplingFilter mSamplingFilter; + gfx::IntSize mScaleToSize; + ScaleMode mScaleMode; + gfx::Matrix4x4 mEffectiveTransformForBuffer; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_IMAGELAYER_H */ diff --git a/gfx/layers/ImageTypes.h b/gfx/layers/ImageTypes.h new file mode 100644 index 0000000000..3beff0d476 --- /dev/null +++ b/gfx/layers/ImageTypes.h @@ -0,0 +1,122 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_IMAGETYPES_H +#define GFX_IMAGETYPES_H + +#include // for uint32_t + +namespace mozilla { + +enum class ImageFormat { + /** + * The PLANAR_YCBCR format creates a PlanarYCbCrImage. All backends should + * support this format, because the Ogg video decoder depends on it. + * The maximum image width and height is 16384. + */ + PLANAR_YCBCR, + + /** + * The NV_IMAGE format creates a NVImage. The PLANAR_YCBCR together with this + * complete the YUV format family. + */ + NV_IMAGE, + + /** + * The SHARED_RGB format creates a SharedRGBImage, which stores RGB data in + * shared memory. Some Android hardware video decoders require this format. + * Currently only used on Android. + */ + SHARED_RGB, + + /** + * The CAIRO_SURFACE format creates a SourceSurfaceImage. All backends should + * support this format, because video rendering sometimes requires it. + * + * This format is useful even though a PaintedLayer could be used. + * It makes it easy to render a cairo surface when another Image format + * could be used. It can also avoid copying the surface data in some + * cases. + * + * Images in CAIRO_SURFACE format should only be created and + * manipulated on the main thread, since the underlying cairo surface + * is main-thread-only. + */ + CAIRO_SURFACE, + + /** + * A MacIOSurface object. + */ + MAC_IOSURFACE, + + /** + * An Android SurfaceTexture ID that can be shared across threads and + * processes. + */ + SURFACE_TEXTURE, + + /** + * The D3D9_RGB32_TEXTURE format creates a D3D9SurfaceImage, and wraps a + * IDirect3DTexture9 in RGB32 layout. + */ + D3D9_RGB32_TEXTURE, + + /** + * An Image type carries an opaque handle once for each stream. + * The opaque handle would be a platform specific identifier. + */ + OVERLAY_IMAGE, + + /** + * A share handle to a ID3D11Texture2D. + */ + D3D11_SHARE_HANDLE_TEXTURE, + + /** + * A wrapper around a drawable TextureClient. + */ + TEXTURE_WRAPPER, + + /** + * A D3D11 backed YUV image. + */ + D3D11_YCBCR_IMAGE, + + /** + * An opaque handle that refers to an Image stored in the GPU + * process. + */ + GPU_VIDEO, + + /** + * The DMABUF format creates a SharedDMABUFImage, which stores YUV + * data in DMABUF memory. Used by VAAPI decoder on Linux. + */ + DMABUF, +}; + +enum class StereoMode { + MONO, + LEFT_RIGHT, + RIGHT_LEFT, + BOTTOM_TOP, + TOP_BOTTOM, + MAX, +}; + +namespace layers { + +typedef uint32_t ContainerFrameID; +constexpr ContainerFrameID kContainerFrameID_Invalid = 0; + +typedef uint32_t ContainerProducerID; +constexpr ContainerProducerID kContainerProducerID_Invalid = 0; + +} // namespace layers + +} // namespace mozilla + +#endif diff --git a/gfx/layers/LayerAttributes.h b/gfx/layers/LayerAttributes.h new file mode 100644 index 0000000000..e76a1678c7 --- /dev/null +++ b/gfx/layers/LayerAttributes.h @@ -0,0 +1,409 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_layers_LayerAttributes_h +#define mozilla_gfx_layers_LayerAttributes_h + +#include "FrameMetrics.h" +#include "mozilla/Maybe.h" +#include "mozilla/gfx/Types.h" +#include "mozilla/layers/LayersTypes.h" +#include "mozilla/layers/ScrollableLayerGuid.h" + +namespace IPC { +template +struct ParamTraits; +} // namespace IPC + +namespace mozilla { +namespace layers { + +// clang-format off +MOZ_DEFINE_ENUM_CLASS_WITH_BASE(ScrollbarLayerType, uint8_t, ( + None, + Thumb, + Container +)); +// clang-format on + +/** + * It stores data for scroll thumb layer or container layers. + */ +struct ScrollbarData { + private: + /** + * This constructor is for Thumb layer type. + */ + ScrollbarData(ScrollDirection aDirection, float aThumbRatio, + CSSCoord aThumbStart, CSSCoord aThumbLength, + bool aThumbIsAsyncDraggable, CSSCoord aScrollTrackStart, + CSSCoord aScrollTrackLength, uint64_t aTargetViewId) + : mDirection(Some(aDirection)), + mScrollbarLayerType(ScrollbarLayerType::Thumb), + mThumbRatio(aThumbRatio), + mThumbStart(aThumbStart), + mThumbLength(aThumbLength), + mThumbIsAsyncDraggable(aThumbIsAsyncDraggable), + mScrollTrackStart(aScrollTrackStart), + mScrollTrackLength(aScrollTrackLength), + mTargetViewId(aTargetViewId) {} + + /** + * This constructor is for Container layer type. + */ + ScrollbarData(const Maybe& aDirection, + uint64_t aTargetViewId) + : mDirection(aDirection), + mScrollbarLayerType(ScrollbarLayerType::Container), + mTargetViewId(aTargetViewId) {} + + public: + ScrollbarData() = default; + + static ScrollbarData CreateForThumb(ScrollDirection aDirection, + float aThumbRatio, CSSCoord aThumbStart, + CSSCoord aThumbLength, + bool aThumbIsAsyncDraggable, + CSSCoord aScrollTrackStart, + CSSCoord aScrollTrackLength, + uint64_t aTargetViewId) { + return ScrollbarData(aDirection, aThumbRatio, aThumbStart, aThumbLength, + aThumbIsAsyncDraggable, aScrollTrackStart, + aScrollTrackLength, aTargetViewId); + } + + static ScrollbarData CreateForScrollbarContainer( + const Maybe& aDirection, uint64_t aTargetViewId) { + return ScrollbarData(aDirection, aTargetViewId); + } + + /** + * The mDirection contains a direction if mScrollbarLayerType is Thumb + * or Container, otherwise it's empty. + */ + Maybe mDirection; + + /** + * Indicate what kind of layer this data is for. All possibilities are defined + * in enum ScrollbarLayerType + */ + ScrollbarLayerType mScrollbarLayerType = ScrollbarLayerType::None; + + /** + * The scrollbar thumb ratio is the ratio of the thumb position (in the CSS + * pixels of the scrollframe's parent's space) to the scroll position (in the + * CSS pixels of the scrollframe's space). + */ + float mThumbRatio = 0.0f; + + CSSCoord mThumbStart; + CSSCoord mThumbLength; + + /** + * Whether the scrollbar thumb can be dragged asynchronously. + */ + bool mThumbIsAsyncDraggable = false; + + CSSCoord mScrollTrackStart; + CSSCoord mScrollTrackLength; + uint64_t mTargetViewId = ScrollableLayerGuid::NULL_SCROLL_ID; + + bool operator==(const ScrollbarData& aOther) const { + return mDirection == aOther.mDirection && + mScrollbarLayerType == aOther.mScrollbarLayerType && + mThumbRatio == aOther.mThumbRatio && + mThumbStart == aOther.mThumbStart && + mThumbLength == aOther.mThumbLength && + mThumbIsAsyncDraggable == aOther.mThumbIsAsyncDraggable && + mScrollTrackStart == aOther.mScrollTrackStart && + mScrollTrackLength == aOther.mScrollTrackLength && + mTargetViewId == aOther.mTargetViewId; + } + bool operator!=(const ScrollbarData& aOther) const { + return !(*this == aOther); + } + + bool IsThumb() const { + return mScrollbarLayerType == ScrollbarLayerType::Thumb; + } +}; + +/** + * Infrequently changing layer attributes. + */ +class SimpleLayerAttributes final { + friend struct IPC::ParamTraits; + + public: + SimpleLayerAttributes() + : mTransformIsPerspective(false), + mPostXScale(1.0f), + mPostYScale(1.0f), + mContentFlags(0), + mOpacity(1.0f), + mIsFixedPosition(false), + mMixBlendMode(gfx::CompositionOp::OP_OVER), + mForceIsolatedGroup(false) {} + + /** + * Setters. + * All set methods return true if values changed, false otherwise. + */ + + bool SetPostScale(float aXScale, float aYScale) { + if (mPostXScale == aXScale && mPostYScale == aYScale) { + return false; + } + mPostXScale = aXScale; + mPostYScale = aYScale; + return true; + } + + bool SetContentFlags(uint32_t aFlags) { + if (aFlags == mContentFlags) { + return false; + } + mContentFlags = aFlags; + return true; + } + + bool SetOpacity(float aOpacity) { + if (aOpacity == mOpacity) { + return false; + } + mOpacity = aOpacity; + return true; + } + + bool SetIsFixedPosition(bool aFixedPosition) { + if (mIsFixedPosition == aFixedPosition) { + return false; + } + mIsFixedPosition = aFixedPosition; + return true; + } + + bool SetIsAsyncZoomContainer(const Maybe& aViewId) { + if (mIsAsyncZoomContainerForViewId == aViewId) { + return false; + } + mIsAsyncZoomContainerForViewId = aViewId; + return true; + } + + bool SetScrollbarData(const ScrollbarData& aScrollbarData) { + if (mScrollbarData == aScrollbarData) { + return false; + } + mScrollbarData = aScrollbarData; + return true; + } + + bool SetMixBlendMode(gfx::CompositionOp aMixBlendMode) { + if (mMixBlendMode == aMixBlendMode) { + return false; + } + mMixBlendMode = aMixBlendMode; + return true; + } + + bool SetForceIsolatedGroup(bool aForceIsolatedGroup) { + if (mForceIsolatedGroup == aForceIsolatedGroup) { + return false; + } + mForceIsolatedGroup = aForceIsolatedGroup; + return true; + } + + bool SetTransform(const gfx::Matrix4x4& aMatrix) { + if (mTransform == aMatrix) { + return false; + } + mTransform = aMatrix; + return true; + } + + bool SetTransformIsPerspective(bool aIsPerspective) { + if (mTransformIsPerspective == aIsPerspective) { + return false; + } + mTransformIsPerspective = aIsPerspective; + return true; + } + + bool SetScrolledClip(const Maybe& aScrolledClip) { + if (mScrolledClip == aScrolledClip) { + return false; + } + mScrolledClip = aScrolledClip; + return true; + } + + bool SetFixedPositionData(ScrollableLayerGuid::ViewID aTargetViewId, + const LayerPoint& aAnchor, SideBits aSides) { + if (mFixedPositionData && mFixedPositionData->mScrollId == aTargetViewId && + mFixedPositionData->mAnchor == aAnchor && + mFixedPositionData->mSides == aSides) { + return false; + } + if (!mFixedPositionData) { + mFixedPositionData.emplace(); + } + mFixedPositionData->mScrollId = aTargetViewId; + mFixedPositionData->mAnchor = aAnchor; + mFixedPositionData->mSides = aSides; + return true; + } + + bool SetStickyPositionData(ScrollableLayerGuid::ViewID aScrollId, + LayerRectAbsolute aOuter, + LayerRectAbsolute aInner) { + if (mStickyPositionData && + mStickyPositionData->mOuter.IsEqualEdges(aOuter) && + mStickyPositionData->mInner.IsEqualEdges(aInner)) { + return false; + } + if (!mStickyPositionData) { + mStickyPositionData.emplace(); + } + mStickyPositionData->mScrollId = aScrollId; + mStickyPositionData->mOuter = aOuter; + mStickyPositionData->mInner = aInner; + return true; + } + + /** + * This returns true if scrolling info is equivalent for the purposes of + * APZ hit testing. + */ + bool HitTestingInfoIsEqual(const SimpleLayerAttributes& aOther) const { + if (mScrollbarData != aOther.mScrollbarData) { + return false; + } + if (GetFixedPositionScrollContainerId() != + aOther.GetFixedPositionScrollContainerId()) { + return false; + } + if (mTransform != aOther.mTransform) { + return false; + } + return true; + } + + /** + * Getters. + */ + + float GetPostXScale() const { return mPostXScale; } + + float GetPostYScale() const { return mPostYScale; } + + uint32_t GetContentFlags() const { return mContentFlags; } + + float GetOpacity() const { return mOpacity; } + + bool IsFixedPosition() const { return mIsFixedPosition; } + + Maybe IsAsyncZoomContainer() const { + return mIsAsyncZoomContainerForViewId; + } + + const ScrollbarData& GetScrollbarData() const { return mScrollbarData; } + + gfx::CompositionOp GetMixBlendMode() const { return mMixBlendMode; } + + bool GetForceIsolatedGroup() const { return mForceIsolatedGroup; } + + const gfx::Matrix4x4& GetTransform() const { return mTransform; } + + bool GetTransformIsPerspective() const { return mTransformIsPerspective; } + + const Maybe& GetScrolledClip() const { return mScrolledClip; } + + ScrollableLayerGuid::ViewID GetFixedPositionScrollContainerId() const { + return (mIsFixedPosition && mFixedPositionData) + ? mFixedPositionData->mScrollId + : ScrollableLayerGuid::NULL_SCROLL_ID; + } + + LayerPoint GetFixedPositionAnchor() const { + return mFixedPositionData ? mFixedPositionData->mAnchor : LayerPoint(); + } + + SideBits GetFixedPositionSides() const { + return mFixedPositionData ? mFixedPositionData->mSides : SideBits::eNone; + } + + bool IsStickyPosition() const { return !!mStickyPositionData; } + + ScrollableLayerGuid::ViewID GetStickyScrollContainerId() const { + return mStickyPositionData->mScrollId; + } + + const LayerRectAbsolute& GetStickyScrollRangeOuter() const { + return mStickyPositionData->mOuter; + } + + const LayerRectAbsolute& GetStickyScrollRangeInner() const { + return mStickyPositionData->mInner; + } + + bool operator==(const SimpleLayerAttributes& aOther) const { + return mTransform == aOther.mTransform && + mTransformIsPerspective == aOther.mTransformIsPerspective && + mScrolledClip == aOther.mScrolledClip && + mPostXScale == aOther.mPostXScale && + mPostYScale == aOther.mPostYScale && + mContentFlags == aOther.mContentFlags && + mOpacity == aOther.mOpacity && + mIsFixedPosition == aOther.mIsFixedPosition && + mIsAsyncZoomContainerForViewId == + aOther.mIsAsyncZoomContainerForViewId && + mScrollbarData == aOther.mScrollbarData && + mMixBlendMode == aOther.mMixBlendMode && + mForceIsolatedGroup == aOther.mForceIsolatedGroup; + } + + private: + gfx::Matrix4x4 mTransform; + bool mTransformIsPerspective; + Maybe mScrolledClip; + float mPostXScale; + float mPostYScale; + uint32_t mContentFlags; + float mOpacity; + bool mIsFixedPosition; + Maybe mIsAsyncZoomContainerForViewId; + ScrollbarData mScrollbarData; + gfx::CompositionOp mMixBlendMode; + bool mForceIsolatedGroup; + + struct FixedPositionData { + ScrollableLayerGuid::ViewID mScrollId; + LayerPoint mAnchor; + SideBits mSides; + }; + Maybe mFixedPositionData; + friend struct IPC::ParamTraits< + mozilla::layers::SimpleLayerAttributes::FixedPositionData>; + + struct StickyPositionData { + ScrollableLayerGuid::ViewID mScrollId; + LayerRectAbsolute mOuter; + LayerRectAbsolute mInner; + }; + Maybe mStickyPositionData; + friend struct IPC::ParamTraits< + mozilla::layers::SimpleLayerAttributes::StickyPositionData>; + + // Make sure to add new members to operator== and the ParamTraits template + // instantiation. +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_gfx_layers_LayerAttributes_h diff --git a/gfx/layers/LayerManager.cpp b/gfx/layers/LayerManager.cpp new file mode 100644 index 0000000000..a701aa1674 --- /dev/null +++ b/gfx/layers/LayerManager.cpp @@ -0,0 +1,216 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/LayerManager.h" + +#include // for uint64_t, uint8_t +#include // for abort +#include // for copy, copy_backward +#include // for move, forward +#include "FrameMetrics.h" // for FrameMetrics +#include "ImageContainer.h" // for ImageContainer, ImageContainer::Mode +#include "LayerUserData.h" // for LayerUserData +#include "Layers.h" // for RecordCompositionPayloadsPresented, Layer +#include "TreeTraversal.h" // for ForwardIterator, BreadthFirstSearch +#include "gfxPlatform.h" // for gfxPlatform +#include "mozilla/AlreadyAddRefed.h" // for already_AddRefed +#include "mozilla/ArrayIterator.h" // for ArrayIterator +#include "mozilla/Assertions.h" // for AssertionConditionType, MOZ_ASSERT, MOZ_ASSERT_HELPER1 +#include "mozilla/EffectSet.h" // for EffectSet +#include "mozilla/Logging.h" // for LazyLogModule, LogModule (ptr only) +#include "mozilla/RefPtr.h" // for RefPtr, getter_AddRefs, RefPtrGetterAddRefs +#include "mozilla/StaticPrefs_layers.h" // for layers_componentalpha_enabled_AtStartup_DoNotUseDirectly +#include "mozilla/UniquePtr.h" // for UniquePtr +#include "mozilla/dom/Animation.h" // for Animation +#include "mozilla/dom/AnimationEffect.h" // for AnimationEffect +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/gfx/Types.h" // for SurfaceFormat, gfx +#include "mozilla/gfx/UserData.h" // for UserData, UserDataKey (ptr only) +#include "mozilla/layers/LayerMetricsWrapper.h" // for LayerMetricsWrapper +#include "mozilla/layers/LayersTypes.h" // for CompositionPayload +#include "mozilla/layers/PersistentBufferProvider.h" // for PersistentBufferProviderBasic, PersistentBufferProvider (ptr only) +#include "mozilla/layers/ScrollableLayerGuid.h" // for ScrollableLayerGuid, ScrollableLayerGuid::NULL_SCROLL_ID, ScrollableLayerGu... +#include "nsHashKeys.h" // for nsUint64HashKey +#include "nsRefPtrHashtable.h" // for nsRefPtrHashtable +#include "nsTArray.h" // for nsTArray + +uint8_t gLayerManagerLayerBuilder; + +// Undo the damage done by mozzconf.h +#undef compress +#include "mozilla/Compression.h" + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; +using namespace mozilla::Compression; + +//-------------------------------------------------- +// LayerManager + +LayerManager::LayerManager() + : mDestroyed(false), + mSnapEffectiveTransforms(true), + mId(0), + mInTransaction(false), + mContainsSVG(false), + mPaintedPixelCount(0) {} + +LayerManager::~LayerManager() = default; + +void LayerManager::Destroy() { + mDestroyed = true; + mUserData.Destroy(); + mRoot = nullptr; + mPartialPrerenderedAnimations.Clear(); +} + +/* static */ mozilla::LogModule* LayerManager::GetLog() { + static LazyLogModule sLog("Layers"); + return sLog; +} + +ScrollableLayerGuid::ViewID LayerManager::GetRootScrollableLayerId() { + if (!mRoot) { + return ScrollableLayerGuid::NULL_SCROLL_ID; + } + + LayerMetricsWrapper layerMetricsRoot = LayerMetricsWrapper(mRoot); + + LayerMetricsWrapper rootScrollableLayerMetrics = + BreadthFirstSearch( + layerMetricsRoot, [](LayerMetricsWrapper aLayerMetrics) { + return aLayerMetrics.Metrics().IsScrollable(); + }); + + return rootScrollableLayerMetrics.IsValid() + ? rootScrollableLayerMetrics.Metrics().GetScrollId() + : ScrollableLayerGuid::NULL_SCROLL_ID; +} + +LayerMetricsWrapper LayerManager::GetRootContentLayer() { + if (!mRoot) { + return LayerMetricsWrapper(); + } + + LayerMetricsWrapper root(mRoot); + + return BreadthFirstSearch( + root, [](LayerMetricsWrapper aLayerMetrics) { + return aLayerMetrics.Metrics().IsRootContent(); + }); +} + +already_AddRefed LayerManager::CreateOptimalDrawTarget( + const gfx::IntSize& aSize, SurfaceFormat aFormat) { + return gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(aSize, + aFormat); +} + +already_AddRefed LayerManager::CreateOptimalMaskDrawTarget( + const gfx::IntSize& aSize) { + return CreateOptimalDrawTarget(aSize, SurfaceFormat::A8); +} + +already_AddRefed LayerManager::CreateDrawTarget( + const IntSize& aSize, SurfaceFormat aFormat) { + return gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget(aSize, + aFormat); +} + +already_AddRefed +LayerManager::CreatePersistentBufferProvider( + const mozilla::gfx::IntSize& aSize, mozilla::gfx::SurfaceFormat aFormat) { + RefPtr bufferProvider = + PersistentBufferProviderBasic::Create( + aSize, aFormat, + gfxPlatform::GetPlatform()->GetPreferredCanvasBackend()); + + if (!bufferProvider) { + bufferProvider = PersistentBufferProviderBasic::Create( + aSize, aFormat, gfxPlatform::GetPlatform()->GetFallbackCanvasBackend()); + } + + return bufferProvider.forget(); +} + +already_AddRefed LayerManager::CreateImageContainer( + ImageContainer::Mode flag) { + RefPtr container = new ImageContainer(flag); + return container.forget(); +} + +bool LayerManager::LayersComponentAlphaEnabled() { + // If MOZ_GFX_OPTIMIZE_MOBILE is defined, we force component alpha off + // and ignore the preference. +#ifdef MOZ_GFX_OPTIMIZE_MOBILE + return false; +#else + return StaticPrefs:: + layers_componentalpha_enabled_AtStartup_DoNotUseDirectly(); +#endif +} + +bool LayerManager::AreComponentAlphaLayersEnabled() { + return LayerManager::LayersComponentAlphaEnabled(); +} + +/*static*/ +void LayerManager::LayerUserDataDestroy(void* data) { + delete static_cast(data); +} + +UniquePtr LayerManager::RemoveUserData(void* aKey) { + UniquePtr d(static_cast( + mUserData.Remove(static_cast(aKey)))); + return d; +} + +void LayerManager::PayloadPresented(const TimeStamp& aTimeStamp) { + RecordCompositionPayloadsPresented(aTimeStamp, mPayload); +} + +void LayerManager::AddPartialPrerenderedAnimation( + uint64_t aCompositorAnimationId, dom::Animation* aAnimation) { + mPartialPrerenderedAnimations.Put(aCompositorAnimationId, RefPtr{aAnimation}); + aAnimation->SetPartialPrerendered(aCompositorAnimationId); +} + +void LayerManager::RemovePartialPrerenderedAnimation( + uint64_t aCompositorAnimationId, dom::Animation* aAnimation) { + MOZ_ASSERT(aAnimation); +#ifdef DEBUG + RefPtr animation; + if (mPartialPrerenderedAnimations.Remove(aCompositorAnimationId, + getter_AddRefs(animation)) && + // It may be possible that either animation's effect has already been + // nulled out via Animation::SetEffect() so ignore such cases. + aAnimation->GetEffect() && aAnimation->GetEffect()->AsKeyframeEffect() && + animation->GetEffect() && animation->GetEffect()->AsKeyframeEffect()) { + MOZ_ASSERT(EffectSet::GetEffectSetForEffect( + aAnimation->GetEffect()->AsKeyframeEffect()) == + EffectSet::GetEffectSetForEffect( + animation->GetEffect()->AsKeyframeEffect())); + } +#else + mPartialPrerenderedAnimations.Remove(aCompositorAnimationId); +#endif + aAnimation->ResetPartialPrerendered(); +} + +void LayerManager::UpdatePartialPrerenderedAnimations( + const nsTArray& aJankedAnimations) { + for (uint64_t id : aJankedAnimations) { + RefPtr animation; + if (mPartialPrerenderedAnimations.Remove(id, getter_AddRefs(animation))) { + animation->UpdatePartialPrerendered(); + } + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/LayerManager.h b/gfx/layers/LayerManager.h new file mode 100644 index 0000000000..a6ecd9e689 --- /dev/null +++ b/gfx/layers/LayerManager.h @@ -0,0 +1,788 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_LAYERMANAGER_H +#define GFX_LAYERMANAGER_H + +#include // for uint32_t, uint64_t, int32_t, uint8_t +#include // for stringstream +#include // for operator new +#include // for unordered_set +#include // for forward +#include "FrameMetrics.h" // for ScrollUpdatesMap +#include "ImageContainer.h" // for ImageContainer, ImageContainer::Mode, ImageContainer::SYNCHRONOUS +#include "mozilla/AlreadyAddRefed.h" // for already_AddRefed +#include "mozilla/Maybe.h" // for Maybe +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/TimeStamp.h" // for TimeStamp +#include "mozilla/UniquePtr.h" // for UniquePtr +#include "mozilla/dom/Animation.h" // for Animation +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/gfx/Types.h" // for SurfaceFormat +#include "mozilla/gfx/UserData.h" // for UserData, UserDataKey (ptr only) +#include "mozilla/layers/CompositorTypes.h" // for TextureFactoryIdentifier +#include "mozilla/layers/ScrollableLayerGuid.h" // for ScrollableLayerGuid, ScrollableLayerGuid::ViewID +#include "nsHashKeys.h" // for nsUint64HashKey +#include "nsISupports.h" // for NS_INLINE_DECL_REFCOUNTING +#include "nsIWidget.h" // for nsIWidget +#include "nsRefPtrHashtable.h" // for nsRefPtrHashtable +#include "nsRegion.h" // for nsIntRegion +#include "nsStringFwd.h" // for nsCString, nsAString +#include "nsTArray.h" // for nsTArray + +// XXX These includes could be avoided by moving function implementations to the +// cpp file +#include "mozilla/Assertions.h" // for AssertionConditionType, MOZ_ASSERT, MOZ_A... +#include "mozilla/layers/LayersTypes.h" // for CompositionPayload, LayersBackend, TransactionId, DrawRegionClip, LayersBackend:... + +class gfxContext; + +extern uint8_t gLayerManagerLayerBuilder; + +namespace mozilla { + +class FrameLayerBuilder; +class LogModule; +class ScrollPositionUpdate; + +namespace gfx { +class DrawTarget; +} // namespace gfx + +namespace layers { + +class AsyncPanZoomController; +class BasicLayerManager; +class ClientLayerManager; +class HostLayerManager; +class Layer; +class LayerMetricsWrapper; +class PaintedLayer; +class ContainerLayer; +class ImageLayer; +class ColorLayer; +class CompositorBridgeChild; +class CanvasLayer; +class ReadbackLayer; +class ReadbackProcessor; +class RefLayer; +class HostLayer; +class FocusTarget; +class KnowsCompositor; +class ShadowableLayer; +class ShadowLayerForwarder; +class LayerManagerComposite; +class TransactionIdAllocator; +class FrameUniformityData; +class PersistentBufferProvider; +class GlyphArray; +class WebRenderLayerManager; +struct LayerPolygon; +struct AnimData; + +namespace layerscope { +class LayersPacket; +} // namespace layerscope + +// Defined in LayerUserData.h; please include that file instead. +class LayerUserData; + +class DidCompositeObserver { + public: + virtual void DidComposite() = 0; +}; + +class FrameRecorder { + public: + /** + * Record (and return) frame-intervals and paint-times for frames which were + * presented between calling StartFrameTimeRecording and + * StopFrameTimeRecording. + * + * - Uses a cyclic buffer and serves concurrent consumers, so if Stop is + * called too late + * (elements were overwritten since Start), result is considered invalid + * and hence empty.) + * - Buffer is capable of holding 10 seconds @ 60fps (or more if frames were + * less frequent). + * Can be changed (up to 1 hour) via pref: + * toolkit.framesRecording.bufferSize. + * - Note: the first frame-interval may be longer than expected because last + * frame + * might have been presented some time before calling + * StartFrameTimeRecording. + */ + + /** + * Returns a handle which represents current recording start position. + */ + virtual uint32_t StartFrameTimeRecording(int32_t aBufferSize); + + /** + * Clears, then populates aFrameIntervals with the recorded frame timing + * data. The array will be empty if data was overwritten since + * aStartIndex was obtained. + */ + virtual void StopFrameTimeRecording(uint32_t aStartIndex, + nsTArray& aFrameIntervals); + + void RecordFrame(); + + private: + struct FramesTimingRecording { + // Stores state and data for frame intervals and paint times recording. + // see LayerManager::StartFrameTimeRecording() at Layers.cpp for more + // details. + FramesTimingRecording() + : mNextIndex(0), + mLatestStartIndex(0), + mCurrentRunStartIndex(0), + mIsPaused(true) {} + nsTArray mIntervals; + TimeStamp mLastFrameTime; + uint32_t mNextIndex; + uint32_t mLatestStartIndex; + uint32_t mCurrentRunStartIndex; + bool mIsPaused; + }; + FramesTimingRecording mRecording; +}; + +/* + * Motivation: For truly smooth animation and video playback, we need to + * be able to compose frames and render them on a dedicated thread (i.e. + * off the main thread where DOM manipulation, script execution and layout + * induce difficult-to-bound latency). This requires Gecko to construct + * some kind of persistent scene structure (graph or tree) that can be + * safely transmitted across threads. We have other scenarios (e.g. mobile + * browsing) where retaining some rendered data between paints is desired + * for performance, so again we need a retained scene structure. + * + * Our retained scene structure is a layer tree. Each layer represents + * content which can be composited onto a destination surface; the root + * layer is usually composited into a window, and non-root layers are + * composited into their parent layers. Layers have attributes (e.g. + * opacity and clipping) that influence their compositing. + * + * We want to support a variety of layer implementations, including + * a simple "immediate mode" implementation that doesn't retain any + * rendered data between paints (i.e. uses cairo in just the way that + * Gecko used it before layers were introduced). But we also don't want + * to have bifurcated "layers"/"non-layers" rendering paths in Gecko. + * Therefore the layers API is carefully designed to permit maximally + * efficient implementation in an "immediate mode" style. See the + * BasicLayerManager for such an implementation. + */ + +/** + * A LayerManager controls a tree of layers. All layers in the tree + * must use the same LayerManager. + * + * All modifications to a layer tree must happen inside a transaction. + * Only the state of the layer tree at the end of a transaction is + * rendered. Transactions cannot be nested + * + * Each transaction has two phases: + * 1) Construction: layers are created, inserted, removed and have + * properties set on them in this phase. + * BeginTransaction and BeginTransactionWithTarget start a transaction in + * the Construction phase. + * 2) Drawing: PaintedLayers are rendered into in this phase, in tree + * order. When the client has finished drawing into the PaintedLayers, it should + * call EndTransaction to complete the transaction. + * + * All layer API calls happen on the main thread. + * + * Layers are refcounted. The layer manager holds a reference to the + * root layer, and each container layer holds a reference to its children. + */ +class LayerManager : public FrameRecorder { + NS_INLINE_DECL_REFCOUNTING(LayerManager) + + protected: + typedef mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::gfx::IntSize IntSize; + typedef mozilla::gfx::SurfaceFormat SurfaceFormat; + + public: + LayerManager(); + + /** + * Release layers and resources held by this layer manager, and mark + * it as destroyed. Should do any cleanup necessary in preparation + * for its widget going away. After this call, only user data calls + * are valid on the layer manager. + */ + virtual void Destroy(); + bool IsDestroyed() { return mDestroyed; } + + virtual ShadowLayerForwarder* AsShadowForwarder() { return nullptr; } + + virtual KnowsCompositor* AsKnowsCompositor() { return nullptr; } + + virtual LayerManagerComposite* AsLayerManagerComposite() { return nullptr; } + + virtual ClientLayerManager* AsClientLayerManager() { return nullptr; } + + virtual BasicLayerManager* AsBasicLayerManager() { return nullptr; } + virtual HostLayerManager* AsHostLayerManager() { return nullptr; } + + virtual WebRenderLayerManager* AsWebRenderLayerManager() { return nullptr; } + + /** + * Returns true if this LayerManager is owned by an nsIWidget, + * and is used for drawing into the widget. + */ + virtual bool IsWidgetLayerManager() { return true; } + virtual bool IsInactiveLayerManager() { return false; } + + /** + * Start a new transaction. Nested transactions are not allowed so + * there must be no transaction currently in progress. + * This transaction will update the state of the window from which + * this LayerManager was obtained. + */ + virtual bool BeginTransaction(const nsCString& aURL = nsCString()) = 0; + /** + * Start a new transaction. Nested transactions are not allowed so + * there must be no transaction currently in progress. + * This transaction will render the contents of the layer tree to + * the given target context. The rendering will be complete when + * EndTransaction returns. + */ + virtual bool BeginTransactionWithTarget( + gfxContext* aTarget, const nsCString& aURL = nsCString()) = 0; + + enum EndTransactionFlags { + END_DEFAULT = 0, + END_NO_IMMEDIATE_REDRAW = 1 << 0, // Do not perform the drawing phase + END_NO_COMPOSITE = + 1 << 1, // Do not composite after drawing painted layer contents. + END_NO_REMOTE_COMPOSITE = 1 << 2 // Do not schedule a composition with a + // remote Compositor, if one exists. + }; + + FrameLayerBuilder* GetLayerBuilder() { + return reinterpret_cast( + GetUserData(&gLayerManagerLayerBuilder)); + } + + /** + * Attempts to end an "empty transaction". There must have been no + * changes to the layer tree since the BeginTransaction(). + * It's possible for this to fail; PaintedLayers may need to be updated + * due to VRAM data being lost, for example. In such cases this method + * returns false, and the caller must proceed with a normal layer tree + * update and EndTransaction. + */ + virtual bool EndEmptyTransaction( + EndTransactionFlags aFlags = END_DEFAULT) = 0; + + /** + * Function called to draw the contents of each PaintedLayer. + * aRegionToDraw contains the region that needs to be drawn. + * This would normally be a subregion of the visible region. + * The callee must draw all of aRegionToDraw. Drawing outside + * aRegionToDraw will be clipped out or ignored. + * The callee must draw all of aRegionToDraw. + * This region is relative to 0,0 in the PaintedLayer. + * + * aDirtyRegion should contain the total region that is be due to be painted + * during the transaction, even though only aRegionToDraw should be drawn + * during this call. aRegionToDraw must be entirely contained within + * aDirtyRegion. If the total dirty region is unknown it is okay to pass a + * subregion of the total dirty region, e.g. just aRegionToDraw, though it + * may not be as efficient. + * + * aRegionToInvalidate contains a region whose contents have been + * changed by the layer manager and which must therefore be invalidated. + * For example, this could be non-empty if a retained layer internally + * switches from RGBA to RGB or back ... we might want to repaint it to + * consistently use subpixel-AA or not. + * This region is relative to 0,0 in the PaintedLayer. + * aRegionToInvalidate may contain areas that are outside + * aRegionToDraw; the callee must ensure that these areas are repainted + * in the current layer manager transaction or in a later layer + * manager transaction. + * + * aContext must not be used after the call has returned. + * We guarantee that buffered contents in the visible + * region are valid once drawing is complete. + * + * The origin of aContext is 0,0 in the PaintedLayer. + */ + typedef void (*DrawPaintedLayerCallback)( + PaintedLayer* aLayer, gfxContext* aContext, + const nsIntRegion& aRegionToDraw, const nsIntRegion& aDirtyRegion, + DrawRegionClip aClip, const nsIntRegion& aRegionToInvalidate, + void* aCallbackData); + + /** + * Finish the construction phase of the transaction, perform the + * drawing phase, and end the transaction. + * During the drawing phase, all PaintedLayers in the tree are + * drawn in tree order, exactly once each, except for those layers + * where it is known that the visible region is empty. + */ + virtual void EndTransaction(DrawPaintedLayerCallback aCallback, + void* aCallbackData, + EndTransactionFlags aFlags = END_DEFAULT) = 0; + + /** + * Schedule a composition with the remote Compositor, if one exists + * for this LayerManager. Useful in conjunction with the + * END_NO_REMOTE_COMPOSITE flag to EndTransaction. + */ + virtual void ScheduleComposite() {} + + virtual void SetNeedsComposite(bool aNeedsComposite) {} + virtual bool NeedsComposite() const { return false; } + + virtual bool HasShadowManagerInternal() const { return false; } + bool HasShadowManager() const { return HasShadowManagerInternal(); } + virtual void StorePluginWidgetConfigurations( + const nsTArray& aConfigurations) {} + bool IsSnappingEffectiveTransforms() { return mSnapEffectiveTransforms; } + + /** + * Returns true if the underlying platform can properly support layers with + * SurfaceMode::SURFACE_COMPONENT_ALPHA. + */ + static bool LayersComponentAlphaEnabled(); + + /** + * Returns true if this LayerManager can properly support layers with + * SurfaceMode::SURFACE_COMPONENT_ALPHA. LayerManagers that can't will use + * transparent surfaces (and lose subpixel-AA for text). + */ + virtual bool AreComponentAlphaLayersEnabled(); + + /** + * Returns true if this LayerManager always requires an intermediate surface + * to render blend operations. + */ + virtual bool BlendingRequiresIntermediateSurface() { return false; } + + /** + * CONSTRUCTION PHASE ONLY + * Set the root layer. The root layer is initially null. If there is + * no root layer, EndTransaction won't draw anything. + */ + virtual void SetRoot(Layer* aLayer) = 0; + /** + * Can be called anytime + */ + Layer* GetRoot() { return mRoot; } + + /** + * Does a breadth-first search from the root layer to find the first + * scrollable layer, and returns its ViewID. Note that there may be + * other layers in the tree which share the same ViewID. + * Can be called any time. + */ + ScrollableLayerGuid::ViewID GetRootScrollableLayerId(); + + /** + * Returns a LayerMetricsWrapper containing the Root + * Content Documents layer. + */ + LayerMetricsWrapper GetRootContentLayer(); + + /** + * CONSTRUCTION PHASE ONLY + * Called when a managee has mutated. + * Subclasses overriding this method must first call their + * superclass's impl + */ + virtual void Mutated(Layer* aLayer) {} + virtual void MutatedSimple(Layer* aLayer) {} + + /** + * Hints that can be used during PaintedLayer creation to influence the type + * or properties of the layer created. + * + * NONE: No hint. + * SCROLLABLE: This layer may represent scrollable content. + */ + enum PaintedLayerCreationHint { NONE, SCROLLABLE }; + + /** + * CONSTRUCTION PHASE ONLY + * Create a PaintedLayer for this manager's layer tree. + */ + virtual already_AddRefed CreatePaintedLayer() = 0; + /** + * CONSTRUCTION PHASE ONLY + * Create a PaintedLayer for this manager's layer tree, with a creation hint + * parameter to help optimise the type of layer created. + */ + virtual already_AddRefed CreatePaintedLayerWithHint( + PaintedLayerCreationHint) { + return CreatePaintedLayer(); + } + /** + * CONSTRUCTION PHASE ONLY + * Create a ContainerLayer for this manager's layer tree. + */ + virtual already_AddRefed CreateContainerLayer() = 0; + /** + * CONSTRUCTION PHASE ONLY + * Create an ImageLayer for this manager's layer tree. + */ + virtual already_AddRefed CreateImageLayer() = 0; + /** + * CONSTRUCTION PHASE ONLY + * Create a ColorLayer for this manager's layer tree. + */ + virtual already_AddRefed CreateColorLayer() = 0; + /** + * CONSTRUCTION PHASE ONLY + * Create a CanvasLayer for this manager's layer tree. + */ + virtual already_AddRefed CreateCanvasLayer() = 0; + /** + * CONSTRUCTION PHASE ONLY + * Create a ReadbackLayer for this manager's layer tree. + */ + virtual already_AddRefed CreateReadbackLayer() { + return nullptr; + } + /** + * CONSTRUCTION PHASE ONLY + * Create a RefLayer for this manager's layer tree. + */ + virtual already_AddRefed CreateRefLayer() { return nullptr; } + /** + * Can be called anytime, from any thread. + * + * Creates an Image container which forwards its images to the compositor + * within layer transactions on the main thread or asynchronously using the + * ImageBridge IPDL protocol. In the case of asynchronous, If the protocol is + * not available, the returned ImageContainer will forward images within layer + * transactions. + */ + static already_AddRefed CreateImageContainer( + ImageContainer::Mode flag = ImageContainer::SYNCHRONOUS); + + /** + * Type of layer manager his is. This is to be used sparsely in order to + * avoid a lot of Layers backend specific code. It should be used only when + * Layers backend specific functionality is necessary. + */ + virtual LayersBackend GetBackendType() = 0; + + /** + * Type of layers backend that will be used to composite this layer tree. + * When compositing is done remotely, then this returns the layers type + * of the compositor. + */ + virtual LayersBackend GetCompositorBackendType() { return GetBackendType(); } + + /** + * Creates a DrawTarget which is optimized for inter-operating with this + * layer manager. + */ + virtual already_AddRefed CreateOptimalDrawTarget( + const IntSize& aSize, SurfaceFormat imageFormat); + + /** + * Creates a DrawTarget for alpha masks which is optimized for inter- + * operating with this layer manager. In contrast to CreateOptimalDrawTarget, + * this surface is optimised for drawing alpha only and we assume that + * drawing the mask is fairly simple. + */ + virtual already_AddRefed CreateOptimalMaskDrawTarget( + const IntSize& aSize); + + /** + * Creates a DrawTarget for use with canvas which is optimized for + * inter-operating with this layermanager. + */ + virtual already_AddRefed CreateDrawTarget( + const mozilla::gfx::IntSize& aSize, mozilla::gfx::SurfaceFormat aFormat); + + /** + * Creates a PersistentBufferProvider for use with canvas which is optimized + * for inter-operating with this layermanager. + */ + virtual already_AddRefed + CreatePersistentBufferProvider(const mozilla::gfx::IntSize& aSize, + mozilla::gfx::SurfaceFormat aFormat); + + virtual bool CanUseCanvasLayerForSize(const gfx::IntSize& aSize) { + return true; + } + + /** + * returns the maximum texture size on this layer backend, or INT32_MAX + * if there is no maximum + */ + virtual int32_t GetMaxTextureSize() const = 0; + + /** + * Return the name of the layer manager's backend. + */ + virtual void GetBackendName(nsAString& aName) = 0; + + /** + * This setter can be used anytime. The user data for all keys is + * initially null. Ownership pases to the layer manager. + */ + void SetUserData(void* aKey, LayerUserData* aData) { + mUserData.Add(static_cast(aKey), aData, + LayerUserDataDestroy); + } + /** + * This can be used anytime. Ownership passes to the caller! + */ + UniquePtr RemoveUserData(void* aKey); + + /** + * This getter can be used anytime. + */ + bool HasUserData(void* aKey) { + return mUserData.Has(static_cast(aKey)); + } + /** + * This getter can be used anytime. Ownership is retained by the layer + * manager. + */ + LayerUserData* GetUserData(void* aKey) const { + return static_cast( + mUserData.Get(static_cast(aKey))); + } + + /** + * Must be called outside of a layers transaction. + * + * For the subtree rooted at |aSubtree|, this attempts to free up + * any free-able resources like retained buffers, but may do nothing + * at all. After this call, the layer tree is left in an undefined + * state; the layers in |aSubtree|'s subtree may no longer have + * buffers with valid content and may no longer be able to draw + * their visible and valid regions. + * + * In general, a painting or forwarding transaction on |this| must + * complete on the tree before it returns to a valid state. + * + * Resource freeing begins from |aSubtree| or |mRoot| if |aSubtree| + * is null. |aSubtree|'s manager must be this. + */ + virtual void ClearCachedResources(Layer* aSubtree = nullptr) {} + + /** + * Flag the next paint as the first for a document. + */ + virtual void SetIsFirstPaint() {} + virtual bool GetIsFirstPaint() const { return false; } + + /** + * Set the current focus target to be sent with the next paint. + */ + virtual void SetFocusTarget(const FocusTarget& aFocusTarget) {} + + /** + * Make sure that the previous transaction has been entirely + * completed. + * + * Note: This may sychronously wait on a remote compositor + * to complete rendering. + */ + virtual void FlushRendering() {} + + /** + * Make sure that the previous transaction has been + * received. This will synchronsly wait on a remote compositor. */ + virtual void WaitOnTransactionProcessed() {} + + virtual void SendInvalidRegion(const nsIntRegion& aRegion) {} + + /** + * Checks if we need to invalidate the OS widget to trigger + * painting when updating this layer manager. + */ + virtual bool NeedsWidgetInvalidation() { return true; } + + virtual const char* Name() const { return "???"; } + + /** + * Dump information about this layer manager and its managed tree to + * aStream. + */ + void Dump(std::stringstream& aStream, const char* aPrefix = "", + bool aDumpHtml = false, bool aSorted = false); + /** + * Dump information about just this layer manager itself to aStream + */ + void DumpSelf(std::stringstream& aStream, const char* aPrefix = "", + bool aSorted = false); + void Dump(bool aSorted = false); + + /** + * Dump information about this layer manager and its managed tree to + * layerscope packet. + */ + void Dump(layerscope::LayersPacket* aPacket); + + /** + * Log information about this layer manager and its managed tree to + * the NSPR log (if enabled for "Layers"). + */ + void Log(const char* aPrefix = ""); + /** + * Log information about just this layer manager itself to the NSPR + * log (if enabled for "Layers"). + */ + void LogSelf(const char* aPrefix = ""); + + static bool IsLogEnabled(); + static mozilla::LogModule* GetLog(); + + bool IsCompositingCheap(LayersBackend aBackend) { + // LayersBackend::LAYERS_NONE is an error state, but in that case we should + // try to avoid loading the compositor! + return LayersBackend::LAYERS_BASIC != aBackend && + LayersBackend::LAYERS_NONE != aBackend; + } + + virtual bool IsCompositingCheap() { return true; } + + bool IsInTransaction() const { return mInTransaction; } + virtual void GetFrameUniformity(FrameUniformityData* aOutData) {} + + virtual void SetRegionToClear(const nsIntRegion& aRegion) { + mRegionToClear = aRegion; + } + + virtual float RequestProperty(const nsAString& property) { return -1; } + + const TimeStamp& GetAnimationReadyTime() const { return mAnimationReadyTime; } + + virtual bool AsyncPanZoomEnabled() const { return false; } + + static void LayerUserDataDestroy(void* data); + + void AddPaintedPixelCount(int32_t aCount) { mPaintedPixelCount += aCount; } + + uint32_t GetAndClearPaintedPixelCount() { + uint32_t count = mPaintedPixelCount; + mPaintedPixelCount = 0; + return count; + } + + virtual void SetLayersObserverEpoch(LayersObserverEpoch aEpoch) {} + + virtual void DidComposite(TransactionId aTransactionId, + const mozilla::TimeStamp& aCompositeStart, + const mozilla::TimeStamp& aCompositeEnd) {} + + virtual void AddDidCompositeObserver(DidCompositeObserver* aObserver) { + MOZ_CRASH("GFX: LayerManager"); + } + virtual void RemoveDidCompositeObserver(DidCompositeObserver* aObserver) { + MOZ_CRASH("GFX: LayerManager"); + } + + virtual void UpdateTextureFactoryIdentifier( + const TextureFactoryIdentifier& aNewIdentifier) {} + + virtual TextureFactoryIdentifier GetTextureFactoryIdentifier() { + return TextureFactoryIdentifier(); + } + + virtual void SetTransactionIdAllocator(TransactionIdAllocator* aAllocator) {} + + virtual TransactionId GetLastTransactionId() { return TransactionId{0}; } + + virtual CompositorBridgeChild* GetCompositorBridgeChild() { return nullptr; } + + void RegisterPayload(const CompositionPayload& aPayload) { + mPayload.AppendElement(aPayload); + MOZ_ASSERT(mPayload.Length() < 10000); + } + + void RegisterPayloads(const nsTArray& aPayload) { + mPayload.AppendElements(aPayload); + MOZ_ASSERT(mPayload.Length() < 10000); + } + + virtual void PayloadPresented(const TimeStamp& aTimeStamp); + + void SetContainsSVG(bool aContainsSVG) { mContainsSVG = aContainsSVG; } + + void AddPartialPrerenderedAnimation(uint64_t aCompositorAnimationId, + dom::Animation* aAnimation); + void RemovePartialPrerenderedAnimation(uint64_t aCompositorAnimationId, + dom::Animation* aAnimation); + void UpdatePartialPrerenderedAnimations( + const nsTArray& aJankedAnimations); + + protected: + RefPtr mRoot; + gfx::UserData mUserData; + bool mDestroyed; + bool mSnapEffectiveTransforms; + + nsIntRegion mRegionToClear; + + // Protected destructor, to discourage deletion outside of Release(): + virtual ~LayerManager(); + + // Print interesting information about this into aStreamo. Internally + // used to implement Dump*() and Log*(). + virtual void PrintInfo(std::stringstream& aStream, const char* aPrefix); + + // Print interesting information about this into layerscope packet. + // Internally used to implement Dump(). + virtual void DumpPacket(layerscope::LayersPacket* aPacket); + + uint64_t mId; + bool mInTransaction; + + // Used for tracking CONTENT_FRAME_TIME_WITH_SVG + bool mContainsSVG; + // The time when painting most recently finished. This is recorded so that + // we can time any play-pending animations from this point. + TimeStamp mAnimationReadyTime; + // The count of pixels that were painted in the current transaction. + uint32_t mPaintedPixelCount; + // The payload associated with currently pending painting work, for + // client layer managers that typically means payload that is part of the + // 'upcoming transaction', for HostLayerManagers this typically means + // what has been included in received transactions to be presented on the + // next composite. + // IMPORTANT: Clients should take care to clear this or risk it slowly + // growing out of control. + nsTArray mPayload; + // Transform animations which are not fully pre-rendered because it's on a + // large frame. We need to update the pre-rendered area once after we tried + // to composite area which is outside of the pre-rendered area on the + // compositor. + nsRefPtrHashtable + mPartialPrerenderedAnimations; + + public: + /* + * Methods to store/get/clear a "pending scroll info update" object on a + * per-scrollid basis. This is used for empty transactions that push over + * scroll position updates to the APZ code. + */ + virtual bool AddPendingScrollUpdateForNextTransaction( + ScrollableLayerGuid::ViewID aScrollId, + const ScrollPositionUpdate& aUpdateInfo); + Maybe> GetPendingScrollInfoUpdate( + ScrollableLayerGuid::ViewID aScrollId); + std::unordered_set + ClearPendingScrollInfoUpdate(); + + protected: + ScrollUpdatesMap mPendingScrollUpdates; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_LAYERS_H */ diff --git a/gfx/layers/LayerMetricsWrapper.h b/gfx/layers/LayerMetricsWrapper.h new file mode 100644 index 0000000000..9bf45a6a55 --- /dev/null +++ b/gfx/layers/LayerMetricsWrapper.h @@ -0,0 +1,519 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_LAYERMETRICSWRAPPER_H +#define GFX_LAYERMETRICSWRAPPER_H + +#include "Layers.h" +#include "UnitTransforms.h" + +namespace mozilla { +namespace layers { + +/** + * A wrapper class around a target Layer with that allows user code to + * walk through the FrameMetrics objects on the layer the same way it + * would walk through a ContainerLayer hierarchy. Consider the following + * layer tree: + * + * +---+ + * | A | + * +---+ + * / | \ + * / | \ + * / | \ + * +---+ +-----+ +---+ + * | B | | C | | D | + * +---+ +-----+ +---+ + * | FMn | + * | . | + * | . | + * | . | + * | FM1 | + * | FM0 | + * +-----+ + * / \ + * / \ + * +---+ +---+ + * | E | | F | + * +---+ +---+ + * + * In this layer tree, there are six layers with A being the root and B,D,E,F + * being leaf nodes. Layer C is in the middle and has n+1 FrameMetrics, labelled + * FM0...FMn. FM0 is the FrameMetrics you get by calling c->GetFrameMetrics(0) + * and FMn is the FrameMetrics you can obtain by calling + * c->GetFrameMetrics(c->GetScrollMetadataCount() - 1). This layer tree is + * conceptually equivalent to this one below: + * + * +---+ + * | A | + * +---+ + * / | \ + * / | \ + * / | \ + * +---+ +-----+ +---+ + * | B | | Cn | | D | + * +---+ +-----+ +---+ + * | + * . + * . + * . + * | + * +-----+ + * | C1 | + * +-----+ + * | + * +-----+ + * | C0 | + * +-----+ + * / \ + * / \ + * +---+ +---+ + * | E | | F | + * +---+ +---+ + * + * In this layer tree, the layer C has been expanded into a stack of container + * layers C1...Cn, where C1 has FrameMetrics FM1 and Cn has FrameMetrics Fn. + * Although in this example C (in the first layer tree) and C0 (in the second + * layer tree) are both ContainerLayers (because they have children), they + * do not have to be. They may just be PaintedLayers or ColorLayers, for + * example, which do not have any children. However, the type of C will always + * be the same as the type of C0. + * + * The LayerMetricsWrapper class allows client code to treat the first layer + * tree as though it were the second. That is, instead of client code having + * to iterate through the FrameMetrics objects directly, it can use a + * LayerMetricsWrapper to encapsulate that aspect of the layer tree and just + * walk the tree as if it were a stack of ContainerLayers. + * + * The functions on this class do different things depending on which + * simulated ContainerLayer is being wrapped. For example, if the + * LayerMetricsWrapper is pretending to be C0, the GetNextSibling() function + * will return null even though the underlying layer C does actually have + * a next sibling. The LayerMetricsWrapper pretending to be Cn will return + * D as the next sibling. + * + * Implementation notes: + * + * The AtTopLayer() and AtBottomLayer() functions in this class refer to + * Cn and C0 in the second layer tree above; that is, they are predicates + * to test if the LayerMetricsWrapper is simulating the topmost or bottommost + * layer, as those will have special behaviour. + * + * It is possible to wrap a nullptr in a LayerMetricsWrapper, in which case + * the IsValid() function will return false. This is required to allow + * LayerMetricsWrapper to be a MOZ_STACK_CLASS (desirable because it is used + * in loops and recursion). + * + * This class purposely does not expose the wrapped layer directly to avoid + * user code from accidentally calling functions directly on it. Instead + * any necessary functions should be wrapped in this class. It does expose + * the wrapped layer as a void* for printf purposes. + * + * The implementation may look like it special-cases mIndex == 0 and/or + * GetScrollMetadataCount() == 0. This is an artifact of the fact that both + * mIndex and GetScrollMetadataCount() are uint32_t and GetScrollMetadataCount() + * can return 0 but mIndex cannot store -1. This seems better than the + * alternative of making mIndex a int32_t that can store -1, but then having + * to cast to uint32_t all over the place. + */ +class MOZ_STACK_CLASS LayerMetricsWrapper final { + public: + enum StartAt { + TOP, + BOTTOM, + }; + + LayerMetricsWrapper() : mLayer(nullptr), mIndex(0) {} + + explicit LayerMetricsWrapper(Layer* aRoot, StartAt aStart = StartAt::TOP) + : mLayer(aRoot), mIndex(0) { + if (!mLayer) { + return; + } + + switch (aStart) { + case StartAt::TOP: + mIndex = mLayer->GetScrollMetadataCount(); + if (mIndex > 0) { + mIndex--; + } + break; + case StartAt::BOTTOM: + mIndex = 0; + break; + default: + MOZ_ASSERT_UNREACHABLE("Unknown startAt value"); + break; + } + } + + explicit LayerMetricsWrapper(Layer* aLayer, uint32_t aMetricsIndex) + : mLayer(aLayer), mIndex(aMetricsIndex) { + MOZ_ASSERT(mLayer); + MOZ_ASSERT(mIndex == 0 || mIndex < mLayer->GetScrollMetadataCount()); + } + + bool IsValid() const { return mLayer != nullptr; } + + explicit operator bool() const { return IsValid(); } + + LayerMetricsWrapper GetParent() const { + MOZ_ASSERT(IsValid()); + + if (!AtTopLayer()) { + return LayerMetricsWrapper(mLayer, mIndex + 1); + } + if (mLayer->GetParent()) { + return LayerMetricsWrapper(mLayer->GetParent(), StartAt::BOTTOM); + } + return LayerMetricsWrapper(nullptr); + } + + LayerMetricsWrapper GetFirstChild() const { + MOZ_ASSERT(IsValid()); + + if (!AtBottomLayer()) { + return LayerMetricsWrapper(mLayer, mIndex - 1); + } + return LayerMetricsWrapper(mLayer->GetFirstChild()); + } + + LayerMetricsWrapper GetLastChild() const { + MOZ_ASSERT(IsValid()); + + if (!AtBottomLayer()) { + return LayerMetricsWrapper(mLayer, mIndex - 1); + } + return LayerMetricsWrapper(mLayer->GetLastChild()); + } + + LayerMetricsWrapper GetPrevSibling() const { + MOZ_ASSERT(IsValid()); + + if (AtTopLayer()) { + return LayerMetricsWrapper(mLayer->GetPrevSibling()); + } + return LayerMetricsWrapper(nullptr); + } + + LayerMetricsWrapper GetNextSibling() const { + MOZ_ASSERT(IsValid()); + + if (AtTopLayer()) { + return LayerMetricsWrapper(mLayer->GetNextSibling()); + } + return LayerMetricsWrapper(nullptr); + } + + const ScrollMetadata& Metadata() const { + MOZ_ASSERT(IsValid()); + + if (mIndex >= mLayer->GetScrollMetadataCount()) { + return *ScrollMetadata::sNullMetadata; + } + return mLayer->GetScrollMetadata(mIndex); + } + + const FrameMetrics& Metrics() const { return Metadata().GetMetrics(); } + + AsyncPanZoomController* GetApzc() const { + MOZ_ASSERT(IsValid()); + + if (mIndex >= mLayer->GetScrollMetadataCount()) { + return nullptr; + } + return mLayer->GetAsyncPanZoomController(mIndex); + } + + void SetApzc(AsyncPanZoomController* aApzc) const { + MOZ_ASSERT(IsValid()); + + if (mLayer->GetScrollMetadataCount() == 0) { + MOZ_ASSERT(mIndex == 0); + MOZ_ASSERT(aApzc == nullptr); + return; + } + MOZ_ASSERT(mIndex < mLayer->GetScrollMetadataCount()); + mLayer->SetAsyncPanZoomController(mIndex, aApzc); + } + + const char* Name() const { + MOZ_ASSERT(IsValid()); + + if (AtBottomLayer()) { + return mLayer->Name(); + } + return "DummyContainerLayer"; + } + + LayerManager* Manager() const { + MOZ_ASSERT(IsValid()); + + return mLayer->Manager(); + } + + gfx::Matrix4x4 GetTransform() const { + MOZ_ASSERT(IsValid()); + + if (AtBottomLayer()) { + return mLayer->GetTransform(); + } + return gfx::Matrix4x4(); + } + + CSSTransformMatrix GetTransformTyped() const { + return ViewAs(GetTransform()); + } + + bool TransformIsPerspective() const { + MOZ_ASSERT(IsValid()); + + // mLayer->GetTransformIsPerspective() tells us whether + // mLayer->GetTransform() is a perspective transform. Since + // mLayer->GetTransform() is only used at the bottom layer, we only + // need to check GetTransformIsPerspective() at the bottom layer too. + if (AtBottomLayer()) { + return mLayer->GetTransformIsPerspective(); + } + return false; + } + + bool Combines3DTransformWithAncestors() const { + MOZ_ASSERT(IsValid()); + + return mLayer->Combines3DTransformWithAncestors(); + } + + EventRegions GetEventRegions() const { + MOZ_ASSERT(IsValid()); + + if (AtBottomLayer()) { + return mLayer->GetEventRegions(); + } + return EventRegions(); + } + + LayerIntRegion GetVisibleRegion() const { + MOZ_ASSERT(IsValid()); + + if (AtBottomLayer()) { + return mLayer->GetVisibleRegion(); + } + + return ViewAs( + TransformBy(mLayer->GetTransformTyped(), mLayer->GetVisibleRegion()), + PixelCastJustification::MovingDownToChildren); + } + + LayerIntSize GetRemoteDocumentSize() const { + MOZ_ASSERT(IsValid()); + + return AsRefLayer() ? AsRefLayer()->GetRemoteDocumentSize() + : LayerIntSize(); + } + + bool HasTransformAnimation() const { + MOZ_ASSERT(IsValid()); + + if (AtBottomLayer()) { + return mLayer->HasTransformAnimation(); + } + return false; + } + + RefLayer* AsRefLayer() const { + MOZ_ASSERT(IsValid()); + + if (AtBottomLayer()) { + return mLayer->AsRefLayer(); + } + return nullptr; + } + + Maybe GetReferentId() const { + MOZ_ASSERT(IsValid()); + + if (AtBottomLayer()) { + return mLayer->AsRefLayer() ? Some(mLayer->AsRefLayer()->GetReferentId()) + : Nothing(); + } + return Nothing(); + } + + Maybe GetClipRect() const { + MOZ_ASSERT(IsValid()); + + Maybe result; + + // The layer can have a clip rect and a scrolled clip, which are considered + // to apply only to the bottommost LayerMetricsWrapper. + // TODO: These actually apply in a different coordinate space than the + // scroll clip of the bottommost metrics, so we shouldn't be intersecting + // them with the scroll clip; bug 1269537 tracks fixing this. + if (AtBottomLayer()) { + result = mLayer->GetClipRect(); + result = IntersectMaybeRects(result, mLayer->GetScrolledClipRect()); + } + + // The scroll metadata can have a clip rect as well. + result = IntersectMaybeRects(result, Metadata().GetClipRect()); + + return result; + } + + float GetPresShellResolution() const { + MOZ_ASSERT(IsValid()); + + if (AtTopLayer() && mLayer->AsContainerLayer()) { + return mLayer->AsContainerLayer()->GetPresShellResolution(); + } + + return 1.0f; + } + + EventRegionsOverride GetEventRegionsOverride() const { + MOZ_ASSERT(IsValid()); + + if (AsRefLayer()) { + return AsRefLayer()->GetEventRegionsOverride(); + } + return EventRegionsOverride::NoOverride; + } + + const ScrollbarData& GetScrollbarData() const { + MOZ_ASSERT(IsValid()); + + return mLayer->GetScrollbarData(); + } + + Maybe GetScrollbarAnimationId() const { + MOZ_ASSERT(IsValid()); + // This function is only really needed for template-compatibility with + // WebRenderScrollDataWrapper. Although it will be called, the return + // value is not used. + return Nothing(); + } + + Maybe GetFixedPositionAnimationId() const { + MOZ_ASSERT(IsValid()); + // This function is only really needed for template-compatibility with + // WebRenderScrollDataWrapper. Although it will be called, the return + // value is not used. + return Nothing(); + } + + ScrollableLayerGuid::ViewID GetFixedPositionScrollContainerId() const { + MOZ_ASSERT(IsValid()); + + if (AtBottomLayer()) { + return mLayer->GetFixedPositionScrollContainerId(); + } + return ScrollableLayerGuid::NULL_SCROLL_ID; + } + + SideBits GetFixedPositionSides() const { + MOZ_ASSERT(IsValid()); + + if (AtBottomLayer()) { + return mLayer->GetFixedPositionSides(); + } + return SideBits::eNone; + } + + ScrollableLayerGuid::ViewID GetStickyScrollContainerId() const { + MOZ_ASSERT(IsValid()); + + if (AtBottomLayer() && mLayer->GetIsStickyPosition()) { + return mLayer->GetStickyScrollContainerId(); + } + return ScrollableLayerGuid::NULL_SCROLL_ID; + } + + const LayerRectAbsolute& GetStickyScrollRangeOuter() const { + MOZ_ASSERT(IsValid()); + + if (AtBottomLayer() && mLayer->GetIsStickyPosition()) { + return mLayer->GetStickyScrollRangeOuter(); + } + + static const LayerRectAbsolute empty; + return empty; + } + + const LayerRectAbsolute& GetStickyScrollRangeInner() const { + MOZ_ASSERT(IsValid()); + + if (AtBottomLayer() && mLayer->GetIsStickyPosition()) { + return mLayer->GetStickyScrollRangeInner(); + } + + static const LayerRectAbsolute empty; + return empty; + } + + Maybe GetStickyPositionAnimationId() const { + MOZ_ASSERT(IsValid()); + // This function is only really needed for template-compatibility with + // WebRenderScrollDataWrapper. Although it will be called, the return + // value is not used. + return Nothing(); + } + + Maybe GetZoomAnimationId() const { + MOZ_ASSERT(IsValid()); + // This function is only really needed for template-compatibility with + // WebRenderScrollDataWrapper. Although it will be called, the return + // value is not used. + return Nothing(); + } + + bool IsBackfaceHidden() const { + MOZ_ASSERT(IsValid()); + + return mLayer->IsBackfaceHidden(); + } + + Maybe IsAsyncZoomContainer() const { + MOZ_ASSERT(IsValid()); + + return mLayer->IsAsyncZoomContainer(); + } + + // Expose an opaque pointer to the layer. Mostly used for printf + // purposes. This is not intended to be a general-purpose accessor + // for the underlying layer. + const void* GetLayer() const { + MOZ_ASSERT(IsValid()); + + return (void*)mLayer; + } + + bool operator==(const LayerMetricsWrapper& aOther) const { + return mLayer == aOther.mLayer && mIndex == aOther.mIndex; + } + + bool operator!=(const LayerMetricsWrapper& aOther) const { + return !(*this == aOther); + } + + private: + bool AtBottomLayer() const { return mIndex == 0; } + + bool AtTopLayer() const { + return mLayer->GetScrollMetadataCount() == 0 || + mIndex == mLayer->GetScrollMetadataCount() - 1; + } + + private: + Layer* mLayer; + uint32_t mIndex; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_LAYERMETRICSWRAPPER_H */ diff --git a/gfx/layers/LayerScope.cpp b/gfx/layers/LayerScope.cpp new file mode 100644 index 0000000000..69c0e9eaff --- /dev/null +++ b/gfx/layers/LayerScope.cpp @@ -0,0 +1,1633 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=4 sw=2 sts=2 et: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* This must occur *after* layers/PLayers.h to avoid typedefs conflicts. */ +#include "LayerScope.h" + +#include "nsAppRunner.h" +#include "Effects.h" +#include "Layers.h" +#include "mozilla/EndianUtils.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/TimeStamp.h" + +#include "mozilla/layers/CompositorOGL.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/layers/LayerManagerComposite.h" +#include "mozilla/layers/TextureHostOGL.h" + +#include "gfxContext.h" +#include "gfxUtils.h" + +#include "nsIWidget.h" + +#include "GLContext.h" +#include "GLContextProvider.h" +#include "GLReadTexImageHelper.h" + +#include +#include "mozilla/LinkedList.h" +#include "mozilla/Base64.h" +#include "mozilla/SHA1.h" +#include "mozilla/StaticPtr.h" +#include "nsComponentManagerUtils.h" +#include "nsThreadUtils.h" +#include "nsISocketTransport.h" +#include "nsIServerSocket.h" +#include "nsReadLine.h" +#include "nsNetCID.h" +#include "nsIOutputStream.h" +#include "nsIAsyncInputStream.h" +#include "nsProxyRelease.h" +#include + +// Undo the damage done by mozzconf.h +#undef compress +#include "mozilla/Compression.h" + +// Undo the damage done by X11 +#ifdef Status +# undef Status +#endif +// Protocol buffer (generated automatically) +#include "protobuf/LayerScopePacket.pb.h" + +namespace mozilla { +namespace layers { + +using namespace mozilla::Compression; +using namespace mozilla::gfx; +using namespace mozilla::gl; +using namespace mozilla; +using namespace layerscope; + +class DebugDataSender; +class DebugGLData; + +/* + * Manage Websocket connections + */ +class LayerScopeWebSocketManager { + public: + LayerScopeWebSocketManager(); + ~LayerScopeWebSocketManager(); + + void RemoveAllConnections() { + MOZ_ASSERT(NS_IsMainThread()); + + MutexAutoLock lock(mHandlerMutex); + mHandlers.Clear(); + } + + bool WriteAll(void* ptr, uint32_t size) { + for (int32_t i = mHandlers.Length() - 1; i >= 0; --i) { + if (!mHandlers[i]->WriteToStream(ptr, size)) { + // Send failed, remove this handler + RemoveConnection(i); + } + } + + return true; + } + + bool IsConnected() { + // This funtion can be called in both main thread and compositor thread. + MutexAutoLock lock(mHandlerMutex); + return (mHandlers.Length() != 0) ? true : false; + } + + void AppendDebugData(DebugGLData* aDebugData); + void CleanDebugData(); + void DispatchDebugData(); + + private: + void AddConnection(nsISocketTransport* aTransport) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aTransport); + + MutexAutoLock lock(mHandlerMutex); + + RefPtr temp = new SocketHandler(); + temp->OpenStream(aTransport); + mHandlers.AppendElement(temp.get()); + } + + void RemoveConnection(uint32_t aIndex) { + // TBD: RemoveConnection is executed on the compositor thread and + // AddConntection is executed on the main thread, which might be + // a problem if a user disconnect and connect readlly quickly at + // viewer side. + + // We should dispatch RemoveConnection onto main thead. + MOZ_ASSERT(aIndex < mHandlers.Length()); + + MutexAutoLock lock(mHandlerMutex); + mHandlers.RemoveElementAt(aIndex); + } + + friend class SocketListener; + class SocketListener : public nsIServerSocketListener { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + SocketListener() = default; + + /* nsIServerSocketListener */ + NS_IMETHOD OnSocketAccepted(nsIServerSocket* aServ, + nsISocketTransport* aTransport) override; + NS_IMETHOD OnStopListening(nsIServerSocket* aServ, + nsresult aStatus) override { + return NS_OK; + } + + private: + virtual ~SocketListener() = default; + }; + + /* + * This class handle websocket protocol which included + * handshake and data frame's header + */ + class SocketHandler : public nsIInputStreamCallback { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + SocketHandler() : mState(NoHandshake), mConnected(false) {} + + void OpenStream(nsISocketTransport* aTransport); + bool WriteToStream(void* aPtr, uint32_t aSize); + + // nsIInputStreamCallback + NS_IMETHOD OnInputStreamReady(nsIAsyncInputStream* aStream) override; + + private: + virtual ~SocketHandler() { CloseConnection(); } + + void ReadInputStreamData(nsTArray& aProtocolString); + bool WebSocketHandshake(nsTArray& aProtocolString); + void ApplyMask(uint32_t aMask, uint8_t* aData, uint64_t aLen); + bool HandleDataFrame(uint8_t* aData, uint32_t aSize); + void CloseConnection(); + + nsresult HandleSocketMessage(nsIAsyncInputStream* aStream); + nsresult ProcessInput(uint8_t* aBuffer, uint32_t aCount); + + private: + enum SocketStateType { NoHandshake, HandshakeSuccess, HandshakeFailed }; + SocketStateType mState; + + nsCOMPtr mOutputStream; + nsCOMPtr mInputStream; + nsCOMPtr mTransport; + bool mConnected; + }; + + nsTArray > mHandlers; + nsCOMPtr mDebugSenderThread; + RefPtr mCurrentSender; + nsCOMPtr mServerSocket; + + // Keep mHandlers accessing thread safe. + Mutex mHandlerMutex; +}; + +NS_IMPL_ISUPPORTS(LayerScopeWebSocketManager::SocketListener, + nsIServerSocketListener); +NS_IMPL_ISUPPORTS(LayerScopeWebSocketManager::SocketHandler, + nsIInputStreamCallback); + +class DrawSession { + public: + DrawSession() : mOffsetX(0.0), mOffsetY(0.0), mRects(0) {} + + float mOffsetX; + float mOffsetY; + gfx::Matrix4x4 mMVMatrix; + size_t mRects; + gfx::Rect mLayerRects[4]; + gfx::Rect mTextureRects[4]; + std::list mTexIDs; +}; + +class ContentMonitor { + public: + using THArray = nsTArray; + + // Notify the content of a TextureHost was changed. + void SetChangedHost(const TextureHost* host) { + if (THArray::NoIndex == mChangedHosts.IndexOf(host)) { + mChangedHosts.AppendElement(host); + } + } + + // Clear changed flag of a host. + void ClearChangedHost(const TextureHost* host) { + if (THArray::NoIndex != mChangedHosts.IndexOf(host)) { + mChangedHosts.RemoveElement(host); + } + } + + // Return true iff host is a new one or the content of it had been changed. + bool IsChangedOrNew(const TextureHost* host) { + if (THArray::NoIndex == mSeenHosts.IndexOf(host)) { + mSeenHosts.AppendElement(host); + return true; + } + + if (decltype(mChangedHosts)::NoIndex != mChangedHosts.IndexOf(host)) { + return true; + } + + return false; + } + + void Empty() { + mSeenHosts.SetLength(0); + mChangedHosts.SetLength(0); + } + + private: + THArray mSeenHosts; + THArray mChangedHosts; +}; + +/* + * Hold all singleton objects used by LayerScope. + */ +class LayerScopeManager { + public: + void CreateServerSocket() { + // WebSocketManager must be created on the main thread. + if (NS_IsMainThread()) { + mWebSocketManager = mozilla::MakeUnique(); + } else { + // Dispatch creation to main thread, and make sure we + // dispatch this only once after booting + static bool dispatched = false; + if (dispatched) { + return; + } + + DebugOnly rv = + NS_DispatchToMainThread(new CreateServerSocketRunnable(this)); + MOZ_ASSERT(NS_SUCCEEDED(rv), + "Failed to dispatch WebSocket Creation to main thread"); + dispatched = true; + } + } + + void DestroyServerSocket() { + // Destroy Web Server Socket + if (mWebSocketManager) { + mWebSocketManager->RemoveAllConnections(); + } + } + + LayerScopeWebSocketManager* GetSocketManager() { + return mWebSocketManager.get(); + } + + ContentMonitor* GetContentMonitor() { + if (!mContentMonitor.get()) { + mContentMonitor = mozilla::MakeUnique(); + } + + return mContentMonitor.get(); + } + + void NewDrawSession() { mSession = mozilla::MakeUnique(); } + + DrawSession& CurrentSession() { return *mSession; } + + void SetPixelScale(double scale) { mScale = scale; } + + double GetPixelScale() const { return mScale; } + + LayerScopeManager() : mScale(1.0) {} + + private: + friend class CreateServerSocketRunnable; + class CreateServerSocketRunnable : public Runnable { + public: + explicit CreateServerSocketRunnable(LayerScopeManager* aLayerScopeManager) + : Runnable("layers::LayerScopeManager::CreateServerSocketRunnable"), + mLayerScopeManager(aLayerScopeManager) {} + NS_IMETHOD Run() override { + mLayerScopeManager->mWebSocketManager = + mozilla::MakeUnique(); + return NS_OK; + } + + private: + LayerScopeManager* mLayerScopeManager; + }; + + mozilla::UniquePtr mWebSocketManager; + mozilla::UniquePtr mSession; + mozilla::UniquePtr mContentMonitor; + double mScale; +}; + +LayerScopeManager gLayerScopeManager; + +/* + * The static helper functions that set data into the packet + * 1. DumpRect + * 2. DumpFilter + */ +template +static void DumpRect(T* aPacketRect, const Rect& aRect) { + aPacketRect->set_x(aRect.X()); + aPacketRect->set_y(aRect.Y()); + aPacketRect->set_w(aRect.Width()); + aPacketRect->set_h(aRect.Height()); +} + +static void DumpFilter(TexturePacket* aTexturePacket, + const SamplingFilter aSamplingFilter) { + switch (aSamplingFilter) { + case SamplingFilter::GOOD: + aTexturePacket->set_mfilter(TexturePacket::GOOD); + break; + case SamplingFilter::LINEAR: + aTexturePacket->set_mfilter(TexturePacket::LINEAR); + break; + case SamplingFilter::POINT: + aTexturePacket->set_mfilter(TexturePacket::POINT); + break; + default: + MOZ_ASSERT(false, + "Can't dump unexpected mSamplingFilter to texture packet!"); + break; + } +} + +/* + * DebugGLData is the base class of + * 1. DebugGLFrameStatusData (Frame start/end packet) + * 2. DebugGLColorData (Color data packet) + * 3. DebugGLTextureData (Texture data packet) + * 4. DebugGLLayersData (Layers Tree data packet) + * 5. DebugGLMetaData (Meta data packet) + */ +class DebugGLData : public LinkedListElement { + public: + explicit DebugGLData(Packet::DataType aDataType) : mDataType(aDataType) {} + + virtual ~DebugGLData() = default; + + virtual bool Write() = 0; + + protected: + static bool WriteToStream(Packet& aPacket) { + if (!gLayerScopeManager.GetSocketManager()) return true; + + size_t size = aPacket.ByteSizeLong(); + auto data = MakeUnique(size); + aPacket.SerializeToArray(data.get(), size); + return gLayerScopeManager.GetSocketManager()->WriteAll(data.get(), size); + } + + Packet::DataType mDataType; +}; + +class DebugGLFrameStatusData final : public DebugGLData { + public: + DebugGLFrameStatusData(Packet::DataType aDataType, int64_t aValue) + : DebugGLData(aDataType), mFrameStamp(aValue) {} + + explicit DebugGLFrameStatusData(Packet::DataType aDataType) + : DebugGLData(aDataType), mFrameStamp(0) {} + + bool Write() override { + Packet packet; + packet.set_type(mDataType); + + FramePacket* fp = packet.mutable_frame(); + fp->set_value(static_cast(mFrameStamp)); + + fp->set_scale(gLayerScopeManager.GetPixelScale()); + + return WriteToStream(packet); + } + + protected: + int64_t mFrameStamp; +}; + +class DebugGLTextureData final : public DebugGLData { + public: + DebugGLTextureData(GLContext* cx, void* layerRef, GLenum target, GLuint name, + DataSourceSurface* img, bool aIsMask, + UniquePtr aPacket) + : DebugGLData(Packet::TEXTURE), + mLayerRef(reinterpret_cast(layerRef)), + mTarget(target), + mName(name), + mContextAddress(reinterpret_cast(cx)), + mDatasize(0), + mIsMask(aIsMask), + mPacket(std::move(aPacket)) { + // pre-packing + // DataSourceSurface may have locked buffer, + // so we should compress now, and then it could + // be unlocked outside. + pack(img); + } + + bool Write() override { return WriteToStream(*mPacket); } + + private: + void pack(DataSourceSurface* aImage) { + mPacket->set_type(mDataType); + + TexturePacket* tp = mPacket->mutable_texture(); + tp->set_layerref(mLayerRef); + tp->set_name(mName); + tp->set_target(mTarget); + tp->set_dataformat(LOCAL_GL_RGBA); + tp->set_glcontext(static_cast(mContextAddress)); + tp->set_ismask(mIsMask); + + if (aImage) { + DataSourceSurface::ScopedMap map(aImage, DataSourceSurface::READ); + tp->set_width(aImage->GetSize().width); + tp->set_height(aImage->GetSize().height); + tp->set_stride(map.GetStride()); + + mDatasize = aImage->GetSize().height * map.GetStride(); + + auto compresseddata = + MakeUnique(LZ4::maxCompressedSize(mDatasize)); + if (compresseddata) { + int ndatasize = LZ4::compress((char*)map.GetData(), mDatasize, + compresseddata.get()); + if (ndatasize > 0) { + mDatasize = ndatasize; + tp->set_dataformat((1 << 16 | tp->dataformat())); + tp->set_data(compresseddata.get(), mDatasize); + } else { + NS_WARNING("Compress data failed"); + tp->set_data(map.GetData(), mDatasize); + } + } else { + NS_WARNING("Couldn't new compressed data."); + tp->set_data(map.GetData(), mDatasize); + } + } else { + tp->set_width(0); + tp->set_height(0); + tp->set_stride(0); + } + } + + protected: + uint64_t mLayerRef; + GLenum mTarget; + GLuint mName; + intptr_t mContextAddress; + uint32_t mDatasize; + bool mIsMask; + + // Packet data + UniquePtr mPacket; +}; + +class DebugGLColorData final : public DebugGLData { + public: + DebugGLColorData(void* layerRef, const DeviceColor& color, int width, + int height) + : DebugGLData(Packet::COLOR), + mLayerRef(reinterpret_cast(layerRef)), + mColor(color.ToABGR()), + mSize(width, height) {} + + bool Write() override { + Packet packet; + packet.set_type(mDataType); + + ColorPacket* cp = packet.mutable_color(); + cp->set_layerref(mLayerRef); + cp->set_color(mColor); + cp->set_width(mSize.width); + cp->set_height(mSize.height); + + return WriteToStream(packet); + } + + protected: + uint64_t mLayerRef; + uint32_t mColor; + IntSize mSize; +}; + +class DebugGLLayersData final : public DebugGLData { + public: + explicit DebugGLLayersData(UniquePtr aPacket) + : DebugGLData(Packet::LAYERS), mPacket(std::move(aPacket)) {} + + bool Write() override { + mPacket->set_type(mDataType); + return WriteToStream(*mPacket); + } + + protected: + UniquePtr mPacket; +}; + +class DebugGLMetaData final : public DebugGLData { + public: + DebugGLMetaData(Packet::DataType aDataType, bool aValue) + : DebugGLData(aDataType), mComposedByHwc(aValue) {} + + explicit DebugGLMetaData(Packet::DataType aDataType) + : DebugGLData(aDataType), mComposedByHwc(false) {} + + bool Write() override { + Packet packet; + packet.set_type(mDataType); + + MetaPacket* mp = packet.mutable_meta(); + mp->set_composedbyhwc(mComposedByHwc); + + return WriteToStream(packet); + } + + protected: + bool mComposedByHwc; +}; + +class DebugGLDrawData final : public DebugGLData { + public: + DebugGLDrawData(float aOffsetX, float aOffsetY, + const gfx::Matrix4x4& aMVMatrix, size_t aRects, + const gfx::Rect* aLayerRects, const gfx::Rect* aTextureRects, + const std::list& aTexIDs, void* aLayerRef) + : DebugGLData(Packet::DRAW), + mOffsetX(aOffsetX), + mOffsetY(aOffsetY), + mMVMatrix(aMVMatrix), + mRects(aRects), + mTexIDs(aTexIDs), + mLayerRef(reinterpret_cast(aLayerRef)) { + for (size_t i = 0; i < mRects; i++) { + mLayerRects[i] = aLayerRects[i]; + mTextureRects[i] = aTextureRects[i]; + } + } + + bool Write() override { + Packet packet; + packet.set_type(mDataType); + + DrawPacket* dp = packet.mutable_draw(); + dp->set_layerref(mLayerRef); + + dp->set_offsetx(mOffsetX); + dp->set_offsety(mOffsetY); + + auto element = reinterpret_cast(&mMVMatrix); + for (int i = 0; i < 16; i++) { + dp->add_mvmatrix(*element++); + } + dp->set_totalrects(mRects); + + MOZ_ASSERT(mRects > 0 && mRects < 4); + for (size_t i = 0; i < mRects; i++) { + // Vertex + DumpRect(dp->add_layerrect(), mLayerRects[i]); + // UV + DumpRect(dp->add_texturerect(), mTextureRects[i]); + } + + for (GLuint texId : mTexIDs) { + dp->add_texids(texId); + } + + return WriteToStream(packet); + } + + protected: + float mOffsetX; + float mOffsetY; + gfx::Matrix4x4 mMVMatrix; + size_t mRects; + gfx::Rect mLayerRects[4]; + gfx::Rect mTextureRects[4]; + std::list mTexIDs; + uint64_t mLayerRef; +}; + +class DebugDataSender { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DebugDataSender) + + // Append a DebugData into mList on mThread + class AppendTask : public nsIRunnable { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + AppendTask(DebugDataSender* host, DebugGLData* d) : mData(d), mHost(host) {} + + NS_IMETHOD Run() override { + mHost->mList.insertBack(mData); + return NS_OK; + } + + private: + virtual ~AppendTask() = default; + + DebugGLData* mData; + // Keep a strong reference to DebugDataSender to prevent this object + // accessing mHost on mThread, when it's been destroyed on the main + // thread. + RefPtr mHost; + }; + + // Clear all DebugData in mList on mThead. + class ClearTask : public nsIRunnable { + public: + NS_DECL_THREADSAFE_ISUPPORTS + explicit ClearTask(DebugDataSender* host) : mHost(host) {} + + NS_IMETHOD Run() override { + mHost->RemoveData(); + return NS_OK; + } + + private: + virtual ~ClearTask() = default; + + RefPtr mHost; + }; + + // Send all DebugData in mList via websocket, and then, clean up + // mList on mThread. + class SendTask : public nsIRunnable { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + explicit SendTask(DebugDataSender* host) : mHost(host) {} + + NS_IMETHOD Run() override { + // Sendout all appended debug data. + DebugGLData* d = nullptr; + while ((d = mHost->mList.popFirst()) != nullptr) { + UniquePtr cleaner(d); + if (!d->Write()) { + gLayerScopeManager.DestroyServerSocket(); + break; + } + } + + // Cleanup. + mHost->RemoveData(); + return NS_OK; + } + + private: + virtual ~SendTask() = default; + + RefPtr mHost; + }; + + explicit DebugDataSender(nsIThread* thread) : mThread(thread) {} + + void Append(DebugGLData* d) { + mThread->Dispatch(new AppendTask(this, d), NS_DISPATCH_NORMAL); + } + + void Cleanup() { mThread->Dispatch(new ClearTask(this), NS_DISPATCH_NORMAL); } + + void Send() { mThread->Dispatch(new SendTask(this), NS_DISPATCH_NORMAL); } + + protected: + virtual ~DebugDataSender() = default; + void RemoveData() { + MOZ_ASSERT(mThread->SerialEventTarget()->IsOnCurrentThread()); + if (mList.isEmpty()) return; + + DebugGLData* d; + while ((d = mList.popFirst()) != nullptr) delete d; + } + + // We can only modify or aceess mList on mThread. + LinkedList mList; + nsCOMPtr mThread; +}; + +NS_IMPL_ISUPPORTS(DebugDataSender::AppendTask, nsIRunnable); +NS_IMPL_ISUPPORTS(DebugDataSender::ClearTask, nsIRunnable); +NS_IMPL_ISUPPORTS(DebugDataSender::SendTask, nsIRunnable); + +/* + * LayerScope SendXXX Structure + * 1. SendLayer + * 2. SendEffectChain + * 1. SendTexturedEffect + * -> SendTextureSource + * 2. SendMaskEffect + * -> SendTextureSource + * 3. SendYCbCrEffect + * -> SendTextureSource + * 4. SendColor + */ +class SenderHelper { + // Sender public APIs + public: + static void SendLayer(LayerComposite* aLayer, int aWidth, int aHeight); + + static void SendEffectChain(gl::GLContext* aGLContext, + const EffectChain& aEffectChain, int aWidth = 0, + int aHeight = 0); + + static void SetLayersTreeSendable(bool aSet) { sLayersTreeSendable = aSet; } + + static void SetLayersBufferSendable(bool aSet) { + sLayersBufferSendable = aSet; + } + + static bool GetLayersTreeSendable() { return sLayersTreeSendable; } + + static void ClearSentTextureIds(); + + // Sender private functions + private: + static void SendColor(void* aLayerRef, const DeviceColor& aColor, int aWidth, + int aHeight); + static void SendTextureSource(GLContext* aGLContext, void* aLayerRef, + TextureSourceOGL* aSource, bool aFlipY, + bool aIsMask, UniquePtr aPacket); + static void SetAndSendTexture(GLContext* aGLContext, void* aLayerRef, + TextureSourceOGL* aSource, + const TexturedEffect* aEffect); + static void SendTexturedEffect(GLContext* aGLContext, void* aLayerRef, + const TexturedEffect* aEffect); + static void SendMaskEffect(GLContext* aGLContext, void* aLayerRef, + const EffectMask* aEffect); + static void SendYCbCrEffect(GLContext* aGLContext, void* aLayerRef, + const EffectYCbCr* aEffect); + static GLuint GetTextureID(GLContext* aGLContext, TextureSourceOGL* aSource); + static bool HasTextureIdBeenSent(GLuint aTextureId); + // Data fields + private: + static bool sLayersTreeSendable; + static bool sLayersBufferSendable; + static std::vector sSentTextureIds; +}; + +bool SenderHelper::sLayersTreeSendable = true; +bool SenderHelper::sLayersBufferSendable = true; +std::vector SenderHelper::sSentTextureIds; + +// ---------------------------------------------- +// SenderHelper implementation +// ---------------------------------------------- +void SenderHelper::ClearSentTextureIds() { sSentTextureIds.clear(); } + +bool SenderHelper::HasTextureIdBeenSent(GLuint aTextureId) { + return std::find(sSentTextureIds.begin(), sSentTextureIds.end(), + aTextureId) != sSentTextureIds.end(); +} + +void SenderHelper::SendLayer(LayerComposite* aLayer, int aWidth, int aHeight) { + MOZ_ASSERT(aLayer && aLayer->GetLayer()); + if (!aLayer || !aLayer->GetLayer()) { + return; + } + + switch (aLayer->GetLayer()->GetType()) { + case Layer::TYPE_COLOR: { + EffectChain effect; + aLayer->GenEffectChain(effect); + + LayerScope::DrawBegin(); + LayerScope::DrawEnd(nullptr, effect, aWidth, aHeight); + break; + } + case Layer::TYPE_IMAGE: + case Layer::TYPE_CANVAS: + case Layer::TYPE_PAINTED: { + // Get CompositableHost and Compositor + CompositableHost* compHost = aLayer->GetCompositableHost(); + TextureSourceProvider* provider = compHost->GetTextureSourceProvider(); + Compositor* comp = provider->AsCompositor(); + // Send EffectChain only for CompositorOGL + if (LayersBackend::LAYERS_OPENGL == comp->GetBackendType()) { + CompositorOGL* compOGL = comp->AsCompositorOGL(); + EffectChain effect; + // Generate primary effect (lock and gen) + AutoLockCompositableHost lock(compHost); + aLayer->GenEffectChain(effect); + + LayerScope::DrawBegin(); + LayerScope::DrawEnd(compOGL->gl(), effect, aWidth, aHeight); + } + break; + } + case Layer::TYPE_CONTAINER: + default: + break; + } +} + +void SenderHelper::SendColor(void* aLayerRef, const DeviceColor& aColor, + int aWidth, int aHeight) { + gLayerScopeManager.GetSocketManager()->AppendDebugData( + new DebugGLColorData(aLayerRef, aColor, aWidth, aHeight)); +} + +GLuint SenderHelper::GetTextureID(GLContext* aGLContext, + TextureSourceOGL* aSource) { + GLenum textureTarget = aSource->GetTextureTarget(); + aSource->BindTexture(LOCAL_GL_TEXTURE0, gfx::SamplingFilter::LINEAR); + + GLuint texID = 0; + // This is horrid hack. It assumes that aGLContext matches the context + // aSource has bound to. + if (textureTarget == LOCAL_GL_TEXTURE_2D) { + aGLContext->GetUIntegerv(LOCAL_GL_TEXTURE_BINDING_2D, &texID); + } else if (textureTarget == LOCAL_GL_TEXTURE_EXTERNAL) { + aGLContext->GetUIntegerv(LOCAL_GL_TEXTURE_BINDING_EXTERNAL, &texID); + } else if (textureTarget == LOCAL_GL_TEXTURE_RECTANGLE) { + aGLContext->GetUIntegerv(LOCAL_GL_TEXTURE_BINDING_RECTANGLE, &texID); + } + + return texID; +} + +void SenderHelper::SendTextureSource(GLContext* aGLContext, void* aLayerRef, + TextureSourceOGL* aSource, bool aFlipY, + bool aIsMask, UniquePtr aPacket) { + MOZ_ASSERT(aGLContext); + if (!aGLContext) { + return; + } + GLuint texID = GetTextureID(aGLContext, aSource); + if (HasTextureIdBeenSent(texID)) { + return; + } + + GLenum textureTarget = aSource->GetTextureTarget(); + ShaderConfigOGL config = + ShaderConfigFromTargetAndFormat(textureTarget, aSource->GetFormat()); + int shaderConfig = config.mFeatures; + + gfx::IntSize size = aSource->GetSize(); + + // By sending 0 to ReadTextureImage rely upon aSource->BindTexture binding + // texture correctly. texID is used for tracking in DebugGLTextureData. + RefPtr img = + aGLContext->ReadTexImageHelper()->ReadTexImage(0, textureTarget, size, + shaderConfig, aFlipY); + gLayerScopeManager.GetSocketManager()->AppendDebugData( + new DebugGLTextureData(aGLContext, aLayerRef, textureTarget, texID, img, + aIsMask, std::move(aPacket))); + + sSentTextureIds.push_back(texID); + gLayerScopeManager.CurrentSession().mTexIDs.push_back(texID); +} + +void SenderHelper::SetAndSendTexture(GLContext* aGLContext, void* aLayerRef, + TextureSourceOGL* aSource, + const TexturedEffect* aEffect) { + // Expose packet creation here, so we could dump primary texture effect + // attributes. + auto packet = MakeUnique(); + layerscope::TexturePacket* texturePacket = packet->mutable_texture(); + texturePacket->set_mpremultiplied(aEffect->mPremultiplied); + DumpFilter(texturePacket, aEffect->mSamplingFilter); + DumpRect(texturePacket->mutable_mtexturecoords(), aEffect->mTextureCoords); + SendTextureSource(aGLContext, aLayerRef, aSource, false, false, + std::move(packet)); +} + +void SenderHelper::SendTexturedEffect(GLContext* aGLContext, void* aLayerRef, + const TexturedEffect* aEffect) { + TextureSourceOGL* source = aEffect->mTexture->AsSourceOGL(); + if (!source) { + return; + } + + // Fallback texture sending path. + SetAndSendTexture(aGLContext, aLayerRef, source, aEffect); +} + +void SenderHelper::SendMaskEffect(GLContext* aGLContext, void* aLayerRef, + const EffectMask* aEffect) { + TextureSourceOGL* source = aEffect->mMaskTexture->AsSourceOGL(); + if (!source) { + return; + } + + // Expose packet creation here, so we could dump secondary mask effect + // attributes. + auto packet = MakeUnique(); + TexturePacket::EffectMask* mask = packet->mutable_texture()->mutable_mask(); + mask->mutable_msize()->set_w(aEffect->mSize.width); + mask->mutable_msize()->set_h(aEffect->mSize.height); + auto element = reinterpret_cast(&(aEffect->mMaskTransform)); + for (int i = 0; i < 16; i++) { + mask->mutable_mmasktransform()->add_m(*element++); + } + + SendTextureSource(aGLContext, aLayerRef, source, false, true, + std::move(packet)); +} + +void SenderHelper::SendYCbCrEffect(GLContext* aGLContext, void* aLayerRef, + const EffectYCbCr* aEffect) { + TextureSource* sourceYCbCr = aEffect->mTexture; + if (!sourceYCbCr) return; + + const int Y = 0, Cb = 1, Cr = 2; + TextureSourceOGL* sources[] = {sourceYCbCr->GetSubSource(Y)->AsSourceOGL(), + sourceYCbCr->GetSubSource(Cb)->AsSourceOGL(), + sourceYCbCr->GetSubSource(Cr)->AsSourceOGL()}; + + for (auto source : sources) { + SetAndSendTexture(aGLContext, aLayerRef, source, aEffect); + } +} + +void SenderHelper::SendEffectChain(GLContext* aGLContext, + const EffectChain& aEffectChain, int aWidth, + int aHeight) { + if (!sLayersBufferSendable) return; + + const Effect* primaryEffect = aEffectChain.mPrimaryEffect; + MOZ_ASSERT(primaryEffect); + + if (!primaryEffect) { + return; + } + + switch (primaryEffect->mType) { + case EffectTypes::RGB: { + const TexturedEffect* texturedEffect = + static_cast(primaryEffect); + SendTexturedEffect(aGLContext, aEffectChain.mLayerRef, texturedEffect); + break; + } + case EffectTypes::YCBCR: { + const EffectYCbCr* yCbCrEffect = + static_cast(primaryEffect); + SendYCbCrEffect(aGLContext, aEffectChain.mLayerRef, yCbCrEffect); + break; + } + case EffectTypes::SOLID_COLOR: { + const EffectSolidColor* solidColorEffect = + static_cast(primaryEffect); + SendColor(aEffectChain.mLayerRef, solidColorEffect->mColor, aWidth, + aHeight); + break; + } + case EffectTypes::COMPONENT_ALPHA: + case EffectTypes::RENDER_TARGET: + default: + break; + } + + if (aEffectChain.mSecondaryEffects[EffectTypes::MASK]) { + const EffectMask* effectMask = static_cast( + aEffectChain.mSecondaryEffects[EffectTypes::MASK].get()); + SendMaskEffect(aGLContext, aEffectChain.mLayerRef, effectMask); + } +} + +void LayerScope::ContentChanged(TextureHost* host) { + if (!CheckSendable()) { + return; + } + + gLayerScopeManager.GetContentMonitor()->SetChangedHost(host); +} + +// ---------------------------------------------- +// SocketHandler implementation +// ---------------------------------------------- +void LayerScopeWebSocketManager::SocketHandler::OpenStream( + nsISocketTransport* aTransport) { + MOZ_ASSERT(aTransport); + + mTransport = aTransport; + mTransport->OpenOutputStream(nsITransport::OPEN_BLOCKING, 0, 0, + getter_AddRefs(mOutputStream)); + + nsCOMPtr debugInputStream; + mTransport->OpenInputStream(0, 0, 0, getter_AddRefs(debugInputStream)); + mInputStream = do_QueryInterface(debugInputStream); + mInputStream->AsyncWait(this, 0, 0, GetCurrentEventTarget()); +} + +bool LayerScopeWebSocketManager::SocketHandler::WriteToStream(void* aPtr, + uint32_t aSize) { + if (mState == NoHandshake) { + // Not yet handshake, just return true in case of + // LayerScope remove this handle + return true; + } else if (mState == HandshakeFailed) { + return false; + } + + if (!mOutputStream) { + return false; + } + + // Generate WebSocket header + uint8_t wsHeader[10]; + int wsHeaderSize = 0; + const uint8_t opcode = 0x2; + wsHeader[0] = 0x80 | (opcode & 0x0f); // FIN + opcode; + if (aSize <= 125) { + wsHeaderSize = 2; + wsHeader[1] = aSize; + } else if (aSize < 65536) { + wsHeaderSize = 4; + wsHeader[1] = 0x7E; + NetworkEndian::writeUint16(wsHeader + 2, aSize); + } else { + wsHeaderSize = 10; + wsHeader[1] = 0x7F; + NetworkEndian::writeUint64(wsHeader + 2, aSize); + } + + // Send WebSocket header + nsresult rv; + uint32_t cnt; + rv = mOutputStream->Write(reinterpret_cast(wsHeader), wsHeaderSize, + &cnt); + if (NS_FAILED(rv)) return false; + + uint32_t written = 0; + while (written < aSize) { + uint32_t cnt; + rv = mOutputStream->Write(reinterpret_cast(aPtr) + written, + aSize - written, &cnt); + if (NS_FAILED(rv)) return false; + + written += cnt; + } + + return true; +} + +NS_IMETHODIMP +LayerScopeWebSocketManager::SocketHandler::OnInputStreamReady( + nsIAsyncInputStream* aStream) { + MOZ_ASSERT(mInputStream); + + if (!mInputStream) { + return NS_OK; + } + + if (!mConnected) { + nsTArray protocolString; + ReadInputStreamData(protocolString); + + if (WebSocketHandshake(protocolString)) { + mState = HandshakeSuccess; + mConnected = true; + mInputStream->AsyncWait(this, 0, 0, GetCurrentEventTarget()); + } else { + mState = HandshakeFailed; + } + return NS_OK; + } else { + return HandleSocketMessage(aStream); + } +} + +void LayerScopeWebSocketManager::SocketHandler::ReadInputStreamData( + nsTArray& aProtocolString) { + nsLineBuffer lineBuffer; + nsCString line; + bool more = true; + do { + NS_ReadLine(mInputStream.get(), &lineBuffer, line, &more); + + if (line.Length() > 0) { + aProtocolString.AppendElement(line); + } + } while (more && line.Length() > 0); +} + +bool LayerScopeWebSocketManager::SocketHandler::WebSocketHandshake( + nsTArray& aProtocolString) { + nsresult rv; + bool isWebSocket = false; + nsCString version; + nsCString wsKey; + nsCString protocol; + + // Validate WebSocket client request. + if (aProtocolString.Length() == 0) return false; + + // Check that the HTTP method is GET + const char* HTTP_METHOD = "GET "; + if (strncmp(aProtocolString[0].get(), HTTP_METHOD, strlen(HTTP_METHOD)) != + 0) { + return false; + } + + for (uint32_t i = 1; i < aProtocolString.Length(); ++i) { + const char* line = aProtocolString[i].get(); + const char* prop_pos = strchr(line, ':'); + if (prop_pos != nullptr) { + nsCString key(line, prop_pos - line); + nsCString value(prop_pos + 2); + if (key.EqualsIgnoreCase("upgrade") && + value.EqualsIgnoreCase("websocket")) { + isWebSocket = true; + } else if (key.EqualsIgnoreCase("sec-websocket-version")) { + version = value; + } else if (key.EqualsIgnoreCase("sec-websocket-key")) { + wsKey = value; + } else if (key.EqualsIgnoreCase("sec-websocket-protocol")) { + protocol = value; + } + } + } + + if (!isWebSocket) { + return false; + } + + if (!(version.EqualsLiteral("7") || version.EqualsLiteral("8") || + version.EqualsLiteral("13"))) { + return false; + } + + if (!(protocol.EqualsIgnoreCase("binary"))) { + return false; + } + + if (!mOutputStream) { + return false; + } + + // Client request is valid. Start to generate and send server response. + nsAutoCString guid("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); + nsAutoCString res; + SHA1Sum sha1; + nsCString combined(wsKey + guid); + sha1.update(combined.get(), combined.Length()); + uint8_t digest[SHA1Sum::kHashSize]; // SHA1 digests are 20 bytes long. + sha1.finish(digest); + nsCString newString(reinterpret_cast(digest), SHA1Sum::kHashSize); + nsCString response("HTTP/1.1 101 Switching Protocols\r\n"); + response.AppendLiteral("Upgrade: websocket\r\n"); + response.AppendLiteral("Connection: Upgrade\r\n"); + response.AppendLiteral("Sec-WebSocket-Accept: "); + rv = Base64EncodeAppend(newString, response); + if (NS_FAILED(rv)) { + return false; + } + response.AppendLiteral("\r\n"); + response.AppendLiteral("Sec-WebSocket-Protocol: binary\r\n\r\n"); + uint32_t written = 0; + uint32_t size = response.Length(); + while (written < size) { + uint32_t cnt; + rv = mOutputStream->Write(const_cast(response.get()) + written, + size - written, &cnt); + if (NS_FAILED(rv)) return false; + + written += cnt; + } + mOutputStream->Flush(); + + return true; +} + +nsresult LayerScopeWebSocketManager::SocketHandler::HandleSocketMessage( + nsIAsyncInputStream* aStream) { + // The reading and parsing of this input stream is customized for layer + // viewer. + const uint32_t cPacketSize = 1024; + char buffer[cPacketSize]; + uint32_t count = 0; + nsresult rv = NS_OK; + + do { + rv = mInputStream->Read((char*)buffer, cPacketSize, &count); + + // TODO: combine packets if we have to read more than once + + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { + mInputStream->AsyncWait(this, 0, 0, GetCurrentEventTarget()); + return NS_OK; + } + + if (NS_FAILED(rv)) { + break; + } + + if (count == 0) { + // NS_BASE_STREAM_CLOSED + CloseConnection(); + break; + } + + rv = ProcessInput(reinterpret_cast(buffer), count); + } while (NS_SUCCEEDED(rv) && mInputStream); + return rv; +} + +nsresult LayerScopeWebSocketManager::SocketHandler::ProcessInput( + uint8_t* aBuffer, uint32_t aCount) { + uint32_t avail = aCount; + + // Decode Websocket data frame + if (avail <= 2) { + NS_WARNING("Packet size is less than 2 bytes"); + return NS_OK; + } + + // First byte, data type, only care the opcode + // rsvBits: aBuffer[0] & 0x70 (0111 0000) + uint8_t finBit = aBuffer[0] & 0x80; // 1000 0000 + uint8_t opcode = aBuffer[0] & 0x0F; // 0000 1111 + + if (!finBit) { + NS_WARNING( + "We cannot handle multi-fragments messages in Layerscope websocket " + "parser."); + return NS_OK; + } + + // Second byte, data length + uint8_t maskBit = aBuffer[1] & 0x80; // 1000 0000 + int64_t payloadLength64 = aBuffer[1] & 0x7F; // 0111 1111 + + if (!maskBit) { + NS_WARNING("Client to Server should set the mask bit"); + return NS_OK; + } + + uint32_t framingLength = 2 + 4; // 4 for masks + + if (payloadLength64 < 126) { + if (avail < framingLength) return NS_OK; + } else if (payloadLength64 == 126) { + // 16 bit length field + framingLength += 2; + if (avail < framingLength) { + return NS_OK; + } + + payloadLength64 = aBuffer[2] << 8 | aBuffer[3]; + } else { + // 64 bit length + framingLength += 8; + if (avail < framingLength) { + return NS_OK; + } + + if (aBuffer[2] & 0x80) { + // Section 4.2 says that the most significant bit MUST be + // 0. (i.e. this is really a 63 bit value) + NS_WARNING("High bit of 64 bit length set"); + return NS_ERROR_ILLEGAL_VALUE; + } + + // copy this in case it is unaligned + payloadLength64 = NetworkEndian::readInt64(aBuffer + 2); + } + + uint8_t* payload = aBuffer + framingLength; + avail -= framingLength; + + uint32_t payloadLength = static_cast(payloadLength64); + if (avail < payloadLength) { + NS_WARNING("Packet size mismatch the payload length"); + return NS_OK; + } + + // Apply mask + uint32_t mask = NetworkEndian::readUint32(payload - 4); + ApplyMask(mask, payload, payloadLength); + + if (opcode == 0x8) { + // opcode == 0x8 means connection close + CloseConnection(); + return NS_BASE_STREAM_CLOSED; + } + + if (!HandleDataFrame(payload, payloadLength)) { + NS_WARNING("Cannot decode payload data by the protocol buffer"); + } + + return NS_OK; +} + +void LayerScopeWebSocketManager::SocketHandler::ApplyMask(uint32_t aMask, + uint8_t* aData, + uint64_t aLen) { + if (!aData || aLen == 0) { + return; + } + + // Optimally we want to apply the mask 32 bits at a time, + // but the buffer might not be alligned. So we first deal with + // 0 to 3 bytes of preamble individually + while (aLen && (reinterpret_cast(aData) & 3)) { + *aData ^= aMask >> 24; + aMask = RotateLeft(aMask, 8); + aData++; + aLen--; + } + + // perform mask on full words of data + uint32_t* iData = reinterpret_cast(aData); + uint32_t* end = iData + (aLen >> 2); + NetworkEndian::writeUint32(&aMask, aMask); + for (; iData < end; iData++) { + *iData ^= aMask; + } + aMask = NetworkEndian::readUint32(&aMask); + aData = (uint8_t*)iData; + aLen = aLen % 4; + + // There maybe up to 3 trailing bytes that need to be dealt with + // individually + while (aLen) { + *aData ^= aMask >> 24; + aMask = RotateLeft(aMask, 8); + aData++; + aLen--; + } +} + +bool LayerScopeWebSocketManager::SocketHandler::HandleDataFrame( + uint8_t* aData, uint32_t aSize) { + // Handle payload data by protocol buffer + auto p = MakeUnique(); + p->ParseFromArray(static_cast(aData), aSize); + + if (!p->has_type()) { + MOZ_ASSERT(false, "Protocol buffer decoding failed or cannot recongize it"); + return false; + } + + switch (p->type()) { + case CommandPacket::LAYERS_TREE: + if (p->has_value()) { + SenderHelper::SetLayersTreeSendable(p->value()); + } + break; + + case CommandPacket::LAYERS_BUFFER: + if (p->has_value()) { + SenderHelper::SetLayersBufferSendable(p->value()); + } + break; + + case CommandPacket::NO_OP: + default: + NS_WARNING("Invalid message type"); + break; + } + return true; +} + +void LayerScopeWebSocketManager::SocketHandler::CloseConnection() { + gLayerScopeManager.GetSocketManager()->CleanDebugData(); + if (mInputStream) { + mInputStream->AsyncWait(nullptr, 0, 0, nullptr); + mInputStream = nullptr; + } + if (mOutputStream) { + mOutputStream = nullptr; + } + if (mTransport) { + mTransport->Close(NS_BASE_STREAM_CLOSED); + mTransport = nullptr; + } + mConnected = false; +} + +// ---------------------------------------------- +// LayerScopeWebSocketManager implementation +// ---------------------------------------------- +LayerScopeWebSocketManager::LayerScopeWebSocketManager() + : mHandlerMutex("LayerScopeWebSocketManager::mHandlerMutex") { + NS_NewNamedThread("LayerScope", getter_AddRefs(mDebugSenderThread)); + + mServerSocket = do_CreateInstance(NS_SERVERSOCKET_CONTRACTID); + int port = StaticPrefs::gfx_layerscope_port(); + mServerSocket->Init(port, false, -1); + mServerSocket->AsyncListen(new SocketListener); +} + +LayerScopeWebSocketManager::~LayerScopeWebSocketManager() { + mServerSocket->Close(); +} + +void LayerScopeWebSocketManager::AppendDebugData(DebugGLData* aDebugData) { + if (!mCurrentSender) { + mCurrentSender = new DebugDataSender(mDebugSenderThread); + } + + mCurrentSender->Append(aDebugData); +} + +void LayerScopeWebSocketManager::CleanDebugData() { + if (mCurrentSender) { + mCurrentSender->Cleanup(); + } +} + +void LayerScopeWebSocketManager::DispatchDebugData() { + MOZ_ASSERT(mCurrentSender.get() != nullptr); + + mCurrentSender->Send(); + mCurrentSender = nullptr; +} + +NS_IMETHODIMP LayerScopeWebSocketManager::SocketListener::OnSocketAccepted( + nsIServerSocket* aServ, nsISocketTransport* aTransport) { + if (!gLayerScopeManager.GetSocketManager()) return NS_OK; + + printf_stderr("*** LayerScope: Accepted connection\n"); + gLayerScopeManager.GetSocketManager()->AddConnection(aTransport); + gLayerScopeManager.GetContentMonitor()->Empty(); + return NS_OK; +} + +// ---------------------------------------------- +// LayerScope implementation +// ---------------------------------------------- +/*static*/ +void LayerScope::Init() { + if (!StaticPrefs::gfx_layerscope_enabled() || XRE_IsGPUProcess()) { + return; + } + + gLayerScopeManager.CreateServerSocket(); +} + +/*static*/ +void LayerScope::DrawBegin() { + if (!CheckSendable()) { + return; + } + + gLayerScopeManager.NewDrawSession(); +} + +/*static*/ +void LayerScope::SetRenderOffset(float aX, float aY) { + if (!CheckSendable()) { + return; + } + + gLayerScopeManager.CurrentSession().mOffsetX = aX; + gLayerScopeManager.CurrentSession().mOffsetY = aY; +} + +/*static*/ +void LayerScope::SetLayerTransform(const gfx::Matrix4x4& aMatrix) { + if (!CheckSendable()) { + return; + } + + gLayerScopeManager.CurrentSession().mMVMatrix = aMatrix; +} + +/*static*/ +void LayerScope::SetDrawRects(size_t aRects, const gfx::Rect* aLayerRects, + const gfx::Rect* aTextureRects) { + if (!CheckSendable()) { + return; + } + + MOZ_ASSERT(aRects > 0 && aRects <= 4); + MOZ_ASSERT(aLayerRects); + + gLayerScopeManager.CurrentSession().mRects = aRects; + + for (size_t i = 0; i < aRects; i++) { + gLayerScopeManager.CurrentSession().mLayerRects[i] = aLayerRects[i]; + gLayerScopeManager.CurrentSession().mTextureRects[i] = aTextureRects[i]; + } +} + +/*static*/ +void LayerScope::DrawEnd(gl::GLContext* aGLContext, + const EffectChain& aEffectChain, int aWidth, + int aHeight) { + // Protect this public function + if (!CheckSendable()) { + return; + } + + // 1. Send textures. + SenderHelper::SendEffectChain(aGLContext, aEffectChain, aWidth, aHeight); + + // 2. Send parameters of draw call, such as uniforms and attributes of + // vertex adnd fragment shader. + DrawSession& draws = gLayerScopeManager.CurrentSession(); + gLayerScopeManager.GetSocketManager()->AppendDebugData( + new DebugGLDrawData(draws.mOffsetX, draws.mOffsetY, draws.mMVMatrix, + draws.mRects, draws.mLayerRects, draws.mTextureRects, + draws.mTexIDs, aEffectChain.mLayerRef)); +} + +/*static*/ +void LayerScope::SendLayer(LayerComposite* aLayer, int aWidth, int aHeight) { + // Protect this public function + if (!CheckSendable()) { + return; + } + SenderHelper::SendLayer(aLayer, aWidth, aHeight); +} + +/*static*/ +void LayerScope::SendLayerDump(UniquePtr aPacket) { + // Protect this public function + if (!CheckSendable() || !SenderHelper::GetLayersTreeSendable()) { + return; + } + gLayerScopeManager.GetSocketManager()->AppendDebugData( + new DebugGLLayersData(std::move(aPacket))); +} + +/*static*/ +bool LayerScope::CheckSendable() { + // Only compositor threads check LayerScope status + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread() || gIsGtest); + + if (!StaticPrefs::gfx_layerscope_enabled()) { + return false; + } + if (!gLayerScopeManager.GetSocketManager()) { + Init(); + return false; + } + if (!gLayerScopeManager.GetSocketManager()->IsConnected()) { + return false; + } + return true; +} + +/*static*/ +void LayerScope::CleanLayer() { + if (CheckSendable()) { + gLayerScopeManager.GetSocketManager()->CleanDebugData(); + } +} + +/*static*/ +void LayerScope::SetHWComposed() { + if (CheckSendable()) { + gLayerScopeManager.GetSocketManager()->AppendDebugData( + new DebugGLMetaData(Packet::META, true)); + } +} + +/*static*/ +void LayerScope::SetPixelScale(double devPixelsPerCSSPixel) { + gLayerScopeManager.SetPixelScale(devPixelsPerCSSPixel); +} + +// ---------------------------------------------- +// LayerScopeAutoFrame implementation +// ---------------------------------------------- +LayerScopeAutoFrame::LayerScopeAutoFrame(int64_t aFrameStamp) { + // Do Begin Frame + BeginFrame(aFrameStamp); +} + +LayerScopeAutoFrame::~LayerScopeAutoFrame() { + // Do End Frame + EndFrame(); +} + +void LayerScopeAutoFrame::BeginFrame(int64_t aFrameStamp) { + if (!LayerScope::CheckSendable()) { + return; + } + SenderHelper::ClearSentTextureIds(); + + gLayerScopeManager.GetSocketManager()->AppendDebugData( + new DebugGLFrameStatusData(Packet::FRAMESTART, aFrameStamp)); +} + +void LayerScopeAutoFrame::EndFrame() { + if (!LayerScope::CheckSendable()) { + return; + } + + gLayerScopeManager.GetSocketManager()->AppendDebugData( + new DebugGLFrameStatusData(Packet::FRAMEEND)); + gLayerScopeManager.GetSocketManager()->DispatchDebugData(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/LayerScope.h b/gfx/layers/LayerScope.h new file mode 100644 index 0000000000..89c09d46a2 --- /dev/null +++ b/gfx/layers/LayerScope.h @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=4 sw=2 sts=2 et: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_LAYERSCOPE_H +#define GFX_LAYERSCOPE_H + +#include +#include +#include "gfxMatrix.h" +#include "mozilla/gfx/Rect.h" + +namespace mozilla { + +namespace gl { +class GLContext; +} + +namespace layers { + +namespace layerscope { +class Packet; +} + +struct EffectChain; +class LayerComposite; +class TextureHost; + +class LayerScope { + public: + static void DrawBegin(); + static void SetRenderOffset(float aX, float aY); + static void SetLayerTransform(const gfx::Matrix4x4& aMatrix); + static void SetDrawRects(size_t aRects, const gfx::Rect* aLayerRects, + const gfx::Rect* aTextureRects); + static void DrawEnd(gl::GLContext* aGLContext, + const EffectChain& aEffectChain, int aWidth, int aHeight); + + static void SendLayer(LayerComposite* aLayer, int aWidth, int aHeight); + static void SendLayerDump(UniquePtr aPacket); + static bool CheckSendable(); + static void CleanLayer(); + static void SetHWComposed(); + + static void SetPixelScale(double devPixelsPerCSSPixel); + static void ContentChanged(TextureHost* host); + + private: + static void Init(); +}; + +// Perform BeginFrame and EndFrame automatically +class LayerScopeAutoFrame final { + public: + explicit LayerScopeAutoFrame(int64_t aFrameStamp); + ~LayerScopeAutoFrame(); + + private: + static void BeginFrame(int64_t aFrameStamp); + static void EndFrame(); +}; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_LAYERSCOPE_H */ diff --git a/gfx/layers/LayerSorter.cpp b/gfx/layers/LayerSorter.cpp new file mode 100644 index 0000000000..65b5264dac --- /dev/null +++ b/gfx/layers/LayerSorter.cpp @@ -0,0 +1,358 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "LayerSorter.h" +#include // for fabs +#include // for uint32_t +#include // for fprintf, stderr, FILE +#include // for getenv +#include "DirectedGraph.h" // for DirectedGraph +#include "Layers.h" // for Layer +#include "gfxEnv.h" // for gfxEnv +#include "gfxLineSegment.h" // for gfxLineSegment +#include "gfxPoint.h" // for gfxPoint +#include "gfxQuad.h" // for gfxQuad +#include "gfxRect.h" // for gfxRect +#include "gfxTypes.h" // for gfxFloat +#include "gfxUtils.h" // for TransformToQuad +#include "mozilla/gfx/BasePoint3D.h" // for BasePoint3D +#include "mozilla/Sprintf.h" // for SprintfLiteral +#include "nsRegion.h" // for nsIntRegion +#include "nsTArray.h" // for nsTArray, etc +#include "limits.h" +#include "mozilla/Assertions.h" + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +enum LayerSortOrder { + Undefined, + ABeforeB, + BBeforeA, +}; + +/** + * Recover the z component from a 2d transformed point by finding the + * intersection of a line through the point in the z direction and the + * transformed plane. + * + * We want to solve: + * + * point = normal . (p0 - l0) / normal . l + */ +static gfxFloat RecoverZDepth(const Matrix4x4& aTransform, + const gfxPoint& aPoint) { + const Point3D l(0, 0, 1); + Point3D l0 = Point3D(aPoint.x, aPoint.y, 0); + Point3D p0 = aTransform.TransformPoint(Point3D(0, 0, 0)); + Point3D normal = aTransform.GetNormalVector(); + + gfxFloat n = normal.DotProduct(p0 - l0); + gfxFloat d = normal.DotProduct(l); + + if (!d) { + return 0; + } + + return n / d; +} + +/** + * Determine if this transform layer should be drawn before another when they + * are both preserve-3d children. + * + * We want to find the relative z depths of the 2 layers at points where they + * intersect when projected onto the 2d screen plane. Intersections are defined + * as corners that are positioned within the other quad, as well as + * intersections of the lines. + * + * We then choose the intersection point with the greatest difference in Z + * depths and use this point to determine an ordering for the two layers. + * For layers that are intersecting in 3d space, this essentially guesses an + * order. In a lot of cases we only intersect right at the edge point (3d cubes + * in particular) and this generates the 'correct' looking ordering. For planes + * that truely intersect, then there is no correct ordering and this remains + * unsolved without changing our rendering code. + */ +static LayerSortOrder CompareDepth(Layer* aOne, Layer* aTwo) { + gfxRect ourRect = + ThebesRect(aOne->GetLocalVisibleRegion().GetBounds().ToUnknownRect()); + gfxRect otherRect = + ThebesRect(aTwo->GetLocalVisibleRegion().GetBounds().ToUnknownRect()); + + MOZ_ASSERT(aOne->GetParent() && aOne->GetParent()->Extend3DContext() && + aTwo->GetParent() && aTwo->GetParent()->Extend3DContext()); + // Effective transform of leaves may had been projected to 2D. + Matrix4x4 ourTransform = + aOne->GetLocalTransform() * aOne->GetParent()->GetEffectiveTransform(); + Matrix4x4 otherTransform = + aTwo->GetLocalTransform() * aTwo->GetParent()->GetEffectiveTransform(); + + // Transform both rectangles and project into 2d space. + gfxQuad ourTransformedRect = gfxUtils::TransformToQuad(ourRect, ourTransform); + gfxQuad otherTransformedRect = + gfxUtils::TransformToQuad(otherRect, otherTransform); + + gfxRect ourBounds = ourTransformedRect.GetBounds(); + gfxRect otherBounds = otherTransformedRect.GetBounds(); + + if (!ourBounds.Intersects(otherBounds)) { + return Undefined; + } + + // Make a list of all points that are within the other rect. + // Could we just check Contains() on the bounds rects. ie, is it possible + // for layers to overlap without intersections (in 2d space) and yet still + // have their bounds rects not completely enclose each other? + nsTArray points; + for (uint32_t i = 0; i < 4; i++) { + if (ourTransformedRect.Contains(otherTransformedRect.mPoints[i])) { + points.AppendElement(otherTransformedRect.mPoints[i]); + } + if (otherTransformedRect.Contains(ourTransformedRect.mPoints[i])) { + points.AppendElement(ourTransformedRect.mPoints[i]); + } + } + + // Look for intersections between lines (in 2d space) and use these as + // depth testing points. + for (uint32_t i = 0; i < 4; i++) { + for (uint32_t j = 0; j < 4; j++) { + gfxPoint intersection; + gfxLineSegment one(ourTransformedRect.mPoints[i], + ourTransformedRect.mPoints[(i + 1) % 4]); + gfxLineSegment two(otherTransformedRect.mPoints[j], + otherTransformedRect.mPoints[(j + 1) % 4]); + if (one.Intersects(two, intersection)) { + points.AppendElement(intersection); + } + } + } + + // No intersections, no defined order between these layers. + if (points.IsEmpty()) { + return Undefined; + } + + // Find the relative Z depths of each intersection point and check that the + // layers are in the same order. + gfxFloat highest = 0; + for (uint32_t i = 0; i < points.Length(); i++) { + gfxFloat ourDepth = RecoverZDepth(ourTransform, points.ElementAt(i)); + gfxFloat otherDepth = RecoverZDepth(otherTransform, points.ElementAt(i)); + + gfxFloat difference = otherDepth - ourDepth; + + if (fabs(difference) > fabs(highest)) { + highest = difference; + } + } + // If layers have the same depth keep the original order + if (fabs(highest) < 0.1 || highest >= 0) { + return ABeforeB; + } else { + return BBeforeA; + } +} + +#ifdef DEBUG +// #define USE_XTERM_COLORING +# ifdef USE_XTERM_COLORING +// List of color values, which can be added to the xterm foreground offset or +// background offset to generate a xterm color code. +// NOTE: The colors that we don't explicitly use (by name) are commented out, +// to avoid triggering Wunused-const-variable build warnings. +static const int XTERM_FOREGROUND_COLOR_OFFSET = 30; +static const int XTERM_BACKGROUND_COLOR_OFFSET = 40; +static const int BLACK = 0; +// static const int RED = 1; +static const int GREEN = 2; +// static const int YELLOW = 3; +// static const int BLUE = 4; +// static const int MAGENTA = 5; +// static const int CYAN = 6; +// static const int WHITE = 7; + +static const int RESET = 0; +// static const int BRIGHT = 1; +// static const int DIM = 2; +// static const int UNDERLINE = 3; +// static const int BLINK = 4; +// static const int REVERSE = 7; +// static const int HIDDEN = 8; + +static void SetTextColor(uint32_t aColor) { + char command[13]; + + /* Command is the control command to the terminal */ + SprintfLiteral(command, "%c[%d;%d;%dm", 0x1B, RESET, + aColor + XTERM_FOREGROUND_COLOR_OFFSET, + BLACK + XTERM_BACKGROUND_COLOR_OFFSET); + printf("%s", command); +} + +static void print_layer_internal(FILE* aFile, Layer* aLayer, uint32_t aColor) { + SetTextColor(aColor); + fprintf(aFile, "%p", aLayer); + SetTextColor(GREEN); +} +# else + +const char* colors[] = {"Black", "Red", "Green", "Yellow", + "Blue", "Magenta", "Cyan", "White"}; + +static void print_layer_internal(FILE* aFile, Layer* aLayer, uint32_t aColor) { + fprintf(aFile, "%p(%s)", aLayer, colors[aColor]); +} +# endif + +static void print_layer(FILE* aFile, Layer* aLayer) { + print_layer_internal(aFile, aLayer, aLayer->GetDebugColorIndex()); +} + +static void DumpLayerList(nsTArray& aLayers) { + for (uint32_t i = 0; i < aLayers.Length(); i++) { + print_layer(stderr, aLayers.ElementAt(i)); + fprintf(stderr, " "); + } + fprintf(stderr, "\n"); +} + +static void DumpEdgeList(DirectedGraph& aGraph) { + const nsTArray::Edge>& edges = aGraph.GetEdgeList(); + + for (uint32_t i = 0; i < edges.Length(); i++) { + fprintf(stderr, "From: "); + print_layer(stderr, edges.ElementAt(i).mFrom); + fprintf(stderr, ", To: "); + print_layer(stderr, edges.ElementAt(i).mTo); + fprintf(stderr, "\n"); + } +} +#endif + +// The maximum number of layers that we will attempt to sort. Anything +// greater than this will be left unsorted. We should consider enabling +// depth buffering for the scene in this case. +#define MAX_SORTABLE_LAYERS 100 + +uint32_t gColorIndex = 1; + +void SortLayersBy3DZOrder(nsTArray& aLayers) { + uint32_t nodeCount = aLayers.Length(); + if (nodeCount > MAX_SORTABLE_LAYERS) { + return; + } + DirectedGraph graph; + +#ifdef DEBUG + if (gfxEnv::DumpLayerSortList()) { + for (uint32_t i = 0; i < nodeCount; i++) { + if (aLayers.ElementAt(i)->GetDebugColorIndex() == 0) { + aLayers.ElementAt(i)->SetDebugColorIndex(gColorIndex++); + if (gColorIndex > 7) { + gColorIndex = 1; + } + } + } + fprintf(stderr, " --- Layers before sorting: --- \n"); + DumpLayerList(aLayers); + } +#endif + + // Iterate layers and determine edges. + for (uint32_t i = 0; i < nodeCount; i++) { + for (uint32_t j = i + 1; j < nodeCount; j++) { + Layer* a = aLayers.ElementAt(i); + Layer* b = aLayers.ElementAt(j); + LayerSortOrder order = CompareDepth(a, b); + if (order == ABeforeB) { + graph.AddEdge(a, b); + } else if (order == BBeforeA) { + graph.AddEdge(b, a); + } + } + } + +#ifdef DEBUG + if (gfxEnv::DumpLayerSortList()) { + fprintf(stderr, " --- Edge List: --- \n"); + DumpEdgeList(graph); + } +#endif + + // Build a new array using the graph. + nsTArray noIncoming; + nsTArray sortedList; + + // Make a list of all layers with no incoming edges. + noIncoming.AppendElements(aLayers); + const nsTArray::Edge>& edges = graph.GetEdgeList(); + for (uint32_t i = 0; i < edges.Length(); i++) { + noIncoming.RemoveElement(edges.ElementAt(i).mTo); + } + + // Move each item without incoming edges into the sorted list, + // and remove edges from it. + do { + if (!noIncoming.IsEmpty()) { + Layer* layer = noIncoming.PopLastElement(); + MOZ_ASSERT(layer); // don't let null layer pointers sneak into sortedList + + sortedList.AppendElement(layer); + + nsTArray::Edge> outgoing; + graph.GetEdgesFrom(layer, outgoing); + for (uint32_t i = 0; i < outgoing.Length(); i++) { + DirectedGraph::Edge edge = outgoing.ElementAt(i); + graph.RemoveEdge(edge); + if (!graph.NumEdgesTo(edge.mTo)) { + // If this node also has no edges now, add it to the list + noIncoming.AppendElement(edge.mTo); + } + } + } + + // If there are no nodes without incoming edges, but there + // are still edges, then we have a cycle. + if (noIncoming.IsEmpty() && graph.GetEdgeCount()) { + // Find the node with the least incoming edges. + uint32_t minEdges = UINT_MAX; + Layer* minNode = nullptr; + for (uint32_t i = 0; i < aLayers.Length(); i++) { + uint32_t edgeCount = graph.NumEdgesTo(aLayers.ElementAt(i)); + if (edgeCount && edgeCount < minEdges) { + minEdges = edgeCount; + minNode = aLayers.ElementAt(i); + if (minEdges == 1) { + break; + } + } + } + + if (minNode) { + // Remove all of them! + graph.RemoveEdgesTo(minNode); + noIncoming.AppendElement(minNode); + } + } + } while (!noIncoming.IsEmpty()); + NS_ASSERTION(!graph.GetEdgeCount(), "Cycles detected!"); +#ifdef DEBUG + if (gfxEnv::DumpLayerSortList()) { + fprintf(stderr, " --- Layers after sorting: --- \n"); + DumpLayerList(sortedList); + } +#endif + + aLayers.Clear(); + aLayers.AppendElements(sortedList); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/LayerSorter.h b/gfx/layers/LayerSorter.h new file mode 100644 index 0000000000..85878baace --- /dev/null +++ b/gfx/layers/LayerSorter.h @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_LAYERSORTER_H +#define GFX_LAYERSORTER_H + +#include "nsTArray.h" + +namespace mozilla { +namespace layers { + +class Layer; + +void SortLayersBy3DZOrder(nsTArray& aLayers); + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_LAYERSORTER_H */ diff --git a/gfx/layers/LayerTreeInvalidation.cpp b/gfx/layers/LayerTreeInvalidation.cpp new file mode 100644 index 0000000000..6b8c33e315 --- /dev/null +++ b/gfx/layers/LayerTreeInvalidation.cpp @@ -0,0 +1,820 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "LayerTreeInvalidation.h" + +#include // for uint32_t +#include "ImageContainer.h" // for ImageContainer +#include "ImageLayers.h" // for ImageLayer, etc +#include "Layers.h" // for Layer, ContainerLayer, etc +#include "Units.h" // for ParentLayerIntRect +#include "gfxRect.h" // for gfxRect +#include "gfxUtils.h" // for gfxUtils +#include "mozilla/ArrayUtils.h" // for ArrayEqual +#include "mozilla/gfx/BaseSize.h" // for BaseSize +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/mozalloc.h" // for operator new, etc +#include "nsDataHashtable.h" // for nsDataHashtable +#include "nsDebug.h" // for NS_ASSERTION +#include "nsHashKeys.h" // for nsPtrHashKey +#include "nsISupportsImpl.h" // for Layer::AddRef, etc +#include "nsRect.h" // for IntRect +#include "nsTArray.h" // for AutoTArray, nsTArray_Impl +#include "mozilla/Poison.h" +#include "mozilla/layers/ImageHost.h" +#include "mozilla/layers/LayerManagerComposite.h" +#include "TreeTraversal.h" // for ForEachNode + +// LayerTreeInvalidation debugging +#define LTI_DEBUG 0 + +#if LTI_DEBUG +# define LTI_DEEPER(aPrefix) nsPrintfCString("%s ", aPrefix).get() +# define LTI_DUMP(rgn, label) \ + if (!(rgn).IsEmpty()) \ + printf_stderr("%s%p: " label " portion is %s\n", aPrefix, mLayer.get(), \ + Stringify(rgn).c_str()); +# define LTI_LOG(...) printf_stderr(__VA_ARGS__) +#else +# define LTI_DEEPER(aPrefix) nullptr +# define LTI_DUMP(rgn, label) +# define LTI_LOG(...) +#endif + +using namespace mozilla::gfx; + +namespace mozilla { +namespace layers { + +struct LayerPropertiesBase; +UniquePtr CloneLayerTreePropertiesInternal( + Layer* aRoot, bool aIsMask = false); + +/** + * Get accumulated transform of from the context creating layer to the + * given layer. + */ +static Matrix4x4 GetTransformIn3DContext(Layer* aLayer) { + Matrix4x4 transform = aLayer->GetLocalTransform(); + for (Layer* layer = aLayer->GetParent(); layer && layer->Extend3DContext(); + layer = layer->GetParent()) { + transform = transform * layer->GetLocalTransform(); + } + return transform; +} + +/** + * Get a transform for the given layer depending on extending 3D + * context. + * + * @return local transform for layers not participating 3D rendering + * context, or the accmulated transform in the context for else. + */ +static Matrix4x4Flagged GetTransformForInvalidation(Layer* aLayer) { + return (!aLayer->Is3DContextLeaf() && !aLayer->Extend3DContext() + ? aLayer->GetLocalTransform() + : GetTransformIn3DContext(aLayer)); +} + +static IntRect TransformRect(const IntRect& aRect, + const Matrix4x4Flagged& aTransform) { + if (aRect.IsEmpty()) { + return IntRect(); + } + + Rect rect(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()); + rect = aTransform.TransformAndClipBounds(rect, Rect::MaxIntRect()); + rect.RoundOut(); + + IntRect intRect; + if (!rect.ToIntRect(&intRect)) { + intRect = IntRect::MaxIntRect(); + } + + return intRect; +} + +static void AddTransformedRegion(nsIntRegion& aDest, const nsIntRegion& aSource, + const Matrix4x4Flagged& aTransform) { + for (auto iter = aSource.RectIter(); !iter.Done(); iter.Next()) { + aDest.Or(aDest, TransformRect(iter.Get(), aTransform)); + } + aDest.SimplifyOutward(20); +} + +static void AddRegion(nsIntRegion& aDest, const nsIntRegion& aSource) { + aDest.Or(aDest, aSource); + aDest.SimplifyOutward(20); +} + +Maybe TransformedBounds(Layer* aLayer) { + if (aLayer->Extend3DContext()) { + ContainerLayer* container = aLayer->AsContainerLayer(); + MOZ_ASSERT(container); + IntRect result; + for (Layer* child = container->GetFirstChild(); child; + child = child->GetNextSibling()) { + Maybe childBounds = TransformedBounds(child); + if (!childBounds) { + return Nothing(); + } + Maybe combined = result.SafeUnion(childBounds.value()); + if (!combined) { + LTI_LOG("overflowed bounds of container %p accumulating child %p\n", + container, child); + return Nothing(); + } + result = combined.value(); + } + return Some(result); + } + + return Some( + TransformRect(aLayer->GetLocalVisibleRegion().GetBounds().ToUnknownRect(), + GetTransformForInvalidation(aLayer))); +} + +/** + * Walks over this layer, and all descendant layers. + * If any of these are a ContainerLayer that reports invalidations to a + * PresShell, then report that the entire bounds have changed. + */ +static void NotifySubdocumentInvalidation( + Layer* aLayer, NotifySubDocInvalidationFunc aCallback) { + ForEachNode( + aLayer, + [aCallback](Layer* layer) { + layer->ClearInvalidRegion(); + if (layer->GetMaskLayer()) { + NotifySubdocumentInvalidation(layer->GetMaskLayer(), aCallback); + } + for (size_t i = 0; i < layer->GetAncestorMaskLayerCount(); i++) { + Layer* maskLayer = layer->GetAncestorMaskLayerAt(i); + NotifySubdocumentInvalidation(maskLayer, aCallback); + } + }, + [aCallback](Layer* layer) { + ContainerLayer* container = layer->AsContainerLayer(); + if (container && !container->Extend3DContext()) { + nsIntRegion region = + container->GetLocalVisibleRegion().ToUnknownRegion(); + aCallback(container, ®ion); + } + }); +} + +static void SetChildrenChangedRecursive(Layer* aLayer) { + ForEachNode(aLayer, [](Layer* layer) { + ContainerLayer* container = layer->AsContainerLayer(); + if (container) { + container->SetChildrenChanged(true); + container->SetInvalidCompositeRect(nullptr); + } + }); +} + +struct LayerPropertiesBase : public LayerProperties { + explicit LayerPropertiesBase(Layer* aLayer) + : mLayer(aLayer), + mMaskLayer(nullptr), + mVisibleRegion(mLayer->Extend3DContext() + ? nsIntRegion() + : mLayer->GetLocalVisibleRegion().ToUnknownRegion()), + mPostXScale(aLayer->GetPostXScale()), + mPostYScale(aLayer->GetPostYScale()), + mOpacity(aLayer->GetLocalOpacity()), + mUseClipRect(!!aLayer->GetLocalClipRect()) { + MOZ_COUNT_CTOR(LayerPropertiesBase); + if (aLayer->GetMaskLayer()) { + mMaskLayer = + CloneLayerTreePropertiesInternal(aLayer->GetMaskLayer(), true); + } + for (size_t i = 0; i < aLayer->GetAncestorMaskLayerCount(); i++) { + Layer* maskLayer = aLayer->GetAncestorMaskLayerAt(i); + mAncestorMaskLayers.AppendElement( + CloneLayerTreePropertiesInternal(maskLayer, true)); + } + if (mUseClipRect) { + mClipRect = *aLayer->GetLocalClipRect(); + } + mTransform = GetTransformForInvalidation(aLayer); + } + LayerPropertiesBase() + : mLayer(nullptr), + mMaskLayer(nullptr), + mPostXScale(0.0), + mPostYScale(0.0), + mOpacity(0.0), + mUseClipRect(false) { + MOZ_COUNT_CTOR(LayerPropertiesBase); + } + MOZ_COUNTED_DTOR_OVERRIDE(LayerPropertiesBase) + + protected: + LayerPropertiesBase(const LayerPropertiesBase& a) = delete; + LayerPropertiesBase& operator=(const LayerPropertiesBase& a) = delete; + + public: + bool ComputeDifferences(Layer* aRoot, nsIntRegion& aOutRegion, + NotifySubDocInvalidationFunc aCallback) override; + + void MoveBy(const IntPoint& aOffset) override; + + bool ComputeChange(const char* aPrefix, nsIntRegion& aOutRegion, + NotifySubDocInvalidationFunc aCallback) { + // Bug 1251615: This canary is sometimes hit. We're still not sure why. + mCanary.Check(); + bool transformChanged = + !mTransform.FuzzyEqual(GetTransformForInvalidation(mLayer)) || + mLayer->GetPostXScale() != mPostXScale || + mLayer->GetPostYScale() != mPostYScale; + const Maybe& otherClip = mLayer->GetLocalClipRect(); + nsIntRegion result; + + bool ancestorMaskChanged = + mAncestorMaskLayers.Length() != mLayer->GetAncestorMaskLayerCount(); + if (!ancestorMaskChanged) { + for (size_t i = 0; i < mAncestorMaskLayers.Length(); i++) { + if (mLayer->GetAncestorMaskLayerAt(i) != + mAncestorMaskLayers[i]->mLayer) { + ancestorMaskChanged = true; + break; + } + } + } + + // Note that we don't bailout early in general since this function + // clears some persistent state at the end. Instead we set an overflow + // flag and check it right before returning. + bool areaOverflowed = false; + + Layer* otherMask = mLayer->GetMaskLayer(); + if ((mMaskLayer ? mMaskLayer->mLayer : nullptr) != otherMask || + ancestorMaskChanged || (mUseClipRect != !!otherClip) || + mLayer->GetLocalOpacity() != mOpacity || transformChanged) { + Maybe oldBounds = OldTransformedBounds(); + Maybe newBounds = NewTransformedBounds(); + if (oldBounds && newBounds) { + LTI_DUMP(oldBounds.value(), "oldtransform"); + LTI_DUMP(newBounds.value(), "newtransform"); + result = oldBounds.value(); + AddRegion(result, newBounds.value()); + } else { + areaOverflowed = true; + } + + // We can't bail out early because we might need to update some internal + // layer state. + } + + nsIntRegion internal; + if (!ComputeChangeInternal(aPrefix, internal, aCallback)) { + areaOverflowed = true; + } + + LTI_DUMP(internal, "internal"); + AddRegion(result, internal); + LTI_DUMP(mLayer->GetInvalidRegion().GetRegion(), "invalid"); + AddTransformedRegion(result, mLayer->GetInvalidRegion().GetRegion(), + mTransform); + + if (mMaskLayer && otherMask) { + nsIntRegion mask; + if (!mMaskLayer->ComputeChange(aPrefix, mask, aCallback)) { + areaOverflowed = true; + } + LTI_DUMP(mask, "mask"); + AddRegion(result, mask); + } + + for (size_t i = 0; i < std::min(mAncestorMaskLayers.Length(), + mLayer->GetAncestorMaskLayerCount()); + i++) { + nsIntRegion mask; + if (!mAncestorMaskLayers[i]->ComputeChange(aPrefix, mask, aCallback)) { + areaOverflowed = true; + } + LTI_DUMP(mask, "ancestormask"); + AddRegion(result, mask); + } + + if (mUseClipRect && otherClip) { + if (!mClipRect.IsEqualInterior(*otherClip)) { + nsIntRegion tmp; + tmp.Xor(mClipRect.ToUnknownRect(), otherClip->ToUnknownRect()); + LTI_DUMP(tmp, "clip"); + AddRegion(result, tmp); + } + } + + mLayer->ClearInvalidRegion(); + + if (areaOverflowed) { + return false; + } + + aOutRegion = std::move(result); + return true; + } + + void CheckCanary() { + mCanary.Check(); + mLayer->CheckCanary(); + } + + IntRect NewTransformedBoundsForLeaf() { + return TransformRect( + mLayer->GetLocalVisibleRegion().GetBounds().ToUnknownRect(), + GetTransformForInvalidation(mLayer)); + } + + IntRect OldTransformedBoundsForLeaf() { + return TransformRect(mVisibleRegion.GetBounds().ToUnknownRect(), + mTransform); + } + + Maybe NewTransformedBounds() { return TransformedBounds(mLayer); } + + virtual Maybe OldTransformedBounds() { + return Some( + TransformRect(mVisibleRegion.GetBounds().ToUnknownRect(), mTransform)); + } + + virtual bool ComputeChangeInternal(const char* aPrefix, + nsIntRegion& aOutRegion, + NotifySubDocInvalidationFunc aCallback) { + if (mLayer->AsHostLayer() && + !mLayer->GetLocalVisibleRegion().ToUnknownRegion().IsEqual( + mVisibleRegion)) { + IntRect result = NewTransformedBoundsForLeaf(); + result = result.Union(OldTransformedBoundsForLeaf()); + aOutRegion = result; + } + return true; + } + + RefPtr mLayer; + UniquePtr mMaskLayer; + nsTArray> mAncestorMaskLayers; + nsIntRegion mVisibleRegion; + Matrix4x4Flagged mTransform; + float mPostXScale; + float mPostYScale; + float mOpacity; + ParentLayerIntRect mClipRect; + bool mUseClipRect; + mozilla::CorruptionCanary mCanary; +}; + +struct ContainerLayerProperties : public LayerPropertiesBase { + explicit ContainerLayerProperties(ContainerLayer* aLayer) + : LayerPropertiesBase(aLayer), + mPreXScale(aLayer->GetPreXScale()), + mPreYScale(aLayer->GetPreYScale()) { + for (Layer* child = aLayer->GetFirstChild(); child; + child = child->GetNextSibling()) { + child->CheckCanary(); + mChildren.AppendElement(CloneLayerTreePropertiesInternal(child)); + } + } + + protected: + ContainerLayerProperties(const ContainerLayerProperties& a) = delete; + ContainerLayerProperties& operator=(const ContainerLayerProperties& a) = + delete; + + public: + bool ComputeChangeInternal(const char* aPrefix, nsIntRegion& aOutRegion, + NotifySubDocInvalidationFunc aCallback) override { + // Make sure we got our virtual call right + mSubtypeCanary.Check(); + ContainerLayer* container = mLayer->AsContainerLayer(); + nsIntRegion invalidOfLayer; // Invalid regions of this layer. + nsIntRegion result; // Invliad regions for children only. + + container->CheckCanary(); + + bool childrenChanged = false; + bool invalidateWholeLayer = false; + bool areaOverflowed = false; + if (mPreXScale != container->GetPreXScale() || + mPreYScale != container->GetPreYScale()) { + Maybe oldBounds = OldTransformedBounds(); + Maybe newBounds = NewTransformedBounds(); + if (oldBounds && newBounds) { + invalidOfLayer = oldBounds.value(); + AddRegion(invalidOfLayer, newBounds.value()); + } else { + areaOverflowed = true; + } + childrenChanged = true; + invalidateWholeLayer = true; + + // Can't bail out early, we need to update the child container layers + } + + // A low frame rate is especially visible to users when scrolling, so we + // particularly want to avoid unnecessary invalidation at that time. For us + // here, that means avoiding unnecessary invalidation of child items when + // other children are added to or removed from our container layer, since + // that may be caused by children being scrolled in or out of view. We are + // less concerned with children changing order. + // TODO: Consider how we could avoid unnecessary invalidation when children + // change order, and whether the overhead would be worth it. + + nsDataHashtable, uint32_t> oldIndexMap( + mChildren.Length()); + for (uint32_t i = 0; i < mChildren.Length(); ++i) { + mChildren[i]->CheckCanary(); + oldIndexMap.Put(mChildren[i]->mLayer, i); + } + + uint32_t i = 0; // cursor into the old child list mChildren + for (Layer* child = container->GetFirstChild(); child; + child = child->GetNextSibling()) { + bool invalidateChildsCurrentArea = false; + if (i < mChildren.Length()) { + uint32_t childsOldIndex; + if (oldIndexMap.Get(child, &childsOldIndex)) { + if (childsOldIndex >= i) { + // Invalidate the old areas of layers that used to be between the + // current |child| and the previous |child| that was also in the + // old list mChildren (if any of those children have been reordered + // rather than removed, we will invalidate their new area when we + // encounter them in the new list): + for (uint32_t j = i; j < childsOldIndex; ++j) { + if (Maybe bounds = + mChildren[j]->OldTransformedBounds()) { + LTI_DUMP(bounds.value(), "reordered child"); + AddRegion(result, bounds.value()); + } else { + areaOverflowed = true; + } + childrenChanged |= true; + } + if (childsOldIndex >= mChildren.Length()) { + MOZ_CRASH("Out of bounds"); + } + // Invalidate any regions of the child that have changed: + nsIntRegion region; + if (!mChildren[childsOldIndex]->ComputeChange(LTI_DEEPER(aPrefix), + region, aCallback)) { + areaOverflowed = true; + } + i = childsOldIndex + 1; + if (!region.IsEmpty()) { + LTI_LOG("%s%p: child %p produced %s\n", aPrefix, mLayer.get(), + mChildren[childsOldIndex]->mLayer.get(), + Stringify(region).c_str()); + AddRegion(result, region); + childrenChanged |= true; + } + } else { + // We've already seen this child in mChildren (which means it must + // have been reordered) and invalidated its old area. We need to + // invalidate its new area too: + invalidateChildsCurrentArea = true; + } + } else { + // |child| is new + invalidateChildsCurrentArea = true; + SetChildrenChangedRecursive(child); + } + } else { + // |child| is new, or was reordered to a higher index + invalidateChildsCurrentArea = true; + if (!oldIndexMap.Contains(child)) { + SetChildrenChangedRecursive(child); + } + } + if (invalidateChildsCurrentArea) { + LTI_DUMP(child->GetLocalVisibleRegion().ToUnknownRegion(), + "invalidateChildsCurrentArea"); + if (Maybe bounds = TransformedBounds(child)) { + AddRegion(result, bounds.value()); + } else { + areaOverflowed = true; + } + if (aCallback) { + NotifySubdocumentInvalidation(child, aCallback); + } else { + ClearInvalidations(child); + } + } + childrenChanged |= invalidateChildsCurrentArea; + } + + // Process remaining removed children. + while (i < mChildren.Length()) { + childrenChanged |= true; + if (Maybe bounds = mChildren[i]->OldTransformedBounds()) { + LTI_DUMP(bounds.value(), "removed child"); + AddRegion(result, bounds.value()); + } else { + areaOverflowed = true; + } + i++; + } + + if (aCallback) { + aCallback(container, areaOverflowed ? nullptr : &result); + } + + if (childrenChanged || areaOverflowed) { + container->SetChildrenChanged(true); + } + + if (container->UseIntermediateSurface()) { + Maybe bounds; + if (!invalidateWholeLayer && !areaOverflowed) { + bounds = Some(result.GetBounds()); + + // Process changes in the visible region. + IntRegion newVisible = + mLayer->GetLocalVisibleRegion().ToUnknownRegion(); + if (!newVisible.IsEqual(mVisibleRegion)) { + newVisible.XorWith(mVisibleRegion); + bounds = bounds->SafeUnion(newVisible.GetBounds()); + } + } + container->SetInvalidCompositeRect(bounds ? bounds.ptr() : nullptr); + } + + // Safe to bail out early now, persistent state has been set. + if (areaOverflowed) { + return false; + } + + if (!mLayer->Extend3DContext()) { + // |result| contains invalid regions only of children. + result.Transform(GetTransformForInvalidation(mLayer).GetMatrix()); + } + // else, effective transforms have applied on children. + + LTI_DUMP(invalidOfLayer, "invalidOfLayer"); + result.OrWith(invalidOfLayer); + + aOutRegion = std::move(result); + return true; + } + + Maybe OldTransformedBounds() override { + if (mLayer->Extend3DContext()) { + IntRect result; + for (UniquePtr& child : mChildren) { + Maybe childBounds = child->OldTransformedBounds(); + if (!childBounds) { + return Nothing(); + } + Maybe combined = result.SafeUnion(childBounds.value()); + if (!combined) { + LTI_LOG("overflowed bounds of container %p accumulating child %p\n", + this, child->mLayer.get()); + return Nothing(); + } + result = combined.value(); + } + return Some(result); + } + return LayerPropertiesBase::OldTransformedBounds(); + } + + // The old list of children: + mozilla::CorruptionCanary mSubtypeCanary; + nsTArray> mChildren; + float mPreXScale; + float mPreYScale; +}; + +struct ColorLayerProperties : public LayerPropertiesBase { + explicit ColorLayerProperties(ColorLayer* aLayer) + : LayerPropertiesBase(aLayer), + mColor(aLayer->GetColor()), + mBounds(aLayer->GetBounds()) {} + + protected: + ColorLayerProperties(const ColorLayerProperties& a) = delete; + ColorLayerProperties& operator=(const ColorLayerProperties& a) = delete; + + public: + bool ComputeChangeInternal(const char* aPrefix, nsIntRegion& aOutRegion, + NotifySubDocInvalidationFunc aCallback) override { + ColorLayer* color = static_cast(mLayer.get()); + + if (mColor != color->GetColor()) { + LTI_DUMP(NewTransformedBoundsForLeaf(), "color"); + aOutRegion = NewTransformedBoundsForLeaf(); + return true; + } + + nsIntRegion boundsDiff; + boundsDiff.Xor(mBounds, color->GetBounds()); + LTI_DUMP(boundsDiff, "colorbounds"); + + AddTransformedRegion(aOutRegion, boundsDiff, mTransform); + return true; + } + + DeviceColor mColor; + IntRect mBounds; +}; + +static ImageHost* GetImageHost(Layer* aLayer) { + HostLayer* compositor = aLayer->AsHostLayer(); + if (compositor) { + return static_cast(compositor->GetCompositableHost()); + } + return nullptr; +} + +struct ImageLayerProperties : public LayerPropertiesBase { + explicit ImageLayerProperties(ImageLayer* aImage, bool aIsMask) + : LayerPropertiesBase(aImage), + mContainer(aImage->GetContainer()), + mImageHost(GetImageHost(aImage)), + mSamplingFilter(aImage->GetSamplingFilter()), + mScaleToSize(aImage->GetScaleToSize()), + mScaleMode(aImage->GetScaleMode()), + mLastProducerID(-1), + mLastFrameID(-1), + mIsMask(aIsMask) { + if (mImageHost) { + if (aIsMask) { + // Mask layers never set the 'last' producer/frame + // id, since they never get composited as their own + // layer. + mLastProducerID = mImageHost->GetProducerID(); + mLastFrameID = mImageHost->GetFrameID(); + } else { + mLastProducerID = mImageHost->GetLastProducerID(); + mLastFrameID = mImageHost->GetLastFrameID(); + } + } + } + + bool ComputeChangeInternal(const char* aPrefix, nsIntRegion& aOutRegion, + NotifySubDocInvalidationFunc aCallback) override { + ImageLayer* imageLayer = static_cast(mLayer.get()); + + if (!imageLayer->GetLocalVisibleRegion().ToUnknownRegion().IsEqual( + mVisibleRegion)) { + IntRect result = NewTransformedBoundsForLeaf(); + result = result.Union(OldTransformedBoundsForLeaf()); + aOutRegion = result; + return true; + } + + ImageContainer* container = imageLayer->GetContainer(); + ImageHost* host = GetImageHost(imageLayer); + if (mContainer != container || + mSamplingFilter != imageLayer->GetSamplingFilter() || + mScaleToSize != imageLayer->GetScaleToSize() || + mScaleMode != imageLayer->GetScaleMode() || host != mImageHost || + (host && host->GetProducerID() != mLastProducerID) || + (host && host->GetFrameID() != mLastFrameID)) { + if (mIsMask) { + // Mask layers have an empty visible region, so we have to + // use the image size instead. + IntSize size; + if (container) { + size = container->GetCurrentSize(); + } + if (host) { + size = host->GetImageSize(); + } + IntRect rect(0, 0, size.width, size.height); + LTI_DUMP(rect, "mask"); + aOutRegion = TransformRect(rect, GetTransformForInvalidation(mLayer)); + return true; + } + LTI_DUMP(NewTransformedBoundsForLeaf(), "bounds"); + aOutRegion = NewTransformedBoundsForLeaf(); + return true; + } + + return true; + } + + RefPtr mContainer; + RefPtr mImageHost; + SamplingFilter mSamplingFilter; + gfx::IntSize mScaleToSize; + ScaleMode mScaleMode; + int32_t mLastProducerID; + int32_t mLastFrameID; + bool mIsMask; +}; + +struct CanvasLayerProperties : public LayerPropertiesBase { + explicit CanvasLayerProperties(CanvasLayer* aCanvas) + : LayerPropertiesBase(aCanvas), mImageHost(GetImageHost(aCanvas)) { + mFrameID = mImageHost ? mImageHost->GetFrameID() : -1; + } + + bool ComputeChangeInternal(const char* aPrefix, nsIntRegion& aOutRegion, + NotifySubDocInvalidationFunc aCallback) override { + CanvasLayer* canvasLayer = static_cast(mLayer.get()); + + ImageHost* host = GetImageHost(canvasLayer); + if (host && host->GetFrameID() != mFrameID) { + LTI_DUMP(NewTransformedBoundsForLeaf(), "frameId"); + aOutRegion = NewTransformedBoundsForLeaf(); + return true; + } + + return true; + } + + RefPtr mImageHost; + int32_t mFrameID; +}; + +UniquePtr CloneLayerTreePropertiesInternal( + Layer* aRoot, bool aIsMask /* = false */) { + if (!aRoot) { + return MakeUnique(); + } + + MOZ_ASSERT(!aIsMask || aRoot->GetType() == Layer::TYPE_IMAGE); + + aRoot->CheckCanary(); + + switch (aRoot->GetType()) { + case Layer::TYPE_CONTAINER: + case Layer::TYPE_REF: + return MakeUnique(aRoot->AsContainerLayer()); + case Layer::TYPE_COLOR: + return MakeUnique(static_cast(aRoot)); + case Layer::TYPE_IMAGE: + return MakeUnique(static_cast(aRoot), + aIsMask); + case Layer::TYPE_CANVAS: + return MakeUnique( + static_cast(aRoot)); + case Layer::TYPE_DISPLAYITEM: + case Layer::TYPE_READBACK: + case Layer::TYPE_SHADOW: + case Layer::TYPE_PAINTED: + return MakeUnique(aRoot); + } + + MOZ_ASSERT_UNREACHABLE("Unexpected root layer type"); + return MakeUnique(aRoot); +} + +/* static */ +UniquePtr LayerProperties::CloneFrom(Layer* aRoot) { + return CloneLayerTreePropertiesInternal(aRoot); +} + +/* static */ +void LayerProperties::ClearInvalidations(Layer* aLayer) { + ForEachNode(aLayer, [](Layer* layer) { + layer->ClearInvalidRegion(); + if (layer->GetMaskLayer()) { + ClearInvalidations(layer->GetMaskLayer()); + } + for (size_t i = 0; i < layer->GetAncestorMaskLayerCount(); i++) { + ClearInvalidations(layer->GetAncestorMaskLayerAt(i)); + } + }); +} + +bool LayerPropertiesBase::ComputeDifferences( + Layer* aRoot, nsIntRegion& aOutRegion, + NotifySubDocInvalidationFunc aCallback) { + NS_ASSERTION(aRoot, "Must have a layer tree to compare against!"); + if (mLayer != aRoot) { + if (aCallback) { + NotifySubdocumentInvalidation(aRoot, aCallback); + } else { + ClearInvalidations(aRoot); + } + IntRect bounds = TransformRect( + aRoot->GetLocalVisibleRegion().GetBounds().ToUnknownRect(), + aRoot->GetLocalTransform()); + Maybe oldBounds = OldTransformedBounds(); + if (!oldBounds) { + return false; + } + Maybe result = bounds.SafeUnion(oldBounds.value()); + if (!result) { + LTI_LOG("overflowed bounds computing the union of layers %p and %p\n", + mLayer.get(), aRoot); + return false; + } + aOutRegion = result.value(); + return true; + } + return ComputeChange(" ", aOutRegion, aCallback); +} + +void LayerPropertiesBase::MoveBy(const IntPoint& aOffset) { + mTransform.PostTranslate(aOffset.x, aOffset.y, 0); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/LayerTreeInvalidation.h b/gfx/layers/LayerTreeInvalidation.h new file mode 100644 index 0000000000..1c26e2ce9c --- /dev/null +++ b/gfx/layers/LayerTreeInvalidation.h @@ -0,0 +1,79 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_LAYER_TREE_INVALIDATION_H +#define GFX_LAYER_TREE_INVALIDATION_H + +#include "nsRegion.h" // for nsIntRegion +#include "mozilla/UniquePtr.h" // for UniquePtr +#include "mozilla/gfx/Point.h" + +namespace mozilla { +namespace layers { + +class Layer; +class ContainerLayer; + +/** + * Callback for ContainerLayer invalidations. + * + * @param aContainer ContainerLayer being invalidated. + * @param aRegion Invalidated region in the ContainerLayer's coordinate + * space. If null, then the entire region must be invalidated. + */ +typedef void (*NotifySubDocInvalidationFunc)(ContainerLayer* aLayer, + const nsIntRegion* aRegion); + +/** + * A set of cached layer properties (including those of child layers), + * used for comparing differences in layer trees. + */ +struct LayerProperties { + protected: + LayerProperties() = default; + + LayerProperties(const LayerProperties& a) = delete; + LayerProperties& operator=(const LayerProperties& a) = delete; + + public: + virtual ~LayerProperties() = default; + + /** + * Copies the current layer tree properties into + * a new LayerProperties object. + * + * @param Layer tree to copy, or nullptr if we have no + * initial layer tree. + */ + static UniquePtr CloneFrom(Layer* aRoot); + + /** + * Clear all invalidation status from this layer tree. + */ + static void ClearInvalidations(Layer* aRoot); + + /** + * Compares a set of existing layer tree properties to the current layer + * tree and generates the changed rectangle. + * + * @param aRoot Root layer of the layer tree to compare against. + * @param aOutRegion Outparam that will contain the painted area changed by + * the layer tree changes. + * @param aCallback If specified, callback to call when ContainerLayers + * are invalidated. + * @return True on success, false if a calculation overflowed and the entire + * layer tree area should be considered invalidated. + */ + virtual bool ComputeDifferences(Layer* aRoot, nsIntRegion& aOutRegion, + NotifySubDocInvalidationFunc aCallback) = 0; + + virtual void MoveBy(const gfx::IntPoint& aOffset) = 0; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_LAYER_TREE_INVALIDATON_H */ diff --git a/gfx/layers/LayerUserData.h b/gfx/layers/LayerUserData.h new file mode 100644 index 0000000000..206fefc998 --- /dev/null +++ b/gfx/layers/LayerUserData.h @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_LAYERUSERDATA_H +#define GFX_LAYERUSERDATA_H + +namespace mozilla { +namespace layers { + +/** + * Base class for userdata objects attached to layers and layer managers. + * + * We define it here in a separate header so clients only need to include + * this header for their class definitions, rather than pulling in Layers.h. + * Everything else in Layers.h should be forward-declarable. + */ +class LayerUserData { + public: + virtual ~LayerUserData() = default; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_LAYERUSERDATA_H */ diff --git a/gfx/layers/Layers.cpp b/gfx/layers/Layers.cpp new file mode 100644 index 0000000000..64b3260766 --- /dev/null +++ b/gfx/layers/Layers.cpp @@ -0,0 +1,2250 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Layers.h" + +#include // for PRIu64 +#include // for stderr +#include // for max, min +#include // for list +#include // for set +#include // for char_traits, string, basic_string +#include // for remove_reference<>::type +#include "CompositableHost.h" // for CompositableHost +#include "GeckoProfiler.h" // for profiler_can_accept_markers, PROFILER_MARKER_TEXT +#include "ImageLayers.h" // for ImageLayer +#include "LayerUserData.h" // for LayerUserData +#include "ReadbackLayer.h" // for ReadbackLayer +#include "TreeTraversal.h" // for ForwardIterator, ForEachNode, DepthFirstSearch, TraversalFlag, TraversalFl... +#include "UnitTransforms.h" // for ViewAs, PixelCastJustification, PixelCastJustification::RenderTargetIsPare... +#include "apz/src/AsyncPanZoomController.h" // for AsyncPanZoomController +#include "gfx2DGlue.h" // for ThebesMatrix, ToPoint, ThebesRect +#include "gfxEnv.h" // for gfxEnv +#include "gfxMatrix.h" // for gfxMatrix +#include "gfxUtils.h" // for gfxUtils, gfxUtils::sDumpPaintFile +#include "mozilla/ArrayIterator.h" // for ArrayIterator +#include "mozilla/BaseProfilerMarkersPrerequisites.h" // for MarkerTiming +#include "mozilla/DebugOnly.h" // for DebugOnly +#include "mozilla/Logging.h" // for LogLevel, LogLevel::Debug, MOZ_LOG_TEST +#include "mozilla/ScrollPositionUpdate.h" // for ScrollPositionUpdate +#include "mozilla/Telemetry.h" // for AccumulateTimeDelta +#include "mozilla/TelemetryHistogramEnums.h" // for KEYPRESS_PRESENT_LATENCY, SCROLL_PRESENT_LATENCY +#include "mozilla/ToString.h" // for ToString +#include "mozilla/gfx/2D.h" // for SourceSurface, DrawTarget, DataSourceSurface +#include "mozilla/gfx/BasePoint3D.h" // for BasePoint3D<>::(anonymous union)::(anonymous), BasePoint3D<>::(anonymous) +#include "mozilla/gfx/BaseRect.h" // for operator<<, BaseRect (ptr only) +#include "mozilla/gfx/BaseSize.h" // for operator<<, BaseSize<>::(anonymous union)::(anonymous), BaseSize<>::(anony... +#include "mozilla/gfx/Matrix.h" // for Matrix4x4, Matrix, Matrix4x4Typed<>::(anonymous union)::(anonymous), Matri... +#include "mozilla/gfx/MatrixFwd.h" // for Float +#include "mozilla/gfx/Polygon.h" // for Polygon, PolygonTyped +#include "mozilla/layers/BSPTree.h" // for LayerPolygon, BSPTree +#include "mozilla/layers/CompositableClient.h" // for CompositableClient +#include "mozilla/layers/Compositor.h" // for Compositor +#include "mozilla/layers/LayerManagerComposite.h" // for HostLayer +#include "mozilla/layers/LayersMessages.h" // for SpecificLayerAttributes, CompositorAnimations (ptr only), ContainerLayerAt... +#include "mozilla/layers/LayersTypes.h" // for EventRegions, operator<<, CompositionPayload, CSSTransformMatrix, MOZ_LAYE... +#include "mozilla/layers/ShadowLayers.h" // for ShadowableLayer +#include "nsBaseHashtable.h" // for nsBaseHashtable<>::Iterator, nsBaseHashtable<>::LookupResult +#include "nsISupportsUtils.h" // for NS_ADDREF, NS_RELEASE +#include "nsPrintfCString.h" // for nsPrintfCString +#include "nsRegionFwd.h" // for IntRegion +#include "nsString.h" // for nsTSubstring +#include "protobuf/LayerScopePacket.pb.h" // for LayersPacket::Layer, LayersPacket, LayersPacket_Layer::Matrix, LayersPacke... + +// Undo the damage done by mozzconf.h +#undef compress +#include "mozilla/Compression.h" + +namespace mozilla { +namespace layers { + +typedef ScrollableLayerGuid::ViewID ViewID; + +using namespace mozilla::gfx; +using namespace mozilla::Compression; + +//-------------------------------------------------- +// Layer + +Layer::Layer(LayerManager* aManager, void* aImplData) + : mManager(aManager), + mParent(nullptr), + mNextSibling(nullptr), + mPrevSibling(nullptr), + mImplData(aImplData), + mUseTileSourceRect(false) +#ifdef DEBUG + , + mDebugColorIndex(0) +#endif +{ +} + +Layer::~Layer() = default; + +void Layer::SetEventRegions(const EventRegions& aRegions) { + if (mEventRegions != aRegions) { + MOZ_LAYERS_LOG_IF_SHADOWABLE( + this, ("Layer::Mutated(%p) eventregions were %s, now %s", this, + ToString(mEventRegions).c_str(), ToString(aRegions).c_str())); + mEventRegions = aRegions; + Mutated(); + } +} + +void Layer::SetCompositorAnimations( + const LayersId& aLayersId, + const CompositorAnimations& aCompositorAnimations) { + MOZ_LAYERS_LOG_IF_SHADOWABLE( + this, ("Layer::Mutated(%p) SetCompositorAnimations with id=%" PRIu64, + this, mAnimationInfo.GetCompositorAnimationsId())); + + mAnimationInfo.SetCompositorAnimations(aLayersId, aCompositorAnimations); + + Mutated(); +} + +void Layer::ClearCompositorAnimations() { + MOZ_LAYERS_LOG_IF_SHADOWABLE( + this, ("Layer::Mutated(%p) ClearCompositorAnimations with id=%" PRIu64, + this, mAnimationInfo.GetCompositorAnimationsId())); + + mAnimationInfo.ClearAnimations(); + + Mutated(); +} + +void Layer::StartPendingAnimations(const TimeStamp& aReadyTime) { + ForEachNode(this, [&aReadyTime](Layer* layer) { + if (layer->mAnimationInfo.StartPendingAnimations(aReadyTime)) { + layer->Mutated(); + } + }); +} + +void Layer::SetAsyncPanZoomController(uint32_t aIndex, + AsyncPanZoomController* controller) { + MOZ_ASSERT(aIndex < GetScrollMetadataCount()); + // We should never be setting an APZC on a non-scrollable layer + MOZ_ASSERT(!controller || GetFrameMetrics(aIndex).IsScrollable()); + mApzcs[aIndex] = controller; +} + +AsyncPanZoomController* Layer::GetAsyncPanZoomController( + uint32_t aIndex) const { + MOZ_ASSERT(aIndex < GetScrollMetadataCount()); +#ifdef DEBUG + if (mApzcs[aIndex]) { + MOZ_ASSERT(GetFrameMetrics(aIndex).IsScrollable()); + } +#endif + return mApzcs[aIndex]; +} + +void Layer::ScrollMetadataChanged() { + mApzcs.SetLength(GetScrollMetadataCount()); +} + +std::unordered_set +Layer::ApplyPendingUpdatesToSubtree() { + ForEachNode(this, [](Layer* layer) { + layer->ApplyPendingUpdatesForThisTransaction(); + }); + // Once we're done recursing through the whole tree, clear the pending + // updates from the manager. + return Manager()->ClearPendingScrollInfoUpdate(); +} + +bool Layer::IsOpaqueForVisibility() { + return GetEffectiveOpacity() == 1.0f && + GetEffectiveMixBlendMode() == CompositionOp::OP_OVER; +} + +bool Layer::CanUseOpaqueSurface() { + // If the visible content in the layer is opaque, there is no need + // for an alpha channel. + if (GetContentFlags() & CONTENT_OPAQUE) return true; + // Also, if this layer is the bottommost layer in a container which + // doesn't need an alpha channel, we can use an opaque surface for this + // layer too. Any transparent areas must be covered by something else + // in the container. + ContainerLayer* parent = GetParent(); + return parent && parent->GetFirstChild() == this && + parent->CanUseOpaqueSurface(); +} + +// NB: eventually these methods will be defined unconditionally, and +// can be moved into Layers.h +const Maybe& Layer::GetLocalClipRect() { + if (HostLayer* shadow = AsHostLayer()) { + return shadow->GetShadowClipRect(); + } + return GetClipRect(); +} + +const LayerIntRegion& Layer::GetLocalVisibleRegion() { + if (HostLayer* shadow = AsHostLayer()) { + return shadow->GetShadowVisibleRegion(); + } + return GetVisibleRegion(); +} + +Matrix4x4 Layer::SnapTransformTranslation(const Matrix4x4& aTransform, + Matrix* aResidualTransform) { + if (aResidualTransform) { + *aResidualTransform = Matrix(); + } + + if (!mManager->IsSnappingEffectiveTransforms()) { + return aTransform; + } + + Matrix matrix2D; + if (aTransform.CanDraw2D(&matrix2D) && !matrix2D.HasNonTranslation() && + matrix2D.HasNonIntegerTranslation()) { + auto snappedTranslation = IntPoint::Round(matrix2D.GetTranslation()); + Matrix snappedMatrix = + Matrix::Translation(snappedTranslation.x, snappedTranslation.y); + Matrix4x4 result = Matrix4x4::From2D(snappedMatrix); + if (aResidualTransform) { + // set aResidualTransform so that aResidual * snappedMatrix == matrix2D. + // (I.e., appying snappedMatrix after aResidualTransform gives the + // ideal transform.) + *aResidualTransform = + Matrix::Translation(matrix2D._31 - snappedTranslation.x, + matrix2D._32 - snappedTranslation.y); + } + return result; + } + + return SnapTransformTranslation3D(aTransform, aResidualTransform); +} + +Matrix4x4 Layer::SnapTransformTranslation3D(const Matrix4x4& aTransform, + Matrix* aResidualTransform) { + if (aTransform.IsSingular() || aTransform.HasPerspectiveComponent() || + aTransform.HasNonTranslation() || + !aTransform.HasNonIntegerTranslation()) { + // For a singular transform, there is no reversed matrix, so we + // don't snap it. + // For a perspective transform, the content is transformed in + // non-linear, so we don't snap it too. + return aTransform; + } + + // Snap for 3D Transforms + + Point3D transformedOrigin = aTransform.TransformPoint(Point3D()); + + // Compute the transformed snap by rounding the values of + // transformed origin. + auto transformedSnapXY = + IntPoint::Round(transformedOrigin.x, transformedOrigin.y); + Matrix4x4 inverse = aTransform; + inverse.Invert(); + // see Matrix4x4::ProjectPoint() + Float transformedSnapZ = + inverse._33 == 0 ? 0 + : (-(transformedSnapXY.x * inverse._13 + + transformedSnapXY.y * inverse._23 + inverse._43) / + inverse._33); + Point3D transformedSnap = + Point3D(transformedSnapXY.x, transformedSnapXY.y, transformedSnapZ); + if (transformedOrigin == transformedSnap) { + return aTransform; + } + + // Compute the snap from the transformed snap. + Point3D snap = inverse.TransformPoint(transformedSnap); + if (snap.z > 0.001 || snap.z < -0.001) { + // Allow some level of accumulated computation error. + MOZ_ASSERT(inverse._33 == 0.0); + return aTransform; + } + + // The difference between the origin and snap is the residual transform. + if (aResidualTransform) { + // The residual transform is to translate the snap to the origin + // of the content buffer. + *aResidualTransform = Matrix::Translation(-snap.x, -snap.y); + } + + // Translate transformed origin to transformed snap since the + // residual transform would trnslate the snap to the origin. + Point3D transformedShift = transformedSnap - transformedOrigin; + Matrix4x4 result = aTransform; + result.PostTranslate(transformedShift.x, transformedShift.y, + transformedShift.z); + + // For non-2d transform, residual translation could be more than + // 0.5 pixels for every axis. + + return result; +} + +Matrix4x4 Layer::SnapTransform(const Matrix4x4& aTransform, + const gfxRect& aSnapRect, + Matrix* aResidualTransform) { + if (aResidualTransform) { + *aResidualTransform = Matrix(); + } + + Matrix matrix2D; + Matrix4x4 result; + if (mManager->IsSnappingEffectiveTransforms() && aTransform.Is2D(&matrix2D) && + gfxSize(1.0, 1.0) <= aSnapRect.Size() && + matrix2D.PreservesAxisAlignedRectangles()) { + auto transformedTopLeft = + IntPoint::Round(matrix2D.TransformPoint(ToPoint(aSnapRect.TopLeft()))); + auto transformedTopRight = + IntPoint::Round(matrix2D.TransformPoint(ToPoint(aSnapRect.TopRight()))); + auto transformedBottomRight = IntPoint::Round( + matrix2D.TransformPoint(ToPoint(aSnapRect.BottomRight()))); + + Matrix snappedMatrix = gfxUtils::TransformRectToRect( + aSnapRect, transformedTopLeft, transformedTopRight, + transformedBottomRight); + + result = Matrix4x4::From2D(snappedMatrix); + if (aResidualTransform && !snappedMatrix.IsSingular()) { + // set aResidualTransform so that aResidual * snappedMatrix == matrix2D. + // (i.e., appying snappedMatrix after aResidualTransform gives the + // ideal transform. + Matrix snappedMatrixInverse = snappedMatrix; + snappedMatrixInverse.Invert(); + *aResidualTransform = matrix2D * snappedMatrixInverse; + } + } else { + result = aTransform; + } + return result; +} + +static bool AncestorLayerMayChangeTransform(Layer* aLayer) { + for (Layer* l = aLayer; l; l = l->GetParent()) { + if (l->GetContentFlags() & Layer::CONTENT_MAY_CHANGE_TRANSFORM) { + return true; + } + + if (l->GetParent() && l->GetParent()->AsRefLayer()) { + return false; + } + } + return false; +} + +bool Layer::MayResample() { + Matrix transform2d; + return !GetEffectiveTransform().Is2D(&transform2d) || + ThebesMatrix(transform2d).HasNonIntegerTranslation() || + AncestorLayerMayChangeTransform(this); +} + +RenderTargetIntRect Layer::CalculateScissorRect( + const RenderTargetIntRect& aCurrentScissorRect) { + ContainerLayer* container = GetParent(); + ContainerLayer* containerChild = nullptr; + NS_ASSERTION(GetParent(), "This can't be called on the root!"); + + // Find the layer creating the 3D context. + while (container->Extend3DContext() && !container->UseIntermediateSurface()) { + containerChild = container; + container = container->GetParent(); + MOZ_ASSERT(container); + } + + // Find the nearest layer with a clip, or this layer. + // ContainerState::SetupScrollingMetadata() may install a clip on + // the layer. + Layer* clipLayer = containerChild && containerChild->GetLocalClipRect() + ? containerChild + : this; + + // Establish initial clip rect: it's either the one passed in, or + // if the parent has an intermediate surface, it's the extents of that + // surface. + RenderTargetIntRect currentClip; + if (container->UseIntermediateSurface()) { + currentClip.SizeTo(container->GetIntermediateSurfaceRect().Size()); + } else { + currentClip = aCurrentScissorRect; + } + + if (!clipLayer->GetLocalClipRect()) { + return currentClip; + } + + if (GetLocalVisibleRegion().IsEmpty()) { + // When our visible region is empty, our parent may not have created the + // intermediate surface that we would require for correct clipping; however, + // this does not matter since we are invisible. + // Make sure we still compute a clip rect if we want to draw checkboarding + // for this layer, since we want to do this even if the layer is invisible. + return RenderTargetIntRect(currentClip.TopLeft(), + RenderTargetIntSize(0, 0)); + } + + const RenderTargetIntRect clipRect = ViewAs( + *clipLayer->GetLocalClipRect(), + PixelCastJustification::RenderTargetIsParentLayerForRoot); + if (clipRect.IsEmpty()) { + // We might have a non-translation transform in the container so we can't + // use the code path below. + return RenderTargetIntRect(currentClip.TopLeft(), + RenderTargetIntSize(0, 0)); + } + + RenderTargetIntRect scissor = clipRect; + if (!container->UseIntermediateSurface()) { + gfx::Matrix matrix; + DebugOnly is2D = container->GetEffectiveTransform().Is2D(&matrix); + // See DefaultComputeEffectiveTransforms below + NS_ASSERTION(is2D && matrix.PreservesAxisAlignedRectangles(), + "Non preserves axis aligned transform with clipped child " + "should have forced intermediate surface"); + gfx::Rect r(scissor.X(), scissor.Y(), scissor.Width(), scissor.Height()); + gfxRect trScissor = gfx::ThebesRect(matrix.TransformBounds(r)); + trScissor.Round(); + IntRect tmp; + if (!gfxUtils::GfxRectToIntRect(trScissor, &tmp)) { + return RenderTargetIntRect(currentClip.TopLeft(), + RenderTargetIntSize(0, 0)); + } + scissor = ViewAs(tmp); + + // Find the nearest ancestor with an intermediate surface + do { + container = container->GetParent(); + } while (container && !container->UseIntermediateSurface()); + } + + if (container) { + scissor.MoveBy(-container->GetIntermediateSurfaceRect().TopLeft()); + } + return currentClip.Intersect(scissor); +} + +Maybe Layer::GetScrolledClipRect() const { + const Maybe clip = mSimpleAttrs.GetScrolledClip(); + return clip ? Some(clip->GetClipRect()) : Nothing(); +} + +const ScrollMetadata& Layer::GetScrollMetadata(uint32_t aIndex) const { + MOZ_ASSERT(aIndex < GetScrollMetadataCount()); + return mScrollMetadata[aIndex]; +} + +const FrameMetrics& Layer::GetFrameMetrics(uint32_t aIndex) const { + return GetScrollMetadata(aIndex).GetMetrics(); +} + +bool Layer::HasScrollableFrameMetrics() const { + for (uint32_t i = 0; i < GetScrollMetadataCount(); i++) { + if (GetFrameMetrics(i).IsScrollable()) { + return true; + } + } + return false; +} + +bool Layer::IsScrollableWithoutContent() const { + // A scrollable container layer with no children + return AsContainerLayer() && HasScrollableFrameMetrics() && !GetFirstChild(); +} + +Matrix4x4 Layer::GetTransform() const { + Matrix4x4 transform = mSimpleAttrs.GetTransform(); + transform.PostScale(GetPostXScale(), GetPostYScale(), 1.0f); + if (const ContainerLayer* c = AsContainerLayer()) { + transform.PreScale(c->GetPreXScale(), c->GetPreYScale(), 1.0f); + } + return transform; +} + +const CSSTransformMatrix Layer::GetTransformTyped() const { + return ViewAs(GetTransform()); +} + +Matrix4x4 Layer::GetLocalTransform() { + if (HostLayer* shadow = AsHostLayer()) { + return shadow->GetShadowTransform(); + } + return GetTransform(); +} + +const LayerToParentLayerMatrix4x4 Layer::GetLocalTransformTyped() { + return ViewAs(GetLocalTransform()); +} + +bool Layer::IsScrollbarContainer() const { + const ScrollbarData& data = GetScrollbarData(); + return (data.mScrollbarLayerType == ScrollbarLayerType::Container) + ? data.mDirection.isSome() + : false; +} + +bool Layer::HasTransformAnimation() const { + return mAnimationInfo.HasTransformAnimation(); +} + +void Layer::ApplyPendingUpdatesForThisTransaction() { + if (mPendingTransform && *mPendingTransform != mSimpleAttrs.GetTransform()) { + MOZ_LAYERS_LOG_IF_SHADOWABLE( + this, ("Layer::Mutated(%p) PendingUpdatesForThisTransaction", this)); + mSimpleAttrs.SetTransform(*mPendingTransform); + MutatedSimple(); + } + mPendingTransform = nullptr; + + if (mAnimationInfo.ApplyPendingUpdatesForThisTransaction()) { + MOZ_LAYERS_LOG_IF_SHADOWABLE( + this, ("Layer::Mutated(%p) PendingUpdatesForThisTransaction", this)); + Mutated(); + } + + for (size_t i = 0; i < mScrollMetadata.Length(); i++) { + FrameMetrics& fm = mScrollMetadata[i].GetMetrics(); + ScrollableLayerGuid::ViewID scrollId = fm.GetScrollId(); + Maybe> update = + Manager()->GetPendingScrollInfoUpdate(scrollId); + if (update) { + nsTArray infos = update.extract(); + mScrollMetadata[i].UpdatePendingScrollInfo(std::move(infos)); + Mutated(); + } + } +} + +float Layer::GetLocalOpacity() { + float opacity = mSimpleAttrs.GetOpacity(); + if (HostLayer* shadow = AsHostLayer()) opacity = shadow->GetShadowOpacity(); + return std::min(std::max(opacity, 0.0f), 1.0f); +} + +float Layer::GetEffectiveOpacity() { + float opacity = GetLocalOpacity(); + for (ContainerLayer* c = GetParent(); c && !c->UseIntermediateSurface(); + c = c->GetParent()) { + opacity *= c->GetLocalOpacity(); + } + return opacity; +} + +CompositionOp Layer::GetEffectiveMixBlendMode() { + if (mSimpleAttrs.GetMixBlendMode() != CompositionOp::OP_OVER) + return mSimpleAttrs.GetMixBlendMode(); + for (ContainerLayer* c = GetParent(); c && !c->UseIntermediateSurface(); + c = c->GetParent()) { + if (c->mSimpleAttrs.GetMixBlendMode() != CompositionOp::OP_OVER) + return c->mSimpleAttrs.GetMixBlendMode(); + } + + return mSimpleAttrs.GetMixBlendMode(); +} + +Matrix4x4 Layer::ComputeTransformToPreserve3DRoot() { + Matrix4x4 transform = GetLocalTransform(); + for (Layer* layer = GetParent(); layer && layer->Extend3DContext(); + layer = layer->GetParent()) { + transform = transform * layer->GetLocalTransform(); + } + return transform; +} + +void Layer::ComputeEffectiveTransformForMaskLayers( + const gfx::Matrix4x4& aTransformToSurface) { + if (GetMaskLayer()) { + ComputeEffectiveTransformForMaskLayer(GetMaskLayer(), aTransformToSurface); + } + for (size_t i = 0; i < GetAncestorMaskLayerCount(); i++) { + Layer* maskLayer = GetAncestorMaskLayerAt(i); + ComputeEffectiveTransformForMaskLayer(maskLayer, aTransformToSurface); + } +} + +/* static */ +void Layer::ComputeEffectiveTransformForMaskLayer( + Layer* aMaskLayer, const gfx::Matrix4x4& aTransformToSurface) { +#ifdef DEBUG + bool maskIs2D = aMaskLayer->GetTransform().CanDraw2D(); + NS_ASSERTION(maskIs2D, "How did we end up with a 3D transform here?!"); +#endif + // The mask layer can have an async transform applied to it in some + // situations, so be sure to use its GetLocalTransform() rather than + // its GetTransform(). + aMaskLayer->mEffectiveTransform = aMaskLayer->SnapTransformTranslation( + aMaskLayer->GetLocalTransform() * aTransformToSurface, nullptr); +} + +RenderTargetRect Layer::TransformRectToRenderTarget(const LayerIntRect& aRect) { + LayerRect rect(aRect); + RenderTargetRect quad = RenderTargetRect::FromUnknownRect( + GetEffectiveTransform().TransformBounds(rect.ToUnknownRect())); + return quad; +} + +bool Layer::GetVisibleRegionRelativeToRootLayer(nsIntRegion& aResult, + IntPoint* aLayerOffset) { + MOZ_ASSERT(aLayerOffset, "invalid offset pointer"); + + if (!GetParent()) { + return false; + } + + IntPoint offset; + aResult = GetLocalVisibleRegion().ToUnknownRegion(); + for (Layer* layer = this; layer; layer = layer->GetParent()) { + gfx::Matrix matrix; + if (!layer->GetLocalTransform().Is2D(&matrix) || !matrix.IsTranslation()) { + return false; + } + + // The offset of |layer| to its parent. + auto currentLayerOffset = IntPoint::Round(matrix.GetTranslation()); + + // Translate the accumulated visible region of |this| by the offset of + // |layer|. + aResult.MoveBy(currentLayerOffset.x, currentLayerOffset.y); + + // If the parent layer clips its lower layers, clip the visible region + // we're accumulating. + if (layer->GetLocalClipRect()) { + aResult.AndWith(layer->GetLocalClipRect()->ToUnknownRect()); + } + + // Now we need to walk across the list of siblings for this parent layer, + // checking to see if any of these layer trees obscure |this|. If so, + // remove these areas from the visible region as well. This will pick up + // chrome overlays like a tab modal prompt. + Layer* sibling; + for (sibling = layer->GetNextSibling(); sibling; + sibling = sibling->GetNextSibling()) { + gfx::Matrix siblingMatrix; + if (!sibling->GetLocalTransform().Is2D(&siblingMatrix) || + !siblingMatrix.IsTranslation()) { + continue; + } + + // Retreive the translation from sibling to |layer|. The accumulated + // visible region is currently oriented with |layer|. + auto siblingOffset = IntPoint::Round(siblingMatrix.GetTranslation()); + nsIntRegion siblingVisibleRegion( + sibling->GetLocalVisibleRegion().ToUnknownRegion()); + // Translate the siblings region to |layer|'s origin. + siblingVisibleRegion.MoveBy(-siblingOffset.x, -siblingOffset.y); + // Apply the sibling's clip. + // Layer clip rects are not affected by the layer's transform. + Maybe clipRect = sibling->GetLocalClipRect(); + if (clipRect) { + siblingVisibleRegion.AndWith(clipRect->ToUnknownRect()); + } + // Subtract the sibling visible region from the visible region of |this|. + aResult.SubOut(siblingVisibleRegion); + } + + // Keep track of the total offset for aLayerOffset. We use this in plugin + // positioning code. + offset += currentLayerOffset; + } + + *aLayerOffset = IntPoint(offset.x, offset.y); + return true; +} + +Maybe Layer::GetCombinedClipRect() const { + Maybe clip = GetClipRect(); + + clip = IntersectMaybeRects(clip, GetScrolledClipRect()); + + for (size_t i = 0; i < mScrollMetadata.Length(); i++) { + clip = IntersectMaybeRects(clip, mScrollMetadata[i].GetClipRect()); + } + + return clip; +} + +ContainerLayer::ContainerLayer(LayerManager* aManager, void* aImplData) + : Layer(aManager, aImplData), + mFirstChild(nullptr), + mLastChild(nullptr), + mPreXScale(1.0f), + mPreYScale(1.0f), + mInheritedXScale(1.0f), + mInheritedYScale(1.0f), + mPresShellResolution(1.0f), + mUseIntermediateSurface(false), + mSupportsComponentAlphaChildren(false), + mMayHaveReadbackChild(false), + mChildrenChanged(false) {} + +ContainerLayer::~ContainerLayer() = default; + +bool ContainerLayer::InsertAfter(Layer* aChild, Layer* aAfter) { + if (aChild->Manager() != Manager()) { + NS_ERROR("Child has wrong manager"); + return false; + } + if (aChild->GetParent()) { + NS_ERROR("aChild already in the tree"); + return false; + } + if (aChild->GetNextSibling() || aChild->GetPrevSibling()) { + NS_ERROR("aChild already has siblings?"); + return false; + } + if (aAfter && + (aAfter->Manager() != Manager() || aAfter->GetParent() != this)) { + NS_ERROR("aAfter is not our child"); + return false; + } + + aChild->SetParent(this); + if (aAfter == mLastChild) { + mLastChild = aChild; + } + if (!aAfter) { + aChild->SetNextSibling(mFirstChild); + if (mFirstChild) { + mFirstChild->SetPrevSibling(aChild); + } + mFirstChild = aChild; + NS_ADDREF(aChild); + DidInsertChild(aChild); + return true; + } + + Layer* next = aAfter->GetNextSibling(); + aChild->SetNextSibling(next); + aChild->SetPrevSibling(aAfter); + if (next) { + next->SetPrevSibling(aChild); + } + aAfter->SetNextSibling(aChild); + NS_ADDREF(aChild); + DidInsertChild(aChild); + return true; +} + +void ContainerLayer::RemoveAllChildren() { + // Optimizes "while (mFirstChild) ContainerLayer::RemoveChild(mFirstChild);" + Layer* current = mFirstChild; + + // This is inlining DidRemoveChild() on each layer; we can skip the calls + // to NotifyPaintedLayerRemoved as it gets taken care of when as we call + // NotifyRemoved prior to removing any layers. + while (current) { + Layer* next = current->GetNextSibling(); + if (current->GetType() == TYPE_READBACK) { + static_cast(current)->NotifyRemoved(); + } + current = next; + } + + current = mFirstChild; + mFirstChild = nullptr; + while (current) { + MOZ_ASSERT(!current->GetPrevSibling()); + + Layer* next = current->GetNextSibling(); + current->SetParent(nullptr); + current->SetNextSibling(nullptr); + if (next) { + next->SetPrevSibling(nullptr); + } + NS_RELEASE(current); + current = next; + } +} + +// Note that ContainerLayer::RemoveAllChildren is an optimized +// version of this code; if you make changes to ContainerLayer::RemoveChild +// consider whether the matching changes need to be made to +// ContainerLayer::RemoveAllChildren +bool ContainerLayer::RemoveChild(Layer* aChild) { + if (aChild->Manager() != Manager()) { + NS_ERROR("Child has wrong manager"); + return false; + } + if (aChild->GetParent() != this) { + NS_ERROR("aChild not our child"); + return false; + } + + Layer* prev = aChild->GetPrevSibling(); + Layer* next = aChild->GetNextSibling(); + if (prev) { + prev->SetNextSibling(next); + } else { + this->mFirstChild = next; + } + if (next) { + next->SetPrevSibling(prev); + } else { + this->mLastChild = prev; + } + + aChild->SetNextSibling(nullptr); + aChild->SetPrevSibling(nullptr); + aChild->SetParent(nullptr); + + this->DidRemoveChild(aChild); + NS_RELEASE(aChild); + return true; +} + +bool ContainerLayer::RepositionChild(Layer* aChild, Layer* aAfter) { + if (aChild->Manager() != Manager()) { + NS_ERROR("Child has wrong manager"); + return false; + } + if (aChild->GetParent() != this) { + NS_ERROR("aChild not our child"); + return false; + } + if (aAfter && + (aAfter->Manager() != Manager() || aAfter->GetParent() != this)) { + NS_ERROR("aAfter is not our child"); + return false; + } + if (aChild == aAfter) { + NS_ERROR("aChild cannot be the same as aAfter"); + return false; + } + + Layer* prev = aChild->GetPrevSibling(); + Layer* next = aChild->GetNextSibling(); + if (prev == aAfter) { + // aChild is already in the correct position, nothing to do. + return true; + } + if (prev) { + prev->SetNextSibling(next); + } else { + mFirstChild = next; + } + if (next) { + next->SetPrevSibling(prev); + } else { + mLastChild = prev; + } + if (!aAfter) { + aChild->SetPrevSibling(nullptr); + aChild->SetNextSibling(mFirstChild); + if (mFirstChild) { + mFirstChild->SetPrevSibling(aChild); + } + mFirstChild = aChild; + return true; + } + + Layer* afterNext = aAfter->GetNextSibling(); + if (afterNext) { + afterNext->SetPrevSibling(aChild); + } else { + mLastChild = aChild; + } + aAfter->SetNextSibling(aChild); + aChild->SetPrevSibling(aAfter); + aChild->SetNextSibling(afterNext); + return true; +} + +void ContainerLayer::FillSpecificAttributes(SpecificLayerAttributes& aAttrs) { + aAttrs = ContainerLayerAttributes(mPreXScale, mPreYScale, mInheritedXScale, + mInheritedYScale, mPresShellResolution); +} + +bool ContainerLayer::Creates3DContextWithExtendingChildren() { + if (Extend3DContext()) { + return false; + } + for (Layer* child = GetFirstChild(); child; child = child->GetNextSibling()) { + if (child->Extend3DContext()) { + return true; + } + } + return false; +} + +RenderTargetIntRect ContainerLayer::GetIntermediateSurfaceRect() { + NS_ASSERTION(mUseIntermediateSurface, "Must have intermediate surface"); + LayerIntRect bounds = GetLocalVisibleRegion().GetBounds(); + return RenderTargetIntRect::FromUnknownRect(bounds.ToUnknownRect()); +} + +bool ContainerLayer::HasMultipleChildren() { + uint32_t count = 0; + for (Layer* child = GetFirstChild(); child; child = child->GetNextSibling()) { + const Maybe& clipRect = child->GetLocalClipRect(); + if (clipRect && clipRect->IsEmpty()) continue; + if (!child->Extend3DContext() && child->GetLocalVisibleRegion().IsEmpty()) + continue; + ++count; + if (count > 1) return true; + } + + return false; +} + +/** + * Collect all leaf descendants of the current 3D context. + */ +void ContainerLayer::Collect3DContextLeaves(nsTArray& aToSort) { + ForEachNode((Layer*)this, [this, &aToSort](Layer* layer) { + ContainerLayer* container = layer->AsContainerLayer(); + if (layer == this || (container && container->Extend3DContext() && + !container->UseIntermediateSurface())) { + return TraversalFlag::Continue; + } + aToSort.AppendElement(layer); + return TraversalFlag::Skip; + }); +} + +static nsTArray SortLayersWithBSPTree(nsTArray& aArray) { + std::list inputLayers; + + // Build a list of polygons to be sorted. + for (Layer* layer : aArray) { + // Ignore invisible layers. + if (!layer->IsVisible()) { + continue; + } + + const gfx::IntRect& bounds = + layer->GetLocalVisibleRegion().GetBounds().ToUnknownRect(); + + const gfx::Matrix4x4& transform = layer->GetEffectiveTransform(); + + if (transform.IsSingular()) { + // Transform cannot be inverted. + continue; + } + + gfx::Polygon polygon = gfx::Polygon::FromRect(gfx::Rect(bounds)); + + // Transform the polygon to screen space. + polygon.TransformToScreenSpace(transform); + + if (polygon.GetPoints().Length() >= 3) { + inputLayers.push_back(LayerPolygon(layer, std::move(polygon))); + } + } + + if (inputLayers.empty()) { + return nsTArray(); + } + + // Build a BSP tree from the list of polygons. + BSPTree tree(inputLayers); + + nsTArray orderedLayers(tree.GetDrawOrder()); + + // Transform the polygons back to layer space. + for (LayerPolygon& layerPolygon : orderedLayers) { + gfx::Matrix4x4 inverse = + layerPolygon.layer->GetEffectiveTransform().Inverse(); + + MOZ_ASSERT(layerPolygon.geometry); + layerPolygon.geometry->TransformToLayerSpace(inverse); + } + + return orderedLayers; +} + +static nsTArray StripLayerGeometry( + const nsTArray& aLayers) { + nsTArray layers; + std::set uniqueLayers; + + for (const LayerPolygon& layerPolygon : aLayers) { + auto result = uniqueLayers.insert(layerPolygon.layer); + + if (result.second) { + // Layer was added to the set. + layers.AppendElement(LayerPolygon(layerPolygon.layer)); + } + } + + return layers; +} + +nsTArray ContainerLayer::SortChildrenBy3DZOrder( + SortMode aSortMode) { + AutoTArray toSort; + nsTArray drawOrder; + + for (Layer* layer = GetFirstChild(); layer; layer = layer->GetNextSibling()) { + ContainerLayer* container = layer->AsContainerLayer(); + + if (container && container->Extend3DContext() && + !container->UseIntermediateSurface()) { + // Collect 3D layers in toSort array. + container->Collect3DContextLeaves(toSort); + + // Sort the 3D layers. + if (toSort.Length() > 0) { + nsTArray sorted = SortLayersWithBSPTree(toSort); + drawOrder.AppendElements(std::move(sorted)); + + toSort.ClearAndRetainStorage(); + } + + continue; + } + + drawOrder.AppendElement(LayerPolygon(layer)); + } + + if (aSortMode == SortMode::WITHOUT_GEOMETRY) { + // Compositor does not support arbitrary layers, strip the layer geometry + // and duplicate layers. + return StripLayerGeometry(drawOrder); + } + + return drawOrder; +} + +bool ContainerLayer::AnyAncestorOrThisIs3DContextLeaf() { + Layer* parent = this; + while (parent != nullptr) { + if (parent->Is3DContextLeaf()) { + return true; + } + + parent = parent->GetParent(); + } + + return false; +} + +void ContainerLayer::DefaultComputeEffectiveTransforms( + const Matrix4x4& aTransformToSurface) { + Matrix residual; + Matrix4x4 idealTransform = GetLocalTransform() * aTransformToSurface; + + // Keep 3D transforms for leaves to keep z-order sorting correct. + if (!Extend3DContext() && !Is3DContextLeaf()) { + idealTransform.ProjectTo2D(); + } + + bool useIntermediateSurface; + if (HasMaskLayers() || GetForceIsolatedGroup()) { + useIntermediateSurface = true; +#ifdef MOZ_DUMP_PAINTING + } else if (gfxEnv::DumpPaintIntermediate() && !Extend3DContext()) { + useIntermediateSurface = true; +#endif + } else { + /* Don't use an intermediate surface for opacity when it's within a 3d + * context, since we'd rather keep the 3d effects. This matches the + * WebKit/blink behaviour, but is changing in the latest spec. + */ + float opacity = GetEffectiveOpacity(); + CompositionOp blendMode = GetEffectiveMixBlendMode(); + if ((HasMultipleChildren() || Creates3DContextWithExtendingChildren()) && + ((opacity != 1.0f && !Extend3DContext()) || + (blendMode != CompositionOp::OP_OVER))) { + useIntermediateSurface = true; + } else if ((!idealTransform.Is2D() || AnyAncestorOrThisIs3DContextLeaf()) && + Creates3DContextWithExtendingChildren()) { + useIntermediateSurface = true; + } else if (blendMode != CompositionOp::OP_OVER && + Manager()->BlendingRequiresIntermediateSurface()) { + useIntermediateSurface = true; + } else { + useIntermediateSurface = false; + gfx::Matrix contTransform; + bool checkClipRect = false; + bool checkMaskLayers = false; + + if (!idealTransform.Is2D(&contTransform)) { + // In 3D case, always check if we should use IntermediateSurface. + checkClipRect = true; + checkMaskLayers = true; + } else { + contTransform.NudgeToIntegers(); +#ifdef MOZ_GFX_OPTIMIZE_MOBILE + if (!contTransform.PreservesAxisAlignedRectangles()) { +#else + if (gfx::ThebesMatrix(contTransform).HasNonIntegerTranslation()) { +#endif + checkClipRect = true; + } + /* In 2D case, only translation and/or positive scaling can be done w/o + * using IntermediateSurface. Otherwise, when rotation or flip happen, + * we should check whether to use IntermediateSurface. + */ + if (contTransform.HasNonAxisAlignedTransform() || + contTransform.HasNegativeScaling()) { + checkMaskLayers = true; + } + } + + if (checkClipRect || checkMaskLayers) { + for (Layer* child = GetFirstChild(); child; + child = child->GetNextSibling()) { + const Maybe& clipRect = child->GetLocalClipRect(); + /* We can't (easily) forward our transform to children with a + * non-empty clip rect since it would need to be adjusted for the + * transform. See the calculations performed by CalculateScissorRect + * above. Nor for a child with a mask layer. + */ + if (checkClipRect && (clipRect && !clipRect->IsEmpty() && + (child->Extend3DContext() || + !child->GetLocalVisibleRegion().IsEmpty()))) { + useIntermediateSurface = true; + break; + } + if (checkMaskLayers && child->HasMaskLayers()) { + useIntermediateSurface = true; + break; + } + } + } + } + } + + NS_ASSERTION(!Extend3DContext() || !useIntermediateSurface, + "Can't have an intermediate surface with preserve-3d!"); + + if (useIntermediateSurface) { + mEffectiveTransform = SnapTransformTranslation(idealTransform, &residual); + } else { + mEffectiveTransform = idealTransform; + } + + // For layers extending 3d context, its ideal transform should be + // applied on children. + if (!Extend3DContext()) { + // Without this projection, non-container children would get a 3D + // transform while 2D is expected. + idealTransform.ProjectTo2D(); + } + mUseIntermediateSurface = useIntermediateSurface; + if (useIntermediateSurface) { + ComputeEffectiveTransformsForChildren(Matrix4x4::From2D(residual)); + } else { + ComputeEffectiveTransformsForChildren(idealTransform); + } + + ComputeEffectiveTransformForMaskLayers(aTransformToSurface); +} + +void ContainerLayer::DefaultComputeSupportsComponentAlphaChildren( + bool* aNeedsSurfaceCopy) { + if (!(GetContentFlags() & Layer::CONTENT_COMPONENT_ALPHA_DESCENDANT) || + !Manager()->AreComponentAlphaLayersEnabled()) { + mSupportsComponentAlphaChildren = false; + if (aNeedsSurfaceCopy) { + *aNeedsSurfaceCopy = false; + } + return; + } + + mSupportsComponentAlphaChildren = false; + bool needsSurfaceCopy = false; + CompositionOp blendMode = GetEffectiveMixBlendMode(); + if (UseIntermediateSurface()) { + if (GetLocalVisibleRegion().GetNumRects() == 1 && + (GetContentFlags() & Layer::CONTENT_OPAQUE)) { + mSupportsComponentAlphaChildren = true; + } else { + gfx::Matrix transform; + if (HasOpaqueAncestorLayer(this) && + GetEffectiveTransform().Is2D(&transform) && + !gfx::ThebesMatrix(transform).HasNonIntegerTranslation() && + blendMode == gfx::CompositionOp::OP_OVER) { + mSupportsComponentAlphaChildren = true; + needsSurfaceCopy = true; + } + } + } else if (blendMode == gfx::CompositionOp::OP_OVER) { + mSupportsComponentAlphaChildren = + (GetContentFlags() & Layer::CONTENT_OPAQUE) || + (GetParent() && GetParent()->SupportsComponentAlphaChildren()); + } + + if (aNeedsSurfaceCopy) { + *aNeedsSurfaceCopy = mSupportsComponentAlphaChildren && needsSurfaceCopy; + } +} + +void ContainerLayer::ComputeEffectiveTransformsForChildren( + const Matrix4x4& aTransformToSurface) { + for (Layer* l = mFirstChild; l; l = l->GetNextSibling()) { + l->ComputeEffectiveTransforms(aTransformToSurface); + } +} + +/* static */ +bool ContainerLayer::HasOpaqueAncestorLayer(Layer* aLayer) { + for (Layer* l = aLayer->GetParent(); l; l = l->GetParent()) { + if (l->GetContentFlags() & Layer::CONTENT_OPAQUE) return true; + } + return false; +} + +// Note that ContainerLayer::RemoveAllChildren contains an optimized +// version of this code; if you make changes to ContainerLayer::DidRemoveChild +// consider whether the matching changes need to be made to +// ContainerLayer::RemoveAllChildren +void ContainerLayer::DidRemoveChild(Layer* aLayer) { + PaintedLayer* tl = aLayer->AsPaintedLayer(); + if (tl && tl->UsedForReadback()) { + for (Layer* l = mFirstChild; l; l = l->GetNextSibling()) { + if (l->GetType() == TYPE_READBACK) { + static_cast(l)->NotifyPaintedLayerRemoved(tl); + } + } + } + if (aLayer->GetType() == TYPE_READBACK) { + static_cast(aLayer)->NotifyRemoved(); + } +} + +void ContainerLayer::DidInsertChild(Layer* aLayer) { + if (aLayer->GetType() == TYPE_READBACK) { + mMayHaveReadbackChild = true; + } +} + +void RefLayer::FillSpecificAttributes(SpecificLayerAttributes& aAttrs) { + aAttrs = RefLayerAttributes(GetReferentId(), mEventRegionsOverride, + mRemoteDocumentSize); +} + +/** + * StartFrameTimeRecording, together with StopFrameTimeRecording + * enable recording of frame intervals. + * + * To allow concurrent consumers, a cyclic array is used which serves all + * consumers, practically stateless with regard to consumers. + * + * To save resources, the buffer is allocated on first call to + * StartFrameTimeRecording and recording is paused if no consumer which called + * StartFrameTimeRecording is able to get valid results (because the cyclic + * buffer was overwritten since that call). + * + * To determine availability of the data upon StopFrameTimeRecording: + * - mRecording.mNextIndex increases on each RecordFrame, and never resets. + * - Cyclic buffer position is realized as mNextIndex % bufferSize. + * - StartFrameTimeRecording returns mNextIndex. When StopFrameTimeRecording is + * called, the required start index is passed as an arg, and we're able to + * calculate the required length. If this length is bigger than bufferSize, it + * means data was overwritten. otherwise, we can return the entire sequence. + * - To determine if we need to pause, mLatestStartIndex is updated to + * mNextIndex on each call to StartFrameTimeRecording. If this index gets + * overwritten, it means that all earlier start indices obtained via + * StartFrameTimeRecording were also overwritten, hence, no point in + * recording, so pause. + * - mCurrentRunStartIndex indicates the oldest index of the recording after + * which the recording was not paused. If StopFrameTimeRecording is invoked + * with a start index older than this, it means that some frames were not + * recorded, so data is invalid. + */ +uint32_t FrameRecorder::StartFrameTimeRecording(int32_t aBufferSize) { + if (mRecording.mIsPaused) { + mRecording.mIsPaused = false; + + if (!mRecording.mIntervals.Length()) { // Initialize recording buffers + mRecording.mIntervals.SetLength(aBufferSize); + } + + // After being paused, recent values got invalid. Update them to now. + mRecording.mLastFrameTime = TimeStamp::Now(); + + // Any recording which started before this is invalid, since we were paused. + mRecording.mCurrentRunStartIndex = mRecording.mNextIndex; + } + + // If we'll overwrite this index, there are no more consumers with aStartIndex + // for which we're able to provide the full recording, so no point in keep + // recording. + mRecording.mLatestStartIndex = mRecording.mNextIndex; + return mRecording.mNextIndex; +} + +void FrameRecorder::RecordFrame() { + if (!mRecording.mIsPaused) { + TimeStamp now = TimeStamp::Now(); + uint32_t i = mRecording.mNextIndex % mRecording.mIntervals.Length(); + mRecording.mIntervals[i] = + static_cast((now - mRecording.mLastFrameTime).ToMilliseconds()); + mRecording.mNextIndex++; + mRecording.mLastFrameTime = now; + + if (mRecording.mNextIndex > + (mRecording.mLatestStartIndex + mRecording.mIntervals.Length())) { + // We've just overwritten the most recent recording start -> pause. + mRecording.mIsPaused = true; + } + } +} + +void FrameRecorder::StopFrameTimeRecording(uint32_t aStartIndex, + nsTArray& aFrameIntervals) { + uint32_t bufferSize = mRecording.mIntervals.Length(); + uint32_t length = mRecording.mNextIndex - aStartIndex; + if (mRecording.mIsPaused || length > bufferSize || + aStartIndex < mRecording.mCurrentRunStartIndex) { + // aStartIndex is too old. Also if aStartIndex was issued before + // mRecordingNextIndex overflowed (uint32_t) + // and stopped after the overflow (would happen once every 828 days of + // constant 60fps). + length = 0; + } + + if (!length) { + aFrameIntervals.Clear(); + return; // empty recording, return empty arrays. + } + // Set length in advance to avoid possibly repeated reallocations + aFrameIntervals.SetLength(length); + + uint32_t cyclicPos = aStartIndex % bufferSize; + for (uint32_t i = 0; i < length; i++, cyclicPos++) { + if (cyclicPos == bufferSize) { + cyclicPos = 0; + } + aFrameIntervals[i] = mRecording.mIntervals[cyclicPos]; + } +} + +static void PrintInfo(std::stringstream& aStream, HostLayer* aLayerComposite); + +#ifdef MOZ_DUMP_PAINTING +template +void WriteSnapshotToDumpFile_internal(T* aObj, DataSourceSurface* aSurf) { + nsCString string(aObj->Name()); + string.Append('-'); + string.AppendInt((uint64_t)aObj); + if (gfxUtils::sDumpPaintFile != stderr) { + fprintf_stderr(gfxUtils::sDumpPaintFile, R"(array["%s"]=")", + string.BeginReading()); + } + gfxUtils::DumpAsDataURI(aSurf, gfxUtils::sDumpPaintFile); + if (gfxUtils::sDumpPaintFile != stderr) { + fprintf_stderr(gfxUtils::sDumpPaintFile, R"(";)"); + } +} + +void WriteSnapshotToDumpFile(Layer* aLayer, DataSourceSurface* aSurf) { + WriteSnapshotToDumpFile_internal(aLayer, aSurf); +} + +void WriteSnapshotToDumpFile(LayerManager* aManager, DataSourceSurface* aSurf) { + WriteSnapshotToDumpFile_internal(aManager, aSurf); +} + +void WriteSnapshotToDumpFile(Compositor* aCompositor, DrawTarget* aTarget) { + RefPtr surf = aTarget->Snapshot(); + RefPtr dSurf = surf->GetDataSurface(); + WriteSnapshotToDumpFile_internal(aCompositor, dSurf); +} +#endif + +void Layer::Dump(std::stringstream& aStream, const char* aPrefix, + bool aDumpHtml, bool aSorted, + const Maybe& aGeometry) { +#ifdef MOZ_DUMP_PAINTING + bool dumpCompositorTexture = gfxEnv::DumpCompositorTextures() && + AsHostLayer() && + AsHostLayer()->GetCompositableHost(); + bool dumpClientTexture = gfxEnv::DumpPaint() && AsShadowableLayer() && + AsShadowableLayer()->GetCompositableClient(); + nsCString layerId(Name()); + layerId.Append('-'); + layerId.AppendInt((uint64_t)this); +#endif + if (aDumpHtml) { + aStream << nsPrintfCString(R"(
  • "; + } + DumpSelf(aStream, aPrefix, aGeometry); + +#ifdef MOZ_DUMP_PAINTING + if (dumpCompositorTexture) { + AsHostLayer()->GetCompositableHost()->Dump(aStream, aPrefix, aDumpHtml); + } else if (dumpClientTexture) { + if (aDumpHtml) { + aStream << nsPrintfCString(R"()"; + } + } +#endif + + if (aDumpHtml) { + aStream << ""; +#ifdef MOZ_DUMP_PAINTING + if (dumpClientTexture) { + aStream << nsPrintfCString("
    \n", + layerId.BeginReading()) + .get(); + } +#endif + } + + if (Layer* mask = GetMaskLayer()) { + aStream << nsPrintfCString("%s Mask layer:\n", aPrefix).get(); + nsAutoCString pfx(aPrefix); + pfx += " "; + mask->Dump(aStream, pfx.get(), aDumpHtml); + } + + for (size_t i = 0; i < GetAncestorMaskLayerCount(); i++) { + aStream << nsPrintfCString("%s Ancestor mask layer %d:\n", aPrefix, + uint32_t(i)) + .get(); + nsAutoCString pfx(aPrefix); + pfx += " "; + GetAncestorMaskLayerAt(i)->Dump(aStream, pfx.get(), aDumpHtml); + } + +#ifdef MOZ_DUMP_PAINTING + for (size_t i = 0; i < mExtraDumpInfo.Length(); i++) { + const nsCString& str = mExtraDumpInfo[i]; + aStream << aPrefix << " Info:\n" << str.get(); + } +#endif + + if (ContainerLayer* container = AsContainerLayer()) { + nsTArray children; + if (aSorted) { + children = container->SortChildrenBy3DZOrder( + ContainerLayer::SortMode::WITH_GEOMETRY); + } else { + for (Layer* l = container->GetFirstChild(); l; l = l->GetNextSibling()) { + children.AppendElement(LayerPolygon(l)); + } + } + nsAutoCString pfx(aPrefix); + pfx += " "; + if (aDumpHtml) { + aStream << "
      "; + } + + for (LayerPolygon& child : children) { + child.layer->Dump(aStream, pfx.get(), aDumpHtml, aSorted, child.geometry); + } + + if (aDumpHtml) { + aStream << "
    "; + } + } + + if (aDumpHtml) { + aStream << "
  • "; + } +} + +static void DumpGeometry(std::stringstream& aStream, + const Maybe& aGeometry) { + aStream << " [geometry=["; + + const nsTArray& points = aGeometry->GetPoints(); + for (size_t i = 0; i < points.Length(); ++i) { + const gfx::IntPoint point = TruncatedToInt(points[i].As2DPoint()); + aStream << point; + if (i != points.Length() - 1) { + aStream << ","; + } + } + + aStream << "]]"; +} + +void Layer::DumpSelf(std::stringstream& aStream, const char* aPrefix, + const Maybe& aGeometry) { + PrintInfo(aStream, aPrefix); + + if (aGeometry) { + DumpGeometry(aStream, aGeometry); + } + + aStream << "\n"; +} + +void Layer::Dump(layerscope::LayersPacket* aPacket, const void* aParent) { + DumpPacket(aPacket, aParent); + + if (Layer* kid = GetFirstChild()) { + kid->Dump(aPacket, this); + } + + if (Layer* next = GetNextSibling()) { + next->Dump(aPacket, aParent); + } +} + +void Layer::SetDisplayListLog(const char* log) { + if (gfxUtils::DumpDisplayList()) { + mDisplayListLog = log; + } +} + +void Layer::GetDisplayListLog(nsCString& log) { + log.SetLength(0); + + if (gfxUtils::DumpDisplayList()) { + // This function returns a plain text string which consists of two things + // 1. DisplayList log. + // 2. Memory address of this layer. + // We know the target layer of each display item by information in #1. + // Here is an example of a Text display item line log in #1 + // Text p=0xa9850c00 f=0x0xaa405b00(..... + // f keeps the address of the target client layer of a display item. + // For LayerScope, display-item-to-client-layer mapping is not enough since + // LayerScope, which lives in the chrome process, knows only composite + // layers. As so, we need display-item-to-client-layer-to-layer-composite + // mapping. That's the reason we insert #2 into the log + log.AppendPrintf("0x%p\n%s", (void*)this, mDisplayListLog.get()); + } +} + +void Layer::Log(const char* aPrefix) { + if (!IsLogEnabled()) return; + + LogSelf(aPrefix); + + if (Layer* kid = GetFirstChild()) { + nsAutoCString pfx(aPrefix); + pfx += " "; + kid->Log(pfx.get()); + } + + if (Layer* next = GetNextSibling()) next->Log(aPrefix); +} + +void Layer::LogSelf(const char* aPrefix) { + if (!IsLogEnabled()) return; + + std::stringstream ss; + PrintInfo(ss, aPrefix); + MOZ_LAYERS_LOG(("%s", ss.str().c_str())); + + if (mMaskLayer) { + nsAutoCString pfx(aPrefix); + pfx += R"( \ MaskLayer )"; + mMaskLayer->LogSelf(pfx.get()); + } +} + +void Layer::PrintInfo(std::stringstream& aStream, const char* aPrefix) { + aStream << aPrefix; + aStream + << nsPrintfCString("%s%s (0x%p)", mManager->Name(), Name(), this).get(); + + layers::PrintInfo(aStream, AsHostLayer()); + + if (mClipRect) { + aStream << " [clip=" << *mClipRect << "]"; + } + if (mSimpleAttrs.GetScrolledClip()) { + aStream << " [scrolled-clip=" + << mSimpleAttrs.GetScrolledClip()->GetClipRect() << "]"; + if (const Maybe& ix = + mSimpleAttrs.GetScrolledClip()->GetMaskLayerIndex()) { + aStream << " [scrolled-mask=" << ix.value() << "]"; + } + } + if (1.0 != mSimpleAttrs.GetPostXScale() || + 1.0 != mSimpleAttrs.GetPostYScale()) { + aStream << nsPrintfCString(" [postScale=%g, %g]", + mSimpleAttrs.GetPostXScale(), + mSimpleAttrs.GetPostYScale()) + .get(); + } + if (!GetBaseTransform().IsIdentity()) { + aStream << " [transform=" << GetBaseTransform() << "]"; + } + if (!GetEffectiveTransform().IsIdentity()) { + aStream << " [effective-transform=" << GetEffectiveTransform() << "]"; + } + if (GetTransformIsPerspective()) { + aStream << " [perspective]"; + } + if (!mVisibleRegion.IsEmpty()) { + aStream << " [visible=" << mVisibleRegion << "]"; + } else { + aStream << " [not visible]"; + } + if (!mEventRegions.IsEmpty()) { + aStream << " " << mEventRegions; + } + if (1.0 != GetOpacity()) { + aStream << nsPrintfCString(" [opacity=%g]", GetOpacity()).get(); + } + if (IsOpaque()) { + aStream << " [opaqueContent]"; + } + if (GetContentFlags() & CONTENT_COMPONENT_ALPHA) { + aStream << " [componentAlpha]"; + } + if (GetContentFlags() & CONTENT_BACKFACE_HIDDEN) { + aStream << " [backfaceHidden]"; + } + if (Extend3DContext()) { + aStream << " [extend3DContext]"; + } + if (Combines3DTransformWithAncestors()) { + aStream << " [combines3DTransformWithAncestors]"; + } + if (Is3DContextLeaf()) { + aStream << " [is3DContextLeaf]"; + } + if (Maybe viewId = IsAsyncZoomContainer()) { + aStream << nsPrintfCString(" [asyncZoomContainer scrollId=%" PRIu64 "]", + *viewId) + .get(); + } + if (IsScrollbarContainer()) { + aStream << " [scrollbar]"; + } + if (GetScrollbarData().IsThumb()) { + if (Maybe thumbDirection = GetScrollbarData().mDirection) { + if (*thumbDirection == ScrollDirection::eVertical) { + aStream << nsPrintfCString(" [vscrollbar=%" PRIu64 "]", + GetScrollbarData().mTargetViewId) + .get(); + } + if (*thumbDirection == ScrollDirection::eHorizontal) { + aStream << nsPrintfCString(" [hscrollbar=%" PRIu64 "]", + GetScrollbarData().mTargetViewId) + .get(); + } + } + } + if (GetIsFixedPosition()) { + LayerPoint anchor = GetFixedPositionAnchor(); + aStream << nsPrintfCString( + " [isFixedPosition scrollId=%" PRIu64 + " sides=0x%x anchor=%s]", + GetFixedPositionScrollContainerId(), + static_cast(GetFixedPositionSides()), + ToString(anchor).c_str()) + .get(); + } + if (GetIsStickyPosition()) { + aStream << nsPrintfCString(" [isStickyPosition scrollId=%" PRIu64 + " outer=(%.3f,%.3f)-(%.3f,%.3f) " + "inner=(%.3f,%.3f)-(%.3f,%.3f)]", + GetStickyScrollContainerId(), + GetStickyScrollRangeOuter().X(), + GetStickyScrollRangeOuter().Y(), + GetStickyScrollRangeOuter().XMost(), + GetStickyScrollRangeOuter().YMost(), + GetStickyScrollRangeInner().X(), + GetStickyScrollRangeInner().Y(), + GetStickyScrollRangeInner().XMost(), + GetStickyScrollRangeInner().YMost()) + .get(); + } + if (mMaskLayer) { + aStream << nsPrintfCString(" [mMaskLayer=%p]", mMaskLayer.get()).get(); + } + for (uint32_t i = 0; i < mScrollMetadata.Length(); i++) { + if (!mScrollMetadata[i].IsDefault()) { + aStream << " [metrics" << i << "=" << mScrollMetadata[i] << "]"; + } + } + // FIXME: On the compositor thread, we don't set mAnimationInfo::mAnimations, + // All animations are transformed by AnimationHelper::ExtractAnimations() into + // mAnimationInfo.mPropertyAnimationGroups, instead. So if we want to check + // if layer trees are properly synced up across processes, we should dump + // mAnimationInfo.mPropertyAnimationGroups for the compositor thread. + // (See AnimationInfo.h for more details.) + if (!mAnimationInfo.GetAnimations().IsEmpty()) { + aStream << nsPrintfCString(" [%d animations with id=%" PRIu64 " ]", + (int)mAnimationInfo.GetAnimations().Length(), + mAnimationInfo.GetCompositorAnimationsId()) + .get(); + } +} + +// The static helper function sets the transform matrix into the packet +static void DumpTransform(layerscope::LayersPacket::Layer::Matrix* aLayerMatrix, + const Matrix4x4& aMatrix) { + aLayerMatrix->set_is2d(aMatrix.Is2D()); + if (aMatrix.Is2D()) { + Matrix m = aMatrix.As2D(); + aLayerMatrix->set_isid(m.IsIdentity()); + if (!m.IsIdentity()) { + aLayerMatrix->add_m(m._11); + aLayerMatrix->add_m(m._12); + aLayerMatrix->add_m(m._21); + aLayerMatrix->add_m(m._22); + aLayerMatrix->add_m(m._31); + aLayerMatrix->add_m(m._32); + } + } else { + aLayerMatrix->add_m(aMatrix._11); + aLayerMatrix->add_m(aMatrix._12); + aLayerMatrix->add_m(aMatrix._13); + aLayerMatrix->add_m(aMatrix._14); + aLayerMatrix->add_m(aMatrix._21); + aLayerMatrix->add_m(aMatrix._22); + aLayerMatrix->add_m(aMatrix._23); + aLayerMatrix->add_m(aMatrix._24); + aLayerMatrix->add_m(aMatrix._31); + aLayerMatrix->add_m(aMatrix._32); + aLayerMatrix->add_m(aMatrix._33); + aLayerMatrix->add_m(aMatrix._34); + aLayerMatrix->add_m(aMatrix._41); + aLayerMatrix->add_m(aMatrix._42); + aLayerMatrix->add_m(aMatrix._43); + aLayerMatrix->add_m(aMatrix._44); + } +} + +// The static helper function sets the IntRect into the packet +template +static void DumpRect(layerscope::LayersPacket::Layer::Rect* aLayerRect, + const BaseRect& aRect) { + aLayerRect->set_x(aRect.X()); + aLayerRect->set_y(aRect.Y()); + aLayerRect->set_w(aRect.Width()); + aLayerRect->set_h(aRect.Height()); +} + +// The static helper function sets the nsIntRegion into the packet +static void DumpRegion(layerscope::LayersPacket::Layer::Region* aLayerRegion, + const nsIntRegion& aRegion) { + for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) { + DumpRect(aLayerRegion->add_r(), iter.Get()); + } +} + +void Layer::DumpPacket(layerscope::LayersPacket* aPacket, const void* aParent) { + // Add a new layer (UnknownLayer) + using namespace layerscope; + LayersPacket::Layer* layer = aPacket->add_layer(); + // Basic information + layer->set_type(LayersPacket::Layer::UnknownLayer); + layer->set_ptr(reinterpret_cast(this)); + layer->set_parentptr(reinterpret_cast(aParent)); + // Shadow + if (HostLayer* lc = AsHostLayer()) { + LayersPacket::Layer::Shadow* s = layer->mutable_shadow(); + if (const Maybe& clipRect = lc->GetShadowClipRect()) { + DumpRect(s->mutable_clip(), *clipRect); + } + if (!lc->GetShadowBaseTransform().IsIdentity()) { + DumpTransform(s->mutable_transform(), lc->GetShadowBaseTransform()); + } + if (!lc->GetShadowVisibleRegion().IsEmpty()) { + DumpRegion(s->mutable_vregion(), + lc->GetShadowVisibleRegion().ToUnknownRegion()); + } + } + // Clip + if (mClipRect) { + DumpRect(layer->mutable_clip(), *mClipRect); + } + // Transform + if (!GetBaseTransform().IsIdentity()) { + DumpTransform(layer->mutable_transform(), GetBaseTransform()); + } + // Visible region + if (!mVisibleRegion.ToUnknownRegion().IsEmpty()) { + DumpRegion(layer->mutable_vregion(), mVisibleRegion.ToUnknownRegion()); + } + // EventRegions + if (!mEventRegions.IsEmpty()) { + const EventRegions& e = mEventRegions; + if (!e.mHitRegion.IsEmpty()) { + DumpRegion(layer->mutable_hitregion(), e.mHitRegion); + } + if (!e.mDispatchToContentHitRegion.IsEmpty()) { + DumpRegion(layer->mutable_dispatchregion(), + e.mDispatchToContentHitRegion); + } + if (!e.mNoActionRegion.IsEmpty()) { + DumpRegion(layer->mutable_noactionregion(), e.mNoActionRegion); + } + if (!e.mHorizontalPanRegion.IsEmpty()) { + DumpRegion(layer->mutable_hpanregion(), e.mHorizontalPanRegion); + } + if (!e.mVerticalPanRegion.IsEmpty()) { + DumpRegion(layer->mutable_vpanregion(), e.mVerticalPanRegion); + } + } + // Opacity + layer->set_opacity(GetOpacity()); + // Content opaque + layer->set_copaque(static_cast(GetContentFlags() & CONTENT_OPAQUE)); + // Component alpha + layer->set_calpha( + static_cast(GetContentFlags() & CONTENT_COMPONENT_ALPHA)); + // Vertical or horizontal bar + if (GetScrollbarData().mScrollbarLayerType == + layers::ScrollbarLayerType::Thumb) { + layer->set_direct(*GetScrollbarData().mDirection == + ScrollDirection::eVertical + ? LayersPacket::Layer::VERTICAL + : LayersPacket::Layer::HORIZONTAL); + layer->set_barid(GetScrollbarData().mTargetViewId); + } + + // Mask layer + if (mMaskLayer) { + layer->set_mask(reinterpret_cast(mMaskLayer.get())); + } + + // DisplayList log. + if (mDisplayListLog.Length() > 0) { + layer->set_displaylistloglength(mDisplayListLog.Length()); + auto compressedData = + MakeUnique(LZ4::maxCompressedSize(mDisplayListLog.Length())); + int compressedSize = + LZ4::compress((char*)mDisplayListLog.get(), mDisplayListLog.Length(), + compressedData.get()); + layer->set_displaylistlog(compressedData.get(), compressedSize); + } +} + +bool Layer::IsBackfaceHidden() { + if (GetContentFlags() & CONTENT_BACKFACE_HIDDEN) { + Layer* container = AsContainerLayer() ? this : GetParent(); + if (container) { + // The effective transform can include non-preserve-3d parent + // transforms, since we don't always require an intermediate. + if (container->Extend3DContext() || container->Is3DContextLeaf()) { + return container->GetEffectiveTransform().IsBackfaceVisible(); + } + return container->GetBaseTransform().IsBackfaceVisible(); + } + } + return false; +} + +UniquePtr Layer::RemoveUserData(void* aKey) { + UniquePtr d(static_cast( + mUserData.Remove(static_cast(aKey)))); + return d; +} + +void Layer::SetManager(LayerManager* aManager, HostLayer* aSelf) { + // No one should be calling this for weird reasons. + MOZ_ASSERT(aSelf); + MOZ_ASSERT(aSelf->GetLayer() == this); + mManager = aManager; +} + +void PaintedLayer::PrintInfo(std::stringstream& aStream, const char* aPrefix) { + Layer::PrintInfo(aStream, aPrefix); + nsIntRegion validRegion = GetValidRegion(); + if (!validRegion.IsEmpty()) { + aStream << " [valid=" << validRegion << "]"; + } +} + +void PaintedLayer::DumpPacket(layerscope::LayersPacket* aPacket, + const void* aParent) { + Layer::DumpPacket(aPacket, aParent); + // get this layer data + using namespace layerscope; + LayersPacket::Layer* layer = + aPacket->mutable_layer(aPacket->layer_size() - 1); + layer->set_type(LayersPacket::Layer::PaintedLayer); + nsIntRegion validRegion = GetValidRegion(); + if (!validRegion.IsEmpty()) { + DumpRegion(layer->mutable_valid(), validRegion); + } +} + +void ContainerLayer::PrintInfo(std::stringstream& aStream, + const char* aPrefix) { + Layer::PrintInfo(aStream, aPrefix); + if (UseIntermediateSurface()) { + aStream << " [usesTmpSurf]"; + } + if (1.0 != mPreXScale || 1.0 != mPreYScale) { + aStream + << nsPrintfCString(" [preScale=%g, %g]", mPreXScale, mPreYScale).get(); + } + aStream << nsPrintfCString(" [presShellResolution=%g]", mPresShellResolution) + .get(); +} + +void ContainerLayer::DumpPacket(layerscope::LayersPacket* aPacket, + const void* aParent) { + Layer::DumpPacket(aPacket, aParent); + // Get this layer data + using namespace layerscope; + LayersPacket::Layer* layer = + aPacket->mutable_layer(aPacket->layer_size() - 1); + layer->set_type(LayersPacket::Layer::ContainerLayer); +} + +void ColorLayer::PrintInfo(std::stringstream& aStream, const char* aPrefix) { + Layer::PrintInfo(aStream, aPrefix); + aStream << " [color=" << mColor << "] [bounds=" << mBounds << "]"; +} + +void ColorLayer::DumpPacket(layerscope::LayersPacket* aPacket, + const void* aParent) { + Layer::DumpPacket(aPacket, aParent); + // Get this layer data + using namespace layerscope; + LayersPacket::Layer* layer = + aPacket->mutable_layer(aPacket->layer_size() - 1); + layer->set_type(LayersPacket::Layer::ColorLayer); + layer->set_color(mColor.ToABGR()); +} + +CanvasLayer::CanvasLayer(LayerManager* aManager, void* aImplData) + : Layer(aManager, aImplData), mSamplingFilter(SamplingFilter::GOOD) {} + +CanvasLayer::~CanvasLayer() = default; + +void CanvasLayer::PrintInfo(std::stringstream& aStream, const char* aPrefix) { + Layer::PrintInfo(aStream, aPrefix); + if (mSamplingFilter != SamplingFilter::GOOD) { + aStream << " [filter=" << mSamplingFilter << "]"; + } +} + +// This help function is used to assign the correct enum value +// to the packet +static void DumpFilter(layerscope::LayersPacket::Layer* aLayer, + const SamplingFilter& aSamplingFilter) { + using namespace layerscope; + switch (aSamplingFilter) { + case SamplingFilter::GOOD: + aLayer->set_filter(LayersPacket::Layer::FILTER_GOOD); + break; + case SamplingFilter::LINEAR: + aLayer->set_filter(LayersPacket::Layer::FILTER_LINEAR); + break; + case SamplingFilter::POINT: + aLayer->set_filter(LayersPacket::Layer::FILTER_POINT); + break; + default: + // ignore it + break; + } +} + +void CanvasLayer::DumpPacket(layerscope::LayersPacket* aPacket, + const void* aParent) { + Layer::DumpPacket(aPacket, aParent); + // Get this layer data + using namespace layerscope; + LayersPacket::Layer* layer = + aPacket->mutable_layer(aPacket->layer_size() - 1); + layer->set_type(LayersPacket::Layer::CanvasLayer); + DumpFilter(layer, mSamplingFilter); +} + +RefPtr CanvasLayer::CreateOrGetCanvasRenderer() { + if (!mCanvasRenderer) { + mCanvasRenderer = CreateCanvasRendererInternal(); + } + + return mCanvasRenderer; +} + +void ImageLayer::PrintInfo(std::stringstream& aStream, const char* aPrefix) { + Layer::PrintInfo(aStream, aPrefix); + if (mSamplingFilter != SamplingFilter::GOOD) { + aStream << " [filter=" << mSamplingFilter << "]"; + } +} + +void ImageLayer::DumpPacket(layerscope::LayersPacket* aPacket, + const void* aParent) { + Layer::DumpPacket(aPacket, aParent); + // Get this layer data + using namespace layerscope; + LayersPacket::Layer* layer = + aPacket->mutable_layer(aPacket->layer_size() - 1); + layer->set_type(LayersPacket::Layer::ImageLayer); + DumpFilter(layer, mSamplingFilter); +} + +void RefLayer::PrintInfo(std::stringstream& aStream, const char* aPrefix) { + ContainerLayer::PrintInfo(aStream, aPrefix); + if (mId.IsValid()) { + aStream << " [id=" << mId << "]"; + } + if (mEventRegionsOverride & EventRegionsOverride::ForceDispatchToContent) { + aStream << " [force-dtc]"; + } + if (mEventRegionsOverride & EventRegionsOverride::ForceEmptyHitRegion) { + aStream << " [force-ehr]"; + } +} + +void RefLayer::DumpPacket(layerscope::LayersPacket* aPacket, + const void* aParent) { + Layer::DumpPacket(aPacket, aParent); + // Get this layer data + using namespace layerscope; + LayersPacket::Layer* layer = + aPacket->mutable_layer(aPacket->layer_size() - 1); + layer->set_type(LayersPacket::Layer::RefLayer); + layer->set_refid(uint64_t(mId)); +} + +void ReadbackLayer::PrintInfo(std::stringstream& aStream, const char* aPrefix) { + Layer::PrintInfo(aStream, aPrefix); + aStream << " [size=" << mSize << "]"; + if (mBackgroundLayer) { + aStream << " [backgroundLayer=" + << nsPrintfCString("%p", mBackgroundLayer).get() << "]"; + aStream << " [backgroundOffset=" << mBackgroundLayerOffset << "]"; + } else if (mBackgroundColor.a == 1.f) { + aStream << " [backgroundColor=" << mBackgroundColor << "]"; + } else { + aStream << " [nobackground]"; + } +} + +void ReadbackLayer::DumpPacket(layerscope::LayersPacket* aPacket, + const void* aParent) { + Layer::DumpPacket(aPacket, aParent); + // Get this layer data + using namespace layerscope; + LayersPacket::Layer* layer = + aPacket->mutable_layer(aPacket->layer_size() - 1); + layer->set_type(LayersPacket::Layer::ReadbackLayer); + LayersPacket::Layer::Size* size = layer->mutable_size(); + size->set_w(mSize.width); + size->set_h(mSize.height); +} + +//-------------------------------------------------- +// LayerManager + +void LayerManager::Dump(std::stringstream& aStream, const char* aPrefix, + bool aDumpHtml, bool aSorted) { +#ifdef MOZ_DUMP_PAINTING + if (aDumpHtml) { + aStream << "
    • "; + } +#endif + DumpSelf(aStream, aPrefix, aSorted); + + nsAutoCString pfx(aPrefix); + pfx += " "; + if (!GetRoot()) { + aStream << nsPrintfCString("%s(null)\n", pfx.get()).get(); + if (aDumpHtml) { + aStream << "
    "; + } + return; + } + + if (aDumpHtml) { + aStream << "
      "; + } + GetRoot()->Dump(aStream, pfx.get(), aDumpHtml, aSorted); + if (aDumpHtml) { + aStream << "
    "; + } + aStream << "\n"; +} + +void LayerManager::DumpSelf(std::stringstream& aStream, const char* aPrefix, + bool aSorted) { + PrintInfo(aStream, aPrefix); + aStream << " --- in " + << (aSorted ? "3D-sorted rendering order" : "content order"); + aStream << "\n"; +} + +void LayerManager::Dump(bool aSorted) { + std::stringstream ss; + Dump(ss, "", false, aSorted); + print_stderr(ss); +} + +void LayerManager::Dump(layerscope::LayersPacket* aPacket) { + DumpPacket(aPacket); + + if (GetRoot()) { + GetRoot()->Dump(aPacket, this); + } +} + +void LayerManager::Log(const char* aPrefix) { + if (!IsLogEnabled()) return; + + LogSelf(aPrefix); + + nsAutoCString pfx(aPrefix); + pfx += " "; + if (!GetRoot()) { + MOZ_LAYERS_LOG(("%s(null)", pfx.get())); + return; + } + + GetRoot()->Log(pfx.get()); +} + +void LayerManager::LogSelf(const char* aPrefix) { + nsAutoCString str; + std::stringstream ss; + PrintInfo(ss, aPrefix); + MOZ_LAYERS_LOG(("%s", ss.str().c_str())); +} + +void LayerManager::PrintInfo(std::stringstream& aStream, const char* aPrefix) { + aStream << aPrefix + << nsPrintfCString("%sLayerManager (0x%p)", Name(), this).get(); +} + +void LayerManager::DumpPacket(layerscope::LayersPacket* aPacket) { + using namespace layerscope; + // Add a new layer data (LayerManager) + LayersPacket::Layer* layer = aPacket->add_layer(); + layer->set_type(LayersPacket::Layer::LayerManager); + layer->set_ptr(reinterpret_cast(this)); + // Layer Tree Root + layer->set_parentptr(0); +} + +/*static*/ +bool LayerManager::IsLogEnabled() { + return MOZ_LOG_TEST(GetLog(), LogLevel::Debug); +} + +bool LayerManager::AddPendingScrollUpdateForNextTransaction( + ScrollableLayerGuid::ViewID aScrollId, + const ScrollPositionUpdate& aUpdateInfo) { + Layer* withPendingTransform = DepthFirstSearch( + GetRoot(), [](Layer* aLayer) { return aLayer->HasPendingTransform(); }); + if (withPendingTransform) { + return false; + } + + mPendingScrollUpdates.GetOrInsert(aScrollId).AppendElement(aUpdateInfo); + return true; +} + +Maybe> LayerManager::GetPendingScrollInfoUpdate( + ScrollableLayerGuid::ViewID aScrollId) { + auto p = mPendingScrollUpdates.Lookup(aScrollId); + if (!p) { + return Nothing(); + } + // We could have this function return a CopyableTArray or something, but it + // seems better to avoid implicit copies and just do the one explicit copy + // where we need it, here. + nsTArray copy; + copy.AppendElements(p.Data()); + return Some(std::move(copy)); +} + +std::unordered_set +LayerManager::ClearPendingScrollInfoUpdate() { + std::unordered_set scrollIds; + for (auto it = mPendingScrollUpdates.Iter(); !it.Done(); it.Next()) { + scrollIds.insert(it.Key()); + } + mPendingScrollUpdates.Clear(); + return scrollIds; +} + +void PrintInfo(std::stringstream& aStream, HostLayer* aLayerComposite) { + if (!aLayerComposite) { + return; + } + if (const Maybe& clipRect = + aLayerComposite->GetShadowClipRect()) { + aStream << " [shadow-clip=" << *clipRect << "]"; + } + if (!aLayerComposite->GetShadowBaseTransform().IsIdentity()) { + aStream << " [shadow-transform=" + << aLayerComposite->GetShadowBaseTransform() << "]"; + } + if (!aLayerComposite->GetLayer()->Extend3DContext() && + !aLayerComposite->GetShadowVisibleRegion().IsEmpty()) { + aStream << " [shadow-visible=" << aLayerComposite->GetShadowVisibleRegion() + << "]"; + } +} + +void SetAntialiasingFlags(Layer* aLayer, DrawTarget* aTarget) { + bool permitSubpixelAA = + !(aLayer->GetContentFlags() & Layer::CONTENT_DISABLE_SUBPIXEL_AA); + if (aTarget->IsCurrentGroupOpaque()) { + aTarget->SetPermitSubpixelAA(permitSubpixelAA); + return; + } + + const IntRect& bounds = + aLayer->GetVisibleRegion().GetBounds().ToUnknownRect(); + gfx::Rect transformedBounds = aTarget->GetTransform().TransformBounds( + gfx::Rect(Float(bounds.X()), Float(bounds.Y()), Float(bounds.Width()), + Float(bounds.Height()))); + transformedBounds.RoundOut(); + IntRect intTransformedBounds; + transformedBounds.ToIntRect(&intTransformedBounds); + permitSubpixelAA &= + !(aLayer->GetContentFlags() & Layer::CONTENT_COMPONENT_ALPHA) || + aTarget->GetOpaqueRect().Contains(intTransformedBounds); + aTarget->SetPermitSubpixelAA(permitSubpixelAA); +} + +IntRect ToOutsideIntRect(const gfxRect& aRect) { + return IntRect::RoundOut(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()); +} + +void RecordCompositionPayloadsPresented( + const TimeStamp& aCompositionEndTime, + const nsTArray& aPayloads) { + if (aPayloads.Length()) { + TimeStamp presented = aCompositionEndTime; + for (const CompositionPayload& payload : aPayloads) { +#if MOZ_GECKO_PROFILER + if (profiler_can_accept_markers()) { + MOZ_RELEASE_ASSERT(payload.mType <= kHighestCompositionPayloadType); + nsAutoCString name( + kCompositionPayloadTypeNames[uint8_t(payload.mType)]); + name.AppendLiteral(" Payload Presented"); + // This doesn't really need to be a text marker. Once we have a version + // of profiler_add_marker that accepts both a start time and an end + // time, we could use that here. + nsPrintfCString text( + "Latency: %dms", + int32_t((presented - payload.mTimeStamp).ToMilliseconds())); + PROFILER_MARKER_TEXT( + name, GRAPHICS, + MarkerTiming::Interval(payload.mTimeStamp, presented), text); + } +#endif + + if (payload.mType == CompositionPayloadType::eKeyPress) { + Telemetry::AccumulateTimeDelta( + mozilla::Telemetry::KEYPRESS_PRESENT_LATENCY, payload.mTimeStamp, + presented); + } else if (payload.mType == CompositionPayloadType::eAPZScroll) { + Telemetry::AccumulateTimeDelta( + mozilla::Telemetry::SCROLL_PRESENT_LATENCY, payload.mTimeStamp, + presented); + } + } + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/Layers.h b/gfx/layers/Layers.h new file mode 100644 index 0000000000..f24af7a5ec --- /dev/null +++ b/gfx/layers/Layers.h @@ -0,0 +1,2063 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_LAYERS_H +#define GFX_LAYERS_H + +#include // for uint32_t, uint64_t, int32_t, uint8_t +#include // for memcpy, size_t +#include // for stringstream +#include // for operator new +#include // for unordered_set +#include // for forward, move +#include "FrameMetrics.h" // for ScrollMetadata, FrameMetrics::ViewID, FrameMetrics +#include "Units.h" // for LayerIntRegion, ParentLayerIntRect, LayerIntSize, Lay... +#include "gfxPoint.h" // for gfxPoint +#include "gfxRect.h" // for gfxRect +#include "mozilla/Maybe.h" // for Maybe, Nothing (ptr only) +#include "mozilla/Poison.h" // for CorruptionCanary +#include "mozilla/RefPtr.h" // for RefPtr, operator!= +#include "mozilla/TimeStamp.h" // for TimeStamp +#include "mozilla/UniquePtr.h" // for UniquePtr, MakeUnique +#include "mozilla/gfx/BasePoint.h" // for BasePoint<>::(anonymous union)::(anonymous), BasePoin... +#include "mozilla/gfx/BaseSize.h" // for BaseSize +#include "mozilla/gfx/Matrix.h" // for Matrix4x4, Matrix, Matrix4x4Typed +#include "mozilla/gfx/Point.h" // for Point, PointTyped +#include "mozilla/gfx/Polygon.h" // for Polygon +#include "mozilla/gfx/Rect.h" // for IntRectTyped, IntRect +#include "mozilla/gfx/TiledRegion.h" // for TiledIntRegion +#include "mozilla/gfx/Types.h" // for CompositionOp, DeviceColor, SamplingFilter, SideBits +#include "mozilla/gfx/UserData.h" // for UserData, UserDataKey (ptr only) +#include "mozilla/layers/AnimationInfo.h" // for AnimationInfo +#include "mozilla/layers/LayerAttributes.h" // for SimpleLayerAttributes, ScrollbarData (ptr only) +#include "mozilla/layers/LayerManager.h" // for LayerManager, LayerManager::PaintedLayerCreationHint +#include "mozilla/layers/ScrollableLayerGuid.h" // for ScrollableLayerGuid, ScrollableLayerGuid::ViewID +#include "nsISupports.h" // for NS_INLINE_DECL_REFCOUNTING +#include "nsPoint.h" // for nsIntPoint +#include "nsRect.h" // for nsIntRect +#include "nsRegion.h" // for nsIntRegion +#include "nsStringFlags.h" // for operator& +#include "nsStringFwd.h" // for nsCString, nsACString +#include "nsTArray.h" // for nsTArray, nsTArray_Impl, nsTArray_Impl<>::elem_type + +// XXX These includes could be avoided by moving function implementations to the +// cpp file +#include "gfx2DGlue.h" // for ThebesPoint +#include "mozilla/Assertions.h" // for AssertionConditionType, MOZ_ASSERT, MOZ_A... +#include "mozilla/DebugOnly.h" // for DebugOnly +#include "mozilla/layers/CanvasRenderer.h" // for CanvasRenderer +#include "mozilla/layers/LayersTypes.h" // for MOZ_LAYERS_LOG_IF_SHADOWABLE, LayersId, EventRegionsO... +#include "nsDebug.h" // for NS_ASSERTION, NS_WARNING + +namespace mozilla { + +namespace gfx { +class DataSourceSurface; +class DrawTarget; +class Path; +} // namespace gfx + +namespace layers { + +class Animation; +class AsyncPanZoomController; +class HostLayerManager; +class PaintedLayer; +class ContainerLayer; +class ImageLayer; +class ColorLayer; +class CompositorAnimations; +class CanvasLayer; +class RefLayer; +class HostLayer; +class ShadowableLayer; +class SpecificLayerAttributes; +class Compositor; +class TransformData; +struct LayerPolygon; +struct PropertyAnimationGroup; + +namespace layerscope { +class LayersPacket; +} // namespace layerscope + +#define MOZ_LAYER_DECL_NAME(n, e) \ + const char* Name() const override { return n; } \ + LayerType GetType() const override { return e; } \ + static LayerType Type() { return e; } + +/* + * Motivation: For truly smooth animation and video playback, we need to + * be able to compose frames and render them on a dedicated thread (i.e. + * off the main thread where DOM manipulation, script execution and layout + * induce difficult-to-bound latency). This requires Gecko to construct + * some kind of persistent scene structure (graph or tree) that can be + * safely transmitted across threads. We have other scenarios (e.g. mobile + * browsing) where retaining some rendered data between paints is desired + * for performance, so again we need a retained scene structure. + * + * Our retained scene structure is a layer tree. Each layer represents + * content which can be composited onto a destination surface; the root + * layer is usually composited into a window, and non-root layers are + * composited into their parent layers. Layers have attributes (e.g. + * opacity and clipping) that influence their compositing. + * + * We want to support a variety of layer implementations, including + * a simple "immediate mode" implementation that doesn't retain any + * rendered data between paints (i.e. uses cairo in just the way that + * Gecko used it before layers were introduced). But we also don't want + * to have bifurcated "layers"/"non-layers" rendering paths in Gecko. + * Therefore the layers API is carefully designed to permit maximally + * efficient implementation in an "immediate mode" style. See the + * BasicLayerManager for such an implementation. + */ + +/** + * A Layer represents anything that can be rendered onto a destination + * surface. + */ +class Layer { + NS_INLINE_DECL_REFCOUNTING(Layer) + + using AnimationArray = nsTArray; + + public: + // Keep these in alphabetical order + enum LayerType { + TYPE_CANVAS, + TYPE_COLOR, + TYPE_CONTAINER, + TYPE_DISPLAYITEM, + TYPE_IMAGE, + TYPE_READBACK, + TYPE_REF, + TYPE_SHADOW, + TYPE_PAINTED + }; + + /** + * Returns the LayerManager this Layer belongs to. Note that the layer + * manager might be in a destroyed state, at which point it's only + * valid to set/get user data from it. + */ + LayerManager* Manager() { return mManager; } + + /** + * This should only be called when changing layer managers from HostLayers. + */ + void SetManager(LayerManager* aManager, HostLayer* aSelf); + + enum { + /** + * If this is set, the caller is promising that by the end of this + * transaction the entire visible region (as specified by + * SetVisibleRegion) will be filled with opaque content. + */ + CONTENT_OPAQUE = 0x01, + /** + * If this is set, the caller is notifying that the contents of this layer + * require per-component alpha for optimal fidelity. However, there is no + * guarantee that component alpha will be supported for this layer at + * paint time. + * This should never be set at the same time as CONTENT_OPAQUE. + */ + CONTENT_COMPONENT_ALPHA = 0x02, + + /** + * If this is set then one of the descendant layers of this one has + * CONTENT_COMPONENT_ALPHA set. + */ + CONTENT_COMPONENT_ALPHA_DESCENDANT = 0x04, + + /** + * If this is set then this layer is part of a preserve-3d group, and should + * be sorted with sibling layers that are also part of the same group. + */ + CONTENT_EXTEND_3D_CONTEXT = 0x08, + /** + * This indicates that the transform may be changed on during an empty + * transaction where there is no possibility of redrawing the content, so + * the implementation should be ready for that. + */ + CONTENT_MAY_CHANGE_TRANSFORM = 0x10, + + /** + * Disable subpixel AA for this layer. This is used if the display isn't + * suited for subpixel AA like hidpi or rotated content. + */ + CONTENT_DISABLE_SUBPIXEL_AA = 0x20, + + /** + * If this is set then the layer contains content that may look + * objectionable if not handled as an active layer (such as text with an + * animated transform). This is for internal layout/FrameLayerBuilder usage + * only until flattening code is obsoleted. See bug 633097 + */ + CONTENT_DISABLE_FLATTENING = 0x40, + + /** + * This layer is hidden if the backface of the layer is visible + * to user. + */ + CONTENT_BACKFACE_HIDDEN = 0x80, + + /** + * This layer should be snapped to the pixel grid. + */ + CONTENT_SNAP_TO_GRID = 0x100 + }; + /** + * CONSTRUCTION PHASE ONLY + * This lets layout make some promises about what will be drawn into the + * visible region of the PaintedLayer. This enables internal quality + * and performance optimizations. + */ + void SetContentFlags(uint32_t aFlags) { + NS_ASSERTION((aFlags & (CONTENT_OPAQUE | CONTENT_COMPONENT_ALPHA)) != + (CONTENT_OPAQUE | CONTENT_COMPONENT_ALPHA), + "Can't be opaque and require component alpha"); + if (mSimpleAttrs.SetContentFlags(aFlags)) { + MOZ_LAYERS_LOG_IF_SHADOWABLE(this, + ("Layer::Mutated(%p) ContentFlags", this)); + MutatedSimple(); + } + } + + /** + * CONSTRUCTION PHASE ONLY + * Tell this layer which region will be visible. The visible region + * is a region which contains all the contents of the layer that can + * actually affect the rendering of the window. It can exclude areas + * that are covered by opaque contents of other layers, and it can + * exclude areas where this layer simply contains no content at all. + * (This can be an overapproximation to the "true" visible region.) + * + * There is no general guarantee that drawing outside the bounds of the + * visible region will be ignored. So if a layer draws outside the bounds + * of its visible region, it needs to ensure that what it draws is valid. + */ + virtual void SetVisibleRegion(const LayerIntRegion& aRegion) { + // IsEmpty is required otherwise we get invalidation glitches. + // See bug 1288464 for investigating why. + if (!mVisibleRegion.IsEqual(aRegion) || aRegion.IsEmpty()) { + MOZ_LAYERS_LOG_IF_SHADOWABLE( + this, ("Layer::Mutated(%p) VisibleRegion was %s is %s", this, + mVisibleRegion.ToString().get(), aRegion.ToString().get())); + mVisibleRegion = aRegion; + Mutated(); + } + } + + /** + * CONSTRUCTION PHASE ONLY + * Set the (sub)document metrics used to render the Layer subtree + * rooted at this. Note that a layer may have multiple FrameMetrics + * objects; calling this function will remove all of them and replace + * them with the provided FrameMetrics. See the documentation for + * SetFrameMetrics(const nsTArray&) for more details. + */ + void SetScrollMetadata(const ScrollMetadata& aScrollMetadata) { + Manager()->ClearPendingScrollInfoUpdate(); + if (mScrollMetadata.Length() != 1 || + mScrollMetadata[0] != aScrollMetadata) { + MOZ_LAYERS_LOG_IF_SHADOWABLE(this, + ("Layer::Mutated(%p) ScrollMetadata", this)); + mScrollMetadata.ReplaceElementsAt(0, mScrollMetadata.Length(), + aScrollMetadata); + ScrollMetadataChanged(); + Mutated(); + } + } + + /** + * CONSTRUCTION PHASE ONLY + * Set the (sub)document metrics used to render the Layer subtree + * rooted at this. There might be multiple metrics on this layer + * because the layer may, for example, be contained inside multiple + * nested scrolling subdocuments. In general a Layer having multiple + * ScrollMetadata objects is conceptually equivalent to having a stack + * of ContainerLayers that have been flattened into this Layer. + * See the documentation in LayerMetricsWrapper.h for a more detailed + * explanation of this conceptual equivalence. + * + * Note also that there is actually a many-to-many relationship between + * Layers and ScrollMetadata, because multiple Layers may have identical + * ScrollMetadata objects. This happens when those layers belong to the + * same scrolling subdocument and therefore end up with the same async + * transform when they are scrolled by the APZ code. + */ + void SetScrollMetadata(const nsTArray& aMetadataArray) { + Manager()->ClearPendingScrollInfoUpdate(); + if (mScrollMetadata != aMetadataArray) { + MOZ_LAYERS_LOG_IF_SHADOWABLE(this, + ("Layer::Mutated(%p) ScrollMetadata", this)); + mScrollMetadata = aMetadataArray.Clone(); + ScrollMetadataChanged(); + Mutated(); + } + } + + /* + * Compositor event handling + * ========================= + * When a touch-start event (or similar) is sent to the + * AsyncPanZoomController, it needs to decide whether the event should be sent + * to the main thread. Each layer has a list of event handling regions. When + * the compositor needs to determine how to handle a touch event, it scans the + * layer tree from top to bottom in z-order (traversing children before their + * parents). Points outside the clip region for a layer cause that layer (and + * its subtree) to be ignored. If a layer has a mask layer, and that mask + * layer's alpha value is zero at the event point, then the layer and its + * subtree should be ignored. For each layer, if the point is outside its hit + * region, we ignore the layer and move onto the next. If the point is inside + * its hit region but outside the dispatch-to-content region, we can initiate + * a gesture without consulting the content thread. Otherwise we must dispatch + * the event to content. Note that if a layer or any ancestor layer has a + * ForceEmptyHitRegion override in GetEventRegionsOverride() then the + * hit-region must be treated as empty. Similarly, if there is a + * ForceDispatchToContent override then the dispatch-to-content region must be + * treated as encompassing the entire hit region, and therefore we must + * consult the content thread before initiating a gesture. (If both flags are + * set, ForceEmptyHitRegion takes priority.) + */ + /** + * CONSTRUCTION PHASE ONLY + * Set the event handling region. + */ + void SetEventRegions(const EventRegions& aRegions); + + /** + * CONSTRUCTION PHASE ONLY + * Set the opacity which will be applied to this layer as it + * is composited to the destination. + */ + void SetOpacity(float aOpacity) { + if (mSimpleAttrs.SetOpacity(aOpacity)) { + MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) Opacity", this)); + MutatedSimple(); + } + } + + void SetMixBlendMode(gfx::CompositionOp aMixBlendMode) { + if (mSimpleAttrs.SetMixBlendMode(aMixBlendMode)) { + MOZ_LAYERS_LOG_IF_SHADOWABLE(this, + ("Layer::Mutated(%p) MixBlendMode", this)); + MutatedSimple(); + } + } + + void SetForceIsolatedGroup(bool aForceIsolatedGroup) { + if (mSimpleAttrs.SetForceIsolatedGroup(aForceIsolatedGroup)) { + MOZ_LAYERS_LOG_IF_SHADOWABLE( + this, ("Layer::Mutated(%p) ForceIsolatedGroup", this)); + MutatedSimple(); + } + } + + bool GetForceIsolatedGroup() const { + return mSimpleAttrs.GetForceIsolatedGroup(); + } + + /** + * CONSTRUCTION PHASE ONLY + * Set a clip rect which will be applied to this layer as it is + * composited to the destination. The coordinates are relative to + * the parent layer (i.e. the contents of this layer + * are transformed before this clip rect is applied). + * For the root layer, the coordinates are relative to the widget, + * in device pixels. + * If aRect is null no clipping will be performed. + */ + void SetClipRect(const Maybe& aRect) { + if (mClipRect) { + if (!aRect) { + MOZ_LAYERS_LOG_IF_SHADOWABLE( + this, ("Layer::Mutated(%p) ClipRect was %d,%d,%d,%d is ", + this, mClipRect->X(), mClipRect->Y(), mClipRect->Width(), + mClipRect->Height())); + mClipRect.reset(); + Mutated(); + } else { + if (!aRect->IsEqualEdges(*mClipRect)) { + MOZ_LAYERS_LOG_IF_SHADOWABLE( + this, + ("Layer::Mutated(%p) ClipRect was %d,%d,%d,%d is %d,%d,%d,%d", + this, mClipRect->X(), mClipRect->Y(), mClipRect->Width(), + mClipRect->Height(), aRect->X(), aRect->Y(), aRect->Width(), + aRect->Height())); + mClipRect = aRect; + Mutated(); + } + } + } else { + if (aRect) { + MOZ_LAYERS_LOG_IF_SHADOWABLE( + this, + ("Layer::Mutated(%p) ClipRect was is %d,%d,%d,%d", this, + aRect->X(), aRect->Y(), aRect->Width(), aRect->Height())); + mClipRect = aRect; + Mutated(); + } + } + } + + /** + * CONSTRUCTION PHASE ONLY + * Set an optional scrolled clip on the layer. + * The scrolled clip, if present, consists of a clip rect and an optional + * mask. This scrolled clip is always scrolled by all scroll frames associated + * with this layer. (By contrast, the scroll clips stored in ScrollMetadata + * are only scrolled by scroll frames above that ScrollMetadata, and the + * layer's mClipRect is always fixed to the layer contents (which may or may + * not be scrolled by some of the scroll frames associated with the layer, + * depending on whether the layer is fixed).) + */ + void SetScrolledClip(const Maybe& aScrolledClip) { + if (mSimpleAttrs.SetScrolledClip(aScrolledClip)) { + MOZ_LAYERS_LOG_IF_SHADOWABLE(this, + ("Layer::Mutated(%p) ScrolledClip", this)); + MutatedSimple(); + } + } + + /** + * CONSTRUCTION PHASE ONLY + * Set a layer to mask this layer. + * + * The mask layer should be applied using its effective transform (after it + * is calculated by ComputeEffectiveTransformForMaskLayer), this should use + * this layer's parent's transform and the mask layer's transform, but not + * this layer's. That is, the mask layer is specified relative to this layer's + * position in it's parent layer's coord space. + * Currently, only 2D translations are supported for the mask layer transform. + * + * Ownership of aMaskLayer passes to this. + * Typical use would be an ImageLayer with an alpha image used for masking. + * See also ContainerState::BuildMaskLayer in FrameLayerBuilder.cpp. + */ + void SetMaskLayer(Layer* aMaskLayer) { +#ifdef DEBUG + if (aMaskLayer) { + bool maskIs2D = aMaskLayer->GetTransform().CanDraw2D(); + NS_ASSERTION(maskIs2D, "Mask layer has invalid transform."); + } +#endif + + if (mMaskLayer != aMaskLayer) { + MOZ_LAYERS_LOG_IF_SHADOWABLE(this, + ("Layer::Mutated(%p) MaskLayer", this)); + mMaskLayer = aMaskLayer; + Mutated(); + } + } + + /** + * CONSTRUCTION PHASE ONLY + * Add mask layers associated with LayerClips. + */ + void SetAncestorMaskLayers(const nsTArray>& aLayers) { + if (aLayers != mAncestorMaskLayers) { + MOZ_LAYERS_LOG_IF_SHADOWABLE( + this, ("Layer::Mutated(%p) AncestorMaskLayers", this)); + mAncestorMaskLayers = aLayers.Clone(); + Mutated(); + } + } + + /** + * CONSTRUCTION PHASE ONLY + * Add a mask layer associated with a LayerClip. + */ + void AddAncestorMaskLayer(const RefPtr& aLayer) { + mAncestorMaskLayers.AppendElement(aLayer); + Mutated(); + } + + /** + * CONSTRUCTION PHASE ONLY + * Tell this layer what its transform should be. The transformation + * is applied when compositing the layer into its parent container. + */ + void SetBaseTransform(const gfx::Matrix4x4& aMatrix) { + NS_ASSERTION(!aMatrix.IsSingular(), + "Shouldn't be trying to draw with a singular matrix!"); + mPendingTransform = nullptr; + if (!mSimpleAttrs.SetTransform(aMatrix)) { + return; + } + MOZ_LAYERS_LOG_IF_SHADOWABLE(this, + ("Layer::Mutated(%p) BaseTransform", this)); + MutatedSimple(); + } + + /** + * Can be called at any time. + * + * Like SetBaseTransform(), but can be called before the next + * transform (i.e. outside an open transaction). Semantically, this + * method enqueues a new transform value to be set immediately after + * the next transaction is opened. + */ + void SetBaseTransformForNextTransaction(const gfx::Matrix4x4& aMatrix) { + mPendingTransform = mozilla::MakeUnique(aMatrix); + } + + void SetPostScale(float aXScale, float aYScale) { + if (!mSimpleAttrs.SetPostScale(aXScale, aYScale)) { + return; + } + MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) PostScale", this)); + MutatedSimple(); + } + + /** + * CONSTRUCTION PHASE ONLY + * A layer is "fixed position" when it draws content from a content + * (not chrome) document, the topmost content document has a root scrollframe + * with a displayport, but the layer does not move when that displayport + * scrolls. + */ + void SetIsFixedPosition(bool aFixedPosition) { + if (mSimpleAttrs.SetIsFixedPosition(aFixedPosition)) { + MOZ_LAYERS_LOG_IF_SHADOWABLE( + this, ("Layer::Mutated(%p) IsFixedPosition", this)); + MutatedSimple(); + } + } + + void SetIsAsyncZoomContainer(const Maybe& aViewId) { + if (mSimpleAttrs.SetIsAsyncZoomContainer(aViewId)) { + MOZ_LAYERS_LOG_IF_SHADOWABLE( + this, ("Layer::Mutated(%p) IsAsyncZoomContainer", this)); + MutatedSimple(); + } + } + + /** + * CONSTRUCTION PHASE ONLY + * This flag is true when the transform on the layer is a perspective + * transform. The compositor treats perspective transforms specially + * for async scrolling purposes. + */ + void SetTransformIsPerspective(bool aTransformIsPerspective) { + if (mSimpleAttrs.SetTransformIsPerspective(aTransformIsPerspective)) { + MOZ_LAYERS_LOG_IF_SHADOWABLE( + this, ("Layer::Mutated(%p) TransformIsPerspective", this)); + MutatedSimple(); + } + } + // This is only called when the layer tree is updated. Do not call this from + // layout code. To add an animation to this layer, use AddAnimation. + void SetCompositorAnimations( + const LayersId& aLayersId, + const CompositorAnimations& aCompositorAnimations); + // Go through all animations in this layer and its children and, for + // any animations with a null start time, update their start time such + // that at |aReadyTime| the animation's current time corresponds to its + // 'initial current time' value. + void StartPendingAnimations(const TimeStamp& aReadyTime); + + void ClearCompositorAnimations(); + + /** + * CONSTRUCTION PHASE ONLY + * If a layer represents a fixed position element, this data is stored on the + * layer for use by the compositor. + * + * - |aScrollId| identifies the scroll frame that this element is fixed + * with respect to. + * + * - |aAnchor| is the point on the layer that is considered the "anchor" + * point, that is, the point which remains in the same position when + * compositing the layer tree with a transformation (such as when + * asynchronously scrolling and zooming). + * + * - |aSides| is the set of sides to which the element is fixed relative to. + * This is used if the viewport size is changed in the compositor and + * fixed position items need to shift accordingly. This value is made up + * combining appropriate values from mozilla::SideBits. + */ + void SetFixedPositionData(ScrollableLayerGuid::ViewID aScrollId, + const LayerPoint& aAnchor, SideBits aSides) { + if (mSimpleAttrs.SetFixedPositionData(aScrollId, aAnchor, aSides)) { + MOZ_LAYERS_LOG_IF_SHADOWABLE( + this, ("Layer::Mutated(%p) FixedPositionData", this)); + MutatedSimple(); + } + } + + /** + * CONSTRUCTION PHASE ONLY + * If a layer is "sticky position", |aScrollId| holds the scroll identifier + * of the scrollable content that contains it. The difference between the two + * rectangles |aOuter| and |aInner| is treated as two intervals in each + * dimension, with the current scroll position at the origin. For each + * dimension, while that component of the scroll position lies within either + * interval, the layer should not move relative to its scrolling container. + */ + void SetStickyPositionData(ScrollableLayerGuid::ViewID aScrollId, + LayerRectAbsolute aOuter, + LayerRectAbsolute aInner) { + if (mSimpleAttrs.SetStickyPositionData(aScrollId, aOuter, aInner)) { + MOZ_LAYERS_LOG_IF_SHADOWABLE( + this, ("Layer::Mutated(%p) StickyPositionData", this)); + MutatedSimple(); + } + } + + /** + * CONSTRUCTION PHASE ONLY + * If a layer is a scroll thumb container layer or a scrollbar container + * layer, set the scroll identifier of the scroll frame scrolled by + * the scrollbar, and other data related to the scrollbar. + */ + void SetScrollbarData(const ScrollbarData& aThumbData) { + if (mSimpleAttrs.SetScrollbarData(aThumbData)) { + MOZ_LAYERS_LOG_IF_SHADOWABLE(this, + ("Layer::Mutated(%p) ScrollbarData", this)); + MutatedSimple(); + } + } + + // Used when forwarding transactions. Do not use at any other time. + void SetSimpleAttributes(const SimpleLayerAttributes& aAttrs) { + mSimpleAttrs = aAttrs; + } + const SimpleLayerAttributes& GetSimpleAttributes() const { + return mSimpleAttrs; + } + + // These getters can be used anytime. + float GetOpacity() { return mSimpleAttrs.GetOpacity(); } + gfx::CompositionOp GetMixBlendMode() const { + return mSimpleAttrs.GetMixBlendMode(); + } + const Maybe& GetClipRect() const { return mClipRect; } + const Maybe& GetScrolledClip() const { + return mSimpleAttrs.GetScrolledClip(); + } + Maybe GetScrolledClipRect() const; + uint32_t GetContentFlags() { return mSimpleAttrs.GetContentFlags(); } + const LayerIntRegion& GetVisibleRegion() const { return mVisibleRegion; } + const ScrollMetadata& GetScrollMetadata(uint32_t aIndex) const; + const FrameMetrics& GetFrameMetrics(uint32_t aIndex) const; + uint32_t GetScrollMetadataCount() const { return mScrollMetadata.Length(); } + const nsTArray& GetAllScrollMetadata() { + return mScrollMetadata; + } + bool HasScrollableFrameMetrics() const; + bool IsScrollableWithoutContent() const; + const EventRegions& GetEventRegions() const { return mEventRegions; } + ContainerLayer* GetParent() const { return mParent; } + Layer* GetNextSibling() { + if (mNextSibling) { + mNextSibling->CheckCanary(); + } + return mNextSibling; + } + const Layer* GetNextSibling() const { + if (mNextSibling) { + mNextSibling->CheckCanary(); + } + return mNextSibling; + } + Layer* GetPrevSibling() { return mPrevSibling; } + const Layer* GetPrevSibling() const { return mPrevSibling; } + virtual Layer* GetFirstChild() const { return nullptr; } + virtual Layer* GetLastChild() const { return nullptr; } + gfx::Matrix4x4 GetTransform() const; + // Same as GetTransform(), but returns the transform as a strongly-typed + // matrix. Eventually this will replace GetTransform(). + const CSSTransformMatrix GetTransformTyped() const; + const gfx::Matrix4x4& GetBaseTransform() const { + return mSimpleAttrs.GetTransform(); + } + // Note: these are virtual because ContainerLayerComposite overrides them. + virtual float GetPostXScale() const { return mSimpleAttrs.GetPostXScale(); } + virtual float GetPostYScale() const { return mSimpleAttrs.GetPostYScale(); } + bool GetIsFixedPosition() { return mSimpleAttrs.IsFixedPosition(); } + Maybe IsAsyncZoomContainer() { + return mSimpleAttrs.IsAsyncZoomContainer(); + } + bool GetTransformIsPerspective() const { + return mSimpleAttrs.GetTransformIsPerspective(); + } + bool GetIsStickyPosition() { return mSimpleAttrs.IsStickyPosition(); } + ScrollableLayerGuid::ViewID GetFixedPositionScrollContainerId() { + return mSimpleAttrs.GetFixedPositionScrollContainerId(); + } + LayerPoint GetFixedPositionAnchor() { + return mSimpleAttrs.GetFixedPositionAnchor(); + } + SideBits GetFixedPositionSides() { + return mSimpleAttrs.GetFixedPositionSides(); + } + ScrollableLayerGuid::ViewID GetStickyScrollContainerId() { + return mSimpleAttrs.GetStickyScrollContainerId(); + } + const LayerRectAbsolute& GetStickyScrollRangeOuter() { + return mSimpleAttrs.GetStickyScrollRangeOuter(); + } + const LayerRectAbsolute& GetStickyScrollRangeInner() { + return mSimpleAttrs.GetStickyScrollRangeInner(); + } + const ScrollbarData& GetScrollbarData() const { + return mSimpleAttrs.GetScrollbarData(); + } + bool IsScrollbarContainer() const; + Layer* GetMaskLayer() const { return mMaskLayer; } + bool HasPendingTransform() const { return !!mPendingTransform; } + + void CheckCanary() const { mCanary.Check(); } + + // Ancestor mask layers are associated with FrameMetrics, but for simplicity + // in maintaining the layer tree structure we attach them to the layer. + size_t GetAncestorMaskLayerCount() const { + return mAncestorMaskLayers.Length(); + } + Layer* GetAncestorMaskLayerAt(size_t aIndex) const { + return mAncestorMaskLayers.ElementAt(aIndex); + } + const nsTArray>& GetAllAncestorMaskLayers() const { + return mAncestorMaskLayers; + } + + bool HasMaskLayers() const { + return GetMaskLayer() || mAncestorMaskLayers.Length() > 0; + } + + /* + * Get the combined clip rect of the Layer clip and all clips on FrameMetrics. + * This is intended for use in Layout. The compositor needs to apply async + * transforms to find the combined clip. + */ + Maybe GetCombinedClipRect() const; + + /** + * Retrieve the root level visible region for |this| taking into account + * clipping applied to parent layers of |this| as well as subtracting + * visible regions of higher siblings of this layer and each ancestor. + * + * Note translation values for offsets of visible regions and accumulated + * aLayerOffset are integer rounded using IntPoint::Round. + * + * @param aResult - the resulting visible region of this layer. + * @param aLayerOffset - this layer's total offset from the root layer. + * @return - false if during layer tree traversal a parent or sibling + * transform is found to be non-translational. This method returns early + * in this case, results will not be valid. Returns true on successful + * traversal. + */ + bool GetVisibleRegionRelativeToRootLayer(nsIntRegion& aResult, + nsIntPoint* aLayerOffset); + + // Note that all lengths in animation data are either in CSS pixels or app + // units and must be converted to device pixels by the compositor. + // Besides, this should only be called on the compositor thread. + AnimationArray& GetAnimations() { return mAnimationInfo.GetAnimations(); } + uint64_t GetCompositorAnimationsId() { + return mAnimationInfo.GetCompositorAnimationsId(); + } + nsTArray& GetPropertyAnimationGroups() { + return mAnimationInfo.GetPropertyAnimationGroups(); + } + const Maybe& GetTransformData() const { + return mAnimationInfo.GetTransformData(); + } + const LayersId& GetAnimationLayersId() const { + return mAnimationInfo.GetLayersId(); + } + + Maybe GetAnimationGeneration() const { + return mAnimationInfo.GetAnimationGeneration(); + } + + gfx::Path* CachedMotionPath() { return mAnimationInfo.CachedMotionPath(); } + + bool HasTransformAnimation() const; + + /** + * Returns the local transform for this layer: either mTransform or, + * for shadow layers, GetShadowBaseTransform(), in either case with the + * pre- and post-scales applied. + */ + gfx::Matrix4x4 GetLocalTransform(); + + /** + * Same as GetLocalTransform(), but returns a strongly-typed matrix. + * Eventually, this will replace GetLocalTransform(). + */ + const LayerToParentLayerMatrix4x4 GetLocalTransformTyped(); + + /** + * Returns the local opacity for this layer: either mOpacity or, + * for shadow layers, GetShadowOpacity() + */ + float GetLocalOpacity(); + + /** + * DRAWING PHASE ONLY + * + * Apply pending changes to layers before drawing them, if those + * pending changes haven't been overridden by later changes. + * + * Returns a list of scroll ids which had pending updates. + */ + std::unordered_set + ApplyPendingUpdatesToSubtree(); + + /** + * DRAWING PHASE ONLY + * + * Write layer-subtype-specific attributes into aAttrs. Used to + * synchronize layer attributes to their shadows'. + */ + virtual void FillSpecificAttributes(SpecificLayerAttributes& aAttrs) {} + + // Returns true if it's OK to save the contents of aLayer in an + // opaque surface (a surface without an alpha channel). + // If we can use a surface without an alpha channel, we should, because + // it will often make painting of antialiased text faster and higher + // quality. + bool CanUseOpaqueSurface(); + + SurfaceMode GetSurfaceMode() { + if (CanUseOpaqueSurface()) return SurfaceMode::SURFACE_OPAQUE; + if (GetContentFlags() & CONTENT_COMPONENT_ALPHA) + return SurfaceMode::SURFACE_COMPONENT_ALPHA; + return SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA; + } + + // Returns true if this layer can be treated as opaque for visibility + // computation. A layer may be non-opaque for visibility even if it + // is not transparent, for example, if it has a mix-blend-mode. + bool IsOpaqueForVisibility(); + + /** + * This setter can be used anytime. The user data for all keys is + * initially null. Ownership pases to the layer manager. + */ + void SetUserData( + void* aKey, LayerUserData* aData, + void (*aDestroy)(void*) = LayerManager::LayerUserDataDestroy) { + mUserData.Add(static_cast(aKey), aData, aDestroy); + } + /** + * This can be used anytime. Ownership passes to the caller! + */ + UniquePtr RemoveUserData(void* aKey); + /** + * This getter can be used anytime. + */ + bool HasUserData(void* aKey) { + return mUserData.Has(static_cast(aKey)); + } + /** + * This getter can be used anytime. Ownership is retained by the layer + * manager. + */ + LayerUserData* GetUserData(void* aKey) const { + return static_cast( + mUserData.Get(static_cast(aKey))); + } + + /** + * |Disconnect()| is used by layers hooked up over IPC. It may be + * called at any time, and may not be called at all. Using an + * IPC-enabled layer after Destroy() (drawing etc.) results in a + * safe no-op; no crashy or uaf etc. + * + * XXX: this interface is essentially LayerManager::Destroy, but at + * Layer granularity. It might be beneficial to unify them. + */ + virtual void Disconnect() {} + + /** + * Dynamic downcast to a PaintedLayer. Returns null if this is not + * a PaintedLayer. + */ + virtual PaintedLayer* AsPaintedLayer() { return nullptr; } + + /** + * Dynamic cast to a ContainerLayer. Returns null if this is not + * a ContainerLayer. + */ + virtual ContainerLayer* AsContainerLayer() { return nullptr; } + virtual const ContainerLayer* AsContainerLayer() const { return nullptr; } + + /** + * Dynamic cast to a RefLayer. Returns null if this is not a + * RefLayer. + */ + virtual RefLayer* AsRefLayer() { return nullptr; } + + /** + * Dynamic cast to a Color. Returns null if this is not a + * ColorLayer. + */ + virtual ColorLayer* AsColorLayer() { return nullptr; } + + /** + * Dynamic cast to a Canvas. Returns null if this is not a + * ColorLayer. + */ + virtual CanvasLayer* AsCanvasLayer() { return nullptr; } + + /** + * Dynamic cast to an Image. Returns null if this is not a + * ColorLayer. + */ + virtual ImageLayer* AsImageLayer() { return nullptr; } + + /** + * Dynamic cast to a LayerComposite. Return null if this is not a + * LayerComposite. Can be used anytime. + */ + virtual HostLayer* AsHostLayer() { return nullptr; } + + /** + * Dynamic cast to a ShadowableLayer. Return null if this is not a + * ShadowableLayer. Can be used anytime. + */ + virtual ShadowableLayer* AsShadowableLayer() { return nullptr; } + + // These getters can be used anytime. They return the effective + // values that should be used when drawing this layer to screen, + // accounting for this layer possibly being a shadow. + const Maybe& GetLocalClipRect(); + const LayerIntRegion& GetLocalVisibleRegion(); + + bool Extend3DContext() { + return GetContentFlags() & CONTENT_EXTEND_3D_CONTEXT; + } + bool Combines3DTransformWithAncestors() { + return GetParent() && + reinterpret_cast(GetParent())->Extend3DContext(); + } + bool Is3DContextLeaf() { + return !Extend3DContext() && Combines3DTransformWithAncestors(); + } + /** + * It is true if the user can see the back of the layer and the + * backface is hidden. The compositor should skip the layer if the + * result is true. + */ + bool IsBackfaceHidden(); + bool IsVisible() { + // For containers extending 3D context, visible region + // is meaningless, since they are just intermediate result of + // content. + return !GetLocalVisibleRegion().IsEmpty() || Extend3DContext(); + } + + /** + * Return true if current layer content is opaque. + * It does not guarantee that layer content is always opaque. + */ + virtual bool IsOpaque() { return GetContentFlags() & CONTENT_OPAQUE; } + + /** + * Returns the product of the opacities of this layer and all ancestors up + * to and excluding the nearest ancestor that has UseIntermediateSurface() + * set. + */ + float GetEffectiveOpacity(); + + /** + * Returns the blendmode of this layer. + */ + gfx::CompositionOp GetEffectiveMixBlendMode(); + + /** + * This returns the effective transform computed by + * ComputeEffectiveTransforms. Typically this is a transform that transforms + * this layer all the way to some intermediate surface or destination + * surface. For non-BasicLayers this will be a transform to the nearest + * ancestor with UseIntermediateSurface() (or to the root, if there is no + * such ancestor), but for BasicLayers it's different. + */ + const gfx::Matrix4x4& GetEffectiveTransform() const { + return mEffectiveTransform; + } + + /** + * This returns the effective transform for Layer's buffer computed by + * ComputeEffectiveTransforms. Typically this is a transform that transforms + * this layer's buffer all the way to some intermediate surface or destination + * surface. For non-BasicLayers this will be a transform to the nearest + * ancestor with UseIntermediateSurface() (or to the root, if there is no + * such ancestor), but for BasicLayers it's different. + * + * By default, its value is same to GetEffectiveTransform(). + * When ImageLayer is rendered with ScaleMode::STRETCH, + * it becomes different from GetEffectiveTransform(). + */ + virtual const gfx::Matrix4x4& GetEffectiveTransformForBuffer() const { + return mEffectiveTransform; + } + + /** + * If the current layers participates in a preserve-3d + * context (returns true for Combines3DTransformWithAncestors), + * returns the combined transform up to the preserve-3d (nearest + * ancestor that doesn't Extend3DContext()). Otherwise returns + * the local transform. + */ + gfx::Matrix4x4 ComputeTransformToPreserve3DRoot(); + + /** + * @param aTransformToSurface the composition of the transforms + * from the parent layer (if any) to the destination pixel grid. + * + * Computes mEffectiveTransform for this layer and all its descendants. + * mEffectiveTransform transforms this layer up to the destination + * pixel grid (whatever aTransformToSurface is relative to). + * + * We promise that when this is called on a layer, all ancestor layers + * have already had ComputeEffectiveTransforms called. + */ + virtual void ComputeEffectiveTransforms( + const gfx::Matrix4x4& aTransformToSurface) = 0; + + /** + * Computes the effective transform for mask layers, if this layer has any. + */ + void ComputeEffectiveTransformForMaskLayers( + const gfx::Matrix4x4& aTransformToSurface); + static void ComputeEffectiveTransformForMaskLayer( + Layer* aMaskLayer, const gfx::Matrix4x4& aTransformToSurface); + + /** + * Calculate the scissor rect required when rendering this layer. + * Returns a rectangle relative to the intermediate surface belonging to the + * nearest ancestor that has an intermediate surface, or relative to the root + * viewport if no ancestor has an intermediate surface, corresponding to the + * clip rect for this layer intersected with aCurrentScissorRect. + */ + RenderTargetIntRect CalculateScissorRect( + const RenderTargetIntRect& aCurrentScissorRect); + + virtual const char* Name() const = 0; + virtual LayerType GetType() const = 0; + + /** + * Only the implementation should call this. This is per-implementation + * private data. Normally, all layers with a given layer manager + * use the same type of ImplData. + */ + void* ImplData() { return mImplData; } + + /** + * Only the implementation should use these methods. + */ + void SetParent(ContainerLayer* aParent) { mParent = aParent; } + void SetNextSibling(Layer* aSibling) { mNextSibling = aSibling; } + void SetPrevSibling(Layer* aSibling) { mPrevSibling = aSibling; } + + /** + * Dump information about this layer manager and its managed tree to + * aStream. + */ + void Dump(std::stringstream& aStream, const char* aPrefix = "", + bool aDumpHtml = false, bool aSorted = false, + const Maybe& aGeometry = Nothing()); + /** + * Dump information about just this layer manager itself to aStream. + */ + void DumpSelf(std::stringstream& aStream, const char* aPrefix = "", + const Maybe& aGeometry = Nothing()); + + /** + * Dump information about this layer and its child & sibling layers to + * layerscope packet. + */ + void Dump(layerscope::LayersPacket* aPacket, const void* aParent); + + /** + * Log information about this layer manager and its managed tree to + * the NSPR log (if enabled for "Layers"). + */ + void Log(const char* aPrefix = ""); + /** + * Log information about just this layer manager itself to the NSPR + * log (if enabled for "Layers"). + */ + void LogSelf(const char* aPrefix = ""); + + // Print interesting information about this into aStream. Internally + // used to implement Dump*() and Log*(). If subclasses have + // additional interesting properties, they should override this with + // an implementation that first calls the base implementation then + // appends additional info to aTo. + virtual void PrintInfo(std::stringstream& aStream, const char* aPrefix); + + // Just like PrintInfo, but this function dump information into layerscope + // packet, instead of a StringStream. It is also internally used to implement + // Dump(); + virtual void DumpPacket(layerscope::LayersPacket* aPacket, + const void* aParent); + + /** + * Store display list log. + */ + void SetDisplayListLog(const char* log); + + /** + * Return display list log. + */ + void GetDisplayListLog(nsCString& log); + + static bool IsLogEnabled() { return LayerManager::IsLogEnabled(); } + + /** + * Returns the current area of the layer (in layer-space coordinates) + * marked as needed to be recomposited. + */ + const virtual gfx::TiledIntRegion& GetInvalidRegion() { + return mInvalidRegion; + } + void AddInvalidRegion(const nsIntRegion& aRegion) { + mInvalidRegion.Add(aRegion); + } + + /** + * Mark the entirety of the layer's visible region as being invalid. + */ + void SetInvalidRectToVisibleRegion() { + mInvalidRegion.SetEmpty(); + mInvalidRegion.Add(GetVisibleRegion().ToUnknownRegion()); + } + + /** + * Adds to the current invalid rect. + */ + void AddInvalidRect(const gfx::IntRect& aRect) { mInvalidRegion.Add(aRect); } + + /** + * Clear the invalid rect, marking the layer as being identical to what is + * currently composited. + */ + virtual void ClearInvalidRegion() { mInvalidRegion.SetEmpty(); } + + // These functions allow attaching an AsyncPanZoomController to this layer, + // and can be used anytime. + // A layer has an APZC at index aIndex only-if + // GetFrameMetrics(aIndex).IsScrollable(); attempting to get an APZC for a + // non-scrollable metrics will return null. The reverse is also generally true + // (that if GetFrameMetrics(aIndex).IsScrollable() is true, then the layer + // will have an APZC). However, it only holds on the the compositor-side layer + // tree, and only after the APZ code has had a chance to rebuild its internal + // hit-testing tree using the layer tree. Also, it may not hold in certain + // "exceptional" scenarios such as if the layer tree doesn't have a + // GeckoContentController registered for it, or if there is a malicious + // content process trying to trip up the compositor over IPC. The aIndex for + // these functions must be less than GetScrollMetadataCount(). + void SetAsyncPanZoomController(uint32_t aIndex, + AsyncPanZoomController* controller); + AsyncPanZoomController* GetAsyncPanZoomController(uint32_t aIndex) const; + // The ScrollMetadataChanged function is used internally to ensure the APZC + // array length matches the frame metrics array length. + + virtual void ClearCachedResources() {} + + virtual bool SupportsAsyncUpdate() { return false; } + + private: + void ScrollMetadataChanged(); + + public: + void ApplyPendingUpdatesForThisTransaction(); + +#ifdef DEBUG + void SetDebugColorIndex(uint32_t aIndex) { mDebugColorIndex = aIndex; } + uint32_t GetDebugColorIndex() { return mDebugColorIndex; } +#endif + + void Mutated() { mManager->Mutated(this); } + void MutatedSimple() { mManager->MutatedSimple(this); } + + virtual int32_t GetMaxLayerSize() { return Manager()->GetMaxTextureSize(); } + + /** + * Returns true if this layer's effective transform is not just + * a translation by integers, or if this layer or some ancestor layer + * is marked as having a transform that may change without a full layer + * transaction. + * + * Note: This function ignores ancestor layers across layer tree boundaries + * so that it returns a consistent value when compositing and when painting. + */ + bool MayResample(); + + RenderTargetRect TransformRectToRenderTarget(const LayerIntRect& aRect); + + /** + * Add debugging information to the layer dump. + */ + void AddExtraDumpInfo(const nsACString& aStr) { +#ifdef MOZ_DUMP_PAINTING + mExtraDumpInfo.AppendElement(aStr); +#endif + } + + /** + * Clear debugging information. Useful for recycling. + */ + void ClearExtraDumpInfo() { +#ifdef MOZ_DUMP_PAINTING + mExtraDumpInfo.Clear(); +#endif + } + + AnimationInfo& GetAnimationInfo() { return mAnimationInfo; } + + protected: + Layer(LayerManager* aManager, void* aImplData); + + // Protected destructor, to discourage deletion outside of Release(): + virtual ~Layer(); + + /** + * We can snap layer transforms for two reasons: + * 1) To avoid unnecessary resampling when a transform is a translation + * by a non-integer number of pixels. + * Snapping the translation to an integer number of pixels avoids + * blurring the layer and can be faster to composite. + * 2) When a layer is used to render a rectangular object, we need to + * emulate the rendering of rectangular inactive content and snap the + * edges of the rectangle to pixel boundaries. This is both to ensure + * layer rendering is consistent with inactive content rendering, and to + * avoid seams. + * This function implements type 1 snapping. If aTransform is a 2D + * translation, and this layer's layer manager has enabled snapping + * (which is the default), return aTransform with the translation snapped + * to nearest pixels. Otherwise just return aTransform. Call this when the + * layer does not correspond to a single rectangular content object. + * This function does not try to snap if aTransform has a scale, because in + * that case resampling is inevitable and there's no point in trying to + * avoid it. In fact snapping can cause problems because pixel edges in the + * layer's content can be rendered unpredictably (jiggling) as the scale + * interacts with the snapping of the translation, especially with animated + * transforms. + * @param aResidualTransform a transform to apply before the result transform + * in order to get the results to completely match aTransform. + */ + gfx::Matrix4x4 SnapTransformTranslation(const gfx::Matrix4x4& aTransform, + gfx::Matrix* aResidualTransform); + gfx::Matrix4x4 SnapTransformTranslation3D(const gfx::Matrix4x4& aTransform, + gfx::Matrix* aResidualTransform); + /** + * See comment for SnapTransformTranslation. + * This function implements type 2 snapping. If aTransform is a translation + * and/or scale, transform aSnapRect by aTransform, snap to pixel boundaries, + * and return the transform that maps aSnapRect to that rect. Otherwise + * just return aTransform. + * @param aSnapRect a rectangle whose edges should be snapped to pixel + * boundaries in the destination surface. + * @param aResidualTransform a transform to apply before the result transform + * in order to get the results to completely match aTransform. + */ + gfx::Matrix4x4 SnapTransform(const gfx::Matrix4x4& aTransform, + const gfxRect& aSnapRect, + gfx::Matrix* aResidualTransform); + + LayerManager* mManager; + ContainerLayer* mParent; + Layer* mNextSibling; + Layer* mPrevSibling; + void* mImplData; + RefPtr mMaskLayer; + nsTArray> mAncestorMaskLayers; + // Look for out-of-bound in the middle of the structure + mozilla::CorruptionCanary mCanary; + gfx::UserData mUserData; + SimpleLayerAttributes mSimpleAttrs; + LayerIntRegion mVisibleRegion; + nsTArray mScrollMetadata; + EventRegions mEventRegions; + // A mutation of |mTransform| that we've queued to be applied at the + // end of the next transaction (if nothing else overrides it in the + // meantime). + UniquePtr mPendingTransform; + gfx::Matrix4x4 mEffectiveTransform; + AnimationInfo mAnimationInfo; + Maybe mClipRect; + gfx::IntRect mTileSourceRect; + gfx::TiledIntRegion mInvalidRegion; + nsTArray> mApzcs; + bool mUseTileSourceRect; +#ifdef DEBUG + uint32_t mDebugColorIndex; +#endif +#ifdef MOZ_DUMP_PAINTING + nsTArray mExtraDumpInfo; +#endif + // Store display list log. + nsCString mDisplayListLog; +}; + +/** + * A Layer which we can paint into. It is a conceptually + * infinite surface, but each PaintedLayer has an associated "valid region" + * of contents that it is currently storing, which is finite. PaintedLayer + * implementations can store content between paints. + * + * PaintedLayers are rendered into during the drawing phase of a transaction. + * + * Currently the contents of a PaintedLayer are in the device output color + * space. + */ +class PaintedLayer : public Layer { + public: + /** + * CONSTRUCTION PHASE ONLY + * Tell this layer that the content in some region has changed and + * will need to be repainted. This area is removed from the valid + * region. + */ + virtual void InvalidateRegion(const nsIntRegion& aRegion) = 0; + /** + * CONSTRUCTION PHASE ONLY + * Set whether ComputeEffectiveTransforms should compute the + * "residual translation" --- the translation that should be applied *before* + * mEffectiveTransform to get the ideal transform for this PaintedLayer. + * When this is true, ComputeEffectiveTransforms will compute the residual + * and ensure that the layer is invalidated whenever the residual changes. + * When it's false, a change in the residual will not trigger invalidation + * and GetResidualTranslation will return 0,0. + * So when the residual is to be ignored, set this to false for better + * performance. + */ + void SetAllowResidualTranslation(bool aAllow) { + mAllowResidualTranslation = aAllow; + } + + void SetValidRegion(const nsIntRegion& aRegion) { + MOZ_LAYERS_LOG_IF_SHADOWABLE(this, + ("Layer::Mutated(%p) ValidRegion", this)); + mValidRegion = aRegion; + mValidRegionIsCurrent = true; + Mutated(); + } + + /** + * Can be used anytime + */ + const nsIntRegion& GetValidRegion() const { + EnsureValidRegionIsCurrent(); + return mValidRegion; + } + + void InvalidateWholeLayer() { + mInvalidRegion.Add(GetValidRegion().GetBounds()); + ClearValidRegion(); + } + + void ClearValidRegion() { + mValidRegion.SetEmpty(); + mValidRegionIsCurrent = true; + } + void AddToValidRegion(const nsIntRegion& aRegion) { + EnsureValidRegionIsCurrent(); + mValidRegion.OrWith(aRegion); + } + void SubtractFromValidRegion(const nsIntRegion& aRegion) { + EnsureValidRegionIsCurrent(); + mValidRegion.SubOut(aRegion); + } + void UpdateValidRegionAfterInvalidRegionChanged() { + // Changes to mInvalidRegion will be applied to mValidRegion on the next + // call to EnsureValidRegionIsCurrent(). + mValidRegionIsCurrent = false; + } + + void ClearInvalidRegion() override { + // mInvalidRegion is about to be reset. This is the last chance to apply + // any pending changes from it to mValidRegion. Do that by calling + // EnsureValidRegionIsCurrent(). + EnsureValidRegionIsCurrent(); + mInvalidRegion.SetEmpty(); + } + + PaintedLayer* AsPaintedLayer() override { return this; } + + MOZ_LAYER_DECL_NAME("PaintedLayer", TYPE_PAINTED) + + void ComputeEffectiveTransforms( + const gfx::Matrix4x4& aTransformToSurface) override { + gfx::Matrix4x4 idealTransform = GetLocalTransform() * aTransformToSurface; + gfx::Matrix residual; + mEffectiveTransform = SnapTransformTranslation( + idealTransform, mAllowResidualTranslation ? &residual : nullptr); + // The residual can only be a translation because SnapTransformTranslation + // only changes the transform if it's a translation + NS_ASSERTION(residual.IsTranslation(), + "Residual transform can only be a translation"); + if (!gfx::ThebesPoint(residual.GetTranslation()) + .WithinEpsilonOf(mResidualTranslation, 1e-3f)) { + mResidualTranslation = gfx::ThebesPoint(residual.GetTranslation()); + DebugOnly transformedOrig = + idealTransform.TransformPoint(mozilla::gfx::Point()); +#ifdef DEBUG + DebugOnly transformed = + idealTransform.TransformPoint(mozilla::gfx::Point( + mResidualTranslation.x, mResidualTranslation.y)) - + *&transformedOrig; +#endif + NS_ASSERTION(-0.5 <= (&transformed)->x && (&transformed)->x < 0.5 && + -0.5 <= (&transformed)->y && (&transformed)->y < 0.5, + "Residual translation out of range"); + ClearValidRegion(); + } + ComputeEffectiveTransformForMaskLayers(aTransformToSurface); + } + + LayerManager::PaintedLayerCreationHint GetCreationHint() const { + return mCreationHint; + } + + bool UsedForReadback() { return mUsedForReadback; } + void SetUsedForReadback(bool aUsed) { mUsedForReadback = aUsed; } + + /** + * Returns true if aLayer is optimized for the given PaintedLayerCreationHint. + */ + virtual bool IsOptimizedFor( + LayerManager::PaintedLayerCreationHint aCreationHint) { + return true; + } + + /** + * Returns the residual translation. Apply this translation when drawing + * into the PaintedLayer so that when mEffectiveTransform is applied + * afterwards by layer compositing, the results exactly match the "ideal + * transform" (the product of the transform of this layer and its ancestors). + * Returns 0,0 unless SetAllowResidualTranslation(true) has been called. + * The residual translation components are always in the range [-0.5, 0.5). + */ + gfxPoint GetResidualTranslation() const { return mResidualTranslation; } + + protected: + PaintedLayer( + LayerManager* aManager, void* aImplData, + LayerManager::PaintedLayerCreationHint aCreationHint = LayerManager::NONE) + : Layer(aManager, aImplData), + mValidRegion(), + mValidRegionIsCurrent(true), + mCreationHint(aCreationHint), + mUsedForReadback(false), + mAllowResidualTranslation(false) {} + + void PrintInfo(std::stringstream& aStream, const char* aPrefix) override; + + void DumpPacket(layerscope::LayersPacket* aPacket, + const void* aParent) override; + + /** + * ComputeEffectiveTransforms snaps the ideal transform to get + * mEffectiveTransform. mResidualTranslation is the translation that should be + * applied *before* mEffectiveTransform to get the ideal transform. + */ + gfxPoint mResidualTranslation; + + private: + /** + * Needs to be called prior to accessing mValidRegion, unless mValidRegion is + * being completely overwritten. + */ + void EnsureValidRegionIsCurrent() const { + if (!mValidRegionIsCurrent) { + // Apply any pending mInvalidRegion changes to mValidRegion. + if (!mValidRegion.IsEmpty()) { + // Calling mInvalidRegion.GetRegion() is expensive. + // That's why we delay the adjustment of mValidRegion for as long as + // possible, so that multiple modifications to mInvalidRegion can be + // applied to mValidRegion in one go. + mValidRegion.SubOut(mInvalidRegion.GetRegion()); + } + mValidRegionIsCurrent = true; + } + } + + /** + * The layer's valid region. If mValidRegionIsCurrent is false, then + * mValidRegion has not yet been updated for recent changes to + * mInvalidRegion. Those pending changes can be applied by calling + * EnsureValidRegionIsCurrent(). + */ + mutable nsIntRegion mValidRegion; + + mutable bool mValidRegionIsCurrent; + + protected: + /** + * The creation hint that was used when constructing this layer. + */ + const LayerManager::PaintedLayerCreationHint mCreationHint; + /** + * Set when this PaintedLayer is participating in readback, i.e. some + * ReadbackLayer (may) be getting its background from this layer. + */ + bool mUsedForReadback; + /** + * True when + */ + bool mAllowResidualTranslation; +}; + +/** + * A Layer which other layers render into. It holds references to its + * children. + */ +class ContainerLayer : public Layer { + public: + virtual ~ContainerLayer(); + + /** + * CONSTRUCTION PHASE ONLY + * Insert aChild into the child list of this container. aChild must + * not be currently in any child list or the root for the layer manager. + * If aAfter is non-null, it must be a child of this container and + * we insert after that layer. If it's null we insert at the start. + */ + virtual bool InsertAfter(Layer* aChild, Layer* aAfter); + /** + * CONSTRUCTION PHASE ONLY + * Remove aChild from the child list of this container. aChild must + * be a child of this container. + */ + virtual bool RemoveChild(Layer* aChild); + /** + * CONSTRUCTION PHASE ONLY + * Reposition aChild from the child list of this container. aChild must + * be a child of this container. + * If aAfter is non-null, it must be a child of this container and we + * reposition after that layer. If it's null, we reposition at the start. + */ + virtual bool RepositionChild(Layer* aChild, Layer* aAfter); + + void SetPreScale(float aXScale, float aYScale) { + if (mPreXScale == aXScale && mPreYScale == aYScale) { + return; + } + + MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) PreScale", this)); + mPreXScale = aXScale; + mPreYScale = aYScale; + Mutated(); + } + + void SetInheritedScale(float aXScale, float aYScale) { + if (mInheritedXScale == aXScale && mInheritedYScale == aYScale) { + return; + } + + MOZ_LAYERS_LOG_IF_SHADOWABLE(this, + ("Layer::Mutated(%p) InheritedScale", this)); + mInheritedXScale = aXScale; + mInheritedYScale = aYScale; + Mutated(); + } + + void SetScaleToResolution(float aResolution) { + if (mPresShellResolution == aResolution) { + return; + } + + MOZ_LAYERS_LOG_IF_SHADOWABLE( + this, ("Layer::Mutated(%p) ScaleToResolution", this)); + mPresShellResolution = aResolution; + Mutated(); + } + + void FillSpecificAttributes(SpecificLayerAttributes& aAttrs) override; + + enum class SortMode { + WITH_GEOMETRY, + WITHOUT_GEOMETRY, + }; + + nsTArray SortChildrenBy3DZOrder(SortMode aSortMode); + + ContainerLayer* AsContainerLayer() override { return this; } + const ContainerLayer* AsContainerLayer() const override { return this; } + + // These getters can be used anytime. + Layer* GetFirstChild() const override { return mFirstChild; } + Layer* GetLastChild() const override { return mLastChild; } + float GetPreXScale() const { return mPreXScale; } + float GetPreYScale() const { return mPreYScale; } + float GetInheritedXScale() const { return mInheritedXScale; } + float GetInheritedYScale() const { return mInheritedYScale; } + float GetPresShellResolution() const { return mPresShellResolution; } + + MOZ_LAYER_DECL_NAME("ContainerLayer", TYPE_CONTAINER) + + /** + * ContainerLayer backends need to override ComputeEffectiveTransforms + * since the decision about whether to use a temporary surface for the + * container is backend-specific. ComputeEffectiveTransforms must also set + * mUseIntermediateSurface. + */ + void ComputeEffectiveTransforms( + const gfx::Matrix4x4& aTransformToSurface) override = 0; + + /** + * Call this only after ComputeEffectiveTransforms has been invoked + * on this layer. + * Returns true if this will use an intermediate surface. This is largely + * backend-dependent, but it affects the operation of GetEffectiveOpacity(). + */ + bool UseIntermediateSurface() { return mUseIntermediateSurface; } + + /** + * Returns the rectangle covered by the intermediate surface, + * in this layer's coordinate system. + * + * NOTE: Since this layer has an intermediate surface it follows + * that LayerPixel == RenderTargetPixel + */ + RenderTargetIntRect GetIntermediateSurfaceRect(); + + /** + * Returns true if this container has more than one non-empty child + */ + bool HasMultipleChildren(); + + /** + * Returns true if this container supports children with component alpha. + * Should only be called while painting a child of this layer. + */ + bool SupportsComponentAlphaChildren() { + return mSupportsComponentAlphaChildren; + } + + /** + * Returns true if aLayer or any layer in its parent chain has the opaque + * content flag set. + */ + static bool HasOpaqueAncestorLayer(Layer* aLayer); + + void SetChildrenChanged(bool aVal) { mChildrenChanged = aVal; } + + // If |aRect| is null, the entire layer should be considered invalid for + // compositing. + virtual void SetInvalidCompositeRect(const gfx::IntRect* aRect) {} + + protected: + friend class ReadbackProcessor; + + // Note that this is not virtual, and is based on the implementation of + // ContainerLayer::RemoveChild, so it should only be called where you would + // want to explicitly call the base class implementation of RemoveChild; + // e.g., while (mFirstChild) ContainerLayer::RemoveChild(mFirstChild); + void RemoveAllChildren(); + + void DidInsertChild(Layer* aLayer); + void DidRemoveChild(Layer* aLayer); + + bool AnyAncestorOrThisIs3DContextLeaf(); + + void Collect3DContextLeaves(nsTArray& aToSort); + + // Collects child layers that do not extend 3D context. For ContainerLayers + // that do extend 3D context, the 3D context leaves are collected. + nsTArray CollectChildren() { + nsTArray children; + + for (Layer* layer = GetFirstChild(); layer; + layer = layer->GetNextSibling()) { + ContainerLayer* container = layer->AsContainerLayer(); + + if (container && container->Extend3DContext() && + !container->UseIntermediateSurface()) { + container->Collect3DContextLeaves(children); + } else { + children.AppendElement(layer); + } + } + + return children; + } + + ContainerLayer(LayerManager* aManager, void* aImplData); + + /** + * A default implementation of ComputeEffectiveTransforms for use by OpenGL + * and D3D. + */ + void DefaultComputeEffectiveTransforms( + const gfx::Matrix4x4& aTransformToSurface); + + /** + * A default implementation to compute and set the value for + * SupportsComponentAlphaChildren(). + * + * If aNeedsSurfaceCopy is provided, then it is set to true if the caller + * needs to copy the background up into the intermediate surface created, + * false otherwise. + */ + void DefaultComputeSupportsComponentAlphaChildren( + bool* aNeedsSurfaceCopy = nullptr); + + /** + * Loops over the children calling ComputeEffectiveTransforms on them. + */ + void ComputeEffectiveTransformsForChildren( + const gfx::Matrix4x4& aTransformToSurface); + + virtual void PrintInfo(std::stringstream& aStream, + const char* aPrefix) override; + + virtual void DumpPacket(layerscope::LayersPacket* aPacket, + const void* aParent) override; + + /** + * True for if the container start a new 3D context extended by one + * or more children. + */ + bool Creates3DContextWithExtendingChildren(); + + Layer* mFirstChild; + Layer* mLastChild; + float mPreXScale; + float mPreYScale; + // The resolution scale inherited from the parent layer. This will already + // be part of mTransform. + float mInheritedXScale; + float mInheritedYScale; + // For layers corresponding to an nsDisplayAsyncZoom, the resolution of the + // associated pres shell; for other layers, 1.0. + float mPresShellResolution; + bool mUseIntermediateSurface; + bool mSupportsComponentAlphaChildren; + bool mMayHaveReadbackChild; + // This is updated by ComputeDifferences. This will be true if we need to + // invalidate the intermediate surface. + bool mChildrenChanged; +}; + +/** + * A Layer which just renders a solid color in its visible region. It actually + * can fill any area that contains the visible region, so if you need to + * restrict the area filled, set a clip region on this layer. + */ +class ColorLayer : public Layer { + public: + ColorLayer* AsColorLayer() override { return this; } + + /** + * CONSTRUCTION PHASE ONLY + * Set the color of the layer. + */ + virtual void SetColor(const gfx::DeviceColor& aColor) { + if (mColor != aColor) { + MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) Color", this)); + mColor = aColor; + Mutated(); + } + } + + void SetBounds(const gfx::IntRect& aBounds) { + if (!mBounds.IsEqualEdges(aBounds)) { + mBounds = aBounds; + Mutated(); + } + } + + const gfx::IntRect& GetBounds() { return mBounds; } + + // This getter can be used anytime. + virtual const gfx::DeviceColor& GetColor() { return mColor; } + + MOZ_LAYER_DECL_NAME("ColorLayer", TYPE_COLOR) + + void ComputeEffectiveTransforms( + const gfx::Matrix4x4& aTransformToSurface) override { + gfx::Matrix4x4 idealTransform = GetLocalTransform() * aTransformToSurface; + mEffectiveTransform = SnapTransformTranslation(idealTransform, nullptr); + ComputeEffectiveTransformForMaskLayers(aTransformToSurface); + } + + protected: + ColorLayer(LayerManager* aManager, void* aImplData) + : Layer(aManager, aImplData), mColor() {} + + void PrintInfo(std::stringstream& aStream, const char* aPrefix) override; + + void DumpPacket(layerscope::LayersPacket* aPacket, + const void* aParent) override; + + gfx::IntRect mBounds; + gfx::DeviceColor mColor; +}; + +/** + * A Layer for HTML Canvas elements. It's backed by either a + * gfxASurface or a GLContext (for WebGL layers), and has some control + * for intelligent updating from the source if necessary (for example, + * if hardware compositing is not available, for reading from the GL + * buffer into an image surface that we can layer composite.) + * + * After Initialize is called, the underlying canvas Surface/GLContext + * must not be modified during a layer transaction. + */ +class CanvasLayer : public Layer { + public: + void SetBounds(gfx::IntRect aBounds) { mBounds = aBounds; } + + CanvasLayer* AsCanvasLayer() override { return this; } + + /** + * Notify this CanvasLayer that the canvas surface contents have + * changed (or will change) before the next transaction. + */ + void Updated() { + mCanvasRenderer->SetDirty(); + SetInvalidRectToVisibleRegion(); + } + + /** + * Notify this CanvasLayer that the canvas surface contents have + * been painted since the last change. + */ + void Painted() { mCanvasRenderer->ResetDirty(); } + + /** + * Returns true if the canvas surface contents have changed since the + * last paint. + */ + bool IsDirty() { + // We can only tell if we are dirty if we're part of the + // widget's retained layer tree. + if (!mManager || !mManager->IsWidgetLayerManager()) { + return true; + } + return mCanvasRenderer->IsDirty(); + } + + const nsIntRect& GetBounds() const { return mBounds; } + + RefPtr CreateOrGetCanvasRenderer(); + + public: + /** + * CONSTRUCTION PHASE ONLY + * Set the filter used to resample this image (if necessary). + */ + void SetSamplingFilter(gfx::SamplingFilter aSamplingFilter) { + if (mSamplingFilter != aSamplingFilter) { + MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) Filter", this)); + mSamplingFilter = aSamplingFilter; + Mutated(); + } + } + gfx::SamplingFilter GetSamplingFilter() const { return mSamplingFilter; } + + MOZ_LAYER_DECL_NAME("CanvasLayer", TYPE_CANVAS) + + void ComputeEffectiveTransforms( + const gfx::Matrix4x4& aTransformToSurface) override { + // Snap our local transform first, and snap the inherited transform as well. + // This makes our snapping equivalent to what would happen if our content + // was drawn into a PaintedLayer (gfxContext would snap using the local + // transform, then we'd snap again when compositing the PaintedLayer). + mEffectiveTransform = + SnapTransform(GetLocalTransform(), + gfxRect(0, 0, mBounds.Width(), mBounds.Height()), + nullptr) * + SnapTransformTranslation(aTransformToSurface, nullptr); + ComputeEffectiveTransformForMaskLayers(aTransformToSurface); + } + + protected: + CanvasLayer(LayerManager* aManager, void* aImplData); + virtual ~CanvasLayer(); + + void PrintInfo(std::stringstream& aStream, const char* aPrefix) override; + + void DumpPacket(layerscope::LayersPacket* aPacket, + const void* aParent) override; + + virtual RefPtr CreateCanvasRendererInternal() = 0; + + RefPtr mCanvasRenderer; + gfx::SamplingFilter mSamplingFilter; + + /** + * 0, 0, canvaswidth, canvasheight + */ + gfx::IntRect mBounds; +}; + +/** + * ContainerLayer that refers to a "foreign" layer tree, through an + * ID. Usage of RefLayer looks like + * + * Construction phase: + * allocate ID for layer subtree + * create RefLayer, SetReferentId(ID) + * + * Composition: + * look up subtree for GetReferentId() + * ConnectReferentLayer(subtree) + * compose + * ClearReferentLayer() + * + * Clients will usually want to Connect/Clear() on each transaction to + * avoid difficulties managing memory across multiple layer subtrees. + */ +class RefLayer : public ContainerLayer { + friend class LayerManager; + + private: + bool InsertAfter(Layer* aChild, Layer* aAfter) override { + MOZ_CRASH("GFX: RefLayer"); + return false; + } + + bool RemoveChild(Layer* aChild) override { + MOZ_CRASH("GFX: RefLayer"); + return false; + } + + bool RepositionChild(Layer* aChild, Layer* aAfter) override { + MOZ_CRASH("GFX: RefLayer"); + return false; + } + + public: + /** + * CONSTRUCTION PHASE ONLY + * Set the ID of the layer's referent. + */ + void SetReferentId(LayersId aId) { + MOZ_ASSERT(aId.IsValid()); + if (mId != aId) { + MOZ_LAYERS_LOG_IF_SHADOWABLE(this, + ("Layer::Mutated(%p) ReferentId", this)); + mId = aId; + Mutated(); + } + } + /** + * CONSTRUCTION PHASE ONLY + * Connect this ref layer to its referent, temporarily. + * ClearReferentLayer() must be called after composition. + */ + void ConnectReferentLayer(Layer* aLayer) { + MOZ_ASSERT(!mFirstChild && !mLastChild); + MOZ_ASSERT(!aLayer->GetParent()); + if (aLayer->Manager() != Manager()) { + // This can happen when e.g. rendering while dragging tabs + // between windows - aLayer's manager may be the manager for the + // old window's tab. In that case, it will be changed before the + // next render (see SetLayerManager). It is simply easier to + // ignore the rendering here than it is to pause it. + NS_WARNING("ConnectReferentLayer failed - Incorrect LayerManager"); + return; + } + + mFirstChild = mLastChild = aLayer; + aLayer->SetParent(this); + } + + /** + * CONSTRUCTION PHASE ONLY + * Set flags that indicate how event regions in the child layer tree need + * to be overridden because of properties of the parent layer tree. + */ + void SetEventRegionsOverride(EventRegionsOverride aVal) { + if (mEventRegionsOverride == aVal) { + return; + } + + MOZ_LAYERS_LOG_IF_SHADOWABLE( + this, ("Layer::Mutated(%p) EventRegionsOverride", this)); + mEventRegionsOverride = aVal; + Mutated(); + } + + EventRegionsOverride GetEventRegionsOverride() const { + return mEventRegionsOverride; + } + + /** + * CONSTRUCTION PHASE ONLY + * Set remote subdocument iframe size. + */ + void SetRemoteDocumentSize(const LayerIntSize& aRemoteDocumentSize) { + if (mRemoteDocumentSize == aRemoteDocumentSize) { + return; + } + mRemoteDocumentSize = aRemoteDocumentSize; + Mutated(); + } + + const LayerIntSize& GetRemoteDocumentSize() const { + return mRemoteDocumentSize; + } + + /** + * DRAWING PHASE ONLY + * |aLayer| is the same as the argument to ConnectReferentLayer(). + */ + void DetachReferentLayer(Layer* aLayer) { + mFirstChild = mLastChild = nullptr; + aLayer->SetParent(nullptr); + } + + // These getters can be used anytime. + RefLayer* AsRefLayer() override { return this; } + + virtual LayersId GetReferentId() { return mId; } + + /** + * DRAWING PHASE ONLY + */ + void FillSpecificAttributes(SpecificLayerAttributes& aAttrs) override; + + MOZ_LAYER_DECL_NAME("RefLayer", TYPE_REF) + + protected: + RefLayer(LayerManager* aManager, void* aImplData) + : ContainerLayer(aManager, aImplData), + mId{0}, + mEventRegionsOverride(EventRegionsOverride::NoOverride) {} + + void PrintInfo(std::stringstream& aStream, const char* aPrefix) override; + + void DumpPacket(layerscope::LayersPacket* aPacket, + const void* aParent) override; + + // 0 is a special value that means "no ID". + LayersId mId; + EventRegionsOverride mEventRegionsOverride; + // The remote documents only need their size because their origin is always + // (0, 0). + LayerIntSize mRemoteDocumentSize; +}; + +void SetAntialiasingFlags(Layer* aLayer, gfx::DrawTarget* aTarget); + +#ifdef MOZ_DUMP_PAINTING +void WriteSnapshotToDumpFile(Layer* aLayer, gfx::DataSourceSurface* aSurf); +void WriteSnapshotToDumpFile(LayerManager* aManager, + gfx::DataSourceSurface* aSurf); +void WriteSnapshotToDumpFile(Compositor* aCompositor, gfx::DrawTarget* aTarget); +#endif + +// A utility function used by different LayerManager implementations. +gfx::IntRect ToOutsideIntRect(const gfxRect& aRect); + +void RecordCompositionPayloadsPresented( + const TimeStamp& aCompositionEndTime, + const nsTArray& aPayloads); + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_LAYERS_H */ diff --git a/gfx/layers/LayersHelpers.cpp b/gfx/layers/LayersHelpers.cpp new file mode 100644 index 0000000000..72f194aa48 --- /dev/null +++ b/gfx/layers/LayersHelpers.cpp @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "LayersHelpers.h" + +namespace mozilla { +namespace layers { + +using namespace gfx; + +gfx::IntRect ComputeBackdropCopyRect(const gfx::Rect& aRect, + const gfx::IntRect& aClipRect, + const gfx::Matrix4x4& aTransform, + const gfx::IntRect& aRenderTargetRect, + gfx::Matrix4x4* aOutTransform, + gfx::Rect* aOutLayerQuad) { + // Compute the clip. + IntPoint rtOffset = aRenderTargetRect.TopLeft(); + IntSize rtSize = aRenderTargetRect.Size(); + + gfx::IntRect renderBounds(0, 0, rtSize.width, rtSize.height); + renderBounds.IntersectRect(renderBounds, aClipRect); + renderBounds.MoveBy(rtOffset); + + // Apply the layer transform. + RectDouble dest = aTransform.TransformAndClipBounds( + RectDouble(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()), + RectDouble(renderBounds.X(), renderBounds.Y(), renderBounds.Width(), + renderBounds.Height())); + dest -= rtOffset; + + // Ensure we don't round out to -1, which trips up Direct3D. + dest.IntersectRect(dest, RectDouble(0, 0, rtSize.width, rtSize.height)); + + if (aOutLayerQuad) { + *aOutLayerQuad = Rect(dest.X(), dest.Y(), dest.Width(), dest.Height()); + } + + // Round out to integer. + IntRect result; + dest.RoundOut(); + dest.ToIntRect(&result); + + // Create a transform from adjusted clip space to render target space, + // translate it for the backdrop rect, then transform it into the backdrop's + // uv-space. + Matrix4x4 transform; + transform.PostScale(rtSize.width, rtSize.height, 1.0); + transform.PostTranslate(-result.X(), -result.Y(), 0.0); + transform.PostScale(1 / float(result.Width()), 1 / float(result.Height()), + 1.0); + *aOutTransform = transform; + return result; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/LayersHelpers.h b/gfx/layers/LayersHelpers.h new file mode 100644 index 0000000000..bce574b662 --- /dev/null +++ b/gfx/layers/LayersHelpers.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_layers_LayersHelpers_h +#define mozilla_gfx_layers_LayersHelpers_h + +#include "mozilla/Maybe.h" +#include "mozilla/gfx/Rect.h" +#include "mozilla/gfx/Matrix.h" +#include "mozilla/gfx/Polygon.h" +#include "nsRegion.h" +#include "nsTArray.h" + +namespace mozilla { +namespace layers { + +class Layer; + +// Compute compositor information for copying the backdrop for a mix-blend +// operation. +gfx::IntRect ComputeBackdropCopyRect(const gfx::Rect& aRect, + const gfx::IntRect& aClipRect, + const gfx::Matrix4x4& aTransform, + const gfx::IntRect& aRenderTargetRect, + gfx::Matrix4x4* aOutTransform, + gfx::Rect* aOutLayerQuad = nullptr); + +// Compute uv-coordinates for a rect inside a texture. +template +static inline gfx::Rect TextureRectToCoords(const T& aRect, + const gfx::IntSize& aSize) { + return gfx::Rect( + float(aRect.X()) / aSize.width, float(aRect.Y()) / aSize.height, + float(aRect.Width()) / aSize.width, float(aRect.Height()) / aSize.height); +} + +// This is defined in Compositor.cpp. +nsTArray GenerateTexturedTriangles( + const gfx::Polygon& aPolygon, const gfx::Rect& aRect, + const gfx::Rect& aTexRect); + +// This is defined in ContainerLayerComposite.cpp. +void TransformLayerGeometry(Layer* aLayer, Maybe& aGeometry); + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_gfx_layers_LayersHelpers_h diff --git a/gfx/layers/LayersTypes.cpp b/gfx/layers/LayersTypes.cpp new file mode 100644 index 0000000000..56efd2c05f --- /dev/null +++ b/gfx/layers/LayersTypes.cpp @@ -0,0 +1,170 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "LayersTypes.h" + +#include +#include "nsPrintfCString.h" +#include "mozilla/gfx/gfxVars.h" + +namespace mozilla { +namespace layers { + +const char* kCompositionPayloadTypeNames[kCompositionPayloadTypeCount] = { + "KeyPress", + "APZScroll", + "APZPinchZoom", + "ContentPaint", +}; + +const char* GetLayersBackendName(LayersBackend aBackend) { + switch (aBackend) { + case LayersBackend::LAYERS_NONE: + return "none"; + case LayersBackend::LAYERS_OPENGL: + return "opengl"; + case LayersBackend::LAYERS_D3D11: + return "d3d11"; + case LayersBackend::LAYERS_CLIENT: + return "client"; + case LayersBackend::LAYERS_WR: + MOZ_ASSERT(gfx::gfxVars::UseWebRender()); + if (gfx::gfxVars::UseSoftwareWebRender()) { + return "webrender_software"; + } + return "webrender"; + case LayersBackend::LAYERS_BASIC: + return "basic"; + default: + MOZ_ASSERT_UNREACHABLE("unknown layers backend"); + return "unknown"; + } +} + +std::ostream& operator<<(std::ostream& aStream, const LayersId& aId) { + return aStream << nsPrintfCString("0x%" PRIx64, aId.mId).get(); +} + +EventRegions::EventRegions() : mDTCRequiresTargetConfirmation(false) {} + +EventRegions::EventRegions(nsIntRegion aHitRegion) + : mHitRegion(aHitRegion), mDTCRequiresTargetConfirmation(false) {} + +EventRegions::EventRegions(const nsIntRegion& aHitRegion, + const nsIntRegion& aMaybeHitRegion, + const nsIntRegion& aDispatchToContentRegion, + const nsIntRegion& aNoActionRegion, + const nsIntRegion& aHorizontalPanRegion, + const nsIntRegion& aVerticalPanRegion, + bool aDTCRequiresTargetConfirmation) { + mHitRegion = aHitRegion; + mNoActionRegion = aNoActionRegion; + mHorizontalPanRegion = aHorizontalPanRegion; + mVerticalPanRegion = aVerticalPanRegion; + // Points whose hit-region status we're not sure about need to be dispatched + // to the content thread. If a point is in both maybeHitRegion and hitRegion + // then it's not a "maybe" any more, and doesn't go into the dispatch-to- + // content region. + mDispatchToContentHitRegion.Sub(aMaybeHitRegion, mHitRegion); + mDispatchToContentHitRegion.OrWith(aDispatchToContentRegion); + mHitRegion.OrWith(aMaybeHitRegion); + mDTCRequiresTargetConfirmation = aDTCRequiresTargetConfirmation; +} + +bool EventRegions::operator==(const EventRegions& aRegions) const { + return mHitRegion == aRegions.mHitRegion && + mDispatchToContentHitRegion == aRegions.mDispatchToContentHitRegion && + mNoActionRegion == aRegions.mNoActionRegion && + mHorizontalPanRegion == aRegions.mHorizontalPanRegion && + mVerticalPanRegion == aRegions.mVerticalPanRegion && + mDTCRequiresTargetConfirmation == + aRegions.mDTCRequiresTargetConfirmation; +} + +bool EventRegions::operator!=(const EventRegions& aRegions) const { + return !(*this == aRegions); +} + +std::ostream& operator<<(std::ostream& aStream, const EventRegions& e) { + aStream << "{"; + if (!e.mHitRegion.IsEmpty()) { + aStream << " Hit=" << e.mHitRegion; + } + if (!e.mDispatchToContentHitRegion.IsEmpty()) { + aStream << " DispatchToContent=" << e.mDispatchToContentHitRegion; + } + if (!e.mNoActionRegion.IsEmpty()) { + aStream << " NoAction=" << e.mNoActionRegion; + } + if (!e.mHorizontalPanRegion.IsEmpty()) { + aStream << " HorizontalPan=" << e.mHorizontalPanRegion; + } + if (!e.mVerticalPanRegion.IsEmpty()) { + aStream << " VerticalPan=" << e.mVerticalPanRegion; + } + aStream << " }"; + return aStream; +} + +void EventRegions::ApplyTranslationAndScale(float aXTrans, float aYTrans, + float aXScale, float aYScale) { + mHitRegion.ScaleRoundOut(aXScale, aYScale); + mDispatchToContentHitRegion.ScaleRoundOut(aXScale, aYScale); + mNoActionRegion.ScaleRoundOut(aXScale, aYScale); + mHorizontalPanRegion.ScaleRoundOut(aXScale, aYScale); + mVerticalPanRegion.ScaleRoundOut(aXScale, aYScale); + + mHitRegion.MoveBy(aXTrans, aYTrans); + mDispatchToContentHitRegion.MoveBy(aXTrans, aYTrans); + mNoActionRegion.MoveBy(aXTrans, aYTrans); + mHorizontalPanRegion.MoveBy(aXTrans, aYTrans); + mVerticalPanRegion.MoveBy(aXTrans, aYTrans); +} + +void EventRegions::Transform(const gfx::Matrix4x4& aTransform) { + mHitRegion.Transform(aTransform); + mDispatchToContentHitRegion.Transform(aTransform); + mNoActionRegion.Transform(aTransform); + mHorizontalPanRegion.Transform(aTransform); + mVerticalPanRegion.Transform(aTransform); +} + +void EventRegions::OrWith(const EventRegions& aOther) { + mHitRegion.OrWith(aOther.mHitRegion); + mDispatchToContentHitRegion.OrWith(aOther.mDispatchToContentHitRegion); + // See the comment in nsDisplayList::AddFrame, where the touch action + // regions are handled. The same thing applies here. + bool alreadyHadRegions = !mNoActionRegion.IsEmpty() || + !mHorizontalPanRegion.IsEmpty() || + !mVerticalPanRegion.IsEmpty(); + mNoActionRegion.OrWith(aOther.mNoActionRegion); + mHorizontalPanRegion.OrWith(aOther.mHorizontalPanRegion); + mVerticalPanRegion.OrWith(aOther.mVerticalPanRegion); + if (alreadyHadRegions) { + nsIntRegion combinedActionRegions; + combinedActionRegions.Or(mHorizontalPanRegion, mVerticalPanRegion); + combinedActionRegions.OrWith(mNoActionRegion); + mDispatchToContentHitRegion.OrWith(combinedActionRegions); + } + mDTCRequiresTargetConfirmation |= aOther.mDTCRequiresTargetConfirmation; +} + +bool EventRegions::IsEmpty() const { + return mHitRegion.IsEmpty() && mDispatchToContentHitRegion.IsEmpty() && + mNoActionRegion.IsEmpty() && mHorizontalPanRegion.IsEmpty() && + mVerticalPanRegion.IsEmpty(); +} + +void EventRegions::SetEmpty() { + mHitRegion.SetEmpty(); + mDispatchToContentHitRegion.SetEmpty(); + mNoActionRegion.SetEmpty(); + mHorizontalPanRegion.SetEmpty(); + mVerticalPanRegion.SetEmpty(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/LayersTypes.h b/gfx/layers/LayersTypes.h new file mode 100644 index 0000000000..9a82c91759 --- /dev/null +++ b/gfx/layers/LayersTypes.h @@ -0,0 +1,444 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_LAYERSTYPES_H +#define GFX_LAYERSTYPES_H + +#include // for ostream +#include // for uint32_t +#include // FILE + +#include "Units.h" +#include "mozilla/DefineEnum.h" // for MOZ_DEFINE_ENUM_CLASS_WITH_BASE +#include "mozilla/Maybe.h" +#include "mozilla/TimeStamp.h" // for TimeStamp +#include "nsRegion.h" +#include "mozilla/EnumSet.h" + +#ifndef MOZ_LAYERS_HAVE_LOG +# define MOZ_LAYERS_HAVE_LOG +#endif +#define MOZ_LAYERS_LOG(_args) \ + MOZ_LOG(LayerManager::GetLog(), LogLevel::Debug, _args) +#define MOZ_LAYERS_LOG_IF_SHADOWABLE(layer, _args) \ + do { \ + if (layer->AsShadowableLayer()) { \ + MOZ_LOG(LayerManager::GetLog(), LogLevel::Debug, _args); \ + } \ + } while (0) + +#define INVALID_OVERLAY -1 + +//#define ENABLE_FRAME_LATENCY_LOG + +namespace IPC { +template +struct ParamTraits; +} // namespace IPC + +namespace mozilla { + +enum class StyleBorderStyle : uint8_t; + +namespace layers { + +class TextureHost; + +#undef NONE +#undef OPAQUE + +struct LayersId { + uint64_t mId = 0; + + bool IsValid() const { return mId != 0; } + + // Allow explicit cast to a uint64_t for now + explicit operator uint64_t() const { return mId; } + + // Implement some operators so this class can be used as a key in + // stdlib classes. + bool operator<(const LayersId& aOther) const { return mId < aOther.mId; } + + bool operator==(const LayersId& aOther) const { return mId == aOther.mId; } + + bool operator!=(const LayersId& aOther) const { return !(*this == aOther); } + + friend std::ostream& operator<<(std::ostream& aStream, const LayersId& aId); + + // Helper struct that allow this class to be used as a key in + // std::unordered_map like so: + // std::unordered_map myMap; + struct HashFn { + std::size_t operator()(const LayersId& aKey) const { + return std::hash{}(aKey.mId); + } + }; +}; + +template +struct BaseTransactionId { + uint64_t mId = 0; + + bool IsValid() const { return mId != 0; } + + [[nodiscard]] BaseTransactionId Next() const { + return BaseTransactionId{mId + 1}; + } + + [[nodiscard]] BaseTransactionId Prev() const { + return BaseTransactionId{mId - 1}; + } + + int64_t operator-(const BaseTransactionId& aOther) const { + return mId - aOther.mId; + } + + // Allow explicit cast to a uint64_t for now + explicit operator uint64_t() const { return mId; } + + bool operator<(const BaseTransactionId& aOther) const { + return mId < aOther.mId; + } + + bool operator<=(const BaseTransactionId& aOther) const { + return mId <= aOther.mId; + } + + bool operator>(const BaseTransactionId& aOther) const { + return mId > aOther.mId; + } + + bool operator>=(const BaseTransactionId& aOther) const { + return mId >= aOther.mId; + } + + bool operator==(const BaseTransactionId& aOther) const { + return mId == aOther.mId; + } + + bool operator!=(const BaseTransactionId& aOther) const { + return mId != aOther.mId; + } +}; + +class TransactionIdType {}; +typedef BaseTransactionId TransactionId; + +struct LayersObserverEpoch { + uint64_t mId; + + [[nodiscard]] LayersObserverEpoch Next() const { + return LayersObserverEpoch{mId + 1}; + } + + bool operator<=(const LayersObserverEpoch& aOther) const { + return mId <= aOther.mId; + } + + bool operator>=(const LayersObserverEpoch& aOther) const { + return mId >= aOther.mId; + } + + bool operator==(const LayersObserverEpoch& aOther) const { + return mId == aOther.mId; + } + + bool operator!=(const LayersObserverEpoch& aOther) const { + return mId != aOther.mId; + } +}; + +// CompositionOpportunityId is a counter that goes up every time we have an +// opportunity to composite. It increments even on no-op composites (if nothing +// has changed) and while compositing is paused. It does not skip values if a +// composite is delayed. It is meaningful per window. +// This counter is used to differentiate intentionally-skipped video frames from +// unintentionally-skipped video frames: If CompositionOpportunityIds are +// observed by the video in +1 increments, then the video was onscreen the +// entire time and compositing was not paused. But if gaps in +// CompositionOpportunityIds are observed, that must mean that the video was not +// considered during some composition opportunities, because compositing was +// paused or because the video was not part of the on-screen scene. +class CompositionOpportunityType {}; +typedef BaseTransactionId CompositionOpportunityId; + +/// We make different decisions about resource allocation sizes in WebRender +/// depending on whether we are going to render web pages or simpler +/// content in the window. +enum class WindowKind : int8_t { MAIN = 0, SECONDARY, LAST }; + +enum class LayersBackend : int8_t { + LAYERS_NONE = 0, + LAYERS_BASIC, + LAYERS_OPENGL, + LAYERS_D3D11, + LAYERS_CLIENT, + LAYERS_WR, + LAYERS_LAST +}; + +enum class WebRenderBackend : int8_t { HARDWARE = 0, SOFTWARE, LAST }; + +enum class WebRenderCompositor : int8_t { + DRAW = 0, + DIRECT_COMPOSITION, + CORE_ANIMATION, + SOFTWARE, + D3D11, + LAST +}; + +const char* GetLayersBackendName(LayersBackend aBackend); + +enum class TextureType : int8_t { + Unknown = 0, + D3D11, + DIB, + X11, + MacIOSurface, + AndroidNativeWindow, + AndroidHardwareBuffer, + DMABUF, + EGLImage, + Last +}; + +enum class BufferMode : int8_t { BUFFER_NONE, BUFFERED }; + +enum class DrawRegionClip : int8_t { DRAW, NONE }; + +enum class SurfaceMode : int8_t { + SURFACE_NONE = 0, + SURFACE_OPAQUE, + SURFACE_SINGLE_CHANNEL_ALPHA, + SURFACE_COMPONENT_ALPHA +}; + +// clang-format off +MOZ_DEFINE_ENUM_CLASS_WITH_BASE( + ScaleMode, int8_t, ( + SCALE_NONE, + STRETCH +// Unimplemented - PRESERVE_ASPECT_RATIO_CONTAIN +)); +// clang-format on + +struct EventRegions { + // The hit region for a layer contains all areas on the layer that are + // sensitive to events. This region is an over-approximation and may + // contain regions that are not actually sensitive, but any such regions + // will be included in the mDispatchToContentHitRegion. + nsIntRegion mHitRegion; + // The mDispatchToContentHitRegion for a layer contains all areas for + // which the main-thread must be consulted before responding to events. + // This region will be a subregion of mHitRegion. + nsIntRegion mDispatchToContentHitRegion; + + // The following regions represent the touch-action areas of this layer. + // All of these regions are approximations to the true region, but any + // variance between the approximation and the true region is guaranteed + // to be included in the mDispatchToContentHitRegion. + nsIntRegion mNoActionRegion; + nsIntRegion mHorizontalPanRegion; + nsIntRegion mVerticalPanRegion; + + // Set to true if events targeting the dispatch-to-content region + // require target confirmation. + // See CompositorHitTestFlags::eRequiresTargetConfirmation. + // We don't bother tracking a separate region for this (which would + // be a sub-region of the dispatch-to-content region), because the added + // overhead of region computations is not worth it, and because + // EventRegions are going to be deprecated anyways. + bool mDTCRequiresTargetConfirmation; + + EventRegions(); + + explicit EventRegions(nsIntRegion aHitRegion); + + // This constructor takes the maybe-hit region and uses it to update the + // hit region and dispatch-to-content region. It is useful from converting + // from the display item representation to the layer representation. + EventRegions(const nsIntRegion& aHitRegion, + const nsIntRegion& aMaybeHitRegion, + const nsIntRegion& aDispatchToContentRegion, + const nsIntRegion& aNoActionRegion, + const nsIntRegion& aHorizontalPanRegion, + const nsIntRegion& aVerticalPanRegion, + bool aDTCRequiresTargetConfirmation); + + bool operator==(const EventRegions& aRegions) const; + bool operator!=(const EventRegions& aRegions) const; + friend std::ostream& operator<<(std::ostream& aStream, const EventRegions& e); + + void ApplyTranslationAndScale(float aXTrans, float aYTrans, float aXScale, + float aYScale); + void Transform(const gfx::Matrix4x4& aTransform); + void OrWith(const EventRegions& aOther); + + bool IsEmpty() const; + void SetEmpty(); +}; + +// Bit flags that go on a RefLayer and override the +// event regions in the entire subtree below. This is needed for propagating +// various flags across processes since the child-process layout code doesn't +// know about parent-process listeners or CSS rules. +enum EventRegionsOverride { + // The default, no flags set + NoOverride = 0, + // Treat all hit regions in the subtree as dispatch-to-content + ForceDispatchToContent = (1 << 0), + // Treat all hit regions in the subtree as empty + ForceEmptyHitRegion = (1 << 1), + // OR union of all valid bit flags, for use in BitFlagsEnumSerializer + ALL_BITS = (1 << 2) - 1 +}; + +MOZ_ALWAYS_INLINE EventRegionsOverride operator|(EventRegionsOverride a, + EventRegionsOverride b) { + return (EventRegionsOverride)((int)a | (int)b); +} + +MOZ_ALWAYS_INLINE EventRegionsOverride& operator|=(EventRegionsOverride& a, + EventRegionsOverride b) { + a = a | b; + return a; +} + +// Flags used as an argument to functions that dump textures. +enum TextureDumpMode { + Compress, // dump texture with LZ4 compression + DoNotCompress // dump texture uncompressed +}; + +typedef uint32_t TouchBehaviorFlags; + +// Some specialized typedefs of Matrix4x4Typed. +typedef gfx::Matrix4x4Typed + CSSTransformMatrix; +// Several different async transforms can contribute to a layer's transform +// (specifically, an async animation can contribute a transform, and each APZC +// that scrolls a layer can contribute async scroll/zoom and overscroll +// transforms). +// To try to model this with typed units, we represent individual async +// transforms as ParentLayer -> ParentLayer transforms (aliased as +// AsyncTransformComponentMatrix), and we represent the product of all of them +// as a CSSTransformLayer -> ParentLayer transform (aliased as +// AsyncTransformMatrix). To create an AsyncTransformMatrix from component +// matrices, a ViewAs operation is needed. A MultipleAsyncTransforms +// PixelCastJustification is provided for this purpose. +typedef gfx::Matrix4x4Typed + AsyncTransformComponentMatrix; +typedef gfx::Matrix4x4Typed + AsyncTransformMatrix; + +typedef Array BorderColors; +typedef Array BorderCorners; +typedef Array BorderWidths; +typedef Array BorderStyles; + +typedef Maybe MaybeLayerRect; + +// This is used to communicate Layers across IPC channels. The Handle is valid +// for layers in the same PLayerTransaction. Handles are created by +// ClientLayerManager, and are cached in LayerTransactionParent on first use. +class LayerHandle final { + friend struct IPC::ParamTraits; + + public: + LayerHandle() : mHandle(0) {} + LayerHandle(const LayerHandle& aOther) = default; + explicit LayerHandle(uint64_t aHandle) : mHandle(aHandle) {} + bool IsValid() const { return mHandle != 0; } + explicit operator bool() const { return IsValid(); } + bool operator==(const LayerHandle& aOther) const { + return mHandle == aOther.mHandle; + } + uint64_t Value() const { return mHandle; } + + private: + uint64_t mHandle; +}; + +// This is used to communicate Compositables across IPC channels. The Handle is +// valid for layers in the same PLayerTransaction or PImageBridge. Handles are +// created by ClientLayerManager or ImageBridgeChild, and are cached in the +// parent side on first use. +class CompositableHandle final { + friend struct IPC::ParamTraits; + + public: + CompositableHandle() : mHandle(0) {} + CompositableHandle(const CompositableHandle& aOther) = default; + explicit CompositableHandle(uint64_t aHandle) : mHandle(aHandle) {} + bool IsValid() const { return mHandle != 0; } + explicit operator bool() const { return IsValid(); } + bool operator==(const CompositableHandle& aOther) const { + return mHandle == aOther.mHandle; + } + uint64_t Value() const { return mHandle; } + + private: + uint64_t mHandle; +}; + +// clang-format off +MOZ_DEFINE_ENUM_CLASS_WITH_BASE(ScrollDirection, uint32_t, ( + eVertical, + eHorizontal +)); + +typedef EnumSet ScrollDirections; + +constexpr ScrollDirections EitherScrollDirection(ScrollDirection::eVertical,ScrollDirection::eHorizontal); +constexpr ScrollDirections HorizontalScrollDirection(ScrollDirection::eHorizontal); +constexpr ScrollDirections VerticalScollDirection(ScrollDirection::eVertical); + + +MOZ_DEFINE_ENUM_CLASS_WITH_BASE(CompositionPayloadType, uint8_t, ( + /** + * A |CompositionPayload| with this type indicates a key press happened + * before composition and will be used to determine latency between key press + * and presentation in |mozilla::Telemetry::KEYPRESS_PRESENT_LATENCY| + */ + eKeyPress, + + /** + * A |CompositionPayload| with this type indicates that an APZ scroll event + * occurred that will be included in the composition. + */ + eAPZScroll, + + /** + * A |CompositionPayload| with this type indicates that an APZ pinch-to-zoom + * event occurred that will be included in the composition. + */ + eAPZPinchZoom, + + /** + * A |CompositionPayload| with this type indicates that content was painted + * that will be included in the composition. + */ + eContentPaint +)); +// clang-format on + +extern const char* kCompositionPayloadTypeNames[kCompositionPayloadTypeCount]; + +struct CompositionPayload { + bool operator==(const CompositionPayload& aOther) const { + return mType == aOther.mType && mTimeStamp == aOther.mTimeStamp; + } + /* The type of payload that is in this composition */ + CompositionPayloadType mType; + /* When this payload was generated */ + TimeStamp mTimeStamp; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_LAYERSTYPES_H */ diff --git a/gfx/layers/MacIOSurfaceHelpers.cpp b/gfx/layers/MacIOSurfaceHelpers.cpp new file mode 100644 index 0000000000..8976b6ad82 --- /dev/null +++ b/gfx/layers/MacIOSurfaceHelpers.cpp @@ -0,0 +1,185 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "libyuv.h" +#include "MacIOSurfaceHelpers.h" +#include "mozilla/gfx/MacIOSurface.h" +#include "YCbCrUtils.h" + +namespace mozilla { + +using namespace gfx; + +namespace layers { + +#define ALIGNED_32(x) ((x + 31) & ~31) +#define ALIGNEDPTR_32(x) \ + reinterpret_cast((reinterpret_cast(x) + 31) & ~31) + +static already_AddRefed +CreateSourceSurfaceFromLockedMacIOSurface(MacIOSurface* aSurface) { + size_t bytesPerRow = aSurface->GetBytesPerRow(); + size_t ioWidth = aSurface->GetDevicePixelWidth(); + size_t ioHeight = aSurface->GetDevicePixelHeight(); + SurfaceFormat ioFormat = aSurface->GetFormat(); + + if ((ioFormat == SurfaceFormat::NV12 || ioFormat == SurfaceFormat::YUV422) && + (ioWidth > PlanarYCbCrImage::MAX_DIMENSION || + ioHeight > PlanarYCbCrImage::MAX_DIMENSION)) { + return nullptr; + } + + SurfaceFormat format = + (ioFormat == SurfaceFormat::NV12 || ioFormat == SurfaceFormat::YUV422) + ? SurfaceFormat::B8G8R8X8 + : SurfaceFormat::B8G8R8A8; + + RefPtr dataSurface = Factory::CreateDataSourceSurface( + IntSize::Truncate(ioWidth, ioHeight), format); + if (NS_WARN_IF(!dataSurface)) { + return nullptr; + } + + DataSourceSurface::MappedSurface mappedSurface; + if (!dataSurface->Map(DataSourceSurface::WRITE, &mappedSurface)) { + return nullptr; + } + + if (ioFormat == SurfaceFormat::NV12) { + /* Extract and separate the CbCr planes */ + size_t cbCrStride = aSurface->GetBytesPerRow(1); + size_t cbCrWidth = aSurface->GetDevicePixelWidth(1); + size_t cbCrHeight = aSurface->GetDevicePixelHeight(1); + + auto cbPlane = MakeUnique(cbCrWidth * cbCrHeight); + auto crPlane = MakeUnique(cbCrWidth * cbCrHeight); + + uint8_t* src = (uint8_t*)aSurface->GetBaseAddressOfPlane(1); + uint8_t* cbDest = cbPlane.get(); + uint8_t* crDest = crPlane.get(); + + for (size_t i = 0; i < cbCrHeight; i++) { + uint8_t* rowSrc = src + cbCrStride * i; + for (size_t j = 0; j < cbCrWidth; j++) { + *cbDest = *rowSrc; + cbDest++; + rowSrc++; + *crDest = *rowSrc; + crDest++; + rowSrc++; + } + } + + /* Convert to RGB */ + PlanarYCbCrData data; + data.mYChannel = (uint8_t*)aSurface->GetBaseAddressOfPlane(0); + data.mYStride = aSurface->GetBytesPerRow(0); + data.mYSize = IntSize::Truncate(ioWidth, ioHeight); + data.mCbChannel = cbPlane.get(); + data.mCrChannel = crPlane.get(); + data.mCbCrStride = cbCrWidth; + data.mCbCrSize = IntSize::Truncate(cbCrWidth, cbCrHeight); + data.mPicSize = data.mYSize; + data.mYUVColorSpace = aSurface->GetYUVColorSpace(); + data.mColorRange = aSurface->IsFullRange() ? gfx::ColorRange::FULL + : gfx::ColorRange::LIMITED; + + ConvertYCbCrToRGB(data, SurfaceFormat::B8G8R8X8, + IntSize::Truncate(ioWidth, ioHeight), mappedSurface.mData, + mappedSurface.mStride); + } else if (ioFormat == SurfaceFormat::YUV422) { + if (ioWidth == ALIGNED_32(ioWidth)) { + // Optimization when width is aligned to 32. + IntSize size = IntSize::Truncate(ioWidth, ioHeight); + libyuv::ConvertToARGB((uint8_t*)aSurface->GetBaseAddress(), + 0 /* not used */, mappedSurface.mData, + mappedSurface.mStride, 0, 0, size.width, + size.height, size.width, size.height, + libyuv::kRotate0, libyuv::FOURCC_YUYV); + } else { + /* Convert to YV16 */ + size_t cbCrWidth = (ioWidth + 1) >> 1; + size_t cbCrHeight = ioHeight; + // Ensure our stride is a multiple of 32 to allow for memory aligned rows. + size_t cbCrStride = ALIGNED_32(cbCrWidth); + size_t strideDelta = cbCrStride - cbCrWidth; + MOZ_ASSERT(strideDelta <= 31); + + auto yPlane = MakeUnique(cbCrStride * 2 * ioHeight + 31); + auto cbPlane = MakeUnique(cbCrStride * cbCrHeight + 31); + auto crPlane = MakeUnique(cbCrStride * cbCrHeight + 31); + + uint8_t* src = (uint8_t*)aSurface->GetBaseAddress(); + uint8_t* yDest = ALIGNEDPTR_32(yPlane.get()); + uint8_t* cbDest = ALIGNEDPTR_32(cbPlane.get()); + uint8_t* crDest = ALIGNEDPTR_32(crPlane.get()); + + for (size_t i = 0; i < ioHeight; i++) { + uint8_t* rowSrc = src + bytesPerRow * i; + for (size_t j = 0; j < cbCrWidth; j++) { + *yDest = *rowSrc; + yDest++; + rowSrc++; + *cbDest = *rowSrc; + cbDest++; + rowSrc++; + *yDest = *rowSrc; + yDest++; + rowSrc++; + *crDest = *rowSrc; + crDest++; + rowSrc++; + } + if (strideDelta) { + cbDest += strideDelta; + crDest += strideDelta; + yDest += strideDelta << 1; + } + } + + /* Convert to RGB */ + PlanarYCbCrData data; + data.mYChannel = ALIGNEDPTR_32(yPlane.get()); + data.mYStride = cbCrStride * 2; + data.mYSize = IntSize::Truncate(ioWidth, ioHeight); + data.mCbChannel = ALIGNEDPTR_32(cbPlane.get()); + data.mCrChannel = ALIGNEDPTR_32(crPlane.get()); + data.mCbCrStride = cbCrStride; + data.mCbCrSize = IntSize::Truncate(cbCrWidth, cbCrHeight); + data.mPicSize = data.mYSize; + data.mYUVColorSpace = aSurface->GetYUVColorSpace(); + data.mColorRange = aSurface->IsFullRange() ? gfx::ColorRange::FULL + : gfx::ColorRange::LIMITED; + + ConvertYCbCrToRGB(data, SurfaceFormat::B8G8R8X8, + IntSize::Truncate(ioWidth, ioHeight), + mappedSurface.mData, mappedSurface.mStride); + } + } else { + unsigned char* ioData = (unsigned char*)aSurface->GetBaseAddress(); + + for (size_t i = 0; i < ioHeight; ++i) { + memcpy(mappedSurface.mData + i * mappedSurface.mStride, + ioData + i * bytesPerRow, ioWidth * 4); + } + } + + dataSurface->Unmap(); + + return dataSurface.forget(); +} + +already_AddRefed CreateSourceSurfaceFromMacIOSurface( + MacIOSurface* aSurface) { + aSurface->Lock(); + RefPtr result = + CreateSourceSurfaceFromLockedMacIOSurface(aSurface); + aSurface->Unlock(); + return result.forget(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/MacIOSurfaceHelpers.h b/gfx/layers/MacIOSurfaceHelpers.h new file mode 100644 index 0000000000..9aa0a299b4 --- /dev/null +++ b/gfx/layers/MacIOSurfaceHelpers.h @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_MACIOSURFACEHELPERS_H +#define GFX_MACIOSURFACEHELPERS_H + +class MacIOSurface; +template +struct already_AddRefed; + +namespace mozilla { + +namespace gfx { +class SourceSurface; +} + +namespace layers { + +// Unlike MacIOSurface::GetAsSurface, this also handles IOSurface formats +// with multiple planes and does YCbCr to RGB conversion, if necessary. +already_AddRefed CreateSourceSurfaceFromMacIOSurface( + MacIOSurface* aSurface); + +} // namespace layers +} // namespace mozilla + +#endif // GFX_MACIOSURFACEHELPERS_H diff --git a/gfx/layers/MacIOSurfaceImage.cpp b/gfx/layers/MacIOSurfaceImage.cpp new file mode 100644 index 0000000000..8a155bc5cc --- /dev/null +++ b/gfx/layers/MacIOSurfaceImage.cpp @@ -0,0 +1,150 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MacIOSurfaceHelpers.h" +#include "MacIOSurfaceImage.h" +#include "gfxPlatform.h" +#include "mozilla/layers/CompositableClient.h" +#include "mozilla/layers/CompositableForwarder.h" +#include "mozilla/layers/MacIOSurfaceTextureClientOGL.h" +#include "mozilla/layers/TextureForwarder.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/UniquePtr.h" +#include "YCbCrUtils.h" + +using namespace mozilla::layers; +using namespace mozilla::gfx; + +TextureClient* MacIOSurfaceImage::GetTextureClient( + KnowsCompositor* aKnowsCompositor) { + if (!mTextureClient) { + BackendType backend = BackendType::NONE; + mTextureClient = TextureClient::CreateWithData( + MacIOSurfaceTextureData::Create(mSurface, backend), + TextureFlags::DEFAULT, aKnowsCompositor->GetTextureForwarder()); + } + return mTextureClient; +} + +already_AddRefed MacIOSurfaceImage::GetAsSourceSurface() { + return CreateSourceSurfaceFromMacIOSurface(mSurface); +} + +bool MacIOSurfaceImage::SetData(ImageContainer* aContainer, + const PlanarYCbCrData& aData) { + MOZ_ASSERT(!mSurface); + + if (aData.mYSkip != 0 || aData.mCbSkip != 0 || aData.mCrSkip != 0 || + !(aData.mYUVColorSpace == YUVColorSpace::BT601 || + aData.mYUVColorSpace == YUVColorSpace::BT709) || + !(aData.mColorRange == ColorRange::FULL || + aData.mColorRange == ColorRange::LIMITED) || + aData.mColorDepth != ColorDepth::COLOR_8) { + return false; + } + + if (aData.mCbCrSize.width * 2 != aData.mYSize.width) { + return false; + } + + // We can only support 4:2:2 and 4:2:0 formats currently. + if (aData.mCbCrSize.height != aData.mYSize.height && + aData.mCbCrSize.height * 2 != aData.mYSize.height) { + return false; + } + + RefPtr allocator = + aContainer->GetMacIOSurfaceRecycleAllocator(); + + RefPtr surf = allocator->Allocate( + aData.mYSize, aData.mCbCrSize, aData.mYUVColorSpace, aData.mColorRange); + + surf->Lock(false); + + // If the CbCrSize's height is half of the YSize's height, then we'll + // need to duplicate the CbCr data on every second row. + size_t heightScale = aData.mYSize.height / aData.mCbCrSize.height; + + MOZ_ASSERT(surf->GetFormat() == SurfaceFormat::YUV422); + + // The underlying IOSurface has format kCVPixelFormatType_422YpCbCr8FullRange + // or kCVPixelFormatType_422YpCbCr8_yuvs, which uses a 4:2:2 Y`0 Cb Y`1 Cr + // layout. See CVPixelBuffer.h for the full list of format descriptions. + MOZ_ASSERT(aData.mYSize.height > 0); + uint8_t* dst = (uint8_t*)surf->GetBaseAddressOfPlane(0); + size_t stride = surf->GetBytesPerRow(0); + for (size_t i = 0; i < (size_t)aData.mYSize.height; i++) { + // Compute the row addresses. If the input was 4:2:0, then + // we divide i by 2, so that each source row of CbCr maps to + // two dest rows. + uint8_t* rowYSrc = aData.mYChannel + aData.mYStride * i; + uint8_t* rowCbSrc = + aData.mCbChannel + aData.mCbCrStride * (i / heightScale); + uint8_t* rowCrSrc = + aData.mCrChannel + aData.mCbCrStride * (i / heightScale); + uint8_t* rowDst = dst + stride * i; + + // Iterate across the CbCr width (which we have guaranteed to be half of + // the surface width), and write two 16bit pixels each time. + for (size_t j = 0; j < (size_t)aData.mCbCrSize.width; j++) { + *rowDst = *rowYSrc; + rowDst++; + rowYSrc++; + + *rowDst = *rowCbSrc; + rowDst++; + rowCbSrc++; + + *rowDst = *rowYSrc; + rowDst++; + rowYSrc++; + + *rowDst = *rowCrSrc; + rowDst++; + rowCrSrc++; + } + } + + surf->Unlock(false); + mSurface = surf; + mPictureRect = aData.GetPictureRect(); + return true; +} + +already_AddRefed MacIOSurfaceRecycleAllocator::Allocate( + const gfx::IntSize aYSize, const gfx::IntSize& aCbCrSize, + gfx::YUVColorSpace aYUVColorSpace, gfx::ColorRange aColorRange) { + nsTArray> surfaces = std::move(mSurfaces); + RefPtr result; + for (auto& surf : surfaces) { + // If the surface size has changed, then discard any surfaces of the old + // size. + if (::IOSurfaceGetWidthOfPlane(surf.get(), 0) != (size_t)aYSize.width || + ::IOSurfaceGetHeightOfPlane(surf.get(), 0) != (size_t)aYSize.height) { + continue; + } + + // Only construct a MacIOSurface object when we find one that isn't + // in-use, since the constructor adds a usage ref. + if (!result && !::IOSurfaceIsInUse(surf.get())) { + result = new MacIOSurface(surf, 1.0, false, aYUVColorSpace); + } + + mSurfaces.AppendElement(surf); + } + + if (!result) { + result = + MacIOSurface::CreateYUV422Surface(aYSize, aYUVColorSpace, aColorRange); + + if (mSurfaces.Length() < + StaticPrefs::layers_iosurfaceimage_recycle_limit()) { + mSurfaces.AppendElement(result->GetIOSurfaceRef()); + } + } + + return result.forget(); +} diff --git a/gfx/layers/MacIOSurfaceImage.h b/gfx/layers/MacIOSurfaceImage.h new file mode 100644 index 0000000000..b67757ad85 --- /dev/null +++ b/gfx/layers/MacIOSurfaceImage.h @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_MACIOSURFACEIMAGE_H +#define GFX_MACIOSURFACEIMAGE_H + +#include "ImageContainer.h" +#include "mozilla/gfx/MacIOSurface.h" +#include "mozilla/gfx/Point.h" +#include "mozilla/layers/TextureClient.h" + +namespace mozilla { + +namespace layers { + +class MacIOSurfaceImage : public Image { + public: + explicit MacIOSurfaceImage(MacIOSurface* aSurface) + : Image(nullptr, ImageFormat::MAC_IOSURFACE), mSurface(aSurface) { + if (aSurface) { + mPictureRect = gfx::IntRect( + gfx::IntPoint{}, gfx::IntSize(aSurface->GetDevicePixelWidth(0), + aSurface->GetDevicePixelHeight(0))); + } + } + + bool SetData(ImageContainer* aContainer, const PlanarYCbCrData& aData); + + MacIOSurface* GetSurface() { return mSurface; } + + gfx::IntSize GetSize() const override { + return gfx::IntSize::Truncate(mSurface->GetDevicePixelWidth(), + mSurface->GetDevicePixelHeight()); + } + + already_AddRefed GetAsSourceSurface() override; + + TextureClient* GetTextureClient(KnowsCompositor* aKnowsCompositor) override; + + MacIOSurfaceImage* AsMacIOSurfaceImage() override { return this; } + + gfx::IntRect GetPictureRect() const override { return mPictureRect; } + + private: + RefPtr mSurface; + RefPtr mTextureClient; + gfx::IntRect mPictureRect; +}; + +class MacIOSurfaceRecycleAllocator { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MacIOSurfaceRecycleAllocator) + + already_AddRefed Allocate(const gfx::IntSize aYSize, + const gfx::IntSize& aCbCrSize, + gfx::YUVColorSpace aYUVColorSpace, + gfx::ColorRange aColorRange); + + private: + ~MacIOSurfaceRecycleAllocator() = default; + + nsTArray> mSurfaces; +}; + +} // namespace layers +} // namespace mozilla + +#endif // GFX_SHAREDTEXTUREIMAGE_H diff --git a/gfx/layers/MemoryPressureObserver.cpp b/gfx/layers/MemoryPressureObserver.cpp new file mode 100644 index 0000000000..16c9ea4b1e --- /dev/null +++ b/gfx/layers/MemoryPressureObserver.cpp @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MemoryPressureObserver.h" + +#include "mozilla/Services.h" +#include "nsCOMPtr.h" +#include "nsDependentString.h" +#include "nsIObserverService.h" +#include "nsLiteralString.h" + +namespace mozilla { +namespace layers { + +MemoryPressureObserver::MemoryPressureObserver( + MemoryPressureListener* aListener) + : mListener(aListener) {} + +MemoryPressureObserver::~MemoryPressureObserver() { + // If this assertion is hit we probably forgot to unregister the observer. + MOZ_ASSERT(!mListener); +} + +already_AddRefed MemoryPressureObserver::Create( + MemoryPressureListener* aListener) { + nsCOMPtr service = services::GetObserverService(); + + if (!service) { + return nullptr; + } + + RefPtr observer = + new MemoryPressureObserver(aListener); + + bool useWeakRef = false; + service->AddObserver(observer, "memory-pressure", useWeakRef); + + return observer.forget(); +} + +void MemoryPressureObserver::Unregister() { + if (!mListener) { + return; + } + + nsCOMPtr service = services::GetObserverService(); + if (service) { + service->RemoveObserver(this, "memory-pressure"); + } + + mListener = nullptr; +} + +NS_IMETHODIMP +MemoryPressureObserver::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (mListener && strcmp(aTopic, "memory-pressure") == 0) { + MemoryPressureReason reason = MemoryPressureReason::LOW_MEMORY; + auto reason_string = nsDependentString(aData); + if (StringBeginsWith(reason_string, u"low-memory-ongoing"_ns)) { + reason = MemoryPressureReason::LOW_MEMORY_ONGOING; + } else if (StringBeginsWith(reason_string, u"heap-minimize"_ns)) { + reason = MemoryPressureReason::HEAP_MINIMIZE; + } + mListener->OnMemoryPressure(reason); + } + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(MemoryPressureObserver, nsIObserver) + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/MemoryPressureObserver.h b/gfx/layers/MemoryPressureObserver.h new file mode 100644 index 0000000000..27048c99ed --- /dev/null +++ b/gfx/layers/MemoryPressureObserver.h @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_LAYERS_MEMORYPRESSUREOBSERVER_H +#define MOZILLA_LAYERS_MEMORYPRESSUREOBSERVER_H + +#include "nsIObserver.h" + +namespace mozilla { +namespace layers { + +// A simple memory pressure observer implementation born out of the realization +// that almost all of our memory pressure observers do exactly the same thing. +// +// The intended way to use it is to have the class that nees to react on memory +// pressure inherit the MemoryPressureListener interface and own a strong +// reference to a MemoryPressureListener object. +// Call Unregister on the listener in the destructor of your class or whenever +// you do not which to receive the notification anymore, otherwise the listener +// will be held alive by the observer service (leak) and keep a dangling pointer +// to your class. + +/// See nsIMemory.idl +enum class MemoryPressureReason { + LOW_MEMORY, + LOW_MEMORY_ONGOING, + HEAP_MINIMIZE, +}; + +class MemoryPressureListener { + public: + virtual void OnMemoryPressure(MemoryPressureReason aWhy) = 0; +}; + +class MemoryPressureObserver final : public nsIObserver { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + // Returns null if anything goes wrong. + static already_AddRefed Create( + MemoryPressureListener* aListener); + + void Unregister(); + + private: + explicit MemoryPressureObserver(MemoryPressureListener* aListener); + virtual ~MemoryPressureObserver(); + MemoryPressureListener* mListener; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/NativeLayer.h b/gfx/layers/NativeLayer.h new file mode 100644 index 0000000000..56b70148da --- /dev/null +++ b/gfx/layers/NativeLayer.h @@ -0,0 +1,231 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_NativeLayer_h +#define mozilla_layers_NativeLayer_h + +#include "mozilla/Maybe.h" +#include "mozilla/Range.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/gfx/Types.h" +#include "mozilla/layers/ScreenshotGrabber.h" + +#include "GLTypes.h" +#include "nsISupportsImpl.h" +#include "nsRegion.h" + +namespace mozilla { + +namespace gl { +class GLContext; +} // namespace gl + +namespace wr { +class RenderTextureHost; +} + +namespace layers { + +class NativeLayer; +class NativeLayerCA; +class NativeLayerRootSnapshotter; +class SurfacePoolHandle; + +// NativeLayerRoot and NativeLayer allow building up a flat layer "tree" of +// sibling layers. These layers provide a cross-platform abstraction for the +// platform's native layers, such as CoreAnimation layers on macOS. +// Every layer has a rectangle that describes its position and size in the +// window. The native layer root is usually be created by the window, and then +// the compositing subsystem uses it to create and place the actual layers. +class NativeLayerRoot { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NativeLayerRoot) + + virtual already_AddRefed CreateLayer( + const gfx::IntSize& aSize, bool aIsOpaque, + SurfacePoolHandle* aSurfacePoolHandle) = 0; + virtual already_AddRefed CreateLayerForExternalTexture( + bool aIsOpaque) = 0; + + virtual void AppendLayer(NativeLayer* aLayer) = 0; + virtual void RemoveLayer(NativeLayer* aLayer) = 0; + virtual void SetLayers(const nsTArray>& aLayers) = 0; + + // Publish the layer changes to the screen. Returns whether the commit was + // successful. + virtual bool CommitToScreen() = 0; + + // Returns a new NativeLayerRootSnapshotter that can be used to read back the + // visual output of this NativeLayerRoot. The snapshotter needs to be + // destroyed on the same thread that CreateSnapshotter() was called on. Only + // one snapshotter per NativeLayerRoot can be in existence at any given time. + // CreateSnapshotter() makes sure of this and crashes if called at a time at + // which there still exists a snapshotter for this NativeLayerRoot. + virtual UniquePtr CreateSnapshotter() { + return nullptr; + } + + protected: + virtual ~NativeLayerRoot() = default; +}; + +// Allows reading back the visual output of a NativeLayerRoot. +// Can only be used on a single thread, unlike NativeLayerRoot. +// Holds a strong reference to the NativeLayerRoot that created it. +// On Mac, this owns a GLContext, which wants to be created and destroyed on the +// same thread. +class NativeLayerRootSnapshotter : public profiler_screenshots::Window { + public: + virtual ~NativeLayerRootSnapshotter() = default; + + // Reads the composited result of the NativeLayer tree into aReadbackBuffer, + // synchronously. Should only be called right after a call to CommitToScreen() + // - in that case it is guaranteed to read back exactly the NativeLayer state + // that was committed. If called at other times, this API does not define + // whether the observed state includes NativeLayer modifications which have + // not been committed. (The macOS implementation will include those pending + // modifications by doing an offscreen commit.) + // The readback buffer's stride is assumed to be aReadbackSize.width * 4. Only + // BGRA is supported. + virtual bool ReadbackPixels(const gfx::IntSize& aReadbackSize, + gfx::SurfaceFormat aReadbackFormat, + const Range& aReadbackBuffer) = 0; +}; + +// Represents a native layer. Native layers, such as CoreAnimation layers on +// macOS, are used to put pixels on the screen and to refresh and manipulate +// the visual contents of a window efficiently. For example, drawing to a layer +// once and then displaying the layer for multiple frames while moving it to +// different positions will be more efficient than drawing into a window (or a +// non-moving layer) multiple times with different internal offsets. +// There are two sources of "work" for a given composited frame: 1) Our own +// drawing (such as OpenGL compositing into a window or layer) and 2) the +// compositing window manager's work to update the screen. Every pixel we draw +// needs to be copied to the screen by the window manager. This suggests two +// avenues for reducing the work load for a given frame: Drawing fewer pixels +// ourselves, and making the window manager copy fewer pixels to the screen. +// Smart use of native layers allows reducing both work loads: If a visual +// change can be expressed purely as a layer attribute change (such as a change +// in the layer's position), this lets us eliminate our own drawing for that +// change. And secondly, manipulating a small layer rather than a large layer +// will reduce the window manager's work for that frame because it'll only copy +// the pixels of the small layer to the screen. +class NativeLayer { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NativeLayer) + + virtual NativeLayerCA* AsNativeLayerCA() { return nullptr; } + + // The size and opaqueness of a layer are supplied during layer creation and + // never change. + virtual gfx::IntSize GetSize() = 0; + virtual bool IsOpaque() = 0; + + // The location of the layer, in integer device pixels. + // This is applied to the layer, before the transform is applied. + virtual void SetPosition(const gfx::IntPoint& aPosition) = 0; + virtual gfx::IntPoint GetPosition() = 0; + + // Sets a transformation to apply to the Layer. This gets applied to + // coordinates with the position applied, but before clipping is + // applied. + virtual void SetTransform(const gfx::Matrix4x4& aTransform) = 0; + virtual gfx::Matrix4x4 GetTransform() = 0; + + virtual gfx::IntRect GetRect() = 0; + + // Set an optional clip rect on the layer. The clip rect is in post-transform + // coordinate space + virtual void SetClipRect(const Maybe& aClipRect) = 0; + virtual Maybe ClipRect() = 0; + + // Returns the "display rect", in content coordinates, of the current front + // surface. This rect acts as an extra clip and prevents invalid content from + // getting to the screen. The display rect starts out empty before the first + // call to NextSurface*. Note the different coordinate space from the regular + // clip rect: the clip rect is "outside" the layer position, the display rect + // is "inside" the layer position (moves with the layer). + virtual gfx::IntRect CurrentSurfaceDisplayRect() = 0; + + // Whether the surface contents are flipped vertically compared to this + // layer's coordinate system. Can be set on any thread at any time. + virtual void SetSurfaceIsFlipped(bool aIsFlipped) = 0; + virtual bool SurfaceIsFlipped() = 0; + + virtual void SetSamplingFilter(gfx::SamplingFilter aSamplingFilter) = 0; + + // Returns a DrawTarget. The size of the DrawTarget will be the same as the + // size of this layer. The caller should draw to that DrawTarget, then drop + // its reference to the DrawTarget, and then call NotifySurfaceReady(). It can + // limit its drawing to aUpdateRegion (which is in the DrawTarget's device + // space). After a call to NextSurface*, NextSurface* must not be called again + // until after NotifySurfaceReady has been called. Can be called on any + // thread. When used from multiple threads, callers need to make sure that + // they still only call NextSurface* and NotifySurfaceReady alternatingly and + // not in any other order. aUpdateRegion and aDisplayRect are in "content + // coordinates" and must not extend beyond the layer size. If aDisplayRect + // contains parts that were not valid before, then those parts must be updated + // (must be part of aUpdateRegion), so that the entirety of aDisplayRect is + // valid after the update. The display rect determines the parts of the + // surface that will be shown; this allows using surfaces with only + // partially-valid content, as long as none of the invalid content is included + // in the display rect. + virtual RefPtr NextSurfaceAsDrawTarget( + const gfx::IntRect& aDisplayRect, const gfx::IntRegion& aUpdateRegion, + gfx::BackendType aBackendType) = 0; + + // Returns a GLuint for a framebuffer that can be used for drawing to the + // surface. The size of the framebuffer will be the same as the size of this + // layer. If aNeedsDepth is true, the framebuffer is created with a depth + // buffer. + // The framebuffer's depth buffer (if present) may be shared with other + // framebuffers of the same size, even from entirely different NativeLayer + // objects. The caller should not assume anything about the depth buffer's + // existing contents (i.e. it should clear it at the beginning of the draw). + // Callers should draw to one layer at a time, such that there is no + // interleaved drawing to different framebuffers that could be tripped up by + // the sharing. + // The caller should draw to the framebuffer, unbind it, and then call + // NotifySurfaceReady(). It can limit its drawing to aUpdateRegion (which is + // in the framebuffer's device space, possibly "upside down" if + // SurfaceIsFlipped()). + // The framebuffer will be created in the GLContext that this layer's + // SurfacePoolHandle was created for. + // After a call to NextSurface*, NextSurface* must not be called again until + // after NotifySurfaceReady has been called. Can be called on any thread. When + // used from multiple threads, callers need to make sure that they still only + // call NextSurface and NotifySurfaceReady alternatingly and not in any other + // order. + // aUpdateRegion and aDisplayRect are in "content coordinates" and must not + // extend beyond the layer size. If aDisplayRect contains parts that were not + // valid before, then those parts must be updated (must be part of + // aUpdateRegion), so that the entirety of aDisplayRect is valid after the + // update. The display rect determines the parts of the surface that will be + // shown; this allows using surfaces with only partially-valid content, as + // long as none of the invalid content is included in the display rect. + virtual Maybe NextSurfaceAsFramebuffer( + const gfx::IntRect& aDisplayRect, const gfx::IntRegion& aUpdateRegion, + bool aNeedsDepth) = 0; + + // Indicates that the surface which has been returned from the most recent + // call to NextSurface* is now finished being drawn to and can be displayed on + // the screen. Resets the invalid region on the surface to the empty region. + virtual void NotifySurfaceReady() = 0; + + // If you know that this layer will likely not draw any more frames, then it's + // good to call DiscardBackbuffers in order to save memory and allow other + // layer's to pick up the released surfaces from the pool. + virtual void DiscardBackbuffers() = 0; + + virtual void AttachExternalImage(wr::RenderTextureHost* aExternalImage) = 0; + + protected: + virtual ~NativeLayer() = default; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_NativeLayer_h diff --git a/gfx/layers/NativeLayerCA.h b/gfx/layers/NativeLayerCA.h new file mode 100644 index 0000000000..bc71f24606 --- /dev/null +++ b/gfx/layers/NativeLayerCA.h @@ -0,0 +1,396 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_NativeLayerCA_h +#define mozilla_layers_NativeLayerCA_h + +#include + +#include +#include + +#include "mozilla/Mutex.h" + +#include "mozilla/gfx/MacIOSurface.h" +#include "mozilla/layers/NativeLayer.h" +#include "CFTypeRefPtr.h" +#include "nsRegion.h" +#include "nsISupportsImpl.h" + +#ifdef __OBJC__ +@class CALayer; +#else +typedef void CALayer; +#endif + +namespace mozilla { + +namespace gl { +class GLContextCGL; +class MozFramebuffer; +} // namespace gl +namespace wr { +class RenderMacIOSurfaceTextureHost; +} // namespace wr + +namespace layers { + +class NativeLayerRootSnapshotterCA; +class SurfacePoolHandleCA; + +// NativeLayerRootCA is the CoreAnimation implementation of the NativeLayerRoot +// interface. A NativeLayerRootCA is created by the widget around an existing +// CALayer with a call to CreateForCALayer - this CALayer is the root of the +// "onscreen" representation of this layer tree. +// All methods can be called from any thread, there is internal locking. +// All effects from mutating methods are buffered locally and don't modify the +// underlying CoreAnimation layers until CommitToScreen() is called. This +// ensures that the modifications happen on the right thread. +// +// More specifically: During normal operation, screen updates are driven from a +// compositing thread. On this thread, the layers are created / destroyed, their +// contents are painted, and the result is committed to the screen. However, +// there are some scenarios that need to involve the main thread, most notably +// window resizing: During a window resize, we still need the drawing part to +// happen on the compositing thread, but the modifications to the underlying +// CALayers need to happen on the main thread, once compositing is done. +// +// NativeLayerRootCA + NativeLayerCA create and maintain *two* CALayer tree +// representations: An "onscreen" representation and an "offscreen" +// representation. These representations are updated via calls to +// CommitToScreen() and CommitOffscreen(), respectively. The reason for having +// two representations is the following: Our implementation of the snapshotter +// API uses CARenderer, which lets us render the composited result of our layer +// tree into a GPU buffer. But CARenderer requires "ownership" of the rendered +// CALayers in the sense that it associates the CALayers with a local +// "CAContext". A CALayer can only be associated with one CAContext at any time. +// If we wanted te render our *onscreen* CALayers with CARenderer, we would need +// to remove them from the window, reparent them to the CARenderer, render them, +// and then put them back into the window. This would lead to a visible flashing +// effect. To solve this problem, we build two CALayer representations, so that +// one representation can stay inside the window and the other can stay attached +// to the CARenderer. +class NativeLayerRootCA : public NativeLayerRoot { + public: + static already_AddRefed CreateForCALayer(CALayer* aLayer); + + // Can be called on any thread at any point. Returns whether comitting was + // successful. Will return false if called off the main thread while + // off-main-thread commits are suspended. + bool CommitToScreen() override; + + void CommitOffscreen(); + void OnNativeLayerRootSnapshotterDestroyed( + NativeLayerRootSnapshotterCA* aNativeLayerRootSnapshotter); + + // Enters a mode during which CommitToScreen(), when called on a non-main + // thread, will not apply any updates to the CALayer tree. + void SuspendOffMainThreadCommits(); + + // Exits the mode entered by SuspendOffMainThreadCommits(). + // Returns true if the last CommitToScreen() was canceled due to suspension, + // indicating that another call to CommitToScreen() is needed. + bool UnsuspendOffMainThreadCommits(); + + bool AreOffMainThreadCommitsSuspended(); + + enum class WhichRepresentation : uint8_t { ONSCREEN, OFFSCREEN }; + + // Overridden methods + already_AddRefed CreateLayer( + const gfx::IntSize& aSize, bool aIsOpaque, + SurfacePoolHandle* aSurfacePoolHandle) override; + void AppendLayer(NativeLayer* aLayer) override; + void RemoveLayer(NativeLayer* aLayer) override; + void SetLayers(const nsTArray>& aLayers) override; + UniquePtr CreateSnapshotter() override; + + void SetBackingScale(float aBackingScale); + float BackingScale(); + + already_AddRefed CreateLayerForExternalTexture( + bool aIsOpaque) override; + + protected: + explicit NativeLayerRootCA(CALayer* aLayer); + ~NativeLayerRootCA() override; + + struct Representation { + explicit Representation(CALayer* aRootCALayer); + ~Representation(); + void Commit(WhichRepresentation aRepresentation, + const nsTArray>& aSublayers); + CALayer* mRootCALayer = nullptr; // strong + bool mMutated = false; + }; + + template + void ForAllRepresentations(F aFn); + + Mutex mMutex; // protects all other fields + Representation mOnscreenRepresentation; + Representation mOffscreenRepresentation; + NativeLayerRootSnapshotterCA* mWeakSnapshotter = nullptr; + nsTArray> mSublayers; // in z-order + float mBackingScale = 1.0f; + bool mMutated = false; + + // While mOffMainThreadCommitsSuspended is true, no commits + // should happen on a non-main thread, because they might race with + // main-thread driven updates such as window shape changes, and cause + // glitches. + bool mOffMainThreadCommitsSuspended = false; + + // Set to true if CommitToScreen() was aborted because of commit suspension. + // Set to false when CommitToScreen() completes successfully. When true, + // indicates that CommitToScreen() needs to be called at the next available + // opportunity. + bool mCommitPending = false; +}; + +class RenderSourceNLRS; + +class NativeLayerRootSnapshotterCA final : public NativeLayerRootSnapshotter { + public: + static UniquePtr Create( + NativeLayerRootCA* aLayerRoot, CALayer* aRootCALayer); + virtual ~NativeLayerRootSnapshotterCA(); + + bool ReadbackPixels(const gfx::IntSize& aReadbackSize, + gfx::SurfaceFormat aReadbackFormat, + const Range& aReadbackBuffer) override; + already_AddRefed GetWindowContents( + const gfx::IntSize& aWindowSize) override; + already_AddRefed CreateDownscaleTarget( + const gfx::IntSize& aSize) override; + already_AddRefed + CreateAsyncReadbackBuffer(const gfx::IntSize& aSize) override; + + protected: + NativeLayerRootSnapshotterCA(NativeLayerRootCA* aLayerRoot, + RefPtr&& aGL, + CALayer* aRootCALayer); + void UpdateSnapshot(const gfx::IntSize& aSize); + + RefPtr mLayerRoot; + RefPtr mGL; + + // Can be null. Created and updated in UpdateSnapshot. + RefPtr mSnapshot; + CARenderer* mRenderer = nullptr; // strong +}; + +// NativeLayerCA wraps a CALayer and lets you draw to it. It ensures that only +// fully-drawn frames make their way to the screen, by maintaining a swap chain +// of IOSurfaces. +// All calls to mutating methods are buffered, and don't take effect on the +// underlying CoreAnimation layers until ApplyChanges() is called. +// The two most important methods are NextSurface and NotifySurfaceReady: +// NextSurface takes an available surface from the swap chain or creates a new +// surface if necessary. This surface can then be drawn to. Once drawing is +// finished, NotifySurfaceReady marks the surface as ready. This surface is +// committed to the layer during the next call to ApplyChanges(). +// The swap chain keeps track of invalid areas within the surfaces. +class NativeLayerCA : public NativeLayer { + public: + virtual NativeLayerCA* AsNativeLayerCA() override { return this; } + + // Overridden methods + gfx::IntSize GetSize() override; + void SetPosition(const gfx::IntPoint& aPosition) override; + gfx::IntPoint GetPosition() override; + void SetTransform(const gfx::Matrix4x4& aTransform) override; + gfx::Matrix4x4 GetTransform() override; + gfx::IntRect GetRect() override; + void SetSamplingFilter(gfx::SamplingFilter aSamplingFilter) override; + RefPtr NextSurfaceAsDrawTarget( + const gfx::IntRect& aDisplayRect, const gfx::IntRegion& aUpdateRegion, + gfx::BackendType aBackendType) override; + Maybe NextSurfaceAsFramebuffer(const gfx::IntRect& aDisplayRect, + const gfx::IntRegion& aUpdateRegion, + bool aNeedsDepth) override; + void NotifySurfaceReady() override; + void DiscardBackbuffers() override; + bool IsOpaque() override; + void SetClipRect(const Maybe& aClipRect) override; + Maybe ClipRect() override; + gfx::IntRect CurrentSurfaceDisplayRect() override; + void SetSurfaceIsFlipped(bool aIsFlipped) override; + bool SurfaceIsFlipped() override; + + void AttachExternalImage(wr::RenderTextureHost* aExternalImage) override; + + protected: + friend class NativeLayerRootCA; + + NativeLayerCA(const gfx::IntSize& aSize, bool aIsOpaque, + SurfacePoolHandleCA* aSurfacePoolHandle); + explicit NativeLayerCA(bool aIsOpaque); + ~NativeLayerCA() override; + + // Gets the next surface for drawing from our swap chain and stores it in + // mInProgressSurface. Returns whether this was successful. + // mInProgressSurface is guaranteed to be not in use by the window server. + // After a call to NextSurface, NextSurface must not be called again until + // after NotifySurfaceReady has been called. Can be called on any thread. When + // used from multiple threads, callers need to make sure that they still only + // call NextSurface and NotifySurfaceReady alternatingly and not in any other + // order. + bool NextSurface(const MutexAutoLock&); + + // To be called by NativeLayerRootCA: + typedef NativeLayerRootCA::WhichRepresentation WhichRepresentation; + CALayer* UnderlyingCALayer(WhichRepresentation aRepresentation); + void ApplyChanges(WhichRepresentation aRepresentation); + void SetBackingScale(float aBackingScale); + + // Invalidates the specified region in all surfaces that are tracked by this + // layer. + void InvalidateRegionThroughoutSwapchain(const MutexAutoLock&, + const gfx::IntRegion& aRegion); + + GLuint GetOrCreateFramebufferForSurface(const MutexAutoLock&, + CFTypeRefPtr aSurface, + bool aNeedsDepth); + + // Invalidate aUpdateRegion and make sure that mInProgressSurface retains any + // valid content from the previous surface outside of aUpdateRegion, so that + // only aUpdateRegion needs to be drawn. If content needs to be copied, + // aCopyFn is called to do the copying. + // aCopyFn: Fn(CFTypeRefPtr aValidSourceIOSurface, + // const gfx::IntRegion& aCopyRegion) -> void + template + void HandlePartialUpdate(const MutexAutoLock&, + const gfx::IntRect& aDisplayRect, + const gfx::IntRegion& aUpdateRegion, F&& aCopyFn); + + struct SurfaceWithInvalidRegion { + CFTypeRefPtr mSurface; + gfx::IntRegion mInvalidRegion; + }; + + struct SurfaceWithInvalidRegionAndCheckCount { + SurfaceWithInvalidRegion mEntry; + uint32_t mCheckCount; // The number of calls to IOSurfaceIsInUse + }; + + Maybe GetUnusedSurfaceAndCleanUp( + const MutexAutoLock&); + + // Wraps one CALayer representation of this NativeLayer. + struct Representation { + ~Representation(); + + CALayer* UnderlyingCALayer() { return mWrappingCALayer; } + + // Applies buffered changes to the native CALayers. The contract with the + // caller is as follows: If any of these values have changed since the last + // call to ApplyChanges, mMutated[Field] needs to have been set to true + // before the call. + void ApplyChanges(const gfx::IntSize& aSize, bool aIsOpaque, + const gfx::IntPoint& aPosition, + const gfx::Matrix4x4& aTransform, + const gfx::IntRect& aDisplayRect, + const Maybe& aClipRect, float aBackingScale, + bool aSurfaceIsFlipped, + gfx::SamplingFilter aSamplingFilter, + CFTypeRefPtr aFrontSurface); + + // Lazily initialized by first call to ApplyChanges. mWrappingLayer is the + // layer that applies the intersection of mDisplayRect and mClipRect (if + // set), and mContentCALayer is the layer that hosts the IOSurface. We do + // not share clip layers between consecutive NativeLayerCA objects with the + // same clip rect. + CALayer* mWrappingCALayer = nullptr; // strong + CALayer* mContentCALayer = nullptr; // strong + CALayer* mOpaquenessTintLayer = nullptr; // strong + + bool mMutatedPosition = true; + bool mMutatedTransform = true; + bool mMutatedDisplayRect = true; + bool mMutatedClipRect = true; + bool mMutatedBackingScale = true; + bool mMutatedSize = true; + bool mMutatedSurfaceIsFlipped = true; + bool mMutatedFrontSurface = true; + bool mMutatedSamplingFilter = true; + }; + + Representation& GetRepresentation(WhichRepresentation aRepresentation); + template + void ForAllRepresentations(F aFn); + + // Controls access to all fields of this class. + Mutex mMutex; + + // Each IOSurface is initially created inside NextSurface. + // The surface stays alive until the recycling mechanism in NextSurface + // determines it is no longer needed (because the swap chain has grown too + // long) or until DiscardBackbuffers() is called or the layer is destroyed. + // During the surface's lifetime, it will continuously move through the fields + // mInProgressSurface, mFrontSurface, and back to front through the mSurfaces + // queue: + // + // mSurfaces.front() + // ------[NextSurface()]-----> mInProgressSurface + // --[NotifySurfaceReady()]--> mFrontSurface + // --[NotifySurfaceReady()]--> mSurfaces.back() --> .... --> + // mSurfaces.front() + // + // We mark an IOSurface as "in use" as long as it is either in + // mInProgressSurface. When it is in mFrontSurface or in the mSurfaces queue, + // it is not marked as "in use" by us - but it can be "in use" by the window + // server. Consequently, IOSurfaceIsInUse on a surface from mSurfaces reflects + // whether the window server is still reading from the surface, and we can use + // this indicator to decide when to recycle the surface. + // + // Users of NativeLayerCA normally proceed in this order: + // 1. Begin a frame by calling NextSurface to get the surface. + // 2. Draw to the surface. + // 3. Mark the surface as done by calling NotifySurfaceReady. + // 4. Call NativeLayerRoot::CommitToScreen(), which calls ApplyChanges() + // during a CATransaction. + + // The surface we returned from the most recent call to NextSurface, before + // the matching call to NotifySurfaceReady. + // Will only be Some() between calls to NextSurface and NotifySurfaceReady. + Maybe mInProgressSurface; + Maybe mInProgressUpdateRegion; + Maybe mInProgressDisplayRect; + + // The surface that the most recent call to NotifySurfaceReady was for. + // Will be Some() after the first call to NotifySurfaceReady, for the rest of + // the layer's life time. + Maybe mFrontSurface; + + // The queue of surfaces which make up the rest of our "swap chain". + // mSurfaces.front() is the next surface we'll attempt to use. + // mSurfaces.back() is the one that was used most recently. + std::vector mSurfaces; + + // Non-null between calls to NextSurfaceAsDrawTarget and NotifySurfaceReady. + RefPtr mInProgressLockedIOSurface; + + RefPtr mSurfacePoolHandle; + RefPtr mTextureHost; + + Representation mOnscreenRepresentation; + Representation mOffscreenRepresentation; + + gfx::IntPoint mPosition; + gfx::Matrix4x4 mTransform; + gfx::IntRect mDisplayRect; + gfx::IntSize mSize; + Maybe mClipRect; + gfx::SamplingFilter mSamplingFilter = gfx::SamplingFilter::POINT; + float mBackingScale = 1.0f; + bool mSurfaceIsFlipped = false; + const bool mIsOpaque = false; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_NativeLayerCA_h diff --git a/gfx/layers/NativeLayerCA.mm b/gfx/layers/NativeLayerCA.mm new file mode 100644 index 0000000000..9d3087e1fb --- /dev/null +++ b/gfx/layers/NativeLayerCA.mm @@ -0,0 +1,1069 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nullptr; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/NativeLayerCA.h" + +#import +#import +#import +#import + +#include +#include + +#include "gfxUtils.h" +#include "GLBlitHelper.h" +#include "GLContextCGL.h" +#include "GLContextProvider.h" +#include "MozFramebuffer.h" +#include "mozilla/gfx/Swizzle.h" +#include "mozilla/layers/ScreenshotGrabber.h" +#include "mozilla/layers/SurfacePoolCA.h" +#include "mozilla/webrender/RenderMacIOSurfaceTextureHost.h" +#include "ScopedGLHelpers.h" + +@interface CALayer (PrivateSetContentsOpaque) +- (void)setContentsOpaque:(BOOL)opaque; +@end + +namespace mozilla { +namespace layers { + +using gfx::IntPoint; +using gfx::IntSize; +using gfx::IntRect; +using gfx::IntRegion; +using gfx::DataSourceSurface; +using gfx::Matrix4x4; +using gfx::SurfaceFormat; +using gl::GLContext; +using gl::GLContextCGL; + +// Utility classes for NativeLayerRootSnapshotter (NLRS) profiler screenshots. + +class RenderSourceNLRS : public profiler_screenshots::RenderSource { + public: + explicit RenderSourceNLRS(UniquePtr&& aFramebuffer) + : RenderSource(aFramebuffer->mSize), mFramebuffer(std::move(aFramebuffer)) {} + auto& FB() { return *mFramebuffer; } + + protected: + UniquePtr mFramebuffer; +}; + +class DownscaleTargetNLRS : public profiler_screenshots::DownscaleTarget { + public: + DownscaleTargetNLRS(gl::GLContext* aGL, UniquePtr&& aFramebuffer) + : profiler_screenshots::DownscaleTarget(aFramebuffer->mSize), + mGL(aGL), + mRenderSource(new RenderSourceNLRS(std::move(aFramebuffer))) {} + already_AddRefed AsRenderSource() override { + return do_AddRef(mRenderSource); + }; + bool DownscaleFrom(profiler_screenshots::RenderSource* aSource, const IntRect& aSourceRect, + const IntRect& aDestRect) override; + + protected: + RefPtr mGL; + RefPtr mRenderSource; +}; + +class AsyncReadbackBufferNLRS : public profiler_screenshots::AsyncReadbackBuffer { + public: + AsyncReadbackBufferNLRS(gl::GLContext* aGL, const IntSize& aSize, GLuint aBufferHandle) + : profiler_screenshots::AsyncReadbackBuffer(aSize), mGL(aGL), mBufferHandle(aBufferHandle) {} + void CopyFrom(profiler_screenshots::RenderSource* aSource) override; + bool MapAndCopyInto(DataSourceSurface* aSurface, const IntSize& aReadSize) override; + + protected: + virtual ~AsyncReadbackBufferNLRS(); + RefPtr mGL; + GLuint mBufferHandle = 0; +}; + +// Needs to be on the stack whenever CALayer mutations are performed. +// (Mutating CALayers outside of a transaction can result in permanently stuck rendering, because +// such mutations create an implicit transaction which never auto-commits if the current thread does +// not have a native runloop.) +// Uses NSAnimationContext, which wraps CATransaction with additional off-main-thread protection, +// see bug 1585523. +struct MOZ_STACK_CLASS AutoCATransaction final { + AutoCATransaction() { + [NSAnimationContext beginGrouping]; + // By default, mutating a CALayer property triggers an animation which smoothly transitions the + // property to the new value. We don't need these animations, and this call turns them off: + [CATransaction setDisableActions:YES]; + } + ~AutoCATransaction() { [NSAnimationContext endGrouping]; } +}; + +/* static */ already_AddRefed NativeLayerRootCA::CreateForCALayer( + CALayer* aLayer) { + RefPtr layerRoot = new NativeLayerRootCA(aLayer); + return layerRoot.forget(); +} + +// Returns an autoreleased CALayer* object. +static CALayer* MakeOffscreenRootCALayer() { + // This layer should behave similarly to the backing layer of a flipped NSView. + // It will never be rendered on the screen and it will never be attached to an NSView's layer; + // instead, it will be the root layer of a "local" CAContext. + // Setting geometryFlipped to YES causes the orientation of descendant CALayers' contents (such as + // IOSurfaces) to be consistent with what happens in a layer subtree that is attached to a flipped + // NSView. Setting it to NO would cause the surfaces in individual leaf layers to render upside + // down (rather than just flipping the entire layer tree upside down). + AutoCATransaction transaction; + CALayer* layer = [CALayer layer]; + layer.position = NSZeroPoint; + layer.bounds = NSZeroRect; + layer.anchorPoint = NSZeroPoint; + layer.contentsGravity = kCAGravityTopLeft; + layer.masksToBounds = YES; + layer.geometryFlipped = YES; + return layer; +} + +NativeLayerRootCA::NativeLayerRootCA(CALayer* aLayer) + : mMutex("NativeLayerRootCA"), + mOnscreenRepresentation(aLayer), + mOffscreenRepresentation(MakeOffscreenRootCALayer()) {} + +NativeLayerRootCA::~NativeLayerRootCA() { + MOZ_RELEASE_ASSERT(mSublayers.IsEmpty(), + "Please clear all layers before destroying the layer root."); +} + +already_AddRefed NativeLayerRootCA::CreateLayer( + const IntSize& aSize, bool aIsOpaque, SurfacePoolHandle* aSurfacePoolHandle) { + RefPtr layer = + new NativeLayerCA(aSize, aIsOpaque, aSurfacePoolHandle->AsSurfacePoolHandleCA()); + return layer.forget(); +} + +already_AddRefed NativeLayerRootCA::CreateLayerForExternalTexture(bool aIsOpaque) { + RefPtr layer = new NativeLayerCA(aIsOpaque); + return layer.forget(); +} + +void NativeLayerRootCA::AppendLayer(NativeLayer* aLayer) { + MutexAutoLock lock(mMutex); + + RefPtr layerCA = aLayer->AsNativeLayerCA(); + MOZ_RELEASE_ASSERT(layerCA); + + mSublayers.AppendElement(layerCA); + layerCA->SetBackingScale(mBackingScale); + ForAllRepresentations([&](Representation& r) { r.mMutated = true; }); +} + +void NativeLayerRootCA::RemoveLayer(NativeLayer* aLayer) { + MutexAutoLock lock(mMutex); + + RefPtr layerCA = aLayer->AsNativeLayerCA(); + MOZ_RELEASE_ASSERT(layerCA); + + mSublayers.RemoveElement(layerCA); + ForAllRepresentations([&](Representation& r) { r.mMutated = true; }); +} + +void NativeLayerRootCA::SetLayers(const nsTArray>& aLayers) { + MutexAutoLock lock(mMutex); + + // Ideally, we'd just be able to do mSublayers = std::move(aLayers). + // However, aLayers has a different type: it carries NativeLayer objects, whereas mSublayers + // carries NativeLayerCA objects, so we have to downcast all the elements first. There's one other + // reason to look at all the elements in aLayers first: We need to make sure any new layers know + // about our current backing scale. + + nsTArray> layersCA(aLayers.Length()); + for (auto& layer : aLayers) { + RefPtr layerCA = layer->AsNativeLayerCA(); + MOZ_RELEASE_ASSERT(layerCA); + layerCA->SetBackingScale(mBackingScale); + layersCA.AppendElement(std::move(layerCA)); + } + + if (layersCA != mSublayers) { + mSublayers = std::move(layersCA); + ForAllRepresentations([&](Representation& r) { r.mMutated = true; }); + } +} + +void NativeLayerRootCA::SetBackingScale(float aBackingScale) { + MutexAutoLock lock(mMutex); + + mBackingScale = aBackingScale; + for (auto layer : mSublayers) { + layer->SetBackingScale(aBackingScale); + } +} + +float NativeLayerRootCA::BackingScale() { + MutexAutoLock lock(mMutex); + return mBackingScale; +} + +void NativeLayerRootCA::SuspendOffMainThreadCommits() { + MutexAutoLock lock(mMutex); + mOffMainThreadCommitsSuspended = true; +} + +bool NativeLayerRootCA::UnsuspendOffMainThreadCommits() { + MutexAutoLock lock(mMutex); + mOffMainThreadCommitsSuspended = false; + return mCommitPending; +} + +bool NativeLayerRootCA::AreOffMainThreadCommitsSuspended() { + MutexAutoLock lock(mMutex); + return mOffMainThreadCommitsSuspended; +} + +bool NativeLayerRootCA::CommitToScreen() { + MutexAutoLock lock(mMutex); + + if (!NS_IsMainThread() && mOffMainThreadCommitsSuspended) { + mCommitPending = true; + return false; + } + + mOnscreenRepresentation.Commit(WhichRepresentation::ONSCREEN, mSublayers); + + mCommitPending = false; + + return true; +} + +UniquePtr NativeLayerRootCA::CreateSnapshotter() { + MutexAutoLock lock(mMutex); + MOZ_RELEASE_ASSERT( + !mWeakSnapshotter, + "No NativeLayerRootSnapshotter for this NativeLayerRoot should exist when this is called"); + + auto cr = NativeLayerRootSnapshotterCA::Create(this, mOffscreenRepresentation.mRootCALayer); + if (cr) { + mWeakSnapshotter = cr.get(); + } + return cr; +} + +void NativeLayerRootCA::OnNativeLayerRootSnapshotterDestroyed( + NativeLayerRootSnapshotterCA* aNativeLayerRootSnapshotter) { + MutexAutoLock lock(mMutex); + MOZ_RELEASE_ASSERT(mWeakSnapshotter == aNativeLayerRootSnapshotter); + mWeakSnapshotter = nullptr; +} + +void NativeLayerRootCA::CommitOffscreen() { + MutexAutoLock lock(mMutex); + mOffscreenRepresentation.Commit(WhichRepresentation::OFFSCREEN, mSublayers); +} + +template +void NativeLayerRootCA::ForAllRepresentations(F aFn) { + aFn(mOnscreenRepresentation); + aFn(mOffscreenRepresentation); +} + +NativeLayerRootCA::Representation::Representation(CALayer* aRootCALayer) + : mRootCALayer([aRootCALayer retain]) {} + +NativeLayerRootCA::Representation::~Representation() { + if (mMutated) { + // Clear the root layer's sublayers. At this point the window is usually closed, so this + // transaction does not cause any screen updates. + AutoCATransaction transaction; + mRootCALayer.sublayers = @[]; + } + + [mRootCALayer release]; +} + +void NativeLayerRootCA::Representation::Commit(WhichRepresentation aRepresentation, + const nsTArray>& aSublayers) { + AutoCATransaction transaction; + + // Call ApplyChanges on our sublayers first, and then update the root layer's + // list of sublayers. The order is important because we need layer->UnderlyingCALayer() + // to be non-null, and the underlying CALayer gets lazily initialized in ApplyChanges(). + for (auto layer : aSublayers) { + layer->ApplyChanges(aRepresentation); + } + + if (mMutated) { + NSMutableArray* sublayers = [NSMutableArray arrayWithCapacity:aSublayers.Length()]; + for (auto layer : aSublayers) { + [sublayers addObject:layer->UnderlyingCALayer(aRepresentation)]; + } + mRootCALayer.sublayers = sublayers; + mMutated = false; + } +} + +/* static */ UniquePtr NativeLayerRootSnapshotterCA::Create( + NativeLayerRootCA* aLayerRoot, CALayer* aRootCALayer) { + if (NS_IsMainThread()) { + // Disallow creating snapshotters on the main thread. + // On the main thread, any explicit CATransaction / NSAnimationContext is nested within a global + // implicit transaction. This makes it impossible to apply CALayer mutations synchronously such + // that they become visible to CARenderer. As a result, the snapshotter would not capture + // the right output on the main thread. + return nullptr; + } + + nsCString failureUnused; + RefPtr gl = + gl::GLContextProvider::CreateHeadless({gl::CreateContextFlags::ALLOW_OFFLINE_RENDERER | + gl::CreateContextFlags::REQUIRE_COMPAT_PROFILE}, + &failureUnused); + if (!gl) { + return nullptr; + } + + return UniquePtr( + new NativeLayerRootSnapshotterCA(aLayerRoot, std::move(gl), aRootCALayer)); +} + +NativeLayerRootSnapshotterCA::NativeLayerRootSnapshotterCA(NativeLayerRootCA* aLayerRoot, + RefPtr&& aGL, + CALayer* aRootCALayer) + : mLayerRoot(aLayerRoot), mGL(aGL) { + AutoCATransaction transaction; + mRenderer = [[CARenderer rendererWithCGLContext:gl::GLContextCGL::Cast(mGL)->GetCGLContext() + options:nil] retain]; + mRenderer.layer = aRootCALayer; +} + +NativeLayerRootSnapshotterCA::~NativeLayerRootSnapshotterCA() { + mLayerRoot->OnNativeLayerRootSnapshotterDestroyed(this); + [mRenderer release]; +} + +already_AddRefed +NativeLayerRootSnapshotterCA::GetWindowContents(const IntSize& aWindowSize) { + UpdateSnapshot(aWindowSize); + return do_AddRef(mSnapshot); +} + +void NativeLayerRootSnapshotterCA::UpdateSnapshot(const IntSize& aSize) { + CGRect bounds = CGRectMake(0, 0, aSize.width, aSize.height); + + { + // Set the correct bounds and scale on the renderer and its root layer. CARenderer always + // renders at unit scale, i.e. the coordinates on the root layer must map 1:1 to render target + // pixels. But the coordinates on our content layers are in "points", where 1 point maps to 2 + // device pixels on HiDPI. So in order to render at the full device pixel resolution, we set a + // scale transform on the root offscreen layer. + AutoCATransaction transaction; + mRenderer.layer.bounds = bounds; + float scale = mLayerRoot->BackingScale(); + mRenderer.layer.sublayerTransform = CATransform3DMakeScale(scale, scale, 1); + mRenderer.bounds = bounds; + } + + mLayerRoot->CommitOffscreen(); + + mGL->MakeCurrent(); + + bool needToRedrawEverything = false; + if (!mSnapshot || mSnapshot->Size() != aSize) { + mSnapshot = nullptr; + auto fb = gl::MozFramebuffer::Create(mGL, aSize, 0, false); + if (!fb) { + return; + } + mSnapshot = new RenderSourceNLRS(std::move(fb)); + needToRedrawEverything = true; + } + + const gl::ScopedBindFramebuffer bindFB(mGL, mSnapshot->FB().mFB); + mGL->fViewport(0.0, 0.0, aSize.width, aSize.height); + + // These legacy OpenGL function calls are part of CARenderer's API contract, see CARenderer.h. + // The size passed to glOrtho must be the device pixel size of the render target, otherwise + // CARenderer will produce incorrect results. + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0.0, aSize.width, 0.0, aSize.height, -1, 1); + + float mediaTime = CACurrentMediaTime(); + [mRenderer beginFrameAtTime:mediaTime timeStamp:nullptr]; + if (needToRedrawEverything) { + [mRenderer addUpdateRect:bounds]; + } + if (!CGRectIsEmpty([mRenderer updateBounds])) { + // CARenderer assumes the layer tree is opaque. It only ever paints over existing content, it + // never erases anything. However, our layer tree is not necessarily opaque. So we manually + // erase the area that's going to be redrawn. This ensures correct rendering in the transparent + // areas. + // + // Since we erase the bounds of the update area, this will erase more than necessary if the + // update area is not a single rectangle. Unfortunately we cannot get the precise update region + // from CARenderer, we can only get the bounds. + CGRect updateBounds = [mRenderer updateBounds]; + gl::ScopedGLState scopedScissorTestState(mGL, LOCAL_GL_SCISSOR_TEST, true); + gl::ScopedScissorRect scissor(mGL, updateBounds.origin.x, updateBounds.origin.y, + updateBounds.size.width, updateBounds.size.height); + mGL->fClearColor(0.0, 0.0, 0.0, 0.0); + mGL->fClear(LOCAL_GL_COLOR_BUFFER_BIT); + // We erased the update region's bounds. Make sure the entire update bounds get repainted. + [mRenderer addUpdateRect:updateBounds]; + } + [mRenderer render]; + [mRenderer endFrame]; +} + +bool NativeLayerRootSnapshotterCA::ReadbackPixels(const IntSize& aReadbackSize, + SurfaceFormat aReadbackFormat, + const Range& aReadbackBuffer) { + if (aReadbackFormat != SurfaceFormat::B8G8R8A8) { + return false; + } + + UpdateSnapshot(aReadbackSize); + if (!mSnapshot) { + return false; + } + + const gl::ScopedBindFramebuffer bindFB(mGL, mSnapshot->FB().mFB); + gl::ScopedPackState safePackState(mGL); + mGL->fReadPixels(0.0f, 0.0f, aReadbackSize.width, aReadbackSize.height, LOCAL_GL_BGRA, + LOCAL_GL_UNSIGNED_BYTE, &aReadbackBuffer[0]); + + return true; +} + +already_AddRefed +NativeLayerRootSnapshotterCA::CreateDownscaleTarget(const IntSize& aSize) { + auto fb = gl::MozFramebuffer::Create(mGL, aSize, 0, false); + if (!fb) { + return nullptr; + } + RefPtr dt = new DownscaleTargetNLRS(mGL, std::move(fb)); + return dt.forget(); +} + +already_AddRefed +NativeLayerRootSnapshotterCA::CreateAsyncReadbackBuffer(const IntSize& aSize) { + size_t bufferByteCount = aSize.width * aSize.height * 4; + GLuint bufferHandle = 0; + mGL->fGenBuffers(1, &bufferHandle); + + gl::ScopedPackState scopedPackState(mGL); + mGL->fBindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, bufferHandle); + mGL->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 1); + mGL->fBufferData(LOCAL_GL_PIXEL_PACK_BUFFER, bufferByteCount, nullptr, LOCAL_GL_STREAM_READ); + return MakeAndAddRef(mGL, aSize, bufferHandle); +} + +NativeLayerCA::NativeLayerCA(const IntSize& aSize, bool aIsOpaque, + SurfacePoolHandleCA* aSurfacePoolHandle) + : mMutex("NativeLayerCA"), + mSurfacePoolHandle(aSurfacePoolHandle), + mSize(aSize), + mIsOpaque(aIsOpaque) { + MOZ_RELEASE_ASSERT(mSurfacePoolHandle, "Need a non-null surface pool handle."); +} + +NativeLayerCA::NativeLayerCA(bool aIsOpaque) + : mMutex("NativeLayerCA"), mSurfacePoolHandle(nullptr), mIsOpaque(aIsOpaque) {} + +NativeLayerCA::~NativeLayerCA() { + if (mInProgressLockedIOSurface) { + mInProgressLockedIOSurface->Unlock(false); + mInProgressLockedIOSurface = nullptr; + } + if (mInProgressSurface) { + IOSurfaceDecrementUseCount(mInProgressSurface->mSurface.get()); + mSurfacePoolHandle->ReturnSurfaceToPool(mInProgressSurface->mSurface); + } + if (mFrontSurface) { + mSurfacePoolHandle->ReturnSurfaceToPool(mFrontSurface->mSurface); + } + for (const auto& surf : mSurfaces) { + mSurfacePoolHandle->ReturnSurfaceToPool(surf.mEntry.mSurface); + } +} + +void NativeLayerCA::AttachExternalImage(wr::RenderTextureHost* aExternalImage) { + wr::RenderMacIOSurfaceTextureHost* texture = aExternalImage->AsRenderMacIOSurfaceTextureHost(); + MOZ_ASSERT(texture); + mTextureHost = texture; + mSize = texture->GetSize(0); + mDisplayRect = IntRect(IntPoint{}, mSize); + + ForAllRepresentations([&](Representation& r) { + r.mMutatedFrontSurface = true; + r.mMutatedDisplayRect = true; + r.mMutatedSize = true; + }); +} + +void NativeLayerCA::SetSurfaceIsFlipped(bool aIsFlipped) { + MutexAutoLock lock(mMutex); + + if (aIsFlipped != mSurfaceIsFlipped) { + mSurfaceIsFlipped = aIsFlipped; + ForAllRepresentations([&](Representation& r) { r.mMutatedSurfaceIsFlipped = true; }); + } +} + +bool NativeLayerCA::SurfaceIsFlipped() { + MutexAutoLock lock(mMutex); + + return mSurfaceIsFlipped; +} + +IntSize NativeLayerCA::GetSize() { + MutexAutoLock lock(mMutex); + return mSize; +} + +void NativeLayerCA::SetPosition(const IntPoint& aPosition) { + MutexAutoLock lock(mMutex); + + if (aPosition != mPosition) { + mPosition = aPosition; + ForAllRepresentations([&](Representation& r) { r.mMutatedPosition = true; }); + } +} + +IntPoint NativeLayerCA::GetPosition() { + MutexAutoLock lock(mMutex); + return mPosition; +} + +void NativeLayerCA::SetTransform(const Matrix4x4& aTransform) { + MutexAutoLock lock(mMutex); + MOZ_ASSERT(aTransform.IsRectilinear()); + + if (aTransform != mTransform) { + mTransform = aTransform; + ForAllRepresentations([&](Representation& r) { r.mMutatedTransform = true; }); + } +} + +void NativeLayerCA::SetSamplingFilter(gfx::SamplingFilter aSamplingFilter) { + MutexAutoLock lock(mMutex); + + if (aSamplingFilter != mSamplingFilter) { + mSamplingFilter = aSamplingFilter; + ForAllRepresentations([&](Representation& r) { r.mMutatedSamplingFilter = true; }); + } +} + +Matrix4x4 NativeLayerCA::GetTransform() { + MutexAutoLock lock(mMutex); + return mTransform; +} + +IntRect NativeLayerCA::GetRect() { + MutexAutoLock lock(mMutex); + return IntRect(mPosition, mSize); +} + +void NativeLayerCA::SetBackingScale(float aBackingScale) { + MutexAutoLock lock(mMutex); + + if (aBackingScale != mBackingScale) { + mBackingScale = aBackingScale; + ForAllRepresentations([&](Representation& r) { r.mMutatedBackingScale = true; }); + } +} + +bool NativeLayerCA::IsOpaque() { + MutexAutoLock lock(mMutex); + return mIsOpaque; +} + +void NativeLayerCA::SetClipRect(const Maybe& aClipRect) { + MutexAutoLock lock(mMutex); + + if (aClipRect != mClipRect) { + mClipRect = aClipRect; + ForAllRepresentations([&](Representation& r) { r.mMutatedClipRect = true; }); + } +} + +Maybe NativeLayerCA::ClipRect() { + MutexAutoLock lock(mMutex); + return mClipRect; +} + +gfx::IntRect NativeLayerCA::CurrentSurfaceDisplayRect() { + MutexAutoLock lock(mMutex); + return mDisplayRect; +} + +NativeLayerCA::Representation::~Representation() { + [mContentCALayer release]; + [mOpaquenessTintLayer release]; + [mWrappingCALayer release]; +} + +void NativeLayerCA::InvalidateRegionThroughoutSwapchain(const MutexAutoLock&, + const IntRegion& aRegion) { + IntRegion r = aRegion; + if (mInProgressSurface) { + mInProgressSurface->mInvalidRegion.OrWith(r); + } + if (mFrontSurface) { + mFrontSurface->mInvalidRegion.OrWith(r); + } + for (auto& surf : mSurfaces) { + surf.mEntry.mInvalidRegion.OrWith(r); + } +} + +bool NativeLayerCA::NextSurface(const MutexAutoLock& aLock) { + if (mSize.IsEmpty()) { + gfxCriticalError() << "NextSurface returning false because of invalid mSize (" << mSize.width + << ", " << mSize.height << ")."; + return false; + } + + MOZ_RELEASE_ASSERT( + !mInProgressSurface, + "ERROR: Do not call NextSurface twice in sequence. Call NotifySurfaceReady before the " + "next call to NextSurface."); + + Maybe surf = GetUnusedSurfaceAndCleanUp(aLock); + if (!surf) { + CFTypeRefPtr newSurf = mSurfacePoolHandle->ObtainSurfaceFromPool(mSize); + MOZ_RELEASE_ASSERT(newSurf, "NextSurface IOSurfaceCreate failed to create the surface."); + surf = Some(SurfaceWithInvalidRegion{newSurf, IntRect({}, mSize)}); + } + + MOZ_RELEASE_ASSERT(surf); + mInProgressSurface = std::move(surf); + IOSurfaceIncrementUseCount(mInProgressSurface->mSurface.get()); + return true; +} + +template +void NativeLayerCA::HandlePartialUpdate(const MutexAutoLock& aLock, const IntRect& aDisplayRect, + const IntRegion& aUpdateRegion, F&& aCopyFn) { + MOZ_RELEASE_ASSERT(IntRect({}, mSize).Contains(aUpdateRegion.GetBounds()), + "The update region should be within the surface bounds."); + MOZ_RELEASE_ASSERT(IntRect({}, mSize).Contains(aDisplayRect), + "The display rect should be within the surface bounds."); + + MOZ_RELEASE_ASSERT(!mInProgressUpdateRegion); + MOZ_RELEASE_ASSERT(!mInProgressDisplayRect); + mInProgressUpdateRegion = Some(aUpdateRegion); + mInProgressDisplayRect = Some(aDisplayRect); + + InvalidateRegionThroughoutSwapchain(aLock, aUpdateRegion); + + if (mFrontSurface) { + // Copy not-overwritten valid content from mFrontSurface so that valid content never gets lost. + gfx::IntRegion copyRegion; + copyRegion.Sub(mInProgressSurface->mInvalidRegion, aUpdateRegion); + copyRegion.SubOut(mFrontSurface->mInvalidRegion); + + if (!copyRegion.IsEmpty()) { + // Now copy the valid content, using a caller-provided copy function. + aCopyFn(mFrontSurface->mSurface, copyRegion); + mInProgressSurface->mInvalidRegion.SubOut(copyRegion); + } + } +} + +RefPtr NativeLayerCA::NextSurfaceAsDrawTarget(const IntRect& aDisplayRect, + const IntRegion& aUpdateRegion, + gfx::BackendType aBackendType) { + MutexAutoLock lock(mMutex); + if (!NextSurface(lock)) { + return nullptr; + } + + mInProgressLockedIOSurface = new MacIOSurface(mInProgressSurface->mSurface); + mInProgressLockedIOSurface->Lock(false); + RefPtr dt = mInProgressLockedIOSurface->GetAsDrawTargetLocked(aBackendType); + + HandlePartialUpdate( + lock, aDisplayRect, aUpdateRegion, + [&](CFTypeRefPtr validSource, const gfx::IntRegion& copyRegion) { + RefPtr source = new MacIOSurface(validSource); + source->Lock(true); + { + RefPtr sourceDT = source->GetAsDrawTargetLocked(aBackendType); + RefPtr sourceSurface = sourceDT->Snapshot(); + + for (auto iter = copyRegion.RectIter(); !iter.Done(); iter.Next()) { + const gfx::IntRect& r = iter.Get(); + dt->CopySurface(sourceSurface, r, r.TopLeft()); + } + } + source->Unlock(true); + }); + + return dt; +} + +Maybe NativeLayerCA::NextSurfaceAsFramebuffer(const IntRect& aDisplayRect, + const IntRegion& aUpdateRegion, + bool aNeedsDepth) { + MutexAutoLock lock(mMutex); + MOZ_RELEASE_ASSERT(NextSurface(lock), "NextSurfaceAsFramebuffer needs a surface."); + + Maybe fbo = + mSurfacePoolHandle->GetFramebufferForSurface(mInProgressSurface->mSurface, aNeedsDepth); + MOZ_RELEASE_ASSERT(fbo, "GetFramebufferForSurface failed."); + + HandlePartialUpdate( + lock, aDisplayRect, aUpdateRegion, + [&](CFTypeRefPtr validSource, const gfx::IntRegion& copyRegion) { + // Copy copyRegion from validSource to fbo. + MOZ_RELEASE_ASSERT(mSurfacePoolHandle->gl()); + mSurfacePoolHandle->gl()->MakeCurrent(); + Maybe sourceFBO = mSurfacePoolHandle->GetFramebufferForSurface(validSource, false); + MOZ_RELEASE_ASSERT(sourceFBO, + "GetFramebufferForSurface failed during HandlePartialUpdate."); + for (auto iter = copyRegion.RectIter(); !iter.Done(); iter.Next()) { + gfx::IntRect r = iter.Get(); + if (mSurfaceIsFlipped) { + r.y = mSize.height - r.YMost(); + } + mSurfacePoolHandle->gl()->BlitHelper()->BlitFramebufferToFramebuffer(*sourceFBO, *fbo, r, + r, LOCAL_GL_NEAREST); + } + }); + + return fbo; +} + +void NativeLayerCA::NotifySurfaceReady() { + MutexAutoLock lock(mMutex); + + MOZ_RELEASE_ASSERT(mInProgressSurface, + "NotifySurfaceReady called without preceding call to NextSurface"); + + if (mInProgressLockedIOSurface) { + mInProgressLockedIOSurface->Unlock(false); + mInProgressLockedIOSurface = nullptr; + } + + if (mFrontSurface) { + mSurfaces.push_back({*mFrontSurface, 0}); + mFrontSurface = Nothing(); + } + + MOZ_RELEASE_ASSERT(mInProgressUpdateRegion); + IOSurfaceDecrementUseCount(mInProgressSurface->mSurface.get()); + mFrontSurface = std::move(mInProgressSurface); + mFrontSurface->mInvalidRegion.SubOut(mInProgressUpdateRegion.extract()); + ForAllRepresentations([&](Representation& r) { r.mMutatedFrontSurface = true; }); + + MOZ_RELEASE_ASSERT(mInProgressDisplayRect); + if (!mDisplayRect.IsEqualInterior(*mInProgressDisplayRect)) { + mDisplayRect = *mInProgressDisplayRect; + ForAllRepresentations([&](Representation& r) { r.mMutatedDisplayRect = true; }); + } + mInProgressDisplayRect = Nothing(); + MOZ_RELEASE_ASSERT(mFrontSurface->mInvalidRegion.Intersect(mDisplayRect).IsEmpty(), + "Parts of the display rect are invalid! This shouldn't happen."); +} + +void NativeLayerCA::DiscardBackbuffers() { + MutexAutoLock lock(mMutex); + + for (const auto& surf : mSurfaces) { + mSurfacePoolHandle->ReturnSurfaceToPool(surf.mEntry.mSurface); + } + mSurfaces.clear(); +} + +NativeLayerCA::Representation& NativeLayerCA::GetRepresentation( + WhichRepresentation aRepresentation) { + switch (aRepresentation) { + case WhichRepresentation::ONSCREEN: + return mOnscreenRepresentation; + case WhichRepresentation::OFFSCREEN: + return mOffscreenRepresentation; + } +} + +template +void NativeLayerCA::ForAllRepresentations(F aFn) { + aFn(mOnscreenRepresentation); + aFn(mOffscreenRepresentation); +} + +void NativeLayerCA::ApplyChanges(WhichRepresentation aRepresentation) { + MutexAutoLock lock(mMutex); + CFTypeRefPtr surface; + if (mFrontSurface) { + surface = mFrontSurface->mSurface; + } else if (mTextureHost) { + surface = mTextureHost->GetSurface()->GetIOSurfaceRef(); + } + GetRepresentation(aRepresentation) + .ApplyChanges(mSize, mIsOpaque, mPosition, mTransform, mDisplayRect, mClipRect, mBackingScale, + mSurfaceIsFlipped, mSamplingFilter, surface); +} + +CALayer* NativeLayerCA::UnderlyingCALayer(WhichRepresentation aRepresentation) { + MutexAutoLock lock(mMutex); + return GetRepresentation(aRepresentation).UnderlyingCALayer(); +} + +void NativeLayerCA::Representation::ApplyChanges( + const IntSize& aSize, bool aIsOpaque, const IntPoint& aPosition, const Matrix4x4& aTransform, + const IntRect& aDisplayRect, const Maybe& aClipRect, float aBackingScale, + bool aSurfaceIsFlipped, gfx::SamplingFilter aSamplingFilter, + CFTypeRefPtr aFrontSurface) { + if (!mWrappingCALayer) { + mWrappingCALayer = [[CALayer layer] retain]; + mWrappingCALayer.position = NSZeroPoint; + mWrappingCALayer.bounds = NSZeroRect; + mWrappingCALayer.anchorPoint = NSZeroPoint; + mWrappingCALayer.contentsGravity = kCAGravityTopLeft; + mContentCALayer = [[CALayer layer] retain]; + mContentCALayer.position = NSZeroPoint; + mContentCALayer.anchorPoint = NSZeroPoint; + mContentCALayer.contentsGravity = kCAGravityTopLeft; + mContentCALayer.contentsScale = 1; + mContentCALayer.bounds = CGRectMake(0, 0, aSize.width, aSize.height); + mContentCALayer.opaque = aIsOpaque; + if ([mContentCALayer respondsToSelector:@selector(setContentsOpaque:)]) { + // The opaque property seems to not be enough when using IOSurface contents. + // Additionally, call the private method setContentsOpaque. + [mContentCALayer setContentsOpaque:aIsOpaque]; + } + [mWrappingCALayer addSublayer:mContentCALayer]; + } + + bool shouldTintOpaqueness = StaticPrefs::gfx_core_animation_tint_opaque(); + if (shouldTintOpaqueness && !mOpaquenessTintLayer) { + mOpaquenessTintLayer = [[CALayer layer] retain]; + mOpaquenessTintLayer.position = NSZeroPoint; + mOpaquenessTintLayer.bounds = mContentCALayer.bounds; + mOpaquenessTintLayer.anchorPoint = NSZeroPoint; + mOpaquenessTintLayer.contentsGravity = kCAGravityTopLeft; + if (aIsOpaque) { + mOpaquenessTintLayer.backgroundColor = + [[[NSColor greenColor] colorWithAlphaComponent:0.5] CGColor]; + } else { + mOpaquenessTintLayer.backgroundColor = + [[[NSColor redColor] colorWithAlphaComponent:0.5] CGColor]; + } + [mWrappingCALayer addSublayer:mOpaquenessTintLayer]; + } else if (!shouldTintOpaqueness && mOpaquenessTintLayer) { + [mOpaquenessTintLayer removeFromSuperlayer]; + [mOpaquenessTintLayer release]; + mOpaquenessTintLayer = nullptr; + } + + // CALayers have a position and a size, specified through the position and the bounds properties. + // layer.bounds.origin must always be (0, 0). + // A layer's position affects the layer's entire layer subtree. In other words, each layer's + // position is relative to its superlayer's position. We implement the clip rect using + // masksToBounds on mWrappingCALayer. So mContentCALayer's position is relative to the clip rect + // position. + // Note: The Core Animation docs on "Positioning and Sizing Sublayers" say: + // Important: Always use integral numbers for the width and height of your layer. + // We hope that this refers to integral physical pixels, and not to integral logical coordinates. + + if (mMutatedBackingScale || mMutatedSize) { + mContentCALayer.bounds = + CGRectMake(0, 0, aSize.width / aBackingScale, aSize.height / aBackingScale); + if (mOpaquenessTintLayer) { + mOpaquenessTintLayer.bounds = mContentCALayer.bounds; + } + mContentCALayer.contentsScale = aBackingScale; + } + + if (mMutatedBackingScale || mMutatedPosition || mMutatedDisplayRect || mMutatedClipRect || + mMutatedTransform || mMutatedSurfaceIsFlipped || mMutatedSize) { + Maybe clipFromDisplayRect; + if (!aDisplayRect.IsEqualInterior(IntRect({}, aSize))) { + // When the display rect is a subset of the layer, then we want to guarantee that no + // pixels outside that rect are sampled, since they might be uninitialized. + // Transforming the display rect into a post-transform clip only maintains this if + // it's an integer translation, which is all we support for this case currently. + MOZ_ASSERT(aTransform.Is2DIntegerTranslation()); + clipFromDisplayRect = + Some(RoundedToInt(aTransform.TransformBounds(IntRectToRect(aDisplayRect + aPosition)))); + } + + auto effectiveClip = IntersectMaybeRects(aClipRect, clipFromDisplayRect); + auto globalClipOrigin = effectiveClip ? effectiveClip->TopLeft() : IntPoint(); + auto clipToLayerOffset = -globalClipOrigin; + + mWrappingCALayer.position = + CGPointMake(globalClipOrigin.x / aBackingScale, globalClipOrigin.y / aBackingScale); + + if (effectiveClip) { + mWrappingCALayer.masksToBounds = YES; + mWrappingCALayer.bounds = CGRectMake(0, 0, effectiveClip->Width() / aBackingScale, + effectiveClip->Height() / aBackingScale); + } else { + mWrappingCALayer.masksToBounds = NO; + } + + Matrix4x4 transform = aTransform; + transform.PreTranslate(aPosition.x, aPosition.y, 0); + transform.PostTranslate(clipToLayerOffset.x, clipToLayerOffset.y, 0); + + if (aSurfaceIsFlipped) { + transform.PreTranslate(0, aSize.height, 0).PreScale(1, -1, 1); + } + + CATransform3D transformCA{transform._11, + transform._12, + transform._13, + transform._14, + transform._21, + transform._22, + transform._23, + transform._24, + transform._31, + transform._32, + transform._33, + transform._34, + transform._41 / aBackingScale, + transform._42 / aBackingScale, + transform._43, + transform._44}; + mContentCALayer.transform = transformCA; + if (mOpaquenessTintLayer) { + mOpaquenessTintLayer.transform = mContentCALayer.transform; + } + } + + if (mMutatedFrontSurface) { + mContentCALayer.contents = (id)aFrontSurface.get(); + } + + if (mMutatedSamplingFilter) { + if (aSamplingFilter == gfx::SamplingFilter::POINT) { + mContentCALayer.minificationFilter = kCAFilterNearest; + mContentCALayer.magnificationFilter = kCAFilterNearest; + } else { + mContentCALayer.minificationFilter = kCAFilterLinear; + mContentCALayer.magnificationFilter = kCAFilterLinear; + } + } + + mMutatedPosition = false; + mMutatedTransform = false; + mMutatedBackingScale = false; + mMutatedSize = false; + mMutatedSurfaceIsFlipped = false; + mMutatedDisplayRect = false; + mMutatedClipRect = false; + mMutatedFrontSurface = false; + mMutatedSamplingFilter = false; +} + +// Called when mMutex is already being held by the current thread. +Maybe NativeLayerCA::GetUnusedSurfaceAndCleanUp( + const MutexAutoLock&) { + std::vector usedSurfaces; + Maybe unusedSurface; + + // Separate mSurfaces into used and unused surfaces. + for (auto& surf : mSurfaces) { + if (IOSurfaceIsInUse(surf.mEntry.mSurface.get())) { + surf.mCheckCount++; + if (surf.mCheckCount < 10) { + usedSurfaces.push_back(std::move(surf)); + } else { + // The window server has been holding on to this surface for an unreasonably long time. This + // is known to happen sometimes, for example in occluded windows or after a GPU switch. In + // that case, release our references to the surface so that it doesn't look like we're + // trying to keep it alive. + mSurfacePoolHandle->ReturnSurfaceToPool(std::move(surf.mEntry.mSurface)); + } + } else { + if (unusedSurface) { + // Multiple surfaces are unused. Keep the most recent one and release any earlier ones. The + // most recent one requires the least amount of copying during partial repaints. + mSurfacePoolHandle->ReturnSurfaceToPool(std::move(unusedSurface->mSurface)); + } + unusedSurface = Some(std::move(surf.mEntry)); + } + } + + // Put the used surfaces back into mSurfaces. + mSurfaces = std::move(usedSurfaces); + + return unusedSurface; +} + +bool DownscaleTargetNLRS::DownscaleFrom(profiler_screenshots::RenderSource* aSource, + const IntRect& aSourceRect, const IntRect& aDestRect) { + mGL->BlitHelper()->BlitFramebufferToFramebuffer(static_cast(aSource)->FB().mFB, + mRenderSource->FB().mFB, aSourceRect, aDestRect, + LOCAL_GL_LINEAR); + + return true; +} + +void AsyncReadbackBufferNLRS::CopyFrom(profiler_screenshots::RenderSource* aSource) { + IntSize size = aSource->Size(); + MOZ_RELEASE_ASSERT(Size() == size); + + gl::ScopedPackState scopedPackState(mGL); + mGL->fBindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, mBufferHandle); + mGL->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 1); + const gl::ScopedBindFramebuffer bindFB(mGL, static_cast(aSource)->FB().mFB); + mGL->fReadPixels(0, 0, size.width, size.height, LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, 0); +} + +bool AsyncReadbackBufferNLRS::MapAndCopyInto(DataSourceSurface* aSurface, + const IntSize& aReadSize) { + MOZ_RELEASE_ASSERT(aReadSize <= aSurface->GetSize()); + + if (!mGL || !mGL->MakeCurrent()) { + return false; + } + + gl::ScopedPackState scopedPackState(mGL); + mGL->fBindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, mBufferHandle); + mGL->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 1); + + const uint8_t* srcData = nullptr; + if (mGL->IsSupported(gl::GLFeature::map_buffer_range)) { + srcData = static_cast(mGL->fMapBufferRange(LOCAL_GL_PIXEL_PACK_BUFFER, 0, + aReadSize.height * aReadSize.width * 4, + LOCAL_GL_MAP_READ_BIT)); + } else { + srcData = + static_cast(mGL->fMapBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, LOCAL_GL_READ_ONLY)); + } + + if (!srcData) { + return false; + } + + int32_t srcStride = mSize.width * 4; // Bind() sets an alignment of 1 + DataSourceSurface::ScopedMap map(aSurface, DataSourceSurface::WRITE); + uint8_t* destData = map.GetData(); + int32_t destStride = map.GetStride(); + SurfaceFormat destFormat = aSurface->GetFormat(); + for (int32_t destRow = 0; destRow < aReadSize.height; destRow++) { + // Turn srcData upside down during the copy. + int32_t srcRow = aReadSize.height - 1 - destRow; + const uint8_t* src = &srcData[srcRow * srcStride]; + uint8_t* dest = &destData[destRow * destStride]; + SwizzleData(src, srcStride, SurfaceFormat::R8G8B8A8, dest, destStride, destFormat, + IntSize(aReadSize.width, 1)); + } + + mGL->fUnmapBuffer(LOCAL_GL_PIXEL_PACK_BUFFER); + + return true; +} + +AsyncReadbackBufferNLRS::~AsyncReadbackBufferNLRS() { + if (mGL && mGL->MakeCurrent()) { + mGL->fDeleteBuffers(1, &mBufferHandle); + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/OOPCanvasRenderer.h b/gfx/layers/OOPCanvasRenderer.h new file mode 100644 index 0000000000..bc56a2a5fa --- /dev/null +++ b/gfx/layers/OOPCanvasRenderer.h @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_LAYERS_OOPCANVASRENDERER_H_ +#define MOZILLA_LAYERS_OOPCANVASRENDERER_H_ + +#include "nsISupportsImpl.h" + +class nsICanvasRenderingContextInternal; + +namespace mozilla { + +namespace dom { +class HTMLCanvasElement; +} + +namespace layers { +class CanvasClient; + +/** + * This renderer works with WebGL running in the host process. It does + * not perform any graphics operations itself -- it is the client-side + * representation. It forwards WebGL composition to the remote process. + */ +class OOPCanvasRenderer final { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(OOPCanvasRenderer) + + public: + explicit OOPCanvasRenderer(nsICanvasRenderingContextInternal* aContext) + : mContext(aContext) {} + + dom::HTMLCanvasElement* mHTMLCanvasElement = nullptr; + + // The ClientWebGLContext that this is for + nsICanvasRenderingContextInternal* mContext = nullptr; + + // The lifetime of this pointer is controlled by OffscreenCanvas + // Can be accessed in active thread and ImageBridge thread. + // But we never accessed it at the same time on both thread. So no + // need to protect this member. + CanvasClient* mCanvasClient = nullptr; + + private: + ~OOPCanvasRenderer() = default; +}; + +} // namespace layers +} // namespace mozilla + +#endif // MOZILLA_LAYERS_OOPCANVASRENDERER_H_ diff --git a/gfx/layers/PaintThread.cpp b/gfx/layers/PaintThread.cpp new file mode 100644 index 0000000000..2c85236c84 --- /dev/null +++ b/gfx/layers/PaintThread.cpp @@ -0,0 +1,272 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PaintThread.h" + +#include + +#include "base/task.h" +#include "gfxPlatform.h" +#include "GeckoProfiler.h" +#include "mozilla/layers/CompositorBridgeChild.h" +#include "mozilla/layers/ShadowLayers.h" +#include "mozilla/layers/SyncObject.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/SharedThreadPool.h" +#include "mozilla/SyncRunnable.h" +#include "nsProxyRelease.h" +#ifdef XP_MACOSX +# include "nsCocoaFeatures.h" +#endif +#include "nsIThreadManager.h" +#include "nsServiceManagerUtils.h" +#include "prsystem.h" + +// Uncomment the following line to dispatch sync runnables when +// painting so that rasterization happens synchronously from +// the perspective of the main thread +// #define OMTP_FORCE_SYNC + +namespace mozilla { +namespace layers { + +using namespace gfx; + +void PaintTask::DropTextureClients() { mClients.Clear(); } + +StaticAutoPtr PaintThread::sSingleton; +StaticRefPtr PaintThread::sThread; +PlatformThreadId PaintThread::sThreadId; + +PaintThread::PaintThread() = default; + +void PaintThread::Release() {} + +void PaintThread::AddRef() {} + +/* static */ +int32_t PaintThread::CalculatePaintWorkerCount() { + int32_t cpuCores = PR_GetNumberOfProcessors(); + int32_t workerCount = StaticPrefs::layers_omtp_paint_workers_AtStartup(); + + // If not manually specified, default to (cpuCores * 3) / 4, and clamp + // between 1 and 4. If a user wants more, they can manually specify it + if (workerCount < 1) { + workerCount = std::min(std::max((cpuCores * 3) / 4, 1), 4); + } + + return workerCount; +} + +/* static */ +void PaintThread::Start() { + PaintThread::sSingleton = new PaintThread(); + + if (!PaintThread::sSingleton->Init()) { + gfxCriticalNote << "Unable to start paint thread"; + PaintThread::sSingleton = nullptr; + } +} + +static uint32_t GetPaintThreadStackSize() { +#ifndef XP_MACOSX + return nsIThreadManager::DEFAULT_STACK_SIZE; +#else + // Workaround bug 1578075 by increasing the stack size of paint threads + if (nsCocoaFeatures::OnCatalinaOrLater()) { + static const uint32_t kCatalinaPaintThreadStackSize = 512 * 1024; + static_assert( + kCatalinaPaintThreadStackSize >= nsIThreadManager::DEFAULT_STACK_SIZE, + "update default stack size of paint " + "workers"); + return kCatalinaPaintThreadStackSize; + } + return nsIThreadManager::DEFAULT_STACK_SIZE; +#endif +} + +bool PaintThread::Init() { + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr thread; + nsresult rv = NS_NewNamedThread("PaintThread", getter_AddRefs(thread), + nullptr, GetPaintThreadStackSize()); + if (NS_FAILED(rv)) { + return false; + } + sThread = thread; + + // Only create paint workers for tiling if we are using tiling or could + // expect to dynamically switch to tiling in the future + if (gfxPlatform::GetPlatform()->UsesTiling()) { + InitPaintWorkers(); + } + + nsCOMPtr paintInitTask = NewRunnableMethod( + "PaintThread::InitOnPaintThread", this, &PaintThread::InitOnPaintThread); + SyncRunnable::DispatchToThread(sThread, paintInitTask); + return true; +} + +void PaintThread::InitOnPaintThread() { + MOZ_ASSERT(!NS_IsMainThread()); + sThreadId = PlatformThread::CurrentId(); +} + +void PaintThread::InitPaintWorkers() { + MOZ_ASSERT(NS_IsMainThread()); + int32_t count = PaintThread::CalculatePaintWorkerCount(); + if (count != 1) { + mPaintWorkers = SharedThreadPool::Get("PaintWorker"_ns, count); + mPaintWorkers->SetThreadStackSize(GetPaintThreadStackSize()); + } +} + +void DestroyPaintThread(UniquePtr&& pt) { + MOZ_ASSERT(PaintThread::IsOnPaintThread()); + pt->ShutdownOnPaintThread(); +} + +/* static */ +void PaintThread::Shutdown() { + MOZ_ASSERT(NS_IsMainThread()); + + UniquePtr pt(sSingleton.forget()); + if (!pt) { + return; + } + + sThread->Dispatch(NewRunnableFunction("DestroyPaintThreadRunnable", + DestroyPaintThread, std::move(pt))); + sThread->Shutdown(); + sThread = nullptr; +} + +void PaintThread::ShutdownOnPaintThread() { MOZ_ASSERT(IsOnPaintThread()); } + +/* static */ +PaintThread* PaintThread::Get() { return PaintThread::sSingleton.get(); } + +/* static */ +bool PaintThread::IsOnPaintThread() { + return sThreadId == PlatformThread::CurrentId(); +} + +bool PaintThread::IsOnPaintWorkerThread() { + return (mPaintWorkers && mPaintWorkers->IsOnCurrentThread()) || + (sThreadId == PlatformThread::CurrentId()); +} + +void PaintThread::Dispatch(RefPtr& aRunnable) { +#ifndef OMTP_FORCE_SYNC + sThread->Dispatch(aRunnable.forget()); +#else + SyncRunnable::DispatchToThread(sThread, aRunnable); +#endif +} + +void PaintThread::UpdateRenderMode() { + if (!!mPaintWorkers != gfxPlatform::GetPlatform()->UsesTiling()) { + if (mPaintWorkers) { + mPaintWorkers = nullptr; + } else { + InitPaintWorkers(); + } + } +} + +void PaintThread::QueuePaintTask(UniquePtr&& aTask) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aTask); + + if (StaticPrefs::layers_omtp_dump_capture() && aTask->mCapture) { + aTask->mCapture->Dump(); + } + + MOZ_RELEASE_ASSERT(aTask->mCapture->hasOneRef()); + + RefPtr cbc(CompositorBridgeChild::Get()); + cbc->NotifyBeginAsyncPaint(aTask.get()); + + RefPtr self = this; + RefPtr task = + NS_NewRunnableFunction("PaintThread::AsyncPaintTask", + [self, cbc, task = std::move(aTask)]() -> void { + self->AsyncPaintTask(cbc, task.get()); + }); + + nsIEventTarget* paintThread = + mPaintWorkers ? static_cast(mPaintWorkers.get()) + : static_cast(sThread.get()); + +#ifndef OMTP_FORCE_SYNC + paintThread->Dispatch(task.forget()); +#else + SyncRunnable::DispatchToThread(paintThread, task); +#endif +} + +void PaintThread::AsyncPaintTask(CompositorBridgeChild* aBridge, + PaintTask* aTask) { + AUTO_PROFILER_LABEL("PaintThread::AsyncPaintTask", GRAPHICS); + + MOZ_ASSERT(IsOnPaintWorkerThread()); + MOZ_ASSERT(aTask); + + gfx::DrawTargetCapture* capture = aTask->mCapture; + gfx::DrawTarget* target = aTask->mTarget; + + if (target->IsValid()) { + // Do not replay to invalid targets. This can happen on device resets and + // the browser will ensure the graphics stack is reinitialized on the main + // thread. + target->DrawCapturedDT(capture, Matrix()); + target->Flush(); + } + + if (StaticPrefs::layers_omtp_release_capture_on_main_thread()) { + // This should ensure the capture drawtarget, which may hold on to + // UnscaledFont objects, gets destroyed on the main thread (See bug + // 1404742). This assumes (unflushed) target DrawTargets do not themselves + // hold on to UnscaledFonts. + NS_ReleaseOnMainThread("PaintTask::DrawTargetCapture", + aTask->mCapture.forget()); + } + + if (aBridge->NotifyFinishedAsyncWorkerPaint(aTask)) { + AsyncEndLayerTransaction(aBridge); + } +} + +void PaintThread::QueueEndLayerTransaction(SyncObjectClient* aSyncObject) { + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr cbc(CompositorBridgeChild::Get()); + + if (cbc->NotifyBeginAsyncEndLayerTransaction(aSyncObject)) { + RefPtr self = this; + RefPtr task = NS_NewRunnableFunction( + "PaintThread::AsyncEndLayerTransaction", + [self, cbc]() -> void { self->AsyncEndLayerTransaction(cbc); }); + +#ifndef OMTP_FORCE_SYNC + sThread->Dispatch(task.forget()); +#else + SyncRunnable::DispatchToThread(sThread, task); +#endif + } +} + +void PaintThread::AsyncEndLayerTransaction(CompositorBridgeChild* aBridge) { + MOZ_ASSERT(IsOnPaintWorkerThread()); + + aBridge->NotifyFinishedAsyncEndLayerTransaction(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/PaintThread.h b/gfx/layers/PaintThread.h new file mode 100644 index 0000000000..27ccfb35ad --- /dev/null +++ b/gfx/layers/PaintThread.h @@ -0,0 +1,112 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_LAYERS_PAINTTHREAD_H +#define MOZILLA_LAYERS_PAINTTHREAD_H + +#include "base/platform_thread.h" +#include "mozilla/RefPtr.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/layers/TextureClient.h" +#include "RotatedBuffer.h" +#include "nsThreadUtils.h" + +class nsIThreadPool; + +namespace mozilla { +namespace gfx { +class DrawTarget; +class DrawTargetCapture; +}; // namespace gfx + +namespace layers { + +// A paint task contains a description of a rasterization work to be done +// on the paint thread or paint worker pool. +// +// More specifically it contains: +// 1. A capture command list of drawing commands +// 2. A destination draw target to replay the draw commands upon +// 3. A list of dependent texture clients that must be kept alive for the +// task's duration, and then destroyed on the main thread +class PaintTask { + public: + PaintTask() = default; + ~PaintTask() = default; + + void DropTextureClients(); + + RefPtr mTarget; + RefPtr mCapture; + AutoTArray, 4> mClients; +}; + +class CompositorBridgeChild; + +class PaintThread final { + friend void DestroyPaintThread(UniquePtr&& aPaintThread); + + public: + static void Start(); + static void Shutdown(); + static PaintThread* Get(); + + // Helper for asserts. + static bool IsOnPaintThread(); + bool IsOnPaintWorkerThread(); + + // This allows external users to run code on the paint thread. + void Dispatch(RefPtr& aRunnable); + + // This allows the paint thread to dynamically toggle between a paint worker + // thread pool used with tiling, and a single paint thread used with rotated + // buffer. + void UpdateRenderMode(); + + // Must be called on the main thread. Queues an async paint + // task to be completed on the paint thread. + void QueuePaintTask(UniquePtr&& aTask); + + // Must be called on the main thread. Signifies that the current + // layer tree transaction has been finished and any async paints + // for it have been queued on the paint thread. This MUST be called + // at the end of a layer transaction as it will be used to do an optional + // texture sync and then unblock the main thread if it is waiting to paint + // a new frame. + void QueueEndLayerTransaction(SyncObjectClient* aSyncObject); + + // Sync Runnables need threads to be ref counted, + // But this thread lives through the whole process. + // We're only temporarily using sync runnables so + // Override release/addref but don't do anything. + void Release(); + void AddRef(); + + static int32_t CalculatePaintWorkerCount(); + + private: + PaintThread(); + + bool Init(); + void ShutdownOnPaintThread(); + void InitOnPaintThread(); + void InitPaintWorkers(); + + void AsyncPaintTask(CompositorBridgeChild* aBridge, PaintTask* aTask); + void AsyncEndLayerTransaction(CompositorBridgeChild* aBridge); + + static StaticAutoPtr sSingleton; + static StaticRefPtr sThread; + static PlatformThreadId sThreadId; + + RefPtr mPaintWorkers; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/PersistentBufferProvider.cpp b/gfx/layers/PersistentBufferProvider.cpp new file mode 100644 index 0000000000..72dbcd9468 --- /dev/null +++ b/gfx/layers/PersistentBufferProvider.cpp @@ -0,0 +1,543 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PersistentBufferProvider.h" + +#include "Layers.h" +#include "mozilla/layers/ShadowLayers.h" +#include "mozilla/layers/TextureClient.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/StaticPrefs_layers.h" +#include "pratom.h" +#include "gfxPlatform.h" + +namespace mozilla { + +using namespace gfx; + +namespace layers { + +PersistentBufferProviderBasic::PersistentBufferProviderBasic(DrawTarget* aDt) + : mDrawTarget(aDt) { + MOZ_COUNT_CTOR(PersistentBufferProviderBasic); +} + +PersistentBufferProviderBasic::~PersistentBufferProviderBasic() { + MOZ_COUNT_DTOR(PersistentBufferProviderBasic); + Destroy(); +} + +already_AddRefed +PersistentBufferProviderBasic::BorrowDrawTarget( + const gfx::IntRect& aPersistedRect) { + MOZ_ASSERT(!mSnapshot); + RefPtr dt(mDrawTarget); + return dt.forget(); +} + +bool PersistentBufferProviderBasic::ReturnDrawTarget( + already_AddRefed aDT) { + RefPtr dt(aDT); + MOZ_ASSERT(mDrawTarget == dt); + if (dt) { + // Since SkiaGL default to storing drawing command until flush + // we have to flush it before present. + dt->Flush(); + } + return true; +} + +already_AddRefed +PersistentBufferProviderBasic::BorrowSnapshot() { + mSnapshot = mDrawTarget->Snapshot(); + RefPtr snapshot = mSnapshot; + return snapshot.forget(); +} + +void PersistentBufferProviderBasic::ReturnSnapshot( + already_AddRefed aSnapshot) { + RefPtr snapshot = aSnapshot; + MOZ_ASSERT(!snapshot || snapshot == mSnapshot); + mSnapshot = nullptr; +} + +void PersistentBufferProviderBasic::Destroy() { + mSnapshot = nullptr; + mDrawTarget = nullptr; +} + +// static +already_AddRefed +PersistentBufferProviderBasic::Create(gfx::IntSize aSize, + gfx::SurfaceFormat aFormat, + gfx::BackendType aBackend) { + RefPtr dt = + gfxPlatform::GetPlatform()->CreateDrawTargetForBackend(aBackend, aSize, + aFormat); + + if (dt) { + // This is simply to ensure the DrawTarget gets initialized, and will detect + // a device reset, even if we're on the main thread. + dt->ClearRect(Rect(0, 0, 0, 0)); + } + + if (!dt || !dt->IsValid()) { + return nullptr; + } + + RefPtr provider = + new PersistentBufferProviderBasic(dt); + + return provider.forget(); +} + +// static +already_AddRefed +PersistentBufferProviderShared::Create(gfx::IntSize aSize, + gfx::SurfaceFormat aFormat, + KnowsCompositor* aKnowsCompositor) { + if (!aKnowsCompositor || !aKnowsCompositor->GetTextureForwarder() || + !aKnowsCompositor->GetTextureForwarder()->IPCOpen() || + // Bug 1556433 - shared buffer provider and direct texture mapping do not + // synchronize properly + aKnowsCompositor->SupportsTextureDirectMapping()) { + return nullptr; + } + + if (!StaticPrefs::layers_shared_buffer_provider_enabled()) { + return nullptr; + } + +#ifdef XP_WIN + // Bug 1285271 - Disable shared buffer provider on Windows with D2D due to + // instability, unless we are remoting the canvas drawing to the GPU process. + if (gfxPlatform::GetPlatform()->GetPreferredCanvasBackend() == + BackendType::DIRECT2D1_1 && + !TextureData::IsRemote(aKnowsCompositor, BackendSelector::Canvas)) { + return nullptr; + } +#endif + + RefPtr texture = TextureClient::CreateForDrawing( + aKnowsCompositor, aFormat, aSize, BackendSelector::Canvas, + TextureFlags::DEFAULT | TextureFlags::NON_BLOCKING_READ_LOCK, + TextureAllocationFlags::ALLOC_DEFAULT); + + if (!texture) { + return nullptr; + } + + RefPtr provider = + new PersistentBufferProviderShared(aSize, aFormat, aKnowsCompositor, + texture); + return provider.forget(); +} + +PersistentBufferProviderShared::PersistentBufferProviderShared( + gfx::IntSize aSize, gfx::SurfaceFormat aFormat, + KnowsCompositor* aKnowsCompositor, RefPtr& aTexture) + + : mSize(aSize), + mFormat(aFormat), + mKnowsCompositor(aKnowsCompositor), + mFront(Nothing()) { + MOZ_ASSERT(aKnowsCompositor); + if (mTextures.append(aTexture)) { + mBack = Some(0); + } + + // If we are using webrender and our textures don't have an intermediate + // buffer, then we have to hold onto the textures for longer to make sure that + // the GPU has finished using them. So, we need to allow more TextureClients + // to be created. + if (!aTexture->HasIntermediateBuffer() && gfxVars::UseWebRender()) { + ++mMaxAllowedTextures; + if (gfxVars::UseWebRenderTripleBufferingWin()) { + ++mMaxAllowedTextures; + } + } + + MOZ_COUNT_CTOR(PersistentBufferProviderShared); +} + +PersistentBufferProviderShared::~PersistentBufferProviderShared() { + MOZ_COUNT_DTOR(PersistentBufferProviderShared); + + if (IsActivityTracked()) { + mKnowsCompositor->GetActiveResourceTracker()->RemoveObject(this); + } + + Destroy(); +} + +LayersBackend PersistentBufferProviderShared::GetType() { + if (mKnowsCompositor->GetCompositorBackendType() == + LayersBackend::LAYERS_WR) { + return LayersBackend::LAYERS_WR; + } else { + return LayersBackend::LAYERS_CLIENT; + } +} + +bool PersistentBufferProviderShared::SetKnowsCompositor( + KnowsCompositor* aKnowsCompositor) { + MOZ_ASSERT(aKnowsCompositor); + if (!aKnowsCompositor) { + return false; + } + + if (mKnowsCompositor == aKnowsCompositor) { + // The forwarder should not change most of the time. + return true; + } + + if (IsActivityTracked()) { + mKnowsCompositor->GetActiveResourceTracker()->RemoveObject(this); + } + + if (mKnowsCompositor->GetTextureForwarder() != + aKnowsCompositor->GetTextureForwarder() || + mKnowsCompositor->GetCompositorBackendType() != + aKnowsCompositor->GetCompositorBackendType()) { + // We are going to be used with an different and/or incompatible forwarder. + // This should be extremely rare. We have to copy the front buffer into a + // texture that is compatible with the new forwarder. + + // Grab the current front buffer. + RefPtr prevTexture = GetTexture(mFront); + + // Get rid of everything else + Destroy(); + + if (prevTexture) { + RefPtr newTexture = TextureClient::CreateForDrawing( + aKnowsCompositor, mFormat, mSize, BackendSelector::Canvas, + TextureFlags::DEFAULT | TextureFlags::NON_BLOCKING_READ_LOCK, + TextureAllocationFlags::ALLOC_DEFAULT); + + MOZ_ASSERT(newTexture); + if (!newTexture) { + return false; + } + + // If we early-return in one of the following branches, we will + // leave the buffer provider in an empty state, since we called + // Destroy. Not ideal but at least we won't try to use it with a + // an incompatible ipc channel. + + if (!newTexture->Lock(OpenMode::OPEN_WRITE)) { + return false; + } + + if (!prevTexture->Lock(OpenMode::OPEN_READ)) { + newTexture->Unlock(); + return false; + } + + bool success = + prevTexture->CopyToTextureClient(newTexture, nullptr, nullptr); + + prevTexture->Unlock(); + newTexture->Unlock(); + + if (!success) { + return false; + } + + if (!mTextures.append(newTexture)) { + return false; + } + mFront = Some(mTextures.length() - 1); + mBack = mFront; + } + } + + mKnowsCompositor = aKnowsCompositor; + + return true; +} + +TextureClient* PersistentBufferProviderShared::GetTexture( + const Maybe& aIndex) { + if (aIndex.isNothing() || !CheckIndex(aIndex.value())) { + return nullptr; + } + return mTextures[aIndex.value()]; +} + +already_AddRefed +PersistentBufferProviderShared::BorrowDrawTarget( + const gfx::IntRect& aPersistedRect) { + if (!mKnowsCompositor->GetTextureForwarder() || + !mKnowsCompositor->GetTextureForwarder()->IPCOpen()) { + return nullptr; + } + + MOZ_ASSERT(!mSnapshot); + + if (IsActivityTracked()) { + mKnowsCompositor->GetActiveResourceTracker()->MarkUsed(this); + } else { + mKnowsCompositor->GetActiveResourceTracker()->AddObject(this); + } + + if (mDrawTarget) { + RefPtr dt(mDrawTarget); + return dt.forget(); + } + + auto previousBackBuffer = mBack; + + TextureClient* tex = GetTexture(mBack); + + // First try to reuse the current back buffer. If we can do that it means + // we can skip copying its content to the new back buffer. + if ((mTextureLockIsUnreliable.isSome() && + mTextureLockIsUnreliable == mBack) || + (tex && tex->IsReadLocked())) { + // The back buffer is currently used by the compositor, we can't draw + // into it. + tex = nullptr; + } + + if (!tex) { + // Try to grab an already allocated texture if any is available. + for (uint32_t i = 0; i < mTextures.length(); ++i) { + if (!mTextures[i]->IsReadLocked() && + !(mTextureLockIsUnreliable.isSome() && + mTextureLockIsUnreliable.ref() == i)) { + mBack = Some(i); + tex = mTextures[i]; + break; + } + } + } + + if (!tex) { + // We have to allocate a new texture. + if (mTextures.length() >= mMaxAllowedTextures) { + // We should never need to buffer that many textures, something's wrong. + // In theory we throttle the main thread when the compositor can't keep + // up, so we shoud never get in a situation where we sent 4 textures to + // the compositor and the latter has not released any of them. In + // practice, though, the throttling mechanism appears to have some issues, + // especially when switching between layer managers (during tab-switch). + // To make sure we don't get too far ahead of the compositor, we send a + // sync ping to the compositor thread... + mKnowsCompositor->SyncWithCompositor(); + // ...and try again. + for (uint32_t i = 0; i < mTextures.length(); ++i) { + if (!mTextures[i]->IsReadLocked()) { + gfxCriticalNote << "Managed to allocate after flush."; + mBack = Some(i); + tex = mTextures[i]; + break; + } + } + + if (!tex) { + gfxCriticalNote << "Unexpected BufferProvider over-production."; + // It would be pretty bad to keep piling textures up at this point so we + // call NotifyInactive to remove some of our textures. + NotifyInactive(); + // Give up now. The caller can fall-back to a non-shared buffer + // provider. + return nullptr; + } + } + + RefPtr newTexture = TextureClient::CreateForDrawing( + mKnowsCompositor, mFormat, mSize, BackendSelector::Canvas, + TextureFlags::DEFAULT | TextureFlags::NON_BLOCKING_READ_LOCK, + TextureAllocationFlags::ALLOC_DEFAULT); + + MOZ_ASSERT(newTexture); + if (newTexture) { + if (mTextures.append(newTexture)) { + tex = newTexture; + mBack = Some(mTextures.length() - 1); + } + } + } + + if (!tex || !tex->Lock(OpenMode::OPEN_READ_WRITE)) { + return nullptr; + } + + // Clear dirty texture, since new back texture is selected. + mTextureLockIsUnreliable = Nothing(); + + mDrawTarget = tex->BorrowDrawTarget(); + if (mBack != previousBackBuffer && !aPersistedRect.IsEmpty()) { + if (mPreviousSnapshot) { + mDrawTarget->CopySurface(mPreviousSnapshot, aPersistedRect, + gfx::IntPoint(0, 0)); + } else { + TextureClient* previous = GetTexture(previousBackBuffer); + if (previous && previous->Lock(OpenMode::OPEN_READ)) { + DebugOnly success = + previous->CopyToTextureClient(tex, &aPersistedRect, nullptr); + MOZ_ASSERT(success); + + previous->Unlock(); + } + } + } + mPreviousSnapshot = nullptr; + + if (mDrawTarget) { + // This is simply to ensure the DrawTarget gets initialized, and will detect + // a device reset, even if we're on the main thread. + mDrawTarget->ClearRect(Rect(0, 0, 0, 0)); + + if (!mDrawTarget->IsValid()) { + mDrawTarget = nullptr; + } + } + + RefPtr dt(mDrawTarget); + return dt.forget(); +} + +bool PersistentBufferProviderShared::ReturnDrawTarget( + already_AddRefed aDT) { + RefPtr dt(aDT); + MOZ_ASSERT(mDrawTarget == dt); + // Can't change the current front buffer while its snapshot is borrowed! + MOZ_ASSERT(!mSnapshot); + + TextureClient* back = GetTexture(mBack); + MOZ_ASSERT(back); + + // If our TextureClients have internal synchronization then, if locks are + // needed for reading and writing, this can cause locking issues with the + // compositor. To prevent this we take a snapshot when the DrawTarget is + // returned, so this can be used when our own BorrowSnapshot is called and + // also for copying to the next TextureClient. Using this snapshot outside of + // the locks is safe, because the TextureClient calls DetachAllSnapshots on + // its DrawTarget when we Unlock below. + if (back->HasSynchronization()) { + mPreviousSnapshot = back->BorrowSnapshot(); + } + + mDrawTarget = nullptr; + dt = nullptr; + + if (back) { + back->Unlock(); + mFront = mBack; + } + + return !!back; +} + +TextureClient* PersistentBufferProviderShared::GetTextureClient() { + // Can't access the front buffer while drawing. + MOZ_ASSERT(!mDrawTarget); + TextureClient* texture = GetTexture(mFront); + if (!texture) { + gfxCriticalNote + << "PersistentBufferProviderShared: front buffer unavailable"; + } + return texture; +} + +already_AddRefed +PersistentBufferProviderShared::BorrowSnapshot() { + if (mPreviousSnapshot) { + mSnapshot = mPreviousSnapshot; + return do_AddRef(mSnapshot); + } + + if (mDrawTarget) { + auto back = GetTexture(mBack); + MOZ_ASSERT(back && back->IsLocked()); + mSnapshot = back->BorrowSnapshot(); + return do_AddRef(mSnapshot); + } + + auto front = GetTexture(mFront); + if (!front || front->IsLocked()) { + MOZ_ASSERT(false); + return nullptr; + } + + if (!front->Lock(OpenMode::OPEN_READ)) { + return nullptr; + } + + mSnapshot = front->BorrowSnapshot(); + + return do_AddRef(mSnapshot); +} + +void PersistentBufferProviderShared::ReturnSnapshot( + already_AddRefed aSnapshot) { + RefPtr snapshot = aSnapshot; + MOZ_ASSERT(!snapshot || snapshot == mSnapshot); + + mSnapshot = nullptr; + snapshot = nullptr; + + if (mPreviousSnapshot || mDrawTarget) { + return; + } + + auto front = GetTexture(mFront); + if (front) { + front->Unlock(); + } +} + +void PersistentBufferProviderShared::NotifyInactive() { + ClearCachedResources(); +} + +void PersistentBufferProviderShared::ClearCachedResources() { + RefPtr front = GetTexture(mFront); + RefPtr back = GetTexture(mBack); + + // Clear all textures (except the front and back ones that we just kept). + mTextures.clear(); + + if (back) { + if (mTextures.append(back)) { + mBack = Some(0); + } + if (front == back) { + mFront = mBack; + } + } + + if (front && front != back) { + if (mTextures.append(front)) { + mFront = Some(mTextures.length() - 1); + } + } + // Set front texture as dirty texture. + // The texture's read lock is unreliable after this function call. + mTextureLockIsUnreliable = mFront; +} + +void PersistentBufferProviderShared::Destroy() { + mSnapshot = nullptr; + mPreviousSnapshot = nullptr; + mDrawTarget = nullptr; + + for (auto& mTexture : mTextures) { + TextureClient* texture = mTexture; + if (texture && texture->IsLocked()) { + MOZ_ASSERT(false); + texture->Unlock(); + } + } + + mTextures.clear(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/PersistentBufferProvider.h b/gfx/layers/PersistentBufferProvider.h new file mode 100644 index 0000000000..2c6ab6f061 --- /dev/null +++ b/gfx/layers/PersistentBufferProvider.h @@ -0,0 +1,212 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_PersistentBUFFERPROVIDER_H +#define MOZILLA_GFX_PersistentBUFFERPROVIDER_H + +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/RefPtr.h" // for RefPtr, already_AddRefed, etc +#include "mozilla/layers/KnowsCompositor.h" +#include "mozilla/layers/LayersTypes.h" +#include "mozilla/RefCounted.h" +#include "mozilla/gfx/Types.h" +#include "mozilla/Vector.h" +#include "mozilla/WeakPtr.h" + +namespace mozilla { + +namespace gfx { +class SourceSurface; +class DrawTarget; +} // namespace gfx + +namespace layers { + +class CopyableCanvasLayer; +class TextureClient; + +/** + * A PersistentBufferProvider is for users which require the temporary use of + * a DrawTarget to draw into. When they're done drawing they return the + * DrawTarget, when they later need to continue drawing they get a DrawTarget + * from the provider again, the provider will guarantee the contents of the + * previously returned DrawTarget is persisted into the one newly returned. + */ +class PersistentBufferProvider : public RefCounted, + public SupportsWeakPtr { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PersistentBufferProvider) + + virtual ~PersistentBufferProvider() = default; + + virtual LayersBackend GetType() { return LayersBackend::LAYERS_NONE; } + + /** + * Get a DrawTarget from the PersistentBufferProvider. + * + * \param aPersistedRect This indicates the area of the DrawTarget that needs + * to have remained the same since the call to + * ReturnDrawTarget. + */ + virtual already_AddRefed BorrowDrawTarget( + const gfx::IntRect& aPersistedRect) = 0; + + /** + * Return a DrawTarget to the PersistentBufferProvider and indicate the + * contents of this DrawTarget is to be considered current by the + * BufferProvider. The caller should forget any references to the DrawTarget. + */ + virtual bool ReturnDrawTarget(already_AddRefed aDT) = 0; + + virtual already_AddRefed BorrowSnapshot() = 0; + + virtual void ReturnSnapshot( + already_AddRefed aSnapshot) = 0; + + virtual TextureClient* GetTextureClient() { return nullptr; } + + virtual void OnShutdown() {} + + virtual bool SetKnowsCompositor(KnowsCompositor* aKnowsCompositor) { + return true; + } + + virtual void ClearCachedResources() {} + + /** + * Return true if this provider preserves the drawing state (clips, + * transforms, etc.) across frames. In practice this means users of the + * provider can skip popping all of the clips at the end of the frames and + * pushing them back at the beginning of the following frames, which can be + * costly (cf. bug 1294351). + */ + virtual bool PreservesDrawingState() const = 0; +}; + +class PersistentBufferProviderBasic : public PersistentBufferProvider { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PersistentBufferProviderBasic, + override) + + static already_AddRefed Create( + gfx::IntSize aSize, gfx::SurfaceFormat aFormat, + gfx::BackendType aBackend); + + explicit PersistentBufferProviderBasic(gfx::DrawTarget* aTarget); + + LayersBackend GetType() override { return LayersBackend::LAYERS_BASIC; } + + already_AddRefed BorrowDrawTarget( + const gfx::IntRect& aPersistedRect) override; + + bool ReturnDrawTarget(already_AddRefed aDT) override; + + already_AddRefed BorrowSnapshot() override; + + void ReturnSnapshot(already_AddRefed aSnapshot) override; + + bool PreservesDrawingState() const override { return true; } + + void OnShutdown() override { Destroy(); } + + protected: + void Destroy(); + + private: + virtual ~PersistentBufferProviderBasic(); + + RefPtr mDrawTarget; + RefPtr mSnapshot; +}; + +/** + * Provides access to a buffer which can be sent to the compositor without + * requiring a copy. + */ +class PersistentBufferProviderShared : public PersistentBufferProvider, + public ActiveResource { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PersistentBufferProviderShared, + override) + + static already_AddRefed Create( + gfx::IntSize aSize, gfx::SurfaceFormat aFormat, + KnowsCompositor* aKnowsCompositor); + + LayersBackend GetType() override; + + already_AddRefed BorrowDrawTarget( + const gfx::IntRect& aPersistedRect) override; + + bool ReturnDrawTarget(already_AddRefed aDT) override; + + already_AddRefed BorrowSnapshot() override; + + void ReturnSnapshot(already_AddRefed aSnapshot) override; + + TextureClient* GetTextureClient() override; + + void NotifyInactive() override; + + void OnShutdown() override { Destroy(); } + + bool SetKnowsCompositor(KnowsCompositor* aKnowsCompositor) override; + + void ClearCachedResources() override; + + bool PreservesDrawingState() const override { return false; } + + protected: + PersistentBufferProviderShared(gfx::IntSize aSize, gfx::SurfaceFormat aFormat, + KnowsCompositor* aKnowsCompositor, + RefPtr& aTexture); + + ~PersistentBufferProviderShared(); + + TextureClient* GetTexture(const Maybe& aIndex); + bool CheckIndex(uint32_t aIndex) { return aIndex < mTextures.length(); } + + void Destroy(); + + gfx::IntSize mSize; + gfx::SurfaceFormat mFormat; + RefPtr mKnowsCompositor; + // We may need two extra textures if webrender is enabled. + static const size_t kMaxTexturesAllowed = 4; + Vector, kMaxTexturesAllowed + 2> mTextures; + // Offset of the texture in mTextures that the canvas uses. + Maybe mBack; + // Offset of the texture in mTextures that is presented to the compositor. + Maybe mFront; + // Offset of the texture in mTextures which texture's readlock is unreliable. + // Therefore it should not be used as next back buffer. + Maybe mTextureLockIsUnreliable; + + RefPtr mDrawTarget; + RefPtr mSnapshot; + RefPtr mPreviousSnapshot; + size_t mMaxAllowedTextures = kMaxTexturesAllowed; +}; + +struct AutoReturnSnapshot final { + PersistentBufferProvider* mBufferProvider; + RefPtr* mSnapshot; + + explicit AutoReturnSnapshot(PersistentBufferProvider* aProvider = nullptr) + : mBufferProvider(aProvider), mSnapshot(nullptr) {} + + ~AutoReturnSnapshot() { + if (mBufferProvider) { + mBufferProvider->ReturnSnapshot(mSnapshot ? mSnapshot->forget() + : nullptr); + } + } +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/ProfilerScreenshots.cpp b/gfx/layers/ProfilerScreenshots.cpp new file mode 100644 index 0000000000..648326150d --- /dev/null +++ b/gfx/layers/ProfilerScreenshots.cpp @@ -0,0 +1,139 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/ProfilerScreenshots.h" + +#include "mozilla/TimeStamp.h" + +#include "GeckoProfiler.h" +#include "gfxUtils.h" +#include "nsThreadUtils.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::layers; + +ProfilerScreenshots::ProfilerScreenshots() + : mMutex("ProfilerScreenshots::mMutex"), mLiveSurfaceCount(0) {} + +ProfilerScreenshots::~ProfilerScreenshots() = default; + +/* static */ +bool ProfilerScreenshots::IsEnabled() { +#ifdef MOZ_GECKO_PROFILER + return profiler_feature_active(ProfilerFeature::Screenshots); +#else + return false; +#endif +} + +void ProfilerScreenshots::SubmitScreenshot( + uintptr_t aWindowIdentifier, const gfx::IntSize& aOriginalSize, + const IntSize& aScaledSize, const TimeStamp& aTimeStamp, + const std::function& aPopulateSurface) { +#ifdef MOZ_GECKO_PROFILER + RefPtr backingSurface = TakeNextSurface(); + if (!backingSurface) { + return; + } + + MOZ_RELEASE_ASSERT(aScaledSize <= backingSurface->GetSize()); + + bool succeeded = aPopulateSurface(backingSurface); + + if (!succeeded) { + PROFILER_MARKER_UNTYPED( + "NoCompositorScreenshot because aPopulateSurface callback failed", + GRAPHICS); + ReturnSurface(backingSurface); + return; + } + + int sourceThread = profiler_current_thread_id(); + uintptr_t windowIdentifier = aWindowIdentifier; + IntSize originalSize = aOriginalSize; + IntSize scaledSize = aScaledSize; + TimeStamp timeStamp = aTimeStamp; + + RefPtr self = this; + + NS_DispatchBackgroundTask(NS_NewRunnableFunction( + "ProfilerScreenshots::SubmitScreenshot", + [self{std::move(self)}, backingSurface, sourceThread, windowIdentifier, + originalSize, scaledSize, timeStamp]() { + // Create a new surface that wraps backingSurface's data but has the + // correct size. + if (profiler_can_accept_markers()) { + DataSourceSurface::ScopedMap scopedMap(backingSurface, + DataSourceSurface::READ); + RefPtr surf = + Factory::CreateWrappingDataSourceSurface( + scopedMap.GetData(), scopedMap.GetStride(), scaledSize, + SurfaceFormat::B8G8R8A8); + + // Encode surf to a JPEG data URL. + nsCString dataURL; + nsresult rv = gfxUtils::EncodeSourceSurface( + surf, ImageType::JPEG, u"quality=85"_ns, gfxUtils::eDataURIEncode, + nullptr, &dataURL); + if (NS_SUCCEEDED(rv)) { + // Add a marker with the data URL. + struct ScreenshotMarker { + static constexpr mozilla::Span MarkerTypeName() { + return mozilla::MakeStringSpan("CompositorScreenshot"); + } + static void StreamJSONMarkerData( + mozilla::baseprofiler::SpliceableJSONWriter& aWriter, + const mozilla::ProfilerString8View& aScreenshotDataURL, + const mozilla::gfx::IntSize& aWindowSize, + uintptr_t aWindowIdentifier) { + aWriter.UniqueStringProperty("url", aScreenshotDataURL); + + char hexWindowID[32]; + SprintfLiteral(hexWindowID, "0x%" PRIXPTR, aWindowIdentifier); + aWriter.StringProperty("windowID", hexWindowID); + aWriter.DoubleProperty("windowWidth", aWindowSize.width); + aWriter.DoubleProperty("windowHeight", aWindowSize.height); + } + static mozilla::MarkerSchema MarkerTypeDisplay() { + return mozilla::MarkerSchema::SpecialFrontendLocation{}; + } + }; + profiler_add_marker( + "CompositorScreenshot", geckoprofiler::category::GRAPHICS, + {MarkerThreadId(sourceThread), + MarkerTiming::InstantAt(timeStamp)}, + ScreenshotMarker{}, dataURL, originalSize, windowIdentifier); + } + } + + // Return backingSurface back to the surface pool. + self->ReturnSurface(backingSurface); + })); +#endif +} + +already_AddRefed ProfilerScreenshots::TakeNextSurface() { + MutexAutoLock mon(mMutex); + if (!mAvailableSurfaces.IsEmpty()) { + RefPtr surf = mAvailableSurfaces[0]; + mAvailableSurfaces.RemoveElementAt(0); + return surf.forget(); + } + if (mLiveSurfaceCount >= 8) { + NS_WARNING( + "already 8 surfaces in flight, skipping capture for this composite"); + return nullptr; + } + mLiveSurfaceCount++; + return Factory::CreateDataSourceSurface(ScreenshotSize(), + SurfaceFormat::B8G8R8A8); +} + +void ProfilerScreenshots::ReturnSurface(DataSourceSurface* aSurface) { + MutexAutoLock mon(this->mMutex); + mAvailableSurfaces.AppendElement(aSurface); +} diff --git a/gfx/layers/ProfilerScreenshots.h b/gfx/layers/ProfilerScreenshots.h new file mode 100644 index 0000000000..8dd5a28f00 --- /dev/null +++ b/gfx/layers/ProfilerScreenshots.h @@ -0,0 +1,112 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_ProfilerScreenshots_h +#define mozilla_layers_ProfilerScreenshots_h + +#include + +#include "mozilla/Mutex.h" +#include "mozilla/RefPtr.h" +#include "mozilla/TimeStamp.h" +#include "nsCOMPtr.h" +#include "nsTArray.h" + +#include "mozilla/gfx/Point.h" + +class nsIThread; + +namespace mozilla { + +namespace gfx { +class DataSourceSurface; +} + +namespace layers { + +/** + * Can be used to submit screenshots from the compositor to the profiler. + * Screenshots have a fixed bounding size. The user of this class will usually + * scale down the window contents first, ideally on the GPU, then read back the + * small scaled down image into main memory, and then call SubmitScreenshot to + * pass the data to the profiler. + * This class encodes each screenshot to a JPEG data URL, on a separate thread. + * This class manages that thread and recycles memory buffers. + */ +class ProfilerScreenshots final { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ProfilerScreenshots) + + public: + ProfilerScreenshots(); + + /** + * Returns whether the profiler is currently active and is running with the + * "screenshots" feature enabled. + */ + static bool IsEnabled(); + + /** + * Returns a fixed size that all screenshots should be resized to fit into. + */ + static gfx::IntSize ScreenshotSize() { return gfx::IntSize(350, 350); } + + /** + * The main functionality provided by this class. + * This method will synchronously invoke the supplied aPopulateSurface + * callback function with a DataSourceSurface in which the callback should + * store the pixel data. This surface may be larger than aScaledSize, but + * only the data in the rectangle (0, 0, aScaledSize.width, + * aScaledSize.height) will be read. + * @param aWindowIdentifier A pointer-sized integer that can be used to match + * up multiple screenshots from the same window. + * @param aOriginalSize The unscaled size of the snapshotted window. + * @param aScaledSize The scaled size, aScaled <= ScreenshotSize(), which the + * snapshot has been resized to. + * @param aTimeStamp The time at which the snapshot was taken. In + * asynchronous readback implementations, this is the time at which the + * readback / copy command was put into the command stream to the GPU, not + * the time at which the readback data was mapped into main memory. + * @param aPopulateSurface A callback that the caller needs to implement, + * which needs to copy the screenshot pixel data into the surface that's + * supplied to the callback. Called zero or one times, synchronously. + */ + void SubmitScreenshot( + uintptr_t aWindowIdentifier, const gfx::IntSize& aOriginalSize, + const gfx::IntSize& aScaledSize, const TimeStamp& aTimeStamp, + const std::function& aPopulateSurface); + + private: + ~ProfilerScreenshots(); + + /** + * Recycle a surface from mAvailableSurfaces or create a new one if all + * surfaces are currently in use, up to some maximum limit. + * Returns null if the limit is reached. + * Can be called on any thread. + */ + already_AddRefed TakeNextSurface(); + + /** + * Return aSurface back into the mAvailableSurfaces pool. Can be called on + * any thread. + */ + void ReturnSurface(gfx::DataSourceSurface* aSurface); + + // An array of surfaces ready to be recycled. Can be accessed from multiple + // threads, protected by mMutex. + nsTArray> mAvailableSurfaces; + // Protects mAvailableSurfaces. + Mutex mMutex; + // The total number of surfaces created. If encoding is fast enough to happen + // entirely in the time between two calls to SubmitScreenshot, this should + // never exceed 1. + uint32_t mLiveSurfaceCount; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_ProfilerScreenshots_h diff --git a/gfx/layers/ReadbackLayer.h b/gfx/layers/ReadbackLayer.h new file mode 100644 index 0000000000..3dde7d0ac7 --- /dev/null +++ b/gfx/layers/ReadbackLayer.h @@ -0,0 +1,202 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_READBACKLAYER_H +#define GFX_READBACKLAYER_H + +#include // for uint64_t +#include "Layers.h" // for Layer, etc +#include "mozilla/gfx/Rect.h" // for gfxRect +#include "mozilla/gfx/Point.h" // for IntPoint +#include "mozilla/mozalloc.h" // for operator delete +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsDebug.h" // for NS_ASSERTION +#include "nsPoint.h" // for nsIntPoint +#include "nscore.h" // for nsACString + +class gfxContext; + +namespace mozilla { +namespace layers { + +class ReadbackProcessor; + +namespace layerscope { +class LayersPacket; +} // namespace layerscope + +/** + * A ReadbackSink receives a stream of updates to a rectangle of pixels. + * These update callbacks are always called on the main thread, either during + * EndTransaction or from the event loop. + */ +class ReadbackSink { + public: + ReadbackSink() = default; + virtual ~ReadbackSink() = default; + + /** + * Sends an update to indicate that the background is currently unknown. + */ + virtual void SetUnknown(uint64_t aSequenceNumber) = 0; + /** + * Called by the layer system to indicate that the contents of part of + * the readback area are changing. + * @param aRect is the rectangle of content that is being updated, + * in the coordinate system of the ReadbackLayer. + * @param aSequenceNumber updates issued out of order should be ignored. + * Only use updates whose sequence counter is greater than all other updates + * seen so far. Return null when a non-fresh sequence value is given. + * @return a context into which the update should be drawn. This should be + * set up to clip to aRect. Zero should never be passed as a sequence number. + * If this returns null, EndUpdate should NOT be called. If it returns + * non-null, EndUpdate must be called. + * + * We don't support partially unknown backgrounds. Therefore, the + * first BeginUpdate after a SetUnknown will have the complete background. + */ + virtual already_AddRefed BeginUpdate( + const gfx::IntRect& aRect, uint64_t aSequenceNumber) = 0; + /** + * EndUpdate must be called immediately after BeginUpdate, without returning + * to the event loop. + * @param aContext the context returned by BeginUpdate + * Implicitly Restore()s the state of aContext. + */ + virtual void EndUpdate(const gfx::IntRect& aRect) = 0; +}; + +/** + * A ReadbackLayer never renders anything. It enables clients to extract + * the rendered contents of the layer tree below the ReadbackLayer. + * The rendered contents are delivered asynchronously via calls to a + * ReadbackSink object supplied by the client. + * + * This is a "best effort" API; it is possible for the layer system to tell + * the ReadbackSink that the contents of the readback area are unknown. + * + * This API exists to work around the limitations of transparent windowless + * plugin rendering APIs. It should not be used for anything else. + */ +class ReadbackLayer : public Layer { + public: + MOZ_LAYER_DECL_NAME("ReadbackLayer", TYPE_READBACK) + + virtual void ComputeEffectiveTransforms( + const gfx::Matrix4x4& aTransformToSurface) override { + // Snap our local transform first, and snap the inherited transform as well. + // This makes our snapping equivalent to what would happen if our content + // was drawn into a PaintedLayer (gfxContext would snap using the local + // transform, then we'd snap again when compositing the PaintedLayer). + mEffectiveTransform = + SnapTransform(GetLocalTransform(), + gfxRect(0, 0, mSize.width, mSize.height), nullptr) * + SnapTransformTranslation(aTransformToSurface, nullptr); + } + + /** + * CONSTRUCTION PHASE ONLY + * Set the callback object to which readback updates will be delivered. + * This also resets the "needed rectangle" so that on the next layer tree + * transaction we will try to deliver the full contents of the readback + * area to the sink. + * This layer takes ownership of the sink. It will be deleted when the + * layer is destroyed or when a new sink is set. + * Initially the contents of the readback area are completely unknown. + */ + void SetSink(ReadbackSink* aSink) { + SetUnknown(); + mSink = mozilla::WrapUnique(aSink); + } + ReadbackSink* GetSink() { return mSink.get(); } + + /** + * CONSTRUCTION PHASE ONLY + * Set the size of content that should be read back. The readback area + * has its top-left at 0,0 and has size aSize. + * Can only be called while the sink is null! + */ + void SetSize(const gfx::IntSize& aSize) { + NS_ASSERTION(!mSink, "Should have no sink while changing size!"); + mSize = aSize; + } + const gfx::IntSize& GetSize() { return mSize; } + gfx::IntRect GetRect() { return gfx::IntRect(gfx::IntPoint(0, 0), mSize); } + + bool IsBackgroundKnown() { + return mBackgroundLayer || mBackgroundColor.a == 1.f; + } + + void NotifyRemoved() { + SetUnknown(); + mSink = nullptr; + } + + void NotifyPaintedLayerRemoved(PaintedLayer* aLayer) { + if (mBackgroundLayer == aLayer) { + mBackgroundLayer = nullptr; + } + } + + const nsIntPoint& GetBackgroundLayerOffset() { + return mBackgroundLayerOffset; + } + + uint64_t AllocateSequenceNumber() { return ++mSequenceCounter; } + + void SetUnknown() { + if (IsBackgroundKnown()) { + if (mSink) { + mSink->SetUnknown(AllocateSequenceNumber()); + } + mBackgroundLayer = nullptr; + mBackgroundColor = gfx::DeviceColor(); + } + } + + protected: + friend class ReadbackProcessor; + + ReadbackLayer(LayerManager* aManager, void* aImplData) + : Layer(aManager, aImplData), + mSequenceCounter(0), + mSize(0, 0), + mBackgroundLayer(nullptr), + mBackgroundLayerOffset(0, 0), + mBackgroundColor(gfx::DeviceColor()) {} + + virtual void PrintInfo(std::stringstream& aStream, + const char* aPrefix) override; + + virtual void DumpPacket(layerscope::LayersPacket* aPacket, + const void* aParent) override; + + uint64_t mSequenceCounter; + UniquePtr mSink; + gfx::IntSize mSize; + + // This can refer to any (earlier) sibling PaintedLayer. That PaintedLayer + // must have mUsedForReadback set on it. If the PaintedLayer is removed + // for the container, this will be set to null by NotifyPaintedLayerRemoved. + // This PaintedLayer contains the contents which have previously been reported + // to mSink. The PaintedLayer had only an integer translation transform, + // and it covered the entire readback area. This layer also had only an + // integer translation transform. + PaintedLayer* mBackgroundLayer; + // When mBackgroundLayer is non-null, this is the offset to add to + // convert from the coordinates of mBackgroundLayer to the coordinates + // of this layer. + nsIntPoint mBackgroundLayerOffset; + // When mBackgroundColor is opaque, this is the color of the ColorLayer + // that contained the contents we reported to mSink, which covered the + // entire readback area. + gfx::DeviceColor mBackgroundColor; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_READBACKLAYER_H */ diff --git a/gfx/layers/ReadbackProcessor.cpp b/gfx/layers/ReadbackProcessor.cpp new file mode 100644 index 0000000000..1e5e21c086 --- /dev/null +++ b/gfx/layers/ReadbackProcessor.cpp @@ -0,0 +1,175 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ReadbackProcessor.h" +#include // for int32_t +#include "Layers.h" // for Layer, PaintedLayer, etc +#include "ReadbackLayer.h" // for ReadbackLayer, ReadbackSink +#include "UnitTransforms.h" // for ViewAs +#include "Units.h" // for ParentLayerIntRect +#include "gfxContext.h" // for gfxContext +#include "gfxUtils.h" +#include "gfxRect.h" // for gfxRect +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/BasePoint.h" // for BasePoint +#include "mozilla/gfx/BaseRect.h" // for BaseRect +#include "mozilla/gfx/Point.h" // for Intsize +#include "nsDebug.h" // for NS_ASSERTION +#include "nsISupportsImpl.h" // for gfxContext::Release, etc +#include "nsPoint.h" // for nsIntPoint +#include "nsRegion.h" // for nsIntRegion + +using namespace mozilla::gfx; + +namespace mozilla { +namespace layers { + +void ReadbackProcessor::BuildUpdates(ContainerLayer* aContainer) { + NS_ASSERTION(mAllUpdates.IsEmpty(), "Some updates not processed?"); + + if (!aContainer->mMayHaveReadbackChild) return; + + aContainer->mMayHaveReadbackChild = false; + // go backwards so the updates read from earlier layers are later in the + // array. + for (Layer* l = aContainer->GetLastChild(); l; l = l->GetPrevSibling()) { + if (l->GetType() == Layer::TYPE_READBACK) { + aContainer->mMayHaveReadbackChild = true; + BuildUpdatesForLayer(static_cast(l)); + } + } +} + +static Layer* FindBackgroundLayer(ReadbackLayer* aLayer, nsIntPoint* aOffset) { + gfx::Matrix transform; + if (!aLayer->GetTransform().Is2D(&transform) || + transform.HasNonIntegerTranslation()) + return nullptr; + nsIntPoint transformOffset(int32_t(transform._31), int32_t(transform._32)); + + for (Layer* l = aLayer->GetPrevSibling(); l; l = l->GetPrevSibling()) { + gfx::Matrix backgroundTransform; + if (!l->GetTransform().Is2D(&backgroundTransform) || + gfx::ThebesMatrix(backgroundTransform).HasNonIntegerTranslation()) + return nullptr; + + nsIntPoint backgroundOffset(int32_t(backgroundTransform._31), + int32_t(backgroundTransform._32)); + IntRect rectInBackground(transformOffset - backgroundOffset, + aLayer->GetSize()); + const nsIntRegion visibleRegion = + l->GetLocalVisibleRegion().ToUnknownRegion(); + if (!visibleRegion.Intersects(rectInBackground)) continue; + // Since l is present in the background, from here on we either choose l + // or nothing. + if (!visibleRegion.Contains(rectInBackground)) return nullptr; + + if (l->GetEffectiveOpacity() != 1.0 || l->HasMaskLayers() || + !(l->GetContentFlags() & Layer::CONTENT_OPAQUE)) { + return nullptr; + } + + // cliprects are post-transform + const Maybe& clipRect = l->GetLocalClipRect(); + if (clipRect && !clipRect->Contains(ViewAs( + IntRect(transformOffset, aLayer->GetSize())))) + return nullptr; + + Layer::LayerType type = l->GetType(); + if (type != Layer::TYPE_COLOR && type != Layer::TYPE_PAINTED) + return nullptr; + + *aOffset = backgroundOffset - transformOffset; + return l; + } + + return nullptr; +} + +void ReadbackProcessor::BuildUpdatesForLayer(ReadbackLayer* aLayer) { + if (!aLayer->mSink) return; + + nsIntPoint offset; + Layer* newBackground = FindBackgroundLayer(aLayer, &offset); + if (!newBackground) { + aLayer->SetUnknown(); + return; + } + + if (newBackground->GetType() == Layer::TYPE_COLOR) { + ColorLayer* colorLayer = static_cast(newBackground); + if (aLayer->mBackgroundColor != colorLayer->GetColor()) { + aLayer->mBackgroundLayer = nullptr; + aLayer->mBackgroundColor = colorLayer->GetColor(); + NS_ASSERTION(aLayer->mBackgroundColor.a == 1.f, + "Color layer said it was opaque!"); + RefPtr dt = aLayer->mSink->BeginUpdate( + aLayer->GetRect(), aLayer->AllocateSequenceNumber()); + if (dt) { + ColorPattern color(aLayer->mBackgroundColor); + IntSize size = aLayer->GetSize(); + dt->FillRect(Rect(0, 0, size.width, size.height), color); + aLayer->mSink->EndUpdate(aLayer->GetRect()); + } + } + } else { + NS_ASSERTION(newBackground->AsPaintedLayer(), "Must be PaintedLayer"); + PaintedLayer* paintedLayer = static_cast(newBackground); + // updateRect is relative to the PaintedLayer + IntRect updateRect = aLayer->GetRect() - offset; + if (paintedLayer != aLayer->mBackgroundLayer || + offset != aLayer->mBackgroundLayerOffset) { + aLayer->mBackgroundLayer = paintedLayer; + aLayer->mBackgroundLayerOffset = offset; + aLayer->mBackgroundColor = DeviceColor(); + paintedLayer->SetUsedForReadback(true); + } else { + nsIntRegion invalid; + invalid.Sub(updateRect, paintedLayer->GetValidRegion()); + updateRect = invalid.GetBounds(); + } + + Update update = {aLayer, updateRect, aLayer->AllocateSequenceNumber()}; + mAllUpdates.AppendElement(update); + } +} + +void ReadbackProcessor::GetPaintedLayerUpdates(PaintedLayer* aLayer, + nsTArray* aUpdates, + nsIntRegion* aUpdateRegion) { + // All PaintedLayers used for readback are in mAllUpdates (some possibly + // with an empty update rect). + aLayer->SetUsedForReadback(false); + if (aUpdateRegion) { + aUpdateRegion->SetEmpty(); + } + for (uint32_t i = mAllUpdates.Length(); i > 0; --i) { + const Update& update = mAllUpdates[i - 1]; + if (update.mLayer->mBackgroundLayer == aLayer) { + aLayer->SetUsedForReadback(true); + // Don't bother asking for updates if we have an empty update rect. + if (!update.mUpdateRect.IsEmpty()) { + aUpdates->AppendElement(update); + if (aUpdateRegion) { + aUpdateRegion->Or(*aUpdateRegion, update.mUpdateRect); + } + } + mAllUpdates.RemoveElementAt(i - 1); + } + } +} + +ReadbackProcessor::~ReadbackProcessor() { + for (uint32_t i = mAllUpdates.Length(); i > 0; --i) { + const Update& update = mAllUpdates[i - 1]; + // Unprocessed update. Notify the readback sink that this content is + // unknown. + update.mLayer->SetUnknown(); + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ReadbackProcessor.h b/gfx/layers/ReadbackProcessor.h new file mode 100644 index 0000000000..a02b2efa6f --- /dev/null +++ b/gfx/layers/ReadbackProcessor.h @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_READBACKPROCESSOR_H +#define GFX_READBACKPROCESSOR_H + +#include // for uint64_t +#include "nsRect.h" // for mozilla::gfx::IntRect +#include "nsRegionFwd.h" // for nsIntRegion +#include "nsTArray.h" // for nsTArray + +namespace mozilla { +namespace layers { + +class ContainerLayer; +class ReadbackLayer; +class PaintedLayer; + +class ReadbackProcessor { + public: + /** + * Called by the container before processing any child layers. Call this + * if any child layer might have changed in any way (other than content-only + * changes to layers other than ColorLayers and PaintedLayers). + * + * This method recomputes the relationship between ReadbackLayers and + * sibling layers, and dispatches changes to ReadbackLayers. Except that + * if a PaintedLayer needs its contents sent to some ReadbackLayer, we'll + * just record that internally and later the PaintedLayer should call + * GetPaintedLayerUpdates when it paints, to find out which rectangle needs + * to be sent, and the ReadbackLayer it needs to be sent to. + */ + void BuildUpdates(ContainerLayer* aContainer); + + struct Update { + /** + * The layer a PaintedLayer should send its contents to. + */ + ReadbackLayer* mLayer; + /** + * The rectangle of content that it should send, in the PaintedLayer's + * coordinate system. This rectangle is guaranteed to be in the + * PaintedLayer's visible region. Translate it to mLayer's coordinate system + * by adding mLayer->GetBackgroundLayerOffset(). + */ + gfx::IntRect mUpdateRect; + /** + * The sequence counter value to use when calling DoUpdate + */ + uint64_t mSequenceCounter; + }; + /** + * Appends any ReadbackLayers that need to be updated, and the rects that + * need to be updated, to aUpdates. Only need to call this for PaintedLayers + * that have been marked UsedForReadback(). + * Each Update's mLayer's mBackgroundLayer will have been set to aLayer. + * If a PaintedLayer doesn't call GetPaintedLayerUpdates, then all the + * ReadbackLayers that needed data from that PaintedLayer will be marked + * as having unknown backgrounds. + * @param aUpdateRegion if non-null, this region is set to the union + * of the mUpdateRects. + */ + void GetPaintedLayerUpdates(PaintedLayer* aLayer, nsTArray* aUpdates, + nsIntRegion* aUpdateRegion = nullptr); + + ~ReadbackProcessor(); + + protected: + void BuildUpdatesForLayer(ReadbackLayer* aLayer); + + nsTArray mAllUpdates; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_READBACKPROCESSOR_H */ diff --git a/gfx/layers/RecordedCanvasEventImpl.h b/gfx/layers/RecordedCanvasEventImpl.h new file mode 100644 index 0000000000..af03b9b562 --- /dev/null +++ b/gfx/layers/RecordedCanvasEventImpl.h @@ -0,0 +1,595 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_RecordedCanvasEventImpl_h +#define mozilla_layers_RecordedCanvasEventImpl_h + +#include "mozilla/gfx/RecordedEvent.h" +#include "mozilla/gfx/RecordingTypes.h" +#include "mozilla/layers/CanvasTranslator.h" +#include "mozilla/layers/CompositorTypes.h" +#include "mozilla/layers/TextureClient.h" + +namespace mozilla { +namespace layers { + +using gfx::DrawTarget; +using gfx::IntSize; +using gfx::RecordedEvent; +using gfx::RecordedEventDerived; +using EventType = gfx::RecordedEvent::EventType; +using gfx::ReadElement; +using gfx::ReferencePtr; +using gfx::SurfaceFormat; +using gfx::WriteElement; +using ipc::SharedMemoryBasic; + +const EventType CANVAS_BEGIN_TRANSACTION = EventType::LAST; +const EventType CANVAS_END_TRANSACTION = EventType(EventType::LAST + 1); +const EventType CANVAS_FLUSH = EventType(EventType::LAST + 2); +const EventType TEXTURE_LOCK = EventType(EventType::LAST + 3); +const EventType TEXTURE_UNLOCK = EventType(EventType::LAST + 4); +const EventType CACHE_DATA_SURFACE = EventType(EventType::LAST + 5); +const EventType PREPARE_DATA_FOR_SURFACE = EventType(EventType::LAST + 6); +const EventType GET_DATA_FOR_SURFACE = EventType(EventType::LAST + 7); +const EventType ADD_SURFACE_ALIAS = EventType(EventType::LAST + 8); +const EventType REMOVE_SURFACE_ALIAS = EventType(EventType::LAST + 9); +const EventType DEVICE_CHANGE_ACKNOWLEDGED = EventType(EventType::LAST + 10); +const EventType NEXT_TEXTURE_ID = EventType(EventType::LAST + 11); +const EventType TEXTURE_DESTRUCTION = EventType(EventType::LAST + 12); + +class RecordedCanvasBeginTransaction final + : public RecordedEventDerived { + public: + RecordedCanvasBeginTransaction() + : RecordedEventDerived(CANVAS_BEGIN_TRANSACTION) {} + + template + MOZ_IMPLICIT RecordedCanvasBeginTransaction(S& aStream); + + bool PlayCanvasEvent(CanvasTranslator* aTranslator) const; + + template + void Record(S& aStream) const; + + std::string GetName() const final { return "RecordedCanvasBeginTransaction"; } +}; + +inline bool RecordedCanvasBeginTransaction::PlayCanvasEvent( + CanvasTranslator* aTranslator) const { + aTranslator->BeginTransaction(); + return true; +} + +template +void RecordedCanvasBeginTransaction::Record(S& aStream) const {} + +template +RecordedCanvasBeginTransaction::RecordedCanvasBeginTransaction(S& aStream) + : RecordedEventDerived(CANVAS_BEGIN_TRANSACTION) {} + +class RecordedCanvasEndTransaction final + : public RecordedEventDerived { + public: + RecordedCanvasEndTransaction() + : RecordedEventDerived(CANVAS_END_TRANSACTION) {} + + template + MOZ_IMPLICIT RecordedCanvasEndTransaction(S& aStream); + + bool PlayCanvasEvent(CanvasTranslator* aTranslator) const; + + template + void Record(S& aStream) const; + + std::string GetName() const final { return "RecordedCanvasEndTransaction"; } +}; + +inline bool RecordedCanvasEndTransaction::PlayCanvasEvent( + CanvasTranslator* aTranslator) const { + aTranslator->EndTransaction(); + return true; +} + +template +void RecordedCanvasEndTransaction::Record(S& aStream) const {} + +template +RecordedCanvasEndTransaction::RecordedCanvasEndTransaction(S& aStream) + : RecordedEventDerived(CANVAS_END_TRANSACTION) {} + +class RecordedCanvasFlush final + : public RecordedEventDerived { + public: + RecordedCanvasFlush() : RecordedEventDerived(CANVAS_FLUSH) {} + + template + MOZ_IMPLICIT RecordedCanvasFlush(S& aStream); + + bool PlayCanvasEvent(CanvasTranslator* aTranslator) const; + + template + void Record(S& aStream) const; + + std::string GetName() const final { return "RecordedCanvasFlush"; } +}; + +inline bool RecordedCanvasFlush::PlayCanvasEvent( + CanvasTranslator* aTranslator) const { + aTranslator->Flush(); + return true; +} + +template +void RecordedCanvasFlush::Record(S& aStream) const {} + +template +RecordedCanvasFlush::RecordedCanvasFlush(S& aStream) + : RecordedEventDerived(CANVAS_FLUSH) {} + +class RecordedTextureLock final + : public RecordedEventDerived { + public: + RecordedTextureLock(int64_t aTextureId, const OpenMode aMode) + : RecordedEventDerived(TEXTURE_LOCK), + mTextureId(aTextureId), + mMode(aMode) {} + + template + MOZ_IMPLICIT RecordedTextureLock(S& aStream); + + bool PlayCanvasEvent(CanvasTranslator* aTranslator) const; + + template + void Record(S& aStream) const; + + std::string GetName() const final { return "TextureLock"; } + + private: + int64_t mTextureId; + OpenMode mMode; +}; + +inline bool RecordedTextureLock::PlayCanvasEvent( + CanvasTranslator* aTranslator) const { + TextureData* textureData = aTranslator->LookupTextureData(mTextureId); + if (!textureData) { + return false; + } + + gfx::AutoSerializeWithMoz2D serializeWithMoz2D(aTranslator->GetBackendType()); + textureData->Lock(mMode); + return true; +} + +template +void RecordedTextureLock::Record(S& aStream) const { + WriteElement(aStream, mTextureId); + WriteElement(aStream, mMode); +} + +template +RecordedTextureLock::RecordedTextureLock(S& aStream) + : RecordedEventDerived(TEXTURE_LOCK) { + ReadElement(aStream, mTextureId); + ReadElementConstrained(aStream, mMode, OpenMode::OPEN_NONE, + OpenMode::OPEN_READ_WRITE_ASYNC); +} + +class RecordedTextureUnlock final + : public RecordedEventDerived { + public: + explicit RecordedTextureUnlock(int64_t aTextureId) + : RecordedEventDerived(TEXTURE_UNLOCK), mTextureId(aTextureId) {} + + template + MOZ_IMPLICIT RecordedTextureUnlock(S& aStream); + + bool PlayCanvasEvent(CanvasTranslator* aTranslator) const; + + template + void Record(S& aStream) const; + + std::string GetName() const final { return "TextureUnlock"; } + + private: + int64_t mTextureId; +}; + +inline bool RecordedTextureUnlock::PlayCanvasEvent( + CanvasTranslator* aTranslator) const { + TextureData* textureData = aTranslator->LookupTextureData(mTextureId); + if (!textureData) { + return false; + } + + gfx::AutoSerializeWithMoz2D serializeWithMoz2D(aTranslator->GetBackendType()); + textureData->Unlock(); + return true; +} + +template +void RecordedTextureUnlock::Record(S& aStream) const { + WriteElement(aStream, mTextureId); +} + +template +RecordedTextureUnlock::RecordedTextureUnlock(S& aStream) + : RecordedEventDerived(TEXTURE_UNLOCK) { + ReadElement(aStream, mTextureId); +} + +class RecordedCacheDataSurface final + : public RecordedEventDerived { + public: + explicit RecordedCacheDataSurface(gfx::SourceSurface* aSurface) + : RecordedEventDerived(CACHE_DATA_SURFACE), mSurface(aSurface) {} + + template + MOZ_IMPLICIT RecordedCacheDataSurface(S& aStream); + + bool PlayCanvasEvent(CanvasTranslator* aTranslator) const; + + template + void Record(S& aStream) const; + + std::string GetName() const final { return "RecordedCacheDataSurface"; } + + private: + ReferencePtr mSurface; +}; + +inline bool RecordedCacheDataSurface::PlayCanvasEvent( + CanvasTranslator* aTranslator) const { + gfx::SourceSurface* surface = aTranslator->LookupSourceSurface(mSurface); + if (!surface) { + return false; + } + + RefPtr dataSurface = surface->GetDataSurface(); + + aTranslator->AddDataSurface(mSurface, std::move(dataSurface)); + return true; +} + +template +void RecordedCacheDataSurface::Record(S& aStream) const { + WriteElement(aStream, mSurface); +} + +template +RecordedCacheDataSurface::RecordedCacheDataSurface(S& aStream) + : RecordedEventDerived(CACHE_DATA_SURFACE) { + ReadElement(aStream, mSurface); +} + +class RecordedPrepareDataForSurface final + : public RecordedEventDerived { + public: + explicit RecordedPrepareDataForSurface(const gfx::SourceSurface* aSurface) + : RecordedEventDerived(PREPARE_DATA_FOR_SURFACE), mSurface(aSurface) {} + + template + MOZ_IMPLICIT RecordedPrepareDataForSurface(S& aStream); + + bool PlayCanvasEvent(CanvasTranslator* aTranslator) const; + + template + void Record(S& aStream) const; + + std::string GetName() const final { return "RecordedPrepareDataForSurface"; } + + private: + ReferencePtr mSurface; +}; + +inline bool RecordedPrepareDataForSurface::PlayCanvasEvent( + CanvasTranslator* aTranslator) const { + RefPtr dataSurface = + aTranslator->LookupDataSurface(mSurface); + if (!dataSurface) { + gfx::SourceSurface* surface = aTranslator->LookupSourceSurface(mSurface); + if (!surface) { + return false; + } + + dataSurface = surface->GetDataSurface(); + if (!dataSurface) { + return false; + } + } + + auto preparedMap = MakeUnique( + dataSurface, gfx::DataSourceSurface::READ); + if (!preparedMap->IsMapped()) { + return false; + } + + aTranslator->SetPreparedMap(mSurface, std::move(preparedMap)); + + return true; +} + +template +void RecordedPrepareDataForSurface::Record(S& aStream) const { + WriteElement(aStream, mSurface); +} + +template +RecordedPrepareDataForSurface::RecordedPrepareDataForSurface(S& aStream) + : RecordedEventDerived(PREPARE_DATA_FOR_SURFACE) { + ReadElement(aStream, mSurface); +} + +class RecordedGetDataForSurface final + : public RecordedEventDerived { + public: + explicit RecordedGetDataForSurface(const gfx::SourceSurface* aSurface) + : RecordedEventDerived(GET_DATA_FOR_SURFACE), mSurface(aSurface) {} + + template + MOZ_IMPLICIT RecordedGetDataForSurface(S& aStream); + + bool PlayCanvasEvent(CanvasTranslator* aTranslator) const; + + template + void Record(S& aStream) const; + + std::string GetName() const final { return "RecordedGetDataForSurface"; } + + private: + ReferencePtr mSurface; +}; + +inline bool RecordedGetDataForSurface::PlayCanvasEvent( + CanvasTranslator* aTranslator) const { + gfx::SourceSurface* surface = aTranslator->LookupSourceSurface(mSurface); + if (!surface) { + return false; + } + + UniquePtr map = + aTranslator->GetPreparedMap(mSurface); + if (!map) { + return false; + } + + gfx::IntSize ssSize = surface->GetSize(); + size_t dataFormatWidth = ssSize.width * BytesPerPixel(surface->GetFormat()); + int32_t srcStride = map->GetStride(); + char* src = reinterpret_cast(map->GetData()); + char* endSrc = src + (ssSize.height * srcStride); + while (src < endSrc) { + aTranslator->ReturnWrite(src, dataFormatWidth); + src += srcStride; + } + + return true; +} + +template +void RecordedGetDataForSurface::Record(S& aStream) const { + WriteElement(aStream, mSurface); +} + +template +RecordedGetDataForSurface::RecordedGetDataForSurface(S& aStream) + : RecordedEventDerived(GET_DATA_FOR_SURFACE) { + ReadElement(aStream, mSurface); +} + +class RecordedAddSurfaceAlias final + : public RecordedEventDerived { + public: + RecordedAddSurfaceAlias(ReferencePtr aSurfaceAlias, + const RefPtr& aActualSurface) + : RecordedEventDerived(ADD_SURFACE_ALIAS), + mSurfaceAlias(aSurfaceAlias), + mActualSurface(aActualSurface) {} + + template + MOZ_IMPLICIT RecordedAddSurfaceAlias(S& aStream); + + bool PlayCanvasEvent(CanvasTranslator* aTranslator) const; + + template + void Record(S& aStream) const; + + std::string GetName() const final { return "RecordedAddSurfaceAlias"; } + + private: + ReferencePtr mSurfaceAlias; + ReferencePtr mActualSurface; +}; + +inline bool RecordedAddSurfaceAlias::PlayCanvasEvent( + CanvasTranslator* aTranslator) const { + RefPtr surface = + aTranslator->LookupSourceSurface(mActualSurface); + if (!surface) { + return false; + } + + aTranslator->AddSourceSurface(mSurfaceAlias, surface); + return true; +} + +template +void RecordedAddSurfaceAlias::Record(S& aStream) const { + WriteElement(aStream, mSurfaceAlias); + WriteElement(aStream, mActualSurface); +} + +template +RecordedAddSurfaceAlias::RecordedAddSurfaceAlias(S& aStream) + : RecordedEventDerived(ADD_SURFACE_ALIAS) { + ReadElement(aStream, mSurfaceAlias); + ReadElement(aStream, mActualSurface); +} + +class RecordedRemoveSurfaceAlias final + : public RecordedEventDerived { + public: + explicit RecordedRemoveSurfaceAlias(ReferencePtr aSurfaceAlias) + : RecordedEventDerived(REMOVE_SURFACE_ALIAS), + mSurfaceAlias(aSurfaceAlias) {} + + template + MOZ_IMPLICIT RecordedRemoveSurfaceAlias(S& aStream); + + bool PlayCanvasEvent(CanvasTranslator* aTranslator) const; + + template + void Record(S& aStream) const; + + std::string GetName() const final { return "RecordedRemoveSurfaceAlias"; } + + private: + ReferencePtr mSurfaceAlias; +}; + +inline bool RecordedRemoveSurfaceAlias::PlayCanvasEvent( + CanvasTranslator* aTranslator) const { + aTranslator->RemoveSourceSurface(mSurfaceAlias); + return true; +} + +template +void RecordedRemoveSurfaceAlias::Record(S& aStream) const { + WriteElement(aStream, mSurfaceAlias); +} + +template +RecordedRemoveSurfaceAlias::RecordedRemoveSurfaceAlias(S& aStream) + : RecordedEventDerived(REMOVE_SURFACE_ALIAS) { + ReadElement(aStream, mSurfaceAlias); +} + +class RecordedDeviceChangeAcknowledged final + : public RecordedEventDerived { + public: + RecordedDeviceChangeAcknowledged() + : RecordedEventDerived(DEVICE_CHANGE_ACKNOWLEDGED) {} + + template + MOZ_IMPLICIT RecordedDeviceChangeAcknowledged(S& aStream); + + bool PlayCanvasEvent(CanvasTranslator* aTranslator) const; + + template + void Record(S& aStream) const; + + std::string GetName() const final { + return "RecordedDeviceChangeAcknowledged"; + } +}; + +inline bool RecordedDeviceChangeAcknowledged::PlayCanvasEvent( + CanvasTranslator* aTranslator) const { + aTranslator->DeviceChangeAcknowledged(); + return true; +} + +template +void RecordedDeviceChangeAcknowledged::Record(S& aStream) const {} + +template +RecordedDeviceChangeAcknowledged::RecordedDeviceChangeAcknowledged(S& aStream) + : RecordedEventDerived(DEVICE_CHANGE_ACKNOWLEDGED) {} + +class RecordedNextTextureId final + : public RecordedEventDerived { + public: + explicit RecordedNextTextureId(int64_t aNextTextureId) + : RecordedEventDerived(NEXT_TEXTURE_ID), mNextTextureId(aNextTextureId) {} + + template + MOZ_IMPLICIT RecordedNextTextureId(S& aStream); + + bool PlayCanvasEvent(CanvasTranslator* aTranslator) const; + + template + void Record(S& aStream) const; + + std::string GetName() const final { return "RecordedNextTextureId"; } + + private: + int64_t mNextTextureId; +}; + +inline bool RecordedNextTextureId::PlayCanvasEvent( + CanvasTranslator* aTranslator) const { + aTranslator->SetNextTextureId(mNextTextureId); + return true; +} + +template +void RecordedNextTextureId::Record(S& aStream) const { + WriteElement(aStream, mNextTextureId); +} + +template +RecordedNextTextureId::RecordedNextTextureId(S& aStream) + : RecordedEventDerived(NEXT_TEXTURE_ID) { + ReadElement(aStream, mNextTextureId); +} + +class RecordedTextureDestruction final + : public RecordedEventDerived { + public: + explicit RecordedTextureDestruction(int64_t aTextureId) + : RecordedEventDerived(TEXTURE_DESTRUCTION), mTextureId(aTextureId) {} + + template + MOZ_IMPLICIT RecordedTextureDestruction(S& aStream); + + bool PlayCanvasEvent(CanvasTranslator* aTranslator) const; + + template + void Record(S& aStream) const; + + std::string GetName() const final { return "RecordedTextureDestruction"; } + + private: + int64_t mTextureId; +}; + +inline bool RecordedTextureDestruction::PlayCanvasEvent( + CanvasTranslator* aTranslator) const { + aTranslator->RemoveTexture(mTextureId); + return true; +} + +template +void RecordedTextureDestruction::Record(S& aStream) const { + WriteElement(aStream, mTextureId); +} + +template +RecordedTextureDestruction::RecordedTextureDestruction(S& aStream) + : RecordedEventDerived(TEXTURE_DESTRUCTION) { + ReadElement(aStream, mTextureId); +} + +#define FOR_EACH_CANVAS_EVENT(f) \ + f(CANVAS_BEGIN_TRANSACTION, RecordedCanvasBeginTransaction); \ + f(CANVAS_END_TRANSACTION, RecordedCanvasEndTransaction); \ + f(CANVAS_FLUSH, RecordedCanvasFlush); \ + f(TEXTURE_LOCK, RecordedTextureLock); \ + f(TEXTURE_UNLOCK, RecordedTextureUnlock); \ + f(CACHE_DATA_SURFACE, RecordedCacheDataSurface); \ + f(PREPARE_DATA_FOR_SURFACE, RecordedPrepareDataForSurface); \ + f(GET_DATA_FOR_SURFACE, RecordedGetDataForSurface); \ + f(ADD_SURFACE_ALIAS, RecordedAddSurfaceAlias); \ + f(REMOVE_SURFACE_ALIAS, RecordedRemoveSurfaceAlias); \ + f(DEVICE_CHANGE_ACKNOWLEDGED, RecordedDeviceChangeAcknowledged); \ + f(NEXT_TEXTURE_ID, RecordedNextTextureId); \ + f(TEXTURE_DESTRUCTION, RecordedTextureDestruction); + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_RecordedCanvasEventImpl_h diff --git a/gfx/layers/RenderTrace.cpp b/gfx/layers/RenderTrace.cpp new file mode 100644 index 0000000000..a2897a675e --- /dev/null +++ b/gfx/layers/RenderTrace.cpp @@ -0,0 +1,79 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "RenderTrace.h" + +// If rendertrace is off let's no compile this code +#ifdef MOZ_RENDERTRACE +# include "Layers.h" +# include "TreeTraversal.h" // for ForEachNode + +namespace mozilla { +namespace layers { + +static gfx::Matrix4x4 GetRootTransform(Layer* aLayer) { + gfx::Matrix4x4 layerTrans = aLayer->GetTransform(); + layerTrans.ProjectTo2D(); + if (aLayer->GetParent() != nullptr) { + return GetRootTransform(aLayer->GetParent()) * layerTrans; + } + return layerTrans; +} + +void RenderTraceLayers(Layer* aLayer, const char* aColor, + const gfx::Matrix4x4 aRootTransform) { + int colorId = 0; + ForEachNode(aLayer, [&colorId](Layer* layer) { + gfx::Matrix4x4 trans = aRootTransform * layer->GetTransform(); + trans.ProjectTo2D(); + gfx::IntRect clipRect = layer->GetLocalVisibleRegion().GetBounds(); + Rect rect(clipRect.x, clipRect.y, clipRect.width, clipRect.height); + trans.TransformBounds(rect); + + if (strcmp(layer->Name(), "ContainerLayer") != 0 && + strcmp(layer->Name(), "ContainerLayerComposite") != 0) { + printf_stderr("%s RENDERTRACE %u rect #%02X%s %i %i %i %i\n", + layer->Name(), (int)PR_IntervalNow(), colorId, aColor, + (int)rect.x, (int)rect.y, (int)rect.width, + (int)rect.height); + } + colorId++; + }); +} + +void RenderTraceInvalidateStart(Layer* aLayer, const char* aColor, + const gfx::IntRect aRect) { + gfx::Matrix4x4 trans = GetRootTransform(aLayer); + gfx::Rect rect(aRect.x, aRect.y, aRect.width, aRect.height); + trans.TransformBounds(rect); + + printf_stderr("%s RENDERTRACE %u fillrect #%s %i %i %i %i\n", aLayer->Name(), + (int)PR_IntervalNow(), aColor, (int)rect.x, (int)rect.y, + (int)rect.width, (int)rect.height); +} +void RenderTraceInvalidateEnd(Layer* aLayer, const char* aColor) { + // Clear with an empty rect + RenderTraceInvalidateStart(aLayer, aColor, gfx::IntRect()); +} + +void renderTraceEventStart(const char* aComment, const char* aColor) { + printf_stderr("%s RENDERTRACE %u fillrect #%s 0 0 10 10\n", aComment, + (int)PR_IntervalNow(), aColor); +} + +void renderTraceEventEnd(const char* aComment, const char* aColor) { + printf_stderr("%s RENDERTRACE %u fillrect #%s 0 0 0 0\n", aComment, + (int)PR_IntervalNow(), aColor); +} + +void renderTraceEventEnd(const char* aColor) { + renderTraceEventEnd("", aColor); +} + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/RenderTrace.h b/gfx/layers/RenderTrace.h new file mode 100644 index 0000000000..404ba1ea1d --- /dev/null +++ b/gfx/layers/RenderTrace.h @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This is a general tool that will let you visualize platform operation. +// Currently used for the layer system, the general syntax allows this +// tools to be adapted to trace other operations. +// +// For the front end see: https://github.com/staktrace/rendertrace + +// Uncomment this line to enable RENDERTRACE +//#define MOZ_RENDERTRACE + +#ifndef GFX_RENDERTRACE_H +#define GFX_RENDERTRACE_H + +#include "nsRect.h" +#include "mozilla/gfx/Matrix.h" + +namespace mozilla { +namespace layers { + +class Layer; + +void RenderTraceLayers(Layer* aLayer, const char* aColor, + const gfx::Matrix4x4 aRootTransform = gfx::Matrix4x4(), + bool aReset = true); + +void RenderTraceInvalidateStart(Layer* aLayer, const char* aColor, + const gfx::IntRect aRect); +void RenderTraceInvalidateEnd(Layer* aLayer, const char* aColor); + +void renderTraceEventStart(const char* aComment, const char* aColor); +void renderTraceEventEnd(const char* aComment, const char* aColor); +void renderTraceEventEnd(const char* aColor); + +struct RenderTraceScope { + public: + RenderTraceScope(const char* aComment, const char* aColor) + : mComment(aComment), mColor(aColor) { + renderTraceEventStart(mComment, mColor); + } + ~RenderTraceScope() { renderTraceEventEnd(mComment, mColor); } + + private: + const char* mComment; + const char* mColor; +}; + +#ifndef MOZ_RENDERTRACE +inline void RenderTraceLayers(Layer* aLayer, const char* aColor, + const gfx::Matrix4x4 aRootTransform, + bool aReset) {} + +inline void RenderTraceInvalidateStart(Layer* aLayer, const char* aColor, + const gfx::IntRect aRect) {} + +inline void RenderTraceInvalidateEnd(Layer* aLayer, const char* aColor) {} + +inline void renderTraceEventStart(const char* aComment, const char* aColor) {} + +inline void renderTraceEventEnd(const char* aComment, const char* aColor) {} + +inline void renderTraceEventEnd(const char* aColor) {} + +#endif // MOZ_RENDERTRACE + +} // namespace layers +} // namespace mozilla + +#endif // GFX_RENDERTRACE_H diff --git a/gfx/layers/RepaintRequest.cpp b/gfx/layers/RepaintRequest.cpp new file mode 100644 index 0000000000..58a72473e8 --- /dev/null +++ b/gfx/layers/RepaintRequest.cpp @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "RepaintRequest.h" + +#include + +namespace mozilla { +namespace layers { + +std::ostream& operator<<(std::ostream& aOut, const RepaintRequest& aRequest) { + aOut << "{ scrollId=" << aRequest.mScrollId + << ", scrollOffset=" << aRequest.mScrollOffset + << ", zoom=" << aRequest.mZoom + << ", viewport=" << aRequest.mLayoutViewport + << ", scrollUpdateType=" << (int)aRequest.mScrollUpdateType + << ", scrollGeneration=" << aRequest.mScrollGeneration + << ", dpMargins=" << aRequest.mDisplayPortMargins << "}"; + return aOut; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/RepaintRequest.h b/gfx/layers/RepaintRequest.h new file mode 100644 index 0000000000..b73045aa6e --- /dev/null +++ b/gfx/layers/RepaintRequest.h @@ -0,0 +1,312 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_REPAINTREQUEST_H +#define GFX_REPAINTREQUEST_H + +#include +#include // for uint8_t, uint32_t, uint64_t + +#include "FrameMetrics.h" // for FrameMetrics +#include "mozilla/DefineEnum.h" // for MOZ_DEFINE_ENUM +#include "mozilla/gfx/BasePoint.h" // for BasePoint +#include "mozilla/gfx/Rect.h" // for RoundedIn +#include "mozilla/gfx/ScaleFactor.h" // for ScaleFactor +#include "mozilla/TimeStamp.h" // for TimeStamp +#include "Units.h" // for CSSRect, CSSPixel, etc + +namespace IPC { +template +struct ParamTraits; +} // namespace IPC + +namespace mozilla { +namespace layers { + +struct RepaintRequest { + friend struct IPC::ParamTraits; + + public: + // clang-format off + MOZ_DEFINE_ENUM_WITH_BASE_AT_CLASS_SCOPE( + ScrollOffsetUpdateType, uint8_t, ( + eNone, // The default; the scroll offset was not updated. + eUserAction, // The scroll offset was updated by APZ in response + // to user action. + eVisualUpdate // The scroll offset was updated by APZ in response + // to a visual scroll update request from the + // main thread. + )); + // clang-format on + + RepaintRequest() + : mScrollId(ScrollableLayerGuid::NULL_SCROLL_ID), + mPresShellResolution(1), + mCompositionBounds(0, 0, 0, 0), + mCumulativeResolution(), + mDevPixelsPerCSSPixel(1), + mScrollOffset(0, 0), + mZoom(), + mDisplayPortMargins(0, 0, 0, 0), + mPresShellId(-1), + mLayoutViewport(0, 0, 0, 0), + mExtraResolution(), + mPaintRequestTime(), + mScrollUpdateType(eNone), + mIsRootContent(false), + mIsAnimationInProgress(false), + mIsScrollInfoLayer(false) {} + + RepaintRequest(const FrameMetrics& aOther, + const ScreenMargin& aDisplayportMargins, + const ScrollOffsetUpdateType aScrollUpdateType, + bool aIsAnimationInProgress) + : mScrollId(aOther.GetScrollId()), + mPresShellResolution(aOther.GetPresShellResolution()), + mCompositionBounds(aOther.GetCompositionBounds()), + mCumulativeResolution(aOther.GetCumulativeResolution()), + mDevPixelsPerCSSPixel(aOther.GetDevPixelsPerCSSPixel()), + mScrollOffset(aOther.GetVisualScrollOffset()), + mZoom(aOther.GetZoom()), + mScrollGeneration(aOther.GetScrollGeneration()), + mDisplayPortMargins(aDisplayportMargins), + mPresShellId(aOther.GetPresShellId()), + mLayoutViewport(aOther.GetLayoutViewport()), + mExtraResolution(aOther.GetExtraResolution()), + mPaintRequestTime(aOther.GetPaintRequestTime()), + mScrollUpdateType(aScrollUpdateType), + mIsRootContent(aOther.IsRootContent()), + mIsAnimationInProgress(aIsAnimationInProgress), + mIsScrollInfoLayer(aOther.IsScrollInfoLayer()) {} + + // Default copy ctor and operator= are fine + + bool operator==(const RepaintRequest& aOther) const { + // Put mScrollId at the top since it's the most likely one to fail. + return mScrollId == aOther.mScrollId && + mPresShellResolution == aOther.mPresShellResolution && + mCompositionBounds.IsEqualEdges(aOther.mCompositionBounds) && + mCumulativeResolution == aOther.mCumulativeResolution && + mDevPixelsPerCSSPixel == aOther.mDevPixelsPerCSSPixel && + mScrollOffset == aOther.mScrollOffset && + // don't compare mZoom + mScrollGeneration == aOther.mScrollGeneration && + mDisplayPortMargins == aOther.mDisplayPortMargins && + mPresShellId == aOther.mPresShellId && + mLayoutViewport.IsEqualEdges(aOther.mLayoutViewport) && + mExtraResolution == aOther.mExtraResolution && + mPaintRequestTime == aOther.mPaintRequestTime && + mScrollUpdateType == aOther.mScrollUpdateType && + mIsRootContent == aOther.mIsRootContent && + mIsAnimationInProgress == aOther.mIsAnimationInProgress && + mIsScrollInfoLayer == aOther.mIsScrollInfoLayer; + } + + bool operator!=(const RepaintRequest& aOther) const { + return !operator==(aOther); + } + + friend std::ostream& operator<<(std::ostream& aOut, + const RepaintRequest& aRequest); + + CSSToScreenScale2D DisplayportPixelsPerCSSPixel() const { + // Note: use 'mZoom * ParentLayerToLayerScale(1.0f)' as the CSS-to-Layer + // scale instead of LayersPixelsPerCSSPixel(), because displayport + // calculations are done in the context of a repaint request, where we ask + // Layout to repaint at a new resolution that includes any async zoom. Until + // this repaint request is processed, LayersPixelsPerCSSPixel() does not yet + // include the async zoom, but it will when the displayport is interpreted + // for the repaint. + return mZoom * ParentLayerToLayerScale(1.0f) / mExtraResolution; + } + + CSSToLayerScale2D LayersPixelsPerCSSPixel() const { + return mDevPixelsPerCSSPixel * mCumulativeResolution; + } + + // Get the amount by which this frame has been zoomed since the last repaint. + LayerToParentLayerScale GetAsyncZoom() const { + // The async portion of the zoom should be the same along the x and y + // axes. + return (mZoom / LayersPixelsPerCSSPixel()).ToScaleFactor(); + } + + CSSSize CalculateCompositedSizeInCssPixels() const { + if (GetZoom() == CSSToParentLayerScale2D(0, 0)) { + return CSSSize(); // avoid division by zero + } + return mCompositionBounds.Size() / GetZoom(); + } + + float GetPresShellResolution() const { return mPresShellResolution; } + + const ParentLayerRect& GetCompositionBounds() const { + return mCompositionBounds; + } + + const LayoutDeviceToLayerScale2D& GetCumulativeResolution() const { + return mCumulativeResolution; + } + + const CSSToLayoutDeviceScale& GetDevPixelsPerCSSPixel() const { + return mDevPixelsPerCSSPixel; + } + + bool IsAnimationInProgress() const { return mIsAnimationInProgress; } + + bool IsRootContent() const { return mIsRootContent; } + + CSSPoint GetLayoutScrollOffset() const { return mLayoutViewport.TopLeft(); } + + const CSSPoint& GetVisualScrollOffset() const { return mScrollOffset; } + + const CSSToParentLayerScale2D& GetZoom() const { return mZoom; } + + ScrollOffsetUpdateType GetScrollUpdateType() const { + return mScrollUpdateType; + } + + bool GetScrollOffsetUpdated() const { return mScrollUpdateType != eNone; } + + ScrollGeneration GetScrollGeneration() const { return mScrollGeneration; } + + ScrollableLayerGuid::ViewID GetScrollId() const { return mScrollId; } + + const ScreenMargin& GetDisplayPortMargins() const { + return mDisplayPortMargins; + } + + uint32_t GetPresShellId() const { return mPresShellId; } + + const CSSRect& GetLayoutViewport() const { return mLayoutViewport; } + + const ScreenToLayerScale2D& GetExtraResolution() const { + return mExtraResolution; + } + + const TimeStamp& GetPaintRequestTime() const { return mPaintRequestTime; } + + bool IsScrollInfoLayer() const { return mIsScrollInfoLayer; } + + protected: + void SetIsAnimationInProgress(bool aInProgress) { + mIsAnimationInProgress = aInProgress; + } + + void SetIsRootContent(bool aIsRootContent) { + mIsRootContent = aIsRootContent; + } + + void SetIsScrollInfoLayer(bool aIsScrollInfoLayer) { + mIsScrollInfoLayer = aIsScrollInfoLayer; + } + + private: + // A ID assigned to each scrollable frame, unique within each LayersId.. + ScrollableLayerGuid::ViewID mScrollId; + + // The pres-shell resolution that has been induced on the document containing + // this scroll frame as a result of zooming this scroll frame (whether via + // user action, or choosing an initial zoom level on page load). This can + // only be different from 1.0 for frames that are zoomable, which currently + // is just the root content document's root scroll frame + // (mIsRootContent = true). + // This is a plain float rather than a ScaleFactor because in and of itself + // it does not convert between any coordinate spaces for which we have names. + float mPresShellResolution; + + // This is the area within the widget that we're compositing to. It is in the + // layer coordinates of the scrollable content's parent layer. + // + // The size of the composition bounds corresponds to the size of the scroll + // frame's scroll port (but in a coordinate system where the size does not + // change during zooming). + // + // The origin of the composition bounds is relative to the layer tree origin. + // Unlike the scroll port's origin, it does not change during scrolling of + // the scrollable layer to which it is associated. However, it may change due + // to scrolling of ancestor layers. + // + // This value is provided by Gecko at layout/paint time. + ParentLayerRect mCompositionBounds; + + // The cumulative resolution that the current frame has been painted at. + // This is the product of the pres-shell resolutions of the document + // containing this scroll frame and its ancestors, and any css-driven + // resolution. This information is provided by Gecko at layout/paint time. + // Note that this is allowed to have different x- and y-scales, but only + // for subframes (mIsRootContent = false). (The same applies to other scales + // that "inherit" the 2D-ness of this one, such as mZoom.) + LayoutDeviceToLayerScale2D mCumulativeResolution; + + // The conversion factor between CSS pixels and device pixels for this frame. + // This can vary based on a variety of things, such as reflowing-zoom. + CSSToLayoutDeviceScale mDevPixelsPerCSSPixel; + + // The position of the top-left of the scroll frame's scroll port, relative + // to the scrollable content's origin. + CSSPoint mScrollOffset; + + // The "user zoom". Content is painted by gecko at mCumulativeResolution * + // mDevPixelsPerCSSPixel, but will be drawn to the screen at mZoom. In the + // steady state, the two will be the same, but during an async zoom action the + // two may diverge. This information is initialized in Gecko but updated in + // the APZC. + CSSToParentLayerScale2D mZoom; + + // The scroll generation counter used to acknowledge the scroll offset update. + ScrollGeneration mScrollGeneration; + + // A display port expressed as layer margins that apply to the rect of what + // is drawn of the scrollable element. + ScreenMargin mDisplayPortMargins; + + uint32_t mPresShellId; + + // For a root scroll frame (RSF), the document's layout viewport + // (sometimes called "CSS viewport" in older code). + // + // Its size is the dimensions we're using to constrain the element + // of the document (i.e. the initial containing block (ICB) size). + // + // Its origin is the RSF's layout scroll position, i.e. the scroll position + // exposed to web content via window.scrollX/Y. + // + // Note that only the root content document's RSF has a layout viewport + // that's distinct from the visual viewport. For an iframe RSF, the two + // are the same. + // + // For a scroll frame that is not an RSF, this metric is meaningless and + // invalid. + CSSRect mLayoutViewport; + + // The extra resolution at which content in this scroll frame is drawn beyond + // that necessary to draw one Layer pixel per Screen pixel. + ScreenToLayerScale2D mExtraResolution; + + // The time at which the APZC last requested a repaint for this scroll frame. + TimeStamp mPaintRequestTime; + + // The type of repaint request this represents. + ScrollOffsetUpdateType mScrollUpdateType; + + // Whether or not this is the root scroll frame for the root content document. + bool mIsRootContent : 1; + + // Whether or not we are in the middle of a scroll animation. + bool mIsAnimationInProgress : 1; + + // True if this scroll frame is a scroll info layer. A scroll info layer is + // not layerized and its content cannot be truly async-scrolled, but its + // metrics are still sent to and updated by the compositor, with the updates + // being reflected on the next paint rather than the next composite. + bool mIsScrollInfoLayer : 1; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_REPAINTREQUEST_H */ diff --git a/gfx/layers/RotatedBuffer.cpp b/gfx/layers/RotatedBuffer.cpp new file mode 100644 index 0000000000..a1651f2b92 --- /dev/null +++ b/gfx/layers/RotatedBuffer.cpp @@ -0,0 +1,477 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "RotatedBuffer.h" + +#include // for int32_t + +#include // for max +#include // for Move + +#include "BasicImplData.h" // for BasicImplData +#include "BasicLayersImpl.h" // for ToData +#include "GeckoProfiler.h" // for AUTO_PROFILER_LABEL +#include "Layers.h" // for PaintedLayer, Layer, etc +#include "PaintThread.h" +#include "gfx2DGlue.h" +#include "gfxPlatform.h" // for gfxPlatform +#include "gfxUtils.h" // for gfxUtils +#include "mozilla/ArrayUtils.h" // for ArrayLength +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/gfx/BasePoint.h" // for BasePoint +#include "mozilla/gfx/BaseRect.h" // for BaseRect +#include "mozilla/gfx/BaseSize.h" // for BaseSize +#include "mozilla/gfx/Matrix.h" // for Matrix +#include "mozilla/gfx/Point.h" // for Point, IntPoint +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/gfx/Rect.h" // for Rect, IntRect +#include "mozilla/gfx/Types.h" // for ExtendMode::ExtendMode::CLAMP, etc +#include "mozilla/layers/ShadowLayers.h" // for ShadowableLayer +#include "mozilla/layers/TextureClient.h" // for TextureClient +#include "nsLayoutUtils.h" // for invalidation debugging + +namespace mozilla { + +using namespace gfx; + +namespace layers { + +void BorrowDrawTarget::ReturnDrawTarget(gfx::DrawTarget*& aReturned) { + MOZ_ASSERT(mLoanedDrawTarget); + MOZ_ASSERT(aReturned == mLoanedDrawTarget); + if (mLoanedDrawTarget) { + mLoanedDrawTarget->SetTransform(mLoanedTransform); + mLoanedDrawTarget = nullptr; + } + aReturned = nullptr; +} + +IntRect RotatedBuffer::GetQuadrantRectangle(XSide aXSide, YSide aYSide) const { + // quadrantTranslation is the amount we translate the top-left + // of the quadrant by to get coordinates relative to the layer + IntPoint quadrantTranslation = -mBufferRotation; + quadrantTranslation.x += aXSide == LEFT ? mBufferRect.Width() : 0; + quadrantTranslation.y += aYSide == TOP ? mBufferRect.Height() : 0; + return mBufferRect + quadrantTranslation; +} + +Rect RotatedBuffer::GetSourceRectangle(XSide aXSide, YSide aYSide) const { + Rect result; + if (aXSide == LEFT) { + result.SetBoxX(0, mBufferRotation.x); + } else { + result.SetBoxX(mBufferRotation.x, mBufferRect.Width()); + } + if (aYSide == TOP) { + result.SetBoxY(0, mBufferRotation.y); + } else { + result.SetBoxY(mBufferRotation.y, mBufferRect.Height()); + } + return result; +} + +void RotatedBuffer::BeginCapture() { + RefPtr target = GetBufferTarget(); + + MOZ_ASSERT(!mCapture); + MOZ_ASSERT(target); + mCapture = Factory::CreateCaptureDrawTargetForTarget( + target, StaticPrefs::layers_omtp_capture_limit_AtStartup()); +} + +RefPtr RotatedBuffer::EndCapture() { + MOZ_ASSERT(mCapture); + return std::move(mCapture); +} + +/** + * @param aXSide LEFT means we draw from the left side of the buffer (which + * is drawn on the right side of mBufferRect). RIGHT means we draw from + * the right side of the buffer (which is drawn on the left side of + * mBufferRect). + * @param aYSide TOP means we draw from the top side of the buffer (which + * is drawn on the bottom side of mBufferRect). BOTTOM means we draw from + * the bottom side of the buffer (which is drawn on the top side of + * mBufferRect). + */ +void RotatedBuffer::DrawBufferQuadrant( + gfx::DrawTarget* aTarget, XSide aXSide, YSide aYSide, float aOpacity, + gfx::CompositionOp aOperator, gfx::SourceSurface* aMask, + const gfx::Matrix* aMaskTransform) const { + // The rectangle that we're going to fill. Basically we're going to + // render the buffer at mBufferRect + quadrantTranslation to get the + // pixels in the right place, but we're only going to paint within + // mBufferRect + IntRect quadrantRect = GetQuadrantRectangle(aXSide, aYSide); + IntRect fillRect; + if (!fillRect.IntersectRect(mBufferRect, quadrantRect)) return; + + gfx::Point quadrantTranslation(quadrantRect.X(), quadrantRect.Y()); + + RefPtr snapshot = GetBufferSource(); + + if (!snapshot) { + gfxCriticalError() + << "Invalid snapshot in RotatedBuffer::DrawBufferQuadrant"; + return; + } + + // direct2d is much slower when using OP_SOURCE so use OP_OVER and + // (maybe) a clear instead. Normally we need to draw in a single operation + // (to avoid flickering) but direct2d is ok since it defers rendering. + // We should try abstract this logic in a helper when we have other use + // cases. + if ((aTarget->GetBackendType() == BackendType::DIRECT2D || + aTarget->GetBackendType() == BackendType::DIRECT2D1_1) && + aOperator == CompositionOp::OP_SOURCE) { + aOperator = CompositionOp::OP_OVER; + if (snapshot->GetFormat() == SurfaceFormat::B8G8R8A8) { + aTarget->ClearRect(IntRectToRect(fillRect)); + } + } + + // OP_SOURCE is unbounded in Azure, and we really don't want that behaviour + // here. We also can't do a ClearRect+FillRect since we need the drawing to + // happen as an atomic operation (to prevent flickering). We also need this + // clip in the case where we have a mask, since the mask surface might cover + // more than fillRect, but we only want to touch the pixels inside fillRect. + aTarget->PushClipRect(IntRectToRect(fillRect)); + + if (aMask) { + Matrix oldTransform = aTarget->GetTransform(); + + // Transform from user -> buffer space. + Matrix transform = + Matrix::Translation(quadrantTranslation.x, quadrantTranslation.y); + + Matrix inverseMask = *aMaskTransform; + inverseMask.Invert(); + + transform *= oldTransform; + transform *= inverseMask; + +#ifdef MOZ_GFX_OPTIMIZE_MOBILE + SurfacePattern source(snapshot, ExtendMode::CLAMP, transform, + SamplingFilter::POINT); +#else + SurfacePattern source(snapshot, ExtendMode::CLAMP, transform); +#endif + + aTarget->SetTransform(*aMaskTransform); + aTarget->MaskSurface(source, aMask, Point(0, 0), + DrawOptions(aOpacity, aOperator)); + aTarget->SetTransform(oldTransform); + } else { +#ifdef MOZ_GFX_OPTIMIZE_MOBILE + DrawSurfaceOptions options(SamplingFilter::POINT); +#else + DrawSurfaceOptions options; +#endif + aTarget->DrawSurface(snapshot, IntRectToRect(fillRect), + GetSourceRectangle(aXSide, aYSide), options, + DrawOptions(aOpacity, aOperator)); + } + + aTarget->PopClip(); +} + +void RotatedBuffer::DrawBufferWithRotation( + gfx::DrawTarget* aTarget, float aOpacity, gfx::CompositionOp aOperator, + gfx::SourceSurface* aMask, const gfx::Matrix* aMaskTransform) const { + AUTO_PROFILER_LABEL("RotatedBuffer::DrawBufferWithRotation", GRAPHICS); + + // See above, in Azure Repeat should always be a safe, even faster choice + // though! Particularly on D2D Repeat should be a lot faster, need to look + // into that. TODO[Bas] + DrawBufferQuadrant(aTarget, LEFT, TOP, aOpacity, aOperator, aMask, + aMaskTransform); + DrawBufferQuadrant(aTarget, RIGHT, TOP, aOpacity, aOperator, aMask, + aMaskTransform); + DrawBufferQuadrant(aTarget, LEFT, BOTTOM, aOpacity, aOperator, aMask, + aMaskTransform); + DrawBufferQuadrant(aTarget, RIGHT, BOTTOM, aOpacity, aOperator, aMask, + aMaskTransform); +} + +static bool IsClippingCheap(gfx::DrawTarget* aTarget, + const nsIntRegion& aRegion) { + // Assume clipping is cheap if the draw target just has an integer + // translation, and the visible region is simple. + return !aTarget->GetTransform().HasNonIntegerTranslation() && + aRegion.GetNumRects() <= 1; +} + +void RotatedBuffer::DrawTo(PaintedLayer* aLayer, DrawTarget* aTarget, + float aOpacity, CompositionOp aOp, + SourceSurface* aMask, const Matrix* aMaskTransform) { + bool clipped = false; + + // If the entire buffer is valid, we can just draw the whole thing, + // no need to clip. But we'll still clip if clipping is cheap --- + // that might let us copy a smaller region of the buffer. + // Also clip to the visible region if we're told to. + if (!aLayer->GetValidRegion().Contains(BufferRect()) || + (ToData(aLayer)->GetClipToVisibleRegion() && + !aLayer->GetVisibleRegion().ToUnknownRegion().Contains(BufferRect())) || + IsClippingCheap(aTarget, + aLayer->GetLocalVisibleRegion().ToUnknownRegion())) { + // We don't want to draw invalid stuff, so we need to clip. Might as + // well clip to the smallest area possible --- the visible region. + // Bug 599189 if there is a non-integer-translation transform in aTarget, + // we might sample pixels outside GetLocalVisibleRegion(), which is wrong + // and may cause gray lines. + gfxUtils::ClipToRegion(aTarget, + aLayer->GetLocalVisibleRegion().ToUnknownRegion()); + clipped = true; + } + + DrawBufferWithRotation(aTarget, aOpacity, aOp, aMask, aMaskTransform); + if (clipped) { + aTarget->PopClip(); + } +} + +void RotatedBuffer::UpdateDestinationFrom(const RotatedBuffer& aSource, + const gfx::IntRect& aUpdateRect) { + DrawIterator iter; + while (DrawTarget* destDT = + BorrowDrawTargetForQuadrantUpdate(aUpdateRect, &iter)) { + bool isClippingCheap = IsClippingCheap(destDT, iter.mDrawRegion); + if (isClippingCheap) { + gfxUtils::ClipToRegion(destDT, iter.mDrawRegion); + } + + aSource.DrawBufferWithRotation(destDT, 1.0, CompositionOp::OP_SOURCE); + if (isClippingCheap) { + destDT->PopClip(); + } + ReturnDrawTarget(destDT); + } +} + +static void WrapRotationAxis(int32_t* aRotationPoint, int32_t aSize) { + if (*aRotationPoint < 0) { + *aRotationPoint += aSize; + } else if (*aRotationPoint >= aSize) { + *aRotationPoint -= aSize; + } +} + +bool RotatedBuffer::Parameters::IsRotated() const { + return mBufferRotation != IntPoint(0, 0); +} + +bool RotatedBuffer::Parameters::RectWrapsBuffer( + const gfx::IntRect& aRect) const { + int32_t xBoundary = mBufferRect.XMost() - mBufferRotation.x; + int32_t yBoundary = mBufferRect.YMost() - mBufferRotation.y; + return (aRect.X() < xBoundary && xBoundary < aRect.XMost()) || + (aRect.Y() < yBoundary && yBoundary < aRect.YMost()); +} + +void RotatedBuffer::Parameters::SetUnrotated() { + mBufferRotation = IntPoint(0, 0); + mDidSelfCopy = true; +} + +RotatedBuffer::Parameters RotatedBuffer::AdjustedParameters( + const gfx::IntRect& aDestBufferRect) const { + IntRect keepArea; + if (keepArea.IntersectRect(aDestBufferRect, mBufferRect)) { + // Set mBufferRotation so that the pixels currently in mDTBuffer + // will still be rendered in the right place when mBufferRect + // changes to aDestBufferRect. + IntPoint newRotation = + mBufferRotation + (aDestBufferRect.TopLeft() - mBufferRect.TopLeft()); + WrapRotationAxis(&newRotation.x, mBufferRect.Width()); + WrapRotationAxis(&newRotation.y, mBufferRect.Height()); + NS_ASSERTION(gfx::IntRect(gfx::IntPoint(0, 0), mBufferRect.Size()) + .Contains(newRotation), + "newRotation out of bounds"); + + return Parameters{aDestBufferRect, newRotation}; + } + + // No pixels are going to be kept. The whole visible region + // will be redrawn, so we don't need to copy anything, so we don't + // set destBuffer. + return Parameters{aDestBufferRect, IntPoint(0, 0)}; +} + +bool RotatedBuffer::UnrotateBufferTo(const Parameters& aParameters) { + RefPtr drawTarget = GetDrawTarget(); + MOZ_ASSERT(drawTarget && drawTarget->IsValid()); + + if (mBufferRotation == IntPoint(0, 0)) { + IntRect srcRect(IntPoint(0, 0), mBufferRect.Size()); + IntPoint dest = mBufferRect.TopLeft() - aParameters.mBufferRect.TopLeft(); + + drawTarget->CopyRect(srcRect, dest); + return true; + } else { + return drawTarget->Unrotate(aParameters.mBufferRotation); + } +} + +void RotatedBuffer::SetParameters( + const RotatedBuffer::Parameters& aParameters) { + mBufferRect = aParameters.mBufferRect; + mBufferRotation = aParameters.mBufferRotation; + mDidSelfCopy = aParameters.mDidSelfCopy; +} + +RotatedBuffer::ContentType RotatedBuffer::GetContentType() const { + return ContentForFormat(GetFormat()); +} + +DrawTarget* RotatedBuffer::BorrowDrawTargetForQuadrantUpdate( + const IntRect& aBounds, DrawIterator* aIter) { + IntRect bounds = aBounds; + if (aIter) { + // If an iterator was provided, then BeginPaint must have been run with + // PAINT_CAN_DRAW_ROTATED, and the draw region might cover multiple + // quadrants. Iterate over each of them, and return an appropriate buffer + // each time we find one that intersects the draw region. The iterator + // mCount value tracks which quadrants we have considered across multiple + // calls to this function. + aIter->mDrawRegion.SetEmpty(); + while (aIter->mCount < 4) { + IntRect quadrant = + GetQuadrantRectangle((aIter->mCount & 1) ? LEFT : RIGHT, + (aIter->mCount & 2) ? TOP : BOTTOM); + aIter->mDrawRegion.And(aBounds, quadrant); + aIter->mCount++; + if (!aIter->mDrawRegion.IsEmpty()) { + break; + } + } + if (aIter->mDrawRegion.IsEmpty()) { + return nullptr; + } + bounds = aIter->mDrawRegion.GetBounds(); + } + + MOZ_ASSERT(!mLoanedDrawTarget, + "draw target has been borrowed and not returned"); + mLoanedDrawTarget = GetDrawTarget(); + + // Figure out which quadrant to draw in + int32_t xBoundary = mBufferRect.XMost() - mBufferRotation.x; + int32_t yBoundary = mBufferRect.YMost() - mBufferRotation.y; + XSide sideX = bounds.XMost() <= xBoundary ? RIGHT : LEFT; + YSide sideY = bounds.YMost() <= yBoundary ? BOTTOM : TOP; + IntRect quadrantRect = GetQuadrantRectangle(sideX, sideY); + NS_ASSERTION(quadrantRect.Contains(bounds), "Messed up quadrants"); + + mLoanedTransform = mLoanedDrawTarget->GetTransform(); + Matrix transform = Matrix(mLoanedTransform) + .PreTranslate(-quadrantRect.X(), -quadrantRect.Y()); + mLoanedDrawTarget->SetTransform(transform); + + return mLoanedDrawTarget; +} + +gfx::SurfaceFormat RemoteRotatedBuffer::GetFormat() const { + return mClient->GetFormat(); +} + +bool RemoteRotatedBuffer::IsLocked() { return mClient->IsLocked(); } + +bool RemoteRotatedBuffer::Lock(OpenMode aMode) { + MOZ_ASSERT(!mTarget); + MOZ_ASSERT(!mTargetOnWhite); + + bool locked = + mClient->Lock(aMode) && (!mClientOnWhite || mClientOnWhite->Lock(aMode)); + if (!locked) { + Unlock(); + return false; + } + + mTarget = mClient->BorrowDrawTarget(); + if (!mTarget || !mTarget->IsValid()) { + gfxCriticalNote << "Invalid draw target " << hexa(mTarget) + << " in RemoteRotatedBuffer::Lock"; + Unlock(); + return false; + } + + if (mClientOnWhite) { + mTargetOnWhite = mClientOnWhite->BorrowDrawTarget(); + if (!mTargetOnWhite || !mTargetOnWhite->IsValid()) { + gfxCriticalNote << "Invalid draw target(s) " << hexa(mTarget) << " and " + << hexa(mTargetOnWhite) + << " in RemoteRotatedBuffer::Lock"; + Unlock(); + return false; + } + } + + if (mTargetOnWhite) { + mTargetDual = Factory::CreateDualDrawTarget(mTarget, mTargetOnWhite); + + if (!mTargetDual || !mTargetDual->IsValid()) { + gfxCriticalNote << "Invalid dual draw target " << hexa(mTargetDual) + << " in RemoteRotatedBuffer::Lock"; + Unlock(); + return false; + } + } else { + mTargetDual = mTarget; + } + + return true; +} + +void RemoteRotatedBuffer::Unlock() { + mTarget = nullptr; + mTargetOnWhite = nullptr; + mTargetDual = nullptr; + + if (mClient->IsLocked()) { + mClient->Unlock(); + } + if (mClientOnWhite && mClientOnWhite->IsLocked()) { + mClientOnWhite->Unlock(); + } +} + +void RemoteRotatedBuffer::SyncWithObject(RefPtr aSyncObject) { + mClient->SyncWithObject(aSyncObject); + if (mClientOnWhite) { + mClientOnWhite->SyncWithObject(aSyncObject); + } +} + +void RemoteRotatedBuffer::Clear() { + MOZ_ASSERT(!mTarget && !mTargetOnWhite); + mClient = nullptr; + mClientOnWhite = nullptr; +} + +gfx::DrawTarget* RemoteRotatedBuffer::GetBufferTarget() const { + return mTargetDual; +} + +gfx::SurfaceFormat DrawTargetRotatedBuffer::GetFormat() const { + return mTarget->GetFormat(); +} + +gfx::DrawTarget* DrawTargetRotatedBuffer::GetBufferTarget() const { + return mTargetDual; +} + +gfx::SurfaceFormat SourceRotatedBuffer::GetFormat() const { + return mSource->GetFormat(); +} + +already_AddRefed SourceRotatedBuffer::GetBufferSource() const { + RefPtr sourceDual = mSourceDual; + return sourceDual.forget(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/RotatedBuffer.h b/gfx/layers/RotatedBuffer.h new file mode 100644 index 0000000000..ceda001b03 --- /dev/null +++ b/gfx/layers/RotatedBuffer.h @@ -0,0 +1,418 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef ROTATEDBUFFER_H_ +#define ROTATEDBUFFER_H_ + +#include "gfxTypes.h" +#include // for uint32_t +#include "mozilla/AlreadyAddRefed.h" // for already_AddRefed +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/RefPtr.h" // for RefPtr, already_AddRefed +#include "mozilla/gfx/2D.h" // for DrawTarget, etc +#include "mozilla/gfx/MatrixFwd.h" // for Matrix +#include "mozilla/layers/TextureClient.h" // for TextureClient +#include "mozilla/mozalloc.h" // for operator delete +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "nsRegion.h" // for nsIntRegion + +namespace mozilla { +namespace layers { + +class PaintedLayer; +class ContentClient; + +// Mixin class for classes which need logic for loaning out a draw target. +// See comments on BorrowDrawTargetForQuadrantUpdate. +class BorrowDrawTarget { + public: + void ReturnDrawTarget(gfx::DrawTarget*& aReturned); + + protected: + // The draw target loaned by BorrowDrawTargetForQuadrantUpdate. It should not + // be used, we just keep a reference to ensure it is kept alive and so we can + // correctly restore state when it is returned. + RefPtr mLoanedDrawTarget; + gfx::Matrix mLoanedTransform; +}; + +/** + * This is a cairo/Thebes surface, but with a literal twist. Scrolling + * causes the layer's visible region to move. We want to keep + * reusing the same surface if the region size hasn't changed, but we don't + * want to keep moving the contents of the surface around in memory. So + * we use a trick. + * Consider just the vertical case, and suppose the buffer is H pixels + * high and we're scrolling down by N pixels. Instead of copying the + * buffer contents up by N pixels, we leave the buffer contents in place, + * and paint content rows H to H+N-1 into rows 0 to N-1 of the buffer. + * Then we can refresh the screen by painting rows N to H-1 of the buffer + * at row 0 on the screen, and then painting rows 0 to N-1 of the buffer + * at row H-N on the screen. + * mBufferRotation.y would be N in this example. + */ +class RotatedBuffer : public BorrowDrawTarget { + public: + typedef gfxContentType ContentType; + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RotatedBuffer) + + RotatedBuffer(const gfx::IntRect& aBufferRect, + const gfx::IntPoint& aBufferRotation) + : mCapture(nullptr), + mBufferRect(aBufferRect), + mBufferRotation(aBufferRotation), + mDidSelfCopy(false) {} + RotatedBuffer() : mCapture(nullptr), mDidSelfCopy(false) {} + + /** + * Initializes the rotated buffer to begin capturing all drawing performed + * on it, to be eventually replayed. Callers must call EndCapture, or + * FlushCapture before the rotated buffer is destroyed. + */ + void BeginCapture(); + + /** + * Finishes a capture and returns it. The capture must be replayed to the + * buffer before it is presented or it will contain invalid contents. + */ + RefPtr EndCapture(); + + /** + * Returns whether the RotatedBuffer is currently capturing all drawing + * performed on it, to be eventually replayed. + */ + bool IsCapturing() const { return !!mCapture; } + + /** + * Draws the contents of this rotated buffer into the specified draw target. + * It is the callers repsonsibility to ensure aTarget is flushed after calling + * this method. + */ + void DrawBufferWithRotation( + gfx::DrawTarget* aTarget, float aOpacity = 1.0, + gfx::CompositionOp aOperator = gfx::CompositionOp::OP_OVER, + gfx::SourceSurface* aMask = nullptr, + const gfx::Matrix* aMaskTransform = nullptr) const; + + /** + * Complete the drawing operation. The region to draw must have been + * drawn before this is called. The contents of the buffer are drawn + * to aTarget. + */ + void DrawTo(PaintedLayer* aLayer, gfx::DrawTarget* aTarget, float aOpacity, + gfx::CompositionOp aOp, gfx::SourceSurface* aMask, + const gfx::Matrix* aMaskTransform); + + /** + * Update the specified region of this rotated buffer with the contents + * of a source rotated buffer. + */ + void UpdateDestinationFrom(const RotatedBuffer& aSource, + const gfx::IntRect& aUpdateRect); + + /** + * A draw iterator is used to keep track of which quadrant of a rotated + * buffer and region of that quadrant is being updated. + */ + struct DrawIterator { + friend class RotatedBuffer; + DrawIterator() : mCount(0) {} + + nsIntRegion mDrawRegion; + + private: + uint32_t mCount; + }; + + /** + * Get a draw target at the specified resolution for updating |aBounds|, + * which must be contained within a single quadrant. + * + * The result should only be held temporarily by the caller (it will be kept + * alive by this). Once used it should be returned using ReturnDrawTarget. + * BorrowDrawTargetForQuadrantUpdate may not be called more than once without + * first calling ReturnDrawTarget. + * + * ReturnDrawTarget will by default restore the transform on the draw target. + * But it is the callers responsibility to restore the clip. + * The caller should flush the draw target, if necessary. + * If aSetTransform is false, the required transform will be set in + * aOutTransform. + */ + gfx::DrawTarget* BorrowDrawTargetForQuadrantUpdate( + const gfx::IntRect& aBounds, DrawIterator* aIter); + + struct Parameters { + Parameters(const gfx::IntRect& aBufferRect, + const gfx::IntPoint& aBufferRotation) + : mBufferRect(aBufferRect), + mBufferRotation(aBufferRotation), + mDidSelfCopy(false) {} + + bool IsRotated() const; + bool RectWrapsBuffer(const gfx::IntRect& aRect) const; + + void SetUnrotated(); + + gfx::IntRect mBufferRect; + gfx::IntPoint mBufferRotation; + bool mDidSelfCopy; + }; + + /** + * Returns the new buffer parameters for rotating to a + * destination buffer rect. + */ + Parameters AdjustedParameters(const gfx::IntRect& aDestBufferRect) const; + + /** + * Unrotates the pixels of the rotated buffer for the specified + * new buffer parameters. + */ + bool UnrotateBufferTo(const Parameters& aParameters); + + void SetParameters(const Parameters& aParameters); + + /** + * |BufferRect()| is the rect of device pixels that this + * RotatedBuffer covers. That is what DrawBufferWithRotation() + * will paint when it's called. + */ + const gfx::IntRect& BufferRect() const { return mBufferRect; } + const gfx::IntPoint& BufferRotation() const { return mBufferRotation; } + + /** + * Overrides the current buffer rect with the specified rect. + * Do not do this unless you know what you're doing. + */ + void SetBufferRect(const gfx::IntRect& aBufferRect) { + mBufferRect = aBufferRect; + } + + /** + * Overrides the current buffer rotation with the specified point. + * Do not do this unless you know what you're doing. + */ + void SetBufferRotation(const gfx::IntPoint& aBufferRotation) { + mBufferRotation = aBufferRotation; + } + + /** + * Returns whether this buffer did a self copy when adjusting to + * a new buffer rect. This is only used externally for syncing + * rotated buffers. + */ + bool DidSelfCopy() const { return mDidSelfCopy; } + + /** + * Clears the self copy flag. + */ + void ClearDidSelfCopy() { mDidSelfCopy = false; } + + /** + * Gets the content type for this buffer. + */ + ContentType GetContentType() const; + + virtual bool IsLocked() = 0; + virtual bool Lock(OpenMode aMode) = 0; + virtual void Unlock() = 0; + + virtual bool HaveBuffer() const = 0; + virtual bool HaveBufferOnWhite() const = 0; + + virtual gfx::SurfaceFormat GetFormat() const = 0; + + virtual already_AddRefed GetBufferSource() const { + return GetBufferTarget()->Snapshot(); + } + virtual gfx::DrawTarget* GetBufferTarget() const = 0; + + virtual TextureClient* GetClient() const { return nullptr; } + virtual TextureClient* GetClientOnWhite() const { return nullptr; } + + protected: + virtual ~RotatedBuffer() { MOZ_ASSERT(!mCapture); } + + enum XSide { LEFT, RIGHT }; + enum YSide { TOP, BOTTOM }; + gfx::IntRect GetQuadrantRectangle(XSide aXSide, YSide aYSide) const; + + gfx::Rect GetSourceRectangle(XSide aXSide, YSide aYSide) const; + + gfx::DrawTarget* GetDrawTarget() const { + if (mCapture) { + return mCapture; + } + return GetBufferTarget(); + } + + /* + * If aMask is non-null, then it is used as an alpha mask for rendering this + * buffer. aMaskTransform must be non-null if aMask is non-null, and is used + * to adjust the coordinate space of the mask. + */ + void DrawBufferQuadrant(gfx::DrawTarget* aTarget, XSide aXSide, YSide aYSide, + float aOpacity, gfx::CompositionOp aOperator, + gfx::SourceSurface* aMask, + const gfx::Matrix* aMaskTransform) const; + + RefPtr mCapture; + + /** The area of the PaintedLayer that is covered by the buffer as a whole */ + gfx::IntRect mBufferRect; + /** + * The x and y rotation of the buffer. Conceptually the buffer + * has its origin translated to mBufferRect.TopLeft() - mBufferRotation, + * is tiled to fill the plane, and the result is clipped to mBufferRect. + * So the pixel at mBufferRotation within the buffer is what gets painted at + * mBufferRect.TopLeft(). + * This is "rotation" in the sense of rotating items in a linear buffer, + * where items falling off the end of the buffer are returned to the + * buffer at the other end, not 2D rotation! + */ + gfx::IntPoint mBufferRotation; + /** + * When this is true it means that all pixels have moved inside the buffer. + * It's not possible to sync with another buffer without a full copy. + */ + bool mDidSelfCopy; +}; + +/** + * RemoteRotatedBuffer is a rotated buffer that is backed by texture + * clients. Before you use this class you must successfully lock it with + * an appropriate open mode, and then also unlock it when you're finished. + * RemoteRotatedBuffer is used by ContentClientSingleBuffered and + * ContentClientDoubleBuffered for the OMTC code path. + */ +class RemoteRotatedBuffer : public RotatedBuffer { + public: + RemoteRotatedBuffer(TextureClient* aClient, TextureClient* aClientOnWhite, + const gfx::IntRect& aBufferRect, + const gfx::IntPoint& aBufferRotation) + : RotatedBuffer(aBufferRect, aBufferRotation), + mClient(aClient), + mClientOnWhite(aClientOnWhite) {} + + virtual bool IsLocked() override; + virtual bool Lock(OpenMode aMode) override; + virtual void Unlock() override; + + virtual bool HaveBuffer() const override { return !!mClient; } + virtual bool HaveBufferOnWhite() const override { return !!mClientOnWhite; } + + virtual gfx::SurfaceFormat GetFormat() const override; + + virtual gfx::DrawTarget* GetBufferTarget() const override; + + virtual TextureClient* GetClient() const override { return mClient; } + virtual TextureClient* GetClientOnWhite() const override { + return mClientOnWhite; + } + + void SyncWithObject(RefPtr aSyncObject); + void Clear(); + + private: + RemoteRotatedBuffer(TextureClient* aClient, TextureClient* aClientOnWhite, + gfx::DrawTarget* aTarget, gfx::DrawTarget* aTargetOnWhite, + gfx::DrawTarget* aTargetDual, + const gfx::IntRect& aBufferRect, + const gfx::IntPoint& aBufferRotation) + : RotatedBuffer(aBufferRect, aBufferRotation), + mClient(aClient), + mClientOnWhite(aClientOnWhite), + mTarget(aTarget), + mTargetOnWhite(aTargetOnWhite), + mTargetDual(aTargetDual) {} + + RefPtr mClient; + RefPtr mClientOnWhite; + + RefPtr mTarget; + RefPtr mTargetOnWhite; + RefPtr mTargetDual; +}; + +/** + * DrawTargetRotatedBuffer is a rotated buffer that is backed by draw targets, + * and is used by ContentClientBasic for the on-mtc code path. + */ +class DrawTargetRotatedBuffer : public RotatedBuffer { + public: + DrawTargetRotatedBuffer(gfx::DrawTarget* aTarget, + gfx::DrawTarget* aTargetOnWhite, + const gfx::IntRect& aBufferRect, + const gfx::IntPoint& aBufferRotation) + : RotatedBuffer(aBufferRect, aBufferRotation), + mTarget(aTarget), + mTargetOnWhite(aTargetOnWhite) { + if (mTargetOnWhite) { + mTargetDual = gfx::Factory::CreateDualDrawTarget(mTarget, mTargetOnWhite); + } else { + mTargetDual = mTarget; + } + } + + virtual bool IsLocked() override { return false; } + virtual bool Lock(OpenMode aMode) override { return true; } + virtual void Unlock() override {} + + virtual bool HaveBuffer() const override { return !!mTargetDual; } + virtual bool HaveBufferOnWhite() const override { return !!mTargetOnWhite; } + + virtual gfx::SurfaceFormat GetFormat() const override; + + virtual gfx::DrawTarget* GetBufferTarget() const override; + + private: + RefPtr mTarget; + RefPtr mTargetOnWhite; + RefPtr mTargetDual; +}; + +/** + * SourceRotatedBuffer is a rotated buffer that is backed by source surfaces, + * and may only be used to draw into other buffers or be read directly. + */ +class SourceRotatedBuffer : public RotatedBuffer { + public: + SourceRotatedBuffer(gfx::SourceSurface* aSource, + gfx::SourceSurface* aSourceOnWhite, + const gfx::IntRect& aBufferRect, + const gfx::IntPoint& aBufferRotation) + : RotatedBuffer(aBufferRect, aBufferRotation), + mSource(aSource), + mSourceOnWhite(aSourceOnWhite) { + mSourceDual = + gfx::Factory::CreateDualSourceSurface(mSource, mSourceOnWhite); + } + + virtual bool IsLocked() override { return false; } + virtual bool Lock(OpenMode aMode) override { return false; } + virtual void Unlock() override {} + + virtual already_AddRefed GetBufferSource() const override; + + virtual gfx::SurfaceFormat GetFormat() const override; + + virtual bool HaveBuffer() const override { return !!mSourceDual; } + virtual bool HaveBufferOnWhite() const override { return !!mSourceOnWhite; } + + virtual gfx::DrawTarget* GetBufferTarget() const override { return nullptr; } + + private: + RefPtr mSource; + RefPtr mSourceOnWhite; + RefPtr mSourceDual; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* ROTATEDBUFFER_H_ */ diff --git a/gfx/layers/SampleTime.cpp b/gfx/layers/SampleTime.cpp new file mode 100644 index 0000000000..fd87b669f0 --- /dev/null +++ b/gfx/layers/SampleTime.cpp @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SampleTime.h" + +namespace mozilla { +namespace layers { + +SampleTime::SampleTime() : mType(eNull) {} + +/*static*/ +SampleTime SampleTime::FromVsync(const TimeStamp& aTime) { + MOZ_ASSERT(!aTime.IsNull()); + return SampleTime(eVsync, aTime); +} + +/*static*/ +SampleTime SampleTime::FromNow() { return SampleTime(eNow, TimeStamp::Now()); } + +/*static*/ +SampleTime SampleTime::FromTest(const TimeStamp& aTime) { + MOZ_ASSERT(!aTime.IsNull()); + return SampleTime(eTest, aTime); +} + +bool SampleTime::IsNull() const { return mType == eNull; } + +SampleTime::TimeType SampleTime::Type() const { return mType; } + +const TimeStamp& SampleTime::Time() const { return mTime; } + +bool SampleTime::operator==(const SampleTime& aOther) const { + return mTime == aOther.mTime; +} + +bool SampleTime::operator!=(const SampleTime& aOther) const { + return !(*this == aOther); +} + +bool SampleTime::operator<(const SampleTime& aOther) const { + return mTime < aOther.mTime; +} + +bool SampleTime::operator<=(const SampleTime& aOther) const { + return mTime <= aOther.mTime; +} + +bool SampleTime::operator>(const SampleTime& aOther) const { + return mTime > aOther.mTime; +} + +bool SampleTime::operator>=(const SampleTime& aOther) const { + return mTime >= aOther.mTime; +} + +SampleTime SampleTime::operator+(const TimeDuration& aDuration) const { + return SampleTime(mType, mTime + aDuration); +} + +SampleTime SampleTime::operator-(const TimeDuration& aDuration) const { + return SampleTime(mType, mTime - aDuration); +} + +TimeDuration SampleTime::operator-(const SampleTime& aOther) const { + return mTime - aOther.mTime; +} + +SampleTime::SampleTime(TimeType aType, const TimeStamp& aTime) + : mType(aType), mTime(aTime) {} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/SampleTime.h b/gfx/layers/SampleTime.h new file mode 100644 index 0000000000..a32f758eb7 --- /dev/null +++ b/gfx/layers/SampleTime.h @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_SampleTime_h +#define mozilla_layers_SampleTime_h + +#include "mozilla/TimeStamp.h" + +namespace mozilla { +namespace layers { + +/** + * When compositing frames, there is usually a "sample time" associated + * with the frame, which may be derived from vsync or controlled by a test. + * This class encapsulates that and tracks where the sample time comes from, + * as the desired behaviour may vary based on the time source. + */ +class SampleTime { + public: + enum TimeType { + // Uninitialized sample time. + eNull, + // Time comes from a vsync tick, possibly adjusted by a vsync interval. + eVsync, + // Time comes from a "now" timestamp + eNow, + // Time is set by a test + eTest, + }; + + SampleTime(); + static SampleTime FromVsync(const TimeStamp& aTime); + static SampleTime FromNow(); + static SampleTime FromTest(const TimeStamp& aTime); + + bool IsNull() const; + TimeType Type() const; + const TimeStamp& Time() const; + + // These operators just compare the timestamps + bool operator==(const SampleTime& aOther) const; + bool operator!=(const SampleTime& aOther) const; + bool operator<(const SampleTime& aOther) const; + bool operator<=(const SampleTime& aOther) const; + bool operator>(const SampleTime& aOther) const; + bool operator>=(const SampleTime& aOther) const; + // These return a new SampleTime with the same type as this one, but the + // time adjusted by the provided TimeDuration + SampleTime operator+(const TimeDuration& aDuration) const; + SampleTime operator-(const TimeDuration& aDuration) const; + // This just produces the time difference between the two SampleTimes, + // ignoring the type. + TimeDuration operator-(const SampleTime& aOther) const; + + private: + // Private constructor; use one of the static FromXXX methods instead. + SampleTime(TimeType aType, const TimeStamp& aTime); + + TimeType mType; + TimeStamp mTime; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_SampleTime_h diff --git a/gfx/layers/ScreenshotGrabber.cpp b/gfx/layers/ScreenshotGrabber.cpp new file mode 100644 index 0000000000..64d845dec3 --- /dev/null +++ b/gfx/layers/ScreenshotGrabber.cpp @@ -0,0 +1,233 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ScreenshotGrabber.h" + +#include "GeckoProfiler.h" +#include "mozilla/RefPtr.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/UniquePtr.h" + +#include "mozilla/layers/Compositor.h" +#include "mozilla/layers/ProfilerScreenshots.h" +#include "mozilla/layers/TextureHost.h" +#include "mozilla/gfx/Point.h" +#include "nsTArray.h" + +namespace mozilla { + +using namespace gfx; + +namespace layers { +namespace profiler_screenshots { + +/** + * The actual implementation of screenshot grabbing. + * The ScreenshotGrabberImpl object is destroyed if the profiler is + * disabled and MaybeGrabScreenshot notices it. + */ +class ScreenshotGrabberImpl final { + public: + explicit ScreenshotGrabberImpl(const IntSize& aBufferSize); + ~ScreenshotGrabberImpl(); + + void GrabScreenshot(Window& aWindow, const IntSize& aWindowSize); + void ProcessQueue(); + + private: + struct QueueItem final { + mozilla::TimeStamp mTimeStamp; + RefPtr mScreenshotBuffer; + IntSize mScreenshotSize; + IntSize mWindowSize; + uintptr_t mWindowIdentifier; + }; + + RefPtr ScaleDownWindowRenderSourceToSize( + Window& aWindow, const IntSize& aDestSize, + RenderSource* aWindowRenderSource, size_t aLevel); + + already_AddRefed TakeNextBuffer(Window& aWindow); + void ReturnBuffer(AsyncReadbackBuffer* aBuffer); + + nsTArray> mCachedLevels; + nsTArray> mAvailableBuffers; + Maybe mCurrentFrameQueueItem; + nsTArray mQueue; + RefPtr mProfilerScreenshots; + const IntSize mBufferSize; +}; + +} // namespace profiler_screenshots + +ScreenshotGrabber::ScreenshotGrabber() = default; + +ScreenshotGrabber::~ScreenshotGrabber() = default; + +void ScreenshotGrabber::MaybeGrabScreenshot( + profiler_screenshots::Window& aWindow, const IntSize& aWindowSize) { + if (ProfilerScreenshots::IsEnabled()) { + if (!mImpl) { + mImpl = MakeUnique( + ProfilerScreenshots::ScreenshotSize()); + } + mImpl->GrabScreenshot(aWindow, aWindowSize); + } else if (mImpl) { + Destroy(); + } +} + +void ScreenshotGrabber::MaybeProcessQueue() { + if (ProfilerScreenshots::IsEnabled()) { + if (!mImpl) { + mImpl = MakeUnique( + ProfilerScreenshots::ScreenshotSize()); + } + mImpl->ProcessQueue(); + } else if (mImpl) { + Destroy(); + } +} + +void ScreenshotGrabber::NotifyEmptyFrame() { +#ifdef MOZ_GECKO_PROFILER + PROFILER_MARKER_UNTYPED("NoCompositorScreenshot because nothing changed", + GRAPHICS); +#endif +} + +void ScreenshotGrabber::Destroy() { mImpl = nullptr; } + +namespace profiler_screenshots { + +ScreenshotGrabberImpl::ScreenshotGrabberImpl(const IntSize& aBufferSize) + : mBufferSize(aBufferSize) {} + +ScreenshotGrabberImpl::~ScreenshotGrabberImpl() { + // Any queue items in mQueue or mCurrentFrameQueueItem will be lost. + // That's ok: Either the profiler has stopped and we don't care about these + // screenshots, or the window is closing and we don't really need the last + // few frames from the window. +} + +// Scale down aWindowRenderSource into a RenderSource of size +// mBufferSize * (1 << aLevel) and return that RenderSource. +// Don't scale down by more than a factor of 2 with a single scaling operation, +// because it'll look bad. If higher scales are needed, use another +// intermediate target by calling this function recursively with aLevel + 1. +RefPtr ScreenshotGrabberImpl::ScaleDownWindowRenderSourceToSize( + Window& aWindow, const IntSize& aDestSize, + RenderSource* aWindowRenderSource, size_t aLevel) { + if (aLevel == mCachedLevels.Length()) { + mCachedLevels.AppendElement( + aWindow.CreateDownscaleTarget(mBufferSize * (1 << aLevel))); + } + MOZ_RELEASE_ASSERT(aLevel < mCachedLevels.Length()); + + RefPtr renderSource = aWindowRenderSource; + IntSize sourceSize = aWindowRenderSource->Size(); + if (sourceSize.width > aDestSize.width * 2) { + sourceSize = aDestSize * 2; + renderSource = ScaleDownWindowRenderSourceToSize( + aWindow, sourceSize, aWindowRenderSource, aLevel + 1); + } + + if (renderSource) { + if (mCachedLevels[aLevel]->DownscaleFrom( + renderSource, IntRect({}, sourceSize), IntRect({}, aDestSize))) { + return mCachedLevels[aLevel]->AsRenderSource(); + } + } + return nullptr; +} + +void ScreenshotGrabberImpl::GrabScreenshot(Window& aWindow, + const IntSize& aWindowSize) { + RefPtr windowRenderSource = + aWindow.GetWindowContents(aWindowSize); + + if (!windowRenderSource) { + PROFILER_MARKER_UNTYPED( + "NoCompositorScreenshot because of unsupported compositor " + "configuration", + GRAPHICS); + return; + } + + Size windowSize(aWindowSize); + float scale = std::min(mBufferSize.width / windowSize.width, + mBufferSize.height / windowSize.height); + IntSize scaledSize = IntSize::Round(windowSize * scale); + RefPtr scaledTarget = ScaleDownWindowRenderSourceToSize( + aWindow, scaledSize, windowRenderSource, 0); + + if (!scaledTarget) { + PROFILER_MARKER_UNTYPED( + "NoCompositorScreenshot because ScaleDownWindowRenderSourceToSize " + "failed", + GRAPHICS); + return; + } + + RefPtr buffer = TakeNextBuffer(aWindow); + if (!buffer) { + PROFILER_MARKER_UNTYPED( + "NoCompositorScreenshot because AsyncReadbackBuffer creation failed", + GRAPHICS); + return; + } + + buffer->CopyFrom(scaledTarget); + + // This QueueItem will be added to the queue at the end of the next call to + // ProcessQueue(). This ensures that the buffer isn't mapped into main memory + // until the next frame. If we did it in this frame, we'd block on the GPU. + mCurrentFrameQueueItem = + Some(QueueItem{TimeStamp::Now(), std::move(buffer), scaledSize, + windowRenderSource->Size(), + reinterpret_cast(static_cast(this))}); +} + +already_AddRefed ScreenshotGrabberImpl::TakeNextBuffer( + Window& aWindow) { + if (!mAvailableBuffers.IsEmpty()) { + RefPtr buffer = mAvailableBuffers[0]; + mAvailableBuffers.RemoveElementAt(0); + return buffer.forget(); + } + return aWindow.CreateAsyncReadbackBuffer(mBufferSize); +} + +void ScreenshotGrabberImpl::ReturnBuffer(AsyncReadbackBuffer* aBuffer) { + mAvailableBuffers.AppendElement(aBuffer); +} + +void ScreenshotGrabberImpl::ProcessQueue() { + if (!mQueue.IsEmpty()) { + if (!mProfilerScreenshots) { + mProfilerScreenshots = new ProfilerScreenshots(); + } + for (const auto& item : mQueue) { + mProfilerScreenshots->SubmitScreenshot( + item.mWindowIdentifier, item.mWindowSize, item.mScreenshotSize, + item.mTimeStamp, [&item](DataSourceSurface* aTargetSurface) { + return item.mScreenshotBuffer->MapAndCopyInto(aTargetSurface, + item.mScreenshotSize); + }); + ReturnBuffer(item.mScreenshotBuffer); + } + } + mQueue.Clear(); + + if (mCurrentFrameQueueItem) { + mQueue.AppendElement(std::move(*mCurrentFrameQueueItem)); + mCurrentFrameQueueItem = Nothing(); + } +} + +} // namespace profiler_screenshots +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ScreenshotGrabber.h b/gfx/layers/ScreenshotGrabber.h new file mode 100644 index 0000000000..a2dfc2a085 --- /dev/null +++ b/gfx/layers/ScreenshotGrabber.h @@ -0,0 +1,139 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_ScreenshotGrabber_h +#define mozilla_layers_ScreenshotGrabber_h + +#include "nsISupportsImpl.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Point.h" +#include "mozilla/gfx/Rect.h" + +namespace mozilla { +namespace layers { + +namespace profiler_screenshots { +class Window; +class RenderSource; +class DownscaleTarget; +class AsyncReadbackBuffer; + +class ScreenshotGrabberImpl; +} // namespace profiler_screenshots + +/** + * Used by various renderers / layer managers to grab snapshots from the window + * and submit them to the Gecko profiler. + * Doesn't do any work if the profiler is not running or the "screenshots" + * feature is not enabled. + * Screenshots are scaled down to fit within a fixed size, and read back to + * main memory using async readback. Scaling is done in multiple scale-by-0.5x + * steps using DownscaleTarget::CopyFrom, and readback is done using + * AsyncReadbackBuffers. + */ +class ScreenshotGrabber final { + public: + ScreenshotGrabber(); + ~ScreenshotGrabber(); + + // Scale the contents of aWindow's current render target into an + // appropriately sized DownscaleTarget and read its contents into an + // AsyncReadbackBuffer. The AsyncReadbackBuffer is not mapped into main + // memory until the second call to MaybeProcessQueue() after this call to + // MaybeGrabScreenshot(). + void MaybeGrabScreenshot(profiler_screenshots::Window& aWindow, + const gfx::IntSize& aWindowSize); + + // Map the contents of any outstanding AsyncReadbackBuffers from previous + // composites into main memory and submit each screenshot to the profiler. + void MaybeProcessQueue(); + + // Insert a special profiler marker for a composite that didn't do any actual + // compositing, so that the profiler knows why no screenshot was taken for + // this frame. + void NotifyEmptyFrame(); + + // Destroy all Window-related resources that this class is holding on to. + void Destroy(); + + private: + // non-null while ProfilerScreenshots::IsEnabled() returns true + UniquePtr mImpl; +}; + +// Interface definitions. + +namespace profiler_screenshots { + +class Window { + public: + virtual already_AddRefed GetWindowContents( + const gfx::IntSize& aWindowSize) = 0; + virtual already_AddRefed CreateDownscaleTarget( + const gfx::IntSize& aSize) = 0; + virtual already_AddRefed CreateAsyncReadbackBuffer( + const gfx::IntSize& aSize) = 0; + + protected: + virtual ~Window() {} +}; + +class RenderSource { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RenderSource) + + const auto& Size() const { return mSize; } + + protected: + explicit RenderSource(const gfx::IntSize& aSize) : mSize(aSize) {} + virtual ~RenderSource() {} + + const gfx::IntSize mSize; +}; + +class DownscaleTarget { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DownscaleTarget) + + virtual already_AddRefed AsRenderSource() = 0; + + const auto& Size() const { return mSize; } + virtual bool DownscaleFrom(RenderSource* aSource, + const gfx::IntRect& aSourceRect, + const gfx::IntRect& aDestRect) = 0; + + protected: + explicit DownscaleTarget(const gfx::IntSize& aSize) : mSize(aSize) {} + virtual ~DownscaleTarget() {} + + const gfx::IntSize mSize; +}; + +class AsyncReadbackBuffer { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING( + mozilla::layers::profiler_screenshots::AsyncReadbackBuffer) + + const auto& Size() const { return mSize; } + virtual void CopyFrom(RenderSource* aSource) = 0; + virtual bool MapAndCopyInto(gfx::DataSourceSurface* aSurface, + const gfx::IntSize& aReadSize) = 0; + + protected: + explicit AsyncReadbackBuffer(const gfx::IntSize& aSize) : mSize(aSize) {} + virtual ~AsyncReadbackBuffer() {} + + const gfx::IntSize mSize; +}; + +} // namespace profiler_screenshots + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_ScreenshotGrabber_h diff --git a/gfx/layers/ScrollableLayerGuid.cpp b/gfx/layers/ScrollableLayerGuid.cpp new file mode 100644 index 0000000000..158d91e611 --- /dev/null +++ b/gfx/layers/ScrollableLayerGuid.cpp @@ -0,0 +1,72 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ScrollableLayerGuid.h" + +#include +#include "mozilla/HashFunctions.h" // for HashGeneric +#include "nsPrintfCString.h" // for nsPrintfCString + +namespace mozilla { +namespace layers { + +ScrollableLayerGuid::ScrollableLayerGuid() + : mLayersId{0}, mPresShellId(0), mScrollId(0) {} + +ScrollableLayerGuid::ScrollableLayerGuid(LayersId aLayersId, + uint32_t aPresShellId, + ViewID aScrollId) + : mLayersId(aLayersId), mPresShellId(aPresShellId), mScrollId(aScrollId) {} + +bool ScrollableLayerGuid::operator==(const ScrollableLayerGuid& other) const { + return mLayersId == other.mLayersId && mPresShellId == other.mPresShellId && + mScrollId == other.mScrollId; +} + +bool ScrollableLayerGuid::operator!=(const ScrollableLayerGuid& other) const { + return !(*this == other); +} + +bool ScrollableLayerGuid::operator<(const ScrollableLayerGuid& other) const { + if (mLayersId < other.mLayersId) { + return true; + } + if (mLayersId == other.mLayersId) { + if (mPresShellId < other.mPresShellId) { + return true; + } + if (mPresShellId == other.mPresShellId) { + return mScrollId < other.mScrollId; + } + } + return false; +} + +std::ostream& operator<<(std::ostream& aOut, const ScrollableLayerGuid& aGuid) { + return aOut << nsPrintfCString("{ l=0x%" PRIx64 ", p=%u, v=%" PRIu64 " }", + uint64_t(aGuid.mLayersId), aGuid.mPresShellId, + aGuid.mScrollId) + .get(); +} + +std::size_t ScrollableLayerGuid::HashFn::operator()( + const ScrollableLayerGuid& aGuid) const { + return HashGeneric(uint64_t(aGuid.mLayersId), aGuid.mPresShellId, + aGuid.mScrollId); +} + +std::size_t ScrollableLayerGuid::HashIgnoringPresShellFn::operator()( + const ScrollableLayerGuid& aGuid) const { + return HashGeneric(uint64_t(aGuid.mLayersId), aGuid.mScrollId); +} + +bool ScrollableLayerGuid::EqualIgnoringPresShellFn::operator()( + const ScrollableLayerGuid& lhs, const ScrollableLayerGuid& rhs) const { + return lhs.mLayersId == rhs.mLayersId && lhs.mScrollId == rhs.mScrollId; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ScrollableLayerGuid.h b/gfx/layers/ScrollableLayerGuid.h new file mode 100644 index 0000000000..63e51c3f6f --- /dev/null +++ b/gfx/layers/ScrollableLayerGuid.h @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_SCROLLABLELAYERGUID_H +#define GFX_SCROLLABLELAYERGUID_H + +#include // for ostream +#include // for uint8_t, uint32_t, uint64_t +#include "mozilla/layers/LayersTypes.h" // for LayersId +#include "nsHashKeys.h" // for nsUint64HashKey + +namespace mozilla { +namespace layers { + +/** + * This class allows us to uniquely identify a scrollable layer. The + * mLayersId identifies the layer tree (corresponding to a child process + * and/or tab) that the scrollable layer belongs to. The mPresShellId + * is a temporal identifier (corresponding to the document loaded that + * contains the scrollable layer, which may change over time). The + * mScrollId corresponds to the actual frame that is scrollable. + */ +struct ScrollableLayerGuid { + // We use IDs to identify frames across processes. + typedef uint64_t ViewID; + typedef nsUint64HashKey ViewIDHashKey; + static const ViewID NULL_SCROLL_ID; // This container layer does not scroll. + static const ViewID START_SCROLL_ID = 2; // This is the ID that scrolling + // subframes will begin at. + + LayersId mLayersId; + uint32_t mPresShellId; + ViewID mScrollId; + + ScrollableLayerGuid(); + + ScrollableLayerGuid(LayersId aLayersId, uint32_t aPresShellId, + ViewID aScrollId); + + ScrollableLayerGuid(const ScrollableLayerGuid& other) = default; + + ~ScrollableLayerGuid() = default; + + bool operator==(const ScrollableLayerGuid& other) const; + bool operator!=(const ScrollableLayerGuid& other) const; + bool operator<(const ScrollableLayerGuid& other) const; + + friend std::ostream& operator<<(std::ostream& aOut, + const ScrollableLayerGuid& aGuid); + + // Helper structs to use as hash/equality functions in std::unordered_map. + // e.g. std::unordered_map myMap; + // std::unordered_map myMap; + + struct HashFn { + std::size_t operator()(const ScrollableLayerGuid& aGuid) const; + }; + + struct HashIgnoringPresShellFn { + std::size_t operator()(const ScrollableLayerGuid& aGuid) const; + }; + + struct EqualIgnoringPresShellFn { + bool operator()(const ScrollableLayerGuid& lhs, + const ScrollableLayerGuid& rhs) const; + }; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_SCROLLABLELAYERGUID_H */ diff --git a/gfx/layers/ShareableCanvasRenderer.cpp b/gfx/layers/ShareableCanvasRenderer.cpp new file mode 100644 index 0000000000..a5aadba9fd --- /dev/null +++ b/gfx/layers/ShareableCanvasRenderer.cpp @@ -0,0 +1,219 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ShareableCanvasRenderer.h" + +#include "mozilla/dom/WebGLTypes.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/layers/TextureClientSharedSurface.h" +#include "mozilla/layers/CompositableForwarder.h" +#include "mozilla/layers/TextureForwarder.h" + +#include "ClientWebGLContext.h" +#include "gfxUtils.h" +#include "GLScreenBuffer.h" +#include "nsICanvasRenderingContextInternal.h" +#include "SharedSurfaceGL.h" + +#ifdef MOZ_WIDGET_ANDROID +# include "mozilla/layers/AndroidHardwareBuffer.h" +#endif + +using namespace mozilla::gfx; + +namespace mozilla { +namespace layers { + +ShareableCanvasRenderer::ShareableCanvasRenderer() { + MOZ_COUNT_CTOR(ShareableCanvasRenderer); +} + +ShareableCanvasRenderer::~ShareableCanvasRenderer() { + MOZ_COUNT_DTOR(ShareableCanvasRenderer); + + mFrontBufferFromDesc = nullptr; + DisconnectClient(); +} + +void ShareableCanvasRenderer::Initialize(const CanvasRendererData& aData) { + CanvasRenderer::Initialize(aData); + mCanvasClient = nullptr; +} + +void ShareableCanvasRenderer::ClearCachedResources() { + CanvasRenderer::ClearCachedResources(); + + if (mCanvasClient) { + mCanvasClient->Clear(); + } +} + +void ShareableCanvasRenderer::DisconnectClient() { + if (mCanvasClient) { + mCanvasClient->OnDetach(); + mCanvasClient = nullptr; + } +} + +RefPtr ShareableCanvasRenderer::GetFrontBufferFromDesc( + const layers::SurfaceDescriptor& desc, TextureFlags flags) { + if (mFrontBufferFromDesc && mFrontBufferDesc == desc) + return mFrontBufferFromDesc; + mFrontBufferFromDesc = nullptr; + + // Test the validity of aAllocator + const auto& compositableForwarder = GetForwarder(); + if (!compositableForwarder) { + return nullptr; + } + const auto& textureForwarder = compositableForwarder->GetTextureForwarder(); + + auto format = gfx::SurfaceFormat::R8G8B8X8; + if (!mData.mIsOpaque) { + format = gfx::SurfaceFormat::R8G8B8A8; + + if (!mData.mIsAlphaPremult) { + flags |= TextureFlags::NON_PREMULTIPLIED; + } + } + + if (desc.type() != + SurfaceDescriptor::TSurfaceDescriptorAndroidHardwareBuffer) { + mFrontBufferFromDesc = SharedSurfaceTextureData::CreateTextureClient( + desc, format, mData.mSize, flags, textureForwarder); + } else { +#ifdef MOZ_WIDGET_ANDROID + const SurfaceDescriptorAndroidHardwareBuffer& bufferDesc = + desc.get_SurfaceDescriptorAndroidHardwareBuffer(); + RefPtr buffer = + AndroidHardwareBufferManager::Get()->GetBuffer(bufferDesc.bufferId()); + if (!buffer) { + return nullptr; + } + // TextureClient is created only when AndroidHardwareBuffer does not own it. + mFrontBufferFromDesc = buffer->GetTextureClientOfSharedSurfaceTextureData( + desc, format, mData.mSize, flags, textureForwarder); +#else + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); +#endif + } + mFrontBufferDesc = desc; + return mFrontBufferFromDesc; +} + +void ShareableCanvasRenderer::UpdateCompositableClient() { + if (!CreateCompositable()) { + return; + } + + if (!IsDirty()) { + return; + } + ResetDirty(); + + const auto context = mData.GetContext(); + if (!context) return; + const auto& provider = context->GetBufferProvider(); + const auto webgl = context->AsWebgl(); + + const auto& forwarder = GetForwarder(); + + // - + + auto flags = TextureFlags::IMMUTABLE; + if (!YIsDown()) { + flags |= TextureFlags::ORIGIN_BOTTOM_LEFT; + } + if (IsOpaque()) { + flags |= TextureFlags::IS_OPAQUE; + } + + // - + + const auto fnGetExistingTc = [&]() -> RefPtr { + if (provider) { + auto tc = provider->GetTextureClient(); + if (!tc) return nullptr; + + if (!provider->SetKnowsCompositor(forwarder)) { + gfxCriticalNote << "BufferProvider::SetForwarder failed"; + return nullptr; + } + tc = provider->GetTextureClient(); // Ask again after SetKnowsCompositor + return tc; + } + + if (!webgl) return nullptr; + + const auto desc = webgl->GetFrontBuffer(nullptr); + if (!desc) return nullptr; + return GetFrontBufferFromDesc(*desc, flags); + }; + + // - + + const auto fnMakeTcFromSnapshot = [&]() -> RefPtr { + const auto& size = mData.mSize; + + auto contentType = gfxContentType::COLOR; + if (!mData.mIsOpaque) { + contentType = gfxContentType::COLOR_ALPHA; + } + const auto surfaceFormat = + gfxPlatform::GetPlatform()->Optimal2DFormatForContent(contentType); + + const auto tc = + mCanvasClient->CreateTextureClientForCanvas(surfaceFormat, size, flags); + if (!tc) { + return nullptr; + } + + { + TextureClientAutoLock tcLock(tc, OpenMode::OPEN_WRITE_ONLY); + if (!tcLock.Succeeded()) { + return nullptr; + } + + const RefPtr dt = tc->BorrowDrawTarget(); + + const bool requireAlphaPremult = false; + const auto borrowed = BorrowSnapshot(requireAlphaPremult); + if (!borrowed) return nullptr; + + dt->CopySurface(borrowed->mSurf, {{0, 0}, size}, {0, 0}); + } + + return tc; + }; + + // - + + { + FirePreTransactionCallback(); + + // First, let's see if we can get a no-copy TextureClient from the canvas. + auto tc = fnGetExistingTc(); + if (!tc) { + // Otherwise, snapshot the surface and copy into a TexClient. + tc = fnMakeTcFromSnapshot(); + } + if (tc != mFrontBufferFromDesc) { + mFrontBufferFromDesc = nullptr; + } + + if (!tc) { + NS_WARNING("Couldn't make TextureClient for CanvasRenderer."); + return; + } + + mCanvasClient->UseTexture(tc); + + FireDidTransactionCallback(); + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ShareableCanvasRenderer.h b/gfx/layers/ShareableCanvasRenderer.h new file mode 100644 index 0000000000..455d2ccb20 --- /dev/null +++ b/gfx/layers/ShareableCanvasRenderer.h @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_SHAREABLECANVASRENDERER_H +#define GFX_SHAREABLECANVASRENDERER_H + +#include "CompositorTypes.h" +#include "CanvasRenderer.h" +#include "mozilla/layers/CanvasClient.h" + +namespace mozilla { +namespace gl { +class SurfaceFactory; +} // namespace gl + +namespace gfx { +class DrawTarget; +} // namespace gfx + +namespace layers { + +class ShareableCanvasRenderer : public CanvasRenderer { + friend class CanvasClient2D; + friend class CanvasClientSharedSurface; + + protected: + RefPtr mCanvasClient; + + private: + layers::SurfaceDescriptor mFrontBufferDesc; + RefPtr mFrontBufferFromDesc; + + public: + ShareableCanvasRenderer(); + virtual ~ShareableCanvasRenderer(); + + public: + void Initialize(const CanvasRendererData&) override; + + virtual CompositableForwarder* GetForwarder() = 0; + + virtual bool CreateCompositable() = 0; + + void ClearCachedResources() override; + void DisconnectClient() override; + + void UpdateCompositableClient(); + + CanvasClient* GetCanvasClient() { return mCanvasClient; } + + private: + RefPtr GetFrontBufferFromDesc(const layers::SurfaceDescriptor&, + TextureFlags); +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/SourceSurfaceSharedData.cpp b/gfx/layers/SourceSurfaceSharedData.cpp new file mode 100644 index 0000000000..28f120a7b9 --- /dev/null +++ b/gfx/layers/SourceSurfaceSharedData.cpp @@ -0,0 +1,195 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SourceSurfaceSharedData.h" + +#include "mozilla/Likely.h" +#include "mozilla/Types.h" // for decltype +#include "mozilla/layers/SharedSurfacesChild.h" + +#include "base/process_util.h" + +#ifdef DEBUG +/** + * If defined, this makes SourceSurfaceSharedData::Finalize memory protect the + * underlying shared buffer in the producing process (the content or UI + * process). Given flushing the page table is expensive, and its utility is + * predominantly diagnostic (in case of overrun), turn it off by default. + */ +# define SHARED_SURFACE_PROTECT_FINALIZED +#endif + +using namespace mozilla::layers; + +namespace mozilla { +namespace gfx { + +bool SourceSurfaceSharedDataWrapper::Init( + const IntSize& aSize, int32_t aStride, SurfaceFormat aFormat, + const SharedMemoryBasic::Handle& aHandle, base::ProcessId aCreatorPid) { + MOZ_ASSERT(!mBuf); + mSize = aSize; + mStride = aStride; + mFormat = aFormat; + mCreatorPid = aCreatorPid; + + size_t len = GetAlignedDataLength(); + mBuf = MakeAndAddRef(); + if (NS_WARN_IF( + !mBuf->SetHandle(aHandle, ipc::SharedMemory::RightsReadOnly)) || + NS_WARN_IF(!mBuf->Map(len))) { + mBuf = nullptr; + return false; + } + + mBuf->CloseHandle(); + return true; +} + +void SourceSurfaceSharedDataWrapper::Init(SourceSurfaceSharedData* aSurface) { + MOZ_ASSERT(!mBuf); + MOZ_ASSERT(aSurface); + mSize = aSurface->mSize; + mStride = aSurface->mStride; + mFormat = aSurface->mFormat; + mCreatorPid = base::GetCurrentProcId(); + mBuf = aSurface->mBuf; +} + +bool SourceSurfaceSharedData::Init(const IntSize& aSize, int32_t aStride, + SurfaceFormat aFormat, + bool aShare /* = true */) { + mSize = aSize; + mStride = aStride; + mFormat = aFormat; + + size_t len = GetAlignedDataLength(); + mBuf = new SharedMemoryBasic(); + if (NS_WARN_IF(!mBuf->Create(len)) || NS_WARN_IF(!mBuf->Map(len))) { + mBuf = nullptr; + return false; + } + + if (aShare) { + layers::SharedSurfacesChild::Share(this); + } + + return true; +} + +void SourceSurfaceSharedData::GuaranteePersistance() { + // Shared memory is not unmapped until we release SourceSurfaceSharedData. +} + +void SourceSurfaceSharedData::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + SizeOfInfo& aInfo) const { + MutexAutoLock lock(mMutex); + aInfo.AddType(SurfaceType::DATA_SHARED); + if (mBuf) { + aInfo.mNonHeapBytes = GetAlignedDataLength(); + } + if (!mClosed) { + aInfo.mExternalHandles = 1; + } + Maybe extId = SharedSurfacesChild::GetExternalId(this); + if (extId) { + aInfo.mExternalId = wr::AsUint64(extId.ref()); + } +} + +uint8_t* SourceSurfaceSharedData::GetDataInternal() const { + mMutex.AssertCurrentThreadOwns(); + + // If we have an old buffer lingering, it is because we get reallocated to + // get a new handle to share, but there were still active mappings. + if (MOZ_UNLIKELY(mOldBuf)) { + MOZ_ASSERT(mMapCount > 0); + MOZ_ASSERT(mFinalized); + return static_cast(mOldBuf->memory()); + } + return static_cast(mBuf->memory()); +} + +nsresult SourceSurfaceSharedData::ShareToProcess( + base::ProcessId aPid, SharedMemoryBasic::Handle& aHandle) { + MutexAutoLock lock(mMutex); + MOZ_ASSERT(mHandleCount > 0); + + if (mClosed) { + return NS_ERROR_NOT_AVAILABLE; + } + + bool shared = mBuf->ShareToProcess(aPid, &aHandle); + if (MOZ_UNLIKELY(!shared)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +void SourceSurfaceSharedData::CloseHandleInternal() { + mMutex.AssertCurrentThreadOwns(); + + if (mClosed) { + MOZ_ASSERT(mHandleCount == 0); + MOZ_ASSERT(mShared); + return; + } + + if (mShared) { + mBuf->CloseHandle(); + mClosed = true; + } +} + +bool SourceSurfaceSharedData::ReallocHandle() { + MutexAutoLock lock(mMutex); + MOZ_ASSERT(mHandleCount > 0); + MOZ_ASSERT(mClosed); + + if (NS_WARN_IF(!mFinalized)) { + // We haven't finished populating the surface data yet, which means we are + // out of luck, as we have no means of synchronizing with the producer to + // write new data to a new buffer. This should be fairly rare, caused by a + // crash in the GPU process, while we were decoding an image. + return false; + } + + size_t len = GetAlignedDataLength(); + RefPtr buf = new SharedMemoryBasic(); + if (NS_WARN_IF(!buf->Create(len)) || NS_WARN_IF(!buf->Map(len))) { + return false; + } + + size_t copyLen = GetDataLength(); + memcpy(buf->memory(), mBuf->memory(), copyLen); +#ifdef SHARED_SURFACE_PROTECT_FINALIZED + buf->Protect(static_cast(buf->memory()), len, RightsRead); +#endif + + if (mMapCount > 0 && !mOldBuf) { + mOldBuf = std::move(mBuf); + } + mBuf = std::move(buf); + mClosed = false; + mShared = false; + return true; +} + +void SourceSurfaceSharedData::Finalize() { + MutexAutoLock lock(mMutex); + MOZ_ASSERT(!mFinalized); + +#ifdef SHARED_SURFACE_PROTECT_FINALIZED + size_t len = GetAlignedDataLength(); + mBuf->Protect(static_cast(mBuf->memory()), len, RightsRead); +#endif + + mFinalized = true; +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/layers/SourceSurfaceSharedData.h b/gfx/layers/SourceSurfaceSharedData.h new file mode 100644 index 0000000000..53e910eb5d --- /dev/null +++ b/gfx/layers/SourceSurfaceSharedData.h @@ -0,0 +1,329 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_SOURCESURFACESHAREDDATA_H_ +#define MOZILLA_GFX_SOURCESURFACESHAREDDATA_H_ + +#include "mozilla/gfx/2D.h" +#include "mozilla/Mutex.h" +#include "mozilla/ipc/SharedMemoryBasic.h" + +namespace mozilla { +namespace gfx { + +class SourceSurfaceSharedData; + +/** + * This class is used to wrap shared (as in process) data buffers allocated by + * a SourceSurfaceSharedData object. It may live in the same process or a + * different process from the actual SourceSurfaceSharedData object. + * + * If it is in the same process, mBuf is the same object as that in the surface. + * It is a useful abstraction over just using the surface directly, because it + * can have a different lifetime from the surface; if the surface gets freed, + * consumers may continue accessing the data in the buffer. Releasing the + * original surface is a signal which feeds into SharedSurfacesParent to decide + * to release the SourceSurfaceSharedDataWrapper. + * + * If it is in a different process, mBuf is a new SharedMemoryBasic object which + * mapped in the given shared memory handle as read only memory. + */ +class SourceSurfaceSharedDataWrapper final : public DataSourceSurface { + typedef mozilla::ipc::SharedMemoryBasic SharedMemoryBasic; + + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SourceSurfaceSharedDataWrapper, + override) + + SourceSurfaceSharedDataWrapper() + : mStride(0), + mConsumers(0), + mFormat(SurfaceFormat::UNKNOWN), + mCreatorPid(0), + mCreatorRef(true) {} + + bool Init(const IntSize& aSize, int32_t aStride, SurfaceFormat aFormat, + const SharedMemoryBasic::Handle& aHandle, + base::ProcessId aCreatorPid); + + void Init(SourceSurfaceSharedData* aSurface); + + base::ProcessId GetCreatorPid() const { return mCreatorPid; } + + int32_t Stride() override { return mStride; } + + SurfaceType GetType() const override { return SurfaceType::DATA; } + IntSize GetSize() const override { return mSize; } + SurfaceFormat GetFormat() const override { return mFormat; } + + uint8_t* GetData() override { return static_cast(mBuf->memory()); } + + bool OnHeap() const override { return false; } + + bool AddConsumer() { return ++mConsumers == 1; } + + bool RemoveConsumer(bool aForCreator) { + MOZ_ASSERT(mConsumers > 0); + if (aForCreator) { + if (!mCreatorRef) { + MOZ_ASSERT_UNREACHABLE("Already released creator reference!"); + return false; + } + mCreatorRef = false; + } + return --mConsumers == 0; + } + + uint32_t GetConsumers() const { + MOZ_ASSERT(mConsumers > 0); + return mConsumers; + } + + bool HasCreatorRef() const { return mCreatorRef; } + + private: + size_t GetDataLength() const { + return static_cast(mStride) * mSize.height; + } + + size_t GetAlignedDataLength() const { + return mozilla::ipc::SharedMemory::PageAlignedSize(GetDataLength()); + } + + int32_t mStride; + uint32_t mConsumers; + IntSize mSize; + RefPtr mBuf; + SurfaceFormat mFormat; + base::ProcessId mCreatorPid; + bool mCreatorRef; +}; + +/** + * This class is used to wrap shared (as in process) data buffers used by a + * source surface. + */ +class SourceSurfaceSharedData : public DataSourceSurface { + typedef mozilla::ipc::SharedMemoryBasic SharedMemoryBasic; + + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SourceSurfaceSharedData, override) + + SourceSurfaceSharedData() + : mMutex("SourceSurfaceSharedData"), + mStride(0), + mHandleCount(0), + mFormat(SurfaceFormat::UNKNOWN), + mClosed(false), + mFinalized(false), + mShared(false) {} + + /** + * Initialize the surface by creating a shared memory buffer with a size + * determined by aSize, aStride and aFormat. If aShare is true, it will also + * immediately attempt to share the surface with the GPU process via + * SharedSurfacesChild. + */ + bool Init(const IntSize& aSize, int32_t aStride, SurfaceFormat aFormat, + bool aShare = true); + + uint8_t* GetData() final { + MutexAutoLock lock(mMutex); + return GetDataInternal(); + } + + int32_t Stride() final { return mStride; } + + SurfaceType GetType() const override { return SurfaceType::DATA_SHARED; } + IntSize GetSize() const final { return mSize; } + SurfaceFormat GetFormat() const final { return mFormat; } + + void GuaranteePersistance() final; + + void SizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + SizeOfInfo& aInfo) const final; + + bool OnHeap() const final { return false; } + + /** + * Although Map (and Moz2D in general) isn't normally threadsafe, + * we want to allow it for SourceSurfaceSharedData since it should + * always be fine (for reading at least). + * + * This is the same as the base class implementation except using + * mMapCount instead of mIsMapped since that breaks for multithread. + * + * Additionally if a reallocation happened while there were active + * mappings, then we guarantee that GetData will continue to return + * the same data pointer by retaining the old shared buffer until + * the last mapping is freed via Unmap. + */ + bool Map(MapType, MappedSurface* aMappedSurface) final { + MutexAutoLock lock(mMutex); + ++mMapCount; + aMappedSurface->mData = GetDataInternal(); + aMappedSurface->mStride = mStride; + return true; + } + + void Unmap() final { + MutexAutoLock lock(mMutex); + MOZ_ASSERT(mMapCount > 0); + if (--mMapCount == 0) { + mOldBuf = nullptr; + } + } + + /** + * Get a handle to share to another process for this buffer. Returns: + * NS_OK -- success, aHandle is valid. + * NS_ERROR_NOT_AVAILABLE -- handle was closed, need to reallocate. + * NS_ERROR_FAILURE -- failed to create a handle to share. + */ + nsresult ShareToProcess(base::ProcessId aPid, + SharedMemoryBasic::Handle& aHandle); + + /** + * Indicates the buffer is not expected to be shared with any more processes. + * May release the handle if possible (see CloseHandleInternal). + */ + void FinishedSharing() { + MutexAutoLock lock(mMutex); + mShared = true; + CloseHandleInternal(); + } + + /** + * Indicates whether or not the buffer can be shared with another process + * without reallocating. Note that this is racy and should only be used for + * informational/reporting purposes. + */ + bool CanShare() const { + MutexAutoLock lock(mMutex); + return !mClosed; + } + + /** + * Allocate a new shared memory buffer so that we can get a new handle for + * sharing to new processes. ShareToProcess must have failed with + * NS_ERROR_NOT_AVAILABLE in order for this to be safe to call. Returns true + * if the operation succeeds. If it fails, there is no state change. + */ + bool ReallocHandle(); + + /** + * Signals we have finished writing to the buffer and it may be marked as + * read only. + */ + void Finalize(); + + /** + * Indicates whether or not the buffer can change. If this returns true, it is + * guaranteed to continue to do so for the remainder of the surface's life. + */ + bool IsFinalized() const { + MutexAutoLock lock(mMutex); + return mFinalized; + } + + /** + * Yields a dirty rect of what has changed since it was last called. + */ + Maybe TakeDirtyRect() final { + MutexAutoLock lock(mMutex); + if (mDirtyRect) { + Maybe ret = std::move(mDirtyRect); + return ret; + } + return Nothing(); + } + + /** + * Increment the invalidation counter. + */ + void Invalidate(const IntRect& aDirtyRect) final { + MutexAutoLock lock(mMutex); + if (!aDirtyRect.IsEmpty()) { + if (mDirtyRect) { + mDirtyRect->UnionRect(mDirtyRect.ref(), aDirtyRect); + } else { + mDirtyRect = Some(aDirtyRect); + } + } else { + mDirtyRect = Some(IntRect(IntPoint(0, 0), mSize)); + } + MOZ_ASSERT_IF(mDirtyRect, !mDirtyRect->IsEmpty()); + } + + /** + * While a HandleLock exists for the given surface, the shared memory handle + * cannot be released. + */ + class MOZ_STACK_CLASS HandleLock final { + public: + explicit HandleLock(SourceSurfaceSharedData* aSurface) + : mSurface(aSurface) { + mSurface->LockHandle(); + } + + ~HandleLock() { mSurface->UnlockHandle(); } + + private: + RefPtr mSurface; + }; + + protected: + virtual ~SourceSurfaceSharedData() = default; + + private: + friend class SourceSurfaceSharedDataWrapper; + + void LockHandle() { + MutexAutoLock lock(mMutex); + ++mHandleCount; + } + + void UnlockHandle() { + MutexAutoLock lock(mMutex); + MOZ_ASSERT(mHandleCount > 0); + --mHandleCount; + mShared = true; + CloseHandleInternal(); + } + + uint8_t* GetDataInternal() const; + + size_t GetDataLength() const { + return static_cast(mStride) * mSize.height; + } + + size_t GetAlignedDataLength() const { + return mozilla::ipc::SharedMemory::PageAlignedSize(GetDataLength()); + } + + /** + * Attempt to close the handle. Only if the buffer has been both finalized + * and we have completed sharing will it be released. + */ + void CloseHandleInternal(); + + mutable Mutex mMutex; + int32_t mStride; + int32_t mHandleCount; + Maybe mDirtyRect; + IntSize mSize; + RefPtr mBuf; + RefPtr mOldBuf; + SurfaceFormat mFormat; + bool mClosed : 1; + bool mFinalized : 1; + bool mShared : 1; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_SOURCESURFACESHAREDDATA_H_ */ diff --git a/gfx/layers/SourceSurfaceVolatileData.cpp b/gfx/layers/SourceSurfaceVolatileData.cpp new file mode 100644 index 0000000000..e555b53deb --- /dev/null +++ b/gfx/layers/SourceSurfaceVolatileData.cpp @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SourceSurfaceVolatileData.h" + +#include "gfxAlphaRecovery.h" +#include "mozilla/Likely.h" +#include "mozilla/Types.h" // for decltype + +namespace mozilla { +namespace gfx { + +bool SourceSurfaceVolatileData::Init(const IntSize& aSize, int32_t aStride, + SurfaceFormat aFormat) { + mSize = aSize; + mStride = aStride; + mFormat = aFormat; + + size_t alignment = size_t(1) << gfxAlphaRecovery::GoodAlignmentLog2(); + mVBuf = new VolatileBuffer(); + if (MOZ_UNLIKELY(!mVBuf->Init(aStride * aSize.height, alignment))) { + mVBuf = nullptr; + return false; + } + + return true; +} + +void SourceSurfaceVolatileData::GuaranteePersistance() { + MOZ_ASSERT_UNREACHABLE("Should use SourceSurfaceRawData wrapper!"); +} + +void SourceSurfaceVolatileData::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + SizeOfInfo& aInfo) const { + aInfo.AddType(SurfaceType::DATA); + if (mVBuf) { + aInfo.mHeapBytes = mVBuf->HeapSizeOfExcludingThis(aMallocSizeOf); + aInfo.mNonHeapBytes = mVBuf->NonHeapSizeOfExcludingThis(); +#ifdef ANDROID + if (!mVBuf->OnHeap()) { + // Volatile buffers keep a file handle open on Android. + aInfo.mExternalHandles = 1; + } +#endif + } +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/layers/SourceSurfaceVolatileData.h b/gfx/layers/SourceSurfaceVolatileData.h new file mode 100644 index 0000000000..cba3579322 --- /dev/null +++ b/gfx/layers/SourceSurfaceVolatileData.h @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_SOURCESURFACEVOLATILEDATA_H_ +#define MOZILLA_GFX_SOURCESURFACEVOLATILEDATA_H_ + +#include "mozilla/gfx/2D.h" +#include "mozilla/Mutex.h" +#include "mozilla/VolatileBuffer.h" + +namespace mozilla { +namespace gfx { + +/** + * This class is used to wrap volatile data buffers used for source surfaces. + * The Map and Unmap semantics are used to guarantee that the volatile data + * buffer is not freed by the operating system while the surface is in active + * use. If GetData is expected to return a non-null value without a + * corresponding Map call (and verification of the result), the surface data + * should be wrapped in a temporary SourceSurfaceRawData with a ScopedMap + * closure. + */ +class SourceSurfaceVolatileData : public DataSourceSurface { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SourceSurfaceVolatileData, override) + + SourceSurfaceVolatileData() + : mMutex("SourceSurfaceVolatileData"), + mStride(0), + mFormat(SurfaceFormat::UNKNOWN), + mWasPurged(false) {} + + bool Init(const IntSize& aSize, int32_t aStride, SurfaceFormat aFormat); + + uint8_t* GetData() override { return mVBufPtr; } + int32_t Stride() override { return mStride; } + + SurfaceType GetType() const override { return SurfaceType::DATA; } + IntSize GetSize() const override { return mSize; } + SurfaceFormat GetFormat() const override { return mFormat; } + + void GuaranteePersistance() override; + + void SizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + SizeOfInfo& aInfo) const override; + + bool OnHeap() const override { return mVBuf->OnHeap(); } + + // Althought Map (and Moz2D in general) isn't normally threadsafe, + // we want to allow it for SourceSurfaceVolatileData since it should + // always be fine (for reading at least). + // + // This is the same as the base class implementation except using + // mMapCount instead of mIsMapped since that breaks for multithread. + bool Map(MapType, MappedSurface* aMappedSurface) override { + MutexAutoLock lock(mMutex); + if (mWasPurged) { + return false; + } + if (mMapCount == 0) { + mVBufPtr = mVBuf; + } + if (mVBufPtr.WasBufferPurged()) { + mWasPurged = true; + return false; + } + aMappedSurface->mData = mVBufPtr; + aMappedSurface->mStride = mStride; + ++mMapCount; + return true; + } + + void Unmap() override { + MutexAutoLock lock(mMutex); + MOZ_ASSERT(mMapCount > 0); + MOZ_ASSERT(!mWasPurged); + if (--mMapCount == 0) { + mVBufPtr = nullptr; + } + } + + private: + virtual ~SourceSurfaceVolatileData() = default; + + Mutex mMutex; + int32_t mStride; + IntSize mSize; + RefPtr mVBuf; + VolatileBufferPtr mVBufPtr; + SurfaceFormat mFormat; + bool mWasPurged; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_SOURCESURFACEVOLATILEDATA_H_ */ diff --git a/gfx/layers/SurfacePool.h b/gfx/layers/SurfacePool.h new file mode 100644 index 0000000000..393042addd --- /dev/null +++ b/gfx/layers/SurfacePool.h @@ -0,0 +1,72 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_SurfacePool_h +#define mozilla_layers_SurfacePool_h + +#include "mozilla/Maybe.h" +#include "mozilla/ThreadSafeWeakPtr.h" + +#include "GLTypes.h" +#include "nsISupportsImpl.h" +#include "nsRegion.h" + +namespace mozilla { + +namespace gl { +class GLContext; +} // namespace gl + +namespace layers { + +class SurfacePoolHandle; + +// A pool of surfaces for NativeLayers. Manages GL resources. Since GLContexts +// are bound to their creator thread, a pool should not be shared across +// threads. Call Create() to create an instance. Call GetHandleForGL() to obtain +// a handle that can be passed to NativeLayerRoot::CreateLayer. +class SurfacePool { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SurfacePool); + +#ifdef XP_MACOSX + static RefPtr Create(size_t aPoolSizeLimit); +#endif + + // aGL can be nullptr. + virtual RefPtr GetHandleForGL(gl::GLContext* aGL) = 0; + virtual void DestroyGLResourcesForContext(gl::GLContext* aGL) = 0; + + protected: + virtual ~SurfacePool() = default; +}; + +class SurfacePoolHandleCA; + +// A handle to the process-wide surface pool. Users should create one handle per +// OS window, and call OnBeginFrame() and OnEndFrame() on the handle at +// appropriate times. OnBeginFrame() and OnEndFrame() should be called on the +// thread that the surface pool was created on. +// These handles are stored on NativeLayers that are created with them and keep +// the SurfacePool alive. +class SurfacePoolHandle { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SurfacePoolHandle); + virtual SurfacePoolHandleCA* AsSurfacePoolHandleCA() { return nullptr; } + + virtual RefPtr Pool() = 0; + + // Should be called every frame, in order to do rate-limited cleanup tasks. + virtual void OnBeginFrame() = 0; + virtual void OnEndFrame() = 0; + + protected: + virtual ~SurfacePoolHandle() = default; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_SurfacePool_h diff --git a/gfx/layers/SurfacePoolCA.h b/gfx/layers/SurfacePoolCA.h new file mode 100644 index 0000000000..b97e7b45ed --- /dev/null +++ b/gfx/layers/SurfacePoolCA.h @@ -0,0 +1,287 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_SurfacePoolCA_h +#define mozilla_layers_SurfacePoolCA_h + +#include + +#include +#include + +#include "mozilla/Atomics.h" +#include "mozilla/DataMutex.h" + +#include "mozilla/layers/SurfacePool.h" +#include "CFTypeRefPtr.h" +#include "nsISupportsImpl.h" + +namespace mozilla { + +namespace gl { +class MozFramebuffer; +} // namespace gl + +namespace layers { + +class SurfacePoolHandleCA; +struct SurfacePoolCAWrapperForGL; + +// An implementation of SurfacePool for IOSurfaces and GL framebuffers. +// The goal of having this pool is to avoid creating and destroying IOSurfaces +// and framebuffers frequently, because doing so is expensive. +// SurfacePoolCA is threadsafe. All its data is wrapped inside LockedPool, and +// each access to LockedPool is guarded with a lock through DataMutex. +// +// The pool satisfies the following requirements: +// - It can be shared across windows, even across windows with different +// GLContexts. +// - The number of unused surfaces that are available for recycling is capped +// to a fixed value per pool, regardless of how many windows use that pool. +// - When all windows are closed (all handles are gone), no surfaces are kept +// alive (the pool is destroyed). +// - There is an explicit way of deleting GL resources for a GLContext so that +// it can happen at a deterministic time on the right thread. +// - Additionally, once a GLContext is no longer being used in any window +// (really: any pool handle), all surface-associated GL resources of that +// context are destroyed. +// - For every IOSurface, only one set of GL resources is in existence at any +// given time. We don't want there to be framebuffers in two different +// GLContexts for one surface. +// - We do not want to recycle an IOSurface that currently has GL resources of +// context A for a pool handle that uses context B. +// - We need to delay IOSurface recycling until the window server is done with +// the surface (`!IOSurfaceIsInUse(surf)`) +class SurfacePoolCA final : public SurfacePool { + public: + // Get a handle for a new window. aGL can be nullptr. + RefPtr GetHandleForGL(gl::GLContext* aGL) override; + + // Destroy all GL resources associated with aGL managed by this pool. + void DestroyGLResourcesForContext(gl::GLContext* aGL) override; + + private: + friend struct SurfacePoolCAWrapperForGL; + friend class SurfacePoolHandleCA; + friend RefPtr SurfacePool::Create(size_t aPoolSizeLimit); + + explicit SurfacePoolCA(size_t aPoolSizeLimit); + ~SurfacePoolCA() override; + + // Get an existing surface of aSize from the pool or create a new surface. + // The returned surface is guaranteed not to be in use by the window server. + CFTypeRefPtr ObtainSurfaceFromPool(const gfx::IntSize& aSize, + gl::GLContext* aGL); + + // Place a surface that was previously obtained from this pool back into the + // pool. aSurface may or may not be in use by the window server. + void ReturnSurfaceToPool(CFTypeRefPtr aSurface); + + // Re-run checks whether the window server still uses IOSurfaces which are + // eligible for recycling. The purpose of the "generation" counter is to + // reduce the number of calls to IOSurfaceIsInUse in a scenario where many + // windows / handles are calling CollectPendingSurfaces in the same frame + // (i.e. multiple simultaneously-animating windows). + uint64_t CollectPendingSurfaces(uint64_t aCheckGenerationsUpTo); + + // Enforce the pool size limit by evicting surfaces as necessary. This should + // happen at the end of the frame so that we can temporarily exceed the limit + // within a frame. + void EnforcePoolSizeLimit(); + + // Get or create the framebuffer for the given surface and GL context. + // The returned framebuffer handle will become invalid once + // DestroyGLResourcesForContext or DecrementGLContextHandleCount are called. + // The framebuffer's depth buffer (if present) may be shared between multiple + // framebuffers! Do not assume anything about the depth buffer's existing + // contents (i.e. clear it at the beginning of the draw), and do not + // interleave drawing commands to different framebuffers in such a way that + // the shared depth buffer could cause trouble. + Maybe GetFramebufferForSurface(CFTypeRefPtr aSurface, + gl::GLContext* aGL, + bool aNeedsDepthBuffer); + + // Called by the destructor of SurfacePoolCAWrapperForGL so that we can clear + // our weak reference to it and delete GL resources. + void OnWrapperDestroyed(gl::GLContext* aGL, + SurfacePoolCAWrapperForGL* aWrapper); + + // The actual pool implementation lives in LockedPool, which is accessed in + // a thread-safe manner. + struct LockedPool { + explicit LockedPool(size_t aPoolSizeLimit); + LockedPool(LockedPool&&) = default; + ~LockedPool(); + + RefPtr GetWrapperForGL(SurfacePoolCA* aPool, + gl::GLContext* aGL); + void DestroyGLResourcesForContext(gl::GLContext* aGL); + + CFTypeRefPtr ObtainSurfaceFromPool(const gfx::IntSize& aSize, + gl::GLContext* aGL); + void ReturnSurfaceToPool(CFTypeRefPtr aSurface); + uint64_t CollectPendingSurfaces(uint64_t aCheckGenerationsUpTo); + void EnforcePoolSizeLimit(); + Maybe GetFramebufferForSurface(CFTypeRefPtr aSurface, + gl::GLContext* aGL, + bool aNeedsDepthBuffer); + void OnWrapperDestroyed(gl::GLContext* aGL, + SurfacePoolCAWrapperForGL* aWrapper); + uint64_t EstimateTotalMemory(); + + uint64_t mCollectionGeneration = 0; + + protected: + struct GLResourcesForSurface { + RefPtr mGLContext; // non-null + UniquePtr mFramebuffer; // non-null + }; + + struct SurfacePoolEntry { + gfx::IntSize mSize; + CFTypeRefPtr mIOSurface; // non-null + Maybe mGLResources; + }; + + struct PendingSurfaceEntry { + SurfacePoolEntry mEntry; + // The value of LockedPool::mCollectionGeneration at the time + // IOSurfaceIsInUse was last called for mEntry.mIOSurface. + uint64_t mPreviousCheckGeneration; + // The number of times an IOSurfaceIsInUse check has been performed. + uint64_t mCheckCount; + }; + + template + void MutateEntryStorage(const char* aMutationType, + const gfx::IntSize& aSize, F aFn); + + template + void ForEachEntry(F aFn); + + bool CanRecycleSurfaceForRequest(const SurfacePoolEntry& aEntry, + const gfx::IntSize& aSize, + gl::GLContext* aGL); + + RefPtr GetDepthBufferForSharing( + gl::GLContext* aGL, const gfx::IntSize& aSize); + UniquePtr CreateFramebufferForTexture( + gl::GLContext* aGL, const gfx::IntSize& aSize, GLuint aTexture, + bool aNeedsDepthBuffer); + + // Every IOSurface that is managed by the pool is wrapped in a + // SurfacePoolEntry object. Every entry is stored in one of three buckets at + // any given time: mInUseEntries, mPendingEntries, or mAvailableEntries. All + // mutations to these buckets are performed via calls to + // MutateEntryStorage(). Entries can move between the buckets in the + // following ways: + // + // [new] + // | Create + // v + // +----------------------------------------------------------------+ + // | mInUseEntries | + // +------+------------------------------+--------------------------+ + // | ^ | Start waiting for + // | | Recycle v + // | | +-----------------------------+ + // | | | mPendingEntries | + // | | +--+--------------------+-----+ + // | Retain | | Stop waiting for | + // v | v | + // +-------------------+-------------------------+ | + // | mAvailableEntries | | + // +-----------------------------+---------------+ | + // | Evict | Eject + // v v + // [destroyed] [destroyed] + // + // Each arrow corresponds to one invocation of MutateEntryStorage() with the + // arrow's label passed as the aMutationType string parameter. + + // Stores the entries for surfaces that are in use by NativeLayerCA, i.e. an + // entry is inside mInUseEntries between calls to ObtainSurfaceFromPool() + // and ReturnSurfaceToPool(). + std::unordered_map, SurfacePoolEntry> + mInUseEntries; + + // Stores entries which are no longer in use by NativeLayerCA but are still + // in use by the window server, i.e. for which + // IOSurfaceIsInUse(pendingSurfaceEntry.mEntry.mIOSurface.get()) still + // returns true. These entries are checked once per frame inside + // CollectPendingSurfaces(), and returned to mAvailableEntries once the + // window server is done. + nsTArray mPendingEntries; + + // Stores entries which are available for recycling. These entries are not + // in use by a NativeLayerCA or by the window server. + nsTArray mAvailableEntries; + + // Keeps weak references to SurfacePoolCAWrapperForGL instances. + // For each GLContext* value (including nullptr), only one wrapper can + // exist at any time. The wrapper keeps a strong reference to us and + // notifies us when it gets destroyed. At that point we can call + // DestroyGLResourcesForContext because we know no other SurfaceHandles for + // that context exist. + std::unordered_map mWrappers; + size_t mPoolSizeLimit = 0; + + struct DepthBufferEntry { + RefPtr mGLContext; + gfx::IntSize mSize; + WeakPtr mBuffer; + }; + + nsTArray mDepthBuffers; + }; + + DataMutex mPool; +}; + +// One process-wide instance per (SurfacePoolCA*, GLContext*) pair. +// Keeps the SurfacePool alive, and the SurfacePool has a weak reference to the +// wrapper so that it can ensure that there's only one wrapper for it per +// GLContext* at any time. +struct SurfacePoolCAWrapperForGL { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SurfacePoolCAWrapperForGL); + + const RefPtr mPool; // non-null + const RefPtr mGL; // can be null + + SurfacePoolCAWrapperForGL(SurfacePoolCA* aPool, gl::GLContext* aGL) + : mPool(aPool), mGL(aGL) {} + + protected: + ~SurfacePoolCAWrapperForGL() { mPool->OnWrapperDestroyed(mGL, this); } +}; + +// A surface pool handle that is stored on NativeLayerCA and keeps the +// SurfacePool alive. +class SurfacePoolHandleCA final : public SurfacePoolHandle { + public: + SurfacePoolHandleCA* AsSurfacePoolHandleCA() override { return this; } + const auto& gl() { return mPoolWrapper->mGL; } + CFTypeRefPtr ObtainSurfaceFromPool(const gfx::IntSize& aSize); + void ReturnSurfaceToPool(CFTypeRefPtr aSurface); + Maybe GetFramebufferForSurface(CFTypeRefPtr aSurface, + bool aNeedsDepthBuffer); + RefPtr Pool() override { return mPoolWrapper->mPool; } + void OnBeginFrame() override; + void OnEndFrame() override; + + private: + friend class SurfacePoolCA; + SurfacePoolHandleCA(RefPtr&& aPoolWrapper, + uint64_t aCurrentCollectionGeneration); + ~SurfacePoolHandleCA() override; + + const RefPtr mPoolWrapper; + DataMutex mPreviousFrameCollectionGeneration; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_SurfacePoolCA_h diff --git a/gfx/layers/SurfacePoolCA.mm b/gfx/layers/SurfacePoolCA.mm new file mode 100644 index 0000000000..abce77eb1f --- /dev/null +++ b/gfx/layers/SurfacePoolCA.mm @@ -0,0 +1,436 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nullptr; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/SurfacePoolCA.h" + +#import + +#include +#include +#include + +#include "mozilla/StaticMutex.h" +#include "mozilla/StaticPrefs_gfx.h" + +#include "GeckoProfiler.h" +#include "GLContextCGL.h" +#include "MozFramebuffer.h" + +namespace mozilla { +namespace layers { + +using gfx::IntPoint; +using gfx::IntSize; +using gfx::IntRect; +using gfx::IntRegion; +using gl::GLContext; +using gl::GLContextCGL; + +/* static */ RefPtr SurfacePool::Create(size_t aPoolSizeLimit) { + return new SurfacePoolCA(aPoolSizeLimit); +} + +// SurfacePoolCA::LockedPool + +SurfacePoolCA::LockedPool::LockedPool(size_t aPoolSizeLimit) : mPoolSizeLimit(aPoolSizeLimit) {} + +SurfacePoolCA::LockedPool::~LockedPool() { + MOZ_RELEASE_ASSERT(mWrappers.empty(), + "Any outstanding wrappers should have kept the surface pool alive"); + MOZ_RELEASE_ASSERT(mInUseEntries.empty(), + "Leak! No more surfaces should be in use at this point."); + // Remove all entries in mPendingEntries and mAvailableEntries. + MutateEntryStorage("Clear", {}, [&]() { + mPendingEntries.Clear(); + mAvailableEntries.Clear(); + }); +} + +RefPtr SurfacePoolCA::LockedPool::GetWrapperForGL(SurfacePoolCA* aPool, + GLContext* aGL) { + auto& wrapper = mWrappers[aGL]; + if (!wrapper) { + wrapper = new SurfacePoolCAWrapperForGL(aPool, aGL); + } + return wrapper; +} + +void SurfacePoolCA::LockedPool::DestroyGLResourcesForContext(GLContext* aGL) { + ForEachEntry([&](SurfacePoolEntry& entry) { + if (entry.mGLResources && entry.mGLResources->mGLContext == aGL) { + entry.mGLResources = Nothing(); + } + }); + mDepthBuffers.RemoveElementsBy( + [&](const DepthBufferEntry& entry) { return entry.mGLContext == aGL; }); +} + +template +void SurfacePoolCA::LockedPool::MutateEntryStorage(const char* aMutationType, + const gfx::IntSize& aSize, F aFn) { +#ifdef MOZ_GECKO_PROFILER + size_t inUseCountBefore = mInUseEntries.size(); + size_t pendingCountBefore = mPendingEntries.Length(); + size_t availableCountBefore = mAvailableEntries.Length(); + TimeStamp before = TimeStamp::NowUnfuzzed(); +#endif + + aFn(); + +#ifdef MOZ_GECKO_PROFILER + if (profiler_thread_is_being_profiled()) { + PROFILER_MARKER_TEXT( + "SurfacePool", GRAPHICS, MarkerTiming::IntervalUntilNowFrom(before), + nsPrintfCString("%d -> %d in use | %d -> %d waiting for | %d -> %d " + "available | %s %dx%d | %dMB total memory", + int(inUseCountBefore), int(mInUseEntries.size()), int(pendingCountBefore), + int(mPendingEntries.Length()), int(availableCountBefore), + int(mAvailableEntries.Length()), aMutationType, aSize.width, aSize.height, + int(EstimateTotalMemory() / 1000 / 1000))); + } +#endif +} + +template +void SurfacePoolCA::LockedPool::ForEachEntry(F aFn) { + for (auto& iter : mInUseEntries) { + aFn(iter.second); + } + for (auto& entry : mPendingEntries) { + aFn(entry.mEntry); + } + for (auto& entry : mAvailableEntries) { + aFn(entry); + } +} + +uint64_t SurfacePoolCA::LockedPool::EstimateTotalMemory() { + std::unordered_set depthAndStencilBuffers; + uint64_t memBytes = 0; + + ForEachEntry([&](const SurfacePoolEntry& entry) { + auto size = entry.mSize; + memBytes += size.width * 4 * size.height; + if (entry.mGLResources) { + const auto& fb = *entry.mGLResources->mFramebuffer; + if (const auto& buffer = fb.GetDepthAndStencilBuffer()) { + depthAndStencilBuffers.insert(buffer.get()); + } + } + }); + + for (const auto& buffer : depthAndStencilBuffers) { + memBytes += buffer->EstimateMemory(); + } + + return memBytes; +} + +bool SurfacePoolCA::LockedPool::CanRecycleSurfaceForRequest(const SurfacePoolEntry& aEntry, + const IntSize& aSize, GLContext* aGL) { + if (aEntry.mSize != aSize) { + return false; + } + if (aEntry.mGLResources) { + return aEntry.mGLResources->mGLContext == aGL; + } + return true; +} + +CFTypeRefPtr SurfacePoolCA::LockedPool::ObtainSurfaceFromPool(const IntSize& aSize, + GLContext* aGL) { + // Do a linear scan through mAvailableEntries to find an eligible surface, going from oldest to + // newest. The size of this array is limited, so the linear scan is fast. + auto iterToRecycle = std::find_if(mAvailableEntries.begin(), mAvailableEntries.end(), + [&](const SurfacePoolEntry& aEntry) { + return CanRecycleSurfaceForRequest(aEntry, aSize, aGL); + }); + if (iterToRecycle != mAvailableEntries.end()) { + CFTypeRefPtr surface = iterToRecycle->mIOSurface; + // Move the entry from mAvailableEntries to mInUseEntries. + MutateEntryStorage("Recycle", aSize, [&]() { + mInUseEntries.insert({surface, std::move(*iterToRecycle)}); + mAvailableEntries.RemoveElementAt(iterToRecycle); + }); + return surface; + } + + AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("IOSurface creation", GRAPHICS_TileAllocation, + nsPrintfCString("%dx%d", aSize.width, aSize.height)); + CFTypeRefPtr surface = + CFTypeRefPtr::WrapUnderCreateRule(IOSurfaceCreate((__bridge CFDictionaryRef) @{ + (__bridge NSString*)kIOSurfaceWidth : @(aSize.width), + (__bridge NSString*)kIOSurfaceHeight : @(aSize.height), + (__bridge NSString*)kIOSurfacePixelFormat : @(kCVPixelFormatType_32BGRA), + (__bridge NSString*)kIOSurfaceBytesPerElement : @(4), + })); + if (surface) { + // Create a new entry in mInUseEntries. + MutateEntryStorage("Create", aSize, [&]() { + mInUseEntries.insert({surface, SurfacePoolEntry{aSize, surface, {}}}); + }); + } + return surface; +} + +void SurfacePoolCA::LockedPool::ReturnSurfaceToPool(CFTypeRefPtr aSurface) { + auto inUseEntryIter = mInUseEntries.find(aSurface); + MOZ_RELEASE_ASSERT(inUseEntryIter != mInUseEntries.end()); + if (IOSurfaceIsInUse(aSurface.get())) { + // Move the entry from mInUseEntries to mPendingEntries. + MutateEntryStorage("Start waiting for", IntSize(inUseEntryIter->second.mSize), [&]() { + mPendingEntries.AppendElement( + PendingSurfaceEntry{std::move(inUseEntryIter->second), mCollectionGeneration, 0}); + mInUseEntries.erase(inUseEntryIter); + }); + } else { + // Move the entry from mInUseEntries to mAvailableEntries. + MutateEntryStorage("Retain", IntSize(inUseEntryIter->second.mSize), [&]() { + mAvailableEntries.AppendElement(std::move(inUseEntryIter->second)); + mInUseEntries.erase(inUseEntryIter); + }); + } +} + +void SurfacePoolCA::LockedPool::EnforcePoolSizeLimit() { + // Enforce the pool size limit, removing least-recently-used entries as necessary. + while (mAvailableEntries.Length() > mPoolSizeLimit) { + MutateEntryStorage("Evict", IntSize(mAvailableEntries[0].mSize), + [&]() { mAvailableEntries.RemoveElementAt(0); }); + } +} + +uint64_t SurfacePoolCA::LockedPool::CollectPendingSurfaces(uint64_t aCheckGenerationsUpTo) { + mCollectionGeneration++; + + // Loop from back to front, potentially deleting items as we iterate. + // mPendingEntries is used as a set; the order of its items is not meaningful. + size_t i = mPendingEntries.Length(); + while (i) { + i -= 1; + auto& pendingSurf = mPendingEntries[i]; + if (pendingSurf.mPreviousCheckGeneration > aCheckGenerationsUpTo) { + continue; + } + // Check if the window server is still using the surface. As long as it is doing that, we cannot + // move the surface to mAvailableSurfaces because anything we draw to it could reach the screen + // in a place where we don't expect it. + if (IOSurfaceIsInUse(pendingSurf.mEntry.mIOSurface.get())) { + // The surface is still in use. Update mPreviousCheckGeneration and mCheckCount. + pendingSurf.mPreviousCheckGeneration = mCollectionGeneration; + pendingSurf.mCheckCount++; + if (pendingSurf.mCheckCount >= 30) { + // The window server has been holding on to this surface for an unreasonably long time. This + // is known to happen sometimes, for example in occluded windows or after a GPU switch. In + // that case, release our references to the surface so that it's Not Our Problem anymore. + // Remove the entry from mPendingEntries. + MutateEntryStorage("Eject", IntSize(pendingSurf.mEntry.mSize), + [&]() { mPendingEntries.RemoveElementAt(i); }); + } + } else { + // The surface has become unused! + // Move the entry from mPendingEntries to mAvailableEntries. + MutateEntryStorage("Stop waiting for", IntSize(pendingSurf.mEntry.mSize), [&]() { + mAvailableEntries.AppendElement(std::move(pendingSurf.mEntry)); + mPendingEntries.RemoveElementAt(i); + }); + } + } + return mCollectionGeneration; +} + +void SurfacePoolCA::LockedPool::OnWrapperDestroyed(gl::GLContext* aGL, + SurfacePoolCAWrapperForGL* aWrapper) { + if (aGL) { + DestroyGLResourcesForContext(aGL); + } + + auto iter = mWrappers.find(aGL); + MOZ_RELEASE_ASSERT(iter != mWrappers.end()); + MOZ_RELEASE_ASSERT(iter->second == aWrapper, "Only one SurfacePoolCAWrapperForGL object should " + "exist for each GLContext* at any time"); + mWrappers.erase(iter); +} + +Maybe SurfacePoolCA::LockedPool::GetFramebufferForSurface( + CFTypeRefPtr aSurface, GLContext* aGL, bool aNeedsDepthBuffer) { + MOZ_RELEASE_ASSERT(aGL); + + auto inUseEntryIter = mInUseEntries.find(aSurface); + MOZ_RELEASE_ASSERT(inUseEntryIter != mInUseEntries.end()); + + SurfacePoolEntry& entry = inUseEntryIter->second; + if (entry.mGLResources) { + // We have an existing framebuffer. + MOZ_RELEASE_ASSERT(entry.mGLResources->mGLContext == aGL, + "Recycled surface that still had GL resources from a different GL context. " + "This shouldn't happen."); + if (!aNeedsDepthBuffer || entry.mGLResources->mFramebuffer->HasDepth()) { + return Some(entry.mGLResources->mFramebuffer->mFB); + } + } + + // No usable existing framebuffer, we need to create one. + +#ifdef MOZ_GECKO_PROFILER + AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING( + "Framebuffer creation", GRAPHICS_TileAllocation, + nsPrintfCString("%dx%d", entry.mSize.width, entry.mSize.height)); +#endif + + RefPtr cgl = GLContextCGL::Cast(aGL); + MOZ_RELEASE_ASSERT(cgl, "Unexpected GLContext type"); + + if (!aGL->MakeCurrent()) { + // Context may have been destroyed. + return {}; + } + + GLuint tex = aGL->CreateTexture(); + { + const gl::ScopedBindTexture bindTex(aGL, tex, LOCAL_GL_TEXTURE_RECTANGLE_ARB); + CGLTexImageIOSurface2D(cgl->GetCGLContext(), LOCAL_GL_TEXTURE_RECTANGLE_ARB, LOCAL_GL_RGBA, + entry.mSize.width, entry.mSize.height, LOCAL_GL_BGRA, + LOCAL_GL_UNSIGNED_INT_8_8_8_8_REV, entry.mIOSurface.get(), 0); + } + + auto fb = CreateFramebufferForTexture(aGL, entry.mSize, tex, aNeedsDepthBuffer); + if (!fb) { + // Framebuffer completeness check may have failed. + return {}; + } + + GLuint fbo = fb->mFB; + entry.mGLResources = Some(GLResourcesForSurface{aGL, std::move(fb)}); + return Some(fbo); +} + +RefPtr SurfacePoolCA::LockedPool::GetDepthBufferForSharing( + GLContext* aGL, const IntSize& aSize) { + // Clean out entries for which the weak pointer has become null. + mDepthBuffers.RemoveElementsBy([&](const DepthBufferEntry& entry) { return !entry.mBuffer; }); + + for (const auto& entry : mDepthBuffers) { + if (entry.mGLContext == aGL && entry.mSize == aSize) { + return entry.mBuffer.get(); + } + } + return nullptr; +} + +UniquePtr SurfacePoolCA::LockedPool::CreateFramebufferForTexture( + GLContext* aGL, const IntSize& aSize, GLuint aTexture, bool aNeedsDepthBuffer) { + if (aNeedsDepthBuffer) { + // Try to find an existing depth buffer of aSize in aGL and create a framebuffer that shares it. + if (auto buffer = GetDepthBufferForSharing(aGL, aSize)) { + return gl::MozFramebuffer::CreateForBackingWithSharedDepthAndStencil( + aSize, 0, LOCAL_GL_TEXTURE_RECTANGLE_ARB, aTexture, buffer); + } + } + + // No depth buffer needed or we didn't find one. Create a framebuffer with a new depth buffer and + // store a weak pointer to the new depth buffer in mDepthBuffers. + UniquePtr fb = gl::MozFramebuffer::CreateForBacking( + aGL, aSize, 0, aNeedsDepthBuffer, LOCAL_GL_TEXTURE_RECTANGLE_ARB, aTexture); + if (fb && fb->GetDepthAndStencilBuffer()) { + mDepthBuffers.AppendElement(DepthBufferEntry{aGL, aSize, fb->GetDepthAndStencilBuffer().get()}); + } + + return fb; +} + +// SurfacePoolHandleCA + +SurfacePoolHandleCA::SurfacePoolHandleCA(RefPtr&& aPoolWrapper, + uint64_t aCurrentCollectionGeneration) + : mPoolWrapper(aPoolWrapper), + mPreviousFrameCollectionGeneration( + "SurfacePoolHandleCA::mPreviousFrameCollectionGeneration") { + auto generation = mPreviousFrameCollectionGeneration.Lock(); + *generation = aCurrentCollectionGeneration; +} + +SurfacePoolHandleCA::~SurfacePoolHandleCA() {} + +void SurfacePoolHandleCA::OnBeginFrame() { + auto generation = mPreviousFrameCollectionGeneration.Lock(); + *generation = mPoolWrapper->mPool->CollectPendingSurfaces(*generation); +} + +void SurfacePoolHandleCA::OnEndFrame() { mPoolWrapper->mPool->EnforcePoolSizeLimit(); } + +CFTypeRefPtr SurfacePoolHandleCA::ObtainSurfaceFromPool(const IntSize& aSize) { + return mPoolWrapper->mPool->ObtainSurfaceFromPool(aSize, mPoolWrapper->mGL); +} + +void SurfacePoolHandleCA::ReturnSurfaceToPool(CFTypeRefPtr aSurface) { + mPoolWrapper->mPool->ReturnSurfaceToPool(aSurface); +} + +Maybe SurfacePoolHandleCA::GetFramebufferForSurface(CFTypeRefPtr aSurface, + bool aNeedsDepthBuffer) { + return mPoolWrapper->mPool->GetFramebufferForSurface(aSurface, mPoolWrapper->mGL, + aNeedsDepthBuffer); +} + +// SurfacePoolCA + +SurfacePoolCA::SurfacePoolCA(size_t aPoolSizeLimit) + : mPool(LockedPool(aPoolSizeLimit), "SurfacePoolCA::mPool") {} + +SurfacePoolCA::~SurfacePoolCA() {} + +RefPtr SurfacePoolCA::GetHandleForGL(GLContext* aGL) { + RefPtr wrapper; + uint64_t collectionGeneration = 0; + { + auto pool = mPool.Lock(); + wrapper = pool->GetWrapperForGL(this, aGL); + collectionGeneration = pool->mCollectionGeneration; + } + + // Run the SurfacePoolHandleCA constructor outside of the lock so that the + // mPool lock and the handle's lock are always ordered the same way. + return new SurfacePoolHandleCA(std::move(wrapper), collectionGeneration); +} + +void SurfacePoolCA::DestroyGLResourcesForContext(GLContext* aGL) { + auto pool = mPool.Lock(); + pool->DestroyGLResourcesForContext(aGL); +} + +CFTypeRefPtr SurfacePoolCA::ObtainSurfaceFromPool(const IntSize& aSize, + GLContext* aGL) { + auto pool = mPool.Lock(); + return pool->ObtainSurfaceFromPool(aSize, aGL); +} + +void SurfacePoolCA::ReturnSurfaceToPool(CFTypeRefPtr aSurface) { + auto pool = mPool.Lock(); + pool->ReturnSurfaceToPool(aSurface); +} + +uint64_t SurfacePoolCA::CollectPendingSurfaces(uint64_t aCheckGenerationsUpTo) { + auto pool = mPool.Lock(); + return pool->CollectPendingSurfaces(aCheckGenerationsUpTo); +} +void SurfacePoolCA::EnforcePoolSizeLimit() { + auto pool = mPool.Lock(); + pool->EnforcePoolSizeLimit(); +} + +Maybe SurfacePoolCA::GetFramebufferForSurface(CFTypeRefPtr aSurface, + GLContext* aGL, bool aNeedsDepthBuffer) { + auto pool = mPool.Lock(); + return pool->GetFramebufferForSurface(aSurface, aGL, aNeedsDepthBuffer); +} + +void SurfacePoolCA::OnWrapperDestroyed(gl::GLContext* aGL, SurfacePoolCAWrapperForGL* aWrapper) { + auto pool = mPool.Lock(); + return pool->OnWrapperDestroyed(aGL, aWrapper); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/SyncObject.cpp b/gfx/layers/SyncObject.cpp new file mode 100644 index 0000000000..adb85da14f --- /dev/null +++ b/gfx/layers/SyncObject.cpp @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SyncObject.h" + +#ifdef XP_WIN +# include "mozilla/gfx/DeviceManagerDx.h" +# include "mozilla/layers/TextureD3D11.h" +#endif +#include "nsIXULRuntime.h" // for BrowserTabsRemoteAutostart + +namespace mozilla { +namespace layers { + +#ifdef XP_WIN +/* static */ +already_AddRefed SyncObjectHost::CreateSyncObjectHost( + ID3D11Device* aDevice) { + return MakeAndAddRef(aDevice); +} + +/* static */ +already_AddRefed SyncObjectClient::CreateSyncObjectClient( + SyncHandle aHandle, ID3D11Device* aDevice) { + if (!aHandle) { + return nullptr; + } + + return MakeAndAddRef(aHandle, aDevice); +} +#endif + +already_AddRefed +SyncObjectClient::CreateSyncObjectClientForContentDevice(SyncHandle aHandle) { +#ifndef XP_WIN + return nullptr; +#else + if (!aHandle) { + return nullptr; + } + + return MakeAndAddRef(aHandle); +#endif +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/SyncObject.h b/gfx/layers/SyncObject.h new file mode 100644 index 0000000000..03590dd436 --- /dev/null +++ b/gfx/layers/SyncObject.h @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_LAYERS_SYNCOBJECT_H +#define MOZILLA_GFX_LAYERS_SYNCOBJECT_H + +#include "mozilla/RefCounted.h" + +struct ID3D11Device; + +namespace mozilla { +namespace layers { + +#ifdef XP_WIN +typedef void* SyncHandle; +#else +typedef uintptr_t SyncHandle; +#endif // XP_WIN + +class SyncObjectHost : public RefCounted { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SyncObjectHost) + virtual ~SyncObjectHost() = default; + +#ifdef XP_WIN + static already_AddRefed CreateSyncObjectHost( + ID3D11Device* aDevice); +#endif + virtual bool Init() = 0; + + virtual SyncHandle GetSyncHandle() = 0; + + // Return false for failed synchronization. + virtual bool Synchronize(bool aFallible = false) = 0; + + protected: + SyncObjectHost() = default; +}; + +class SyncObjectClient : public external::AtomicRefCounted { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SyncObjectClient) + virtual ~SyncObjectClient() = default; + +#ifdef XP_WIN + static already_AddRefed CreateSyncObjectClient( + SyncHandle aHandle, ID3D11Device* aDevice); +#endif + static already_AddRefed + CreateSyncObjectClientForContentDevice(SyncHandle aHandle); + + enum class SyncType { + D3D11, + }; + + virtual SyncType GetSyncType() = 0; + + // Return false for failed synchronization. + virtual bool Synchronize(bool aFallible = false) = 0; + + virtual bool IsSyncObjectValid() = 0; + + virtual void EnsureInitialized() = 0; + + protected: + SyncObjectClient() = default; +}; + +} // namespace layers +} // namespace mozilla + +#endif // MOZILLA_GFX_LAYERS_SYNCOBJECT_H diff --git a/gfx/layers/TextureDIB.cpp b/gfx/layers/TextureDIB.cpp new file mode 100644 index 0000000000..8c0c3ad116 --- /dev/null +++ b/gfx/layers/TextureDIB.cpp @@ -0,0 +1,466 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TextureDIB.h" +#include "gfx2DGlue.h" +#include "mozilla/gfx/DataSurfaceHelpers.h" // For BufferSizeFromDimensions +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/layers/ISurfaceAllocator.h" +#include "mozilla/layers/TextureForwarder.h" // For LayersIPCChannel + +namespace mozilla { + +using namespace gfx; + +namespace layers { + +/** + * Can only be drawn into through Cairo. + * The coresponding TextureHost depends on the compositor + */ +class MemoryDIBTextureData : public DIBTextureData { + public: + virtual bool Serialize(SurfaceDescriptor& aOutDescriptor) override; + + virtual TextureData* CreateSimilar( + LayersIPCChannel* aAllocator, LayersBackend aLayersBackend, + TextureFlags aFlags = TextureFlags::DEFAULT, + TextureAllocationFlags aAllocFlags = ALLOC_DEFAULT) const override; + + virtual bool UpdateFromSurface(gfx::SourceSurface* aSurface) override; + + static DIBTextureData* Create(gfx::IntSize aSize, gfx::SurfaceFormat aFormat); + + virtual void Deallocate(LayersIPCChannel* aAllocator) override { + mSurface = nullptr; + } + + MemoryDIBTextureData(gfx::IntSize aSize, gfx::SurfaceFormat aFormat, + gfxWindowsSurface* aSurface) + : DIBTextureData(aSize, aFormat, aSurface) { + MOZ_COUNT_CTOR(MemoryDIBTextureData); + } + + MOZ_COUNTED_DTOR_OVERRIDE(MemoryDIBTextureData) +}; + +/** + * Can only be drawn into through Cairo. + * The coresponding TextureHost depends on the compositor + */ +class ShmemDIBTextureData : public DIBTextureData { + public: + virtual bool Serialize(SurfaceDescriptor& aOutDescriptor) override; + + virtual TextureData* CreateSimilar( + LayersIPCChannel* aAllocator, LayersBackend aLayersBackend, + TextureFlags aFlags = TextureFlags::DEFAULT, + TextureAllocationFlags aAllocFlags = ALLOC_DEFAULT) const override; + + virtual bool UpdateFromSurface(gfx::SourceSurface* aSurface) override; + + static DIBTextureData* Create(gfx::IntSize aSize, gfx::SurfaceFormat aFormat, + LayersIPCChannel* aAllocator); + + void DeallocateData() { + if (mSurface) { + ::DeleteObject(mBitmap); + ::DeleteDC(mDC); + ::CloseHandle(mFileMapping); + mBitmap = NULL; + mDC = NULL; + mFileMapping = NULL; + mSurface = nullptr; + } + } + + virtual void Deallocate(LayersIPCChannel* aAllocator) override { + DeallocateData(); + } + + ShmemDIBTextureData(gfx::IntSize aSize, gfx::SurfaceFormat aFormat, + gfxWindowsSurface* aSurface, HANDLE aFileMapping, + HANDLE aHostHandle, HDC aDC, HBITMAP aBitmap) + : DIBTextureData(aSize, aFormat, aSurface), + mFileMapping(aFileMapping), + mHostHandle(aHostHandle), + mDC(aDC), + mBitmap(aBitmap) { + MOZ_COUNT_CTOR(ShmemDIBTextureData); + } + + virtual ~ShmemDIBTextureData() { + MOZ_COUNT_DTOR(ShmemDIBTextureData); + + // The host side has its own references and handles to this data, we can + // safely clear ours. + DeallocateData(); + } + + HANDLE mFileMapping; + HANDLE mHostHandle; + HDC mDC; + HBITMAP mBitmap; +}; + +void DIBTextureData::FillInfo(TextureData::Info& aInfo) const { + aInfo.size = mSize; + aInfo.format = mFormat; + aInfo.hasIntermediateBuffer = true; + aInfo.hasSynchronization = false; + aInfo.supportsMoz2D = true; + aInfo.canExposeMappedData = false; +} + +already_AddRefed DIBTextureData::BorrowDrawTarget() { + return gfxPlatform::CreateDrawTargetForSurface(mSurface, mSize); +} + +DIBTextureData* DIBTextureData::Create(gfx::IntSize aSize, + gfx::SurfaceFormat aFormat, + LayersIPCChannel* aAllocator) { + if (!aAllocator) { + return nullptr; + } + if (aFormat == gfx::SurfaceFormat::UNKNOWN) { + return nullptr; + } + if (aAllocator->IsSameProcess()) { + return MemoryDIBTextureData::Create(aSize, aFormat); + } else { + return ShmemDIBTextureData::Create(aSize, aFormat, aAllocator); + } +} + +TextureData* MemoryDIBTextureData::CreateSimilar( + LayersIPCChannel* aAllocator, LayersBackend aLayersBackend, + TextureFlags aFlags, TextureAllocationFlags aAllocFlags) const { + if (!aAllocator) { + return nullptr; + } + return MemoryDIBTextureData::Create(mSize, mFormat); +} + +bool MemoryDIBTextureData::Serialize(SurfaceDescriptor& aOutDescriptor) { + MOZ_ASSERT(mSurface); + // The host will release this ref when it receives the surface descriptor. + // We AddRef in case we die before the host receives the pointer. + aOutDescriptor = + SurfaceDescriptorDIB(reinterpret_cast(mSurface.get())); + mSurface.get()->AddRef(); + return true; +} + +DIBTextureData* MemoryDIBTextureData::Create(gfx::IntSize aSize, + gfx::SurfaceFormat aFormat) { + RefPtr surface = + new gfxWindowsSurface(aSize, SurfaceFormatToImageFormat(aFormat)); + if (!surface || surface->CairoStatus()) { + NS_WARNING("Could not create DIB surface"); + return nullptr; + } + + return new MemoryDIBTextureData(aSize, aFormat, surface); +} + +bool MemoryDIBTextureData::UpdateFromSurface(gfx::SourceSurface* aSurface) { + RefPtr imgSurf = mSurface->GetAsImageSurface(); + + RefPtr srcSurf = aSurface->GetDataSurface(); + + if (!srcSurf) { + gfxCriticalError() + << "Failed to GetDataSurface in UpdateFromSurface (DIB)."; + return false; + } + + DataSourceSurface::MappedSurface sourceMap; + if (!srcSurf->Map(gfx::DataSourceSurface::READ, &sourceMap)) { + gfxCriticalError() << "Failed to map source surface for UpdateFromSurface."; + return false; + } + + for (int y = 0; y < srcSurf->GetSize().height; y++) { + memcpy(imgSurf->Data() + imgSurf->Stride() * y, + sourceMap.mData + sourceMap.mStride * y, + srcSurf->GetSize().width * BytesPerPixel(srcSurf->GetFormat())); + } + + srcSurf->Unmap(); + return true; +} + +TextureData* ShmemDIBTextureData::CreateSimilar( + LayersIPCChannel* aAllocator, LayersBackend aLayersBackend, + TextureFlags aFlags, TextureAllocationFlags aAllocFlags) const { + if (!aAllocator) { + return nullptr; + } + return ShmemDIBTextureData::Create(mSize, mFormat, aAllocator); +} + +bool ShmemDIBTextureData::UpdateFromSurface(gfx::SourceSurface* aSurface) { + RefPtr srcSurf = aSurface->GetDataSurface(); + + if (!srcSurf) { + gfxCriticalError() + << "Failed to GetDataSurface in UpdateFromSurface (DTD)."; + return false; + } + + DataSourceSurface::MappedSurface sourceMap; + if (!srcSurf->Map(gfx::DataSourceSurface::READ, &sourceMap)) { + gfxCriticalError() << "Failed to map source surface for UpdateFromSurface."; + return false; + } + + GdiFlush(); + + uint32_t stride = mSize.width * BytesPerPixel(mFormat); + uint8_t* data = (uint8_t*)::MapViewOfFile(mFileMapping, FILE_MAP_WRITE, 0, 0, + stride * mSize.height); + + if (!data) { + gfxCriticalError() << "Failed to map view of file for UpdateFromSurface."; + srcSurf->Unmap(); + return false; + } + + for (int y = 0; y < srcSurf->GetSize().height; y++) { + memcpy(data + stride * y, sourceMap.mData + sourceMap.mStride * y, + srcSurf->GetSize().width * BytesPerPixel(srcSurf->GetFormat())); + } + + ::UnmapViewOfFile(data); + + srcSurf->Unmap(); + return true; +} + +bool ShmemDIBTextureData::Serialize(SurfaceDescriptor& aOutDescriptor) { + if (mFormat == gfx::SurfaceFormat::UNKNOWN) { + return false; + } + + ::GdiFlush(); + aOutDescriptor = + SurfaceDescriptorFileMapping((WindowsHandle)mHostHandle, mFormat, mSize); + return true; +} + +DIBTextureData* ShmemDIBTextureData::Create(gfx::IntSize aSize, + gfx::SurfaceFormat aFormat, + LayersIPCChannel* aAllocator) { + MOZ_ASSERT(aAllocator->GetParentPid() != base::ProcessId()); + + DWORD mapSize = aSize.width * aSize.height * BytesPerPixel(aFormat); + HANDLE fileMapping = ::CreateFileMapping(INVALID_HANDLE_VALUE, NULL, + PAGE_READWRITE, 0, mapSize, NULL); + + if (!fileMapping) { + gfxCriticalError() << "Failed to create memory file mapping for " << mapSize + << " bytes."; + return nullptr; + } + + BITMAPV4HEADER header; + memset(&header, 0, sizeof(BITMAPV4HEADER)); + header.bV4Size = sizeof(BITMAPV4HEADER); + header.bV4Width = aSize.width; + header.bV4Height = -LONG(aSize.height); // top-to-buttom DIB + header.bV4Planes = 1; + header.bV4BitCount = 32; + header.bV4V4Compression = BI_BITFIELDS; + header.bV4RedMask = 0x00FF0000; + header.bV4GreenMask = 0x0000FF00; + header.bV4BlueMask = 0x000000FF; + + HDC nulldc = ::GetDC(NULL); + + HDC dc = ::CreateCompatibleDC(nulldc); + + ::ReleaseDC(nullptr, nulldc); + + if (!dc) { + ::CloseHandle(fileMapping); + gfxCriticalError() << "Failed to create DC for bitmap."; + return nullptr; + } + + void* bits; + HBITMAP bitmap = ::CreateDIBSection(dc, (BITMAPINFO*)&header, DIB_RGB_COLORS, + &bits, fileMapping, 0); + + if (!bitmap) { + gfxCriticalError() << "Failed to create DIB section for a bitmap of size " + << aSize << " and mapSize " << mapSize; + ::CloseHandle(fileMapping); + ::DeleteDC(dc); + return nullptr; + } + + ::SelectObject(dc, bitmap); + + RefPtr surface = new gfxWindowsSurface(dc, 0); + if (surface->CairoStatus()) { + ::DeleteObject(bitmap); + ::DeleteDC(dc); + ::CloseHandle(fileMapping); + gfxCriticalError() << "Could not create surface, status: " + << surface->CairoStatus(); + return nullptr; + } + + HANDLE hostHandle = NULL; + + if (!ipc::DuplicateHandle(fileMapping, aAllocator->GetParentPid(), + &hostHandle, 0, DUPLICATE_SAME_ACCESS)) { + gfxCriticalError() + << "Failed to duplicate handle to parent process for surface."; + ::DeleteObject(bitmap); + ::DeleteDC(dc); + ::CloseHandle(fileMapping); + return nullptr; + } + + return new ShmemDIBTextureData(aSize, aFormat, surface, fileMapping, + hostHandle, dc, bitmap); +} + +bool TextureHostDirectUpload::Lock() { + MOZ_ASSERT(!mIsLocked); + mIsLocked = true; + return true; +} + +void TextureHostDirectUpload::Unlock() { + MOZ_ASSERT(mIsLocked); + mIsLocked = false; +} + +void TextureHostDirectUpload::SetTextureSourceProvider( + TextureSourceProvider* aProvider) { + mProvider = aProvider; +} + +void TextureHostDirectUpload::DeallocateDeviceData() { + if (mTextureSource) { + mTextureSource->DeallocateDeviceData(); + } +} + +bool TextureHostDirectUpload::BindTextureSource( + CompositableTextureSourceRef& aTexture) { + return AcquireTextureSource(aTexture); +} + +bool TextureHostDirectUpload::AcquireTextureSource( + CompositableTextureSourceRef& aTexture) { + if (!mTextureSource) { + Updated(); + } + + aTexture = mTextureSource; + return !!aTexture; +} + +DIBTextureHost::DIBTextureHost(TextureFlags aFlags, + const SurfaceDescriptorDIB& aDescriptor) + : TextureHostDirectUpload(aFlags, SurfaceFormat::B8G8R8X8, IntSize()) { + // We added an extra ref for transport, so we shouldn't AddRef now. + mSurface = + dont_AddRef(reinterpret_cast(aDescriptor.surface())); + MOZ_ASSERT(mSurface); + + mSize = mSurface->GetSize(); + mFormat = mSurface->GetSurfaceFormat(); +} + +void DIBTextureHost::UpdatedInternal(const nsIntRegion* aRegion) { + if (!mProvider) { + // This can happen if we send textures to a compositable that isn't yet + // attached to a layer. + return; + } + + if (!mTextureSource) { + mTextureSource = mProvider->CreateDataTextureSource(mFlags); + } + + if (mSurface->CairoStatus()) { + gfxWarning() << "Bad Cairo surface internal update " + << mSurface->CairoStatus(); + mTextureSource = nullptr; + return; + } + RefPtr imgSurf = mSurface->GetAsImageSurface(); + + RefPtr surf = Factory::CreateWrappingDataSourceSurface( + imgSurf->Data(), imgSurf->Stride(), mSize, mFormat); + + if (!surf || + !mTextureSource->Update(surf, const_cast(aRegion))) { + mTextureSource = nullptr; + } + + ReadUnlock(); +} + +TextureHostFileMapping::TextureHostFileMapping( + TextureFlags aFlags, const SurfaceDescriptorFileMapping& aDescriptor) + : TextureHostDirectUpload(aFlags, aDescriptor.format(), aDescriptor.size()), + mFileMapping((HANDLE)aDescriptor.handle()) {} + +TextureHostFileMapping::~TextureHostFileMapping() { + ::CloseHandle(mFileMapping); +} + +UserDataKey kFileMappingKey; + +static void UnmapFileData(void* aData) { + MOZ_ASSERT(aData); + ::UnmapViewOfFile(aData); +} + +void TextureHostFileMapping::UpdatedInternal(const nsIntRegion* aRegion) { + if (!mProvider) { + // This can happen if we send textures to a compositable that isn't yet + // attached to a layer. + return; + } + + if (!mTextureSource) { + mTextureSource = mProvider->CreateDataTextureSource(mFlags); + } + + uint8_t* data = nullptr; + int32_t totalBytes = BufferSizeFromDimensions(mSize.width, mSize.height, + BytesPerPixel(mFormat)); + if (totalBytes > 0) { + data = (uint8_t*)::MapViewOfFile(mFileMapping, FILE_MAP_READ, 0, 0, + totalBytes); + } + + if (data) { + RefPtr surf = Factory::CreateWrappingDataSourceSurface( + data, mSize.width * BytesPerPixel(mFormat), mSize, mFormat); + if (surf) { + surf->AddUserData(&kFileMappingKey, data, UnmapFileData); + if (!mTextureSource->Update(surf, const_cast(aRegion))) { + mTextureSource = nullptr; + } + } else { + mTextureSource = nullptr; + } + } else { + mTextureSource = nullptr; + } + + ReadUnlock(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/TextureDIB.h b/gfx/layers/TextureDIB.h new file mode 100644 index 0000000000..707cd9ae3e --- /dev/null +++ b/gfx/layers/TextureDIB.h @@ -0,0 +1,116 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_TEXTUREDIB_H +#define MOZILLA_GFX_TEXTUREDIB_H + +#include "mozilla/layers/Compositor.h" +#include "mozilla/layers/TextureClient.h" +#include "mozilla/layers/TextureHost.h" +#include "mozilla/GfxMessageUtils.h" +#include "gfxWindowsPlatform.h" + +namespace mozilla { +namespace layers { + +class DIBTextureData : public TextureData { + public: + bool Lock(OpenMode) override { return true; } + + void Unlock() override {} + + void FillInfo(TextureData::Info& aInfo) const override; + + already_AddRefed BorrowDrawTarget() override; + + static DIBTextureData* Create(gfx::IntSize aSize, gfx::SurfaceFormat aFormat, + LayersIPCChannel* aAllocator); + + protected: + DIBTextureData(gfx::IntSize aSize, gfx::SurfaceFormat aFormat, + gfxWindowsSurface* aSurface) + : mSurface(aSurface), mSize(aSize), mFormat(aFormat) { + MOZ_ASSERT(aSurface); + } + + RefPtr mSurface; + gfx::IntSize mSize; + gfx::SurfaceFormat mFormat; +}; + +/** + * This is meant for a texture host which does a direct upload from + * Updated to a Compositor specific DataTextureSource and therefor doesn't + * need any specific Lock/Unlock magic. + */ +class TextureHostDirectUpload : public TextureHost { + public: + TextureHostDirectUpload(TextureFlags aFlags, gfx::SurfaceFormat aFormat, + gfx::IntSize aSize) + : TextureHost(aFlags), mFormat(aFormat), mSize(aSize), mIsLocked(false) {} + + void DeallocateDeviceData() override; + + void SetTextureSourceProvider(TextureSourceProvider* aProvider) override; + + gfx::SurfaceFormat GetFormat() const override { return mFormat; } + + gfx::IntSize GetSize() const override { return mSize; } + + bool Lock() override; + + void Unlock() override; + + bool HasIntermediateBuffer() const { return true; } + + bool BindTextureSource(CompositableTextureSourceRef& aTexture) override; + bool AcquireTextureSource(CompositableTextureSourceRef& aTexture) override; + + protected: + RefPtr mProvider; + RefPtr mTextureSource; + gfx::SurfaceFormat mFormat; + gfx::IntSize mSize; + bool mIsLocked; +}; + +class DIBTextureHost : public TextureHostDirectUpload { + public: + DIBTextureHost(TextureFlags aFlags, const SurfaceDescriptorDIB& aDescriptor); + + already_AddRefed GetAsSurface() override { + return nullptr; // TODO: cf bug 872568 + } + + protected: + void UpdatedInternal(const nsIntRegion* aRegion = nullptr) override; + + RefPtr mSurface; +}; + +class TextureHostFileMapping : public TextureHostDirectUpload { + public: + TextureHostFileMapping(TextureFlags aFlags, + const SurfaceDescriptorFileMapping& aDescriptor); + ~TextureHostFileMapping(); + + already_AddRefed GetAsSurface() override { + MOZ_CRASH("GFX: TextureHostFileMapping::GetAsSurface not implemented"); + // Not implemented! It would be tricky to keep track of the + // scope of the file mapping. We could do this through UserData + // on the DataSourceSurface but we don't need this right now. + } + + protected: + void UpdatedInternal(const nsIntRegion* aRegion = nullptr) override; + + HANDLE mFileMapping; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* MOZILLA_GFX_TEXTUREDIB_H */ diff --git a/gfx/layers/TextureSourceProvider.cpp b/gfx/layers/TextureSourceProvider.cpp new file mode 100644 index 0000000000..de3505a783 --- /dev/null +++ b/gfx/layers/TextureSourceProvider.cpp @@ -0,0 +1,88 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/TextureSourceProvider.h" +#include "mozilla/layers/TextureHost.h" +#include "mozilla/layers/PTextureParent.h" +#ifdef XP_DARWIN +# include "mozilla/layers/TextureSync.h" +# include "nsClassHashtable.h" +#endif + +namespace mozilla { +namespace layers { + +TextureSourceProvider::~TextureSourceProvider() { ReadUnlockTextures(); } + +void TextureSourceProvider::ReadUnlockTextures() { +#ifdef XP_DARWIN + nsClassHashtable> + texturesIdsToUnlockByPid; + for (auto& texture : mUnlockAfterComposition) { + auto bufferTexture = texture->AsBufferTextureHost(); + if (bufferTexture && bufferTexture->IsDirectMap()) { + texture->ReadUnlock(); + auto actor = texture->GetIPDLActor(); + if (actor) { + base::ProcessId pid = actor->OtherPid(); + nsTArray* textureIds = + texturesIdsToUnlockByPid.LookupOrAdd(pid); + textureIds->AppendElement(TextureHost::GetTextureSerial(actor)); + } + } else { + texture->ReadUnlock(); + } + } + for (auto it = texturesIdsToUnlockByPid.ConstIter(); !it.Done(); it.Next()) { + TextureSync::SetTexturesUnlocked(it.Key(), *it.UserData()); + } +#else + for (auto& texture : mUnlockAfterComposition) { + texture->ReadUnlock(); + } +#endif + mUnlockAfterComposition.Clear(); +} + +void TextureSourceProvider::UnlockAfterComposition(TextureHost* aTexture) { + mUnlockAfterComposition.AppendElement(aTexture); +} + +bool TextureSourceProvider::NotifyNotUsedAfterComposition( + TextureHost* aTextureHost) { + mNotifyNotUsedAfterComposition.AppendElement(aTextureHost); + + // If Compositor holds many TextureHosts without compositing, + // the TextureHosts should be flushed to reduce memory consumption. + const int thresholdCount = 5; + const double thresholdSec = 2.0f; + if (mNotifyNotUsedAfterComposition.Length() > thresholdCount) { + TimeStamp lastCompositionEndTime = GetLastCompositionEndTime(); + TimeDuration duration = lastCompositionEndTime + ? TimeStamp::Now() - lastCompositionEndTime + : TimeDuration(); + // Check if we could flush + if (duration.ToSeconds() > thresholdSec) { + FlushPendingNotifyNotUsed(); + } + } + return true; +} + +void TextureSourceProvider::FlushPendingNotifyNotUsed() { + for (auto& textureHost : mNotifyNotUsedAfterComposition) { + textureHost->CallNotifyNotUsed(); + } + mNotifyNotUsedAfterComposition.Clear(); +} + +void TextureSourceProvider::Destroy() { + ReadUnlockTextures(); + FlushPendingNotifyNotUsed(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/TextureSourceProvider.h b/gfx/layers/TextureSourceProvider.h new file mode 100644 index 0000000000..aeaace8514 --- /dev/null +++ b/gfx/layers/TextureSourceProvider.h @@ -0,0 +1,141 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef mozilla_gfx_layers_TextureSourceProvider_h +#define mozilla_gfx_layers_TextureSourceProvider_h + +#include "nsISupportsImpl.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/layers/CompositorTypes.h" +#include "nsTArray.h" + +struct ID3D11Device; + +namespace mozilla { +namespace gfx { +class DataSourceSurface; +} // namespace gfx +namespace gl { +class GLContext; +} // namespace gl +namespace layers { + +class TextureHost; +class DataTextureSource; +class BasicCompositor; +class Compositor; +class CompositorOGL; + +// Provided by a HostLayerManager or Compositor for allocating backend-specific +// texture types. +class TextureSourceProvider { + public: + NS_INLINE_DECL_REFCOUNTING(TextureSourceProvider) + + virtual already_AddRefed CreateDataTextureSource( + TextureFlags aFlags = TextureFlags::NO_FLAGS) = 0; + + virtual already_AddRefed CreateDataTextureSourceAround( + gfx::DataSourceSurface* aSurface) { + return nullptr; + } + + virtual already_AddRefed + CreateDataTextureSourceAroundYCbCr(TextureHost* aTexture) { + return nullptr; + } + + virtual TimeStamp GetLastCompositionEndTime() const = 0; + + // Return true if the effect type is supported. + // + // By default Compositor implementations should support all effects but in + // some rare cases it is not possible to support an effect efficiently. + // This is the case for BasicCompositor with EffectYCbCr. + virtual bool SupportsEffect(EffectTypes aEffect) { return true; } + + /// Most compositor backends operate asynchronously under the hood. This + /// means that when a layer stops using a texture it is often desirable to + /// wait for the end of the next composition before releasing the texture's + /// ReadLock. + /// This function provides a convenient way to do this delayed unlocking, if + /// the texture itself requires it. + virtual void UnlockAfterComposition(TextureHost* aTexture); + + /// Most compositor backends operate asynchronously under the hood. This + /// means that when a layer stops using a texture it is often desirable to + /// wait for the end of the next composition before NotifyNotUsed() call. + /// This function provides a convenient way to do this delayed NotifyNotUsed() + /// call, if the texture itself requires it. + /// See bug 1260611 and bug 1252835 + /// + /// Returns true if notified, false otherwise. + virtual bool NotifyNotUsedAfterComposition(TextureHost* aTextureHost); + + virtual void MaybeUnlockBeforeNextComposition(TextureHost* aTextureHost) {} + virtual void TryUnlockTextures() {} + + // If overridden, make sure to call the base function. + virtual void Destroy(); + + void FlushPendingNotifyNotUsed(); + + // If this provider is also a Compositor, return the compositor. Otherwise + // return null. + virtual Compositor* AsCompositor() { return nullptr; } + + // If this provider is also a BasicCompositor, return the compositor. + // Otherwise return nullptr. + virtual BasicCompositor* AsBasicCompositor() { return nullptr; } + + // If this provider is also a CompositorOGL, return the compositor. Otherwise + // return nullptr. + virtual CompositorOGL* AsCompositorOGL() { return nullptr; } + +#ifdef XP_WIN + // On Windows, if this provides Direct3D textures, it must expose the device. + virtual ID3D11Device* GetD3D11Device() const { return nullptr; } +#endif + + // If this provides OpenGL textures, it must expose the GLContext. + virtual gl::GLContext* GetGLContext() const { return nullptr; } + + virtual int32_t GetMaxTextureSize() const = 0; + + // Return whether or not this provider is still valid (i.e., is still being + // used to composite). + virtual bool IsValid() const = 0; + + public: + class MOZ_STACK_CLASS AutoReadUnlockTextures final { + public: + explicit AutoReadUnlockTextures(TextureSourceProvider* aProvider) + : mProvider(aProvider) {} + ~AutoReadUnlockTextures() { mProvider->ReadUnlockTextures(); } + + private: + RefPtr mProvider; + }; + + protected: + // Should be called at the end of each composition. + void ReadUnlockTextures(); + + virtual ~TextureSourceProvider(); + + private: + // An array of locks that will need to be unlocked after the next composition. + nsTArray> mUnlockAfterComposition; + + // An array of TextureHosts that will need to call NotifyNotUsed() after the + // next composition. + nsTArray> mNotifyNotUsedAfterComposition; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_gfx_layers_TextureSourceProvider_h diff --git a/gfx/layers/TextureSync.cpp b/gfx/layers/TextureSync.cpp new file mode 100644 index 0000000000..6a0b17f206 --- /dev/null +++ b/gfx/layers/TextureSync.cpp @@ -0,0 +1,283 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TextureSync.h" + +#include + +#include "base/process_util.h" +#include "chrome/common/mach_ipc_mac.h" +#include "mozilla/ipc/SharedMemoryBasic.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/StaticMonitor.h" +#include "mozilla/StaticPtr.h" + +#ifdef DEBUG +# define LOG_ERROR(str, args...) \ + PR_BEGIN_MACRO \ + mozilla::SmprintfPointer msg = mozilla::Smprintf(str, ##args); \ + NS_WARNING(msg.get()); \ + PR_END_MACRO +#else +# define LOG_ERROR(str, args...) \ + do { /* nothing */ \ + } while (0) +#endif + +namespace mozilla { + +namespace layers { + +// Hold raw pointers and trust that TextureSourceProviders will be +// unregistered in their destructors - we don't want to keep these +// alive, and destroying them from the main thread will be an +// error anyway. +StaticAutoPtr> gTextureSourceProviders; + +static std::map> gProcessTextureIds; +static StaticMonitor gTextureLockMonitor; + +const int kSendMessageTimeout = 1000; +const int kTextureLockTimeout = 32; // We really don't want to wait more than + // two frames for a texture to unlock. This + // will in any case be very uncommon. + +struct WaitForTexturesReply { + bool success; +}; + +struct WaitForTexturesRequest { + pid_t pid; +}; + +static std::unordered_set* GetLockedTextureIdsForProcess(pid_t pid) { + gTextureLockMonitor.AssertCurrentThreadOwns(); + + if (gProcessTextureIds.find(pid) == gProcessTextureIds.end()) { + gProcessTextureIds[pid] = std::unordered_set(); + } + + return &gProcessTextureIds.at(pid); +} + +static bool WaitForTextureIdsToUnlock(pid_t pid, + const Span& textureIds) { + { + StaticMonitorAutoLock lock(gTextureLockMonitor); + std::unordered_set* freedTextureIds = + GetLockedTextureIdsForProcess(pid); + + TimeStamp start = TimeStamp::Now(); + while (true) { + bool allCleared = true; + for (uint64_t textureId : textureIds) { + if (freedTextureIds->find(textureId) != freedTextureIds->end()) { + allCleared = false; + } + } + + if (allCleared) { + return true; + } + + if (lock.Wait(TimeDuration::FromMilliseconds(kTextureLockTimeout)) == + CVStatus::Timeout) { + return false; + } + + // In case the monitor gets signaled multiple times, each less than + // kTextureLockTimeout. This ensures that the total time we wait is + // < 2 * kTextureLockTimeout + if ((TimeStamp::Now() - start).ToMilliseconds() > + (double)kTextureLockTimeout) { + return false; + } + } + } +} + +static void CheckTexturesForUnlock() { + if (gTextureSourceProviders) { + for (auto it = gTextureSourceProviders->begin(); + it != gTextureSourceProviders->end(); ++it) { + (*it)->TryUnlockTextures(); + } + } +} + +void TextureSync::DispatchCheckTexturesForUnlock() { + RefPtr task = + NS_NewRunnableFunction("CheckTexturesForUnlock", &CheckTexturesForUnlock); + CompositorThread()->Dispatch(task.forget()); +} + +void TextureSync::HandleWaitForTexturesMessage(MachReceiveMessage* rmsg, + ipc::MemoryPorts* ports) { + WaitForTexturesRequest* req = + reinterpret_cast(rmsg->GetData()); + uint64_t* textureIds = (uint64_t*)(req + 1); + uint32_t textureIdsLength = + (rmsg->GetDataLength() - sizeof(WaitForTexturesRequest)) / + sizeof(uint64_t); + + bool success = + WaitForTextureIdsToUnlock(req->pid, Span(textureIds, textureIdsLength)); + + if (!success) { + LOG_ERROR("Waiting for textures to unlock failed.\n"); + } + + MachSendMessage msg(ipc::kReturnWaitForTexturesMsg); + WaitForTexturesReply replydata; + replydata.success = success; + msg.SetData(&replydata, sizeof(WaitForTexturesReply)); + kern_return_t err = ports->mSender->SendMessage(msg, kSendMessageTimeout); + if (KERN_SUCCESS != err) { + LOG_ERROR("SendMessage failed 0x%x %s\n", err, mach_error_string(err)); + } +} + +void TextureSync::RegisterTextureSourceProvider( + TextureSourceProvider* textureSourceProvider) { + if (!gTextureSourceProviders) { + gTextureSourceProviders = new nsTArray(); + } + MOZ_RELEASE_ASSERT(!gTextureSourceProviders->Contains(textureSourceProvider)); + gTextureSourceProviders->AppendElement(textureSourceProvider); +} + +void TextureSync::UnregisterTextureSourceProvider( + TextureSourceProvider* textureSourceProvider) { + if (gTextureSourceProviders) { + MOZ_ASSERT(gTextureSourceProviders->Contains(textureSourceProvider)); + gTextureSourceProviders->RemoveElement(textureSourceProvider); + if (gTextureSourceProviders->Length() == 0) { + gTextureSourceProviders = nullptr; + } + } +} + +void TextureSync::SetTexturesLocked(pid_t pid, + const nsTArray& textureIds) { + StaticMonitorAutoLock mal(gTextureLockMonitor); + std::unordered_set* lockedTextureIds = + GetLockedTextureIdsForProcess(pid); + for (uint64_t textureId : textureIds) { + lockedTextureIds->insert(textureId); + } +} + +void TextureSync::SetTexturesUnlocked(pid_t pid, + const nsTArray& textureIds) { + bool oneErased = false; + { + StaticMonitorAutoLock mal(gTextureLockMonitor); + std::unordered_set* lockedTextureIds = + GetLockedTextureIdsForProcess(pid); + for (uint64_t textureId : textureIds) { + if (lockedTextureIds->erase(textureId)) { + oneErased = true; + } + } + } + if (oneErased) { + gTextureLockMonitor.NotifyAll(); + } +} + +void TextureSync::Shutdown() { + { + StaticMonitorAutoLock lock(gTextureLockMonitor); + + for (auto& lockedTextureIds : gProcessTextureIds) { + lockedTextureIds.second.clear(); + } + } + + gTextureLockMonitor.NotifyAll(); + + { + StaticMonitorAutoLock lock(gTextureLockMonitor); + gProcessTextureIds.clear(); + } +} + +void TextureSync::UpdateTextureLocks(base::ProcessId aProcessId) { + if (aProcessId == base::GetCurrentProcId()) { + DispatchCheckTexturesForUnlock(); + return; + } + + MachSendMessage smsg(ipc::kUpdateTextureLocksMsg); + smsg.SetData(&aProcessId, sizeof(aProcessId)); + ipc::SharedMemoryBasic::SendMachMessage(aProcessId, smsg, NULL); +} + +bool TextureSync::WaitForTextures(base::ProcessId aProcessId, + const nsTArray& textureIds) { + if (aProcessId == base::GetCurrentProcId()) { + bool success = WaitForTextureIdsToUnlock(aProcessId, Span(textureIds)); + if (!success) { + LOG_ERROR("Failed waiting for textures to unlock.\n"); + } + + return success; + } + + MachSendMessage smsg(ipc::kWaitForTexturesMsg); + size_t messageSize = + sizeof(WaitForTexturesRequest) + textureIds.Length() * sizeof(uint64_t); + UniquePtr messageData = MakeUnique(messageSize); + WaitForTexturesRequest* req = (WaitForTexturesRequest*)messageData.get(); + uint64_t* reqTextureIds = (uint64_t*)(req + 1); + + for (uint32_t i = 0; i < textureIds.Length(); ++i) { + reqTextureIds[i] = textureIds[i]; + } + + req->pid = base::GetCurrentProcId(); + bool dataWasSet = smsg.SetData(req, messageSize); + + if (!dataWasSet) { + LOG_ERROR("Data was too large: %zu\n", messageSize); + return false; + } + + MachReceiveMessage msg; + bool success = + ipc::SharedMemoryBasic::SendMachMessage(aProcessId, smsg, &msg); + if (!success) { + return false; + } + + if (msg.GetDataLength() != sizeof(WaitForTexturesReply)) { + LOG_ERROR("Improperly formatted reply\n"); + return false; + } + + WaitForTexturesReply* msg_data = + reinterpret_cast(msg.GetData()); + if (!msg_data->success) { + LOG_ERROR("Failed waiting for textures to unlock.\n"); + return false; + } + + return true; +} + +void TextureSync::CleanupForPid(base::ProcessId aProcessId) { + { + StaticMonitorAutoLock lock(gTextureLockMonitor); + std::unordered_set* lockedTextureIds = + GetLockedTextureIdsForProcess(aProcessId); + lockedTextureIds->clear(); + } + gTextureLockMonitor.NotifyAll(); +} + +} // namespace layers + +} // namespace mozilla diff --git a/gfx/layers/TextureSync.h b/gfx/layers/TextureSync.h new file mode 100644 index 0000000000..a48646fcee --- /dev/null +++ b/gfx/layers/TextureSync.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_LAYERS_TEXTURESYNC_H +#define MOZILLA_LAYERS_TEXTURESYNC_H + +#include "base/process.h" + +#include "nsTArray.h" +#include "mozilla/layers/TextureSourceProvider.h" + +#include "SharedMemory.h" + +class MachReceiveMessage; + +namespace mozilla { +namespace ipc { +struct MemoryPorts; +} // namespace ipc + +namespace layers { + +class TextureSync { + public: + static void RegisterTextureSourceProvider( + layers::TextureSourceProvider* aTextureSourceProvider); + static void UnregisterTextureSourceProvider( + layers::TextureSourceProvider* aTextureSourceProvider); + static void DispatchCheckTexturesForUnlock(); + static void HandleWaitForTexturesMessage(MachReceiveMessage* rmsg, + ipc::MemoryPorts* ports); + static void UpdateTextureLocks(base::ProcessId aProcessId); + static bool WaitForTextures(base::ProcessId aProcessId, + const nsTArray& aTextureIds); + static void SetTexturesLocked(base::ProcessId aProcessId, + const nsTArray& aTextureIds); + static void SetTexturesUnlocked(base::ProcessId aProcessId, + const nsTArray& aTextureIds); + static void Shutdown(); + static void CleanupForPid(base::ProcessId aProcessId); +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/TextureWrapperImage.cpp b/gfx/layers/TextureWrapperImage.cpp new file mode 100644 index 0000000000..324bc31ef9 --- /dev/null +++ b/gfx/layers/TextureWrapperImage.cpp @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "TextureWrapperImage.h" + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +TextureWrapperImage::TextureWrapperImage(TextureClient* aClient, + const IntRect& aPictureRect) + : Image(nullptr, ImageFormat::TEXTURE_WRAPPER), + mPictureRect(aPictureRect), + mTextureClient(aClient) {} + +TextureWrapperImage::~TextureWrapperImage() = default; + +gfx::IntSize TextureWrapperImage::GetSize() const { + return mTextureClient->GetSize(); +} + +gfx::IntRect TextureWrapperImage::GetPictureRect() const { + return mPictureRect; +} + +already_AddRefed TextureWrapperImage::GetAsSourceSurface() { + TextureClientAutoLock autoLock(mTextureClient, OpenMode::OPEN_READ); + if (!autoLock.Succeeded()) { + return nullptr; + } + + RefPtr dt = mTextureClient->BorrowDrawTarget(); + if (!dt) { + return nullptr; + } + + return dt->Snapshot(); +} + +TextureClient* TextureWrapperImage::GetTextureClient( + KnowsCompositor* aKnowsCompositor) { + return mTextureClient; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/TextureWrapperImage.h b/gfx/layers/TextureWrapperImage.h new file mode 100644 index 0000000000..8da62626d0 --- /dev/null +++ b/gfx/layers/TextureWrapperImage.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_LAYERS_TEXTUREWRAPPINGIMAGE_H_ +#define GFX_LAYERS_TEXTUREWRAPPINGIMAGE_H_ + +#include "mozilla/RefPtr.h" +#include "ImageContainer.h" +#include "mozilla/layers/TextureClient.h" + +namespace mozilla { +namespace layers { + +// Wraps a TextureClient into an Image. This may only be used on the main +// thread, and only with TextureClients that support BorrowDrawTarget(). +class TextureWrapperImage final : public Image { + public: + TextureWrapperImage(TextureClient* aClient, const gfx::IntRect& aPictureRect); + virtual ~TextureWrapperImage(); + + gfx::IntSize GetSize() const override; + gfx::IntRect GetPictureRect() const override; + already_AddRefed GetAsSourceSurface() override; + TextureClient* GetTextureClient(KnowsCompositor* aKnowsCompositor) override; + + private: + gfx::IntRect mPictureRect; + RefPtr mTextureClient; +}; + +} // namespace layers +} // namespace mozilla + +#endif // GFX_LAYERS_TEXTUREWRAPPINGIMAGE_H_ diff --git a/gfx/layers/TiledLayerBuffer.h b/gfx/layers/TiledLayerBuffer.h new file mode 100644 index 0000000000..8b2a7b900e --- /dev/null +++ b/gfx/layers/TiledLayerBuffer.h @@ -0,0 +1,214 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_TILEDLAYERBUFFER_H +#define GFX_TILEDLAYERBUFFER_H + +// Debug defines +//#define GFX_TILEDLAYER_DEBUG_OVERLAY +//#define GFX_TILEDLAYER_PREF_WARNINGS +//#define GFX_TILEDLAYER_RETAINING_LOG + +#include // for uint16_t, uint32_t +#include // for int32_t +#include +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/gfx/Logging.h" // for gfxCriticalError +#include "mozilla/layers/LayersTypes.h" // for TextureDumpMode +#include "nsDebug.h" // for NS_ASSERTION +#include "nsPoint.h" // for nsIntPoint +#include "nsRect.h" // for mozilla::gfx::IntRect +#include "nsRegion.h" // for nsIntRegion +#include "nsTArray.h" // for nsTArray + +namespace mozilla { + +struct TileCoordUnit {}; +template <> +struct IsPixel : std::true_type {}; + +namespace layers { + +// You can enable all the TILING_LOG print statements by +// changing the 0 to a 1 in the following #define. +#define ENABLE_TILING_LOG 0 + +#if ENABLE_TILING_LOG +# define TILING_LOG(...) printf_stderr(__VA_ARGS__); +#else +# define TILING_LOG(...) +#endif + +// Normal integer division truncates towards zero, +// we instead want to floor to hangle negative numbers. +static inline int floor_div(int a, int b) { + int rem = a % b; + int div = a / b; + if (rem == 0) { + return div; + } else { + // If the signs are different substract 1. + int sub; + sub = a ^ b; + // The results of this shift is either 0 or -1. + sub >>= 8 * sizeof(int) - 1; + return div + sub; + } +} + +// Tiles are aligned to a grid with one of the grid points at (0,0) and other +// grid points spaced evenly in the x- and y-directions by GetTileSize() +// multiplied by mResolution. GetScaledTileSize() provides convenience for +// accessing these values. +// +// This tile buffer stores a valid region, which defines the areas that have +// up-to-date content. The contents of tiles within this region will be reused +// from paint to paint. It also stores the region that was modified in the last +// paint operation; this is useful when one tiled layer buffer shadows another +// (as in an off-main-thread-compositing scenario), so that the shadow tiled +// layer buffer can correctly reflect the updates of the master layer buffer. +// +// The associated Tile may be of any type as long as the derived class can +// validate and return tiles of that type. Tiles will be frequently copied, so +// the tile type should be a reference or some other type with an efficient +// copy constructor. +// +// The contents of the tile buffer will be rendered at the resolution specified +// in mResolution, which can be altered with SetResolution. The resolution +// should always be a factor of the tile length, to avoid tiles covering +// non-integer amounts of pixels. + +// Size and Point in number of tiles rather than in pixels +typedef gfx::IntSizeTyped TileCoordIntSize; +typedef gfx::IntPointTyped TileCoordIntPoint; + +/** + * Stores the origin and size of a tile buffer and handles switching between + * tile indices and tile coordinates. + * + * Tile coordinates in TileCoordIntPoint take the first tile offset into account + * which means that two TilesPlacement of the same layer and resolution give + * tile coordinates in the same coordinate space (useful when changing the + * offset and/or size of a tile buffer). + */ +struct TilesPlacement { + TileCoordIntPoint mFirst; + TileCoordIntSize mSize; + + TilesPlacement(int aFirstX, int aFirstY, int aRetainedWidth, + int aRetainedHeight) + : mFirst(aFirstX, aFirstY), mSize(aRetainedWidth, aRetainedHeight) {} + + int TileIndex(TileCoordIntPoint aCoord) const { + return (aCoord.x - mFirst.x) * mSize.height + aCoord.y - mFirst.y; + } + + TileCoordIntPoint TileCoord(size_t aIndex) const { + return TileCoordIntPoint(mFirst.x + aIndex / mSize.height, + mFirst.y + aIndex % mSize.height); + } + + bool HasTile(TileCoordIntPoint aCoord) const { + return aCoord.x >= mFirst.x && aCoord.x < mFirst.x + mSize.width && + aCoord.y >= mFirst.y && aCoord.y < mFirst.y + mSize.height; + } +}; + +// Given a position i, this function returns the position inside the current +// tile. +inline int GetTileStart(int i, int aTileLength) { + return (i >= 0) ? (i % aTileLength) + : ((aTileLength - (-i % aTileLength)) % aTileLength); +} + +// Rounds the given coordinate down to the nearest tile boundary. +inline int RoundDownToTileEdge(int aX, int aTileLength) { + return aX - GetTileStart(aX, aTileLength); +} + +template +class TiledLayerBuffer { + public: + TiledLayerBuffer() + : mTiles(0, 0, 0, 0), + mResolution(1), + mTileSize(mozilla::gfx::gfxVars::TileSize()) {} + + ~TiledLayerBuffer() = default; + + gfx::IntPoint GetTileOffset(TileCoordIntPoint aPosition) const { + gfx::IntSize scaledTileSize = GetScaledTileSize(); + return gfx::IntPoint(aPosition.x * scaledTileSize.width, + aPosition.y * scaledTileSize.height) + + mTileOrigin; + } + + const TilesPlacement& GetPlacement() const { return mTiles; } + + const gfx::IntSize& GetTileSize() const { return mTileSize; } + + gfx::IntSize GetScaledTileSize() const { + return gfx::IntSize::Round(gfx::Size(mTileSize) / mResolution); + } + + unsigned int GetTileCount() const { return mRetainedTiles.Length(); } + + Tile& GetTile(size_t i) { return mRetainedTiles[i]; } + + const nsIntRegion& GetValidRegion() const { return mValidRegion; } + + // Get and set draw scaling. mResolution affects the resolution at which the + // contents of the buffer are drawn. mResolution has no effect on the + // coordinate space of the valid region, but does affect the size of an + // individual tile's rect in relation to the valid region. + // Setting the resolution will invalidate the buffer. + float GetResolution() const { return mResolution; } + bool IsLowPrecision() const { return mResolution < 1; } + + void Dump(std::stringstream& aStream, const char* aPrefix, bool aDumpHtml, + TextureDumpMode aCompress); + + protected: + nsIntRegion mValidRegion; + + /** + * mRetainedTiles is a rectangular buffer of mTiles.mSize.width x + * mTiles.mSize.height stored as column major with the same origin as + * mValidRegion.GetBounds(). Any tile that does not intersect mValidRegion is + * a PlaceholderTile. Only the region intersecting with mValidRegion should be + * read from a tile, another other region is assumed to be uninitialized. The + * contents of the tiles is scaled by mResolution. + */ + nsTArray mRetainedTiles; + TilesPlacement mTiles; + float mResolution; + gfx::IntSize mTileSize; + gfx::IntPoint mTileOrigin; +}; + +template +void TiledLayerBuffer::Dump(std::stringstream& aStream, + const char* aPrefix, bool aDumpHtml, + TextureDumpMode aCompress) { + for (size_t i = 0; i < mRetainedTiles.Length(); ++i) { + const TileCoordIntPoint tileCoord = mTiles.TileCoord(i); + gfx::IntPoint tileOffset = GetTileOffset(tileCoord); + + aStream << "\n" + << aPrefix << "Tile (x=" << tileOffset.x << ", y=" << tileOffset.y + << "): "; + if (!mRetainedTiles[i].IsPlaceholderTile()) { + mRetainedTiles[i].DumpTexture(aStream, aCompress); + } else { + aStream << "empty tile"; + } + } +} + +} // namespace layers +} // namespace mozilla + +#endif // GFX_TILEDLAYERBUFFER_H diff --git a/gfx/layers/TransactionIdAllocator.h b/gfx/layers/TransactionIdAllocator.h new file mode 100644 index 0000000000..c9f545470f --- /dev/null +++ b/gfx/layers/TransactionIdAllocator.h @@ -0,0 +1,93 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_TRANSACTION_ID_ALLOCATOR_H +#define GFX_TRANSACTION_ID_ALLOCATOR_H + +#include "nsISupportsImpl.h" +#include "mozilla/layers/LayersTypes.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/VsyncDispatcher.h" + +namespace mozilla { +namespace layers { + +class TransactionIdAllocator { + protected: + virtual ~TransactionIdAllocator() = default; + + public: + NS_INLINE_DECL_REFCOUNTING(TransactionIdAllocator) + + /** + * Allocate a unique id number for the current refresh tick, can + * only be called while IsInRefresh(). + * + * If too many id's are allocated without being returned then + * the refresh driver will suspend until they catch up. This + * "throttling" behaviour can be skipped by passing aThrottle=false. + * Otherwise call sites should generally be passing aThrottle=true. + */ + virtual TransactionId GetTransactionId(bool aThrottle) = 0; + + /** + * Return the transaction id that for the last non-revoked transaction. + * This allows the caller to tell whether a composite was triggered by + * a paint that occurred after a call to TransactionId(). + */ + virtual TransactionId LastTransactionId() const = 0; + + /** + * Notify that all work (including asynchronous composites) + * for a given transaction id has been completed. + * + * If the refresh driver has been suspended because + * of having too many outstanding id's, then this may + * resume it. + */ + virtual void NotifyTransactionCompleted(TransactionId aTransactionId) = 0; + + /** + * Revoke a transaction id that isn't needed to track + * completion of asynchronous work. This is similar + * to NotifyTransactionCompleted except avoids + * return ordering issues. + */ + virtual void RevokeTransactionId(TransactionId aTransactionId) = 0; + + /** + * Stop waiting for pending transactions, if any. + * + * This is used when ClientLayerManager is assigning to another refresh + * driver, and the current refresh driver may never receive transaction + * completed notifications. + */ + virtual void ClearPendingTransactions() = 0; + + /** + * Transaction id is usually initialized as 0, however when ClientLayerManager + * switches to another refresh driver, completed transactions of the previous + * refresh driver could be delivered and confuse the newly adopted refresh + * driver. To prevent this situation, use this function to reset transaction + * id to the last transaction id from previous refresh driver, so that all + * completed transactions of previous refresh driver will be ignored. + */ + virtual void ResetInitialTransactionId(TransactionId aTransactionId) = 0; + + /** + * Get the start time of the current refresh tick. + */ + virtual mozilla::TimeStamp GetTransactionStart() = 0; + + virtual VsyncId GetVsyncId() = 0; + + virtual mozilla::TimeStamp GetVsyncStart() = 0; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_TRANSACTION_ID_ALLOCATOR_H */ diff --git a/gfx/layers/TreeTraversal.h b/gfx/layers/TreeTraversal.h new file mode 100644 index 0000000000..4c02a4e5b5 --- /dev/null +++ b/gfx/layers/TreeTraversal.h @@ -0,0 +1,276 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_TreeTraversal_h +#define mozilla_layers_TreeTraversal_h + +#include +#include + +namespace mozilla { +namespace layers { + +/* + * Returned by |aPostAction| and |aPreAction| in ForEachNode, indicates + * the behavior to follow either action: + * + * TraversalFlag::Skip - the node's children are not traversed. If this + * flag is returned by |aPreAction|, |aPostAction| is skipped for the + * current node, as well. + * TraversalFlag::Continue - traversal continues normally. + * TraversalFlag::Abort - traversal stops immediately. + */ +enum class TraversalFlag { Skip, Continue, Abort }; + +/* + * Iterator types to be specified in traversal function calls: + * + * ForwardIterator - for nodes using GetFirstChild() and GetNextSibling() + * ReverseIterator - for nodes using GetLastChild() and GetPrevSibling() + */ +class ForwardIterator { + public: + template + static Node* FirstChild(Node* n) { + return n->GetFirstChild(); + } + template + static Node* NextSibling(Node* n) { + return n->GetNextSibling(); + } + template + static Node FirstChild(Node n) { + return n.GetFirstChild(); + } + template + static Node NextSibling(Node n) { + return n.GetNextSibling(); + } +}; +class ReverseIterator { + public: + template + static Node* FirstChild(Node* n) { + return n->GetLastChild(); + } + template + static Node* NextSibling(Node* n) { + return n->GetPrevSibling(); + } + template + static Node FirstChild(Node n) { + return n.GetLastChild(); + } + template + static Node NextSibling(Node n) { + return n.GetPrevSibling(); + } +}; + +/* + * Do a depth-first traversal of the tree rooted at |aRoot|, performing + * |aPreAction| before traversal of children and |aPostAction| after. + * + * Returns true if traversal aborted, false if continued normally. If + * TraversalFlag::Skip is returned in |aPreAction|, then |aPostAction| + * is not performed. + * + * |Iterator| should have static methods named NextSibling() and FirstChild() + * that accept an argument of type Node. For convenience, classes + * |ForwardIterator| and |ReverseIterator| are provided which implement these + * methods as GetNextSibling()/GetFirstChild() and + * GetPrevSibling()/GetLastChild(), respectively. + */ +template +static auto ForEachNode(Node aRoot, const PreAction& aPreAction, + const PostAction& aPostAction) + -> std::enable_if_t< + std::is_same_v && + std::is_same_v, + bool> { + if (!aRoot) { + return false; + } + + TraversalFlag result = aPreAction(aRoot); + + if (result == TraversalFlag::Abort) { + return true; + } + + if (result == TraversalFlag::Continue) { + for (Node child = Iterator::FirstChild(aRoot); child; + child = Iterator::NextSibling(child)) { + bool abort = ForEachNode(child, aPreAction, aPostAction); + if (abort) { + return true; + } + } + + result = aPostAction(aRoot); + + if (result == TraversalFlag::Abort) { + return true; + } + } + + return false; +} + +/* + * Do a depth-first traversal of the tree rooted at |aRoot|, performing + * |aPreAction| before traversal of children and |aPostAction| after. + */ +template +static auto ForEachNode(Node aRoot, const PreAction& aPreAction, + const PostAction& aPostAction) + -> std::enable_if_t && + std::is_same_v, + void> { + if (!aRoot) { + return; + } + + aPreAction(aRoot); + + for (Node child = Iterator::FirstChild(aRoot); child; + child = Iterator::NextSibling(child)) { + ForEachNode(child, aPreAction, aPostAction); + } + + aPostAction(aRoot); +} + +/* + * ForEachNode pre-order traversal, using TraversalFlag. + */ +template +auto ForEachNode(Node aRoot, const PreAction& aPreAction) -> std::enable_if_t< + std::is_same_v, bool> { + return ForEachNode( + aRoot, aPreAction, [](Node aNode) { return TraversalFlag::Continue; }); +} + +/* + * ForEachNode pre-order, not using TraversalFlag. + */ +template +auto ForEachNode(Node aRoot, const PreAction& aPreAction) + -> std::enable_if_t, + void> { + ForEachNode(aRoot, aPreAction, [](Node aNode) {}); +} + +/* + * ForEachNode post-order traversal, using TraversalFlag. + */ +template +auto ForEachNodePostOrder(Node aRoot, const PostAction& aPostAction) + -> std::enable_if_t< + std::is_same_v, bool> { + return ForEachNode( + aRoot, [](Node aNode) { return TraversalFlag::Continue; }, aPostAction); +} + +/* + * ForEachNode post-order, not using TraversalFlag. + */ +template +auto ForEachNodePostOrder(Node aRoot, const PostAction& aPostAction) + -> std::enable_if_t, + void> { + ForEachNode( + aRoot, [](Node aNode) {}, aPostAction); +} + +/* + * Do a breadth-first search of the tree rooted at |aRoot|, and return the + * first visited node that satisfies |aCondition|, or nullptr if no such node + * was found. + * + * |Iterator| and |Node| have all the same requirements seen in ForEachNode()'s + * definition, but in addition to those, |Node| must be able to express a null + * value, returned from Node() + */ +template +Node BreadthFirstSearch(Node aRoot, const Condition& aCondition) { + if (!aRoot) { + return Node(); + } + + std::queue queue; + queue.push(aRoot); + while (!queue.empty()) { + Node node = queue.front(); + queue.pop(); + + if (aCondition(node)) { + return node; + } + + for (Node child = Iterator::FirstChild(node); child; + child = Iterator::NextSibling(child)) { + queue.push(child); + } + } + + return Node(); +} + +/* + * Do a pre-order, depth-first search of the tree rooted at |aRoot|, and + * return the first visited node that satisfies |aCondition|, or nullptr + * if no such node was found. + * + * |Iterator| and |Node| have all the same requirements seen in ForEachNode()'s + * definition, but in addition to those, |Node| must be able to express a null + * value, returned from Node(). + */ +template +Node DepthFirstSearch(Node aRoot, const Condition& aCondition) { + Node result = Node(); + + ForEachNode(aRoot, [&aCondition, &result](Node aNode) { + if (aCondition(aNode)) { + result = aNode; + return TraversalFlag::Abort; + } + + return TraversalFlag::Continue; + }); + + return result; +} + +/* + * Perform a post-order, depth-first search starting at aRoot. + * + * |Iterator| and |Node| have all the same requirements seen in ForEachNode()'s + * definition, but in addition to those, |Node| must be able to express a null + * value, returned from Node(). + */ +template +Node DepthFirstSearchPostOrder(Node aRoot, const Condition& aCondition) { + Node result = Node(); + + ForEachNodePostOrder(aRoot, [&aCondition, &result](Node aNode) { + if (aCondition(aNode)) { + result = aNode; + return TraversalFlag::Abort; + } + + return TraversalFlag::Continue; + }); + + return result; +} + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_TreeTraversal_h diff --git a/gfx/layers/UpdateImageHelper.h b/gfx/layers/UpdateImageHelper.h new file mode 100644 index 0000000000..b0d07c85fc --- /dev/null +++ b/gfx/layers/UpdateImageHelper.h @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_UPDATEIMAGEHELPER_H +#define GFX_UPDATEIMAGEHELPER_H + +#include "mozilla/layers/CompositorTypes.h" +#include "mozilla/layers/ImageClient.h" +#include "mozilla/layers/TextureClient.h" +#include "mozilla/layers/TextureClientRecycleAllocator.h" +#include "mozilla/layers/TextureWrapperImage.h" +#include "mozilla/gfx/Types.h" + +namespace mozilla { +namespace layers { + +class UpdateImageHelper { + public: + UpdateImageHelper(ImageContainer* aImageContainer, ImageClient* aImageClient, + gfx::IntSize aImageSize, gfx::SurfaceFormat aFormat) + : mImageContainer(aImageContainer), + mImageClient(aImageClient), + mImageSize(aImageSize), + mIsLocked(false) { + mTexture = mImageClient->GetTextureClientRecycler()->CreateOrRecycle( + aFormat, mImageSize, BackendSelector::Content, TextureFlags::DEFAULT); + if (!mTexture) { + return; + } + + mIsLocked = mTexture->Lock(OpenMode::OPEN_WRITE_ONLY); + if (!mIsLocked) { + return; + } + } + + ~UpdateImageHelper() { + if (mIsLocked) { + mTexture->Unlock(); + mIsLocked = false; + } + } + + already_AddRefed GetDrawTarget() { + RefPtr target; + if (mTexture) { + target = mTexture->BorrowDrawTarget(); + } + return target.forget(); + } + + bool UpdateImage() { + if (!mTexture) { + return false; + } + + if (mIsLocked) { + mTexture->Unlock(); + mIsLocked = false; + } + + RefPtr image = new TextureWrapperImage( + mTexture, gfx::IntRect(gfx::IntPoint(0, 0), mImageSize)); + mImageContainer->SetCurrentImageInTransaction(image); + return mImageClient->UpdateImage(mImageContainer, /* unused */ 0); + } + + private: + RefPtr mImageContainer; + RefPtr mImageClient; + gfx::IntSize mImageSize; + RefPtr mTexture; + bool mIsLocked; +}; + +} // namespace layers +} // namespace mozilla + +#endif // GFX_UPDATEIMAGEHELPER_H diff --git a/gfx/layers/ZoomConstraints.cpp b/gfx/layers/ZoomConstraints.cpp new file mode 100644 index 0000000000..97754d1e11 --- /dev/null +++ b/gfx/layers/ZoomConstraints.cpp @@ -0,0 +1,23 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ZoomConstraints.h" + +#include +#include "nsPrintfCString.h" + +namespace mozilla { +namespace layers { + +std::ostream& operator<<(std::ostream& aStream, const ZoomConstraints& z) { + return aStream << nsPrintfCString("{ z=%d, dt=%d, min=%f, max=%f }", + z.mAllowZoom, z.mAllowDoubleTapZoom, + z.mMinZoom.scale, z.mMaxZoom.scale) + .get(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ZoomConstraints.h b/gfx/layers/ZoomConstraints.h new file mode 100644 index 0000000000..1050f37332 --- /dev/null +++ b/gfx/layers/ZoomConstraints.h @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_ZOOMCONSTRAINTS_H +#define GFX_ZOOMCONSTRAINTS_H + +#include + +#include "Units.h" // for CSSRect, CSSPixel, etc +#include "mozilla/Maybe.h" +#include "mozilla/layers/ScrollableLayerGuid.h" // for ScrollableLayerGuid + +namespace mozilla { +namespace layers { + +struct ZoomConstraints { + bool mAllowZoom; + bool mAllowDoubleTapZoom; + CSSToParentLayerScale mMinZoom; + CSSToParentLayerScale mMaxZoom; + + ZoomConstraints() : mAllowZoom(true), mAllowDoubleTapZoom(true) { + MOZ_COUNT_CTOR(ZoomConstraints); + } + + ZoomConstraints(bool aAllowZoom, bool aAllowDoubleTapZoom, + const CSSToParentLayerScale& aMinZoom, + const CSSToParentLayerScale& aMaxZoom) + : mAllowZoom(aAllowZoom), + mAllowDoubleTapZoom(aAllowDoubleTapZoom), + mMinZoom(aMinZoom), + mMaxZoom(aMaxZoom) { + MOZ_COUNT_CTOR(ZoomConstraints); + } + + ZoomConstraints(const ZoomConstraints& other) + : mAllowZoom(other.mAllowZoom), + mAllowDoubleTapZoom(other.mAllowDoubleTapZoom), + mMinZoom(other.mMinZoom), + mMaxZoom(other.mMaxZoom) { + MOZ_COUNT_CTOR(ZoomConstraints); + } + + MOZ_COUNTED_DTOR(ZoomConstraints) + + bool operator==(const ZoomConstraints& other) const { + return mAllowZoom == other.mAllowZoom && + mAllowDoubleTapZoom == other.mAllowDoubleTapZoom && + mMinZoom == other.mMinZoom && mMaxZoom == other.mMaxZoom; + } + + bool operator!=(const ZoomConstraints& other) const { + return !(*this == other); + } + + friend std::ostream& operator<<(std::ostream& aStream, + const ZoomConstraints& z); +}; + +typedef Maybe MaybeZoomConstraints; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_ZOOMCONSTRAINTS_H */ diff --git a/gfx/layers/apz/public/APZInputBridge.h b/gfx/layers/apz/public/APZInputBridge.h new file mode 100644 index 0000000000..b5cc8856a2 --- /dev/null +++ b/gfx/layers/apz/public/APZInputBridge.h @@ -0,0 +1,167 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_APZInputBridge_h +#define mozilla_layers_APZInputBridge_h + +#include "mozilla/EventForwards.h" // for WidgetInputEvent, nsEventStatus +#include "mozilla/layers/APZPublicUtils.h" // for APZWheelAction +#include "mozilla/layers/ScrollableLayerGuid.h" // for ScrollableLayerGuid +#include "Units.h" // for LayoutDeviceIntPoint + +namespace mozilla { + +class InputData; + +namespace layers { + +class APZInputBridgeParent; +struct ScrollableLayerGuid; + +enum class APZHandledResult : uint8_t { + Unhandled = 0, // we know for sure that the event will not be handled + // by either the root APZC or others + HandledByRoot = 1, // we know for sure that the event will be handled + // by the root content APZC + HandledByContent = 2, // we know for sure it will be handled by a non-root + // APZC or by an event listener using preventDefault() + // in a document + Invalid = 3, + Last = Invalid +}; + +/** + * Represents the outcome of APZ receiving and processing an input event. + * This is returned from APZInputBridge::ReceiveInputEvent() and related APIs. + */ +struct APZEventResult { + /** + * Creates a default result with a status of eIgnore, no block ID, and empty + * target guid. + */ + APZEventResult(); + + /** + * A status flag indicated how APZ handled the event. + * The interpretation of each value is as follows: + * + * nsEventStatus_eConsumeNoDefault is returned to indicate the + * APZ is consuming this event and the caller should discard the event with + * extreme prejudice. The exact scenarios under which this is returned is + * implementation-dependent and may vary. + * nsEventStatus_eIgnore is returned to indicate that the APZ code didn't + * use this event. This might be because it was directed at a point on + * the screen where there was no APZ, or because the thing the user was + * trying to do was not allowed. (For example, attempting to pan a + * non-pannable document). + * nsEventStatus_eConsumeDoDefault is returned to indicate that the APZ + * code may have used this event to do some user-visible thing. Note that + * in some cases CONSUMED is returned even if the event was NOT used. This + * is because we cannot always know at the time of event delivery whether + * the event will be used or not. So we err on the side of sending + * CONSUMED when we are uncertain. + */ + nsEventStatus mStatus; + /** + * The guid of the APZC initially targeted by this event. + * This will usually be the APZC that handles the event, but in cases + * where the event is dispatched to content, it may end up being + * handled by a different APZC. + */ + ScrollableLayerGuid mTargetGuid; + /** + * This is: + * - set to HandledByRoot if we know for sure that the event will be handled + * by the root content APZC; + * - set to HandledByContent if we know for sure it will not be; + * - left empty if we are unsure. + */ + Maybe mHandledResult; + /** + * If this event started or was added to an input block, the id of that + * input block, otherwise InputBlockState::NO_BLOCK_ID. + */ + uint64_t mInputBlockId; +}; + +/** + * This class lives in the main process, and is accessed via the controller + * thread (which is the process main thread for desktop, and the Java UI + * thread for Android). This class exposes a synchronous API to deliver + * incoming input events to APZ and modify them in-place to unapply the APZ + * async transform. If there is a GPU process, then this class does sync IPC + * calls over to the GPU process in order to accomplish this. Otherwise, + * APZCTreeManager overrides and implements these methods directly. + */ +class APZInputBridge { + public: + /** + * General handler for incoming input events. Manipulates the frame metrics + * based on what type of input it is. For example, a PinchGestureEvent will + * cause scaling. This should only be called externally to this class, and + * must be called on the controller thread. + * + * This function transforms |aEvent| to have its coordinates in DOM space. + * This is so that the event can be passed through the DOM and content can + * handle them. The event may need to be converted to a WidgetInputEvent + * by the caller if it wants to do this. + * + * @param aEvent input event object; is modified in-place + * @return The result of processing the event. Refer to the documentation of + * APZEventResult and its field. + */ + virtual APZEventResult ReceiveInputEvent(InputData& aEvent) = 0; + + /** + * WidgetInputEvent handler. Transforms |aEvent| (which is assumed to be an + * already-existing instance of an WidgetInputEvent which may be an + * WidgetTouchEvent) to have its coordinates in DOM space. This is so that the + * event can be passed through the DOM and content can handle them. + * + * NOTE: Be careful of invoking the WidgetInputEvent variant. This can only be + * called on the main thread. See widget/InputData.h for more information on + * why we have InputData and WidgetInputEvent separated. If this function is + * used, the controller thread must be the main thread, or undefined behaviour + * may occur. + * NOTE: On unix, mouse events are treated as touch and are forwarded + * to the appropriate apz as such. + * + * See documentation for other ReceiveInputEvent above. + */ + APZEventResult ReceiveInputEvent(WidgetInputEvent& aEvent); + + // Returns the kind of wheel event action, if any, that will be (or was) + // performed by APZ. If this returns true, the event must not perform a + // synchronous scroll. + // + // Even if this returns Nothing(), all wheel events in APZ-aware widgets must + // be sent through APZ so they are transformed correctly for BrowserParent. + static Maybe ActionForWheelEvent(WidgetWheelEvent* aEvent); + + protected: + friend class APZInputBridgeParent; + + // Methods to help process WidgetInputEvents (or manage conversion to/from + // InputData) + + virtual void ProcessUnhandledEvent(LayoutDeviceIntPoint* aRefPoint, + ScrollableLayerGuid* aOutTargetGuid, + uint64_t* aOutFocusSequenceNumber, + LayersId* aOutLayersId) = 0; + + virtual void UpdateWheelTransaction(LayoutDeviceIntPoint aRefPoint, + EventMessage aEventMessage) = 0; + + virtual ~APZInputBridge() = default; +}; + +std::ostream& operator<<(std::ostream& aOut, + const APZHandledResult& aHandledResult); + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_APZInputBridge_h diff --git a/gfx/layers/apz/public/APZPublicUtils.h b/gfx/layers/apz/public/APZPublicUtils.h new file mode 100644 index 0000000000..671f3186f4 --- /dev/null +++ b/gfx/layers/apz/public/APZPublicUtils.h @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_APZPublicUtils_h +#define mozilla_layers_APZPublicUtils_h + +// This file is for APZ-related utilities that need to be consumed from outside +// of gfx/layers. For internal utilities, prefer APZUtils.h. + +#include +#include +#include "ScrollAnimationBezierPhysics.h" +#include "Units.h" +#include "mozilla/DefineEnum.h" +#include "mozilla/ScrollOrigin.h" +#include "mozilla/gfx/Point.h" + +namespace mozilla { + +namespace layers { + +struct FrameMetrics; + +// clang-format off +MOZ_DEFINE_ENUM_CLASS_WITH_BASE(APZWheelAction, uint8_t, ( + Scroll, + PinchZoom +)) +// clang-format on + +namespace apz { + +/** + * Initializes the global state used in AsyncPanZoomController. + * This is normally called when it is first needed in the constructor + * of APZCTreeManager, but can be called manually to force it to be + * initialized earlier. + */ +void InitializeGlobalState(); + +/** + * See AsyncPanZoomController::CalculatePendingDisplayPort. This + * function simply delegates to that one, so that non-layers code + * never needs to include AsyncPanZoomController.h + */ +const ScreenMargin CalculatePendingDisplayPort( + const FrameMetrics& aFrameMetrics, const ParentLayerPoint& aVelocity); + +/** + * Returns a width and height multiplier, each of which is a power of two + * between 1 and 8 inclusive. The multiplier is chosen based on the provided + * base size, such that multiplier is larger when the base size is larger. + * The exact details are somewhat arbitrary and tuned by hand. + * This function is intended to only be used with WebRender, because that is + * the codepath that wants to use a larger displayport alignment, because + * moving the displayport is relatively expensive with WebRender. + */ +gfx::IntSize GetDisplayportAlignmentMultiplier(const ScreenSize& aBaseSize); + +/** + * This computes the min/max values to use for the mousewheel animation + * duration. Normally this just comes from prefs but we are doing a gradual + * migration of users from old values to new values so this encapsulates some + * of that behaviour. Values are in milliseconds, same as the + * general.smoothScroll.mouseWheel.duration* prefs. + */ +std::pair GetMouseWheelAnimationDurations(); + +/** + * Calculate the physics parameters for smooth scroll animations for the + * given origin, based on pref values. + */ +ScrollAnimationBezierPhysicsSettings ComputeBezierAnimationSettingsForOrigin( + ScrollOrigin aOrigin); + +} // namespace apz + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_APZPublicUtils_h diff --git a/gfx/layers/apz/public/APZSampler.h b/gfx/layers/apz/public/APZSampler.h new file mode 100644 index 0000000000..552e5c4a14 --- /dev/null +++ b/gfx/layers/apz/public/APZSampler.h @@ -0,0 +1,174 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_APZSampler_h +#define mozilla_layers_APZSampler_h + +#include + +#include "base/platform_thread.h" // for PlatformThreadId +#include "mozilla/layers/APZUtils.h" +#include "mozilla/layers/SampleTime.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/webrender/WebRenderTypes.h" +#include "Units.h" +#include "VsyncSource.h" + +namespace mozilla { + +class TimeStamp; + +namespace wr { +struct Transaction; +class TransactionWrapper; +struct WrWindowId; +} // namespace wr + +namespace layers { + +class APZCTreeManager; +class LayerMetricsWrapper; +struct ScrollbarData; + +/** + * This interface exposes APZ methods related to "sampling" (i.e. reading the + * async transforms produced by APZ). These methods should all be called on + * the sampler thread. + */ +class APZSampler { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(APZSampler) + + public: + APZSampler(const RefPtr& aApz, bool aIsUsingWebRender); + + // Whoever creates this sampler is responsible for calling Destroy() on it + // before releasing the owning refptr. + void Destroy(); + + void SetWebRenderWindowId(const wr::WindowId& aWindowId); + + /** + * This function is invoked from rust on the render backend thread when it + * is created. It effectively tells the APZSampler "the current thread is + * the sampler thread for this window id" and allows APZSampler to remember + * which thread it is. + */ + static void SetSamplerThread(const wr::WrWindowId& aWindowId); + static void SampleForWebRender(const wr::WrWindowId& aWindowId, + const uint64_t* aGeneratedFrameId, + wr::Transaction* aTransaction); + + void SetSampleTime(const SampleTime& aSampleTime); + void SampleForWebRender(const Maybe& aGeneratedFrameId, + wr::TransactionWrapper& aTxn); + + bool AdvanceAnimations(const SampleTime& aSampleTime); + + /** + * Compute the updated shadow transform for a scroll thumb layer that + * reflects async scrolling of the associated scroll frame. + * + * Refer to APZCTreeManager::ComputeTransformForScrollThumb for the + * description of parameters. The only difference is that this function takes + * |aContent| instead of |aApzc| and |aMetrics|; aContent is the + * LayerMetricsWrapper corresponding to the scroll frame that is scrolled by + * the scroll thumb, and so the APZC and metrics can be obtained from + * |aContent|. + */ + LayerToParentLayerMatrix4x4 ComputeTransformForScrollThumb( + const LayerToParentLayerMatrix4x4& aCurrentTransform, + const LayerMetricsWrapper& aContent, const ScrollbarData& aThumbData, + bool aScrollbarIsDescendant, + AsyncTransformComponentMatrix* aOutClipTransform); + + CSSRect GetCurrentAsyncLayoutViewport(const LayerMetricsWrapper& aLayer); + ParentLayerPoint GetCurrentAsyncScrollOffset( + const LayerMetricsWrapper& aLayer); + AsyncTransform GetCurrentAsyncTransform(const LayerMetricsWrapper& aLayer, + AsyncTransformComponents aComponents); + AsyncTransformComponentMatrix GetOverscrollTransform( + const LayerMetricsWrapper& aLayer); + AsyncTransformComponentMatrix GetCurrentAsyncTransformWithOverscroll( + const LayerMetricsWrapper& aLayer); + Maybe NotifyScrollSampling( + const LayerMetricsWrapper& aLayer); + + void MarkAsyncTransformAppliedToContent(const LayerMetricsWrapper& aLayer); + bool HasUnusedAsyncTransform(const LayerMetricsWrapper& aLayer); + + /** + * Similar to above GetCurrentAsyncTransform, but get the current transform + * with LayersId and ViewID. + * NOTE: This function should NOT be called on the compositor thread. + */ + AsyncTransform GetCurrentAsyncTransform( + const LayersId& aLayersId, const ScrollableLayerGuid::ViewID& aScrollId, + AsyncTransformComponents aComponents) const; + + /** + * Returns the composition bounds of the APZC correspoinding to the pair of + * |aLayersId| and |aScrollId|. + */ + ParentLayerRect GetCompositionBounds( + const LayersId& aLayersId, + const ScrollableLayerGuid::ViewID& aScrollId) const; + + ScrollableLayerGuid GetGuid(const LayerMetricsWrapper& aLayer); + + GeckoViewMetrics GetGeckoViewMetrics(const LayerMetricsWrapper& aLayer) const; + + ScreenMargin GetGeckoFixedLayerMargins() const; + + /** + * This can be used to assert that the current thread is the + * sampler thread (which samples the async transform). + * This does nothing if thread assertions are disabled. + */ + void AssertOnSamplerThread() const; + + /** + * Returns true if currently on the APZSampler's "sampler thread". + */ + bool IsSamplerThread() const; + + protected: + virtual ~APZSampler(); + + static already_AddRefed GetSampler( + const wr::WrWindowId& aWindowId); + + private: + RefPtr mApz; + bool mIsUsingWebRender; + + // Used to manage the mapping from a WR window id to APZSampler. These are + // only used if WebRender is enabled. Both sWindowIdMap and mWindowId should + // only be used while holding the sWindowIdLock. Note that we use a + // StaticAutoPtr wrapper on sWindowIdMap to avoid a static initializer for the + // unordered_map. This also avoids the initializer/memory allocation in cases + // where we're not using WebRender. + static StaticMutex sWindowIdLock; + static StaticAutoPtr>> + sWindowIdMap; + Maybe mWindowId; + + // Lock used to protected mSamplerThreadId + mutable Mutex mThreadIdLock; + // If WebRender is enabled, this holds the thread id of the render backend + // thread (which is the sampler thread) for the compositor associated with + // this APZSampler instance. + Maybe mSamplerThreadId; + + Mutex mSampleTimeLock; + // Can only be accessed or modified while holding mSampleTimeLock. + SampleTime mSampleTime; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_APZSampler_h diff --git a/gfx/layers/apz/public/APZUpdater.h b/gfx/layers/apz/public/APZUpdater.h new file mode 100644 index 0000000000..20e3ff2c34 --- /dev/null +++ b/gfx/layers/apz/public/APZUpdater.h @@ -0,0 +1,235 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_APZUpdater_h +#define mozilla_layers_APZUpdater_h + +#include +#include + +#include "base/platform_thread.h" // for PlatformThreadId +#include "LayersTypes.h" +#include "mozilla/layers/APZTestData.h" +#include "mozilla/layers/WebRenderScrollData.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/webrender/WebRenderTypes.h" +#include "nsThreadUtils.h" +#include "Units.h" + +namespace mozilla { + +namespace layers { + +class APZCTreeManager; +class FocusTarget; +class Layer; +class WebRenderScrollData; + +/** + * This interface is used to send updates or otherwise mutate APZ internal + * state. These functions is usually called from the compositor thread in + * response to IPC messages. The method implementations internally redispatch + * themselves to the updater thread in the case where the compositor thread + * is not the updater thread. + */ +class APZUpdater { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(APZUpdater) + + public: + APZUpdater(const RefPtr& aApz, bool aIsUsingWebRender); + + bool HasTreeManager(const RefPtr& aApz); + void SetWebRenderWindowId(const wr::WindowId& aWindowId); + + /** + * This function is invoked from rust on the scene builder thread when it + * is created. It effectively tells the APZUpdater "the current thread is + * the updater thread for this window id" and allows APZUpdater to remember + * which thread it is. + */ + static void SetUpdaterThread(const wr::WrWindowId& aWindowId); + static void PrepareForSceneSwap(const wr::WrWindowId& aWindowId); + static void CompleteSceneSwap(const wr::WrWindowId& aWindowId, + const wr::WrPipelineInfo& aInfo); + static void ProcessPendingTasks(const wr::WrWindowId& aWindowId); + + void ClearTree(LayersId aRootLayersId); + void UpdateFocusState(LayersId aRootLayerTreeId, + LayersId aOriginatingLayersId, + const FocusTarget& aFocusTarget); + void UpdateHitTestingTree(Layer* aRoot, bool aIsFirstPaint, + LayersId aOriginatingLayersId, + uint32_t aPaintSequenceNumber); + /** + * This should be called (in the WR-enabled case) when the compositor receives + * a new WebRenderScrollData for a layers id. The |aScrollData| parameter is + * the scroll data for |aOriginatingLayersId| and |aEpoch| is the + * corresponding epoch for the transaction that transferred the scroll data. + * This function will store the new scroll data and update the focus state and + * hit-testing tree. + */ + void UpdateScrollDataAndTreeState(LayersId aRootLayerTreeId, + LayersId aOriginatingLayersId, + const wr::Epoch& aEpoch, + WebRenderScrollData&& aScrollData); + /** + * This is called in the WR-enabled case when we get an empty transaction that + * has some scroll offset updates (from paint-skipped scrolling on the content + * side). This function will update the stored scroll offsets and the + * hit-testing tree. + */ + void UpdateScrollOffsets(LayersId aRootLayerTreeId, + LayersId aOriginatingLayersId, + ScrollUpdatesMap&& aUpdates, + uint32_t aPaintSequenceNumber); + + void NotifyLayerTreeAdopted(LayersId aLayersId, + const RefPtr& aOldUpdater); + void NotifyLayerTreeRemoved(LayersId aLayersId); + + bool GetAPZTestData(LayersId aLayersId, APZTestData* aOutData); + + void SetTestAsyncScrollOffset(LayersId aLayersId, + const ScrollableLayerGuid::ViewID& aScrollId, + const CSSPoint& aOffset); + void SetTestAsyncZoom(LayersId aLayersId, + const ScrollableLayerGuid::ViewID& aScrollId, + const LayerToParentLayerScale& aZoom); + + // This can only be called on the updater thread. + const WebRenderScrollData* GetScrollData(LayersId aLayersId) const; + + /** + * This can be used to assert that the current thread is the + * updater thread (which samples the async transform). + * This does nothing if thread assertions are disabled. + */ + void AssertOnUpdaterThread() const; + + /** + * Runs the given task on the APZ "updater thread" for this APZUpdater. If + * this function is called from the updater thread itself then the task is + * run immediately without getting queued. + * + * The layers id argument should be the id of the layer tree that is + * requesting this task to be run. Conceptually each layer tree has a separate + * task queue, so that if one layer tree is blocked waiting for a scene build + * then tasks for the other layer trees can still be processed. + */ + void RunOnUpdaterThread(LayersId aLayersId, already_AddRefed aTask); + + /** + * Returns true if currently on the APZUpdater's "updater thread". + */ + bool IsUpdaterThread() const; + + /** + * Dispatches the given task to the APZ "controller thread", but does it + * *from* the updater thread. That is, if the thread on which this function is + * called is not the updater thread, the task is first dispatched to the + * updater thread. When the updater thread runs it (or if this is called + * directly on the updater thread), that is when the task gets dispatched to + * the controller thread. The controller thread then actually runs the task. + * + * See the RunOnUpdaterThread method for details on the layers id argument. + */ + void RunOnControllerThread(LayersId aLayersId, + already_AddRefed aTask); + + void MarkAsDetached(LayersId aLayersId); + + protected: + virtual ~APZUpdater(); + + bool UsingWebRenderUpdaterThread() const; + static already_AddRefed GetUpdater( + const wr::WrWindowId& aWindowId); + + void ProcessQueue(); + + private: + RefPtr mApz; + bool mDestroyed; + bool mIsUsingWebRender; + + // Map from layers id to WebRenderScrollData. This can only be touched on + // the updater thread. + std::unordered_map + mScrollData; + + // Stores epoch state for a particular layers id. This structure is only + // accessed on the updater thread. + struct EpochState { + // The epoch for the most recent scroll data sent from the content side. + wr::Epoch mRequired; + // The epoch for the most recent scene built and swapped in on the WR side. + Maybe mBuilt; + // True if and only if the layers id is the root layers id for the + // compositor + bool mIsRoot; + + EpochState(); + + // Whether or not the state for this layers id is such that it blocks + // processing of tasks for the layer tree. This happens if the root layers + // id or a "visible" layers id has scroll data for an epoch newer than what + // has been built. A "visible" layers id is one that is attached to the full + // layer tree (i.e. there is a chain of reflayer items from the root layer + // tree to the relevant layer subtree). This is not always the case; for + // instance a content process may send the compositor layers for a document + // before the chrome has attached the remote iframe to the root document. + // Since WR only builds pipelines for "visible" layers ids, |mBuilt| being + // populated means that the layers id is "visible". + bool IsBlocked() const; + }; + + // Map from layers id to epoch state. + // This data structure can only be touched on the updater thread. + std::unordered_map mEpochData; + + // Used to manage the mapping from a WR window id to APZUpdater. These are + // only used if WebRender is enabled. Both sWindowIdMap and mWindowId should + // only be used while holding the sWindowIdLock. Note that we use a + // StaticAutoPtr wrapper on sWindowIdMap to avoid a static initializer for the + // unordered_map. This also avoids the initializer/memory allocation in cases + // where we're not using WebRender. + static StaticMutex sWindowIdLock; + static StaticAutoPtr> sWindowIdMap; + Maybe mWindowId; + + // Lock used to protected mUpdaterThreadId; + mutable Mutex mThreadIdLock; + // If WebRender and async scene building are enabled, this holds the thread id + // of the scene builder thread (which is the updater thread) for the + // compositor associated with this APZUpdater instance. It may be populated + // even if async scene building is not enabled, but in that case we don't + // care about the contents. + Maybe mUpdaterThreadId; + + // Helper struct that pairs each queued runnable with the layers id that it is + // associated with. This allows us to easily implement the conceptual + // separation of mUpdaterQueue into independent queues per layers id. + struct QueuedTask { + LayersId mLayersId; + RefPtr mRunnable; + }; + + // Lock used to protect mUpdaterQueue + Mutex mQueueLock; + // Holds a queue of tasks to be run on the updater thread, when the updater + // thread is a WebRender thread, since it won't have a message loop we can + // dispatch to. Note that although this is a single queue it is conceptually + // separated into multiple ones, one per layers id. Tasks for a given layers + // id will always run in FIFO order, but there is no guaranteed ordering for + // tasks with different layers ids. + std::deque mUpdaterQueue; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_APZUpdater_h diff --git a/gfx/layers/apz/public/CompositorController.h b/gfx/layers/apz/public/CompositorController.h new file mode 100644 index 0000000000..5b0dca0f7e --- /dev/null +++ b/gfx/layers/apz/public/CompositorController.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_CompositorController_h +#define mozilla_layers_CompositorController_h + +#include "nsISupportsImpl.h" // for NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING +#include "mozilla/Maybe.h" +#include "mozilla/webrender/WebRenderTypes.h" + +namespace mozilla { +namespace layers { + +class CompositorController { + public: + NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING + + /** + * Ask the compositor to schedule a new composite. + */ + virtual void ScheduleRenderOnCompositorThread() = 0; + virtual void ScheduleHideAllPluginWindows() = 0; + virtual void ScheduleShowAllPluginWindows() = 0; + + protected: + virtual ~CompositorController() = default; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_CompositorController_h diff --git a/gfx/layers/apz/public/GeckoContentController.h b/gfx/layers/apz/public/GeckoContentController.h new file mode 100644 index 0000000000..16f9622aaa --- /dev/null +++ b/gfx/layers/apz/public/GeckoContentController.h @@ -0,0 +1,177 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_GeckoContentController_h +#define mozilla_layers_GeckoContentController_h + +#include "GeckoContentControllerTypes.h" +#include "InputData.h" // for PinchGestureInput +#include "LayersTypes.h" // for ScrollDirection +#include "Units.h" // for CSSPoint, CSSRect, etc +#include "mozilla/Assertions.h" // for MOZ_ASSERT_HELPER2 +#include "mozilla/Attributes.h" // for MOZ_CAN_RUN_SCRIPT +#include "mozilla/DefineEnum.h" // for MOZ_DEFINE_ENUM +#include "mozilla/EventForwards.h" // for Modifiers +#include "mozilla/layers/APZThreadUtils.h" +#include "mozilla/layers/MatrixMessage.h" // for MatrixMessage +#include "mozilla/layers/ScrollableLayerGuid.h" // for ScrollableLayerGuid, etc +#include "nsISupportsImpl.h" + +namespace mozilla { + +class Runnable; + +namespace layers { + +struct RepaintRequest; + +class GeckoContentController { + public: + using APZStateChange = GeckoContentController_APZStateChange; + using TapType = GeckoContentController_TapType; + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GeckoContentController) + + /** + * Notifies the content side of the most recently computed transforms for + * each layers subtree to the root. The nsTArray will contain one + * MatrixMessage for each layers id in the current APZ tree, along with the + * corresponding transform. + */ + virtual void NotifyLayerTransforms(nsTArray&& aTransforms) = 0; + + /** + * Requests a paint of the given RepaintRequest |aRequest| from Gecko. + * Implementations per-platform are responsible for actually handling this. + * + * This method must always be called on the repaint thread, which depends + * on the GeckoContentController. For ChromeProcessController it is the + * Gecko main thread, while for RemoteContentController it is the compositor + * thread where it can send IPDL messages. + */ + virtual void RequestContentRepaint(const RepaintRequest& aRequest) = 0; + + /** + * Requests handling of a tap event. |aPoint| is in LD pixels, relative to the + * current scroll offset. + */ + MOZ_CAN_RUN_SCRIPT + virtual void HandleTap(TapType aType, const LayoutDevicePoint& aPoint, + Modifiers aModifiers, const ScrollableLayerGuid& aGuid, + uint64_t aInputBlockId) = 0; + + /** + * When the apz.allow_zooming pref is set to false, the APZ will not + * translate pinch gestures to actual zooming. Instead, it will call this + * method to notify gecko of the pinch gesture, and allow it to deal with it + * however it wishes. Note that this function is not called if the pinch is + * prevented by content calling preventDefault() on the touch events, or via + * use of the touch-action property. + * @param aType One of PINCHGESTURE_START, PINCHGESTURE_SCALE, + * PINCHGESTURE_FINGERLIFTED, or PINCHGESTURE_END, indicating the phase + * of the pinch. + * @param aGuid The guid of the APZ that is detecting the pinch. This is + * generally the root APZC for the layers id. + * @param aFocusPoint The focus point of the pinch event. + * @param aSpanChange For the START or END event, this is always 0. + * For a SCALE event, this is the difference in span between the + * previous state and the new state. + * @param aModifiers The keyboard modifiers depressed during the pinch. + */ + virtual void NotifyPinchGesture(PinchGestureInput::PinchGestureType aType, + const ScrollableLayerGuid& aGuid, + const LayoutDevicePoint& aFocusPoint, + LayoutDeviceCoord aSpanChange, + Modifiers aModifiers) = 0; + + /** + * Schedules a runnable to run on the controller thread at some time + * in the future. + * This method must always be called on the controller thread. + */ + virtual void PostDelayedTask(already_AddRefed aRunnable, + int aDelayMs) { + APZThreadUtils::DelayedDispatch(std::move(aRunnable), aDelayMs); + } + + /** + * Returns true if we are currently on the thread that can send repaint + * requests. + */ + virtual bool IsRepaintThread() = 0; + + /** + * Runs the given task on the "repaint" thread. + */ + virtual void DispatchToRepaintThread(already_AddRefed aTask) = 0; + + /** + * General notices of APZ state changes for consumers. + * |aGuid| identifies the APZC originating the state change. + * |aChange| identifies the type of state change + * |aArg| is used by some state changes to pass extra information (see + * the documentation for each state change above) + */ + virtual void NotifyAPZStateChange(const ScrollableLayerGuid& aGuid, + APZStateChange aChange, int aArg = 0) {} + + /** + * Notify content of a MozMouseScrollFailed event. + */ + virtual void NotifyMozMouseScrollEvent( + const ScrollableLayerGuid::ViewID& aScrollId, const nsString& aEvent) {} + + /** + * Notify content that the repaint requests have been flushed. + */ + virtual void NotifyFlushComplete() = 0; + + /** + * If the async scrollbar-drag initiation code kicks in on the APZ side, then + * we need to let content know that we are dragging the scrollbar. Otherwise, + * by the time the mousedown events is handled by content, the scrollthumb + * could already have been moved via a RequestContentRepaint message at a + * new scroll position, and the mousedown might end up triggering a click-to- + * scroll on where the thumb used to be. + */ + virtual void NotifyAsyncScrollbarDragInitiated( + uint64_t aDragBlockId, const ScrollableLayerGuid::ViewID& aScrollId, + ScrollDirection aDirection) = 0; + virtual void NotifyAsyncScrollbarDragRejected( + const ScrollableLayerGuid::ViewID& aScrollId) = 0; + + virtual void NotifyAsyncAutoscrollRejected( + const ScrollableLayerGuid::ViewID& aScrollId) = 0; + + virtual void CancelAutoscroll(const ScrollableLayerGuid& aGuid) = 0; + + virtual void UpdateOverscrollVelocity(const ScrollableLayerGuid& aGuid, + float aX, float aY, + bool aIsRootContent) {} + virtual void UpdateOverscrollOffset(const ScrollableLayerGuid& aGuid, + float aX, float aY, bool aIsRootContent) { + } + + GeckoContentController() = default; + + /** + * Needs to be called on the main thread. + */ + virtual void Destroy() {} + + /** + * Whether this is RemoteContentController. + */ + virtual bool IsRemote() { return false; } + + protected: + // Protected destructor, to discourage deletion outside of Release(): + virtual ~GeckoContentController() = default; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_GeckoContentController_h diff --git a/gfx/layers/apz/public/GeckoContentControllerTypes.h b/gfx/layers/apz/public/GeckoContentControllerTypes.h new file mode 100644 index 0000000000..8ab478eab5 --- /dev/null +++ b/gfx/layers/apz/public/GeckoContentControllerTypes.h @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_GeckoContentControllerTypes_h +#define mozilla_layers_GeckoContentControllerTypes_h + +#include "mozilla/DefineEnum.h" + +namespace mozilla { +namespace layers { + +// clang-format off +MOZ_DEFINE_ENUM_CLASS(GeckoContentController_APZStateChange, ( + /** + * APZ started modifying the view (including panning, zooming, and fling). + */ + eTransformBegin, + /** + * APZ finished modifying the view. + */ + eTransformEnd, + /** + * APZ started a touch. + * |aArg| is 1 if touch can be a pan, 0 otherwise. + */ + eStartTouch, + /** + * APZ started a pan. + */ + eStartPanning, + /** + * APZ finished processing a touch. + * |aArg| is 1 if touch was a click, 0 otherwise. + */ + eEndTouch +)); +// clang-format on + +/** + * Different types of tap-related events that can be sent in + * the HandleTap function. The names should be relatively self-explanatory. + * Note that the eLongTapUp will always be preceded by an eLongTap, but not + * all eLongTap notifications will be followed by an eLongTapUp (for instance, + * if the user moves their finger after triggering the long-tap but before + * lifting it). + * The difference between eDoubleTap and eSecondTap is subtle - the eDoubleTap + * is for an actual double-tap "gesture" while eSecondTap is for the same user + * input but where a double-tap gesture is not allowed. This is used to fire + * a click event with detail=2 to web content (similar to what a mouse double- + * click would do). + */ +// clang-format off +MOZ_DEFINE_ENUM_CLASS(GeckoContentController_TapType, ( + eSingleTap, + eDoubleTap, + eSecondTap, + eLongTap, + eLongTapUp +)); +// clang-format on + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_GeckoContentControllerTypes_h diff --git a/gfx/layers/apz/public/IAPZCTreeManager.h b/gfx/layers/apz/public/IAPZCTreeManager.h new file mode 100644 index 0000000000..72babd67f1 --- /dev/null +++ b/gfx/layers/apz/public/IAPZCTreeManager.h @@ -0,0 +1,165 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_IAPZCTreeManager_h +#define mozilla_layers_IAPZCTreeManager_h + +#include // for uint64_t, uint32_t + +#include "mozilla/layers/LayersTypes.h" // for TouchBehaviorFlags +#include "mozilla/layers/ScrollableLayerGuid.h" // for ScrollableLayerGuid, etc +#include "mozilla/layers/ZoomConstraints.h" // for ZoomConstraints +#include "nsTArrayForwardDeclare.h" // for nsTArray, nsTArray_Impl, etc +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "Units.h" // for CSSRect, etc + +namespace mozilla { +namespace layers { + +class APZInputBridge; +class KeyboardMap; + +enum AllowedTouchBehavior { + NONE = 0, + VERTICAL_PAN = 1 << 0, + HORIZONTAL_PAN = 1 << 1, + PINCH_ZOOM = 1 << 2, + DOUBLE_TAP_ZOOM = 1 << 3, + UNKNOWN = 1 << 4 +}; + +enum ZoomToRectBehavior : uint32_t { + DEFAULT_BEHAVIOR = 0, + DISABLE_ZOOM_OUT = 1 << 0, + PAN_INTO_VIEW_ONLY = 1 << 1, + ONLY_ZOOM_TO_DEFAULT_SCALE = 1 << 2 +}; + +class AsyncDragMetrics; +enum class APZHandledResult : uint8_t; + +class IAPZCTreeManager { + NS_INLINE_DECL_THREADSAFE_VIRTUAL_REFCOUNTING(IAPZCTreeManager) + + public: + /** + * Set the keyboard shortcuts to use for translating keyboard events. + */ + virtual void SetKeyboardMap(const KeyboardMap& aKeyboardMap) = 0; + + /** + * Kicks an animation to zoom to a rect. This may be either a zoom out or zoom + * in. The actual animation is done on the sampler thread after being set + * up. |aRect| must be given in CSS pixels, relative to the document. + * |aFlags| is a combination of the ZoomToRectBehavior enum values. + */ + virtual void ZoomToRect(const ScrollableLayerGuid& aGuid, + const CSSRect& aRect, + const uint32_t aFlags = DEFAULT_BEHAVIOR) = 0; + + /** + * If we have touch listeners, this should always be called when we know + * definitively whether or not content has preventDefaulted any touch events + * that have come in. If |aPreventDefault| is true, any touch events in the + * queue will be discarded. This function must be called on the controller + * thread. + */ + virtual void ContentReceivedInputBlock(uint64_t aInputBlockId, + bool aPreventDefault) = 0; + + /** + * When the event regions code is enabled, this function should be invoked to + * to confirm the target of the input block. This is only needed in cases + * where the initial input event of the block hit a dispatch-to-content region + * but is safe to call for all input blocks. This function should always be + * invoked on the controller thread. + * The different elements in the array of targets correspond to the targets + * for the different touch points. In the case where the touch point has no + * target, or the target is not a scrollable frame, the target's |mScrollId| + * should be set to ScrollableLayerGuid::NULL_SCROLL_ID. + */ + virtual void SetTargetAPZC(uint64_t aInputBlockId, + const nsTArray& aTargets) = 0; + + /** + * Updates any zoom constraints contained in the tag. + * If the |aConstraints| is Nothing() then previously-provided constraints for + * the given |aGuid| are cleared. + */ + virtual void UpdateZoomConstraints( + const ScrollableLayerGuid& aGuid, + const Maybe& aConstraints) = 0; + + virtual void SetDPI(float aDpiValue) = 0; + + /** + * Sets allowed touch behavior values for current touch-session for specific + * input block (determined by aInputBlock). + * Should be invoked by the widget. Each value of the aValues arrays + * corresponds to the different touch point that is currently active. + * Must be called after receiving the TOUCH_START event that starts the + * touch-session. + * This must be called on the controller thread. + */ + virtual void SetAllowedTouchBehavior( + uint64_t aInputBlockId, const nsTArray& aValues) = 0; + + virtual void StartScrollbarDrag(const ScrollableLayerGuid& aGuid, + const AsyncDragMetrics& aDragMetrics) = 0; + + virtual bool StartAutoscroll(const ScrollableLayerGuid& aGuid, + const ScreenPoint& aAnchorLocation) = 0; + + virtual void StopAutoscroll(const ScrollableLayerGuid& aGuid) = 0; + + /** + * Function used to disable LongTap gestures. + * + * On slow running tests, drags and touch events can be misinterpreted + * as a long tap. This allows tests to disable long tap gesture detection. + */ + virtual void SetLongTapEnabled(bool aTapGestureEnabled) = 0; + + /** + * Returns an APZInputBridge interface that can be used to send input + * events to APZ in a synchronous manner. This will always be non-null, and + * the returned object's lifetime will match the lifetime of this + * IAPZCTreeManager implementation. + * It is only valid to call this function in the UI process. + */ + virtual APZInputBridge* InputBridge() = 0; + + /** + * Add a callback to be invoked when |aInputBlockId| is ready for handling, + * with a boolean indicating whether the APZC handling the input block is + * the root content APZC. + * + * Should only be used for input blocks that are not yet ready for handling + * at the time this is called. If the input block was already handled, + * the callback will never be called. + * + * Only one callback can be registered for an input block at a time. + * Subsequent attempts to register a callback for an input block will be + * ignored until the existing callback is triggered. + * + * This is only implemented when the caller is in the same process as + * the APZCTreeManager. + */ + using InputBlockCallback = std::function; + virtual void AddInputBlockCallback(uint64_t aInputBlockId, + InputBlockCallback&& aCallback) = 0; + + protected: + // Discourage destruction outside of decref + + virtual ~IAPZCTreeManager() = default; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_IAPZCTreeManager_h diff --git a/gfx/layers/apz/public/MatrixMessage.h b/gfx/layers/apz/public/MatrixMessage.h new file mode 100644 index 0000000000..f053dd0903 --- /dev/null +++ b/gfx/layers/apz/public/MatrixMessage.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_MatrixMessage_h +#define mozilla_layers_MatrixMessage_h + +#include "mozilla/Maybe.h" +#include "mozilla/gfx/Matrix.h" +#include "mozilla/layers/LayersTypes.h" +#include "Units.h" // for ScreenRect +#include "UnitTransforms.h" + +namespace mozilla { +namespace layers { +class MatrixMessage { + public: + // Don't use this one directly + MatrixMessage() = default; + + MatrixMessage(const Maybe& aMatrix, + const ScreenRect& aTopLevelViewportVisibleRectInBrowserCoords, + const LayersId& aLayersId) + : mMatrix(ToUnknownMatrix(aMatrix)), + mTopLevelViewportVisibleRectInBrowserCoords( + aTopLevelViewportVisibleRectInBrowserCoords), + mLayersId(aLayersId) {} + + inline Maybe GetMatrix() const { + return LayerToScreenMatrix4x4::FromUnknownMatrix(mMatrix); + } + + inline ScreenRect GetTopLevelViewportVisibleRectInBrowserCoords() const { + return mTopLevelViewportVisibleRectInBrowserCoords; + } + + inline const LayersId& GetLayersId() const { return mLayersId; } + + // Fields are public for IPC. Don't access directly + // elsewhere. + // Transform matrix to convert this layer to screen coordinate. + Maybe mMatrix; // Untyped for IPC + // The remote iframe document rectangle corresponding to this layer. + // The rectangle is the result of clipped out by ancestor async scrolling so + // that the rectangle will be empty if it's completely scrolled out of view. + ScreenRect mTopLevelViewportVisibleRectInBrowserCoords; + LayersId mLayersId; +}; +}; // namespace layers +}; // namespace mozilla + +#endif // mozilla_layers_MatrixMessage_h diff --git a/gfx/layers/apz/public/MetricsSharingController.h b/gfx/layers/apz/public/MetricsSharingController.h new file mode 100644 index 0000000000..869f2e2346 --- /dev/null +++ b/gfx/layers/apz/public/MetricsSharingController.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_MetricsSharingController_h +#define mozilla_layers_MetricsSharingController_h + +#include "FrameMetrics.h" // for FrameMetrics +#include "mozilla/ipc/CrossProcessMutex.h" // for CrossProcessMutexHandle +#include "mozilla/ipc/SharedMemoryBasic.h" // for SharedMemoryBasic +#include "nsISupportsImpl.h" // for NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING + +namespace mozilla { +namespace layers { + +class MetricsSharingController { + public: + NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING + + virtual base::ProcessId RemotePid() = 0; + virtual bool StartSharingMetrics( + mozilla::ipc::SharedMemoryBasic::Handle aHandle, + CrossProcessMutexHandle aMutexHandle, LayersId aLayersId, + uint32_t aApzcId) = 0; + virtual bool StopSharingMetrics(ScrollableLayerGuid::ViewID aScrollId, + uint32_t aApzcId) = 0; + + protected: + virtual ~MetricsSharingController() = default; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_MetricsSharingController_h diff --git a/gfx/layers/apz/src/APZCTreeManager.cpp b/gfx/layers/apz/src/APZCTreeManager.cpp new file mode 100644 index 0000000000..208aba114e --- /dev/null +++ b/gfx/layers/apz/src/APZCTreeManager.cpp @@ -0,0 +1,3967 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#include +#include "APZCTreeManager.h" +#include "AsyncPanZoomController.h" +#include "Compositor.h" // for Compositor +#include "DragTracker.h" // for DragTracker +#include "GenericFlingAnimation.h" // for FLING_LOG +#include "HitTestingTreeNode.h" // for HitTestingTreeNode +#include "InputBlockState.h" // for InputBlockState +#include "InputData.h" // for InputData, etc +#include "Layers.h" // for Layer, etc +#include "mozilla/RecursiveMutex.h" +#include "mozilla/dom/MouseEventBinding.h" // for MouseEvent constants +#include "mozilla/dom/BrowserParent.h" // for AreRecordReplayTabsActive +#include "mozilla/dom/Touch.h" // for Touch +#include "mozilla/gfx/CompositorHitTestInfo.h" +#include "mozilla/gfx/LoggingConstants.h" +#include "mozilla/gfx/gfxVars.h" // for gfxVars +#include "mozilla/gfx/GPUParent.h" // for GPUParent +#include "mozilla/gfx/Logging.h" // for gfx::TreeLog +#include "mozilla/gfx/Point.h" // for Point +#include "mozilla/layers/APZSampler.h" // for APZSampler +#include "mozilla/layers/APZThreadUtils.h" // for AssertOnControllerThread, etc +#include "mozilla/layers/APZUpdater.h" // for APZUpdater +#include "mozilla/layers/APZUtils.h" // for AsyncTransform +#include "mozilla/layers/AsyncDragMetrics.h" // for AsyncDragMetrics +#include "mozilla/layers/CompositorBridgeParent.h" // for CompositorBridgeParent, etc +#include "mozilla/layers/LayerMetricsWrapper.h" +#include "mozilla/layers/MatrixMessage.h" +#include "mozilla/layers/UiCompositorControllerParent.h" +#include "mozilla/layers/WebRenderScrollDataWrapper.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/mozalloc.h" // for operator new +#include "mozilla/Preferences.h" // for Preferences +#include "mozilla/StaticPrefs_accessibility.h" +#include "mozilla/StaticPrefs_apz.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/ToString.h" +#include "mozilla/TouchEvents.h" +#include "mozilla/EventStateManager.h" // for WheelPrefs +#include "mozilla/webrender/WebRenderAPI.h" +#include "nsDebug.h" // for NS_WARNING +#include "nsPoint.h" // for nsIntPoint +#include "nsThreadUtils.h" // for NS_IsMainThread +#include "OverscrollHandoffState.h" // for OverscrollHandoffState +#include "TreeTraversal.h" // for ForEachNode, BreadthFirstSearch, etc +#include "Units.h" // for ParentlayerPixel +#include "GestureEventListener.h" // for GestureEventListener::setLongTapEnabled +#include "UnitTransforms.h" // for ViewAs + +static mozilla::LazyLogModule sApzMgrLog("apz.manager"); +#define APZCTM_LOG(...) MOZ_LOG(sApzMgrLog, LogLevel::Debug, (__VA_ARGS__)) + +static mozilla::LazyLogModule sApzKeyLog("apz.key"); +#define APZ_KEY_LOG(...) MOZ_LOG(sApzKeyLog, LogLevel::Debug, (__VA_ARGS__)) + +namespace mozilla { +namespace layers { + +using mozilla::gfx::CompositorHitTestDispatchToContent; +using mozilla::gfx::CompositorHitTestFlags; +using mozilla::gfx::CompositorHitTestInfo; +using mozilla::gfx::CompositorHitTestInvisibleToHit; +using mozilla::gfx::LOG_DEFAULT; + +typedef mozilla::gfx::Point Point; +typedef mozilla::gfx::Point4D Point4D; +typedef mozilla::gfx::Matrix4x4 Matrix4x4; + +typedef CompositorBridgeParent::LayerTreeState LayerTreeState; + +struct APZCTreeManager::TreeBuildingState { + TreeBuildingState(LayersId aRootLayersId, bool aIsFirstPaint, + LayersId aOriginatingLayersId, APZTestData* aTestData, + uint32_t aPaintSequence) + : mIsFirstPaint(aIsFirstPaint), + mOriginatingLayersId(aOriginatingLayersId), + mPaintLogger(aTestData, aPaintSequence) { + CompositorBridgeParent::CallWithIndirectShadowTree( + aRootLayersId, [this](LayerTreeState& aState) -> void { + mCompositorController = aState.GetCompositorController(); + mInProcessSharingController = aState.InProcessSharingController(); + }); + } + + typedef std::unordered_map + DeferredTransformMap; + + // State that doesn't change as we recurse in the tree building + RefPtr mCompositorController; + RefPtr mInProcessSharingController; + const bool mIsFirstPaint; + const LayersId mOriginatingLayersId; + const APZPaintLogHelper mPaintLogger; + + // State that is updated as we perform the tree build + + // A list of nodes that need to be destroyed at the end of the tree building. + // This is initialized with all nodes in the old tree, and nodes are removed + // from it as we reuse them in the new tree. + nsTArray> mNodesToDestroy; + + // This map is populated as we place APZCs into the new tree. Its purpose is + // to facilitate re-using the same APZC for different layers that scroll + // together (and thus have the same ScrollableLayerGuid). The presShellId + // doesn't matter for this purpose, and we move the map to the APZCTreeManager + // after we're done building, so it's useful to have the presshell-ignoring + // map for that. + std::unordered_map + mApzcMap; + + // This is populated with all the HitTestingTreeNodes that are scroll thumbs + // and have a scrollthumb animation id (which indicates that they need to be + // sampled for WebRender on the sampler thread). + std::vector mScrollThumbs; + // This is populated with all the scroll target nodes. We use in conjunction + // with mScrollThumbs to build APZCTreeManager::mScrollThumbInfo. + std::unordered_map + mScrollTargets; + + // As the tree is traversed, the top element of this stack tracks whether + // the parent scroll node has a perspective transform. + std::stack mParentHasPerspective; + + // During the tree building process, the perspective transform component + // of the ancestor transforms of some APZCs can be "deferred" to their + // children, meaning they are added to the children's ancestor transforms + // instead. Those deferred transforms are tracked here. + DeferredTransformMap mPerspectiveTransformsDeferredToChildren; + + // As we recurse down through the tree, this picks up the zoom animation id + // from a node in the layer tree, and propagates it downwards to the nearest + // APZC instance that is for an RCD node. Generally it will be set on the + // root node of the layers (sub-)tree, which may not be same as the RCD node + // for the subtree, and so we need this mechanism to ensure it gets propagated + // to the RCD's APZC instance. Once it is set on the APZC instance, the value + // is cleared back to Nothing(). Note that this is only used in the WebRender + // codepath. + Maybe mZoomAnimationId; + + // See corresponding members of APZCTreeManager. These are the same thing, but + // on the tree-walking state. They are populated while walking the tree in + // a layers update, and then moved into APZCTreeManager. + std::vector mFixedPositionInfo; + std::vector mRootScrollbarInfo; + std::vector mStickyPositionInfo; + + // As we recurse down through reflayers in the tree, this picks up the + // cumulative EventRegionsOverride flags from the reflayers, and is used to + // apply them to descendant layers. + std::stack mOverrideFlags; +}; + +class APZCTreeManager::CheckerboardFlushObserver : public nsIObserver { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + explicit CheckerboardFlushObserver(APZCTreeManager* aTreeManager) + : mTreeManager(aTreeManager) { + MOZ_ASSERT(NS_IsMainThread()); + nsCOMPtr obsSvc = + mozilla::services::GetObserverService(); + MOZ_ASSERT(obsSvc); + if (obsSvc) { + obsSvc->AddObserver(this, "APZ:FlushActiveCheckerboard", false); + } + } + + void Unregister() { + MOZ_ASSERT(NS_IsMainThread()); + nsCOMPtr obsSvc = + mozilla::services::GetObserverService(); + if (obsSvc) { + obsSvc->RemoveObserver(this, "APZ:FlushActiveCheckerboard"); + } + mTreeManager = nullptr; + } + + protected: + virtual ~CheckerboardFlushObserver() = default; + + private: + RefPtr mTreeManager; +}; + +NS_IMPL_ISUPPORTS(APZCTreeManager::CheckerboardFlushObserver, nsIObserver) + +NS_IMETHODIMP +APZCTreeManager::CheckerboardFlushObserver::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t*) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mTreeManager.get()); + + RecursiveMutexAutoLock lock(mTreeManager->mTreeLock); + if (mTreeManager->mRootNode) { + ForEachNode( + mTreeManager->mRootNode.get(), [](HitTestingTreeNode* aNode) { + if (aNode->IsPrimaryHolder()) { + MOZ_ASSERT(aNode->GetApzc()); + aNode->GetApzc()->FlushActiveCheckerboardReport(); + } + }); + } + if (XRE_IsGPUProcess()) { + if (gfx::GPUParent* gpu = gfx::GPUParent::GetSingleton()) { + nsCString topic("APZ:FlushActiveCheckerboard:Done"); + Unused << gpu->SendNotifyUiObservers(topic); + } + } else { + MOZ_ASSERT(XRE_IsParentProcess()); + nsCOMPtr obsSvc = + mozilla::services::GetObserverService(); + if (obsSvc) { + obsSvc->NotifyObservers(nullptr, "APZ:FlushActiveCheckerboard:Done", + nullptr); + } + } + return NS_OK; +} + +/** + * A RAII class used for setting the focus sequence number on input events + * as they are being processed. Any input event is assumed to be potentially + * focus changing unless explicitly marked otherwise. + */ +class MOZ_RAII AutoFocusSequenceNumberSetter { + public: + AutoFocusSequenceNumberSetter(FocusState& aFocusState, InputData& aEvent) + : mFocusState(aFocusState), mEvent(aEvent), mMayChangeFocus(true) {} + + void MarkAsNonFocusChanging() { mMayChangeFocus = false; } + + ~AutoFocusSequenceNumberSetter() { + if (mMayChangeFocus) { + mFocusState.ReceiveFocusChangingEvent(); + + APZ_KEY_LOG( + "Marking input with type=%d as focus changing with seq=%" PRIu64 "\n", + static_cast(mEvent.mInputType), + mFocusState.LastAPZProcessedEvent()); + } else { + APZ_KEY_LOG( + "Marking input with type=%d as non focus changing with seq=%" PRIu64 + "\n", + static_cast(mEvent.mInputType), + mFocusState.LastAPZProcessedEvent()); + } + + mEvent.mFocusSequenceNumber = mFocusState.LastAPZProcessedEvent(); + } + + private: + FocusState& mFocusState; + InputData& mEvent; + bool mMayChangeFocus; +}; + +APZCTreeManager::APZCTreeManager(LayersId aRootLayersId, bool aIsUsingWebRender) + : mTestSampleTime(Nothing(), "APZCTreeManager::mTestSampleTime"), + mInputQueue(new InputQueue()), + mRootLayersId(aRootLayersId), + mSampler(nullptr), + mUpdater(nullptr), + mTreeLock("APZCTreeLock"), + mMapLock("APZCMapLock"), + mRetainedTouchIdentifier(-1), + mInScrollbarTouchDrag(false), + mCurrentMousePosition(ScreenPoint(), + "APZCTreeManager::mCurrentMousePosition"), + mApzcTreeLog("apzctree"), + mTestDataLock("APZTestDataLock"), + mDPI(160.0), + mIsUsingWebRender(aIsUsingWebRender) { + RefPtr self(this); + NS_DispatchToMainThread(NS_NewRunnableFunction( + "layers::APZCTreeManager::APZCTreeManager", + [self] { self->mFlushObserver = new CheckerboardFlushObserver(self); })); + AsyncPanZoomController::InitializeGlobalState(); + mApzcTreeLog.ConditionOnPrefFunction(StaticPrefs::apz_printtree); +} + +APZCTreeManager::~APZCTreeManager() = default; + +void APZCTreeManager::SetSampler(APZSampler* aSampler) { + // We're either setting the sampler or clearing it + MOZ_ASSERT((mSampler == nullptr) != (aSampler == nullptr)); + mSampler = aSampler; +} + +void APZCTreeManager::SetUpdater(APZUpdater* aUpdater) { + // We're either setting the updater or clearing it + MOZ_ASSERT((mUpdater == nullptr) != (aUpdater == nullptr)); + mUpdater = aUpdater; +} + +void APZCTreeManager::NotifyLayerTreeAdopted( + LayersId aLayersId, const RefPtr& aOldApzcTreeManager) { + AssertOnUpdaterThread(); + + if (aOldApzcTreeManager) { + aOldApzcTreeManager->mFocusState.RemoveFocusTarget(aLayersId); + // While we could move the focus target information from the old APZC tree + // manager into this one, it's safer to not do that, as we'll probably have + // that information repopulated soon anyway (on the next layers update). + } + + UniquePtr adoptedData; + if (aOldApzcTreeManager) { + MutexAutoLock lock(aOldApzcTreeManager->mTestDataLock); + auto it = aOldApzcTreeManager->mTestData.find(aLayersId); + if (it != aOldApzcTreeManager->mTestData.end()) { + adoptedData = std::move(it->second); + aOldApzcTreeManager->mTestData.erase(it); + } + } + if (adoptedData) { + MutexAutoLock lock(mTestDataLock); + mTestData[aLayersId] = std::move(adoptedData); + } +} + +void APZCTreeManager::NotifyLayerTreeRemoved(LayersId aLayersId) { + AssertOnUpdaterThread(); + + mFocusState.RemoveFocusTarget(aLayersId); + + { // scope lock + MutexAutoLock lock(mTestDataLock); + mTestData.erase(aLayersId); + } +} + +AsyncPanZoomController* APZCTreeManager::NewAPZCInstance( + LayersId aLayersId, GeckoContentController* aController) { + return new AsyncPanZoomController( + aLayersId, this, mInputQueue, aController, + AsyncPanZoomController::USE_GESTURE_DETECTOR); +} + +void APZCTreeManager::SetTestSampleTime(const Maybe& aTime) { + auto testSampleTime = mTestSampleTime.Lock(); + testSampleTime.ref() = aTime; +} + +SampleTime APZCTreeManager::GetFrameTime() { + auto testSampleTime = mTestSampleTime.Lock(); + if (testSampleTime.ref()) { + return SampleTime::FromTest(*testSampleTime.ref()); + } + return SampleTime::FromNow(); +} + +void APZCTreeManager::SetAllowedTouchBehavior( + uint64_t aInputBlockId, const nsTArray& aValues) { + APZThreadUtils::AssertOnControllerThread(); + + mInputQueue->SetAllowedTouchBehavior(aInputBlockId, aValues); +} + +template +void // ScrollNode is a LayerMetricsWrapper or a WebRenderScrollDataWrapper +APZCTreeManager::UpdateHitTestingTreeImpl(const ScrollNode& aRoot, + bool aIsFirstPaint, + LayersId aOriginatingLayersId, + uint32_t aPaintSequenceNumber) { + RecursiveMutexAutoLock lock(mTreeLock); + + // For testing purposes, we log some data to the APZTestData associated with + // the layers id that originated this update. + APZTestData* testData = nullptr; + if (StaticPrefs::apz_test_logging_enabled()) { + MutexAutoLock lock(mTestDataLock); + UniquePtr ptr = MakeUnique(); + auto result = + mTestData.insert(std::make_pair(aOriginatingLayersId, std::move(ptr))); + testData = result.first->second.get(); + testData->StartNewPaint(aPaintSequenceNumber); + } + + TreeBuildingState state(mRootLayersId, aIsFirstPaint, aOriginatingLayersId, + testData, aPaintSequenceNumber); + + // We do this business with collecting the entire tree into an array because + // otherwise it's very hard to determine which APZC instances need to be + // destroyed. In the worst case, there are two scenarios: (a) a layer with an + // APZC is removed from the layer tree and (b) a layer with an APZC is moved + // in the layer tree from one place to a completely different place. In + // scenario (a) we would want to destroy the APZC while walking the layer tree + // and noticing that the layer/APZC is no longer there. But if we do that then + // we run into a problem in scenario (b) because we might encounter that layer + // later during the walk. To handle both of these we have to 'remember' that + // the layer was not found, and then do the destroy only at the end of the + // tree walk after we are sure that the layer was removed and not just + // transplanted elsewhere. Doing that as part of a recursive tree walk is hard + // and so maintaining a list and removing APZCs that are still alive is much + // simpler. + ForEachNode(mRootNode.get(), + [&state](HitTestingTreeNode* aNode) { + state.mNodesToDestroy.AppendElement(aNode); + }); + mRootNode = nullptr; + mAsyncZoomContainerSubtree = Nothing(); + int asyncZoomContainerNestingDepth = 0; + bool haveNestedAsyncZoomContainers = false; + nsTArray subtreesWithRootContentOutsideAsyncZoomContainer; + + if (aRoot) { + std::unordered_set seenLayersIds; + std::stack> indents; + std::stack ancestorTransforms; + HitTestingTreeNode* parent = nullptr; + HitTestingTreeNode* next = nullptr; + LayersId layersId = mRootLayersId; + seenLayersIds.insert(mRootLayersId); + ancestorTransforms.push(AncestorTransform()); + state.mParentHasPerspective.push(false); + state.mOverrideFlags.push(EventRegionsOverride::NoOverride); + + mApzcTreeLog << "[start]\n"; + mTreeLock.AssertCurrentThreadIn(); + + ForEachNode( + aRoot, + [&](ScrollNode aLayerMetrics) { + mApzcTreeLog << aLayerMetrics.Name() << '\t'; + + if (aLayerMetrics.IsAsyncZoomContainer()) { + if (asyncZoomContainerNestingDepth > 0) { + haveNestedAsyncZoomContainers = true; + } + mAsyncZoomContainerSubtree = Some(layersId); + ++asyncZoomContainerNestingDepth; + } + + if (aLayerMetrics.Metrics().IsRootContent()) { + MutexAutoLock lock(mMapLock); + mGeckoFixedLayerMargins = + aLayerMetrics.Metrics().GetFixedLayerMargins(); + } else { + MOZ_ASSERT(aLayerMetrics.Metrics().GetFixedLayerMargins() == + ScreenMargin(), + "fixed-layer-margins should be 0 on non-root layer"); + } + + // Note that this check happens after the potential increment of + // asyncZoomContainerNestingDepth, to allow the root content + // metadata to be on the same node as the async zoom container. + if (aLayerMetrics.Metrics().IsRootContent() && + asyncZoomContainerNestingDepth == 0) { + subtreesWithRootContentOutsideAsyncZoomContainer.AppendElement( + layersId); + } + + HitTestingTreeNode* node = PrepareNodeForLayer( + lock, aLayerMetrics, aLayerMetrics.Metrics(), layersId, + ancestorTransforms.top(), parent, next, state); + MOZ_ASSERT(node); + AsyncPanZoomController* apzc = node->GetApzc(); + aLayerMetrics.SetApzc(apzc); + + // GetScrollbarAnimationId is only set when webrender is enabled, + // which limits the extra thumb mapping work to the webrender-enabled + // case where it is needed. + // Note also that when webrender is enabled, a "valid" animation id + // is always nonzero, so we don't need to worry about handling the + // case where WR is enabled and the animation id is zero. + if (node->GetScrollbarAnimationId()) { + if (node->IsScrollThumbNode()) { + state.mScrollThumbs.push_back(node); + } else if (node->IsScrollbarContainerNode()) { + // Only scrollbar containers for the root have an animation id. + state.mRootScrollbarInfo.emplace_back( + *(node->GetScrollbarAnimationId()), + node->GetScrollbarDirection()); + } + } + + // GetFixedPositionAnimationId is only set when webrender is enabled. + if (node->GetFixedPositionAnimationId().isSome()) { + state.mFixedPositionInfo.emplace_back(node); + } + // GetStickyPositionAnimationId is only set when webrender is enabled. + if (node->GetStickyPositionAnimationId().isSome()) { + state.mStickyPositionInfo.emplace_back(node); + } + if (apzc && node->IsPrimaryHolder()) { + state.mScrollTargets[apzc->GetGuid()] = node; + } + + mApzcTreeLog << '\n'; + + // Accumulate the CSS transform between layers that have an APZC. + // In the terminology of the big comment above + // APZCTreeManager::GetScreenToApzcTransform, if we are at layer M, + // then aAncestorTransform is NC * OC * PC, and we left-multiply MC + // and compute ancestorTransform to be MC * NC * OC * PC. This gets + // passed down as the ancestor transform to layer L when we recurse + // into the children below. If we are at a layer with an APZC, such as + // P, then we reset the ancestorTransform to just PC, to start the new + // accumulation as we go down. + AncestorTransform currentTransform{ + aLayerMetrics.GetTransform(), + aLayerMetrics.TransformIsPerspective()}; + if (!apzc) { + currentTransform = currentTransform * ancestorTransforms.top(); + } + ancestorTransforms.push(currentTransform); + + // Note that |node| at this point will not have any children, + // otherwise we we would have to set next to node->GetFirstChild(). + MOZ_ASSERT(!node->GetFirstChild()); + parent = node; + next = nullptr; + + // Update the layersId if we have a new one + if (Maybe newLayersId = aLayerMetrics.GetReferentId()) { + layersId = *newLayersId; + seenLayersIds.insert(layersId); + + // Propagate any event region override flags down into all + // descendant nodes from the reflayer that has the flag. This is an + // optimization to avoid having to walk up the tree to check the + // override flags. Note that we don't keep the flags on the reflayer + // itself, because the semantics of the flags are that they apply + // to all content in the layer subtree being referenced. This + // matters with the WR hit-test codepath, because this reflayer may + // be just one of many nodes associated with a particular APZC, and + // calling GetTargetNode with a guid may return any one of the + // nodes. If different nodes have different flags on them that can + // make the WR hit-test result incorrect, but being strict about + // only putting the flags on descendant layers avoids this problem. + state.mOverrideFlags.push(state.mOverrideFlags.top() | + aLayerMetrics.GetEventRegionsOverride()); + } + + indents.push(gfx::TreeAutoIndent(mApzcTreeLog)); + state.mParentHasPerspective.push( + aLayerMetrics.TransformIsPerspective()); + }, + [&](ScrollNode aLayerMetrics) { + if (aLayerMetrics.IsAsyncZoomContainer()) { + --asyncZoomContainerNestingDepth; + } + if (aLayerMetrics.GetReferentId()) { + state.mOverrideFlags.pop(); + } + + next = parent; + parent = parent->GetParent(); + layersId = next->GetLayersId(); + ancestorTransforms.pop(); + indents.pop(); + state.mParentHasPerspective.pop(); + }); + + mApzcTreeLog << "[end]\n"; + + MOZ_ASSERT( + !mAsyncZoomContainerSubtree || + !subtreesWithRootContentOutsideAsyncZoomContainer.Contains( + *mAsyncZoomContainerSubtree), + "If there is an async zoom container, all scroll nodes with root " + "content scroll metadata should be inside it"); + MOZ_ASSERT(!haveNestedAsyncZoomContainers, + "Should not have nested async zoom container"); + + // If we have perspective transforms deferred to children, do another + // walk of the tree and actually apply them to the children. + // We can't do this "as we go" in the previous traversal, because by the + // time we realize we need to defer a perspective transform for an APZC, + // we may already have processed a previous layer (including children + // found in its subtree) that shares that APZC. + if (!state.mPerspectiveTransformsDeferredToChildren.empty()) { + ForEachNode( + mRootNode.get(), [&state](HitTestingTreeNode* aNode) { + AsyncPanZoomController* apzc = aNode->GetApzc(); + if (!apzc) { + return; + } + if (!aNode->IsPrimaryHolder()) { + return; + } + + AsyncPanZoomController* parent = apzc->GetParent(); + if (!parent) { + return; + } + + auto it = + state.mPerspectiveTransformsDeferredToChildren.find(parent); + if (it != state.mPerspectiveTransformsDeferredToChildren.end()) { + apzc->SetAncestorTransform(AncestorTransform{ + it->second * apzc->GetAncestorTransform(), false}); + } + }); + } + + // Remove any layers ids for which we no longer have content from + // mDetachedLayersIds. + for (auto iter = mDetachedLayersIds.begin(); + iter != mDetachedLayersIds.end();) { + // unordered_set::erase() invalidates the iterator pointing to the + // element being erased, but returns an iterator to the next element. + if (seenLayersIds.find(*iter) == seenLayersIds.end()) { + iter = mDetachedLayersIds.erase(iter); + } else { + ++iter; + } + } + } + + // We do not support tree structures where the root node has siblings. + MOZ_ASSERT(!(mRootNode && mRootNode->GetPrevSibling())); + + { // scope lock and update our mApzcMap before we destroy all the unused + // APZC instances + MutexAutoLock lock(mMapLock); + mApzcMap = std::move(state.mApzcMap); + + for (auto& mapping : mApzcMap) { + AsyncPanZoomController* parent = mapping.second.apzc->GetParent(); + mapping.second.parent = parent ? Some(parent->GetGuid()) : Nothing(); + } + + mScrollThumbInfo.clear(); + // For non-webrender, state.mScrollThumbs will be empty so this will be a + // no-op. + for (HitTestingTreeNode* thumb : state.mScrollThumbs) { + MOZ_ASSERT(thumb->IsScrollThumbNode()); + ScrollableLayerGuid targetGuid(thumb->GetLayersId(), 0, + thumb->GetScrollTargetId()); + auto it = state.mScrollTargets.find(targetGuid); + if (it == state.mScrollTargets.end()) { + // It could be that |thumb| is a scrollthumb for content which didn't + // have an APZC, for example if the content isn't layerized. Regardless, + // we can't async-scroll it so we don't need to worry about putting it + // in mScrollThumbInfo. + continue; + } + HitTestingTreeNode* target = it->second; + mScrollThumbInfo.emplace_back( + *(thumb->GetScrollbarAnimationId()), thumb->GetTransform(), + thumb->GetScrollbarData(), targetGuid, target->GetTransform(), + target->IsAncestorOf(thumb)); + } + + mRootScrollbarInfo = std::move(state.mRootScrollbarInfo); + mFixedPositionInfo = std::move(state.mFixedPositionInfo); + mStickyPositionInfo = std::move(state.mStickyPositionInfo); + } + + for (size_t i = 0; i < state.mNodesToDestroy.Length(); i++) { + APZCTM_LOG("Destroying node at %p with APZC %p\n", + state.mNodesToDestroy[i].get(), + state.mNodesToDestroy[i]->GetApzc()); + state.mNodesToDestroy[i]->Destroy(); + } + + APZCTM_LOG("APZCTreeManager (%p)\n", this); + if (mRootNode && MOZ_LOG_TEST(sApzMgrLog, LogLevel::Debug)) { + mRootNode->Dump(" "); + } + SendSubtreeTransformsToChromeMainThread(nullptr); +} + +void APZCTreeManager::UpdateFocusState(LayersId aRootLayerTreeId, + LayersId aOriginatingLayersId, + const FocusTarget& aFocusTarget) { + AssertOnUpdaterThread(); + + if (!StaticPrefs::apz_keyboard_enabled_AtStartup()) { + return; + } + + mFocusState.Update(aRootLayerTreeId, aOriginatingLayersId, aFocusTarget); +} + +void APZCTreeManager::UpdateHitTestingTree(Layer* aRoot, bool aIsFirstPaint, + LayersId aOriginatingLayersId, + uint32_t aPaintSequenceNumber) { + AssertOnUpdaterThread(); + + LayerMetricsWrapper root(aRoot); + UpdateHitTestingTreeImpl(root, aIsFirstPaint, aOriginatingLayersId, + aPaintSequenceNumber); +} + +void APZCTreeManager::UpdateHitTestingTree( + const WebRenderScrollDataWrapper& aScrollWrapper, bool aIsFirstPaint, + LayersId aOriginatingLayersId, uint32_t aPaintSequenceNumber) { + AssertOnUpdaterThread(); + + UpdateHitTestingTreeImpl(aScrollWrapper, aIsFirstPaint, aOriginatingLayersId, + aPaintSequenceNumber); +} + +void APZCTreeManager::SampleForWebRender(const Maybe& aVsyncId, + wr::TransactionWrapper& aTxn, + const SampleTime& aSampleTime) { + AssertOnSamplerThread(); + MutexAutoLock lock(mMapLock); + + RefPtr wrBridgeParent; + RefPtr controller; + CompositorBridgeParent::CallWithIndirectShadowTree( + mRootLayersId, [&](LayerTreeState& aState) -> void { + controller = aState.GetCompositorController(); + wrBridgeParent = aState.mWrBridge; + }); + + bool activeAnimations = AdvanceAnimationsInternal(lock, aSampleTime); + if (activeAnimations && controller) { + controller->ScheduleRenderOnCompositorThread(); + } + + nsTArray transforms; + + // Sample async transforms on scrollable layers. + for (const auto& mapping : mApzcMap) { + AsyncPanZoomController* apzc = mapping.second.apzc; + + const AsyncTransformComponents asyncTransformComponents = + apzc->GetZoomAnimationId() + ? AsyncTransformComponents{AsyncTransformComponent::eLayout} + : LayoutAndVisual; + ParentLayerPoint layerTranslation = + apzc->GetCurrentAsyncTransform(AsyncPanZoomController::eForCompositing, + asyncTransformComponents) + .mTranslation; + + if (Maybe payload = apzc->NotifyScrollSampling()) { + if (wrBridgeParent && aVsyncId) { + wrBridgeParent->AddPendingScrollPayload(*payload, *aVsyncId); + } + } + + if (Maybe zoomAnimationId = apzc->GetZoomAnimationId()) { + // for now we only support zooming on root content APZCs + MOZ_ASSERT(apzc->IsRootContent()); + + LayoutDeviceToParentLayerScale zoom = apzc->GetCurrentPinchZoomScale( + AsyncPanZoomController::eForCompositing); + + AsyncTransform asyncVisualTransform = apzc->GetCurrentAsyncTransform( + AsyncPanZoomController::eForCompositing, + AsyncTransformComponents{AsyncTransformComponent::eVisual}); + + transforms.AppendElement(wr::ToWrTransformProperty( + *zoomAnimationId, LayoutDeviceToParentLayerMatrix4x4::Scaling( + zoom.scale, zoom.scale, 1.0f) * + AsyncTransformComponentMatrix::Translation( + asyncVisualTransform.mTranslation))); + + aTxn.UpdateIsTransformAsyncZooming(*zoomAnimationId, + apzc->IsAsyncZooming()); + } + + layerTranslation = + apzc->GetOverscrollTransform(AsyncPanZoomController::eForCompositing) + .TransformPoint(layerTranslation); + + // If layerTranslation includes only the layout component of the async + // transform then it has not been scaled by the async zoom, so we want to + // divide it by the resolution. If layerTranslation includes the visual + // component, then we should use the pinch zoom scale, which includes the + // async zoom. However, we only use LayoutAndVisual for non-zoomable APZCs, + // so it makes no difference. + LayoutDeviceToParentLayerScale resolution = + apzc->GetCumulativeResolution().ToScaleFactor() * + LayerToParentLayerScale(1.0f); + // The positive translation means the painted content is supposed to + // move down (or to the right), and that corresponds to a reduction in + // the scroll offset. Since we are effectively giving WR the async + // scroll delta here, we want to negate the translation. + LayoutDevicePoint asyncScrollDelta = -layerTranslation / resolution; + aTxn.UpdateScrollPosition(wr::AsPipelineId(apzc->GetGuid().mLayersId), + apzc->GetGuid().mScrollId, + wr::ToLayoutPoint(asyncScrollDelta)); + +#if defined(MOZ_WIDGET_ANDROID) + // Send the root frame metrics to java through the UIController + RefPtr uiController = + UiCompositorControllerParent::GetFromRootLayerTreeId(mRootLayersId); + if (uiController && + apzc->UpdateRootFrameMetricsIfChanged(mLastRootMetrics)) { + uiController->NotifyUpdateScreenMetrics(mLastRootMetrics); + } +#endif + } + + // Now collect all the async transforms needed for the scrollthumbs. + for (const ScrollThumbInfo& info : mScrollThumbInfo) { + auto it = mApzcMap.find(info.mTargetGuid); + if (it == mApzcMap.end()) { + // It could be that |info| is a scrollthumb for content which didn't + // have an APZC, for example if the content isn't layerized. Regardless, + // we can't async-scroll it so we don't need to worry about putting it + // in mScrollThumbInfo. + continue; + } + AsyncPanZoomController* scrollTargetApzc = it->second.apzc; + MOZ_ASSERT(scrollTargetApzc); + LayerToParentLayerMatrix4x4 transform = + scrollTargetApzc->CallWithLastContentPaintMetrics( + [&](const FrameMetrics& aMetrics) { + return ComputeTransformForScrollThumb( + info.mThumbTransform * AsyncTransformMatrix(), + info.mTargetTransform.ToUnknownMatrix(), scrollTargetApzc, + aMetrics, info.mThumbData, info.mTargetIsAncestor, nullptr); + }); + transforms.AppendElement( + wr::ToWrTransformProperty(info.mThumbAnimationId, transform)); + } + + // Move the root scrollbar in response to the dynamic toolbar transition. + for (const RootScrollbarInfo& info : mRootScrollbarInfo) { + // We only care about the horizontal scrollbar. + if (info.mScrollDirection == ScrollDirection::eHorizontal) { + ScreenPoint translation = + apz::ComputeFixedMarginsOffset(GetCompositorFixedLayerMargins(lock), + SideBits::eBottom, ScreenMargin()); + + LayerToParentLayerMatrix4x4 transform = + LayerToParentLayerMatrix4x4::Translation(ViewAs( + translation, PixelCastJustification::ScreenIsParentLayerForRoot)); + + transforms.AppendElement( + wr::ToWrTransformProperty(info.mScrollbarAnimationId, transform)); + } + } + + for (const FixedPositionInfo& info : mFixedPositionInfo) { + MOZ_ASSERT(info.mFixedPositionAnimationId.isSome()); + if (!IsFixedToRootContent(info, lock)) { + continue; + } + + ScreenPoint translation = apz::ComputeFixedMarginsOffset( + GetCompositorFixedLayerMargins(lock), info.mFixedPosSides, + mGeckoFixedLayerMargins); + + LayerToParentLayerMatrix4x4 transform = + LayerToParentLayerMatrix4x4::Translation(ViewAs( + translation, PixelCastJustification::ScreenIsParentLayerForRoot)); + + transforms.AppendElement( + wr::ToWrTransformProperty(*info.mFixedPositionAnimationId, transform)); + } + + for (const StickyPositionInfo& info : mStickyPositionInfo) { + MOZ_ASSERT(info.mStickyPositionAnimationId.isSome()); + SideBits sides = SidesStuckToRootContent(info, lock); + if (sides == SideBits::eNone) { + continue; + } + + ScreenPoint translation = apz::ComputeFixedMarginsOffset( + GetCompositorFixedLayerMargins(lock), sides, + // For sticky layers, we don't need to factor + // mGeckoFixedLayerMargins because Gecko doesn't shift the + // position of sticky elements for dynamic toolbar movements. + ScreenMargin()); + + LayerToParentLayerMatrix4x4 transform = + LayerToParentLayerMatrix4x4::Translation(ViewAs( + translation, PixelCastJustification::ScreenIsParentLayerForRoot)); + + transforms.AppendElement( + wr::ToWrTransformProperty(*info.mStickyPositionAnimationId, transform)); + } + + aTxn.AppendTransformProperties(transforms); +} + +bool APZCTreeManager::AdvanceAnimations(const SampleTime& aSampleTime) { + MutexAutoLock lock(mMapLock); + return AdvanceAnimationsInternal(lock, aSampleTime); +} + +ParentLayerRect APZCTreeManager::ComputeClippedCompositionBounds( + const MutexAutoLock& aProofOfMapLock, ClippedCompositionBoundsMap& aDestMap, + ScrollableLayerGuid aGuid) { + if (auto iter = aDestMap.find(aGuid); iter != aDestMap.end()) { + // We already computed it for this one, early-exit. This might happen + // because on a later iteration of mApzcMap we might encounter an ancestor + // of an APZC that we processed on an earlier iteration. In this case we + // would have computed the ancestor's clipped composition bounds when + // recursing up on the earlier iteration. + return iter->second; + } + + ParentLayerRect bounds = mApzcMap[aGuid].apzc->GetCompositionBounds(); + const auto& mapEntry = mApzcMap.find(aGuid); + MOZ_ASSERT(mapEntry != mApzcMap.end()); + if (mapEntry->second.parent.isNothing()) { + // Recursion base case, where the APZC with guid `aGuid` has no parent. + // In this case, we don't need to clip `bounds` any further and can just + // early exit. + aDestMap.emplace(aGuid, bounds); + return bounds; + } + + ScrollableLayerGuid parentGuid = mapEntry->second.parent.value(); + auto parentBoundsEntry = aDestMap.find(parentGuid); + // If aDestMap doesn't contain the parent entry yet, we recurse to compute + // that one first. + ParentLayerRect parentClippedBounds = + (parentBoundsEntry == aDestMap.end()) + ? ComputeClippedCompositionBounds(aProofOfMapLock, aDestMap, + parentGuid) + : parentBoundsEntry->second; + + // The parent layer's async transform applies to the current layer to take + // `bounds` into the same coordinate space as `parentClippedBounds`. However, + // we're going to do the inverse operation and unapply this transform to + // `parentClippedBounds` to bring it into the same coordinate space as + // `bounds`. + AsyncTransform appliesToLayer = + mApzcMap[parentGuid].apzc->GetCurrentAsyncTransform( + AsyncPanZoomController::eForCompositing); + + // Do the unapplication + LayerRect parentClippedBoundsInParentLayerSpace = + (parentClippedBounds - appliesToLayer.mTranslation) / + appliesToLayer.mScale; + + // And then clip `bounds` by the parent's comp bounds in the current space. + bounds = bounds.Intersect( + ViewAs(parentClippedBoundsInParentLayerSpace, + PixelCastJustification::MovingDownToChildren)); + + // Done! + aDestMap.emplace(aGuid, bounds); + return bounds; +} + +bool APZCTreeManager::AdvanceAnimationsInternal( + const MutexAutoLock& aProofOfMapLock, const SampleTime& aSampleTime) { + ClippedCompositionBoundsMap clippedCompBounds; + bool activeAnimations = false; + for (const auto& mapping : mApzcMap) { + AsyncPanZoomController* apzc = mapping.second.apzc; + // Note that this call is recursive, but it early-exits if called again + // with the same guid. So this loop is still amortized O(n) with respect to + // the number of APZCs. + ParentLayerRect clippedBounds = ComputeClippedCompositionBounds( + aProofOfMapLock, clippedCompBounds, mapping.first); + + apzc->ReportCheckerboard(aSampleTime, clippedBounds); + activeAnimations |= apzc->AdvanceAnimations(aSampleTime); + } + return activeAnimations; +} + +// Compute the clip region to be used for a layer with an APZC. This function +// is only called for layers which actually have scrollable metrics and an APZC. +template +Maybe APZCTreeManager::ComputeClipRegion( + const LayersId& aLayersId, const ScrollNode& aLayer) { + Maybe clipRegion; + if (aLayer.GetClipRect()) { + clipRegion.emplace(*aLayer.GetClipRect()); + } else if (aLayer.Metrics().IsRootContent() && + mAsyncZoomContainerSubtree == Some(aLayersId)) { + // If we are using containerless scrolling, part of the root content + // layers' async transform has been lifted to the async zoom container + // layer. The composition bounds clip, which applies after the async + // transform, needs to be lifted too. Layout code already takes care of + // this for us, we just need to not mess it up by introducing a + // composition bounds clip here, so we leave the clip empty. + } else { + // if there is no clip on this layer (which should only happen for the + // root scrollable layer in a process, or for some of the LayerMetrics + // expansions of a multi-metrics layer), fall back to using the comp + // bounds which should be equivalent. + clipRegion.emplace(RoundedToInt(aLayer.Metrics().GetCompositionBounds())); + } + + return clipRegion; +} + +template +void APZCTreeManager::PrintAPZCInfo(const ScrollNode& aLayer, + const AsyncPanZoomController* apzc) { + const FrameMetrics& metrics = aLayer.Metrics(); + std::stringstream guidStr; + guidStr << apzc->GetGuid(); + mApzcTreeLog << "APZC " << guidStr.str() + << "\tcb=" << metrics.GetCompositionBounds() + << "\tsr=" << metrics.GetScrollableRect() + << (metrics.IsScrollInfoLayer() ? "\tscrollinfo" : "") + << (apzc->HasScrollgrab() ? "\tscrollgrab" : "") << "\t" + << aLayer.Metadata().GetContentDescription().get(); +} + +void APZCTreeManager::AttachNodeToTree(HitTestingTreeNode* aNode, + HitTestingTreeNode* aParent, + HitTestingTreeNode* aNextSibling) { + if (aNextSibling) { + aNextSibling->SetPrevSibling(aNode); + } else if (aParent) { + aParent->SetLastChild(aNode); + } else { + MOZ_ASSERT(!mRootNode); + mRootNode = aNode; + aNode->MakeRoot(); + } +} + +template +static EventRegions GetEventRegions(const ScrollNode& aLayer) { + if (aLayer.Metrics().IsScrollInfoLayer()) { + ParentLayerIntRect compositionBounds( + RoundedToInt(aLayer.Metrics().GetCompositionBounds())); + nsIntRegion hitRegion(compositionBounds.ToUnknownRect()); + EventRegions eventRegions(hitRegion); + eventRegions.mDispatchToContentHitRegion = eventRegions.mHitRegion; + return eventRegions; + } + return aLayer.GetEventRegions(); +} + +already_AddRefed APZCTreeManager::RecycleOrCreateNode( + const RecursiveMutexAutoLock& aProofOfTreeLock, TreeBuildingState& aState, + AsyncPanZoomController* aApzc, LayersId aLayersId) { + // Find a node without an APZC and return it. Note that unless the layer tree + // actually changes, this loop should generally do an early-return on the + // first iteration, so it should be cheap in the common case. + for (int32_t i = aState.mNodesToDestroy.Length() - 1; i >= 0; i--) { + RefPtr node = aState.mNodesToDestroy[i]; + if (node->IsRecyclable(aProofOfTreeLock)) { + aState.mNodesToDestroy.RemoveElementAt(i); + node->RecycleWith(aProofOfTreeLock, aApzc, aLayersId); + return node.forget(); + } + } + RefPtr node = + new HitTestingTreeNode(aApzc, false, aLayersId); + return node.forget(); +} + +void APZCTreeManager::StartScrollbarDrag(const ScrollableLayerGuid& aGuid, + const AsyncDragMetrics& aDragMetrics) { + APZThreadUtils::AssertOnControllerThread(); + + RefPtr apzc = GetTargetAPZC(aGuid); + if (!apzc) { + NotifyScrollbarDragRejected(aGuid); + return; + } + + uint64_t inputBlockId = aDragMetrics.mDragStartSequenceNumber; + mInputQueue->ConfirmDragBlock(inputBlockId, apzc, aDragMetrics); +} + +bool APZCTreeManager::StartAutoscroll(const ScrollableLayerGuid& aGuid, + const ScreenPoint& aAnchorLocation) { + APZThreadUtils::AssertOnControllerThread(); + + RefPtr apzc = GetTargetAPZC(aGuid); + if (!apzc) { + if (XRE_IsGPUProcess()) { + // If we're in the compositor process, the "return false" will be + // ignored because the query comes over the PAPZCTreeManager protocol + // via an async message. In this case, send an explicit rejection + // message to content. + NotifyAutoscrollRejected(aGuid); + } + return false; + } + + apzc->StartAutoscroll(aAnchorLocation); + return true; +} + +void APZCTreeManager::StopAutoscroll(const ScrollableLayerGuid& aGuid) { + APZThreadUtils::AssertOnControllerThread(); + + if (RefPtr apzc = GetTargetAPZC(aGuid)) { + apzc->StopAutoscroll(); + } +} + +void APZCTreeManager::NotifyScrollbarDragInitiated( + uint64_t aDragBlockId, const ScrollableLayerGuid& aGuid, + ScrollDirection aDirection) const { + RefPtr controller = + GetContentController(aGuid.mLayersId); + if (controller) { + controller->NotifyAsyncScrollbarDragInitiated(aDragBlockId, aGuid.mScrollId, + aDirection); + } +} + +void APZCTreeManager::NotifyScrollbarDragRejected( + const ScrollableLayerGuid& aGuid) const { + RefPtr controller = + GetContentController(aGuid.mLayersId); + if (controller) { + controller->NotifyAsyncScrollbarDragRejected(aGuid.mScrollId); + } +} + +void APZCTreeManager::NotifyAutoscrollRejected( + const ScrollableLayerGuid& aGuid) const { + RefPtr controller = + GetContentController(aGuid.mLayersId); + MOZ_ASSERT(controller); + controller->NotifyAsyncAutoscrollRejected(aGuid.mScrollId); +} + +template +void SetHitTestData(HitTestingTreeNode* aNode, const ScrollNode& aLayer, + const Maybe& aClipRegion, + const EventRegionsOverride& aOverrideFlags) { + aNode->SetHitTestData(GetEventRegions(aLayer), aLayer.GetVisibleRegion(), + aLayer.GetRemoteDocumentSize(), + aLayer.GetTransformTyped(), aClipRegion, aOverrideFlags, + aLayer.IsBackfaceHidden(), + !!aLayer.IsAsyncZoomContainer()); +} + +template +HitTestingTreeNode* APZCTreeManager::PrepareNodeForLayer( + const RecursiveMutexAutoLock& aProofOfTreeLock, const ScrollNode& aLayer, + const FrameMetrics& aMetrics, LayersId aLayersId, + const AncestorTransform& aAncestorTransform, HitTestingTreeNode* aParent, + HitTestingTreeNode* aNextSibling, TreeBuildingState& aState) { + bool needsApzc = true; + if (!aMetrics.IsScrollable()) { + needsApzc = false; + } + + // XXX: As a future optimization we can probably stick these things on the + // TreeBuildingState, and update them as we change layers id during the + // traversal + RefPtr geckoContentController; + RefPtr crossProcessSharingController; + CompositorBridgeParent::CallWithIndirectShadowTree( + aLayersId, [&](LayerTreeState& lts) -> void { + geckoContentController = lts.mController; + crossProcessSharingController = lts.CrossProcessSharingController(); + }); + + if (!geckoContentController) { + needsApzc = false; + } + + bool parentHasPerspective = aState.mParentHasPerspective.top(); + + if (Maybe zoomAnimationId = aLayer.GetZoomAnimationId()) { + aState.mZoomAnimationId = zoomAnimationId; + } + + RefPtr node = nullptr; + if (!needsApzc) { + // Note: if layer properties must be propagated to nodes, RecvUpdate in + // LayerTransactionParent.cpp must ensure that APZ will be notified + // when those properties change. + node = RecycleOrCreateNode(aProofOfTreeLock, aState, nullptr, aLayersId); + AttachNodeToTree(node, aParent, aNextSibling); + SetHitTestData(node, aLayer, + (!parentHasPerspective && aLayer.GetClipRect()) + ? Some(ParentLayerIntRegion(*aLayer.GetClipRect())) + : Nothing(), + aState.mOverrideFlags.top()); + node->SetScrollbarData(aLayer.GetScrollbarAnimationId(), + aLayer.GetScrollbarData()); + node->SetFixedPosData(aLayer.GetFixedPositionScrollContainerId(), + aLayer.GetFixedPositionSides(), + aLayer.GetFixedPositionAnimationId()); + node->SetStickyPosData(aLayer.GetStickyScrollContainerId(), + aLayer.GetStickyScrollRangeOuter(), + aLayer.GetStickyScrollRangeInner(), + aLayer.GetStickyPositionAnimationId()); + return node; + } + + AsyncPanZoomController* apzc = nullptr; + // If we get here, aLayer is a scrollable layer and somebody + // has registered a GeckoContentController for it, so we need to ensure + // it has an APZC instance to manage its scrolling. + + // aState.mApzcMap allows reusing the exact same APZC instance for different + // layers with the same FrameMetrics data. This is needed because in some + // cases content that is supposed to scroll together is split into multiple + // layers because of e.g. non-scrolling content interleaved in z-index order. + ScrollableLayerGuid guid(aLayersId, aMetrics.GetPresShellId(), + aMetrics.GetScrollId()); + auto insertResult = aState.mApzcMap.insert(std::make_pair( + guid, + ApzcMapData{static_cast(nullptr), Nothing()})); + if (!insertResult.second) { + apzc = insertResult.first->second.apzc; + PrintAPZCInfo(aLayer, apzc); + } + APZCTM_LOG("Found APZC %p for layer %p with identifiers %" PRIx64 " %" PRId64 + "\n", + apzc, aLayer.GetLayer(), uint64_t(guid.mLayersId), guid.mScrollId); + + // If we haven't encountered a layer already with the same metrics, then we + // need to do the full reuse-or-make-an-APZC algorithm, which is contained + // inside the block below. + if (apzc == nullptr) { + apzc = aLayer.GetApzc(); + + // If the content represented by the scrollable layer has changed (which may + // be possible because of DLBI heuristics) then we don't want to keep using + // the same old APZC for the new content. Also, when reparenting a tab into + // a new window a layer might get moved to a different layer tree with a + // different APZCTreeManager. In these cases we don't want to reuse the same + // APZC, so null it out so we run through the code to find another one or + // create one. + if (apzc && (!apzc->Matches(guid) || !apzc->HasTreeManager(this))) { + apzc = nullptr; + } + + // See if we can find an APZC from the previous tree that matches the + // ScrollableLayerGuid from this layer. If there is one, then we know that + // the layout of the page changed causing the layer tree to be rebuilt, but + // the underlying content for the APZC is still there somewhere. Therefore, + // we want to find the APZC instance and continue using it here. + // + // We particularly want to find the primary-holder node from the previous + // tree that matches, because we don't want that node to get destroyed. If + // it does get destroyed, then the APZC will get destroyed along with it by + // definition, but we want to keep that APZC around in the new tree. + // We leave non-primary-holder nodes in the destroy list because we don't + // care about those nodes getting destroyed. + for (size_t i = 0; i < aState.mNodesToDestroy.Length(); i++) { + RefPtr n = aState.mNodesToDestroy[i]; + if (n->IsPrimaryHolder() && n->GetApzc() && n->GetApzc()->Matches(guid)) { + node = n; + if (apzc != nullptr) { + // If there is an APZC already then it should match the one from the + // old primary-holder node + MOZ_ASSERT(apzc == node->GetApzc()); + } + apzc = node->GetApzc(); + break; + } + } + + // The APZC we get off the layer may have been destroyed previously if the + // layer was inactive or omitted from the layer tree for whatever reason + // from a layers update. If it later comes back it will have a reference to + // a destroyed APZC and so we need to throw that out and make a new one. + bool newApzc = (apzc == nullptr || apzc->IsDestroyed()); + if (newApzc) { + apzc = NewAPZCInstance(aLayersId, geckoContentController); + apzc->SetCompositorController(aState.mCompositorController.get()); + if (crossProcessSharingController) { + apzc->SetMetricsSharingController(crossProcessSharingController); + } else { + apzc->SetMetricsSharingController( + aState.mInProcessSharingController.get()); + } + MOZ_ASSERT(node == nullptr); + node = new HitTestingTreeNode(apzc, true, aLayersId); + } else { + // If we are re-using a node for this layer clear the tree pointers + // so that it doesn't continue pointing to nodes that might no longer + // be in the tree. These pointers will get reset properly as we continue + // building the tree. Also remove it from the set of nodes that are going + // to be destroyed, because it's going to remain active. + aState.mNodesToDestroy.RemoveElement(node); + node->SetPrevSibling(nullptr); + node->SetLastChild(nullptr); + } + + if (aMetrics.IsRootContent()) { + apzc->SetZoomAnimationId(aState.mZoomAnimationId); + aState.mZoomAnimationId = Nothing(); + } + + APZCTM_LOG( + "Using APZC %p for layer %p with identifiers %" PRIx64 " %" PRId64 "\n", + apzc, aLayer.GetLayer(), uint64_t(aLayersId), aMetrics.GetScrollId()); + + apzc->NotifyLayersUpdated(aLayer.Metadata(), aState.mIsFirstPaint, + aLayersId == aState.mOriginatingLayersId); + + // Since this is the first time we are encountering an APZC with this guid, + // the node holding it must be the primary holder. It may be newly-created + // or not, depending on whether it went through the newApzc branch above. + MOZ_ASSERT(node->IsPrimaryHolder() && node->GetApzc() && + node->GetApzc()->Matches(guid)); + + Maybe clipRegion = + parentHasPerspective ? Nothing() : ComputeClipRegion(aLayersId, aLayer); + SetHitTestData(node, aLayer, clipRegion, aState.mOverrideFlags.top()); + apzc->SetAncestorTransform(aAncestorTransform); + + PrintAPZCInfo(aLayer, apzc); + + // Bind the APZC instance into the tree of APZCs + AttachNodeToTree(node, aParent, aNextSibling); + + // For testing, log the parent scroll id of every APZC that has a + // parent. This allows test code to reconstruct the APZC tree. + // Note that we currently only do this for APZCs in the layer tree + // that originated the update, because the only identifying information + // we are logging about APZCs is the scroll id, and otherwise we could + // confuse APZCs from different layer trees with the same scroll id. + if (aLayersId == aState.mOriginatingLayersId) { + if (apzc->HasNoParentWithSameLayersId()) { + aState.mPaintLogger.LogTestData(aMetrics.GetScrollId(), + "hasNoParentWithSameLayersId", true); + } else { + MOZ_ASSERT(apzc->GetParent()); + aState.mPaintLogger.LogTestData(aMetrics.GetScrollId(), + "parentScrollId", + apzc->GetParent()->GetGuid().mScrollId); + } + if (aMetrics.IsRootContent()) { + aState.mPaintLogger.LogTestData(aMetrics.GetScrollId(), "isRootContent", + true); + } + // Note that the async scroll offset is in ParentLayer pixels + aState.mPaintLogger.LogTestData( + aMetrics.GetScrollId(), "asyncScrollOffset", + apzc->GetCurrentAsyncScrollOffset( + AsyncPanZoomController::eForHitTesting)); + aState.mPaintLogger.LogTestData(aMetrics.GetScrollId(), + "hasAsyncKeyScrolled", + apzc->TestHasAsyncKeyScrolled()); + } + + if (newApzc) { + auto it = mZoomConstraints.find(guid); + if (it != mZoomConstraints.end()) { + // We have a zoomconstraints for this guid, apply it. + apzc->UpdateZoomConstraints(it->second); + } else if (!apzc->HasNoParentWithSameLayersId()) { + // This is a sub-APZC, so inherit the zoom constraints from its parent. + // This ensures that if e.g. user-scalable=no was specified, none of the + // APZCs for that subtree allow double-tap to zoom. + apzc->UpdateZoomConstraints(apzc->GetParent()->GetZoomConstraints()); + } + // Otherwise, this is the root of a layers id, but we didn't have a saved + // zoom constraints. Leave it empty for now. + } + + // Add a guid -> APZC mapping for the newly created APZC. + insertResult.first->second.apzc = apzc; + } else { + // We already built an APZC earlier in this tree walk, but we have another + // layer now that will also be using that APZC. The hit-test region on the + // APZC needs to be updated to deal with the new layer's hit region. + + node = RecycleOrCreateNode(aProofOfTreeLock, aState, apzc, aLayersId); + AttachNodeToTree(node, aParent, aNextSibling); + + // Even though different layers associated with a given APZC may be at + // different levels in the layer tree (e.g. one being an uncle of another), + // we require from Layout that the CSS transforms up to their common + // ancestor be roughly the same. There are cases in which the transforms + // are not exactly the same, for example if the parent is container layer + // for an opacity, and this container layer has a resolution-induced scale + // as its base transform and a prescale that is supposed to undo that scale. + // Due to floating point inaccuracies those transforms can end up not quite + // canceling each other. That's why we're using a fuzzy comparison here + // instead of an exact one. + // In addition, two ancestor transforms are allowed to differ if one of + // them contains a perspective transform component and the other does not. + // This represents situations where some content in a scrollable frame + // is subject to a perspective transform and other content does not. + // In such cases, go with the one that does not include the perspective + // component; the perspective transform is remembered and applied to the + // children instead. + if (!aAncestorTransform.CombinedTransform().FuzzyEqualsMultiplicative( + apzc->GetAncestorTransform())) { + typedef TreeBuildingState::DeferredTransformMap::value_type PairType; + if (!aAncestorTransform.ContainsPerspectiveTransform() && + !apzc->AncestorTransformContainsPerspective()) { + NS_ASSERTION(false, + "Two layers that scroll together have different ancestor " + "transforms"); + } else if (!aAncestorTransform.ContainsPerspectiveTransform()) { + aState.mPerspectiveTransformsDeferredToChildren.insert( + PairType{apzc, apzc->GetAncestorTransformPerspective()}); + apzc->SetAncestorTransform(aAncestorTransform); + } else { + aState.mPerspectiveTransformsDeferredToChildren.insert( + PairType{apzc, aAncestorTransform.GetPerspectiveTransform()}); + } + } + + Maybe clipRegion = + parentHasPerspective ? Nothing() : ComputeClipRegion(aLayersId, aLayer); + SetHitTestData(node, aLayer, clipRegion, aState.mOverrideFlags.top()); + } + + // Note: if layer properties must be propagated to nodes, RecvUpdate in + // LayerTransactionParent.cpp must ensure that APZ will be notified + // when those properties change. + node->SetScrollbarData(aLayer.GetScrollbarAnimationId(), + aLayer.GetScrollbarData()); + node->SetFixedPosData(aLayer.GetFixedPositionScrollContainerId(), + aLayer.GetFixedPositionSides(), + aLayer.GetFixedPositionAnimationId()); + node->SetStickyPosData(aLayer.GetStickyScrollContainerId(), + aLayer.GetStickyScrollRangeOuter(), + aLayer.GetStickyScrollRangeInner(), + aLayer.GetStickyPositionAnimationId()); + return node; +} + +template +static bool WillHandleInput(const PanGestureOrScrollWheelInput& aPanInput) { + if (!XRE_IsParentProcess() || !NS_IsMainThread()) { + return true; + } + + WidgetWheelEvent wheelEvent = aPanInput.ToWidgetEvent(nullptr); + return APZInputBridge::ActionForWheelEvent(&wheelEvent).isSome(); +} + +/*static*/ +void APZCTreeManager::FlushApzRepaints(LayersId aLayersId) { + // Previously, paints were throttled and therefore this method was used to + // ensure any pending paints were flushed. Now, paints are flushed + // immediately, so it is safe to simply send a notification now. + APZCTM_LOG("Flushing repaints for layers id 0x%" PRIx64 "\n", + uint64_t(aLayersId)); + RefPtr controller = GetContentController(aLayersId); +#ifndef MOZ_WIDGET_ANDROID + // On Android, this code is run in production and may actually get a nullptr + // controller here. On other platforms this code is test-only and should never + // get a nullptr. + MOZ_ASSERT(controller); +#endif + if (controller) { + controller->DispatchToRepaintThread(NewRunnableMethod( + "layers::GeckoContentController::NotifyFlushComplete", controller, + &GeckoContentController::NotifyFlushComplete)); + } +} + +void APZCTreeManager::MarkAsDetached(LayersId aLayersId) { + RecursiveMutexAutoLock lock(mTreeLock); + mDetachedLayersIds.insert(aLayersId); +} + +static bool HasNonLockModifier(Modifiers aModifiers) { + return (aModifiers & (MODIFIER_ALT | MODIFIER_ALTGRAPH | MODIFIER_CONTROL | + MODIFIER_FN | MODIFIER_META | MODIFIER_SHIFT | + MODIFIER_SYMBOL | MODIFIER_OS)) != 0; +} + +APZEventResult APZCTreeManager::ReceiveInputEvent(InputData& aEvent) { + APZThreadUtils::AssertOnControllerThread(); + APZEventResult result; + + // Use a RAII class for updating the focus sequence number of this event + AutoFocusSequenceNumberSetter focusSetter(mFocusState, aEvent); + + CompositorHitTestInfo hitResult = CompositorHitTestInvisibleToHit; + switch (aEvent.mInputType) { + case MULTITOUCH_INPUT: { + MultiTouchInput& touchInput = aEvent.AsMultiTouchInput(); + result = ProcessTouchInput(touchInput); + break; + } + case MOUSE_INPUT: { + MouseInput& mouseInput = aEvent.AsMouseInput(); + mouseInput.mHandledByAPZ = true; + + SetCurrentMousePosition(mouseInput.mOrigin); + + bool startsDrag = DragTracker::StartsDrag(mouseInput); + if (startsDrag) { + // If this is the start of a drag we need to unambiguously know if it's + // going to land on a scrollbar or not. We can't apply an untransform + // here without knowing that, so we need to ensure the untransform is + // a no-op. + FlushRepaintsToClearScreenToGeckoTransform(); + } + + HitTestResult hit = GetTargetAPZC(mouseInput.mOrigin); + aEvent.mLayersId = hit.mLayersId; + hitResult = hit.mHitResult; + bool hitScrollbar = (bool)hit.mScrollbarNode; + + // When the mouse is outside the window we still want to handle dragging + // but we won't find an APZC. Fallback to root APZC then. + { // scope lock + RecursiveMutexAutoLock lock(mTreeLock); + if (!hit.mTargetApzc && mRootNode) { + hit.mTargetApzc = mRootNode->GetApzc(); + } + } + + if (hit.mTargetApzc) { + if (StaticPrefs::apz_test_logging_enabled() && + mouseInput.mType == MouseInput::MOUSE_HITTEST) { + ScrollableLayerGuid guid = hit.mTargetApzc->GetGuid(); + + MutexAutoLock lock(mTestDataLock); + auto it = mTestData.find(guid.mLayersId); + MOZ_ASSERT(it != mTestData.end()); + it->second->RecordHitResult(mouseInput.mOrigin, hitResult, + guid.mLayersId, guid.mScrollId); + } + + TargetConfirmationFlags confFlags{hitResult}; + result.mStatus = mInputQueue->ReceiveInputEvent( + hit.mTargetApzc, confFlags, mouseInput, &result.mInputBlockId); + + // If we're starting an async scrollbar drag + bool apzDragEnabled = StaticPrefs::apz_drag_enabled(); + if (apzDragEnabled && startsDrag && hit.mScrollbarNode && + hit.mScrollbarNode->IsScrollThumbNode() && + hit.mScrollbarNode->GetScrollbarData().mThumbIsAsyncDraggable) { + SetupScrollbarDrag(mouseInput, hit.mScrollbarNode, + hit.mTargetApzc.get()); + } + + if (result.mStatus == nsEventStatus_eConsumeDoDefault) { + // This input event is part of a drag block, so whether or not it is + // directed at a scrollbar depends on whether the drag block started + // on a scrollbar. + hitScrollbar = mInputQueue->IsDragOnScrollbar(hitScrollbar); + } + + // Update the out-parameters so they are what the caller expects. + hit.mTargetApzc->GetGuid(&result.mTargetGuid); + result.mHandledResult = hit.HandledByRoot(); + + if (!hitScrollbar) { + // The input was not targeted at a scrollbar, so we untransform it + // like we do for other content. Scrollbars are "special" because they + // have special handling in AsyncCompositionManager when resolution is + // applied. TODO: we should find a better way to deal with this. + ScreenToParentLayerMatrix4x4 transformToApzc = + GetScreenToApzcTransform(hit.mTargetApzc); + ParentLayerToScreenMatrix4x4 transformToGecko = + GetApzcToGeckoTransform(hit.mTargetApzc); + ScreenToScreenMatrix4x4 outTransform = + transformToApzc * transformToGecko; + Maybe untransformedRefPoint = + UntransformBy(outTransform, mouseInput.mOrigin); + if (untransformedRefPoint) { + mouseInput.mOrigin = *untransformedRefPoint; + } + } else { + // Likewise, if the input was targeted at a scrollbar, we don't want + // to apply the callback transform in the main thread, so we remove + // the scrollid from the guid. We need to keep the layersId intact so + // that the response from the child process doesn't get discarded. + result.mTargetGuid.mScrollId = ScrollableLayerGuid::NULL_SCROLL_ID; + } + } + break; + } + case SCROLLWHEEL_INPUT: { + FlushRepaintsToClearScreenToGeckoTransform(); + + // Do this before early return for Fission hit testing. + ScrollWheelInput& wheelInput = aEvent.AsScrollWheelInput(); + HitTestResult hit = GetTargetAPZC(wheelInput.mOrigin); + aEvent.mLayersId = hit.mLayersId; + hitResult = hit.mHitResult; + + wheelInput.mHandledByAPZ = WillHandleInput(wheelInput); + if (!wheelInput.mHandledByAPZ) { + return result; + } + + mOvershootDetector.Update(wheelInput); + + if (hit.mTargetApzc) { + MOZ_ASSERT(hitResult != CompositorHitTestInvisibleToHit); + + if (wheelInput.mAPZAction == APZWheelAction::PinchZoom) { + // The mousewheel may have hit a subframe, but we want to send the + // pinch-zoom events to the root-content APZC. + { + RecursiveMutexAutoLock lock(mTreeLock); + hit.mTargetApzc = + FindRootContentApzcForLayersId(hit.mTargetApzc->GetLayersId()); + } + if (hit.mTargetApzc) { + SynthesizePinchGestureFromMouseWheel(wheelInput, hit.mTargetApzc); + } + result.mStatus = nsEventStatus_eConsumeNoDefault; + return result; + } + + MOZ_ASSERT(wheelInput.mAPZAction == APZWheelAction::Scroll); + + // For wheel events, the call to ReceiveInputEvent below may result in + // scrolling, which changes the async transform. However, the event we + // want to pass to gecko should be the pre-scroll event coordinates, + // transformed into the gecko space. (pre-scroll because the mouse + // cursor is stationary during wheel scrolling, unlike touchmove + // events). Since we just flushed the pending repaints the transform to + // gecko space should only consist of overscroll-cancelling transforms. + ScreenToScreenMatrix4x4 transformToGecko = + GetScreenToApzcTransform(hit.mTargetApzc) * + GetApzcToGeckoTransform(hit.mTargetApzc); + Maybe untransformedOrigin = + UntransformBy(transformToGecko, wheelInput.mOrigin); + + if (!untransformedOrigin) { + return result; + } + + result.mStatus = mInputQueue->ReceiveInputEvent( + hit.mTargetApzc, TargetConfirmationFlags{hitResult}, wheelInput, + &result.mInputBlockId); + + // Update the out-parameters so they are what the caller expects. + hit.mTargetApzc->GetGuid(&result.mTargetGuid); + result.mHandledResult = hit.HandledByRoot(); + wheelInput.mOrigin = *untransformedOrigin; + } + break; + } + case PANGESTURE_INPUT: { + FlushRepaintsToClearScreenToGeckoTransform(); + + // Do this before early return for Fission hit testing. + PanGestureInput& panInput = aEvent.AsPanGestureInput(); + HitTestResult hit = GetTargetAPZC(panInput.mPanStartPoint); + aEvent.mLayersId = hit.mLayersId; + hitResult = hit.mHitResult; + + panInput.mHandledByAPZ = WillHandleInput(panInput); + if (!panInput.mHandledByAPZ) { + return result; + } + + // If/when we enable support for pan inputs off-main-thread, we'll need + // to duplicate this EventStateManager code or something. See the call to + // GetUserPrefsForWheelEvent in IAPZCTreeManager.cpp for why these fields + // are stored separately. + MOZ_ASSERT(NS_IsMainThread()); + WidgetWheelEvent wheelEvent = panInput.ToWidgetEvent(nullptr); + EventStateManager::GetUserPrefsForWheelEvent( + &wheelEvent, &panInput.mUserDeltaMultiplierX, + &panInput.mUserDeltaMultiplierY); + + if (hit.mTargetApzc) { + MOZ_ASSERT(hitResult != CompositorHitTestInvisibleToHit); + + // For pan gesture events, the call to ReceiveInputEvent below may + // result in scrolling, which changes the async transform. However, the + // event we want to pass to gecko should be the pre-scroll event + // coordinates, transformed into the gecko space. (pre-scroll because + // the mouse cursor is stationary during pan gesture scrolling, unlike + // touchmove events). Since we just flushed the pending repaints the + // transform to gecko space should only consist of overscroll-cancelling + // transforms. + ScreenToScreenMatrix4x4 transformToGecko = + GetScreenToApzcTransform(hit.mTargetApzc) * + GetApzcToGeckoTransform(hit.mTargetApzc); + Maybe untransformedStartPoint = + UntransformBy(transformToGecko, panInput.mPanStartPoint); + Maybe untransformedDisplacement = + UntransformVector(transformToGecko, panInput.mPanDisplacement, + panInput.mPanStartPoint); + + if (!untransformedStartPoint || !untransformedDisplacement) { + return result; + } + + result.mStatus = mInputQueue->ReceiveInputEvent( + hit.mTargetApzc, TargetConfirmationFlags{hitResult}, panInput, + &result.mInputBlockId); + + // Update the out-parameters so they are what the caller expects. + hit.mTargetApzc->GetGuid(&result.mTargetGuid); + result.mHandledResult = hit.HandledByRoot(); + panInput.mPanStartPoint = *untransformedStartPoint; + panInput.mPanDisplacement = *untransformedDisplacement; + + panInput.mOverscrollBehaviorAllowsSwipe = + hit.mTargetApzc->OverscrollBehaviorAllowsSwipe(); + } + break; + } + case PINCHGESTURE_INPUT: { + PinchGestureInput& pinchInput = aEvent.AsPinchGestureInput(); + if (HasNonLockModifier(pinchInput.modifiers)) { + APZCTM_LOG("Discarding pinch input due to modifiers 0x%x\n", + pinchInput.modifiers); + return result; + } + + HitTestResult hit = GetTargetAPZC(pinchInput.mFocusPoint); + aEvent.mLayersId = hit.mLayersId; + hitResult = hit.mHitResult; + + // We always handle pinch gestures as pinch zooms. + pinchInput.mHandledByAPZ = true; + + if (hit.mTargetApzc) { + MOZ_ASSERT(hitResult != CompositorHitTestInvisibleToHit); + + if (!hit.mTargetApzc->IsRootContent()) { + hit.mTargetApzc = FindZoomableApzc(hit.mTargetApzc); + } + } + + if (hit.mTargetApzc) { + ScreenToScreenMatrix4x4 outTransform = + GetScreenToApzcTransform(hit.mTargetApzc) * + GetApzcToGeckoTransform(hit.mTargetApzc); + Maybe untransformedFocusPoint = + UntransformBy(outTransform, pinchInput.mFocusPoint); + + if (!untransformedFocusPoint) { + return result; + } + + result.mStatus = mInputQueue->ReceiveInputEvent( + hit.mTargetApzc, TargetConfirmationFlags{hitResult}, pinchInput, + &result.mInputBlockId); + + // Update the out-parameters so they are what the caller expects. + hit.mTargetApzc->GetGuid(&result.mTargetGuid); + result.mHandledResult = hit.HandledByRoot(); + pinchInput.mFocusPoint = *untransformedFocusPoint; + } + break; + } + case TAPGESTURE_INPUT: { // note: no one currently sends these + TapGestureInput& tapInput = aEvent.AsTapGestureInput(); + HitTestResult hit = GetTargetAPZC(tapInput.mPoint); + aEvent.mLayersId = hit.mLayersId; + hitResult = hit.mHitResult; + + if (hit.mTargetApzc) { + MOZ_ASSERT(hitResult != CompositorHitTestInvisibleToHit); + + ScreenToScreenMatrix4x4 outTransform = + GetScreenToApzcTransform(hit.mTargetApzc) * + GetApzcToGeckoTransform(hit.mTargetApzc); + Maybe untransformedPoint = + UntransformBy(outTransform, tapInput.mPoint); + + if (!untransformedPoint) { + return result; + } + + result.mStatus = mInputQueue->ReceiveInputEvent( + hit.mTargetApzc, TargetConfirmationFlags{hitResult}, tapInput, + &result.mInputBlockId); + + // Update the out-parameters so they are what the caller expects. + hit.mTargetApzc->GetGuid(&result.mTargetGuid); + result.mHandledResult = hit.HandledByRoot(); + tapInput.mPoint = *untransformedPoint; + } + break; + } + case KEYBOARD_INPUT: { + // Disable async keyboard scrolling when accessibility.browsewithcaret is + // enabled + if (!StaticPrefs::apz_keyboard_enabled_AtStartup() || + StaticPrefs::accessibility_browsewithcaret()) { + APZ_KEY_LOG("Skipping key input from invalid prefs\n"); + return result; + } + + KeyboardInput& keyInput = aEvent.AsKeyboardInput(); + + // Try and find a matching shortcut for this keyboard input + Maybe shortcut = mKeyboardMap.FindMatch(keyInput); + + if (!shortcut) { + APZ_KEY_LOG("Skipping key input with no shortcut\n"); + + // If we don't have a shortcut for this key event, then we can keep our + // focus only if we know there are no key event listeners for this + // target + if (mFocusState.CanIgnoreKeyboardShortcutMisses()) { + focusSetter.MarkAsNonFocusChanging(); + } + return result; + } + + // Check if this shortcut needs to be dispatched to content. Anything + // matching this is assumed to be able to change focus. + if (shortcut->mDispatchToContent) { + APZ_KEY_LOG("Skipping key input with dispatch-to-content shortcut\n"); + return result; + } + + // We know we have an action to execute on whatever is the current focus + // target + const KeyboardScrollAction& action = shortcut->mAction; + + // The current focus target depends on which direction the scroll is to + // happen + Maybe targetGuid; + switch (action.mType) { + case KeyboardScrollAction::eScrollCharacter: { + targetGuid = mFocusState.GetHorizontalTarget(); + break; + } + case KeyboardScrollAction::eScrollLine: + case KeyboardScrollAction::eScrollPage: + case KeyboardScrollAction::eScrollComplete: { + targetGuid = mFocusState.GetVerticalTarget(); + break; + } + } + + // If we don't have a scroll target then either we have a stale focus + // target, the focused element has event listeners, or the focused element + // doesn't have a layerized scroll frame. In any case we need to dispatch + // to content. + if (!targetGuid) { + APZ_KEY_LOG("Skipping key input with no current focus target\n"); + return result; + } + + RefPtr targetApzc = + GetTargetAPZC(targetGuid->mLayersId, targetGuid->mScrollId); + + if (!targetApzc) { + APZ_KEY_LOG("Skipping key input with focus target but no APZC\n"); + return result; + } + + // Attach the keyboard scroll action to the input event for processing + // by the input queue. + keyInput.mAction = action; + + APZ_KEY_LOG("Dispatching key input with apzc=%p\n", targetApzc.get()); + + // Dispatch the event to the input queue. + result.mStatus = mInputQueue->ReceiveInputEvent( + targetApzc, TargetConfirmationFlags{true}, keyInput, + &result.mInputBlockId); + + // Any keyboard event that is dispatched to the input queue at this point + // should have been consumed + MOZ_ASSERT(result.mStatus == nsEventStatus_eConsumeDoDefault || + result.mStatus == nsEventStatus_eConsumeNoDefault); + + keyInput.mHandledByAPZ = true; + focusSetter.MarkAsNonFocusChanging(); + + break; + } + } + return result; +} + +static TouchBehaviorFlags ConvertToTouchBehavior( + const CompositorHitTestInfo& info) { + TouchBehaviorFlags result = AllowedTouchBehavior::UNKNOWN; + if (info == CompositorHitTestInvisibleToHit) { + result = AllowedTouchBehavior::NONE; + } else if (info.contains(CompositorHitTestFlags::eIrregularArea)) { + // Note that eApzAwareListeners and eInactiveScrollframe are similar + // to eIrregularArea in some respects, but are not relevant for the + // purposes of this function, which deals specifically with touch-action. + result = AllowedTouchBehavior::UNKNOWN; + } else { + result = AllowedTouchBehavior::VERTICAL_PAN | + AllowedTouchBehavior::HORIZONTAL_PAN | + AllowedTouchBehavior::PINCH_ZOOM | + AllowedTouchBehavior::DOUBLE_TAP_ZOOM; + if (info.contains(CompositorHitTestFlags::eTouchActionPanXDisabled)) { + result &= ~AllowedTouchBehavior::HORIZONTAL_PAN; + } + if (info.contains(CompositorHitTestFlags::eTouchActionPanYDisabled)) { + result &= ~AllowedTouchBehavior::VERTICAL_PAN; + } + if (info.contains(CompositorHitTestFlags::eTouchActionPinchZoomDisabled)) { + result &= ~AllowedTouchBehavior::PINCH_ZOOM; + } + if (info.contains( + CompositorHitTestFlags::eTouchActionDoubleTapZoomDisabled)) { + result &= ~AllowedTouchBehavior::DOUBLE_TAP_ZOOM; + } + } + return result; +} + +APZCTreeManager::HitTestResult APZCTreeManager::GetTouchInputBlockAPZC( + const MultiTouchInput& aEvent, + nsTArray* aOutTouchBehaviors) { + HitTestResult hit; + if (aEvent.mTouches.Length() == 0) { + return hit; + } + + FlushRepaintsToClearScreenToGeckoTransform(); + + hit = GetTargetAPZC(aEvent.mTouches[0].mScreenPoint); + // Don't set a layers id on multi-touch events. + if (aEvent.mTouches.Length() != 1) { + hit.mLayersId = LayersId{0}; + } + + if (aOutTouchBehaviors) { + aOutTouchBehaviors->AppendElement(ConvertToTouchBehavior(hit.mHitResult)); + } + for (size_t i = 1; i < aEvent.mTouches.Length(); i++) { + HitTestResult hit2 = GetTargetAPZC(aEvent.mTouches[i].mScreenPoint); + if (aOutTouchBehaviors) { + aOutTouchBehaviors->AppendElement( + ConvertToTouchBehavior(hit2.mHitResult)); + } + hit.mTargetApzc = GetZoomableTarget(hit.mTargetApzc, hit2.mTargetApzc); + APZCTM_LOG("Using APZC %p as the root APZC for multi-touch\n", + hit.mTargetApzc.get()); + // A multi-touch gesture will not be a scrollbar drag, even if the + // first touch point happened to hit a scrollbar. + hit.mScrollbarNode.Clear(); + + // XXX we should probably be combining the hit results from the different + // touch points somehow, instead of just using the last one. + hit.mHitResult = hit2.mHitResult; + } + + return hit; +} + +APZEventResult APZCTreeManager::ProcessTouchInput(MultiTouchInput& aInput) { + APZEventResult result; // mStatus == eIgnore + aInput.mHandledByAPZ = true; + nsTArray touchBehaviors; + HitTestingTreeNodeAutoLock hitScrollbarNode; + if (aInput.mType == MultiTouchInput::MULTITOUCH_START) { + // If we are panned into overscroll and a second finger goes down, + // ignore that second touch point completely. The touch-start for it is + // dropped completely; subsequent touch events until the touch-end for it + // will have this touch point filtered out. + // (By contrast, if we're in overscroll but not panning, such as after + // putting two fingers down during an overscroll animation, we process the + // second touch and proceed to pinch.) + if (mTouchBlockHitResult.mTargetApzc && + mTouchBlockHitResult.mTargetApzc->IsInPanningState() && + BuildOverscrollHandoffChain(mTouchBlockHitResult.mTargetApzc) + ->HasOverscrolledApzc()) { + if (mRetainedTouchIdentifier == -1) { + mRetainedTouchIdentifier = + mTouchBlockHitResult.mTargetApzc->GetLastTouchIdentifier(); + } + result.mStatus = nsEventStatus_eConsumeNoDefault; + return result; + } + + HitTestResult hit = GetTouchInputBlockAPZC(aInput, &touchBehaviors); + // Repopulate mTouchBlockHitResult with the fields we care about. + mTouchBlockHitResult = HitTestResult(); + mTouchBlockHitResult.mTargetApzc = hit.mTargetApzc; + mTouchBlockHitResult.mHitResult = hit.mHitResult; + mTouchBlockHitResult.mFixedPosSides = hit.mFixedPosSides; + if (hit.mLayersId.IsValid()) { + // Check for validity because we won't get a layers id for multi-touch + // events. + aInput.mLayersId = hit.mLayersId; + } + hitScrollbarNode = std::move(hit.mScrollbarNode); + + // Check if this event starts a scrollbar touch-drag. The conditions + // checked are similar to the ones we check for MOUSE_INPUT starting + // a scrollbar mouse-drag. + mInScrollbarTouchDrag = + StaticPrefs::apz_drag_enabled() && + StaticPrefs::apz_drag_touch_enabled() && hitScrollbarNode && + hitScrollbarNode->IsScrollThumbNode() && + hitScrollbarNode->GetScrollbarData().mThumbIsAsyncDraggable; + + MOZ_ASSERT(touchBehaviors.Length() == aInput.mTouches.Length()); + for (size_t i = 0; i < touchBehaviors.Length(); i++) { + APZCTM_LOG("Touch point has allowed behaviours 0x%02x\n", + touchBehaviors[i]); + if (touchBehaviors[i] == AllowedTouchBehavior::UNKNOWN) { + // If there's any unknown items in the list, throw it out and we'll + // wait for the main thread to send us a notification. + touchBehaviors.Clear(); + break; + } + } + } else if (mTouchBlockHitResult.mTargetApzc) { + APZCTM_LOG("Re-using APZC %p as continuation of event block\n", + mTouchBlockHitResult.mTargetApzc.get()); + } + + if (mInScrollbarTouchDrag) { + result = ProcessTouchInputForScrollbarDrag(aInput, hitScrollbarNode, + mTouchBlockHitResult.mHitResult); + } else { + // If we receive a touch-cancel, it means all touches are finished, so we + // can stop ignoring any that we were ignoring. + if (aInput.mType == MultiTouchInput::MULTITOUCH_CANCEL) { + mRetainedTouchIdentifier = -1; + } + + // If we are currently ignoring any touch points, filter them out from the + // set of touch points included in this event. Note that we modify aInput + // itself, so that the touch points are also filtered out when the caller + // passes the event on to content. + if (mRetainedTouchIdentifier != -1) { + for (size_t j = 0; j < aInput.mTouches.Length(); ++j) { + if (aInput.mTouches[j].mIdentifier != mRetainedTouchIdentifier) { + aInput.mTouches.RemoveElementAt(j); + if (!touchBehaviors.IsEmpty()) { + MOZ_ASSERT(touchBehaviors.Length() > j); + touchBehaviors.RemoveElementAt(j); + } + --j; + } + } + if (aInput.mTouches.IsEmpty()) { + result.mStatus = nsEventStatus_eConsumeNoDefault; + return result; + } + } + + if (mTouchBlockHitResult.mTargetApzc) { + MOZ_ASSERT(mTouchBlockHitResult.mHitResult != + CompositorHitTestInvisibleToHit); + + mTouchBlockHitResult.mTargetApzc->GetGuid(&result.mTargetGuid); + result.mHandledResult = mTouchBlockHitResult.HandledByRoot(); + result.mStatus = mInputQueue->ReceiveInputEvent( + mTouchBlockHitResult.mTargetApzc, + TargetConfirmationFlags{mTouchBlockHitResult.mHitResult}, aInput, + &result.mInputBlockId, &result.mHandledResult, + touchBehaviors.IsEmpty() ? Nothing() + : Some(std::move(touchBehaviors))); + + // For computing the event to pass back to Gecko, use up-to-date + // transforms (i.e. not anything cached in an input block). This ensures + // that transformToApzc and transformToGecko are in sync. + // Note: we are not using ConvertToGecko() here, because we don't + // want to multiply transformToApzc and transformToGecko once + // for each touch point. + ScreenToParentLayerMatrix4x4 transformToApzc = + GetScreenToApzcTransform(mTouchBlockHitResult.mTargetApzc); + ParentLayerToScreenMatrix4x4 transformToGecko = + GetApzcToGeckoTransform(mTouchBlockHitResult.mTargetApzc); + ScreenToScreenMatrix4x4 outTransform = transformToApzc * transformToGecko; + + for (size_t i = 0; i < aInput.mTouches.Length(); i++) { + SingleTouchData& touchData = aInput.mTouches[i]; + Maybe untransformedScreenPoint = + UntransformBy(outTransform, touchData.mScreenPoint); + if (!untransformedScreenPoint) { + result.mStatus = nsEventStatus_eIgnore; + return result; + } + touchData.mScreenPoint = *untransformedScreenPoint; + if (mTouchBlockHitResult.mFixedPosSides != SideBits::eNone) { + MutexAutoLock lock(mMapLock); + touchData.mScreenPoint -= RoundedToInt(apz::ComputeFixedMarginsOffset( + GetCompositorFixedLayerMargins(lock), + mTouchBlockHitResult.mFixedPosSides, mGeckoFixedLayerMargins)); + } + } + } + } + + mTouchCounter.Update(aInput); + + // If it's the end of the touch sequence then clear out variables so we + // don't keep dangling references and leak things. + if (mTouchCounter.GetActiveTouchCount() == 0) { + mTouchBlockHitResult = HitTestResult(); + mRetainedTouchIdentifier = -1; + mInScrollbarTouchDrag = false; + } + + return result; +} + +static MouseInput::MouseType MultiTouchTypeToMouseType( + MultiTouchInput::MultiTouchType aType) { + switch (aType) { + case MultiTouchInput::MULTITOUCH_START: + return MouseInput::MOUSE_DOWN; + case MultiTouchInput::MULTITOUCH_MOVE: + return MouseInput::MOUSE_MOVE; + case MultiTouchInput::MULTITOUCH_END: + case MultiTouchInput::MULTITOUCH_CANCEL: + return MouseInput::MOUSE_UP; + } + MOZ_ASSERT_UNREACHABLE("Invalid multi-touch type"); + return MouseInput::MOUSE_NONE; +} + +APZEventResult APZCTreeManager::ProcessTouchInputForScrollbarDrag( + MultiTouchInput& aTouchInput, + const HitTestingTreeNodeAutoLock& aScrollThumbNode, + const gfx::CompositorHitTestInfo& aHitInfo) { + MOZ_ASSERT(mRetainedTouchIdentifier == -1); + MOZ_ASSERT(mTouchBlockHitResult.mTargetApzc); + MOZ_ASSERT(aTouchInput.mTouches.Length() == 1); + + // Synthesize a mouse event based on the touch event, so that we can + // reuse code in InputQueue and APZC for handling scrollbar mouse-drags. + MouseInput mouseInput{MultiTouchTypeToMouseType(aTouchInput.mType), + MouseInput::PRIMARY_BUTTON, + dom::MouseEvent_Binding::MOZ_SOURCE_TOUCH, + MouseButtonsFlag::ePrimaryFlag, + aTouchInput.mTouches[0].mScreenPoint, + aTouchInput.mTime, + aTouchInput.mTimeStamp, + aTouchInput.modifiers}; + mouseInput.mHandledByAPZ = true; + + TargetConfirmationFlags targetConfirmed{aHitInfo}; + APZEventResult result; + result.mStatus = mInputQueue->ReceiveInputEvent( + mTouchBlockHitResult.mTargetApzc, targetConfirmed, mouseInput, + &result.mInputBlockId); + + // |aScrollThumbNode| is non-null iff. this is the event that starts the drag. + // If so, set up the drag. + if (aScrollThumbNode) { + SetupScrollbarDrag(mouseInput, aScrollThumbNode, + mTouchBlockHitResult.mTargetApzc.get()); + } + + mTouchBlockHitResult.mTargetApzc->GetGuid(&result.mTargetGuid); + result.mHandledResult = mTouchBlockHitResult.HandledByRoot(); + + // Since the input was targeted at a scrollbar: + // - The original touch event (which will be sent on to content) will + // not be untransformed. + // - We don't want to apply the callback transform in the main thread, + // so we remove the scrollid from the guid. + // Both of these match the behaviour of mouse events that target a scrollbar; + // see the code for handling mouse events in ReceiveInputEvent() for + // additional explanation. + result.mTargetGuid.mScrollId = ScrollableLayerGuid::NULL_SCROLL_ID; + + return result; +} + +void APZCTreeManager::SetupScrollbarDrag( + MouseInput& aMouseInput, const HitTestingTreeNodeAutoLock& aScrollThumbNode, + AsyncPanZoomController* aApzc) { + DragBlockState* dragBlock = mInputQueue->GetCurrentDragBlock(); + if (!dragBlock) { + return; + } + + const ScrollbarData& thumbData = aScrollThumbNode->GetScrollbarData(); + MOZ_ASSERT(thumbData.mDirection.isSome()); + + // Record the thumb's position at the start of the drag. + // We snap back to this position if, during the drag, the mouse + // gets sufficiently far away from the scrollbar. + dragBlock->SetInitialThumbPos(thumbData.mThumbStart); + + // Under some conditions, we can confirm the drag block right away. + // Otherwise, we have to wait for a main-thread confirmation. + if (StaticPrefs::apz_drag_initial_enabled() && + // check that the scrollbar's target scroll frame is layerized + aScrollThumbNode->GetScrollTargetId() == aApzc->GetGuid().mScrollId && + !aApzc->IsScrollInfoLayer()) { + uint64_t dragBlockId = dragBlock->GetBlockId(); + // AsyncPanZoomController::HandleInputEvent() will call + // TransformToLocal() on the event, but we need its mLocalOrigin now + // to compute a drag start offset for the AsyncDragMetrics. + aMouseInput.TransformToLocal(aApzc->GetTransformToThis()); + CSSCoord dragStart = + aApzc->ConvertScrollbarPoint(aMouseInput.mLocalOrigin, thumbData); + // ConvertScrollbarPoint() got the drag start offset relative to + // the scroll track. Now get it relative to the thumb. + // ScrollThumbData::mThumbStart stores the offset of the thumb + // relative to the scroll track at the time of the last paint. + // Since that paint, the thumb may have acquired an async transform + // due to async scrolling, so look that up and apply it. + LayerToParentLayerMatrix4x4 thumbTransform; + { + RecursiveMutexAutoLock lock(mTreeLock); + thumbTransform = ComputeTransformForNode(aScrollThumbNode.Get(lock)); + } + // Only consider the translation, since we do not support both + // zooming and scrollbar dragging on any platform. + CSSCoord thumbStart = + thumbData.mThumbStart + + ((*thumbData.mDirection == ScrollDirection::eHorizontal) + ? thumbTransform._41 + : thumbTransform._42); + dragStart -= thumbStart; + + // Content can't prevent scrollbar dragging with preventDefault(), + // so we don't need to wait for a content response. It's important + // to do this before calling ConfirmDragBlock() since that can + // potentially process and consume the block. + dragBlock->SetContentResponse(false); + + NotifyScrollbarDragInitiated(dragBlockId, aApzc->GetGuid(), + *thumbData.mDirection); + + mInputQueue->ConfirmDragBlock( + dragBlockId, aApzc, + AsyncDragMetrics(aApzc->GetGuid().mScrollId, + aApzc->GetGuid().mPresShellId, dragBlockId, dragStart, + *thumbData.mDirection)); + } +} + +void APZCTreeManager::SynthesizePinchGestureFromMouseWheel( + const ScrollWheelInput& aWheelInput, + const RefPtr& aTarget) { + MOZ_ASSERT(aTarget); + + ScreenPoint focusPoint = aWheelInput.mOrigin; + + // Compute span values based on the wheel delta. + ScreenCoord oldSpan = 100; + ScreenCoord newSpan = oldSpan + aWheelInput.mDeltaY; + + // There's no ambiguity as to the target for pinch gesture events. + TargetConfirmationFlags confFlags{true}; + + PinchGestureInput pinchStart{PinchGestureInput::PINCHGESTURE_START, + PinchGestureInput::MOUSEWHEEL, + aWheelInput.mTime, + aWheelInput.mTimeStamp, + ExternalPoint(0, 0), + focusPoint, + oldSpan, + oldSpan, + aWheelInput.modifiers}; + PinchGestureInput pinchScale1{PinchGestureInput::PINCHGESTURE_SCALE, + PinchGestureInput::MOUSEWHEEL, + aWheelInput.mTime, + aWheelInput.mTimeStamp, + ExternalPoint(0, 0), + focusPoint, + oldSpan, + oldSpan, + aWheelInput.modifiers}; + PinchGestureInput pinchScale2{PinchGestureInput::PINCHGESTURE_SCALE, + PinchGestureInput::MOUSEWHEEL, + aWheelInput.mTime, + aWheelInput.mTimeStamp, + ExternalPoint(0, 0), + focusPoint, + oldSpan, + newSpan, + aWheelInput.modifiers}; + PinchGestureInput pinchEnd{PinchGestureInput::PINCHGESTURE_END, + PinchGestureInput::MOUSEWHEEL, + aWheelInput.mTime, + aWheelInput.mTimeStamp, + ExternalPoint(0, 0), + focusPoint, + newSpan, + newSpan, + aWheelInput.modifiers}; + + mInputQueue->ReceiveInputEvent(aTarget, confFlags, pinchStart, nullptr); + mInputQueue->ReceiveInputEvent(aTarget, confFlags, pinchScale1, nullptr); + mInputQueue->ReceiveInputEvent(aTarget, confFlags, pinchScale2, nullptr); + mInputQueue->ReceiveInputEvent(aTarget, confFlags, pinchEnd, nullptr); +} + +void APZCTreeManager::UpdateWheelTransaction(LayoutDeviceIntPoint aRefPoint, + EventMessage aEventMessage) { + APZThreadUtils::AssertOnControllerThread(); + + WheelBlockState* txn = mInputQueue->GetActiveWheelTransaction(); + if (!txn) { + return; + } + + // If the transaction has simply timed out, we don't need to do anything + // else. + if (txn->MaybeTimeout(TimeStamp::Now())) { + return; + } + + switch (aEventMessage) { + case eMouseMove: + case eDragOver: { + ScreenIntPoint point = ViewAs( + aRefPoint, + PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent); + + txn->OnMouseMove(point); + + return; + } + case eKeyPress: + case eKeyUp: + case eKeyDown: + case eMouseUp: + case eMouseDown: + case eMouseDoubleClick: + case eMouseAuxClick: + case eMouseClick: + case eContextMenu: + case eDrop: + txn->EndTransaction(); + return; + default: + break; + } +} + +void APZCTreeManager::ProcessUnhandledEvent(LayoutDeviceIntPoint* aRefPoint, + ScrollableLayerGuid* aOutTargetGuid, + uint64_t* aOutFocusSequenceNumber, + LayersId* aOutLayersId) { + APZThreadUtils::AssertOnControllerThread(); + + // Transform the aRefPoint. + // If the event hits an overscrolled APZC, instruct the caller to ignore it. + PixelCastJustification LDIsScreen = + PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent; + ScreenIntPoint refPointAsScreen = ViewAs(*aRefPoint, LDIsScreen); + HitTestResult hit = GetTargetAPZC(refPointAsScreen); + if (aOutLayersId) { + *aOutLayersId = hit.mLayersId; + } + if (hit.mTargetApzc) { + MOZ_ASSERT(hit.mHitResult != CompositorHitTestInvisibleToHit); + hit.mTargetApzc->GetGuid(aOutTargetGuid); + ScreenToParentLayerMatrix4x4 transformToApzc = + GetScreenToApzcTransform(hit.mTargetApzc); + ParentLayerToScreenMatrix4x4 transformToGecko = + GetApzcToGeckoTransform(hit.mTargetApzc); + ScreenToScreenMatrix4x4 outTransform = transformToApzc * transformToGecko; + Maybe untransformedRefPoint = + UntransformBy(outTransform, refPointAsScreen); + if (untransformedRefPoint) { + *aRefPoint = + ViewAs(*untransformedRefPoint, LDIsScreen); + } + } + + // Update the focus sequence number and attach it to the event + mFocusState.ReceiveFocusChangingEvent(); + *aOutFocusSequenceNumber = mFocusState.LastAPZProcessedEvent(); +} + +void APZCTreeManager::SetKeyboardMap(const KeyboardMap& aKeyboardMap) { + APZThreadUtils::AssertOnControllerThread(); + + mKeyboardMap = aKeyboardMap; +} + +void APZCTreeManager::ZoomToRect(const ScrollableLayerGuid& aGuid, + const CSSRect& aRect, const uint32_t aFlags) { + // We could probably move this to run on the updater thread if needed, but + // either way we should restrict it to a single thread. For now let's use the + // controller thread. + APZThreadUtils::AssertOnControllerThread(); + + RefPtr apzc = GetTargetAPZC(aGuid); + if (apzc) { + apzc->ZoomToRect(aRect, aFlags); + } +} + +void APZCTreeManager::ContentReceivedInputBlock(uint64_t aInputBlockId, + bool aPreventDefault) { + APZThreadUtils::AssertOnControllerThread(); + + mInputQueue->ContentReceivedInputBlock(aInputBlockId, aPreventDefault); +} + +void APZCTreeManager::SetTargetAPZC( + uint64_t aInputBlockId, const nsTArray& aTargets) { + APZThreadUtils::AssertOnControllerThread(); + + RefPtr target = nullptr; + if (aTargets.Length() > 0) { + target = GetTargetAPZC(aTargets[0]); + } + for (size_t i = 1; i < aTargets.Length(); i++) { + RefPtr apzc = GetTargetAPZC(aTargets[i]); + target = GetZoomableTarget(target, apzc); + } + if (InputBlockState* block = mInputQueue->GetBlockForId(aInputBlockId)) { + if (block->AsPinchGestureBlock() && aTargets.Length() == 1) { + target = FindZoomableApzc(target); + } + } + mInputQueue->SetConfirmedTargetApzc(aInputBlockId, target); +} + +void APZCTreeManager::UpdateZoomConstraints( + const ScrollableLayerGuid& aGuid, + const Maybe& aConstraints) { + if (!GetUpdater()->IsUpdaterThread()) { + // This can happen if we're in the UI process and got a call directly from + // nsBaseWidget or from a content process over PAPZCTreeManager. In that + // case we get this call on the compositor thread, which may be different + // from the updater thread. It can also happen in the GPU process if that is + // enabled, since the call will go over PAPZCTreeManager and arrive on the + // compositor thread in the GPU process. + GetUpdater()->RunOnUpdaterThread( + aGuid.mLayersId, + NewRunnableMethod>( + "APZCTreeManager::UpdateZoomConstraints", this, + &APZCTreeManager::UpdateZoomConstraints, aGuid, aConstraints)); + return; + } + + AssertOnUpdaterThread(); + + RecursiveMutexAutoLock lock(mTreeLock); + RefPtr node = GetTargetNode(aGuid, nullptr); + MOZ_ASSERT(!node || node->GetApzc()); // any node returned must have an APZC + + // Propagate the zoom constraints down to the subtree, stopping at APZCs + // which have their own zoom constraints or are in a different layers id. + if (aConstraints) { + APZCTM_LOG("Recording constraints %s for guid %s\n", + ToString(aConstraints.value()).c_str(), ToString(aGuid).c_str()); + mZoomConstraints[aGuid] = aConstraints.ref(); + } else { + APZCTM_LOG("Removing constraints for guid %s\n", ToString(aGuid).c_str()); + mZoomConstraints.erase(aGuid); + } + if (node && aConstraints) { + ForEachNode( + node.get(), [&aConstraints, &node, this](HitTestingTreeNode* aNode) { + if (aNode != node) { + if (AsyncPanZoomController* childApzc = aNode->GetApzc()) { + // We can have subtrees with their own zoom constraints or + // separate layers id - leave these alone. + if (childApzc->HasNoParentWithSameLayersId() || + this->mZoomConstraints.find(childApzc->GetGuid()) != + this->mZoomConstraints.end()) { + return TraversalFlag::Skip; + } + } + } + if (aNode->IsPrimaryHolder()) { + MOZ_ASSERT(aNode->GetApzc()); + aNode->GetApzc()->UpdateZoomConstraints(aConstraints.ref()); + } + return TraversalFlag::Continue; + }); + } +} + +void APZCTreeManager::FlushRepaintsToClearScreenToGeckoTransform() { + // As the name implies, we flush repaint requests for the entire APZ tree in + // order to clear the screen-to-gecko transform (aka the "untransform" applied + // to incoming input events before they can be passed on to Gecko). + // + // The primary reason we do this is to avoid the problem where input events, + // after being untransformed, end up hit-testing differently in Gecko. This + // might happen in cases where the input event lands on content that is async- + // scrolled into view, but Gecko still thinks it is out of view given the + // visible area of a scrollframe. + // + // Another reason we want to clear the untransform is that if our APZ hit-test + // hits a dispatch-to-content region then that's an ambiguous result and we + // need to ask Gecko what actually got hit. In order to do this we need to + // untransform the input event into Gecko space - but to do that we need to + // know which APZC got hit! This leads to a circular dependency; the only way + // to get out of it is to make sure that the untransform for all the possible + // matched APZCs is the same. It is simplest to ensure that by flushing the + // pending repaint requests, which makes all of the untransforms empty (and + // therefore equal). + RecursiveMutexAutoLock lock(mTreeLock); + + ForEachNode(mRootNode.get(), [](HitTestingTreeNode* aNode) { + if (aNode->IsPrimaryHolder()) { + MOZ_ASSERT(aNode->GetApzc()); + aNode->GetApzc()->FlushRepaintForNewInputBlock(); + } + }); +} + +void APZCTreeManager::ClearTree() { + AssertOnUpdaterThread(); + + // Ensure that no references to APZCs are alive in any lingering input + // blocks. This breaks cycles from InputBlockState::mTargetApzc back to + // the InputQueue. + APZThreadUtils::RunOnControllerThread(NewRunnableMethod( + "layers::InputQueue::Clear", mInputQueue, &InputQueue::Clear)); + + RecursiveMutexAutoLock lock(mTreeLock); + + // Collect the nodes into a list, and then destroy each one. + // We can't destroy them as we collect them, because ForEachNode() + // does a pre-order traversal of the tree, and Destroy() nulls out + // the fields needed to reach the children of the node. + nsTArray> nodesToDestroy; + ForEachNode(mRootNode.get(), + [&nodesToDestroy](HitTestingTreeNode* aNode) { + nodesToDestroy.AppendElement(aNode); + }); + + for (size_t i = 0; i < nodesToDestroy.Length(); i++) { + nodesToDestroy[i]->Destroy(); + } + mRootNode = nullptr; + + { + // Also remove references to APZC instances in the map + MutexAutoLock lock(mMapLock); + mApzcMap.clear(); + } + + RefPtr self(this); + NS_DispatchToMainThread( + NS_NewRunnableFunction("layers::APZCTreeManager::ClearTree", [self] { + self->mFlushObserver->Unregister(); + self->mFlushObserver = nullptr; + })); +} + +RefPtr APZCTreeManager::GetRootNode() const { + RecursiveMutexAutoLock lock(mTreeLock); + return mRootNode; +} + +/** + * Transform a displacement from the ParentLayer coordinates of a source APZC + * to the ParentLayer coordinates of a target APZC. + * @param aTreeManager the tree manager for the APZC tree containing |aSource| + * and |aTarget| + * @param aSource the source APZC + * @param aTarget the target APZC + * @param aStartPoint the start point of the displacement + * @param aEndPoint the end point of the displacement + * @return true on success, false if aStartPoint or aEndPoint cannot be + * transformed into target's coordinate space + */ +static bool TransformDisplacement(APZCTreeManager* aTreeManager, + AsyncPanZoomController* aSource, + AsyncPanZoomController* aTarget, + ParentLayerPoint& aStartPoint, + ParentLayerPoint& aEndPoint) { + if (aSource == aTarget) { + return true; + } + + // Convert start and end points to Screen coordinates. + ParentLayerToScreenMatrix4x4 untransformToApzc = + aTreeManager->GetScreenToApzcTransform(aSource).Inverse(); + ScreenPoint screenStart = TransformBy(untransformToApzc, aStartPoint); + ScreenPoint screenEnd = TransformBy(untransformToApzc, aEndPoint); + + // Convert start and end points to aTarget's ParentLayer coordinates. + ScreenToParentLayerMatrix4x4 transformToApzc = + aTreeManager->GetScreenToApzcTransform(aTarget); + Maybe startPoint = + UntransformBy(transformToApzc, screenStart); + Maybe endPoint = UntransformBy(transformToApzc, screenEnd); + if (!startPoint || !endPoint) { + return false; + } + aEndPoint = *endPoint; + aStartPoint = *startPoint; + + return true; +} + +bool APZCTreeManager::DispatchScroll( + AsyncPanZoomController* aPrev, ParentLayerPoint& aStartPoint, + ParentLayerPoint& aEndPoint, + OverscrollHandoffState& aOverscrollHandoffState) { + const OverscrollHandoffChain& overscrollHandoffChain = + aOverscrollHandoffState.mChain; + uint32_t overscrollHandoffChainIndex = aOverscrollHandoffState.mChainIndex; + RefPtr next; + // If we have reached the end of the overscroll handoff chain, there is + // nothing more to scroll, so we ignore the rest of the pan gesture. + if (overscrollHandoffChainIndex >= overscrollHandoffChain.Length()) { + // Nothing more to scroll - ignore the rest of the pan gesture. + return false; + } + + next = overscrollHandoffChain.GetApzcAtIndex(overscrollHandoffChainIndex); + + if (next == nullptr || next->IsDestroyed()) { + return false; + } + + // Convert the start and end points from |aPrev|'s coordinate space to + // |next|'s coordinate space. + if (!TransformDisplacement(this, aPrev, next, aStartPoint, aEndPoint)) { + return false; + } + + // Scroll |next|. If this causes overscroll, it will call DispatchScroll() + // again with an incremented index. + if (!next->AttemptScroll(aStartPoint, aEndPoint, aOverscrollHandoffState)) { + // Transform |aStartPoint| and |aEndPoint| (which now represent the + // portion of the displacement that wasn't consumed by APZCs later + // in the handoff chain) back into |aPrev|'s coordinate space. This + // allows the caller (which is |aPrev|) to interpret the unconsumed + // displacement in its own coordinate space, and make use of it + // (e.g. by going into overscroll). + if (!TransformDisplacement(this, next, aPrev, aStartPoint, aEndPoint)) { + NS_WARNING("Failed to untransform scroll points during dispatch"); + } + return false; + } + + // Return true to indicate the scroll was consumed entirely. + return true; +} + +ParentLayerPoint APZCTreeManager::DispatchFling( + AsyncPanZoomController* aPrev, const FlingHandoffState& aHandoffState) { + // If immediate handoff is disallowed, do not allow handoff beyond the + // single APZC that's scrolled by the input block that triggered this fling. + if (aHandoffState.mIsHandoff && !StaticPrefs::apz_allow_immediate_handoff() && + aHandoffState.mScrolledApzc == aPrev) { + FLING_LOG("APZCTM dropping handoff due to disallowed immediate handoff\n"); + return aHandoffState.mVelocity; + } + + const OverscrollHandoffChain* chain = aHandoffState.mChain; + RefPtr current; + uint32_t overscrollHandoffChainLength = chain->Length(); + uint32_t startIndex; + + // The fling's velocity needs to be transformed from the screen coordinates + // of |aPrev| to the screen coordinates of |next|. To transform a velocity + // correctly, we need to convert it to a displacement. For now, we do this + // by anchoring it to a start point of (0, 0). + // TODO: For this to be correct in the presence of 3D transforms, we should + // use the end point of the touch that started the fling as the start point + // rather than (0, 0). + ParentLayerPoint startPoint; // (0, 0) + ParentLayerPoint endPoint; + + if (aHandoffState.mIsHandoff) { + startIndex = chain->IndexOf(aPrev) + 1; + + // IndexOf will return aOverscrollHandoffChain->Length() if + // |aPrev| is not found. + if (startIndex >= overscrollHandoffChainLength) { + return aHandoffState.mVelocity; + } + } else { + startIndex = 0; + } + + // This will store any velocity left over after the entire handoff. + ParentLayerPoint finalResidualVelocity = aHandoffState.mVelocity; + + ParentLayerPoint currentVelocity = aHandoffState.mVelocity; + for (; startIndex < overscrollHandoffChainLength; startIndex++) { + current = chain->GetApzcAtIndex(startIndex); + + // Make sure the apzc about to be handled can be handled + if (current == nullptr || current->IsDestroyed()) { + break; + } + + endPoint = startPoint + currentVelocity; + + RefPtr prevApzc = + (startIndex > 0) ? chain->GetApzcAtIndex(startIndex - 1) : nullptr; + + // Only transform when current apzc can be transformed with previous + if (prevApzc) { + if (!TransformDisplacement(this, prevApzc, current, startPoint, + endPoint)) { + break; + } + } + + ParentLayerPoint availableVelocity = (endPoint - startPoint); + ParentLayerPoint residualVelocity; + + FlingHandoffState transformedHandoffState = aHandoffState; + transformedHandoffState.mVelocity = availableVelocity; + + // Obey overscroll-behavior. + if (prevApzc) { + residualVelocity += prevApzc->AdjustHandoffVelocityForOverscrollBehavior( + transformedHandoffState.mVelocity); + } + + residualVelocity += current->AttemptFling(transformedHandoffState); + + // If there's no residual velocity, there's nothing more to hand off. + if (IsZero(residualVelocity)) { + return ParentLayerPoint(); + } + + // If any of the velocity available to be handed off was consumed, + // subtract the proportion of consumed velocity from finalResidualVelocity. + // Note: it's important to compare |residualVelocity| to |availableVelocity| + // here and not to |transformedHandoffState.mVelocity|, since the latter + // may have been modified by AdjustHandoffVelocityForOverscrollBehavior(). + if (!FuzzyEqualsAdditive(availableVelocity.x, residualVelocity.x, + COORDINATE_EPSILON)) { + finalResidualVelocity.x *= (residualVelocity.x / availableVelocity.x); + } + if (!FuzzyEqualsAdditive(availableVelocity.y, residualVelocity.y, + COORDINATE_EPSILON)) { + finalResidualVelocity.y *= (residualVelocity.y / availableVelocity.y); + } + + currentVelocity = residualVelocity; + } + + // Return any residual velocity left over after the entire handoff process. + return finalResidualVelocity; +} + +already_AddRefed APZCTreeManager::GetTargetAPZC( + const ScrollableLayerGuid& aGuid) { + RecursiveMutexAutoLock lock(mTreeLock); + RefPtr node = GetTargetNode(aGuid, nullptr); + MOZ_ASSERT(!node || node->GetApzc()); // any node returned must have an APZC + RefPtr apzc = node ? node->GetApzc() : nullptr; + return apzc.forget(); +} + +static bool GuidComparatorIgnoringPresShell(const ScrollableLayerGuid& aOne, + const ScrollableLayerGuid& aTwo) { + return aOne.mLayersId == aTwo.mLayersId && aOne.mScrollId == aTwo.mScrollId; +} + +already_AddRefed APZCTreeManager::GetTargetAPZC( + const LayersId& aLayersId, + const ScrollableLayerGuid::ViewID& aScrollId) const { + MutexAutoLock lock(mMapLock); + ScrollableLayerGuid guid(aLayersId, 0, aScrollId); + auto it = mApzcMap.find(guid); + RefPtr apzc = + (it != mApzcMap.end() ? it->second.apzc : nullptr); + return apzc.forget(); +} + +already_AddRefed APZCTreeManager::GetTargetNode( + const ScrollableLayerGuid& aGuid, GuidComparator aComparator) const { + mTreeLock.AssertCurrentThreadIn(); + RefPtr target = + DepthFirstSearchPostOrder( + mRootNode.get(), [&aGuid, &aComparator](HitTestingTreeNode* node) { + bool matches = false; + if (node->GetApzc()) { + if (aComparator) { + matches = aComparator(aGuid, node->GetApzc()->GetGuid()); + } else { + matches = node->GetApzc()->Matches(aGuid); + } + } + return matches; + }); + return target.forget(); +} + +APZCTreeManager::HitTestResult APZCTreeManager::GetTargetAPZC( + const ScreenPoint& aPoint) { + RecursiveMutexAutoLock lock(mTreeLock); + if (mIsUsingWebRender) { + return GetAPZCAtPointWR(aPoint, lock); + } + return GetAPZCAtPoint(aPoint, lock); +} + +APZCTreeManager::HitTestResult APZCTreeManager::GetAPZCAtPointWR( + const ScreenPoint& aHitTestPoint, + const RecursiveMutexAutoLock& aProofOfTreeLock) { + HitTestResult hit; + RefPtr wr = GetWebRenderAPI(); + if (!wr) { + // If WebRender isn't running, fall back to the root APZC. + // This is mostly for the benefit of GTests which do not + // run a WebRender instance, but gracefully falling back + // here allows those tests which are not specifically + // testing the hit-test algorithm to still work. + hit.mTargetApzc = FindRootApzcForLayersId(mRootLayersId); + hit.mHitResult = CompositorHitTestFlags::eVisibleToHitTest; + return hit; + } + + APZCTM_LOG("Hit-testing point %s with WR\n", ToString(aHitTestPoint).c_str()); + std::vector results = + wr->HitTest(wr::ToWorldPoint(aHitTestPoint)); + + Maybe chosenResult; + for (const wr::WrHitResult& result : results) { + ScrollableLayerGuid guid{result.mLayersId, 0, result.mScrollId}; + APZCTM_LOG("Examining result with guid %s hit info 0x%x... ", + ToString(guid).c_str(), result.mHitInfo.serialize()); + if (result.mHitInfo == CompositorHitTestInvisibleToHit) { + APZCTM_LOG("skipping due to invisibility.\n"); + continue; + } + RefPtr node = + GetTargetNode(guid, &GuidComparatorIgnoringPresShell); + if (!node) { + APZCTM_LOG("no corresponding node found, falling back to root.\n"); + // We can enter here during normal codepaths for cases where the + // nsDisplayCompositorHitTestInfo item emitted a scrollId of + // NULL_SCROLL_ID to the webrender display list. The semantics of that + // is to fall back to the root APZC for the layers id, so that's what + // we do here. + // If we enter this codepath and scrollId is not NULL_SCROLL_ID, then + // that's more likely to be due to a race condition between rebuilding + // the APZ tree and updating the WR scene/hit-test information, resulting + // in WR giving us a hit result for a scene that is not active in APZ. + // Such a scenario would need debugging and fixing. + MOZ_ASSERT(result.mScrollId == ScrollableLayerGuid::NULL_SCROLL_ID); + node = FindRootNodeForLayersId(result.mLayersId); + if (!node) { + // Should never happen, but handle gracefully in release builds just + // in case. + MOZ_ASSERT(false); + chosenResult = Some(result); + break; + } + } + MOZ_ASSERT(node->GetApzc()); // any node returned must have an APZC + EventRegionsOverride flags = node->GetEventRegionsOverride(); + if (flags & EventRegionsOverride::ForceEmptyHitRegion) { + // This result is inside a subtree that is invisible to hit-testing. + APZCTM_LOG("skipping due to FEHR subtree.\n"); + continue; + } + + APZCTM_LOG("selecting as chosen result.\n"); + chosenResult = Some(result); + hit.mTargetApzc = node->GetApzc(); + if (flags & EventRegionsOverride::ForceDispatchToContent) { + chosenResult->mHitInfo += CompositorHitTestFlags::eApzAwareListeners; + } + break; + } + if (!chosenResult) { + return hit; + } + + MOZ_ASSERT(hit.mTargetApzc); + hit.mLayersId = chosenResult->mLayersId; + ScrollableLayerGuid::ViewID scrollId = chosenResult->mScrollId; + gfx::CompositorHitTestInfo hitInfo = chosenResult->mHitInfo; + SideBits sideBits = chosenResult->mSideBits; + + APZCTM_LOG("Successfully matched APZC %p (hit result 0x%x)\n", + hit.mTargetApzc.get(), hitInfo.serialize()); + + const bool isScrollbar = + hitInfo.contains(gfx::CompositorHitTestFlags::eScrollbar); + const bool isScrollbarThumb = + hitInfo.contains(gfx::CompositorHitTestFlags::eScrollbarThumb); + const ScrollDirection direction = + hitInfo.contains(gfx::CompositorHitTestFlags::eScrollbarVertical) + ? ScrollDirection::eVertical + : ScrollDirection::eHorizontal; + HitTestingTreeNode* scrollbarNode = nullptr; + if (isScrollbar || isScrollbarThumb) { + scrollbarNode = BreadthFirstSearch( + mRootNode.get(), [&](HitTestingTreeNode* aNode) { + return (aNode->GetLayersId() == hit.mLayersId) && + (aNode->IsScrollbarNode() == isScrollbar) && + (aNode->IsScrollThumbNode() == isScrollbarThumb) && + (aNode->GetScrollbarDirection() == direction) && + (aNode->GetScrollTargetId() == scrollId); + }); + } + + hit.mHitResult = hitInfo; + + if (scrollbarNode) { + RefPtr scrollbarRef = scrollbarNode; + hit.mScrollbarNode.Initialize(aProofOfTreeLock, scrollbarRef.forget(), + mTreeLock); + } + + hit.mFixedPosSides = sideBits; + + return hit; +} + +RefPtr +APZCTreeManager::BuildOverscrollHandoffChain( + const RefPtr& aInitialTarget) { + // Scroll grabbing is a mechanism that allows content to specify that + // the initial target of a pan should be not the innermost scrollable + // frame at the touch point (which is what GetTargetAPZC finds), but + // something higher up in the tree. + // It's not sufficient to just find the initial target, however, as + // overscroll can be handed off to another APZC. Without scroll grabbing, + // handoff just occurs from child to parent. With scroll grabbing, the + // handoff order can be different, so we build a chain of APZCs in the + // order in which scroll will be handed off to them. + + // Grab tree lock since we'll be walking the APZC tree. + RecursiveMutexAutoLock lock(mTreeLock); + + // Build the chain. If there is a scroll parent link, we use that. This is + // needed to deal with scroll info layers, because they participate in handoff + // but do not follow the expected layer tree structure. If there are no + // scroll parent links we just walk up the tree to find the scroll parent. + OverscrollHandoffChain* result = new OverscrollHandoffChain; + AsyncPanZoomController* apzc = aInitialTarget; + while (apzc != nullptr) { + result->Add(apzc); + + if (apzc->GetScrollHandoffParentId() == + ScrollableLayerGuid::NULL_SCROLL_ID) { + if (!apzc->IsRootForLayersId()) { + // This probably indicates a bug or missed case in layout code + NS_WARNING("Found a non-root APZ with no handoff parent"); + } + apzc = apzc->GetParent(); + continue; + } + + // Guard against a possible infinite-loop condition. If we hit this, the + // layout code that generates the handoff parents did something wrong. + MOZ_ASSERT(apzc->GetScrollHandoffParentId() != apzc->GetGuid().mScrollId); + RefPtr scrollParent = GetTargetAPZC( + apzc->GetGuid().mLayersId, apzc->GetScrollHandoffParentId()); + apzc = scrollParent.get(); + } + + // Now adjust the chain to account for scroll grabbing. Sorting is a bit + // of an overkill here, but scroll grabbing will likely be generalized + // to scroll priorities, so we might as well do it this way. + result->SortByScrollPriority(); + + // Print the overscroll chain for debugging. + for (uint32_t i = 0; i < result->Length(); ++i) { + APZCTM_LOG("OverscrollHandoffChain[%d] = %p\n", i, + result->GetApzcAtIndex(i).get()); + } + + return result; +} + +void APZCTreeManager::SetLongTapEnabled(bool aLongTapEnabled) { + APZThreadUtils::AssertOnControllerThread(); + GestureEventListener::SetLongTapEnabled(aLongTapEnabled); +} + +void APZCTreeManager::AddInputBlockCallback(uint64_t aInputBlockId, + InputBlockCallback&& aCallback) { + mInputQueue->AddInputBlockCallback(aInputBlockId, std::move(aCallback)); +} + +void APZCTreeManager::FindScrollThumbNode( + const AsyncDragMetrics& aDragMetrics, + HitTestingTreeNodeAutoLock& aOutThumbNode) { + if (!aDragMetrics.mDirection) { + // The AsyncDragMetrics has not been initialized yet - there will be + // no matching node, so don't bother searching the tree. + return; + } + + RecursiveMutexAutoLock lock(mTreeLock); + + RefPtr result = DepthFirstSearch( + mRootNode.get(), [&aDragMetrics](HitTestingTreeNode* aNode) { + return aNode->MatchesScrollDragMetrics(aDragMetrics); + }); + if (result) { + aOutThumbNode.Initialize(lock, result.forget(), mTreeLock); + } +} + +AsyncPanZoomController* APZCTreeManager::GetTargetApzcForNode( + HitTestingTreeNode* aNode) { + for (const HitTestingTreeNode* n = aNode; + n && n->GetLayersId() == aNode->GetLayersId(); n = n->GetParent()) { + if (n->GetApzc()) { + APZCTM_LOG("Found target %p using ancestor lookup\n", n->GetApzc()); + return n->GetApzc(); + } + if (n->GetFixedPosTarget() != ScrollableLayerGuid::NULL_SCROLL_ID) { + RefPtr fpTarget = + GetTargetAPZC(n->GetLayersId(), n->GetFixedPosTarget()); + APZCTM_LOG("Found target APZC %p using fixed-pos lookup on %" PRIu64 "\n", + fpTarget.get(), n->GetFixedPosTarget()); + return fpTarget.get(); + } + } + return nullptr; +} + +APZCTreeManager::HitTestResult APZCTreeManager::GetAPZCAtPoint( + const ScreenPoint& aHitTestPoint, + const RecursiveMutexAutoLock& aProofOfTreeLock) { + HitTestResult hit; + // This walks the tree in depth-first, reverse order, so that it encounters + // APZCs front-to-back on the screen. + HitTestingTreeNode* resultNode; + HitTestingTreeNode* root = mRootNode; + HitTestingTreeNode* scrollbarNode = nullptr; + std::stack hitTestPoints; + ParentLayerPoint point = ViewAs( + aHitTestPoint, PixelCastJustification::ScreenIsParentLayerForRoot); + hitTestPoints.push( + ViewAs(point, PixelCastJustification::MovingDownToChildren)); + + ForEachNode( + root, + [&hitTestPoints, this](HitTestingTreeNode* aNode) { + ParentLayerPoint hitTestPointForParent = ViewAs( + hitTestPoints.top(), PixelCastJustification::MovingDownToChildren); + if (aNode->IsOutsideClip(hitTestPointForParent)) { + // If the point being tested is outside the clip region for this node + // then we don't need to test against this node or any of its + // children. Just skip it and move on. + APZCTM_LOG("Point %f %f outside clip for node %p\n", + hitTestPoints.top().x, hitTestPoints.top().y, aNode); + return TraversalFlag::Skip; + } + // First check the subtree rooted at this node, because deeper nodes + // are more "in front". + Maybe hitTestPoint = aNode->Untransform( + hitTestPointForParent, ComputeTransformForNode(aNode)); + APZCTM_LOG("Transformed ParentLayer point %s to layer %s\n", + ToString(hitTestPointForParent).c_str(), + hitTestPoint ? ToString(hitTestPoint.ref()).c_str() : "nil"); + if (!hitTestPoint) { + return TraversalFlag::Skip; + } + hitTestPoints.push(hitTestPoint.ref()); + return TraversalFlag::Continue; + }, + [&resultNode, &hitTestPoints, &hit](HitTestingTreeNode* aNode) { + CompositorHitTestInfo hitResult = aNode->HitTest(hitTestPoints.top()); + hitTestPoints.pop(); + APZCTM_LOG("Testing Layer point %s against node %p\n", + ToString(hitTestPoints.top()).c_str(), aNode); + if (hitResult != CompositorHitTestInvisibleToHit) { + resultNode = aNode; + hit.mHitResult = hitResult; + return TraversalFlag::Abort; + } + return TraversalFlag::Continue; + }); + + if (hit.mHitResult != CompositorHitTestInvisibleToHit) { + MOZ_ASSERT(resultNode); + for (HitTestingTreeNode* n = resultNode; n; n = n->GetParent()) { + if (n->IsScrollbarNode()) { + scrollbarNode = n; + hit.mHitResult += CompositorHitTestFlags::eScrollbar; + if (n->IsScrollThumbNode()) { + hit.mHitResult += CompositorHitTestFlags::eScrollbarThumb; + } + if (n->GetScrollbarDirection() == ScrollDirection::eVertical) { + hit.mHitResult += CompositorHitTestFlags::eScrollbarVertical; + } + + // If we hit a scrollbar, target the APZC for the content scrolled + // by the scrollbar. (The scrollbar itself doesn't scroll with the + // scrolled content, so it doesn't carry the scrolled content's + // scroll metadata). + RefPtr scrollTarget = + GetTargetAPZC(n->GetLayersId(), n->GetScrollTargetId()); + if (scrollTarget) { + hit.mLayersId = n->GetLayersId(); + hit.mTargetApzc = std::move(scrollTarget); + RefPtr scrollbarRef = scrollbarNode; + hit.mScrollbarNode.Initialize(aProofOfTreeLock, scrollbarRef.forget(), + mTreeLock); + return hit; + } + } else if (IsFixedToRootContent(n)) { + hit.mFixedPosSides = n->GetFixedPosSides(); + } + } + + hit.mTargetApzc = GetTargetApzcForNode(resultNode); + if (!hit.mTargetApzc) { + hit.mTargetApzc = FindRootApzcForLayersId(resultNode->GetLayersId()); + MOZ_ASSERT(hit.mTargetApzc); + APZCTM_LOG("Found target %p using root lookup\n", hit.mTargetApzc.get()); + } + APZCTM_LOG("Successfully matched APZC %p via node %p (hit result 0x%x)\n", + hit.mTargetApzc.get(), resultNode, hit.mHitResult.serialize()); + hit.mLayersId = resultNode->GetLayersId(); + + return hit; + } + + return hit; +} + +HitTestingTreeNode* APZCTreeManager::FindRootNodeForLayersId( + LayersId aLayersId) const { + mTreeLock.AssertCurrentThreadIn(); + + HitTestingTreeNode* resultNode = BreadthFirstSearch( + mRootNode.get(), [aLayersId](HitTestingTreeNode* aNode) { + AsyncPanZoomController* apzc = aNode->GetApzc(); + return apzc && apzc->GetLayersId() == aLayersId && + apzc->IsRootForLayersId(); + }); + return resultNode; +} + +AsyncPanZoomController* APZCTreeManager::FindRootApzcForLayersId( + LayersId aLayersId) const { + mTreeLock.AssertCurrentThreadIn(); + + HitTestingTreeNode* resultNode = FindRootNodeForLayersId(aLayersId); + return resultNode ? resultNode->GetApzc() : nullptr; +} + +already_AddRefed APZCTreeManager::FindZoomableApzc( + AsyncPanZoomController* aStart) const { + return GetZoomableTarget(aStart, aStart); +} + +ScreenMargin APZCTreeManager::GetGeckoFixedLayerMargins() const { + RecursiveMutexAutoLock lock(mTreeLock); + return mGeckoFixedLayerMargins; +} + +AsyncPanZoomController* APZCTreeManager::FindRootContentApzcForLayersId( + LayersId aLayersId) const { + mTreeLock.AssertCurrentThreadIn(); + + HitTestingTreeNode* resultNode = BreadthFirstSearch( + mRootNode.get(), [aLayersId](HitTestingTreeNode* aNode) { + AsyncPanZoomController* apzc = aNode->GetApzc(); + return apzc && apzc->GetLayersId() == aLayersId && + apzc->IsRootContent(); + }); + return resultNode ? resultNode->GetApzc() : nullptr; +} + +// clang-format off +/* The methods GetScreenToApzcTransform() and GetApzcToGeckoTransform() return + some useful transformations that input events may need applied. This is best + illustrated with an example. Consider a chain of layers, L, M, N, O, P, Q, R. Layer L + is the layer that corresponds to the argument |aApzc|, and layer R is the root + of the layer tree. Layer M is the parent of L, N is the parent of M, and so on. + When layer L is displayed to the screen by the compositor, the set of transforms that + are applied to L are (in order from top to bottom): + + L's CSS transform (hereafter referred to as transform matrix LC) + L's nontransient async transform (hereafter referred to as transform matrix LN) + L's transient async transform (hereafter referred to as transform matrix LT) + M's CSS transform (hereafter referred to as transform matrix MC) + M's nontransient async transform (hereafter referred to as transform matrix MN) + M's transient async transform (hereafter referred to as transform matrix MT) + ... + R's CSS transform (hereafter referred to as transform matrix RC) + R's nontransient async transform (hereafter referred to as transform matrix RN) + R's transient async transform (hereafter referred to as transform matrix RT) + + Also, for any layer, the async transform is the combination of its transient and non-transient + parts. That is, for any layer L: + LA === LN * LT + LA.Inverse() === LT.Inverse() * LN.Inverse() + + If we want user input to modify L's transient async transform, we have to first convert + user input from screen space to the coordinate space of L's transient async transform. Doing + this involves applying the following transforms (in order from top to bottom): + RT.Inverse() + RN.Inverse() + RC.Inverse() + ... + MT.Inverse() + MN.Inverse() + MC.Inverse() + This combined transformation is returned by GetScreenToApzcTransform(). + + Next, if we want user inputs sent to gecko for event-dispatching, we will need to strip + out all of the async transforms that are involved in this chain. This is because async + transforms are stored only in the compositor and gecko does not account for them when + doing display-list-based hit-testing for event dispatching. + Furthermore, because these input events are processed by Gecko in a FIFO queue that + includes other things (specifically paint requests), it is possible that by time the + input event reaches gecko, it will have painted something else. Therefore, we need to + apply another transform to the input events to account for the possible disparity between + what we know gecko last painted and the last paint request we sent to gecko. Let this + transform be represented by LD, MD, ... RD. + Therefore, given a user input in screen space, the following transforms need to be applied + (in order from top to bottom): + RT.Inverse() + RN.Inverse() + RC.Inverse() + ... + MT.Inverse() + MN.Inverse() + MC.Inverse() + LT.Inverse() + LN.Inverse() + LC.Inverse() + LC + LD + MC + MD + ... + RC + RD + This sequence can be simplified and refactored to the following: + GetScreenToApzcTransform() + LA.Inverse() + LD + MC + MD + ... + RC + RD + Since GetScreenToApzcTransform() can be obtained by calling that function, GetApzcToGeckoTransform() + returns the remaining transforms (LA.Inverse() * LD * ... * RD), so that the caller code can + combine it with GetScreenToApzcTransform() to get the final transform required in this case. + + Note that for many of these layers, there will be no AsyncPanZoomController attached, and + so the async transform will be the identity transform. So, in the example above, if layers + L and P have APZC instances attached, MT, MN, MD, NT, NN, ND, OT, ON, OD, QT, QN, QD, RT, + RN and RD will be identity transforms. + Additionally, for space-saving purposes, each APZC instance stores its layer's individual + CSS transform and the accumulation of CSS transforms to its parent APZC. So the APZC for + layer L would store LC and (MC * NC * OC), and the layer P would store PC and (QC * RC). + The APZC instances track the last dispatched paint request and so are able to calculate LD and + PD using those internally stored values. + The APZCs also obviously have LT, LN, PT, and PN, so all of the above transformation combinations + required can be generated. + */ +// clang-format on + +/* + * See the long comment above for a detailed explanation of this function. + */ +ScreenToParentLayerMatrix4x4 APZCTreeManager::GetScreenToApzcTransform( + const AsyncPanZoomController* aApzc) const { + Matrix4x4 result; + RecursiveMutexAutoLock lock(mTreeLock); + + // The comments below assume there is a chain of layers L..R with L and P + // having APZC instances as explained in the comment above. This function is + // called with aApzc at L, and the loop below performs one iteration, where + // parent is at P. The comments explain what values are stored in the + // variables at these two levels. All the comments use standard matrix + // notation where the leftmost matrix in a multiplication is applied first. + + // ancestorUntransform is PC.Inverse() * OC.Inverse() * NC.Inverse() * + // MC.Inverse() + Matrix4x4 ancestorUntransform = aApzc->GetAncestorTransform().Inverse(); + + // result is initialized to PC.Inverse() * OC.Inverse() * NC.Inverse() * + // MC.Inverse() + result = ancestorUntransform; + + for (AsyncPanZoomController* parent = aApzc->GetParent(); parent; + parent = parent->GetParent()) { + // ancestorUntransform is updated to RC.Inverse() * QC.Inverse() when parent + // == P + ancestorUntransform = parent->GetAncestorTransform().Inverse(); + // asyncUntransform is updated to PA.Inverse() when parent == P + Matrix4x4 asyncUntransform = parent + ->GetCurrentAsyncTransformWithOverscroll( + AsyncPanZoomController::eForHitTesting) + .Inverse() + .ToUnknownMatrix(); + // untransformSinceLastApzc is RC.Inverse() * QC.Inverse() * PA.Inverse() + Matrix4x4 untransformSinceLastApzc = ancestorUntransform * asyncUntransform; + + // result is RC.Inverse() * QC.Inverse() * PA.Inverse() * PC.Inverse() * + // OC.Inverse() * NC.Inverse() * MC.Inverse() + result = untransformSinceLastApzc * result; + + // The above value for result when parent == P matches the required output + // as explained in the comment above this method. Note that any missing + // terms are guaranteed to be identity transforms. + } + + return ViewAs(result); +} + +/* + * See the long comment above GetScreenToApzcTransform() for a detailed + * explanation of this function. + */ +ParentLayerToScreenMatrix4x4 APZCTreeManager::GetApzcToGeckoTransform( + const AsyncPanZoomController* aApzc) const { + Matrix4x4 result; + RecursiveMutexAutoLock lock(mTreeLock); + + // The comments below assume there is a chain of layers L..R with L and P + // having APZC instances as explained in the comment above. This function is + // called with aApzc at L, and the loop below performs one iteration, where + // parent is at P. The comments explain what values are stored in the + // variables at these two levels. All the comments use standard matrix + // notation where the leftmost matrix in a multiplication is applied first. + + // asyncUntransform is LA.Inverse() + Matrix4x4 asyncUntransform = aApzc + ->GetCurrentAsyncTransformWithOverscroll( + AsyncPanZoomController::eForHitTesting) + .Inverse() + .ToUnknownMatrix(); + + // aTransformToGeckoOut is initialized to LA.Inverse() * LD * MC * NC * OC * + // PC + result = asyncUntransform * aApzc->GetTransformToLastDispatchedPaint() * + aApzc->GetAncestorTransform(); + + for (AsyncPanZoomController* parent = aApzc->GetParent(); parent; + parent = parent->GetParent()) { + // aTransformToGeckoOut is LA.Inverse() * LD * MC * NC * OC * PC * PD * QC * + // RC + result = result * parent->GetTransformToLastDispatchedPaint() * + parent->GetAncestorTransform(); + + // The above value for result when parent == P matches the required output + // as explained in the comment above this method. Note that any missing + // terms are guaranteed to be identity transforms. + } + + return ViewAs(result); +} + +ScreenPoint APZCTreeManager::GetCurrentMousePosition() const { + auto pos = mCurrentMousePosition.Lock(); + return pos.ref(); +} + +void APZCTreeManager::SetCurrentMousePosition(const ScreenPoint& aNewPos) { + auto pos = mCurrentMousePosition.Lock(); + pos.ref() = aNewPos; +} + +already_AddRefed APZCTreeManager::GetZoomableTarget( + AsyncPanZoomController* aApzc1, AsyncPanZoomController* aApzc2) const { + RecursiveMutexAutoLock lock(mTreeLock); + RefPtr apzc; + // For now, we only ever want to do pinching on the root-content APZC for + // a given layers id. + if (aApzc1 && aApzc2 && aApzc1->GetLayersId() == aApzc2->GetLayersId()) { + // If the two APZCs have the same layers id, find the root-content APZC + // for that layers id. Don't call CommonAncestor() because there may not + // be a common ancestor for the layers id (e.g. if one APZCs is inside a + // fixed-position element). + apzc = FindRootContentApzcForLayersId(aApzc1->GetLayersId()); + } else { + // Otherwise, find the common ancestor (to reach a common layers id), and + // get the root-content APZC for that layers id. + apzc = CommonAncestor(aApzc1, aApzc2); + if (apzc) { + apzc = FindRootContentApzcForLayersId(apzc->GetLayersId()); + } + } + return apzc.forget(); +} + +Maybe APZCTreeManager::ConvertToGecko( + const ScreenIntPoint& aPoint, AsyncPanZoomController* aApzc) { + RecursiveMutexAutoLock lock(mTreeLock); + ScreenToScreenMatrix4x4 transformScreenToGecko = + GetScreenToApzcTransform(aApzc) * GetApzcToGeckoTransform(aApzc); + Maybe geckoPoint = + UntransformBy(transformScreenToGecko, aPoint); + if (geckoPoint) { + if (mTouchBlockHitResult.mFixedPosSides != SideBits::eNone) { + MutexAutoLock mapLock(mMapLock); + *geckoPoint -= RoundedToInt(apz::ComputeFixedMarginsOffset( + GetCompositorFixedLayerMargins(mapLock), + mTouchBlockHitResult.mFixedPosSides, mGeckoFixedLayerMargins)); + } + } + return geckoPoint; +} + +already_AddRefed APZCTreeManager::CommonAncestor( + AsyncPanZoomController* aApzc1, AsyncPanZoomController* aApzc2) const { + mTreeLock.AssertCurrentThreadIn(); + RefPtr ancestor; + + // If either aApzc1 or aApzc2 is null, min(depth1, depth2) will be 0 and this + // function will return null. + + // Calculate depth of the APZCs in the tree + int depth1 = 0, depth2 = 0; + for (AsyncPanZoomController* parent = aApzc1; parent; + parent = parent->GetParent()) { + depth1++; + } + for (AsyncPanZoomController* parent = aApzc2; parent; + parent = parent->GetParent()) { + depth2++; + } + + // At most one of the following two loops will be executed; the deeper APZC + // pointer will get walked up to the depth of the shallower one. + int minDepth = depth1 < depth2 ? depth1 : depth2; + while (depth1 > minDepth) { + depth1--; + aApzc1 = aApzc1->GetParent(); + } + while (depth2 > minDepth) { + depth2--; + aApzc2 = aApzc2->GetParent(); + } + + // Walk up the ancestor chains of both APZCs, always staying at the same depth + // for either APZC, and return the the first common ancestor encountered. + while (true) { + if (aApzc1 == aApzc2) { + ancestor = aApzc1; + break; + } + if (depth1 <= 0) { + break; + } + aApzc1 = aApzc1->GetParent(); + aApzc2 = aApzc2->GetParent(); + } + return ancestor.forget(); +} + +bool APZCTreeManager::IsFixedToRootContent( + const HitTestingTreeNode* aNode) const { + MutexAutoLock lock(mMapLock); + return IsFixedToRootContent(FixedPositionInfo(aNode), lock); +} + +bool APZCTreeManager::IsFixedToRootContent( + const FixedPositionInfo& aFixedInfo, + const MutexAutoLock& aProofOfMapLock) const { + ScrollableLayerGuid::ViewID fixedTarget = aFixedInfo.mFixedPosTarget; + if (fixedTarget == ScrollableLayerGuid::NULL_SCROLL_ID) { + return false; + } + auto it = + mApzcMap.find(ScrollableLayerGuid(aFixedInfo.mLayersId, 0, fixedTarget)); + if (it == mApzcMap.end()) { + return false; + } + RefPtr targetApzc = it->second.apzc; + return targetApzc && targetApzc->IsRootContent(); +} + +SideBits APZCTreeManager::SidesStuckToRootContent( + const HitTestingTreeNode* aNode) const { + MutexAutoLock lock(mMapLock); + return SidesStuckToRootContent(StickyPositionInfo(aNode), lock); +} + +SideBits APZCTreeManager::SidesStuckToRootContent( + const StickyPositionInfo& aStickyInfo, + const MutexAutoLock& aProofOfMapLock) const { + SideBits result = SideBits::eNone; + + ScrollableLayerGuid::ViewID stickyTarget = aStickyInfo.mStickyPosTarget; + if (stickyTarget == ScrollableLayerGuid::NULL_SCROLL_ID) { + return result; + } + + // We support the dynamic toolbar at top and bottom. + if ((aStickyInfo.mFixedPosSides & SideBits::eTopBottom) == SideBits::eNone) { + return result; + } + + auto it = mApzcMap.find( + ScrollableLayerGuid(aStickyInfo.mLayersId, 0, stickyTarget)); + if (it == mApzcMap.end()) { + return result; + } + RefPtr stickyTargetApzc = it->second.apzc; + if (!stickyTargetApzc || !stickyTargetApzc->IsRootContent()) { + return result; + } + + ParentLayerPoint translation = + stickyTargetApzc + ->GetCurrentAsyncTransform( + AsyncPanZoomController::eForHitTesting, + AsyncTransformComponents{AsyncTransformComponent::eLayout}) + .mTranslation; + + if (apz::IsStuckAtTop(translation.y, aStickyInfo.mStickyScrollRangeInner, + aStickyInfo.mStickyScrollRangeOuter)) { + result |= SideBits::eTop; + } + if (apz::IsStuckAtBottom(translation.y, aStickyInfo.mStickyScrollRangeInner, + aStickyInfo.mStickyScrollRangeOuter)) { + result |= SideBits::eBottom; + } + return result; +} + +LayerToParentLayerMatrix4x4 APZCTreeManager::ComputeTransformForNode( + const HitTestingTreeNode* aNode) const { + mTreeLock.AssertCurrentThreadIn(); + // The async transforms applied here for hit-testing purposes, are intended + // to match the ones AsyncCompositionManager (or equivalent WebRender code) + // applies for rendering purposes. + // Note that with containerless scrolling, the layer structure looks like + // this: + // + // root container layer + // async zoom container layer + // scrollable content layers (with scroll metadata) + // fixed content layers (no scroll metadta, annotated isFixedPosition) + // scrollbar layers + // + // The intended async transforms in this case are: + // * On the async zoom container layer, the "visual" portion of the root + // content APZC's async transform (which includes the zoom, and async + // scrolling of the visual viewport relative to the layout viewport). + // * On the scrollable layers bearing the root content APZC's scroll + // metadata, the "layout" portion of the root content APZC's async + // transform (which includes async scrolling of the layout viewport + // relative to the visual viewport). + if (AsyncPanZoomController* apzc = aNode->GetApzc()) { + // If the node represents scrollable content, apply the async transform + // from its APZC. + bool visualTransformIsInheritedFromAncestor = + /* we're the APZC whose visual transform might be on the async + zoom container */ + apzc->IsRootContent() && + /* there is an async zoom container on this subtree */ + mAsyncZoomContainerSubtree == Some(aNode->GetLayersId()) && + /* it's not us */ + !aNode->IsAsyncZoomContainer(); + AsyncTransformComponents components = + visualTransformIsInheritedFromAncestor + ? AsyncTransformComponents{AsyncTransformComponent::eLayout} + : LayoutAndVisual; + return aNode->GetTransform() * + CompleteAsyncTransform(apzc->GetCurrentAsyncTransformWithOverscroll( + AsyncPanZoomController::eForHitTesting, components)); + } else if (aNode->IsAsyncZoomContainer()) { + if (AsyncPanZoomController* rootContent = + FindRootContentApzcForLayersId(aNode->GetLayersId())) { + return aNode->GetTransform() * + CompleteAsyncTransform( + rootContent->GetCurrentAsyncTransformWithOverscroll( + AsyncPanZoomController::eForHitTesting, + {AsyncTransformComponent::eVisual})); + } + } else if (aNode->IsScrollThumbNode()) { + // If the node represents a scrollbar thumb, compute and apply the + // transformation that will be applied to the thumb in + // AsyncCompositionManager. + ScrollableLayerGuid guid{aNode->GetLayersId(), 0, + aNode->GetScrollTargetId()}; + if (RefPtr scrollTargetNode = + GetTargetNode(guid, &GuidComparatorIgnoringPresShell)) { + AsyncPanZoomController* scrollTargetApzc = scrollTargetNode->GetApzc(); + MOZ_ASSERT(scrollTargetApzc); + return scrollTargetApzc->CallWithLastContentPaintMetrics( + [&](const FrameMetrics& aMetrics) { + return ComputeTransformForScrollThumb( + aNode->GetTransform() * AsyncTransformMatrix(), + scrollTargetNode->GetTransform().ToUnknownMatrix(), + scrollTargetApzc, aMetrics, aNode->GetScrollbarData(), + scrollTargetNode->IsAncestorOf(aNode), nullptr); + }); + } + } else if (IsFixedToRootContent(aNode)) { + ParentLayerPoint translation; + { + MutexAutoLock mapLock(mMapLock); + translation = ViewAs( + apz::ComputeFixedMarginsOffset( + GetCompositorFixedLayerMargins(mapLock), + aNode->GetFixedPosSides(), mGeckoFixedLayerMargins), + PixelCastJustification::ScreenIsParentLayerForRoot); + } + return aNode->GetTransform() * + CompleteAsyncTransform( + AsyncTransformComponentMatrix::Translation(translation)); + } + SideBits sides = SidesStuckToRootContent(aNode); + if (sides != SideBits::eNone) { + ParentLayerPoint translation; + { + MutexAutoLock mapLock(mMapLock); + translation = ViewAs( + apz::ComputeFixedMarginsOffset( + GetCompositorFixedLayerMargins(mapLock), sides, + // For sticky layers, we don't need to factor + // mGeckoFixedLayerMargins because Gecko doesn't shift the + // position of sticky elements for dynamic toolbar movements. + ScreenMargin()), + PixelCastJustification::ScreenIsParentLayerForRoot); + } + return aNode->GetTransform() * + CompleteAsyncTransform( + AsyncTransformComponentMatrix::Translation(translation)); + } + // Otherwise, the node does not have an async transform. + return aNode->GetTransform() * AsyncTransformMatrix(); +} + +already_AddRefed APZCTreeManager::GetWebRenderAPI() const { + RefPtr api; + CompositorBridgeParent::CallWithIndirectShadowTree( + mRootLayersId, [&](LayerTreeState& aState) -> void { + if (aState.mWrBridge) { + api = aState.mWrBridge->GetWebRenderAPI(); + } + }); + return api.forget(); +} + +/*static*/ +already_AddRefed APZCTreeManager::GetContentController( + LayersId aLayersId) { + RefPtr controller; + CompositorBridgeParent::CallWithIndirectShadowTree( + aLayersId, + [&](LayerTreeState& aState) -> void { controller = aState.mController; }); + return controller.forget(); +} + +ScreenMargin APZCTreeManager::GetCompositorFixedLayerMargins( + const MutexAutoLock& aProofOfMapLock) const { + ScreenMargin result = mCompositorFixedLayerMargins; + if (StaticPrefs::apz_fixed_margin_override_enabled()) { + result.top = StaticPrefs::apz_fixed_margin_override_top(); + result.bottom = StaticPrefs::apz_fixed_margin_override_bottom(); + } + return result; +} + +bool APZCTreeManager::GetAPZTestData(LayersId aLayersId, + APZTestData* aOutData) { + AssertOnUpdaterThread(); + + { // copy the relevant test data into aOutData while holding the + // mTestDataLock + MutexAutoLock lock(mTestDataLock); + auto it = mTestData.find(aLayersId); + if (it == mTestData.end()) { + return false; + } + *aOutData = *(it->second); + } + + { // add some additional "current state" into the returned APZTestData + MutexAutoLock mapLock(mMapLock); + + ClippedCompositionBoundsMap clippedCompBounds; + for (const auto& mapping : mApzcMap) { + if (mapping.first.mLayersId != aLayersId) { + continue; + } + + ParentLayerRect clippedBounds = ComputeClippedCompositionBounds( + mapLock, clippedCompBounds, mapping.first); + AsyncPanZoomController* apzc = mapping.second.apzc; + std::string viewId = std::to_string(mapping.first.mScrollId); + std::string apzcState; + if (apzc->GetCheckerboardMagnitude(clippedBounds)) { + apzcState += "checkerboarding,"; + } + aOutData->RecordAdditionalData(viewId, apzcState); + } + } + return true; +} + +void APZCTreeManager::SendSubtreeTransformsToChromeMainThread( + const AsyncPanZoomController* aAncestor) { + RefPtr controller = + GetContentController(mRootLayersId); + if (!controller) { + return; + } + nsTArray messages; + bool underAncestor = (aAncestor == nullptr); + { + RecursiveMutexAutoLock lock(mTreeLock); + if (!mRootNode) { + // Event dispatched during shutdown, after ClearTree(). + // Note, mRootNode needs to be checked with mTreeLock held. + return; + } + // This formulation duplicates matrix multiplications closer + // to the root of the tree. For now, aiming for separation + // of concerns rather than minimum number of multiplications. + ForEachNode( + mRootNode.get(), + [&](HitTestingTreeNode* aNode) { + bool atAncestor = (aAncestor && aNode->GetApzc() == aAncestor); + MOZ_ASSERT(!(underAncestor && atAncestor)); + underAncestor |= atAncestor; + if (!underAncestor) { + return; + } + LayersId layersId = aNode->GetLayersId(); + HitTestingTreeNode* parent = aNode->GetParent(); + if (!parent) { + messages.AppendElement(MatrixMessage(Some(LayerToScreenMatrix4x4()), + ScreenRect(), layersId)); + } else if (layersId != parent->GetLayersId()) { + if (mDetachedLayersIds.find(layersId) != mDetachedLayersIds.end()) { + messages.AppendElement( + MatrixMessage(Nothing(), ScreenRect(), layersId)); + } else { + messages.AppendElement(MatrixMessage( + Some(parent->GetTransformToGecko()), + parent->GetRemoteDocumentScreenRect(), layersId)); + } + } + }, + [&](HitTestingTreeNode* aNode) { + bool atAncestor = (aAncestor && aNode->GetApzc() == aAncestor); + if (atAncestor) { + MOZ_ASSERT(underAncestor); + underAncestor = false; + } + }); + } + controller->NotifyLayerTransforms(std::move(messages)); +} + +void APZCTreeManager::SetFixedLayerMargins(ScreenIntCoord aTop, + ScreenIntCoord aBottom) { + MutexAutoLock lock(mMapLock); + mCompositorFixedLayerMargins.top = aTop; + mCompositorFixedLayerMargins.bottom = aBottom; +} + +/*static*/ +LayerToParentLayerMatrix4x4 APZCTreeManager::ComputeTransformForScrollThumb( + const LayerToParentLayerMatrix4x4& aCurrentTransform, + const Matrix4x4& aScrollableContentTransform, AsyncPanZoomController* aApzc, + const FrameMetrics& aMetrics, const ScrollbarData& aScrollbarData, + bool aScrollbarIsDescendant, + AsyncTransformComponentMatrix* aOutClipTransform) { + // We only apply the transform if the scroll-target layer has non-container + // children (i.e. when it has some possibly-visible content). This is to + // avoid moving scroll-bars in the situation that only a scroll information + // layer has been built for a scroll frame, as this would result in a + // disparity between scrollbars and visible content. + if (aMetrics.IsScrollInfoLayer()) { + return LayerToParentLayerMatrix4x4{}; + } + + MOZ_RELEASE_ASSERT(aApzc); + + AsyncTransformComponentMatrix asyncTransform = + aApzc->GetCurrentAsyncTransform(AsyncPanZoomController::eForCompositing); + + // |asyncTransform| represents the amount by which we have scrolled and + // zoomed since the last paint. Because the scrollbar was sized and positioned + // based on the painted content, we need to adjust it based on asyncTransform + // so that it reflects what the user is actually seeing now. + AsyncTransformComponentMatrix scrollbarTransform; + if (*aScrollbarData.mDirection == ScrollDirection::eVertical) { + ParentLayerCoord asyncScrollY = asyncTransform._42; + const float asyncZoomY = asyncTransform._22; + + // The scroll thumb needs to be scaled in the direction of scrolling by the + // inverse of the async zoom. This is because zooming in decreases the + // fraction of the whole srollable rect that is in view. + const float yScale = 1.f / asyncZoomY; + + // Note: |metrics.GetZoom()| doesn't yet include the async zoom. + const CSSToParentLayerScale effectiveZoom(aMetrics.GetZoom().yScale * + asyncZoomY); + + if (gfxPlatform::UseDesktopZoomingScrollbars()) { + // As computed by GetCurrentAsyncTransform, asyncScrollY is + // asyncScrollY = -(GetEffectiveScrollOffset - + // mLastContentPaintMetrics.GetLayoutScrollOffset()) * + // effectiveZoom + // where GetEffectiveScrollOffset includes the visual viewport offset that + // the main thread knows about plus any async scrolling to the visual + // viewport offset that the main thread does not (yet) know about. We want + // asyncScrollY to be + // asyncScrollY = -(GetEffectiveScrollOffset - + // mLastContentPaintMetrics.GetVisualScrollOffset()) * effectiveZoom + // because the main thread positions the scrollbars at the visual viewport + // offset that it knows about. (aMetrics is mLastContentPaintMetrics) + + asyncScrollY -= ((aMetrics.GetLayoutScrollOffset() - + aMetrics.GetVisualScrollOffset()) * + effectiveZoom) + .y; + } + + // Here we convert the scrollbar thumb ratio into a true unitless ratio by + // dividing out the conversion factor from the scrollframe's parent's space + // to the scrollframe's space. + const float ratio = aScrollbarData.mThumbRatio / + (aMetrics.GetPresShellResolution() * asyncZoomY); + // The scroll thumb needs to be translated in opposite direction of the + // async scroll. This is because scrolling down, which translates the layer + // content up, should result in moving the scroll thumb down. + ParentLayerCoord yTranslation = -asyncScrollY * ratio; + + // The scroll thumb additionally needs to be translated to compensate for + // the scale applied above. The origin with respect to which the scale is + // applied is the origin of the entire scrollbar, rather than the origin of + // the scroll thumb (meaning, for a vertical scrollbar it's at the top of + // the composition bounds). This means that empty space above the thumb + // is scaled too, effectively translating the thumb. We undo that + // translation here. + // (One can think of the adjustment being done to the translation here as + // a change of basis. We have a method to help with that, + // Matrix4x4::ChangeBasis(), but it wouldn't necessarily make the code + // cleaner in this case). + const CSSCoord thumbOrigin = (aMetrics.GetVisualScrollOffset().y * ratio); + const CSSCoord thumbOriginScaled = thumbOrigin * yScale; + const CSSCoord thumbOriginDelta = thumbOriginScaled - thumbOrigin; + const ParentLayerCoord thumbOriginDeltaPL = + thumbOriginDelta * effectiveZoom; + yTranslation -= thumbOriginDeltaPL; + + scrollbarTransform.PostScale(1.f, yScale, 1.f); + scrollbarTransform.PostTranslate(0, yTranslation, 0); + } + if (*aScrollbarData.mDirection == ScrollDirection::eHorizontal) { + // See detailed comments under the eVertical case. + + ParentLayerCoord asyncScrollX = asyncTransform._41; + const float asyncZoomX = asyncTransform._11; + + const float xScale = 1.f / asyncZoomX; + + const CSSToParentLayerScale effectiveZoom(aMetrics.GetZoom().xScale * + asyncZoomX); + + if (gfxPlatform::UseDesktopZoomingScrollbars()) { + asyncScrollX -= ((aMetrics.GetLayoutScrollOffset() - + aMetrics.GetVisualScrollOffset()) * + effectiveZoom) + .x; + } + + const float ratio = aScrollbarData.mThumbRatio / + (aMetrics.GetPresShellResolution() * asyncZoomX); + ParentLayerCoord xTranslation = -asyncScrollX * ratio; + + const CSSCoord thumbOrigin = (aMetrics.GetVisualScrollOffset().x * ratio); + const CSSCoord thumbOriginScaled = thumbOrigin * xScale; + const CSSCoord thumbOriginDelta = thumbOriginScaled - thumbOrigin; + const ParentLayerCoord thumbOriginDeltaPL = + thumbOriginDelta * effectiveZoom; + xTranslation -= thumbOriginDeltaPL; + + scrollbarTransform.PostScale(xScale, 1.f, 1.f); + scrollbarTransform.PostTranslate(xTranslation, 0, 0); + } + + LayerToParentLayerMatrix4x4 transform = + aCurrentTransform * scrollbarTransform; + + AsyncTransformComponentMatrix compensation; + // If the scrollbar layer is a child of the content it is a scrollbar for, + // then we need to adjust for any async transform (including an overscroll + // transform) on the content. This needs to be cancelled out because layout + // positions and sizes the scrollbar on the assumption that there is no async + // transform, and without this adjustment the scrollbar will end up in the + // wrong place. + // + // Note that since the async transform is applied on top of the content's + // regular transform, we need to make sure to unapply the async transform in + // the same coordinate space. This requires applying the content transform + // and then unapplying it after unapplying the async transform. + if (aScrollbarIsDescendant) { + AsyncTransformComponentMatrix overscroll = + aApzc->GetOverscrollTransform(AsyncPanZoomController::eForCompositing); + Matrix4x4 asyncUntransform = + (asyncTransform * overscroll).Inverse().ToUnknownMatrix(); + const Matrix4x4& contentTransform = aScrollableContentTransform; + Matrix4x4 contentUntransform = contentTransform.Inverse(); + + compensation *= ViewAs( + contentTransform * asyncUntransform * contentUntransform); + + // Pass the total compensation out to the caller so that it can use it + // to transform clip transforms as needed. + if (aOutClipTransform) { + *aOutClipTransform = compensation; + } + } + transform = transform * compensation; + + return transform; +} + +APZSampler* APZCTreeManager::GetSampler() const { + // We should always have a sampler here, since in practice the sampler + // is destroyed at the same time that this APZCTreeMAnager instance is. + MOZ_ASSERT(mSampler); + return mSampler; +} + +void APZCTreeManager::AssertOnSamplerThread() { + GetSampler()->AssertOnSamplerThread(); +} + +APZUpdater* APZCTreeManager::GetUpdater() const { + // We should always have an updater here, since in practice the updater + // is destroyed at the same time that this APZCTreeManager instance is. + MOZ_ASSERT(mUpdater); + return mUpdater; +} + +void APZCTreeManager::AssertOnUpdaterThread() { + GetUpdater()->AssertOnUpdaterThread(); +} + +void APZCTreeManager::LockTree() { + AssertOnUpdaterThread(); + mTreeLock.Lock(); +} + +void APZCTreeManager::UnlockTree() { + AssertOnUpdaterThread(); + mTreeLock.Unlock(); +} + +void APZCTreeManager::SetDPI(float aDpiValue) { + APZThreadUtils::AssertOnControllerThread(); + mDPI = aDpiValue; +} + +float APZCTreeManager::GetDPI() const { + APZThreadUtils::AssertOnControllerThread(); + return mDPI; +} + +APZCTreeManager::FixedPositionInfo::FixedPositionInfo( + const HitTestingTreeNode* aNode) { + mFixedPositionAnimationId = aNode->GetFixedPositionAnimationId(); + mFixedPosSides = aNode->GetFixedPosSides(); + mFixedPosTarget = aNode->GetFixedPosTarget(); + mLayersId = aNode->GetLayersId(); +} + +APZCTreeManager::StickyPositionInfo::StickyPositionInfo( + const HitTestingTreeNode* aNode) { + mStickyPositionAnimationId = aNode->GetStickyPositionAnimationId(); + mFixedPosSides = aNode->GetFixedPosSides(); + mStickyPosTarget = aNode->GetStickyPosTarget(); + mLayersId = aNode->GetLayersId(); + mStickyScrollRangeInner = aNode->GetStickyScrollRangeInner(); + mStickyScrollRangeOuter = aNode->GetStickyScrollRangeOuter(); +} + +Maybe APZCTreeManager::HitTestResult::HandledByRoot() const { + if (!mTargetApzc->IsRootContent()) { + // If the initial target is not the root, this will definitely not be + // handled by the root. (The confirmed target is either the initial + // target, or a descendant.) + return Some(APZHandledResult::HandledByContent); + } else if ((mHitResult & CompositorHitTestDispatchToContent).isEmpty()) { + // If the initial target is the root and we don't need to dispatch to + // content, the event will definitely be handled by the root. + return Some(APZHandledResult::HandledByRoot); + } + // Otherwise, we're not sure. + return Nothing(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/src/APZCTreeManager.h b/gfx/layers/apz/src/APZCTreeManager.h new file mode 100644 index 0000000000..f7cdde91db --- /dev/null +++ b/gfx/layers/apz/src/APZCTreeManager.h @@ -0,0 +1,1042 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_APZCTreeManager_h +#define mozilla_layers_APZCTreeManager_h + +#include // for std::unordered_map + +#include "FocusState.h" // for FocusState +#include "HitTestingTreeNode.h" // for HitTestingTreeNodeAutoLock +#include "gfxPoint.h" // for gfxPoint +#include "mozilla/Assertions.h" // for MOZ_ASSERT_HELPER2 +#include "mozilla/DataMutex.h" // for DataMutex +#include "mozilla/gfx/CompositorHitTestInfo.h" +#include "mozilla/gfx/Logging.h" // for gfx::TreeLog +#include "mozilla/gfx/Matrix.h" // for Matrix4x4 +#include "mozilla/layers/APZInputBridge.h" // for APZInputBridge +#include "mozilla/layers/APZTestData.h" // for APZTestData +#include "mozilla/layers/APZUtils.h" // for GeckoViewMetrics +#include "mozilla/layers/IAPZCTreeManager.h" // for IAPZCTreeManager +#include "mozilla/layers/LayerAttributes.h" +#include "mozilla/layers/LayersTypes.h" +#include "mozilla/layers/KeyboardMap.h" // for KeyboardMap +#include "mozilla/layers/TouchCounter.h" // for TouchCounter +#include "mozilla/layers/ZoomConstraints.h" // for ZoomConstraints +#include "mozilla/webrender/webrender_ffi.h" +#include "mozilla/RecursiveMutex.h" // for RecursiveMutex +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/TimeStamp.h" // for mozilla::TimeStamp +#include "mozilla/UniquePtr.h" // for UniquePtr +#include "nsCOMPtr.h" // for already_AddRefed +#include "OvershootDetector.h" + +namespace mozilla { +class MultiTouchInput; + +namespace wr { +class TransactionWrapper; +class WebRenderAPI; +} // namespace wr + +namespace layers { + +class Layer; +class AsyncPanZoomController; +class APZCTreeManagerParent; +class APZSampler; +class APZUpdater; +class CompositorBridgeParent; +class OverscrollHandoffChain; +struct OverscrollHandoffState; +class FocusTarget; +struct FlingHandoffState; +class InputQueue; +class GeckoContentController; +class HitTestingTreeNode; +class HitTestingTreeNodeAutoLock; +class LayerMetricsWrapper; +class SampleTime; +class WebRenderScrollDataWrapper; +struct AncestorTransform; +struct ScrollThumbData; + +/** + * ****************** NOTE ON LOCK ORDERING IN APZ ************************** + * + * To avoid deadlock, APZ imposes and respects a global ordering on threads + * and locks relevant to APZ. + * + * Please see the "Threading / Locking Overview" section of + * gfx/docs/AsyncPanZoom.rst (hosted in rendered form at + * https://firefox-source-docs.mozilla.org/gfx/gfx/AsyncPanZoom.html#threading-locking-overview) + * for what the ordering is, and what are the rules for respecting it. + * ************************************************************************** + */ + +/** + * This class manages the tree of AsyncPanZoomController instances. There is one + * instance of this class owned by each CompositorBridgeParent, and it contains + * as many AsyncPanZoomController instances as there are scrollable container + * layers. This class generally lives on the updater thread, although some + * functions may be called from other threads as noted; thread safety is ensured + * internally. + * + * The bulk of the work of this class happens as part of the + * UpdateHitTestingTree function, which is when a layer tree update is received + * by the compositor. This function walks through the layer tree and creates a + * tree of HitTestingTreeNode instances to match the layer tree and for use in + * hit-testing on the controller thread. APZC instances may be preserved across + * calls to this function if the corresponding layers are still present in the + * layer tree. + * + * The other functions on this class are used by various pieces of client code + * to notify the APZC instances of events relevant to them. This includes, for + * example, user input events that drive panning and zooming, changes to the + * scroll viewport area, and changes to pan/zoom constraints. + * + * Note that the ClearTree function MUST be called when this class is no longer + * needed; see the method documentation for details. + * + * Behaviour of APZ is controlled by a number of preferences shown + * \ref APZCPrefs "here". + */ +class APZCTreeManager : public IAPZCTreeManager, public APZInputBridge { + typedef mozilla::layers::AllowedTouchBehavior AllowedTouchBehavior; + typedef mozilla::layers::AsyncDragMetrics AsyncDragMetrics; + + // Helper struct to hold some state while we build the hit-testing tree. The + // sole purpose of this struct is to shorten the argument list to + // UpdateHitTestingTree. All the state that we don't need to + // push on the stack during recursion and pop on unwind is stored here. + struct TreeBuildingState; + + public: + APZCTreeManager(LayersId aRootLayersId, bool aIsUsingWebRender); + + void SetSampler(APZSampler* aSampler); + void SetUpdater(APZUpdater* aUpdater); + + /** + * Notifies this APZCTreeManager that the associated compositor is now + * responsible for managing another layers id, which got moved over from + * some other compositor. That other compositor's APZCTreeManager is also + * provided. This allows APZCTreeManager to transfer any necessary state + * from the old APZCTreeManager related to that layers id. + * This function must be called on the updater thread. + */ + void NotifyLayerTreeAdopted(LayersId aLayersId, + const RefPtr& aOldTreeManager); + + /** + * Notifies this APZCTreeManager that a layer tree being managed by the + * associated compositor has been removed/destroyed. Note that this does + * NOT get called during shutdown situations, when the root layer tree is + * also getting destroyed. + * This function must be called on the updater thread. + */ + void NotifyLayerTreeRemoved(LayersId aLayersId); + + /** + * Rebuild the focus state based on the focus target from the layer tree + * update that just occurred. This must be called on the updater thread. + * + * @param aRootLayerTreeId The layer tree ID of the root layer corresponding + * to this APZCTreeManager + * @param aOriginatingLayersId The layer tree ID of the layer corresponding to + * this layer tree update. + */ + void UpdateFocusState(LayersId aRootLayerTreeId, + LayersId aOriginatingLayersId, + const FocusTarget& aFocusTarget); + + /** + * Rebuild the hit-testing tree based on the layer update that just came up. + * Preserve nodes and APZC instances where possible, but retire those whose + * layers are no longer in the layer tree. + * + * This must be called on the updater thread as it walks the layer tree. + * + * @param aRoot The root of the (full) layer tree + * @param aOriginatingLayersId The layers id of the subtree that triggered + * this repaint, and to which aIsFirstPaint + * applies. + * @param aIsFirstPaint True if the layers update that this is called in + * response to included a first-paint. If this is true, + * the part of the tree that is affected by the + * first-paint flag is indicated by the + * aFirstPaintLayersId parameter. + * @param aPaintSequenceNumber The sequence number of the paint that triggered + * this layer update. Note that every layer child + * process' layer subtree has its own sequence + * numbers. + */ + void UpdateHitTestingTree(Layer* aRoot, bool aIsFirstPaint, + LayersId aOriginatingLayersId, + uint32_t aPaintSequenceNumber); + + /** + * Same as the above UpdateHitTestingTree, except slightly modified to take + * the scrolling data passed over PWebRenderBridge instead of the raw layer + * tree. This version is used when WebRender is enabled because we don't have + * shadow layers in that scenario. + */ + void UpdateHitTestingTree(const WebRenderScrollDataWrapper& aScrollWrapper, + bool aIsFirstPaint, LayersId aOriginatingLayersId, + uint32_t aPaintSequenceNumber); + + /** + * Called when webrender is enabled, from the sampler thread. This function + * populates the provided transaction with any async scroll offsets needed. + * It also advances APZ animations to the specified sample time, and requests + * another composite if there are still active animations. + * In effect it is the webrender equivalent of (part of) the code in + * AsyncCompositionManager. + */ + void SampleForWebRender(const Maybe& aVsyncId, + wr::TransactionWrapper& aTxn, + const SampleTime& aSampleTime); + + /** + * Walk through all the APZCs and do the sampling steps needed when + * advancing to the next frame. The APZCs walked can be restricted to a + * specific render root by providing that as the first argument. + */ + bool AdvanceAnimations(const SampleTime& aSampleTime); + + /** + * Refer to the documentation of APZInputBridge::ReceiveInputEvent() and + * APZEventResult. + */ + APZEventResult ReceiveInputEvent(InputData& aEvent) override; + + /** + * Set the keyboard shortcuts to use for translating keyboard events. + */ + void SetKeyboardMap(const KeyboardMap& aKeyboardMap) override; + + /** + * Kicks an animation to zoom to a rect. This may be either a zoom out or zoom + * in. The actual animation is done on the sampler thread after being set + * up. |aRect| must be given in CSS pixels, relative to the document. + * |aFlags| is a combination of the ZoomToRectBehavior enum values. + */ + void ZoomToRect(const ScrollableLayerGuid& aGuid, const CSSRect& aRect, + const uint32_t aFlags = DEFAULT_BEHAVIOR) override; + + /** + * If we have touch listeners, this should always be called when we know + * definitively whether or not content has preventDefaulted any touch events + * that have come in. If |aPreventDefault| is true, any touch events in the + * queue will be discarded. This function must be called on the controller + * thread. + */ + void ContentReceivedInputBlock(uint64_t aInputBlockId, + bool aPreventDefault) override; + + /** + * When the event regions code is enabled, this function should be invoked to + * to confirm the target of the input block. This is only needed in cases + * where the initial input event of the block hit a dispatch-to-content region + * but is safe to call for all input blocks. This function should always be + * invoked on the controller thread. + * The different elements in the array of targets correspond to the targets + * for the different touch points. In the case where the touch point has no + * target, or the target is not a scrollable frame, the target's |mScrollId| + * should be set to ScrollableLayerGuid::NULL_SCROLL_ID. + * Note: For mouse events that start a scrollbar drag, both SetTargetAPZC() + * and StartScrollbarDrag() will be called, and the calls may happen + * in either order. That's fine - whichever arrives first will confirm + * the block, and StartScrollbarDrag() will fill in the drag metrics. + * If the block is confirmed before we have drag metrics, some events + * in the drag block may be handled as no-ops until the drag metrics + * arrive. + */ + void SetTargetAPZC(uint64_t aInputBlockId, + const nsTArray& aTargets) override; + + /** + * Updates any zoom constraints contained in the tag. + * If the |aConstraints| is Nothing() then previously-provided constraints for + * the given |aGuid| are cleared. + */ + void UpdateZoomConstraints( + const ScrollableLayerGuid& aGuid, + const Maybe& aConstraints) override; + + /** + * Calls Destroy() on all APZC instances attached to the tree, and resets the + * tree back to empty. This function must be called exactly once during the + * lifetime of this APZCTreeManager, when this APZCTreeManager is no longer + * needed. Failing to call this function may prevent objects from being freed + * properly. + * This must be called on the updater thread. + */ + void ClearTree(); + + /** + * Sets the dpi value used by all AsyncPanZoomControllers attached to this + * tree manager. + * DPI defaults to 160 if not set using SetDPI() at any point. + */ + void SetDPI(float aDpiValue) override; + + /** + * Returns the current dpi value in use. + */ + float GetDPI() const; + + /** + * Find the hit testing node for the scrollbar thumb that matches these + * drag metrics. Initializes aOutThumbNode with the node, if there is one. + */ + void FindScrollThumbNode(const AsyncDragMetrics& aDragMetrics, + HitTestingTreeNodeAutoLock& aOutThumbNode); + + /** + * Sets allowed touch behavior values for current touch-session for specific + * input block (determined by aInputBlock). + * Should be invoked by the widget. Each value of the aValues arrays + * corresponds to the different touch point that is currently active. + * Must be called after receiving the TOUCH_START event that starts the + * touch-session. + * This must be called on the controller thread. + */ + void SetAllowedTouchBehavior( + uint64_t aInputBlockId, + const nsTArray& aValues) override; + + /** + * This is a callback for AsyncPanZoomController to call when it wants to + * scroll in response to a touch-move event, or when it needs to hand off + * overscroll to the next APZC. Note that because of scroll grabbing, the + * first APZC to scroll may not be the one that is receiving the touch events. + * + * |aPrev| is the APZC that received the touch events triggering the scroll + * (in the case of an initial scroll), or the last APZC to scroll (in the + * case of overscroll) + * |aStartPoint| and |aEndPoint| are in |aPrev|'s transformed screen + * coordinates (i.e. the same coordinates in which touch points are given to + * APZCs). The amount of (over)scroll is represented by two points rather + * than a displacement because with certain 3D transforms, the same + * displacement between different points in transformed coordinates can + * represent different displacements in untransformed coordinates. + * |aOverscrollHandoffChain| is the overscroll handoff chain used for + * determining the order in which scroll should be handed off between + * APZCs + * |aOverscrollHandoffChainIndex| is the next position in the overscroll + * handoff chain that should be scrolled. + * + * aStartPoint and aEndPoint will be modified depending on how much of the + * scroll each APZC consumes. This is to allow the sending APZC to go into + * an overscrolled state if no APZC further up in the handoff chain accepted + * the entire scroll. + * + * The function will return true if the entire scroll was consumed, and + * false otherwise. As this function also modifies aStartPoint and aEndPoint, + * when scroll is consumed, it should always the case that this function + * returns true if and only if IsZero(aStartPoint - aEndPoint), using the + * modified aStartPoint and aEndPoint after the function returns. + * + * The way this method works is best illustrated with an example. + * Consider three nested APZCs, A, B, and C, with C being the innermost one. + * Say B is scroll-grabbing. + * The touch events go to C because it's the innermost one (so e.g. taps + * should go through C), but the overscroll handoff chain is B -> C -> A + * because B is scroll-grabbing. + * For convenience I'll refer to the three APZC objects as A, B, and C, and + * to the tree manager object as TM. + * Here's what happens when C receives a touch-move event: + * - C.TrackTouch() calls TM.DispatchScroll() with index = 0. + * - TM.DispatchScroll() calls B.AttemptScroll() (since B is at index 0 in + * the chain). + * - B.AttemptScroll() scrolls B. If there is overscroll, it calls + * TM.DispatchScroll() with index = 1. + * - TM.DispatchScroll() calls C.AttemptScroll() (since C is at index 1 in + * the chain) + * - C.AttemptScroll() scrolls C. If there is overscroll, it calls + * TM.DispatchScroll() with index = 2. + * - TM.DispatchScroll() calls A.AttemptScroll() (since A is at index 2 in + * the chain) + * - A.AttemptScroll() scrolls A. If there is overscroll, it calls + * TM.DispatchScroll() with index = 3. + * - TM.DispatchScroll() discards the rest of the scroll as there are no + * more elements in the chain. + * + * Note: this should be used for panning only. For handing off overscroll for + * a fling, use DispatchFling(). + */ + bool DispatchScroll(AsyncPanZoomController* aPrev, + ParentLayerPoint& aStartPoint, + ParentLayerPoint& aEndPoint, + OverscrollHandoffState& aOverscrollHandoffState); + + /** + * This is a callback for AsyncPanZoomController to call when it wants to + * start a fling in response to a touch-end event, or when it needs to hand + * off a fling to the next APZC. Note that because of scroll grabbing, the + * first APZC to fling may not be the one that is receiving the touch events. + * + * @param aApzc the APZC that wants to start or hand off the fling + * @param aHandoffState a collection of state about the operation, + * which contains the following: + * + * mVelocity the current velocity of the fling, in |aApzc|'s screen + * pixels per millisecond + * mChain the chain of APZCs along which the fling + * should be handed off + * mIsHandoff is true if |aApzc| is handing off an existing fling (in + * this case the fling is given to the next APZC in the + * handoff chain after |aApzc|), and false is |aApzc| wants + * start a fling (in this case the fling is given to the + * first APZC in the chain) + * + * The return value is the "residual velocity", the portion of + * |aHandoffState.mVelocity| that was not consumed by APZCs in the + * handoff chain doing flings. + * The caller can use this value to determine whether it should consume + * the excess velocity by going into overscroll. + */ + ParentLayerPoint DispatchFling(AsyncPanZoomController* aApzc, + const FlingHandoffState& aHandoffState); + + void StartScrollbarDrag(const ScrollableLayerGuid& aGuid, + const AsyncDragMetrics& aDragMetrics) override; + + bool StartAutoscroll(const ScrollableLayerGuid& aGuid, + const ScreenPoint& aAnchorLocation) override; + + void StopAutoscroll(const ScrollableLayerGuid& aGuid) override; + + /* + * Build the chain of APZCs that will handle overscroll for a pan starting at + * |aInitialTarget|. + */ + RefPtr BuildOverscrollHandoffChain( + const RefPtr& aInitialTarget); + + /** + * Function used to disable LongTap gestures. + * + * On slow running tests, drags and touch events can be misinterpreted + * as a long tap. This allows tests to disable long tap gesture detection. + */ + void SetLongTapEnabled(bool aTapGestureEnabled) override; + + APZInputBridge* InputBridge() override { return this; } + + void AddInputBlockCallback(uint64_t aInputBlockId, + InputBlockCallback&& aCallback) override; + + // Methods to help process WidgetInputEvents (or manage conversion to/from + // InputData) + + void ProcessUnhandledEvent(LayoutDeviceIntPoint* aRefPoint, + ScrollableLayerGuid* aOutTargetGuid, + uint64_t* aOutFocusSequenceNumber, + LayersId* aOutLayersId) override; + + void UpdateWheelTransaction(LayoutDeviceIntPoint aRefPoint, + EventMessage aEventMessage) override; + + bool GetAPZTestData(LayersId aLayersId, APZTestData* aOutData); + + /** + * Iterates over the hit testing tree, collects LayersIds and associated + * transforms from layer coordinate space to root coordinate space, and + * sends these over to the main thread of the chrome process. If the provided + * |aAncestor| argument is non-null, then only the transforms for layer + * subtrees scrolled by the aAncestor (i.e. descendants of aAncestor) will be + * sent. + */ + void SendSubtreeTransformsToChromeMainThread( + const AsyncPanZoomController* aAncestor); + + /** + * Set fixed layer margins for dynamic toolbar. + */ + void SetFixedLayerMargins(ScreenIntCoord aTop, ScreenIntCoord aBottom); + + /** + * Compute the updated shadow transform for a scroll thumb layer that + * reflects async scrolling of the associated scroll frame. + * + * @param aCurrentTransform The current shadow transform on the scroll thumb + * layer, as returned by Layer::GetLocalTransform() or similar. + * @param aScrollableContentTransform The current content transform on the + * scrollable content, as returned by Layer::GetTransform(). + * @param aApzc The APZC that scrolls the scroll frame. + * @param aMetrics The metrics associated with the scroll frame, reflecting + * the last paint of the associated content. Note: this metrics should + * NOT reflect async scrolling, i.e. they should be the layer tree's + * copy of the metrics, or APZC's last-content-paint metrics. + * @param aScrollbarData The scrollbar data for the the scroll thumb layer. + * @param aScrollbarIsDescendant True iff. the scroll thumb layer is a + * descendant of the layer bearing the scroll frame's metrics. + * @param aOutClipTransform If not null, and |aScrollbarIsDescendant| is true, + * this will be populated with a transform that should be applied to the + * clip rects of all layers between the scroll thumb layer and the ancestor + * layer for the scrollable content. + * @return The new shadow transform for the scroll thumb layer, including + * any pre- or post-scales. + */ + static LayerToParentLayerMatrix4x4 ComputeTransformForScrollThumb( + const LayerToParentLayerMatrix4x4& aCurrentTransform, + const gfx::Matrix4x4& aScrollableContentTransform, + AsyncPanZoomController* aApzc, const FrameMetrics& aMetrics, + const ScrollbarData& aScrollbarData, bool aScrollbarIsDescendant, + AsyncTransformComponentMatrix* aOutClipTransform); + + /** + * Dispatch a flush complete notification from the repaint thread of the + * content controller for the given layers id. + */ + static void FlushApzRepaints(LayersId aLayersId); + + /** + * Mark |aLayersId| as having been moved from the compositor that owns this + * tree manager to a compositor that doesn't use APZ. + * See |mDetachedLayersIds| for more details. + */ + void MarkAsDetached(LayersId aLayersId); + + // Assert that the current thread is the sampler thread for this APZCTM. + void AssertOnSamplerThread(); + // Assert that the current thread is the updater thread for this APZCTM. + void AssertOnUpdaterThread(); + + // Returns a pointer to the WebRenderAPI this APZCTreeManager is for. + // This might be null (for example, if WebRender is not enabled). + already_AddRefed GetWebRenderAPI() const; + + protected: + // Protected destructor, to discourage deletion outside of Release(): + virtual ~APZCTreeManager(); + + APZSampler* GetSampler() const; + APZUpdater* GetUpdater() const; + + // We need to allow APZUpdater to lock and unlock this tree during a WR + // scene swap. We do this using private helpers to avoid exposing these + // functions to the world. + private: + friend class APZUpdater; + void LockTree(); + void UnlockTree(); + + // Protected hooks for gtests subclass + virtual AsyncPanZoomController* NewAPZCInstance( + LayersId aLayersId, GeckoContentController* aController); + + public: + // Public hook for gtests subclass + virtual SampleTime GetFrameTime(); + + // Also used for controlling time during tests + void SetTestSampleTime(const Maybe& aTime); + + private: + mutable DataMutex> mTestSampleTime; + + public: + // Represents the results of an APZ hit test. + struct HitTestResult { + // The APZC targeted by the hit test. + RefPtr mTargetApzc; + // The applicable hit test flags. + gfx::CompositorHitTestInfo mHitResult; + // The layers id of the content that was hit. + // This effectively identifiers the process that was hit for + // Fission purposes. + LayersId mLayersId; + // If a scrollbar was hit, this will be populated with the + // scrollbar node. The AutoLock allows accessing the scrollbar + // node without having to hold the tree lock. + HitTestingTreeNodeAutoLock mScrollbarNode; + // If content that is fixed to the root-content APZC was hit, + // the sides of the viewport to which the content is fixed. + SideBits mFixedPosSides = SideBits::eNone; + + HitTestResult() = default; + // Make it move-only. + HitTestResult(HitTestResult&&) = default; + HitTestResult& operator=(HitTestResult&&) = default; + Maybe HandledByRoot() const; + }; + + /* Some helper functions to find an APZC given some identifying input. These + functions lock the tree of APZCs while they find the right one, and then + return an addref'd pointer to it. This allows caller code to just use the + target APZC without worrying about it going away. These are public for + testing code and generally should not be used by other production code. + */ + RefPtr GetRootNode() const; + HitTestResult GetTargetAPZC(const ScreenPoint& aPoint); + already_AddRefed GetTargetAPZC( + const LayersId& aLayersId, + const ScrollableLayerGuid::ViewID& aScrollId) const; + ScreenToParentLayerMatrix4x4 GetScreenToApzcTransform( + const AsyncPanZoomController* aApzc) const; + ParentLayerToScreenMatrix4x4 GetApzcToGeckoTransform( + const AsyncPanZoomController* aApzc) const; + ScreenPoint GetCurrentMousePosition() const; + void SetCurrentMousePosition(const ScreenPoint& aNewPos); + + /** + * Convert a screen point of an event targeting |aApzc| to Gecko + * coordinates. + */ + Maybe ConvertToGecko(const ScreenIntPoint& aPoint, + AsyncPanZoomController* aApzc); + + /** + * Find the zoomable APZC in the same layer subtree (i.e. with the same + * layers id) as the given APZC. + */ + already_AddRefed FindZoomableApzc( + AsyncPanZoomController* aStart) const; + + ScreenMargin GetGeckoFixedLayerMargins() const; + + private: + typedef bool (*GuidComparator)(const ScrollableLayerGuid&, + const ScrollableLayerGuid&); + + /* Helpers */ + template + void UpdateHitTestingTreeImpl(const ScrollNode& aRoot, bool aIsFirstPaint, + LayersId aOriginatingLayersId, + uint32_t aPaintSequenceNumber); + + void AttachNodeToTree(HitTestingTreeNode* aNode, HitTestingTreeNode* aParent, + HitTestingTreeNode* aNextSibling); + already_AddRefed GetTargetAPZC( + const ScrollableLayerGuid& aGuid); + already_AddRefed GetTargetNode( + const ScrollableLayerGuid& aGuid, GuidComparator aComparator) const; + HitTestingTreeNode* FindTargetNode(HitTestingTreeNode* aNode, + const ScrollableLayerGuid& aGuid, + GuidComparator aComparator); + AsyncPanZoomController* GetTargetApzcForNode(HitTestingTreeNode* aNode); + HitTestResult GetAPZCAtPoint(const ScreenPoint& aHitTestPoint, + const RecursiveMutexAutoLock& aProofOfTreeLock); + HitTestResult GetAPZCAtPointWR( + const ScreenPoint& aHitTestPoint, + const RecursiveMutexAutoLock& aProofOfTreeLock); + HitTestingTreeNode* FindRootNodeForLayersId(LayersId aLayersId) const; + AsyncPanZoomController* FindRootApzcForLayersId(LayersId aLayersId) const; + AsyncPanZoomController* FindRootContentApzcForLayersId( + LayersId aLayersId) const; + already_AddRefed GetZoomableTarget( + AsyncPanZoomController* aApzc1, AsyncPanZoomController* aApzc2) const; + already_AddRefed CommonAncestor( + AsyncPanZoomController* aApzc1, AsyncPanZoomController* aApzc2) const; + + struct FixedPositionInfo; + struct StickyPositionInfo; + + // Returns true if |aNode| is a fixed layer that is fixed to the root content + // APZC. + // The map lock is required within these functions; if the map lock is already + // being held by the caller, the second overload should be used. If the map + // lock is not being held at the call site, the first overload should be used. + bool IsFixedToRootContent(const HitTestingTreeNode* aNode) const; + bool IsFixedToRootContent(const FixedPositionInfo& aFixedInfo, + const MutexAutoLock& aProofOfMapLock) const; + + // Returns the vertical sides of |aNode| that are stuck to the root content. + // The map lock is required within these functions; if the map lock is already + // being held by the caller, the second overload should be used. If the map + // lock is not being held at the call site, the first overload should be used. + SideBits SidesStuckToRootContent(const HitTestingTreeNode* aNode) const; + SideBits SidesStuckToRootContent(const StickyPositionInfo& aStickyInfo, + const MutexAutoLock& aProofOfMapLock) const; + + /** + * Perform hit testing for a touch-start event. + * + * @param aEvent The touch-start event. + * + * The remaining parameters are out-parameter used to communicate additional + * return values: + * + * @param aOutTouchBehaviors + * The touch behaviours that should be allowed for this touch block. + + * @return The results of the hit test, including the APZC that was hit. + */ + HitTestResult GetTouchInputBlockAPZC( + const MultiTouchInput& aEvent, + nsTArray* aOutTouchBehaviors); + APZEventResult ProcessTouchInput(MultiTouchInput& aInput); + /** + * Given a mouse-down event that hit a scroll thumb node, set up APZ + * dragging of the scroll thumb. + * + * Must be called after the mouse event has been sent to InputQueue. + * + * @param aMouseInput The mouse-down event. + * @param aScrollThumbNode Tthe scroll thumb node that was hit. + * @param aApzc + * The APZC for the scroll frame scrolled by the scroll thumb, if that + * scroll frame is layerized. (A thumb can be layerized without its + * target scroll frame being layerized.) Otherwise, an enclosing APZC. + */ + void SetupScrollbarDrag(MouseInput& aMouseInput, + const HitTestingTreeNodeAutoLock& aScrollThumbNode, + AsyncPanZoomController* aApzc); + /** + * Process a touch event that's part of a scrollbar touch-drag gesture. + * + * @param aInput The touch event. + * @param aScrollThumbNode + * If this is the touch-start event, the node representing the scroll + * thumb we are starting to drag. Otherwise nullptr. + * @param aHitInfo + * The hit-test flags for the touch input. + * @return See ReceiveInputEvent() for what the return value means. + */ + APZEventResult ProcessTouchInputForScrollbarDrag( + MultiTouchInput& aInput, + const HitTestingTreeNodeAutoLock& aScrollThumbNode, + const gfx::CompositorHitTestInfo& aHitInfo); + void FlushRepaintsToClearScreenToGeckoTransform(); + + void SynthesizePinchGestureFromMouseWheel( + const ScrollWheelInput& aWheelInput, + const RefPtr& aTarget); + + already_AddRefed RecycleOrCreateNode( + const RecursiveMutexAutoLock& aProofOfTreeLock, TreeBuildingState& aState, + AsyncPanZoomController* aApzc, LayersId aLayersId); + template + HitTestingTreeNode* PrepareNodeForLayer( + const RecursiveMutexAutoLock& aProofOfTreeLock, const ScrollNode& aLayer, + const FrameMetrics& aMetrics, LayersId aLayersId, + const AncestorTransform& aAncestorTransform, HitTestingTreeNode* aParent, + HitTestingTreeNode* aNextSibling, TreeBuildingState& aState); + template + Maybe ComputeClipRegion(const LayersId& aLayersId, + const ScrollNode& aLayer); + + template + void PrintAPZCInfo(const ScrollNode& aLayer, + const AsyncPanZoomController* apzc); + + void NotifyScrollbarDragInitiated(uint64_t aDragBlockId, + const ScrollableLayerGuid& aGuid, + ScrollDirection aDirection) const; + void NotifyScrollbarDragRejected(const ScrollableLayerGuid& aGuid) const; + void NotifyAutoscrollRejected(const ScrollableLayerGuid& aGuid) const; + + // Requires the caller to hold mTreeLock. + LayerToParentLayerMatrix4x4 ComputeTransformForNode( + const HitTestingTreeNode* aNode) const; + + // Look up the GeckoContentController for the given layers id. + static already_AddRefed GetContentController( + LayersId aLayersId); + + bool AdvanceAnimationsInternal(const MutexAutoLock& aProofOfMapLock, + const SampleTime& aSampleTime); + + using ClippedCompositionBoundsMap = + std::unordered_map; + // This is a recursive function that populates `aDestMap` with the clipped + // composition bounds for the APZC corresponding to `aGuid` and returns those + // bounds as a convenience. It recurses to also populate `aDestMap` with that + // APZC's ancestors. In order to do this it needs to access mApzcMap + // and therefore requires the caller to hold the map lock. + ParentLayerRect ComputeClippedCompositionBounds( + const MutexAutoLock& aProofOfMapLock, + ClippedCompositionBoundsMap& aDestMap, ScrollableLayerGuid aGuid); + + ScreenMargin GetCompositorFixedLayerMargins( + const MutexAutoLock& aProofOfMapLock) const; + + protected: + /* The input queue where input events are held until we know enough to + * figure out where they're going. Protected so gtests can access it. + */ + RefPtr mInputQueue; + + private: + /* Layers id for the root CompositorBridgeParent that owns this + * APZCTreeManager. */ + LayersId mRootLayersId; + + /* Pointer to the APZSampler instance that is bound to this APZCTreeManager. + * The sampler has a RefPtr to this class, and this non-owning raw pointer + * back to the APZSampler is nulled out in the sampler's destructor, so this + * pointer should always be valid. + */ + APZSampler* MOZ_NON_OWNING_REF mSampler; + /* Pointer to the APZUpdater instance that is bound to this APZCTreeManager. + * The updater has a RefPtr to this class, and this non-owning raw pointer + * back to the APZUpdater is nulled out in the updater's destructor, so this + * pointer should always be valid. + */ + APZUpdater* MOZ_NON_OWNING_REF mUpdater; + + /* Whenever walking or mutating the tree rooted at mRootNode, mTreeLock must + * be held. This lock does not need to be held while manipulating a single + * APZC instance in isolation (that is, if its tree pointers are not being + * accessed or mutated). The lock also needs to be held when accessing the + * mRootNode instance variable, as that is considered part of the APZC tree + * management state. + * IMPORTANT: See the note about lock ordering at the top of this file. */ + mutable mozilla::RecursiveMutex mTreeLock; + RefPtr mRootNode; + + /* + * A set of LayersIds for which APZCTM should only send empty + * MatrixMessages via NotifyLayerTransform(). + * This is used in cases where a tab has been transferred to a non-APZ + * compositor (and thus will not receive MatrixMessages reflecting its new + * transforms) and we need to make sure it doesn't get stuck with transforms + * from its old tree manager (us). + * Acquire mTreeLock before accessing this. + */ + std::unordered_set mDetachedLayersIds; + + /* If the current hit-testing tree contains an async zoom container + * node, this is set to the layers id of subtree that has the node. + */ + Maybe mAsyncZoomContainerSubtree; + + /** A lock that protects mApzcMap, mScrollThumbInfo, mRootScrollbarInfo, + * mFixedPositionInfo, and mStickyPositionInfo. + */ + mutable mozilla::Mutex mMapLock; + + /** + * Helper structure to store a bunch of things in mApzcMap so that they can + * be used from the sampler thread. + */ + struct ApzcMapData { + // A pointer to the APZC itself + RefPtr apzc; + // The parent APZC's guid, or Nothing() if there is no parent + Maybe parent; + }; + + /** + * A map for quick access to get some APZC data by guid, without having to + * acquire the tree lock. mMapLock must be acquired while accessing or + * modifying mApzcMap. + */ + std::unordered_map + mApzcMap; + /** + * A helper structure to store all the information needed to compute the + * async transform for a scrollthumb on the sampler thread. + */ + struct ScrollThumbInfo { + uint64_t mThumbAnimationId; + CSSTransformMatrix mThumbTransform; + ScrollbarData mThumbData; + ScrollableLayerGuid mTargetGuid; + CSSTransformMatrix mTargetTransform; + bool mTargetIsAncestor; + + ScrollThumbInfo(const uint64_t& aThumbAnimationId, + const CSSTransformMatrix& aThumbTransform, + const ScrollbarData& aThumbData, + const ScrollableLayerGuid& aTargetGuid, + const CSSTransformMatrix& aTargetTransform, + bool aTargetIsAncestor) + : mThumbAnimationId(aThumbAnimationId), + mThumbTransform(aThumbTransform), + mThumbData(aThumbData), + mTargetGuid(aTargetGuid), + mTargetTransform(aTargetTransform), + mTargetIsAncestor(aTargetIsAncestor) { + MOZ_ASSERT(mTargetGuid.mScrollId == mThumbData.mTargetViewId); + } + }; + /** + * If this APZCTreeManager is being used with WebRender, this vector gets + * populated during a layers update. It holds a package of information needed + * to compute and set the async transforms on scroll thumbs. This information + * is extracted from the HitTestingTreeNodes for the WebRender case because + * accessing the HitTestingTreeNodes requires holding the tree lock which + * we cannot do on the WR sampler thread. mScrollThumbInfo, however, can + * be accessed while just holding the mMapLock which is safe to do on the + * sampler thread. + * mMapLock must be acquired while accessing or modifying mScrollThumbInfo. + */ + std::vector mScrollThumbInfo; + + /** + * A helper structure to store all the information needed to compute the + * async transform for a scrollthumb on the sampler thread. + */ + struct RootScrollbarInfo { + uint64_t mScrollbarAnimationId; + ScrollDirection mScrollDirection; + + RootScrollbarInfo(const uint64_t& aScrollbarAnimationId, + const ScrollDirection aScrollDirection) + : mScrollbarAnimationId(aScrollbarAnimationId), + mScrollDirection(aScrollDirection) {} + }; + /** + * If this APZCTreeManager is being used with WebRender, this vector gets + * populated during a layers update. It holds a package of information needed + * to compute and set the async transforms on root scrollbars. This + * information is extracted from the HitTestingTreeNodes for the WebRender + * case because accessing the HitTestingTreeNodes requires holding the tree + * lock which we cannot do on the WR sampler thread. mRootScrollbarInfo, + * however, can be accessed while just holding the mMapLock which is safe to + * do on the sampler thread. + * mMapLock must be acquired while accessing or modifying mRootScrollbarInfo. + */ + std::vector mRootScrollbarInfo; + + /** + * A helper structure to store all the information needed to compute the + * async transform for a fixed position element on the sampler thread. + */ + struct FixedPositionInfo { + Maybe mFixedPositionAnimationId; + SideBits mFixedPosSides; + ScrollableLayerGuid::ViewID mFixedPosTarget; + LayersId mLayersId; + + explicit FixedPositionInfo(const HitTestingTreeNode* aNode); + }; + /** + * If this APZCTreeManager is being used with WebRender, this vector gets + * populated during a layers update. It holds a package of information needed + * to compute and set the async transforms on fixed position content. This + * information is extracted from the HitTestingTreeNodes for the WebRender + * case because accessing the HitTestingTreeNodes requires holding the tree + * lock which we cannot do on the WR sampler thread. mFixedPositionInfo, + * however, can be accessed while just holding the mMapLock which is safe to + * do on the sampler thread. mMapLock must be acquired while accessing or + * modifying mFixedPositionInfo. + */ + std::vector mFixedPositionInfo; + + /** + * A helper structure to store all the information needed to compute the + * async transform for a sticky position element on the sampler thread. + */ + struct StickyPositionInfo { + Maybe mStickyPositionAnimationId; + SideBits mFixedPosSides; + ScrollableLayerGuid::ViewID mStickyPosTarget; + LayersId mLayersId; + LayerRectAbsolute mStickyScrollRangeInner; + LayerRectAbsolute mStickyScrollRangeOuter; + + explicit StickyPositionInfo(const HitTestingTreeNode* aNode); + }; + /** + * If this APZCTreeManager is being used with WebRender, this vector gets + * populated during a layers update. It holds a package of information needed + * to compute and set the async transforms on sticky position content. This + * information is extracted from the HitTestingTreeNodes for the WebRender + * case because accessing the HitTestingTreeNodes requires holding the tree + * lock which we cannot do on the WR sampler thread. mStickyPositionInfo, + * however, can be accessed while just holding the mMapLock which is safe to + * do on the sampler thread. mMapLock must be acquired while accessing or + * modifying mStickyPositionInfo. + */ + std::vector mStickyPositionInfo; + + /* Holds the zoom constraints for scrollable layers, as determined by the + * the main-thread gecko code. This can only be accessed on the updater + * thread. */ + std::unordered_map + mZoomConstraints; + /* A list of keyboard shortcuts to use for translating keyboard inputs into + * keyboard actions. This is gathered on the main thread from XBL bindings. + * This must only be accessed on the controller thread. + */ + KeyboardMap mKeyboardMap; + /* This tracks the focus targets of chrome and content and whether we have + * a current focus target or whether we are waiting for a new confirmation. + */ + FocusState mFocusState; + /* This tracks the hit test result info for the current touch input block. + * In particular, it tracks the target APZC, the hit test flags, and the + * fixed pos sides. This is populated at the start of a touch block based + * on the hit-test result, and used for subsequent touch events in the block. + * This allows touch points to move outside the thing they started on, but + * still have the touch events delivered to the same initial APZC. This will + * only ever be touched on the input delivery thread, and so does not require + * locking. + */ + HitTestResult mTouchBlockHitResult; + /* Sometimes we want to ignore all touches except one. In such cases, this + * is set to the identifier of the touch we are not ignoring; in other cases, + * this is set to -1. + */ + int32_t mRetainedTouchIdentifier; + /* This tracks whether the current input block represents a touch-drag of + * a scrollbar. In this state, touch events are forwarded to content as touch + * events, but converted to mouse events before going into InputQueue and + * being handled by an APZC (to reuse the APZ code for scrollbar dragging + * with a mouse). + */ + bool mInScrollbarTouchDrag; + /* Tracks the number of touch points we are tracking that are currently on + * the screen. */ + TouchCounter mTouchCounter; + /* Stores the current mouse position in screen coordinates. + */ + mutable DataMutex mCurrentMousePosition; + /* Extra margins that should be applied to content that fixed wrt. the + * RCD-RSF, to account for the dynamic toolbar. + * Acquire mMapLock before accessing this. + */ + ScreenMargin mCompositorFixedLayerMargins; + /* Similar to above |mCompositorFixedLayerMargins|. But this value is the + * margins on the main-thread at the last time position:fixed elements were + * updated during the dynamic toolbar transitions. + * Acquire mMapLock before accessing this. + */ + ScreenMargin mGeckoFixedLayerMargins; + /* For logging the APZC tree for debugging (enabled by the apz.printtree + * pref). */ + gfx::TreeLog mApzcTreeLog; + + class CheckerboardFlushObserver; + friend class CheckerboardFlushObserver; + RefPtr mFlushObserver; + + // Map from layers id to APZTestData. Accesses and mutations must be + // protected by the mTestDataLock. + std::unordered_map, LayersId::HashFn> + mTestData; + mutable mozilla::Mutex mTestDataLock; + + // A state machine that tries to record how much users are overshooting + // their desired scroll destination while using the scrollwheel. + OvershootDetector mOvershootDetector; + + // This must only be touched on the controller thread. + float mDPI; + + bool mIsUsingWebRender; + +#if defined(MOZ_WIDGET_ANDROID) + private: + // Last Frame metrics sent to java through UIController. + GeckoViewMetrics mLastRootMetrics; +#endif // defined(MOZ_WIDGET_ANDROID) +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_PanZoomController_h diff --git a/gfx/layers/apz/src/APZInputBridge.cpp b/gfx/layers/apz/src/APZInputBridge.cpp new file mode 100644 index 0000000000..3472c497b5 --- /dev/null +++ b/gfx/layers/apz/src/APZInputBridge.cpp @@ -0,0 +1,215 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/APZInputBridge.h" + +#include "InputData.h" // for MouseInput, etc +#include "InputBlockState.h" // for InputBlockState +#include "mozilla/dom/WheelEventBinding.h" // for WheelEvent constants +#include "mozilla/EventStateManager.h" // for EventStateManager +#include "mozilla/layers/APZThreadUtils.h" // for AssertOnControllerThread, etc +#include "mozilla/MouseEvents.h" // for WidgetMouseEvent +#include "mozilla/StaticPrefs_apz.h" +#include "mozilla/StaticPrefs_general.h" +#include "mozilla/StaticPrefs_test.h" +#include "mozilla/TextEvents.h" // for WidgetKeyboardEvent +#include "mozilla/TouchEvents.h" // for WidgetTouchEvent +#include "mozilla/WheelHandlingHelper.h" // for WheelDeltaHorizontalizer, + // WheelDeltaAdjustmentStrategy + +namespace mozilla { +namespace layers { + +APZEventResult::APZEventResult() + : mStatus(nsEventStatus_eIgnore), + mInputBlockId(InputBlockState::NO_BLOCK_ID) {} + +static bool WillHandleMouseEvent(const WidgetMouseEventBase& aEvent) { + return aEvent.mMessage == eMouseMove || aEvent.mMessage == eMouseDown || + aEvent.mMessage == eMouseUp || aEvent.mMessage == eDragEnd || + (StaticPrefs::test_events_async_enabled() && + aEvent.mMessage == eMouseHitTest); +} + +/* static */ +Maybe APZInputBridge::ActionForWheelEvent( + WidgetWheelEvent* aEvent) { + if (!(aEvent->mDeltaMode == dom::WheelEvent_Binding::DOM_DELTA_LINE || + aEvent->mDeltaMode == dom::WheelEvent_Binding::DOM_DELTA_PIXEL || + aEvent->mDeltaMode == dom::WheelEvent_Binding::DOM_DELTA_PAGE)) { + return Nothing(); + } + return EventStateManager::APZWheelActionFor(aEvent); +} + +APZEventResult APZInputBridge::ReceiveInputEvent(WidgetInputEvent& aEvent) { + APZThreadUtils::AssertOnControllerThread(); + + APZEventResult result; + + switch (aEvent.mClass) { + case eMouseEventClass: + case eDragEventClass: { + WidgetMouseEvent& mouseEvent = *aEvent.AsMouseEvent(); + + // Note, we call this before having transformed the reference point. + if (mouseEvent.IsReal()) { + UpdateWheelTransaction(mouseEvent.mRefPoint, mouseEvent.mMessage); + } + + if (WillHandleMouseEvent(mouseEvent)) { + MouseInput input(mouseEvent); + input.mOrigin = + ScreenPoint(mouseEvent.mRefPoint.x, mouseEvent.mRefPoint.y); + + result = ReceiveInputEvent(input); + + mouseEvent.mRefPoint.x = input.mOrigin.x; + mouseEvent.mRefPoint.y = input.mOrigin.y; + mouseEvent.mFlags.mHandledByAPZ = input.mHandledByAPZ; + mouseEvent.mFocusSequenceNumber = input.mFocusSequenceNumber; + aEvent.mLayersId = input.mLayersId; + return result; + } + + ProcessUnhandledEvent(&mouseEvent.mRefPoint, &result.mTargetGuid, + &aEvent.mFocusSequenceNumber, &aEvent.mLayersId); + return result; + } + case eTouchEventClass: { + WidgetTouchEvent& touchEvent = *aEvent.AsTouchEvent(); + MultiTouchInput touchInput(touchEvent); + result = ReceiveInputEvent(touchInput); + // touchInput was modified in-place to possibly remove some + // touch points (if we are overscrolled), and the coordinates were + // modified using the APZ untransform. We need to copy these changes + // back into the WidgetInputEvent. + touchEvent.mTouches.Clear(); + touchEvent.mTouches.SetCapacity(touchInput.mTouches.Length()); + for (size_t i = 0; i < touchInput.mTouches.Length(); i++) { + *touchEvent.mTouches.AppendElement() = + touchInput.mTouches[i].ToNewDOMTouch(); + } + touchEvent.mFlags.mHandledByAPZ = touchInput.mHandledByAPZ; + touchEvent.mFocusSequenceNumber = touchInput.mFocusSequenceNumber; + aEvent.mLayersId = touchInput.mLayersId; + return result; + } + case eWheelEventClass: { + WidgetWheelEvent& wheelEvent = *aEvent.AsWheelEvent(); + + if (Maybe action = ActionForWheelEvent(&wheelEvent)) { + ScrollWheelInput::ScrollMode scrollMode = + ScrollWheelInput::SCROLLMODE_INSTANT; + if (StaticPrefs::general_smoothScroll() && + ((wheelEvent.mDeltaMode == + dom::WheelEvent_Binding::DOM_DELTA_LINE && + StaticPrefs::general_smoothScroll_mouseWheel()) || + (wheelEvent.mDeltaMode == + dom::WheelEvent_Binding::DOM_DELTA_PAGE && + StaticPrefs::general_smoothScroll_pages()))) { + scrollMode = ScrollWheelInput::SCROLLMODE_SMOOTH; + } + + WheelDeltaAdjustmentStrategy strategy = + EventStateManager::GetWheelDeltaAdjustmentStrategy(wheelEvent); + // Adjust the delta values of the wheel event if the current default + // action is to horizontalize scrolling. I.e., deltaY values are set to + // deltaX and deltaY and deltaZ values are set to 0. + // If horizontalized, the delta values will be restored and its overflow + // deltaX will become 0 when the WheelDeltaHorizontalizer instance is + // being destroyed. + WheelDeltaHorizontalizer horizontalizer(wheelEvent); + if (WheelDeltaAdjustmentStrategy::eHorizontalize == strategy) { + horizontalizer.Horizontalize(); + } + + // If the wheel event becomes no-op event, don't handle it as scroll. + if (wheelEvent.mDeltaX || wheelEvent.mDeltaY) { + ScreenPoint origin(wheelEvent.mRefPoint.x, wheelEvent.mRefPoint.y); + ScrollWheelInput input( + wheelEvent.mTime, wheelEvent.mTimeStamp, 0, scrollMode, + ScrollWheelInput::DeltaTypeForDeltaMode(wheelEvent.mDeltaMode), + origin, wheelEvent.mDeltaX, wheelEvent.mDeltaY, + wheelEvent.mAllowToOverrideSystemScrollSpeed, strategy); + input.mAPZAction = action.value(); + + // We add the user multiplier as a separate field, rather than + // premultiplying it, because if the input is converted back to a + // WidgetWheelEvent, then EventStateManager would apply the delta a + // second time. We could in theory work around this by asking ESM to + // customize the event much sooner, and then save the + // "mCustomizedByUserPrefs" bit on ScrollWheelInput - but for now, + // this seems easier. + EventStateManager::GetUserPrefsForWheelEvent( + &wheelEvent, &input.mUserDeltaMultiplierX, + &input.mUserDeltaMultiplierY); + + result = ReceiveInputEvent(input); + wheelEvent.mRefPoint.x = input.mOrigin.x; + wheelEvent.mRefPoint.y = input.mOrigin.y; + wheelEvent.mFlags.mHandledByAPZ = input.mHandledByAPZ; + wheelEvent.mFocusSequenceNumber = input.mFocusSequenceNumber; + aEvent.mLayersId = input.mLayersId; + + return result; + } + } + + UpdateWheelTransaction(aEvent.mRefPoint, aEvent.mMessage); + ProcessUnhandledEvent(&aEvent.mRefPoint, &result.mTargetGuid, + &aEvent.mFocusSequenceNumber, &aEvent.mLayersId); + result.mStatus = nsEventStatus_eIgnore; + return result; + } + case eKeyboardEventClass: { + WidgetKeyboardEvent& keyboardEvent = *aEvent.AsKeyboardEvent(); + + KeyboardInput input(keyboardEvent); + + result = ReceiveInputEvent(input); + + keyboardEvent.mFlags.mHandledByAPZ = input.mHandledByAPZ; + keyboardEvent.mFocusSequenceNumber = input.mFocusSequenceNumber; + return result; + } + default: { + UpdateWheelTransaction(aEvent.mRefPoint, aEvent.mMessage); + ProcessUnhandledEvent(&aEvent.mRefPoint, &result.mTargetGuid, + &aEvent.mFocusSequenceNumber, &aEvent.mLayersId); + return result; + } + } + + MOZ_ASSERT_UNREACHABLE("Invalid WidgetInputEvent type."); + result.mStatus = nsEventStatus_eConsumeNoDefault; + return result; +} + +std::ostream& operator<<(std::ostream& aOut, + const APZHandledResult& aHandledResult) { + switch (aHandledResult) { + case APZHandledResult::Unhandled: + aOut << "unhandled"; + break; + case APZHandledResult::HandledByRoot: { + aOut << "handled-by-root"; + break; + } + case APZHandledResult::HandledByContent: { + aOut << "handled-by-content"; + break; + } + case APZHandledResult::Invalid: { + aOut << "INVALID"; + break; + } + } + return aOut; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/src/APZPublicUtils.cpp b/gfx/layers/apz/src/APZPublicUtils.cpp new file mode 100644 index 0000000000..899b14a006 --- /dev/null +++ b/gfx/layers/apz/src/APZPublicUtils.cpp @@ -0,0 +1,145 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/APZPublicUtils.h" + +#include "AsyncPanZoomController.h" +#include "mozilla/HelperMacros.h" +#include "mozilla/StaticPrefs_general.h" + +namespace mozilla { +namespace layers { + +namespace apz { + +/*static*/ void InitializeGlobalState() { + MOZ_ASSERT(NS_IsMainThread()); + AsyncPanZoomController::InitializeGlobalState(); +} + +/*static*/ const ScreenMargin CalculatePendingDisplayPort( + const FrameMetrics& aFrameMetrics, const ParentLayerPoint& aVelocity) { + return AsyncPanZoomController::CalculatePendingDisplayPort( + aFrameMetrics, aVelocity, AsyncPanZoomController::ZoomInProgress::No); +} + +/*static*/ gfx::IntSize GetDisplayportAlignmentMultiplier( + const ScreenSize& aBaseSize) { + return AsyncPanZoomController::GetDisplayportAlignmentMultiplier(aBaseSize); +} + +static int32_t GetNormalizedAppVersion() { + static int32_t sAppVersion = 0; + if (sAppVersion == 0) { + const char* kVersion = MOZ_STRINGIFY(MOZ_APP_VERSION); + long version = strtol(kVersion, nullptr, 10); + if (version < 81 || version > 86) { + // If the version is garbage or unreasonable, set it to a value that will + // complete the migration. This also clamps legitimate values > 86 down to + // 86. + version = 86; + } + sAppVersion = static_cast(version); + } + return sAppVersion; +} + +/*static*/ std::pair GetMouseWheelAnimationDurations() { + // Special code for migrating users from old values to new values over + // a period of time. The old values are defaults prior to bug 1660933, which + // we hard-code here. The user's migration percentage is stored in the + // migration pref. If the migration percentage is zero, the user gets the old + // values, and at a 100 percentage the user gets the new values. Linear + // interpolation in between. We can control the speed of migration by + // increasing the percentage value over time (e.g. by increasing the min + // bound on the clamped migration value). Once it reaches 100 the migration + // code can be removed. + + int32_t minMS = StaticPrefs::general_smoothScroll_mouseWheel_durationMinMS(); + int32_t maxMS = StaticPrefs::general_smoothScroll_mouseWheel_durationMaxMS(); + + const int32_t oldMin = 200; + const int32_t oldMax = 400; + + int32_t migration = + StaticPrefs::general_smoothScroll_mouseWheel_migrationPercent(); + + // Increase migration by 25% for each version, starting in version 83. + int32_t version = GetNormalizedAppVersion(); + int32_t minMigrationPercentage = std::max(0, (version - 82) * 25); + MOZ_ASSERT(minMigrationPercentage >= 0 && minMigrationPercentage <= 100); + + migration = clamped(migration, minMigrationPercentage, 100); + + minMS = ((oldMin * (100 - migration)) + (minMS * migration)) / 100; + maxMS = ((oldMax * (100 - migration)) + (maxMS * migration)) / 100; + + return std::make_pair(minMS, maxMS); +} + +ScrollAnimationBezierPhysicsSettings ComputeBezierAnimationSettingsForOrigin( + ScrollOrigin aOrigin) { + int32_t minMS = 0; + int32_t maxMS = 0; + bool isOriginSmoothnessEnabled = false; + +#define READ_DURATIONS(prefbase) \ + isOriginSmoothnessEnabled = StaticPrefs::general_smoothScroll() && \ + StaticPrefs::general_smoothScroll_##prefbase(); \ + if (isOriginSmoothnessEnabled) { \ + minMS = StaticPrefs::general_smoothScroll_##prefbase##_durationMinMS(); \ + maxMS = StaticPrefs::general_smoothScroll_##prefbase##_durationMaxMS(); \ + } + + switch (aOrigin) { + case ScrollOrigin::Pixels: + READ_DURATIONS(pixels) + break; + case ScrollOrigin::Lines: + READ_DURATIONS(lines) + break; + case ScrollOrigin::Pages: + READ_DURATIONS(pages) + break; + case ScrollOrigin::MouseWheel: + READ_DURATIONS(mouseWheel) + break; + case ScrollOrigin::Scrollbars: + READ_DURATIONS(scrollbars) + break; + default: + READ_DURATIONS(other) + break; + } + +#undef READ_DURATIONS + + if (aOrigin == ScrollOrigin::MouseWheel && isOriginSmoothnessEnabled) { + std::tie(minMS, maxMS) = GetMouseWheelAnimationDurations(); + } + + if (isOriginSmoothnessEnabled) { + static const int32_t kSmoothScrollMaxAllowedAnimationDurationMS = 10000; + maxMS = clamped(maxMS, 0, kSmoothScrollMaxAllowedAnimationDurationMS); + minMS = clamped(minMS, 0, maxMS); + } + + // Keep the animation duration longer than the average event intervals + // (to "connect" consecutive scroll animations before the scroll comes to a + // stop). + double intervalRatio = + ((double)StaticPrefs::general_smoothScroll_durationToIntervalRatio()) / + 100.0; + + // Duration should be at least as long as the intervals -> ratio is at least 1 + intervalRatio = std::max(1.0, intervalRatio); + + return ScrollAnimationBezierPhysicsSettings{minMS, maxMS, intervalRatio}; +} + +} // namespace apz +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/src/APZSampler.cpp b/gfx/layers/apz/src/APZSampler.cpp new file mode 100644 index 0000000000..72a56ed856 --- /dev/null +++ b/gfx/layers/apz/src/APZSampler.cpp @@ -0,0 +1,341 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/APZSampler.h" + +#include "APZCTreeManager.h" +#include "AsyncPanZoomController.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/layers/APZThreadUtils.h" +#include "mozilla/layers/APZUtils.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/layers/LayerMetricsWrapper.h" +#include "mozilla/layers/SynchronousTask.h" +#include "TreeTraversal.h" +#include "mozilla/webrender/WebRenderAPI.h" + +namespace mozilla { +namespace layers { + +StaticMutex APZSampler::sWindowIdLock; +StaticAutoPtr>> + APZSampler::sWindowIdMap; + +APZSampler::APZSampler(const RefPtr& aApz, + bool aIsUsingWebRender) + : mApz(aApz), + mIsUsingWebRender(aIsUsingWebRender), + mThreadIdLock("APZSampler::mThreadIdLock"), + mSampleTimeLock("APZSampler::mSampleTimeLock") { + MOZ_ASSERT(aApz); + mApz->SetSampler(this); +} + +APZSampler::~APZSampler() { mApz->SetSampler(nullptr); } + +void APZSampler::Destroy() { + StaticMutexAutoLock lock(sWindowIdLock); + if (mWindowId) { + MOZ_ASSERT(sWindowIdMap); + sWindowIdMap->erase(wr::AsUint64(*mWindowId)); + } +} + +void APZSampler::SetWebRenderWindowId(const wr::WindowId& aWindowId) { + StaticMutexAutoLock lock(sWindowIdLock); + MOZ_ASSERT(!mWindowId); + mWindowId = Some(aWindowId); + if (!sWindowIdMap) { + sWindowIdMap = new std::unordered_map>(); + NS_DispatchToMainThread(NS_NewRunnableFunction( + "APZSampler::ClearOnShutdown", [] { ClearOnShutdown(&sWindowIdMap); })); + } + (*sWindowIdMap)[wr::AsUint64(aWindowId)] = this; +} + +/*static*/ +void APZSampler::SetSamplerThread(const wr::WrWindowId& aWindowId) { + if (RefPtr sampler = GetSampler(aWindowId)) { + MutexAutoLock lock(sampler->mThreadIdLock); + sampler->mSamplerThreadId = Some(PlatformThread::CurrentId()); + } +} + +/*static*/ +void APZSampler::SampleForWebRender(const wr::WrWindowId& aWindowId, + const uint64_t* aGeneratedFrameId, + wr::Transaction* aTransaction) { + if (RefPtr sampler = GetSampler(aWindowId)) { + wr::TransactionWrapper txn(aTransaction); + Maybe vsyncId = + aGeneratedFrameId ? Some(VsyncId{*aGeneratedFrameId}) : Nothing(); + sampler->SampleForWebRender(vsyncId, txn); + } +} + +void APZSampler::SetSampleTime(const SampleTime& aSampleTime) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + MutexAutoLock lock(mSampleTimeLock); + // This only gets called with WR, and the time provided is going to be + // the time at which the current vsync interval ends. i.e. it is the timestamp + // for the next vsync that will occur. + mSampleTime = aSampleTime; +} + +void APZSampler::SampleForWebRender(const Maybe& aVsyncId, + wr::TransactionWrapper& aTxn) { + AssertOnSamplerThread(); + SampleTime sampleTime; + { // scope lock + MutexAutoLock lock(mSampleTimeLock); + + // If mSampleTime is null we're in a startup phase where the + // WebRenderBridgeParent hasn't yet provided us with a sample time. + // If we're that early there probably aren't any APZ animations happening + // anyway, so using Timestamp::Now() should be fine. + // + // If mSampleTime is in the past, then this is a "delayed sampling", i.e. + // we have passed the vsync interval during which SetSampleTime was called. + // We know this because SetSampleTime is called with the timestamp for the + // next vsync (i.e. the time at which the then-ongoing vsync interval ends) + // and we expect that SampleForWebRender gets called within that same vsync + // interval. If it does not, then the SampleForWebRender call has been + // delayed. This can happen if e.g. there was a very long scene build, + // or WR decided to generate a frame after an idle period for whatever + // random reason without Gecko requesting it explicitly. In these cases + // we shouldn't use mSampleTime, because the current frame will be presented + // at the end of the *current* vsync interval rather than the vsync interval + // SetSampleTime was called in. Ideally we would be able to know when the + // *current* vsync interval ends, and use that timestamp, but plumbing that + // here is hard, so instead we use Now() which will at least get us pretty + // close. + // The exception is if we're in a test situation, where the test is + // expecting us to use a specific timestamp and we shouldn't arbitrarily + // fiddle with it. + SampleTime now = SampleTime::FromNow(); + sampleTime = + (mSampleTime.IsNull() || + (mSampleTime.Type() != SampleTime::eTest && mSampleTime < now)) + ? now + : mSampleTime; + } + mApz->SampleForWebRender(aVsyncId, aTxn, sampleTime); +} + +bool APZSampler::AdvanceAnimations(const SampleTime& aSampleTime) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + AssertOnSamplerThread(); + return mApz->AdvanceAnimations(aSampleTime); +} + +LayerToParentLayerMatrix4x4 APZSampler::ComputeTransformForScrollThumb( + const LayerToParentLayerMatrix4x4& aCurrentTransform, + const LayerMetricsWrapper& aContent, const ScrollbarData& aThumbData, + bool aScrollbarIsDescendant, + AsyncTransformComponentMatrix* aOutClipTransform) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + AssertOnSamplerThread(); + + return mApz->ComputeTransformForScrollThumb( + aCurrentTransform, aContent.GetTransform(), aContent.GetApzc(), + aContent.Metrics(), aThumbData, aScrollbarIsDescendant, + aOutClipTransform); +} + +CSSRect APZSampler::GetCurrentAsyncLayoutViewport( + const LayerMetricsWrapper& aLayer) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + AssertOnSamplerThread(); + + MOZ_ASSERT(aLayer.GetApzc()); + return aLayer.GetApzc()->GetCurrentAsyncLayoutViewport( + AsyncPanZoomController::eForCompositing); +} + +ParentLayerPoint APZSampler::GetCurrentAsyncScrollOffset( + const LayerMetricsWrapper& aLayer) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + AssertOnSamplerThread(); + + MOZ_ASSERT(aLayer.GetApzc()); + return aLayer.GetApzc()->GetCurrentAsyncScrollOffset( + AsyncPanZoomController::eForCompositing); +} + +AsyncTransform APZSampler::GetCurrentAsyncTransform( + const LayerMetricsWrapper& aLayer, AsyncTransformComponents aComponents) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + AssertOnSamplerThread(); + + MOZ_ASSERT(aLayer.GetApzc()); + return aLayer.GetApzc()->GetCurrentAsyncTransform( + AsyncPanZoomController::eForCompositing, aComponents); +} + +AsyncTransform APZSampler::GetCurrentAsyncTransform( + const LayersId& aLayersId, const ScrollableLayerGuid::ViewID& aScrollId, + AsyncTransformComponents aComponents) const { + MOZ_ASSERT(!CompositorThreadHolder::IsInCompositorThread()); + AssertOnSamplerThread(); + + RefPtr apzc = + mApz->GetTargetAPZC(aLayersId, aScrollId); + if (!apzc) { + // It's possible that this function can get called even after the target + // APZC has been already destroyed because destroying the animation which + // triggers this function call is basically processed later than the APZC, + // i.e. queue mCompositorAnimationsToDelete in WebRenderBridgeParent and + // then remove in WebRenderBridgeParent::RemoveEpochDataPriorTo. + return AsyncTransform{}; + } + + return apzc->GetCurrentAsyncTransform(AsyncPanZoomController::eForCompositing, + aComponents); +} + +Maybe APZSampler::NotifyScrollSampling( + const LayerMetricsWrapper& aLayer) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + AssertOnSamplerThread(); + MOZ_ASSERT(aLayer.GetApzc()); + return aLayer.GetApzc()->NotifyScrollSampling(); +} + +AsyncTransformComponentMatrix APZSampler::GetOverscrollTransform( + const LayerMetricsWrapper& aLayer) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + AssertOnSamplerThread(); + + MOZ_ASSERT(aLayer.GetApzc()); + return aLayer.GetApzc()->GetOverscrollTransform( + AsyncPanZoomController::eForCompositing); +} + +AsyncTransformComponentMatrix +APZSampler::GetCurrentAsyncTransformWithOverscroll( + const LayerMetricsWrapper& aLayer) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + AssertOnSamplerThread(); + + MOZ_ASSERT(aLayer.GetApzc()); + return aLayer.GetApzc()->GetCurrentAsyncTransformWithOverscroll( + AsyncPanZoomController::eForCompositing); +} + +void APZSampler::MarkAsyncTransformAppliedToContent( + const LayerMetricsWrapper& aLayer) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + AssertOnSamplerThread(); + + MOZ_ASSERT(aLayer.GetApzc()); + aLayer.GetApzc()->MarkAsyncTransformAppliedToContent(); +} + +bool APZSampler::HasUnusedAsyncTransform(const LayerMetricsWrapper& aLayer) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + AssertOnSamplerThread(); + + AsyncPanZoomController* apzc = aLayer.GetApzc(); + return apzc && !apzc->GetAsyncTransformAppliedToContent() && + !AsyncTransformComponentMatrix( + apzc->GetCurrentAsyncTransform( + AsyncPanZoomController::eForCompositing)) + .IsIdentity(); +} + +ScrollableLayerGuid APZSampler::GetGuid(const LayerMetricsWrapper& aLayer) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + AssertOnSamplerThread(); + + MOZ_ASSERT(aLayer.GetApzc()); + return aLayer.GetApzc()->GetGuid(); +} + +GeckoViewMetrics APZSampler::GetGeckoViewMetrics( + const LayerMetricsWrapper& aLayer) const { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + AssertOnSamplerThread(); + + MOZ_ASSERT(aLayer.GetApzc()); + return aLayer.GetApzc()->GetGeckoViewMetrics(); +} + +ScreenMargin APZSampler::GetGeckoFixedLayerMargins() const { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + AssertOnSamplerThread(); + + return mApz->GetGeckoFixedLayerMargins(); +} + +ParentLayerRect APZSampler::GetCompositionBounds( + const LayersId& aLayersId, + const ScrollableLayerGuid::ViewID& aScrollId) const { + // This function can get called on the compositor in case of non WebRender + // get called on the sampler thread in case of WebRender. + AssertOnSamplerThread(); + + RefPtr apzc = + mApz->GetTargetAPZC(aLayersId, aScrollId); + if (!apzc) { + // On WebRender it's possible that this function can get called even after + // the target APZC has been already destroyed because destroying the + // animation which triggers this function call is basically processed later + // than the APZC one, i.e. queue mCompositorAnimationsToDelete in + // WebRenderBridgeParent and then remove them in + // WebRenderBridgeParent::RemoveEpochDataPriorTo. + return ParentLayerRect(); + } + + return apzc->GetCompositionBounds(); +} + +void APZSampler::AssertOnSamplerThread() const { + if (APZThreadUtils::GetThreadAssertionsEnabled()) { + MOZ_ASSERT(IsSamplerThread()); + } +} + +bool APZSampler::IsSamplerThread() const { + if (mIsUsingWebRender) { + // If the sampler thread id isn't set yet then we cannot be running on the + // sampler thread (because we will have the thread id before we run any + // other C++ code on it, and this function is only ever invoked from C++ + // code), so return false in that scenario. + MutexAutoLock lock(mThreadIdLock); + return mSamplerThreadId && PlatformThread::CurrentId() == *mSamplerThreadId; + } + return CompositorThreadHolder::IsInCompositorThread(); +} + +/*static*/ +already_AddRefed APZSampler::GetSampler( + const wr::WrWindowId& aWindowId) { + RefPtr sampler; + StaticMutexAutoLock lock(sWindowIdLock); + if (sWindowIdMap) { + auto it = sWindowIdMap->find(wr::AsUint64(aWindowId)); + if (it != sWindowIdMap->end()) { + sampler = it->second; + } + } + return sampler.forget(); +} + +} // namespace layers +} // namespace mozilla + +void apz_register_sampler(mozilla::wr::WrWindowId aWindowId) { + mozilla::layers::APZSampler::SetSamplerThread(aWindowId); +} + +void apz_sample_transforms(mozilla::wr::WrWindowId aWindowId, + const uint64_t* aGeneratedFrameId, + mozilla::wr::Transaction* aTransaction) { + mozilla::layers::APZSampler::SampleForWebRender(aWindowId, aGeneratedFrameId, + aTransaction); +} + +void apz_deregister_sampler(mozilla::wr::WrWindowId aWindowId) {} diff --git a/gfx/layers/apz/src/APZUpdater.cpp b/gfx/layers/apz/src/APZUpdater.cpp new file mode 100644 index 0000000000..942bbced8e --- /dev/null +++ b/gfx/layers/apz/src/APZUpdater.cpp @@ -0,0 +1,544 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/APZUpdater.h" + +#include "APZCTreeManager.h" +#include "AsyncPanZoomController.h" +#include "base/task.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/layers/APZThreadUtils.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/layers/SynchronousTask.h" +#include "mozilla/layers/WebRenderScrollDataWrapper.h" +#include "mozilla/webrender/WebRenderAPI.h" + +namespace mozilla { +namespace layers { + +StaticMutex APZUpdater::sWindowIdLock; +StaticAutoPtr> + APZUpdater::sWindowIdMap; + +APZUpdater::APZUpdater(const RefPtr& aApz, + bool aIsUsingWebRender) + : mApz(aApz), + mDestroyed(false), + mIsUsingWebRender(aIsUsingWebRender), + mThreadIdLock("APZUpdater::ThreadIdLock"), + mQueueLock("APZUpdater::QueueLock") { + MOZ_ASSERT(aApz); + mApz->SetUpdater(this); +} + +APZUpdater::~APZUpdater() { + mApz->SetUpdater(nullptr); + + StaticMutexAutoLock lock(sWindowIdLock); + if (mWindowId) { + MOZ_ASSERT(sWindowIdMap); + // Ensure that ClearTree was called and the task got run + MOZ_ASSERT(sWindowIdMap->find(wr::AsUint64(*mWindowId)) == + sWindowIdMap->end()); + } +} + +bool APZUpdater::HasTreeManager(const RefPtr& aApz) { + return aApz.get() == mApz.get(); +} + +void APZUpdater::SetWebRenderWindowId(const wr::WindowId& aWindowId) { + StaticMutexAutoLock lock(sWindowIdLock); + MOZ_ASSERT(!mWindowId); + mWindowId = Some(aWindowId); + if (!sWindowIdMap) { + sWindowIdMap = new std::unordered_map(); + NS_DispatchToMainThread(NS_NewRunnableFunction( + "APZUpdater::ClearOnShutdown", [] { ClearOnShutdown(&sWindowIdMap); })); + } + (*sWindowIdMap)[wr::AsUint64(aWindowId)] = this; +} + +/*static*/ +void APZUpdater::SetUpdaterThread(const wr::WrWindowId& aWindowId) { + if (RefPtr updater = GetUpdater(aWindowId)) { + MutexAutoLock lock(updater->mThreadIdLock); + updater->mUpdaterThreadId = Some(PlatformThread::CurrentId()); + } +} + +/*static*/ +void APZUpdater::PrepareForSceneSwap(const wr::WrWindowId& aWindowId) { + if (RefPtr updater = GetUpdater(aWindowId)) { + updater->mApz->LockTree(); + } +} + +/*static*/ +void APZUpdater::CompleteSceneSwap(const wr::WrWindowId& aWindowId, + const wr::WrPipelineInfo& aInfo) { + RefPtr updater = GetUpdater(aWindowId); + if (!updater) { + // This should only happen in cases where PrepareForSceneSwap also got a + // null updater. No updater-thread tasks get run between PrepareForSceneSwap + // and this function, so there is no opportunity for the updater mapping + // to have gotten removed from sWindowIdMap in between the two calls. + return; + } + + for (const auto& removedPipeline : aInfo.removed_pipelines) { + LayersId layersId = wr::AsLayersId(removedPipeline.pipeline_id); + updater->mEpochData.erase(layersId); + } + // Reset the built info for all pipelines, then put it back for the ones + // that got built in this scene swap. + for (auto& i : updater->mEpochData) { + i.second.mBuilt = Nothing(); + } + for (const auto& epoch : aInfo.epochs) { + LayersId layersId = wr::AsLayersId(epoch.pipeline_id); + updater->mEpochData[layersId].mBuilt = Some(epoch.epoch); + } + + // Run any tasks that got unblocked, then unlock the tree. The order is + // important because we want to run all the tasks up to and including the + // UpdateHitTestingTree calls corresponding to the built epochs, and we + // want to run those before we release the lock (i.e. atomically with the + // scene swap). This ensures that any hit-tests always encounter a consistent + // state between the APZ tree and the built scene in WR. + // + // While we could add additional information to the queued tasks to figure + // out the minimal set of tasks we want to run here, it's easier and harmless + // to just run all the queued and now-unblocked tasks inside the lock. + // + // Note that the ProcessQueue here might remove the window id -> APZUpdater + // mapping from sWindowIdMap, but we still unlock the tree successfully to + // leave things in a good state. + updater->ProcessQueue(); + + updater->mApz->UnlockTree(); +} + +/*static*/ +void APZUpdater::ProcessPendingTasks(const wr::WrWindowId& aWindowId) { + if (RefPtr updater = GetUpdater(aWindowId)) { + updater->ProcessQueue(); + } +} + +void APZUpdater::ClearTree(LayersId aRootLayersId) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + RefPtr self = this; + RunOnUpdaterThread(aRootLayersId, + NS_NewRunnableFunction("APZUpdater::ClearTree", [=]() { + self->mApz->ClearTree(); + self->mDestroyed = true; + + // Once ClearTree is called on the APZCTreeManager, we + // are in a shutdown phase. After this point it's ok if + // WebRender cannot get a hold of the updater via the + // window id, and it's a good point to remove the mapping + // and avoid leaving a dangling pointer to this object. + StaticMutexAutoLock lock(sWindowIdLock); + if (self->mWindowId) { + MOZ_ASSERT(sWindowIdMap); + sWindowIdMap->erase(wr::AsUint64(*(self->mWindowId))); + } + })); +} + +void APZUpdater::UpdateFocusState(LayersId aRootLayerTreeId, + LayersId aOriginatingLayersId, + const FocusTarget& aFocusTarget) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + RunOnUpdaterThread(aOriginatingLayersId, + NewRunnableMethod( + "APZUpdater::UpdateFocusState", mApz, + &APZCTreeManager::UpdateFocusState, aRootLayerTreeId, + aOriginatingLayersId, aFocusTarget)); +} + +void APZUpdater::UpdateHitTestingTree(Layer* aRoot, bool aIsFirstPaint, + LayersId aOriginatingLayersId, + uint32_t aPaintSequenceNumber) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + AssertOnUpdaterThread(); + mApz->UpdateHitTestingTree(aRoot, aIsFirstPaint, aOriginatingLayersId, + aPaintSequenceNumber); +} + +void APZUpdater::UpdateScrollDataAndTreeState( + LayersId aRootLayerTreeId, LayersId aOriginatingLayersId, + const wr::Epoch& aEpoch, WebRenderScrollData&& aScrollData) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + RefPtr self = this; + // Insert an epoch requirement update into the queue, so that + // tasks inserted into the queue after this point only get executed + // once the epoch requirement is satisfied. In particular, the + // UpdateHitTestingTree call below needs to wait until the epoch requirement + // is satisfied, which is why it is a separate task in the queue. + RunOnUpdaterThread( + aOriginatingLayersId, + NS_NewRunnableFunction("APZUpdater::UpdateEpochRequirement", [=]() { + if (aRootLayerTreeId == aOriginatingLayersId) { + self->mEpochData[aOriginatingLayersId].mIsRoot = true; + } + self->mEpochData[aOriginatingLayersId].mRequired = aEpoch; + })); + RunOnUpdaterThread( + aOriginatingLayersId, + NS_NewRunnableFunction( + "APZUpdater::UpdateHitTestingTree", + [=, aScrollData = std::move(aScrollData)]() mutable { + auto isFirstPaint = aScrollData.IsFirstPaint(); + auto paintSequenceNumber = aScrollData.GetPaintSequenceNumber(); + + self->mScrollData[aOriginatingLayersId] = std::move(aScrollData); + auto root = self->mScrollData.find(aRootLayerTreeId); + if (root == self->mScrollData.end()) { + return; + } + self->mApz->UpdateHitTestingTree( + WebRenderScrollDataWrapper(*self, &(root->second)), + isFirstPaint, aOriginatingLayersId, paintSequenceNumber); + })); +} + +void APZUpdater::UpdateScrollOffsets(LayersId aRootLayerTreeId, + LayersId aOriginatingLayersId, + ScrollUpdatesMap&& aUpdates, + uint32_t aPaintSequenceNumber) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + RefPtr self = this; + RunOnUpdaterThread( + aOriginatingLayersId, + NS_NewRunnableFunction( + "APZUpdater::UpdateScrollOffsets", + [=, updates = std::move(aUpdates)]() mutable { + self->mScrollData[aOriginatingLayersId].ApplyUpdates( + std::move(updates), aPaintSequenceNumber); + auto root = self->mScrollData.find(aRootLayerTreeId); + if (root == self->mScrollData.end()) { + return; + } + self->mApz->UpdateHitTestingTree( + WebRenderScrollDataWrapper(*self, &(root->second)), + /*isFirstPaint*/ false, aOriginatingLayersId, + aPaintSequenceNumber); + })); +} + +void APZUpdater::NotifyLayerTreeAdopted(LayersId aLayersId, + const RefPtr& aOldUpdater) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + RunOnUpdaterThread(aLayersId, + NewRunnableMethod>( + "APZUpdater::NotifyLayerTreeAdopted", mApz, + &APZCTreeManager::NotifyLayerTreeAdopted, aLayersId, + aOldUpdater ? aOldUpdater->mApz : nullptr)); +} + +void APZUpdater::NotifyLayerTreeRemoved(LayersId aLayersId) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + RefPtr self = this; + RunOnUpdaterThread( + aLayersId, + NS_NewRunnableFunction("APZUpdater::NotifyLayerTreeRemoved", [=]() { + self->mEpochData.erase(aLayersId); + self->mScrollData.erase(aLayersId); + self->mApz->NotifyLayerTreeRemoved(aLayersId); + })); +} + +bool APZUpdater::GetAPZTestData(LayersId aLayersId, APZTestData* aOutData) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + + RefPtr apz = mApz; + bool ret = false; + SynchronousTask waiter("APZUpdater::GetAPZTestData"); + RunOnUpdaterThread( + aLayersId, NS_NewRunnableFunction("APZUpdater::GetAPZTestData", [&]() { + AutoCompleteTask notifier(&waiter); + ret = apz->GetAPZTestData(aLayersId, aOutData); + })); + + // Wait until the task posted above has run and populated aOutData and ret + waiter.Wait(); + + return ret; +} + +void APZUpdater::SetTestAsyncScrollOffset( + LayersId aLayersId, const ScrollableLayerGuid::ViewID& aScrollId, + const CSSPoint& aOffset) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + RefPtr apz = mApz; + RunOnUpdaterThread( + aLayersId, + NS_NewRunnableFunction("APZUpdater::SetTestAsyncScrollOffset", [=]() { + RefPtr apzc = + apz->GetTargetAPZC(aLayersId, aScrollId); + if (apzc) { + apzc->SetTestAsyncScrollOffset(aOffset); + } else { + NS_WARNING("Unable to find APZC in SetTestAsyncScrollOffset"); + } + })); +} + +void APZUpdater::SetTestAsyncZoom(LayersId aLayersId, + const ScrollableLayerGuid::ViewID& aScrollId, + const LayerToParentLayerScale& aZoom) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + RefPtr apz = mApz; + RunOnUpdaterThread( + aLayersId, NS_NewRunnableFunction("APZUpdater::SetTestAsyncZoom", [=]() { + RefPtr apzc = + apz->GetTargetAPZC(aLayersId, aScrollId); + if (apzc) { + apzc->SetTestAsyncZoom(aZoom); + } else { + NS_WARNING("Unable to find APZC in SetTestAsyncZoom"); + } + })); +} + +const WebRenderScrollData* APZUpdater::GetScrollData(LayersId aLayersId) const { + AssertOnUpdaterThread(); + auto it = mScrollData.find(aLayersId); + return (it == mScrollData.end() ? nullptr : &(it->second)); +} + +void APZUpdater::AssertOnUpdaterThread() const { + if (APZThreadUtils::GetThreadAssertionsEnabled()) { + MOZ_ASSERT(IsUpdaterThread()); + } +} + +void APZUpdater::RunOnUpdaterThread(LayersId aLayersId, + already_AddRefed aTask) { + RefPtr task = aTask; + + // In the scenario where UsingWebRenderUpdaterThread() is true, this function + // might get called early (before mUpdaterThreadId is set). In that case + // IsUpdaterThread() will return false and we'll queue the task onto + // mUpdaterQueue. This is fine; the task is still guaranteed to run (barring + // catastrophic failure) because the WakeSceneBuilder call will still trigger + // the callback to run tasks. + + if (IsUpdaterThread()) { + task->Run(); + return; + } + + if (UsingWebRenderUpdaterThread()) { + // If the updater thread is a WebRender thread, and we're not on it + // right now, save the task in the queue. We will run tasks from the queue + // during the callback from the updater thread, which we trigger by the + // call to WakeSceneBuilder. + + bool sendWakeMessage = true; + { // scope lock + MutexAutoLock lock(mQueueLock); + for (const auto& queuedTask : mUpdaterQueue) { + if (queuedTask.mLayersId == aLayersId) { + // If there's already a task in the queue with this layers id, then + // we must have previously sent a WakeSceneBuilder message (when + // adding the first task with this layers id to the queue). Either + // that hasn't been fully processed yet, or the layers id is blocked + // waiting for an epoch - in either case there's no point in sending + // another WakeSceneBuilder message. + sendWakeMessage = false; + break; + } + } + mUpdaterQueue.push_back(QueuedTask{aLayersId, task}); + } + if (sendWakeMessage) { + RefPtr api = mApz->GetWebRenderAPI(); + if (api) { + api->WakeSceneBuilder(); + } else { + // Not sure if this can happen, but it might be possible. If it does, + // the task is in the queue, but if we didn't get a WebRenderAPI it + // might never run, or it might run later if we manage to get a + // WebRenderAPI later. For now let's just emit a warning, this can + // probably be upgraded to an assert later. + NS_WARNING("Possibly dropping task posted to updater thread"); + } + } + return; + } + + if (CompositorThread()) { + CompositorThread()->Dispatch(task.forget()); + } else { + // Could happen during startup + NS_WARNING("Dropping task posted to updater thread"); + } +} + +bool APZUpdater::IsUpdaterThread() const { + if (UsingWebRenderUpdaterThread()) { + // If the updater thread id isn't set yet then we cannot be running on the + // updater thread (because we will have the thread id before we run any + // C++ code on it, and this function is only ever invoked from C++ code), + // so return false in that scenario. + MutexAutoLock lock(mThreadIdLock); + return mUpdaterThreadId && PlatformThread::CurrentId() == *mUpdaterThreadId; + } + return CompositorThreadHolder::IsInCompositorThread(); +} + +void APZUpdater::RunOnControllerThread(LayersId aLayersId, + already_AddRefed aTask) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + + RefPtr task = aTask; + + RunOnUpdaterThread(aLayersId, + NewRunnableFunction("APZUpdater::RunOnControllerThread", + &APZThreadUtils::RunOnControllerThread, + std::move(task))); +} + +bool APZUpdater::UsingWebRenderUpdaterThread() const { + return mIsUsingWebRender; +} + +/*static*/ +already_AddRefed APZUpdater::GetUpdater( + const wr::WrWindowId& aWindowId) { + RefPtr updater; + StaticMutexAutoLock lock(sWindowIdLock); + if (sWindowIdMap) { + auto it = sWindowIdMap->find(wr::AsUint64(aWindowId)); + if (it != sWindowIdMap->end()) { + updater = it->second; + } + } + return updater.forget(); +} + +void APZUpdater::ProcessQueue() { + MOZ_ASSERT(!mDestroyed); + + { // scope lock to check for emptiness + MutexAutoLock lock(mQueueLock); + if (mUpdaterQueue.empty()) { + return; + } + } + + std::deque blockedTasks; + while (true) { + QueuedTask task; + + { // scope lock to extract a task + MutexAutoLock lock(mQueueLock); + if (mUpdaterQueue.empty()) { + // If we're done processing mUpdaterQueue, swap the tasks that are + // still blocked back in and finish + std::swap(mUpdaterQueue, blockedTasks); + break; + } + task = mUpdaterQueue.front(); + mUpdaterQueue.pop_front(); + } + + // We check the task to see if it is blocked. Note that while this + // ProcessQueue function is executing, a particular layers id cannot go + // from blocked to unblocked, because only CompleteSceneSwap can unblock + // a layers id, and that also runs on the updater thread. If somehow + // a layers id gets unblocked while we're processing the queue, then it + // might result in tasks getting executed out of order. + + auto it = mEpochData.find(task.mLayersId); + if (it != mEpochData.end() && it->second.IsBlocked()) { + // If this task is blocked, put it into the blockedTasks queue that + // we will replace mUpdaterQueue with + blockedTasks.push_back(task); + } else { + // Run and discard the task + task.mRunnable->Run(); + } + } + + if (mDestroyed) { + // If we get here, then we must have just run the ClearTree task for + // this updater. There might be tasks in the queue from content subtrees + // of this window that are blocked due to stale epochs. This can happen + // if the tasks were queued after the root pipeline was removed in + // WebRender, which prevents scene builds (and therefore prevents us + // from getting updated epochs via CompleteSceneSwap). See bug 1465658 + // comment 43 for some more context. + // To avoid leaking these tasks, we discard the contents of the queue. + // This happens during window shutdown so if we don't run the tasks it's + // not going to matter much. + MutexAutoLock lock(mQueueLock); + if (!mUpdaterQueue.empty()) { + mUpdaterQueue.clear(); + } + } +} + +void APZUpdater::MarkAsDetached(LayersId aLayersId) { + mApz->MarkAsDetached(aLayersId); +} + +APZUpdater::EpochState::EpochState() : mRequired{0}, mIsRoot(false) {} + +bool APZUpdater::EpochState::IsBlocked() const { + // The root is a special case because we basically assume it is "visible" + // even before it is built for the first time. This is because building the + // scene automatically makes it visible, and we need to make sure the APZ + // scroll data gets applied atomically with that happening. + // + // Layer subtrees on the other hand do not automatically become visible upon + // being built, because there must be a another layer tree update to change + // the visibility (i.e. an ancestor layer tree update that adds the necessary + // reflayer to complete the chain of reflayers). + // + // So in the case of non-visible subtrees, we know that no hit-test will + // actually end up hitting that subtree either before or after the scene swap, + // because the subtree will remain non-visible. That in turns means that we + // can apply the APZ scroll data for that subtree epoch before the scene is + // built, because it's not going to get used anyway. And that means we don't + // need to block the queue for non-visible subtrees. Which is a good thing, + // because in practice it seems like we often have non-visible subtrees sent + // to the compositor from content. + if (mIsRoot && !mBuilt) { + return true; + } + return mBuilt && (*mBuilt < mRequired); +} + +} // namespace layers +} // namespace mozilla + +// Rust callback implementations + +void apz_register_updater(mozilla::wr::WrWindowId aWindowId) { + mozilla::layers::APZUpdater::SetUpdaterThread(aWindowId); +} + +void apz_pre_scene_swap(mozilla::wr::WrWindowId aWindowId) { + mozilla::layers::APZUpdater::PrepareForSceneSwap(aWindowId); +} + +void apz_post_scene_swap(mozilla::wr::WrWindowId aWindowId, + const mozilla::wr::WrPipelineInfo* aInfo) { + mozilla::layers::APZUpdater::CompleteSceneSwap(aWindowId, *aInfo); +} + +void apz_run_updater(mozilla::wr::WrWindowId aWindowId) { + mozilla::layers::APZUpdater::ProcessPendingTasks(aWindowId); +} + +void apz_deregister_updater(mozilla::wr::WrWindowId aWindowId) { + // Run anything that's still left. + mozilla::layers::APZUpdater::ProcessPendingTasks(aWindowId); +} diff --git a/gfx/layers/apz/src/APZUtils.cpp b/gfx/layers/apz/src/APZUtils.cpp new file mode 100644 index 0000000000..4f7d8d91a2 --- /dev/null +++ b/gfx/layers/apz/src/APZUtils.cpp @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/APZUtils.h" + +#include "mozilla/StaticPrefs_apz.h" + +namespace mozilla { +namespace layers { + +namespace apz { + +bool IsCloseToHorizontal(float aAngle, float aThreshold) { + return (aAngle < aThreshold || aAngle > (M_PI - aThreshold)); +} + +bool IsCloseToVertical(float aAngle, float aThreshold) { + return (fabs(aAngle - (M_PI / 2)) < aThreshold); +} + +bool IsStuckAtBottom(gfxFloat aTranslation, + const LayerRectAbsolute& aInnerRange, + const LayerRectAbsolute& aOuterRange) { + // The item will be stuck at the bottom if the async scroll delta is in + // the range [aOuterRange.Y(), aInnerRange.Y()]. Since the translation + // is negated with repect to the async scroll delta (i.e. scrolling down + // produces a positive scroll delta and negative translation), we invert it + // and check to see if it falls in the specified range. + return aOuterRange.Y() <= -aTranslation && -aTranslation <= aInnerRange.Y(); +} + +bool IsStuckAtTop(gfxFloat aTranslation, const LayerRectAbsolute& aInnerRange, + const LayerRectAbsolute& aOuterRange) { + // Same as IsStuckAtBottom, except we want to check for the range + // [aInnerRange.YMost(), aOuterRange.YMost()]. + return aInnerRange.YMost() <= -aTranslation && + -aTranslation <= aOuterRange.YMost(); +} + +ScreenPoint ComputeFixedMarginsOffset( + const ScreenMargin& aCompositorFixedLayerMargins, SideBits aFixedSides, + const ScreenMargin& aGeckoFixedLayerMargins) { + // Work out the necessary translation, in screen space. + ScreenPoint translation; + + ScreenMargin effectiveMargin = + aCompositorFixedLayerMargins - aGeckoFixedLayerMargins; + if ((aFixedSides & SideBits::eLeftRight) == SideBits::eLeftRight) { + translation.x += (effectiveMargin.left - effectiveMargin.right) / 2; + } else if (aFixedSides & SideBits::eRight) { + translation.x -= effectiveMargin.right; + } else if (aFixedSides & SideBits::eLeft) { + translation.x += effectiveMargin.left; + } + + if ((aFixedSides & SideBits::eTopBottom) == SideBits::eTopBottom) { + translation.y += (effectiveMargin.top - effectiveMargin.bottom) / 2; + } else if (aFixedSides & SideBits::eBottom) { + translation.y -= effectiveMargin.bottom; + } else if (aFixedSides & SideBits::eTop) { + translation.y += effectiveMargin.top; + } + + return translation; +} + +bool AboutToCheckerboard(const FrameMetrics& aPaintedMetrics, + const FrameMetrics& aCompositorMetrics) { + // The main-thread code to compute the painted area can introduce some + // rounding error due to multiple unit conversions, so we inflate the rect by + // one app unit to account for that. + CSSRect painted = (aPaintedMetrics.GetCriticalDisplayPort().IsEmpty() + ? aPaintedMetrics.GetDisplayPort() + : aPaintedMetrics.GetCriticalDisplayPort()) + + aPaintedMetrics.GetLayoutScrollOffset(); + painted.Inflate(CSSMargin::FromAppUnits(nsMargin(1, 1, 1, 1))); + + // Inflate the rect by the danger zone. See the description of the danger zone + // prefs in AsyncPanZoomController.cpp for an explanation of this. + CSSRect visible = + CSSRect(aCompositorMetrics.GetVisualScrollOffset(), + aCompositorMetrics.CalculateBoundedCompositedSizeInCssPixels()); + visible.Inflate(LayerSize(StaticPrefs::apz_danger_zone_x(), + StaticPrefs::apz_danger_zone_y()) / + aCompositorMetrics.LayersPixelsPerCSSPixel()); + + // Clamp both rects to the scrollable rect, because having either of those + // exceed the scrollable rect doesn't make sense, and could lead to false + // positives. + painted = painted.Intersect(aPaintedMetrics.GetScrollableRect()); + visible = visible.Intersect(aPaintedMetrics.GetScrollableRect()); + + return !painted.Contains(visible); +} + +} // namespace apz +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/src/APZUtils.h b/gfx/layers/apz/src/APZUtils.h new file mode 100644 index 0000000000..4e27358a12 --- /dev/null +++ b/gfx/layers/apz/src/APZUtils.h @@ -0,0 +1,207 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_APZUtils_h +#define mozilla_layers_APZUtils_h + +// This file is for APZ-related utilities that are used by code in gfx/layers +// only. For APZ-related utilities used by the Rest of the World (widget/, +// layout/, dom/, IPDL protocols, etc.), use APZPublicUtils.h. +// Do not include this header from source files outside of gfx/layers. + +#include // for uint32_t +#include +#include "gfxTypes.h" +#include "FrameMetrics.h" +#include "LayersTypes.h" +#include "UnitTransforms.h" +#include "mozilla/gfx/CompositorHitTestInfo.h" +#include "mozilla/gfx/Point.h" +#include "mozilla/DefineEnum.h" +#include "mozilla/EnumSet.h" +#include "mozilla/FloatingPoint.h" + +namespace mozilla { + +namespace layers { + +enum CancelAnimationFlags : uint32_t { + Default = 0x0, /* Cancel all animations */ + ExcludeOverscroll = 0x1, /* Don't clear overscroll */ + ScrollSnap = 0x2, /* Snap to snap points */ + ExcludeWheel = 0x4, /* Don't stop wheel smooth-scroll animations */ + TriggeredExternally = 0x8, /* Cancellation was not triggered by APZ in + response to an input event */ +}; + +inline CancelAnimationFlags operator|(CancelAnimationFlags a, + CancelAnimationFlags b) { + return static_cast(static_cast(a) | + static_cast(b)); +} + +// clang-format off +enum class ScrollSource { + // scrollTo() or something similar. + DOM, + + // Touch-screen or trackpad with gesture support. + Touch, + + // Mouse wheel. + Wheel, + + // Keyboard + Keyboard, +}; +// clang-format on + +// Epsilon to be used when comparing 'float' coordinate values +// with FuzzyEqualsAdditive. The rationale is that 'float' has 7 decimal +// digits of precision, and coordinate values should be no larger than in the +// ten thousands. Note also that the smallest legitimate difference in page +// coordinates is 1 app unit, which is 1/60 of a (CSS pixel), so this epsilon +// isn't too large. +const float COORDINATE_EPSILON = 0.02f; + +template +static bool IsZero(const gfx::PointTyped& aPoint) { + return FuzzyEqualsAdditive(aPoint.x, 0.0f, COORDINATE_EPSILON) && + FuzzyEqualsAdditive(aPoint.y, 0.0f, COORDINATE_EPSILON); +} + +// Represents async transforms consisting of a scale and a translation. +struct AsyncTransform { + explicit AsyncTransform( + LayerToParentLayerScale aScale = LayerToParentLayerScale(), + ParentLayerPoint aTranslation = ParentLayerPoint()) + : mScale(aScale), mTranslation(aTranslation) {} + + operator AsyncTransformComponentMatrix() const { + return AsyncTransformComponentMatrix::Scaling(mScale.scale, mScale.scale, 1) + .PostTranslate(mTranslation.x, mTranslation.y, 0); + } + + bool operator==(const AsyncTransform& rhs) const { + return mTranslation == rhs.mTranslation && mScale == rhs.mScale; + } + + bool operator!=(const AsyncTransform& rhs) const { return !(*this == rhs); } + + LayerToParentLayerScale mScale; + ParentLayerPoint mTranslation; +}; + +// Deem an AsyncTransformComponentMatrix (obtained by multiplying together +// one or more AsyncTransformComponentMatrix objects) as constituting a +// complete async transform. +inline AsyncTransformMatrix CompleteAsyncTransform( + const AsyncTransformComponentMatrix& aMatrix) { + return ViewAs( + aMatrix, PixelCastJustification::MultipleAsyncTransforms); +} + +struct TargetConfirmationFlags final { + explicit TargetConfirmationFlags(bool aTargetConfirmed) + : mTargetConfirmed(aTargetConfirmed), + mRequiresTargetConfirmation(false), + mHitScrollbar(false), + mHitScrollThumb(false), + mDispatchToContent(false) {} + + explicit TargetConfirmationFlags( + const gfx::CompositorHitTestInfo& aHitTestInfo) + : mTargetConfirmed( + (aHitTestInfo != gfx::CompositorHitTestInvisibleToHit) && + (aHitTestInfo & gfx::CompositorHitTestDispatchToContent).isEmpty()), + mRequiresTargetConfirmation(aHitTestInfo.contains( + gfx::CompositorHitTestFlags::eRequiresTargetConfirmation)), + mHitScrollbar( + aHitTestInfo.contains(gfx::CompositorHitTestFlags::eScrollbar)), + mHitScrollThumb(aHitTestInfo.contains( + gfx::CompositorHitTestFlags::eScrollbarThumb)), + mDispatchToContent( + !(aHitTestInfo & gfx::CompositorHitTestDispatchToContent) + .isEmpty()) {} + + bool mTargetConfirmed : 1; + bool mRequiresTargetConfirmation : 1; + bool mHitScrollbar : 1; + bool mHitScrollThumb : 1; + bool mDispatchToContent : 1; +}; + +enum class AsyncTransformComponent { eLayout, eVisual }; + +using AsyncTransformComponents = EnumSet; + +constexpr AsyncTransformComponents LayoutAndVisual( + AsyncTransformComponent::eLayout, AsyncTransformComponent::eVisual); + +/** + * Metrics that GeckoView wants to know at every composite. + * These are the effective visual scroll offset and zoom level of + * the root content APZC at composition time. + */ +struct GeckoViewMetrics { + CSSPoint mVisualScrollOffset; + CSSToParentLayerScale2D mZoom; +}; + +namespace apz { + +/** + * Is aAngle within the given threshold of the horizontal axis? + * @param aAngle an angle in radians in the range [0, pi] + * @param aThreshold an angle in radians in the range [0, pi/2] + */ +bool IsCloseToHorizontal(float aAngle, float aThreshold); + +// As above, but for the vertical axis. +bool IsCloseToVertical(float aAngle, float aThreshold); + +// Returns true if a sticky layer with async translation |aTranslation| is +// stuck with a bottom margin. The inner/outer ranges are produced by the main +// thread at the last paint, and so |aTranslation| only needs to be the +// async translation from the last paint. +bool IsStuckAtBottom(gfxFloat aTranslation, + const LayerRectAbsolute& aInnerRange, + const LayerRectAbsolute& aOuterRange); + +// Returns true if a sticky layer with async translation |aTranslation| is +// stuck with a top margin. +bool IsStuckAtTop(gfxFloat aTranslation, const LayerRectAbsolute& aInnerRange, + const LayerRectAbsolute& aOuterRange); + +/** + * Compute the translation that should be applied to a layer that's fixed + * at |eFixedSides|, to respect the fixed layer margins |aFixedMargins|. + */ +ScreenPoint ComputeFixedMarginsOffset( + const ScreenMargin& aCompositorFixedLayerMargins, SideBits aFixedSides, + const ScreenMargin& aGeckoFixedLayerMargins); + +/** + * Takes the visible rect from the compositor metrics, adds a pref-based + * margin around it, and checks to see if it is contained inside the painted + * rect from the painted metrics. Returns true if it is contained, or false + * if not. Returning false means that a (relatively) small amount of async + * scrolling/zooming can result in the visible area going outside the painted + * area and resulting in visual checkerboarding. + * Note that this may return false positives for cases where the scrollframe + * in question is nested inside other scrollframes, as the composition bounds + * used to determine the visible rect may in fact be clipped by enclosing + * scrollframes, but that is not accounted for in this function. + */ +bool AboutToCheckerboard(const FrameMetrics& aPaintedMetrics, + const FrameMetrics& aCompositorMetrics); + +} // namespace apz + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_APZUtils_h diff --git a/gfx/layers/apz/src/AndroidAPZ.cpp b/gfx/layers/apz/src/AndroidAPZ.cpp new file mode 100644 index 0000000000..4895c893de --- /dev/null +++ b/gfx/layers/apz/src/AndroidAPZ.cpp @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AndroidAPZ.h" + +#include "AndroidFlingPhysics.h" +#include "AndroidVelocityTracker.h" +#include "AsyncPanZoomController.h" +#include "GenericFlingAnimation.h" +#include "OverscrollHandoffState.h" + +namespace mozilla { +namespace layers { + +AsyncPanZoomAnimation* AndroidSpecificState::CreateFlingAnimation( + AsyncPanZoomController& aApzc, const FlingHandoffState& aHandoffState, + float aPLPPI) { + return new GenericFlingAnimation(aApzc, aHandoffState, + aPLPPI); +} + +UniquePtr AndroidSpecificState::CreateVelocityTracker( + Axis* aAxis) { + return MakeUnique(); +} + +/* static */ +void AndroidSpecificState::InitializeGlobalState() { + AndroidFlingPhysics::InitializeGlobalState(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/src/AndroidAPZ.h b/gfx/layers/apz/src/AndroidAPZ.h new file mode 100644 index 0000000000..ab30b4e612 --- /dev/null +++ b/gfx/layers/apz/src/AndroidAPZ.h @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_AndroidAPZ_h_ +#define mozilla_layers_AndroidAPZ_h_ + +#include "AsyncPanZoomAnimation.h" +#include "AsyncPanZoomController.h" + +namespace mozilla { +namespace layers { + +class AndroidSpecificState : public PlatformSpecificStateBase { + public: + virtual AndroidSpecificState* AsAndroidSpecificState() override { + return this; + } + + virtual AsyncPanZoomAnimation* CreateFlingAnimation( + AsyncPanZoomController& aApzc, const FlingHandoffState& aHandoffState, + float aPLPPI) override; + virtual UniquePtr CreateVelocityTracker( + Axis* aAxis) override; + + static void InitializeGlobalState(); +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_AndroidAPZ_h_ diff --git a/gfx/layers/apz/src/AndroidFlingPhysics.cpp b/gfx/layers/apz/src/AndroidFlingPhysics.cpp new file mode 100644 index 0000000000..7c826b377e --- /dev/null +++ b/gfx/layers/apz/src/AndroidFlingPhysics.cpp @@ -0,0 +1,216 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AndroidFlingPhysics.h" + +#include + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/StaticPrefs_apz.h" +#include "mozilla/StaticPtr.h" + +namespace mozilla { +namespace layers { + +// The fling physics calculations implemented here are adapted from +// Chrome's implementation of fling physics on Android: +// https://cs.chromium.org/chromium/src/ui/events/android/scroller.cc?rcl=3ae3aaff927038a5c644926842cb0c31dea60c79 + +static double ComputeDeceleration(float aDPI) { + const float kFriction = 0.84f; + const float kGravityEarth = 9.80665f; + return kGravityEarth // g (m/s^2) + * 39.37f // inch/meter + * aDPI // pixels/inch + * kFriction; +} + +// == std::log(0.78f) / std::log(0.9f) +const float kDecelerationRate = 2.3582018f; + +// Default friction constant in android.view.ViewConfiguration. +static float GetFlingFriction() { + return StaticPrefs::apz_android_chrome_fling_physics_friction(); +} + +// Tension lines cross at (GetInflexion(), 1). +static float GetInflexion() { + // Clamp the inflexion to the range [0,1]. Values outside of this range + // do not make sense in the physics model, and for negative values the + // approximation used to compute the spline curve does not converge. + const float inflexion = + StaticPrefs::apz_android_chrome_fling_physics_inflexion(); + if (inflexion < 0.0f) { + return 0.0f; + } + if (inflexion > 1.0f) { + return 1.0f; + } + return inflexion; +} + +// Fling scroll is stopped when the scroll position is |kThresholdForFlingEnd| +// pixels or closer from the end. +static float GetThresholdForFlingEnd() { + return StaticPrefs::apz_android_chrome_fling_physics_stop_threshold(); +} + +static double ComputeSplineDeceleration(ParentLayerCoord aVelocity, + double aTuningCoeff) { + float velocityPerSec = aVelocity * 1000.0f; + return std::log(GetInflexion() * velocityPerSec / + (GetFlingFriction() * aTuningCoeff)); +} + +static TimeDuration ComputeFlingDuration(ParentLayerCoord aVelocity, + double aTuningCoeff) { + const double splineDecel = ComputeSplineDeceleration(aVelocity, aTuningCoeff); + const double timeSeconds = std::exp(splineDecel / (kDecelerationRate - 1.0)); + return TimeDuration::FromSeconds(timeSeconds); +} + +static ParentLayerCoord ComputeFlingDistance(ParentLayerCoord aVelocity, + double aTuningCoeff) { + const double splineDecel = ComputeSplineDeceleration(aVelocity, aTuningCoeff); + return GetFlingFriction() * aTuningCoeff * + std::exp(kDecelerationRate / (kDecelerationRate - 1.0) * splineDecel); +} + +struct SplineConstants { + public: + SplineConstants() { + const float kStartTension = 0.5f; + const float kEndTension = 1.0f; + const float kP1 = kStartTension * GetInflexion(); + const float kP2 = 1.0f - kEndTension * (1.0f - GetInflexion()); + + float xMin = 0.0f; + for (int i = 0; i < kNumSamples; i++) { + const float alpha = static_cast(i) / kNumSamples; + + float xMax = 1.0f; + float x, tx, coef; + // While the inflexion can be overridden by the user, it's clamped to + // [0,1]. For values in this range, the approximation algorithm below + // should converge in < 20 iterations. For good measure, we impose an + // iteration limit as well. + static const int sIterationLimit = 100; + int iterations = 0; + while (iterations++ < sIterationLimit) { + x = xMin + (xMax - xMin) / 2.0f; + coef = 3.0f * x * (1.0f - x); + tx = coef * ((1.0f - x) * kP1 + x * kP2) + x * x * x; + if (FuzzyEqualsAdditive(tx, alpha)) { + break; + } + if (tx > alpha) { + xMax = x; + } else { + xMin = x; + } + } + mSplinePositions[i] = coef * ((1.0f - x) * kStartTension + x) + x * x * x; + } + mSplinePositions[kNumSamples] = 1.0f; + } + + void CalculateCoefficients(float aTime, float* aOutDistanceCoef, + float* aOutVelocityCoef) { + *aOutDistanceCoef = 1.0f; + *aOutVelocityCoef = 0.0f; + const int index = static_cast(kNumSamples * aTime); + if (index < kNumSamples) { + const float tInf = static_cast(index) / kNumSamples; + const float dInf = mSplinePositions[index]; + const float tSup = static_cast(index + 1) / kNumSamples; + const float dSup = mSplinePositions[index + 1]; + *aOutVelocityCoef = (dSup - dInf) / (tSup - tInf); + *aOutDistanceCoef = dInf + (aTime - tInf) * *aOutVelocityCoef; + } + } + + private: + static const int kNumSamples = 100; + float mSplinePositions[kNumSamples + 1]; +}; + +StaticAutoPtr gSplineConstants; + +/* static */ +void AndroidFlingPhysics::InitializeGlobalState() { + gSplineConstants = new SplineConstants(); + ClearOnShutdown(&gSplineConstants); +} + +void AndroidFlingPhysics::Init(const ParentLayerPoint& aStartingVelocity, + float aPLPPI) { + mVelocity = aStartingVelocity.Length(); + // We should not have created a fling animation if there is no velocity. + MOZ_ASSERT(mVelocity != 0.0f); + const double tuningCoeff = ComputeDeceleration(aPLPPI); + mTargetDuration = ComputeFlingDuration(mVelocity, tuningCoeff); + MOZ_ASSERT(!mTargetDuration.IsZero()); + mDurationSoFar = TimeDuration(); + mLastPos = ParentLayerPoint(); + mCurrentPos = ParentLayerPoint(); + float coeffX = mVelocity == 0 ? 1.0f : aStartingVelocity.x / mVelocity; + float coeffY = mVelocity == 0 ? 1.0f : aStartingVelocity.y / mVelocity; + mTargetDistance = ComputeFlingDistance(mVelocity, tuningCoeff); + mTargetPos = + ParentLayerPoint(mTargetDistance * coeffX, mTargetDistance * coeffY); + const float hyp = mTargetPos.Length(); + if (FuzzyEqualsAdditive(hyp, 0.0f)) { + mDeltaNorm = ParentLayerPoint(1, 1); + } else { + mDeltaNorm = ParentLayerPoint(mTargetPos.x / hyp, mTargetPos.y / hyp); + } +} +void AndroidFlingPhysics::Sample(const TimeDuration& aDelta, + ParentLayerPoint* aOutVelocity, + ParentLayerPoint* aOutOffset) { + float newVelocity; + if (SampleImpl(aDelta, &newVelocity)) { + *aOutOffset = (mCurrentPos - mLastPos); + *aOutVelocity = ParentLayerPoint(mDeltaNorm.x * newVelocity, + mDeltaNorm.y * newVelocity); + mLastPos = mCurrentPos; + } else { + *aOutOffset = (mTargetPos - mLastPos); + *aOutVelocity = ParentLayerPoint(); + } +} + +bool AndroidFlingPhysics::SampleImpl(const TimeDuration& aDelta, + float* aOutVelocity) { + mDurationSoFar += aDelta; + if (mDurationSoFar >= mTargetDuration) { + return false; + } + + const float timeRatio = + mDurationSoFar.ToSeconds() / mTargetDuration.ToSeconds(); + float distanceCoef = 1.0f; + float velocityCoef = 0.0f; + gSplineConstants->CalculateCoefficients(timeRatio, &distanceCoef, + &velocityCoef); + + // The caller expects the velocity in pixels per _millisecond_. + *aOutVelocity = + velocityCoef * mTargetDistance / mTargetDuration.ToMilliseconds(); + + mCurrentPos = mTargetPos * distanceCoef; + + ParentLayerPoint remainder = mTargetPos - mCurrentPos; + const float threshold = GetThresholdForFlingEnd(); + if (fabsf(remainder.x) < threshold && fabsf(remainder.y) < threshold) { + return false; + } + + return true; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/src/AndroidFlingPhysics.h b/gfx/layers/apz/src/AndroidFlingPhysics.h new file mode 100644 index 0000000000..68fb53e804 --- /dev/null +++ b/gfx/layers/apz/src/AndroidFlingPhysics.h @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_AndroidFlingPhysics_h_ +#define mozilla_layers_AndroidFlingPhysics_h_ + +#include "AsyncPanZoomController.h" +#include "Units.h" +#include "mozilla/Assertions.h" + +namespace mozilla { +namespace layers { + +class AndroidFlingPhysics { + public: + void Init(const ParentLayerPoint& aVelocity, float aPLPPI); + void Sample(const TimeDuration& aDelta, ParentLayerPoint* aOutVelocity, + ParentLayerPoint* aOutOffset); + + static void InitializeGlobalState(); + + private: + // Returns false if the animation should end. + bool SampleImpl(const TimeDuration& aDelta, float* aOutVelocity); + + // Information pertaining to the current fling. + // This is initialized on each call to Init(). + ParentLayerCoord mVelocity; // diagonal velocity (length of velocity vector) + TimeDuration mTargetDuration; + TimeDuration mDurationSoFar; + ParentLayerPoint mLastPos; + ParentLayerPoint mCurrentPos; + ParentLayerCoord mTargetDistance; // diagonal distance + ParentLayerPoint mTargetPos; // really a target *offset* relative to the + // start position, which we don't track + ParentLayerPoint mDeltaNorm; // mTargetPos with length normalized to 1 +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_AndroidFlingPhysics_h_ diff --git a/gfx/layers/apz/src/AndroidVelocityTracker.cpp b/gfx/layers/apz/src/AndroidVelocityTracker.cpp new file mode 100644 index 0000000000..a355811a00 --- /dev/null +++ b/gfx/layers/apz/src/AndroidVelocityTracker.cpp @@ -0,0 +1,288 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AndroidVelocityTracker.h" + +#include "mozilla/StaticPrefs_apz.h" + +namespace mozilla { +namespace layers { + +// This velocity tracker implementation was adapted from Chromium's +// second-order unweighted least-squares velocity tracker strategy +// (https://cs.chromium.org/chromium/src/ui/events/gesture_detection/velocity_tracker.cc?l=101&rcl=9ea9a086d4f54c702ec9a38e55fb3eb8bbc2401b). + +// Threshold between position updates for determining that a pointer has +// stopped moving. Some input devices do not send move events in the +// case where a pointer has stopped. We need to detect this case so that we can +// accurately predict the velocity after the pointer starts moving again. +static const TimeDuration kAssumePointerMoveStoppedTime = + TimeDuration::FromMilliseconds(40); + +// The degree of the approximation. +static const uint8_t kDegree = 2; + +// The degree of the polynomial used in SolveLeastSquares(). +// This should be the degree of the approximation plus one. +static const uint8_t kPolyDegree = kDegree + 1; + +// Maximum size of position history. +static const uint8_t kHistorySize = 20; + +AndroidVelocityTracker::AndroidVelocityTracker() {} + +void AndroidVelocityTracker::StartTracking(ParentLayerCoord aPos, + TimeStamp aTimestamp) { + Clear(); + mHistory.AppendElement(std::make_pair(aTimestamp, aPos)); + mLastEventTime = aTimestamp; +} + +Maybe AndroidVelocityTracker::AddPosition(ParentLayerCoord aPos, + TimeStamp aTimestamp) { + if ((aTimestamp - mLastEventTime) >= kAssumePointerMoveStoppedTime) { + Clear(); + } + + if ((aTimestamp - mLastEventTime).ToMilliseconds() < 1.0) { + // If we get a sample within a millisecond of the previous one, + // just update its position. Two samples in the history with the + // same timestamp can lead to things like infinite velocities. + if (mHistory.Length() > 0) { + mHistory[mHistory.Length() - 1].second = aPos; + } + } else { + mHistory.AppendElement(std::make_pair(aTimestamp, aPos)); + if (mHistory.Length() > kHistorySize) { + mHistory.RemoveElementAt(0); + } + } + + mLastEventTime = aTimestamp; + + if (mHistory.Length() < 2) { + return Nothing(); + } + + auto start = mHistory[mHistory.Length() - 2]; + auto end = mHistory[mHistory.Length() - 1]; + auto velocity = + (end.second - start.second) / (end.first - start.first).ToMilliseconds(); + // The velocity needs to be negated because the positions represent + // touch positions, and the direction of scrolling is opposite to the + // direction of the finger's movement. + return Some(-velocity); +} + +static float VectorDot(const float* a, const float* b, uint32_t m) { + float r = 0; + while (m--) { + r += *(a++) * *(b++); + } + return r; +} + +static float VectorNorm(const float* a, uint32_t m) { + float r = 0; + while (m--) { + float t = *(a++); + r += t * t; + } + return sqrtf(r); +} + +/** + * Solves a linear least squares problem to obtain a N degree polynomial that + * fits the specified input data as nearly as possible. + * + * Returns true if a solution is found, false otherwise. + * + * The input consists of two vectors of data points X and Y with indices 0..m-1 + * along with a weight vector W of the same size. + * + * The output is a vector B with indices 0..n that describes a polynomial + * that fits the data, such the sum of W[i] * W[i] * abs(Y[i] - (B[0] + B[1] + * X[i] * + B[2] X[i]^2 ... B[n] X[i]^n)) for all i between 0 and m-1 is + * minimized. + * + * Accordingly, the weight vector W should be initialized by the caller with the + * reciprocal square root of the variance of the error in each input data point. + * In other words, an ideal choice for W would be W[i] = 1 / var(Y[i]) = 1 / + * stddev(Y[i]). + * The weights express the relative importance of each data point. If the + * weights are* all 1, then the data points are considered to be of equal + * importance when fitting the polynomial. It is a good idea to choose weights + * that diminish the importance of data points that may have higher than usual + * error margins. + * + * Errors among data points are assumed to be independent. W is represented + * here as a vector although in the literature it is typically taken to be a + * diagonal matrix. + * + * That is to say, the function that generated the input data can be + * approximated by y(x) ~= B[0] + B[1] x + B[2] x^2 + ... + B[n] x^n. + * + * The coefficient of determination (R^2) is also returned to describe the + * goodness of fit of the model for the given data. It is a value between 0 + * and 1, where 1 indicates perfect correspondence. + * + * This function first expands the X vector to a m by n matrix A such that + * A[i][0] = 1, A[i][1] = X[i], A[i][2] = X[i]^2, ..., A[i][n] = X[i]^n, then + * multiplies it by w[i]. + * + * Then it calculates the QR decomposition of A yielding an m by m orthonormal + * matrix Q and an m by n upper triangular matrix R. Because R is upper + * triangular (lower part is all zeroes), we can simplify the decomposition into + * an m by n matrix Q1 and a n by n matrix R1 such that A = Q1 R1. + * + * Finally we solve the system of linear equations given by + * R1 B = (Qtranspose W Y) to find B. + * + * For efficiency, we lay out A and Q column-wise in memory because we + * frequently operate on the column vectors. Conversely, we lay out R row-wise. + * + * http://en.wikipedia.org/wiki/Numerical_methods_for_linear_least_squares + * http://en.wikipedia.org/wiki/Gram-Schmidt + */ +static bool SolveLeastSquares(const float* x, const float* y, const float* w, + uint32_t m, uint32_t n, float* out_b) { + // MSVC does not support variable-length arrays (used by the original Android + // implementation of this function). +#if defined(COMPILER_MSVC) + const uint32_t M_ARRAY_LENGTH = VelocityTracker::kHistorySize; + const uint32_t N_ARRAY_LENGTH = VelocityTracker::kPolyDegree; + DCHECK_LE(m, M_ARRAY_LENGTH); + DCHECK_LE(n, N_ARRAY_LENGTH); +#else + const uint32_t M_ARRAY_LENGTH = m; + const uint32_t N_ARRAY_LENGTH = n; +#endif + + // Expand the X vector to a matrix A, pre-multiplied by the weights. + float a[N_ARRAY_LENGTH][M_ARRAY_LENGTH]; // column-major order + for (uint32_t h = 0; h < m; h++) { + a[0][h] = w[h]; + for (uint32_t i = 1; i < n; i++) { + a[i][h] = a[i - 1][h] * x[h]; + } + } + + // Apply the Gram-Schmidt process to A to obtain its QR decomposition. + + // Orthonormal basis, column-major order. + float q[N_ARRAY_LENGTH][M_ARRAY_LENGTH]; + // Upper triangular matrix, row-major order. + float r[N_ARRAY_LENGTH][N_ARRAY_LENGTH]; + for (uint32_t j = 0; j < n; j++) { + for (uint32_t h = 0; h < m; h++) { + q[j][h] = a[j][h]; + } + for (uint32_t i = 0; i < j; i++) { + float dot = VectorDot(&q[j][0], &q[i][0], m); + for (uint32_t h = 0; h < m; h++) { + q[j][h] -= dot * q[i][h]; + } + } + + float norm = VectorNorm(&q[j][0], m); + if (norm < 0.000001f) { + // vectors are linearly dependent or zero so no solution + return false; + } + + float invNorm = 1.0f / norm; + for (uint32_t h = 0; h < m; h++) { + q[j][h] *= invNorm; + } + for (uint32_t i = 0; i < n; i++) { + r[j][i] = i < j ? 0 : VectorDot(&q[j][0], &a[i][0], m); + } + } + + // Solve R B = Qt W Y to find B. This is easy because R is upper triangular. + // We just work from bottom-right to top-left calculating B's coefficients. + float wy[M_ARRAY_LENGTH]; + for (uint32_t h = 0; h < m; h++) { + wy[h] = y[h] * w[h]; + } + for (uint32_t i = n; i-- != 0;) { + out_b[i] = VectorDot(&q[i][0], wy, m); + for (uint32_t j = n - 1; j > i; j--) { + out_b[i] -= r[i][j] * out_b[j]; + } + out_b[i] /= r[i][i]; + } + + return true; +} + +Maybe AndroidVelocityTracker::ComputeVelocity(TimeStamp aTimestamp) { + if (mHistory.IsEmpty()) { + return Nothing{}; + } + + // Polynomial coefficients describing motion along the axis. + float xcoeff[kPolyDegree + 1]; + for (size_t i = 0; i <= kPolyDegree; i++) { + xcoeff[i] = 0; + } + + // Iterate over movement samples in reverse time order and collect samples. + float pos[kHistorySize]; + float w[kHistorySize]; + float time[kHistorySize]; + uint32_t m = 0; + int index = mHistory.Length() - 1; + const TimeDuration horizon = TimeDuration::FromMilliseconds( + StaticPrefs::apz_velocity_relevance_time_ms()); + const auto& newest_movement = mHistory[index]; + + do { + const auto& movement = mHistory[index]; + TimeDuration age = newest_movement.first - movement.first; + if (age > horizon) break; + + ParentLayerCoord position = movement.second; + pos[m] = position; + w[m] = 1.0f; + time[m] = + -static_cast(age.ToMilliseconds()) / 1000.0f; // in seconds + index--; + m++; + } while (index >= 0); + + if (m == 0) { + return Nothing{}; // no data + } + + // Calculate a least squares polynomial fit. + + // Polynomial degree (number of coefficients), or zero if no information is + // available. + uint32_t degree = kDegree; + if (degree > m - 1) { + degree = m - 1; + } + + if (degree >= 1) { // otherwise, no velocity data available + uint32_t n = degree + 1; + if (SolveLeastSquares(time, pos, w, m, n, xcoeff)) { + float velocity = xcoeff[1]; + + // The velocity needs to be negated because the positions represent + // touch positions, and the direction of scrolling is opposite to the + // direction of the finger's movement. + return Some(-velocity / 1000.0f); // convert to pixels per millisecond + } + } + + return Nothing{}; +} + +void AndroidVelocityTracker::Clear() { mHistory.Clear(); } + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/src/AndroidVelocityTracker.h b/gfx/layers/apz/src/AndroidVelocityTracker.h new file mode 100644 index 0000000000..40e346a9ea --- /dev/null +++ b/gfx/layers/apz/src/AndroidVelocityTracker.h @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_AndroidVelocityTracker_h +#define mozilla_layers_AndroidVelocityTracker_h + +#include +#include + +#include "Axis.h" +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" +#include "nsTArray.h" + +namespace mozilla { +namespace layers { + +class AndroidVelocityTracker : public VelocityTracker { + public: + explicit AndroidVelocityTracker(); + void StartTracking(ParentLayerCoord aPos, TimeStamp aTimestamp) override; + Maybe AddPosition(ParentLayerCoord aPos, + TimeStamp aTimestamp) override; + Maybe ComputeVelocity(TimeStamp aTimestamp) override; + void Clear() override; + + private: + // A queue of (timestamp, position) pairs; these are the historical + // positions at the given timestamps. + nsTArray> mHistory; + // The last time an event was added to the tracker, or the null moment if no + // events have been added. + TimeStamp mLastEventTime; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/apz/src/AsyncDragMetrics.h b/gfx/layers/apz/src/AsyncDragMetrics.h new file mode 100644 index 0000000000..f7f24c3781 --- /dev/null +++ b/gfx/layers/apz/src/AsyncDragMetrics.h @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_DragMetrics_h +#define mozilla_layers_DragMetrics_h + +#include "mozilla/layers/ScrollableLayerGuid.h" +#include "LayersTypes.h" +#include "mozilla/Maybe.h" + +namespace IPC { +template +struct ParamTraits; +} // namespace IPC + +namespace mozilla { + +namespace layers { + +class AsyncDragMetrics { + friend struct IPC::ParamTraits; + + public: + // IPC constructor + AsyncDragMetrics() + : mViewId(0), + mPresShellId(0), + mDragStartSequenceNumber(0), + mScrollbarDragOffset(0) {} + + AsyncDragMetrics(const ScrollableLayerGuid::ViewID& aViewId, + uint32_t aPresShellId, uint64_t aDragStartSequenceNumber, + CSSCoord aScrollbarDragOffset, ScrollDirection aDirection) + : mViewId(aViewId), + mPresShellId(aPresShellId), + mDragStartSequenceNumber(aDragStartSequenceNumber), + mScrollbarDragOffset(aScrollbarDragOffset), + mDirection(Some(aDirection)) {} + + ScrollableLayerGuid::ViewID mViewId; + uint32_t mPresShellId; + uint64_t mDragStartSequenceNumber; + CSSCoord mScrollbarDragOffset; // relative to the thumb's start offset + Maybe mDirection; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/apz/src/AsyncPanZoomAnimation.h b/gfx/layers/apz/src/AsyncPanZoomAnimation.h new file mode 100644 index 0000000000..3839444bac --- /dev/null +++ b/gfx/layers/apz/src/AsyncPanZoomAnimation.h @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_AsyncPanZoomAnimation_h_ +#define mozilla_layers_AsyncPanZoomAnimation_h_ + +#include "APZUtils.h" +#include "mozilla/RefPtr.h" +#include "mozilla/TimeStamp.h" +#include "nsISupportsImpl.h" +#include "nsTArray.h" +#include "nsThreadUtils.h" + +namespace mozilla { +namespace layers { + +struct FrameMetrics; + +class WheelScrollAnimation; +class KeyboardScrollAnimation; +class SmoothMsdScrollAnimation; +class SmoothScrollAnimation; + +class AsyncPanZoomAnimation { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AsyncPanZoomAnimation) + + public: + explicit AsyncPanZoomAnimation() = default; + + virtual bool DoSample(FrameMetrics& aFrameMetrics, + const TimeDuration& aDelta) = 0; + + /** + * Attempt to handle a main-thread scroll offset update without cancelling + * the animation. This may or may not make sense depending on the type of + * the animation and whether the scroll update is relative or absolute. + * + * If the scroll update is relative, |aRelativeDelta| will contain the + * delta of the relative update. If the scroll update is absolute, + * |aRelativeDelta| will be Nothing() (the animation can check the APZC's + * FrameMetrics for the new absolute scroll offset if it wants to handle + * and absolute update). + * + * Returns whether the animation could handle the scroll update. If the + * return value is false, the animation will be cancelled. + */ + virtual bool HandleScrollOffsetUpdate(const Maybe& aRelativeDelta) { + return false; + } + + bool Sample(FrameMetrics& aFrameMetrics, const TimeDuration& aDelta) { + // In some situations, particularly when handoff is involved, it's possible + // for |aDelta| to be negative on the first call to sample. Ignore such a + // sample here, to avoid each derived class having to deal with this case. + if (aDelta.ToMilliseconds() <= 0) { + return true; + } + + return DoSample(aFrameMetrics, aDelta); + } + + /** + * Get the deferred tasks in |mDeferredTasks| and place them in |aTasks|. See + * |mDeferredTasks| for more information. Clears |mDeferredTasks|. + */ + nsTArray> TakeDeferredTasks() { + return std::move(mDeferredTasks); + } + + virtual KeyboardScrollAnimation* AsKeyboardScrollAnimation() { + return nullptr; + } + virtual WheelScrollAnimation* AsWheelScrollAnimation() { return nullptr; } + virtual SmoothMsdScrollAnimation* AsSmoothMsdScrollAnimation() { + return nullptr; + } + virtual SmoothScrollAnimation* AsSmoothScrollAnimation() { return nullptr; } + + virtual bool WantsRepaints() { return true; } + + virtual void Cancel(CancelAnimationFlags aFlags) {} + + protected: + // Protected destructor, to discourage deletion outside of Release(): + virtual ~AsyncPanZoomAnimation() = default; + + /** + * Tasks scheduled for execution after the APZC's mMonitor is released. + * Derived classes can add tasks here in Sample(), and the APZC can call + * ExecuteDeferredTasks() to execute them. + */ + nsTArray> mDeferredTasks; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_AsyncPanZoomAnimation_h_ diff --git a/gfx/layers/apz/src/AsyncPanZoomController.cpp b/gfx/layers/apz/src/AsyncPanZoomController.cpp new file mode 100644 index 0000000000..66bd97173a --- /dev/null +++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp @@ -0,0 +1,5713 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AsyncPanZoomController.h" // for AsyncPanZoomController, etc + +#include // for fabsf, fabs, atan2 +#include // for uint32_t, uint64_t +#include // for int32_t +#include // for max, min + +#include "APZCTreeManager.h" // for APZCTreeManager +#include "AsyncPanZoomAnimation.h" // for AsyncPanZoomAnimation +#include "AutoDirWheelDeltaAdjuster.h" // for APZAutoDirWheelDeltaAdjuster +#include "AutoscrollAnimation.h" // for AutoscrollAnimation +#include "Axis.h" // for AxisX, AxisY, Axis, etc +#include "CheckerboardEvent.h" // for CheckerboardEvent +#include "Compositor.h" // for Compositor +#include "DesktopFlingPhysics.h" // for DesktopFlingPhysics +#include "FrameMetrics.h" // for FrameMetrics, etc +#include "GenericFlingAnimation.h" // for GenericFlingAnimation +#include "GestureEventListener.h" // for GestureEventListener +#include "HitTestingTreeNode.h" // for HitTestingTreeNode +#include "InputData.h" // for MultiTouchInput, etc +#include "InputBlockState.h" // for InputBlockState, TouchBlockState +#include "InputQueue.h" // for InputQueue +#include "Overscroll.h" // for OverscrollAnimation +#include "OverscrollHandoffState.h" // for OverscrollHandoffState +#include "SimpleVelocityTracker.h" // for SimpleVelocityTracker +#include "Units.h" // for CSSRect, CSSPoint, etc +#include "UnitTransforms.h" // for TransformTo +#include "base/message_loop.h" // for MessageLoop +#include "base/task.h" // for NewRunnableMethod, etc +#include "gfxTypes.h" // for gfxFloat +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/BasicEvents.h" // for Modifiers, MODIFIER_* +#include "mozilla/ClearOnShutdown.h" // for ClearOnShutdown +#include "mozilla/ComputedTimingFunction.h" // for ComputedTimingFunction +#include "mozilla/EventForwards.h" // for nsEventStatus_* +#include "mozilla/EventStateManager.h" // for EventStateManager +#include "mozilla/MouseEvents.h" // for WidgetWheelEvent +#include "mozilla/Preferences.h" // for Preferences +#include "mozilla/RecursiveMutex.h" // for RecursiveMutexAutoLock, etc +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/ScrollTypes.h" +#include "mozilla/StaticPrefs_apz.h" +#include "mozilla/StaticPrefs_general.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/StaticPrefs_mousewheel.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/StaticPrefs_slider.h" +#include "mozilla/StaticPrefs_test.h" +#include "mozilla/StaticPrefs_toolkit.h" +#include "mozilla/Telemetry.h" // for Telemetry +#include "mozilla/TimeStamp.h" // for TimeDuration, TimeStamp +#include "mozilla/dom/CheckerboardReportService.h" // for CheckerboardEventStorage +// note: CheckerboardReportService.h actually lives in gfx/layers/apz/util/ +#include "mozilla/dom/Touch.h" // for Touch +#include "mozilla/gfx/gfxVars.h" // for gfxVars +#include "mozilla/gfx/BasePoint.h" // for BasePoint +#include "mozilla/gfx/BaseRect.h" // for BaseRect +#include "mozilla/gfx/Point.h" // for Point, RoundedToInt, etc +#include "mozilla/gfx/Rect.h" // for RoundedIn +#include "mozilla/gfx/ScaleFactor.h" // for ScaleFactor +#include "mozilla/layers/APZThreadUtils.h" // for AssertOnControllerThread, etc +#include "mozilla/layers/APZUtils.h" // for AsyncTransform +#include "mozilla/layers/CompositorController.h" // for CompositorController +#include "mozilla/layers/DirectionUtils.h" // for GetAxis{Start,End,Length,Scale} +#include "mozilla/layers/LayerTransactionParent.h" // for LayerTransactionParent +#include "mozilla/layers/MetricsSharingController.h" // for MetricsSharingController +#include "mozilla/layers/ScrollInputMethods.h" // for ScrollInputMethod +#include "mozilla/mozalloc.h" // for operator new, etc +#include "mozilla/Unused.h" // for unused +#include "mozilla/FloatingPoint.h" // for FuzzyEquals* +#include "nsAlgorithm.h" // for clamped +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsDebug.h" // for NS_WARNING +#include "nsLayoutUtils.h" +#include "nsMathUtils.h" // for NS_hypot +#include "nsPoint.h" // for nsIntPoint +#include "nsStyleConsts.h" +#include "nsTimingFunction.h" +#include "nsTArray.h" // for nsTArray, nsTArray_Impl, etc +#include "nsThreadUtils.h" // for NS_IsMainThread +#include "nsViewportInfo.h" // for kViewportMinScale, kViewportMaxScale +#include "prsystem.h" // for PR_GetPhysicalMemorySize +#include "SharedMemoryBasic.h" // for SharedMemoryBasic +#include "ScrollSnap.h" // for ScrollSnapUtils +#include "ScrollAnimationPhysics.h" // for ComputeAcceleratedWheelDelta +#include "SmoothMsdScrollAnimation.h" +#include "SmoothScrollAnimation.h" +#include "WheelScrollAnimation.h" +#if defined(MOZ_WIDGET_ANDROID) +# include "AndroidAPZ.h" +#endif // defined(MOZ_WIDGET_ANDROID) + +static mozilla::LazyLogModule sApzCtlLog("apz.controller"); +#define APZC_LOG(...) MOZ_LOG(sApzCtlLog, LogLevel::Debug, (__VA_ARGS__)) +#define APZC_LOGV(...) MOZ_LOG(sApzCtlLog, LogLevel::Verbose, (__VA_ARGS__)) + +#define APZC_LOG_FM_COMMON(fm, prefix, level, ...) \ + if (MOZ_LOG_TEST(sApzCtlLog, level)) { \ + std::stringstream ss; \ + ss << nsPrintfCString(prefix, __VA_ARGS__).get() << ":" << fm; \ + MOZ_LOG(sApzCtlLog, level, ("%s\n", ss.str().c_str())); \ + } +#define APZC_LOG_FM(fm, prefix, ...) \ + APZC_LOG_FM_COMMON(fm, prefix, LogLevel::Debug, __VA_ARGS__) +#define APZC_LOGV_FM(fm, prefix, ...) \ + APZC_LOG_FM_COMMON(fm, prefix, LogLevel::Verbose, __VA_ARGS__) + +namespace mozilla { +namespace layers { + +typedef mozilla::layers::AllowedTouchBehavior AllowedTouchBehavior; +typedef GeckoContentController::APZStateChange APZStateChange; +typedef GeckoContentController::TapType TapType; +typedef mozilla::gfx::Point Point; +typedef mozilla::gfx::Matrix4x4 Matrix4x4; + +// Choose between platform-specific implementations. +#ifdef MOZ_WIDGET_ANDROID +typedef WidgetOverscrollEffect OverscrollEffect; +typedef AndroidSpecificState PlatformSpecificState; +#else +typedef GenericOverscrollEffect OverscrollEffect; +typedef PlatformSpecificStateBase + PlatformSpecificState; // no extra state, just use the base class +#endif + +/** + * \page APZCPrefs APZ preferences + * + * The following prefs are used to control the behaviour of the APZC. + * The default values are provided in StaticPrefList.yaml. + * + * \li\b apz.allow_double_tap_zooming + * Pref that allows or disallows double tap to zoom + * + * \li\b apz.allow_immediate_handoff + * If set to true, scroll can be handed off from one APZC to another within + * a single input block. If set to false, a single input block can only + * scroll one APZC. + * + * \li\b apz.allow_zooming_out + * If set to true, APZ will allow zooming out past the initial scale on + * desktop. This is false by default to match Chrome's behaviour. + * + * \li\b apz.android.chrome_fling_physics.friction + * A tunable parameter for Chrome fling physics on Android that governs + * how quickly a fling animation slows down due to friction (and therefore + * also how far it reaches). Should be in the range [0-1]. + * + * \li\b apz.android.chrome_fling_physics.inflexion + * A tunable parameter for Chrome fling physics on Android that governs + * the shape of the fling curve. Should be in the range [0-1]. + * + * \li\b apz.android.chrome_fling_physics.stop_threshold + * A tunable parameter for Chrome fling physics on Android that governs + * how close the fling animation has to get to its target destination + * before it stops. + * Units: ParentLayer pixels + * + * \li\b apz.autoscroll.enabled + * If set to true, autoscrolling is driven by APZ rather than the content + * process main thread. + * + * \li\b apz.axis_lock.mode + * The preferred axis locking style. See AxisLockMode for possible values. + * + * \li\b apz.axis_lock.lock_angle + * Angle from axis within which we stay axis-locked.\n + * Units: radians + * + * \li\b apz.axis_lock.breakout_threshold + * Distance in inches the user must pan before axis lock can be broken.\n + * Units: (real-world, i.e. screen) inches + * + * \li\b apz.axis_lock.breakout_angle + * Angle at which axis lock can be broken.\n + * Units: radians + * + * \li\b apz.axis_lock.direct_pan_angle + * If the angle from an axis to the line drawn by a pan move is less than + * this value, we can assume that panning can be done in the allowed direction + * (horizontal or vertical).\n + * Currently used only for touch-action css property stuff and was addded to + * keep behaviour consistent with IE.\n + * Units: radians + * + * \li\b apz.content_response_timeout + * Amount of time before we timeout response from content. For example, if + * content is being unruly/slow and we don't get a response back within this + * time, we will just pretend that content did not preventDefault any touch + * events we dispatched to it.\n + * Units: milliseconds + * + * \li\b apz.danger_zone_x + * \li\b apz.danger_zone_y + * When drawing high-res tiles, we drop down to drawing low-res tiles + * when we know we can't keep up with the scrolling. The way we determine + * this is by checking if we are entering the "danger zone", which is the + * boundary of the painted content. For example, if the painted content + * goes from y=0...1000 and the visible portion is y=250...750 then + * we're far from checkerboarding. If we get to y=490...990 though then we're + * only 10 pixels away from showing checkerboarding so we are probably in + * a state where we can't keep up with scrolling. The danger zone prefs specify + * how wide this margin is; in the above example a y-axis danger zone of 10 + * pixels would make us drop to low-res at y=490...990.\n + * This value is in layer pixels. + * + * \li\b apz.disable_for_scroll_linked_effects + * Setting this pref to true will disable APZ scrolling on documents where + * scroll-linked effects are detected. A scroll linked effect is detected if + * positioning or transform properties are updated inside a scroll event + * dispatch; we assume that such an update is in response to the scroll event + * and is therefore a scroll-linked effect which will be laggy with APZ + * scrolling. + * + * \li\b apz.displayport_expiry_ms + * While a scrollable frame is scrolling async, we set a displayport on it + * to make sure it is layerized. However this takes up memory, so once the + * scrolling stops we want to remove the displayport. This pref controls how + * long after scrolling stops the displayport is removed. A value of 0 will + * disable the expiry behavior entirely. + * Units: milliseconds + * + * \li\b apz.drag.enabled + * Setting this pref to true will cause APZ to handle mouse-dragging of + * scrollbar thumbs. + * + * \li\b apz.drag.initial.enabled + * Setting this pref to true will cause APZ to try to handle mouse-dragging + * of scrollbar thumbs without an initial round-trip to content to start it + * if possible. Only has an effect if apz.drag.enabled is also true. + * + * \li\b apz.drag.touch.enabled + * Setting this pref to true will cause APZ to handle touch-dragging of + * scrollbar thumbs. Only has an effect if apz.drag.enabled is also true. + * + * \li\b apz.enlarge_displayport_when_clipped + * Pref that enables enlarging of the displayport along one axis when the + * generated displayport's size is beyond that of the scrollable rect on the + * opposite axis. + * + * \li\b apz.fling_accel_min_fling_velocity + * The minimum velocity of the second fling, and the minimum velocity of the + * previous fling animation at the point of interruption, for the new fling to + * be considered for fling acceleration. + * Units: screen pixels per milliseconds + * + * \li\b apz.fling_accel_min_pan_velocity + * The minimum velocity during the pan gesture that causes a fling for that + * fling to be considered for fling acceleration. + * Units: screen pixels per milliseconds + * + * \li\b apz.fling_accel_max_pause_interval_ms + * The maximum time that is allowed to elapse between the touch start event that + * interrupts the previous fling, and the touch move that initiates panning for + * the current fling, for that fling to be considered for fling acceleration. + * Units: milliseconds + * + * \li\b apz.fling_accel_base_mult + * \li\b apz.fling_accel_supplemental_mult + * When applying an acceleration on a fling, the new computed velocity is + * (new_fling_velocity * base_mult) + (old_velocity * supplemental_mult). + * The base_mult and supplemental_mult multiplier values are controlled by + * these prefs. Note that "old_velocity" here is the initial velocity of the + * previous fling _after_ acceleration was applied to it (if applicable). + * + * \li\b apz.fling_curve_function_x1 + * \li\b apz.fling_curve_function_y1 + * \li\b apz.fling_curve_function_x2 + * \li\b apz.fling_curve_function_y2 + * \li\b apz.fling_curve_threshold_inches_per_ms + * These five parameters define a Bezier curve function and threshold used to + * increase the actual velocity relative to the user's finger velocity. When the + * finger velocity is below the threshold (or if the threshold is not positive), + * the velocity is used as-is. If the finger velocity exceeds the threshold + * velocity, then the function defined by the curve is applied on the part of + * the velocity that exceeds the threshold. Note that the upper bound of the + * velocity is still specified by the \b apz.max_velocity_inches_per_ms pref, + * and the function will smoothly curve the velocity from the threshold to the + * max. In general the function parameters chosen should define an ease-out + * curve in order to increase the velocity in this range, or an ease-in curve to + * decrease the velocity. A straight-line curve is equivalent to disabling the + * curve entirely by setting the threshold to -1. The max velocity pref must + * also be set in order for the curving to take effect, as it defines the upper + * bound of the velocity curve.\n + * The points (x1, y1) and (x2, y2) used as the two intermediate control points + * in the cubic bezier curve; the first and last points are (0,0) and (1,1).\n + * Some example values for these prefs can be found at\n + * https://searchfox.org/mozilla-central/rev/f82d5c549f046cb64ce5602bfd894b7ae807c8f8/dom/animation/ComputedTimingFunction.cpp#27-33 + * + * \li\b apz.fling_friction + * Amount of friction applied during flings. This is used in the following + * formula: v(t1) = v(t0) * (1 - f)^(t1 - t0), where v(t1) is the velocity + * for a new sample, v(t0) is the velocity at the previous sample, f is the + * value of this pref, and (t1 - t0) is the amount of time, in milliseconds, + * that has elapsed between the two samples.\n + * NOTE: Not currently used in Android fling calculations. + * + * \li\b apz.fling_min_velocity_threshold + * Minimum velocity for a fling to actually kick off. If the user pans and lifts + * their finger such that the velocity is smaller than or equal to this amount, + * no fling is initiated.\n + * Units: screen pixels per millisecond + * + * \li\b apz.fling_stop_on_tap_threshold + * When flinging, if the velocity is above this number, then a tap on the + * screen will stop the fling without dispatching a tap to content. If the + * velocity is below this threshold a tap will also be dispatched. + * Note: when modifying this pref be sure to run the APZC gtests as some of + * them depend on the value of this pref.\n + * Units: screen pixels per millisecond + * + * \li\b apz.fling_stopped_threshold + * When flinging, if the velocity goes below this number, we just stop the + * animation completely. This is to prevent asymptotically approaching 0 + * velocity and rerendering unnecessarily.\n + * Units: screen pixels per millisecond.\n + * NOTE: Should not be set to anything + * other than 0.0 for Android except for tests to disable flings. + * + * \li\b apz.keyboard.enabled + * Determines whether scrolling with the keyboard will be allowed to be handled + * by APZ. + * + * \li\b apz.keyboard.passive-listeners + * When enabled, APZ will interpret the passive event listener flag to mean + * that the event listener won't change the focused element or selection of + * the page. With this, web content can use passive key listeners and not have + * keyboard APZ disabled. + * + * \li\b apz.max_tap_time + * Maximum time for a touch on the screen and corresponding lift of the finger + * to be considered a tap. This also applies to double taps, except that it is + * used both for the interval between the first touchdown and first touchup, + * and for the interval between the first touchup and the second touchdown.\n + * Units: milliseconds. + * + * \li\b apz.max_velocity_inches_per_ms + * Maximum velocity. Velocity will be capped at this value if a faster fling + * occurs. Negative values indicate unlimited velocity.\n + * Units: (real-world, i.e. screen) inches per millisecond + * + * \li\b apz.max_velocity_queue_size + * Maximum size of velocity queue. The queue contains last N velocity records. + * On touch end we calculate the average velocity in order to compensate + * touch/mouse drivers misbehaviour. + * + * \li\b apz.min_skate_speed + * Minimum amount of speed along an axis before we switch to "skate" multipliers + * rather than using the "stationary" multipliers.\n + * Units: CSS pixels per millisecond + * + * \li\b apz.one_touch_pinch.enabled + * Whether or not the "one-touch-pinch" gesture (for zooming with one finger) + * is enabled or not. + * + * \li\b apz.overscroll.enabled + * Pref that enables overscrolling. If this is disabled, excess scroll that + * cannot be handed off is discarded. + * + * \li\b apz.overscroll.min_pan_distance_ratio + * The minimum ratio of the pan distance along one axis to the pan distance + * along the other axis needed to initiate overscroll along the first axis + * during panning. + * + * \li\b apz.overscroll.stretch_factor + * How much overscrolling can stretch content along an axis. + * The maximum stretch along an axis is a factor of (1 + kStretchFactor). + * (So if kStretchFactor is 0, you can't stretch at all; if kStretchFactor + * is 1, you can stretch at most by a factor of 2). + * + * \li\b apz.overscroll.stop_distance_threshold + * \li\b apz.overscroll.stop_velocity_threshold + * Thresholds for stopping the overscroll animation. When both the distance + * and the velocity fall below their thresholds, we stop oscillating.\n + * Units: screen pixels (for distance) + * screen pixels per millisecond (for velocity) + * + * \li\b apz.paint_skipping.enabled + * When APZ is scrolling and sending repaint requests to the main thread, often + * the main thread doesn't actually need to do a repaint. This pref allows the + * main thread to skip doing those repaints in cases where it doesn't need to. + * + * \li\b apz.pinch_lock.mode + * The preferred pinch locking style. See PinchLockMode for possible values. + * + * \li\b apz.pinch_lock.scroll_lock_threshold + * Pinch locking is triggered if the user scrolls more than this distance + * and pinches less than apz.pinch_lock.span_lock_threshold.\n + * Units: (real-world, i.e. screen) inches + * + * \li\b apz.pinch_lock.span_breakout_threshold + * Distance in inches the user must pinch before lock can be broken.\n + * Units: (real-world, i.e. screen) inches measured between two touch points + * + * \li\b apz.pinch_lock.span_lock_threshold + * Pinch locking is triggered if the user pinches less than this distance + * and scrolls more than apz.pinch_lock.scroll_lock_threshold.\n + * Units: (real-world, i.e. screen) inches measured between two touch points + * + * \li\b apz.pinch_lock.buffer_max_age + * To ensure that pinch locking threshold calculations are not affected by + * variations in touch screen sensitivity, calculations draw from a buffer of + * recent events. This preference specifies the maximum time that events are + * held in this buffer. + * Units: milliseconds + * + * \li\b apz.popups.enabled + * Determines whether APZ is used for XUL popup widgets with remote content. + * Ideally, this should always be true, but it is currently not well tested, and + * has known issues, so needs to be prefable. + * + * \li\b apz.record_checkerboarding + * Whether or not to record detailed info on checkerboarding events. + * + * \li\b apz.second_tap_tolerance + * Constant describing the tolerance in distance we use, multiplied by the + * device DPI, within which a second tap is counted as part of a gesture + * continuing from the first tap. Making this larger allows the user more + * distance between the first and second taps in a "double tap" or "one touch + * pinch" gesture.\n + * Units: (real-world, i.e. screen) inches + * + * \li\b apz.test.logging_enabled + * Enable logging of APZ test data (see bug 961289). + * + * \li\b apz.touch_move_tolerance + * See the description for apz.touch_start_tolerance below. This is a similar + * threshold, except it is used to suppress touchmove events from being + * delivered to content for NON-scrollable frames (or more precisely, for APZCs + * where ArePointerEventsConsumable returns false).\n Units: (real-world, i.e. + * screen) inches + * + * \li\b apz.touch_start_tolerance + * Constant describing the tolerance in distance we use, multiplied by the + * device DPI, before we start panning the screen. This is to prevent us from + * accidentally processing taps as touch moves, and from very short/accidental + * touches moving the screen. touchmove events are also not delivered to content + * within this distance on scrollable frames.\n + * Units: (real-world, i.e. screen) inches + * + * \li\b apz.velocity_bias + * How much to adjust the displayport in the direction of scrolling. This value + * is multiplied by the velocity and added to the displayport offset. + * + * \li\b apz.velocity_relevance_time_ms + * When computing a fling velocity from the most recently stored velocity + * information, only velocities within the most X milliseconds are used. + * This pref controls the value of X.\n + * Units: ms + * + * \li\b apz.x_skate_size_multiplier + * \li\b apz.y_skate_size_multiplier + * The multiplier we apply to the displayport size if it is skating (current + * velocity is above \b apz.min_skate_speed). We prefer to increase the size of + * the Y axis because it is more natural in the case that a user is reading a + * page page that scrolls up/down. Note that one, both or neither of these may + * be used at any instant.\n In general we want \b + * apz.[xy]_skate_size_multiplier to be smaller than the corresponding + * stationary size multiplier because when panning fast we would like to paint + * less and get faster, more predictable paint times. When panning slowly we + * can afford to paint more even though it's slower. + * + * \li\b apz.x_stationary_size_multiplier + * \li\b apz.y_stationary_size_multiplier + * The multiplier we apply to the displayport size if it is not skating (see + * documentation for the skate size multipliers above). + * + * \li\b apz.x_skate_highmem_adjust + * \li\b apz.y_skate_highmem_adjust + * On high memory systems, we adjust the displayport during skating + * to be larger so we can reduce checkerboarding. + * + * \li\b apz.zoom_animation_duration_ms + * This controls how long the zoom-to-rect animation takes.\n + * Units: ms + * + * \li\b apz.scale_repaint_delay_ms + * How long to delay between repaint requests during a scale. + * A negative number prevents repaint requests during a scale.\n + * Units: ms + */ + +/** + * Computed time function used for sampling frames of a zoom to animation. + */ +StaticAutoPtr gZoomAnimationFunction; + +/** + * Computed time function used for curving up velocity when it gets high. + */ +StaticAutoPtr gVelocityCurveFunction; + +/** + * The estimated duration of a paint for the purposes of calculating a new + * displayport, in milliseconds. + */ +static const double kDefaultEstimatedPaintDurationMs = 50; + +/** + * Returns true if this is a high memory system and we can use + * extra memory for a larger displayport to reduce checkerboarding. + */ +static bool gIsHighMemSystem = false; +static bool IsHighMemSystem() { return gIsHighMemSystem; } + +// Counter used to give each APZC a unique id +static uint32_t sAsyncPanZoomControllerCount = 0; + +AsyncPanZoomAnimation* PlatformSpecificStateBase::CreateFlingAnimation( + AsyncPanZoomController& aApzc, const FlingHandoffState& aHandoffState, + float aPLPPI) { + return new GenericFlingAnimation(aApzc, aHandoffState, + aPLPPI); +} + +UniquePtr PlatformSpecificStateBase::CreateVelocityTracker( + Axis* aAxis) { + return MakeUnique(aAxis); +} + +SampleTime AsyncPanZoomController::GetFrameTime() const { + APZCTreeManager* treeManagerLocal = GetApzcTreeManager(); + return treeManagerLocal ? treeManagerLocal->GetFrameTime() + : SampleTime::FromNow(); +} + +class MOZ_STACK_CLASS StateChangeNotificationBlocker final { + public: + explicit StateChangeNotificationBlocker(AsyncPanZoomController* aApzc) + : mApzc(aApzc) { + RecursiveMutexAutoLock lock(mApzc->mRecursiveMutex); + mInitialState = mApzc->mState; + mApzc->mNotificationBlockers++; + } + + ~StateChangeNotificationBlocker() { + AsyncPanZoomController::PanZoomState newState; + { + RecursiveMutexAutoLock lock(mApzc->mRecursiveMutex); + mApzc->mNotificationBlockers--; + newState = mApzc->mState; + } + mApzc->DispatchStateChangeNotification(mInitialState, newState); + } + + private: + AsyncPanZoomController* mApzc; + AsyncPanZoomController::PanZoomState mInitialState; +}; + +/** + * An RAII class to temporarily apply async test attributes to the provided + * AsyncPanZoomController. + * + * This class should be used in the implementation of any AsyncPanZoomController + * method that queries the async scroll offset or async zoom (this includes + * the async layout viewport offset, since modifying the async scroll offset + * may result in the layout viewport moving as well). + */ +class MOZ_RAII AutoApplyAsyncTestAttributes final { + public: + explicit AutoApplyAsyncTestAttributes( + const AsyncPanZoomController*, + const RecursiveMutexAutoLock& aProofOfLock); + ~AutoApplyAsyncTestAttributes(); + + private: + AsyncPanZoomController* mApzc; + FrameMetrics mPrevFrameMetrics; + const RecursiveMutexAutoLock& mProofOfLock; +}; + +AutoApplyAsyncTestAttributes::AutoApplyAsyncTestAttributes( + const AsyncPanZoomController* aApzc, + const RecursiveMutexAutoLock& aProofOfLock) + // Having to use const_cast here seems less ugly than the alternatives + // of making several members of AsyncPanZoomController that + // ApplyAsyncTestAttributes() modifies |mutable|, or several methods that + // query the async transforms non-const. + : mApzc(const_cast(aApzc)), + mPrevFrameMetrics(aApzc->Metrics()), + mProofOfLock(aProofOfLock) { + mApzc->ApplyAsyncTestAttributes(aProofOfLock); +} + +AutoApplyAsyncTestAttributes::~AutoApplyAsyncTestAttributes() { + mApzc->UnapplyAsyncTestAttributes(mProofOfLock, mPrevFrameMetrics); +} + +class ZoomAnimation : public AsyncPanZoomAnimation { + public: + ZoomAnimation(AsyncPanZoomController& aApzc, const CSSPoint& aStartOffset, + const CSSToParentLayerScale2D& aStartZoom, + const CSSPoint& aEndOffset, + const CSSToParentLayerScale2D& aEndZoom) + : mApzc(aApzc), + mTotalDuration(TimeDuration::FromMilliseconds( + StaticPrefs::apz_zoom_animation_duration_ms())), + mStartOffset(aStartOffset), + mStartZoom(aStartZoom), + mEndOffset(aEndOffset), + mEndZoom(aEndZoom) {} + + virtual bool DoSample(FrameMetrics& aFrameMetrics, + const TimeDuration& aDelta) override { + mDuration += aDelta; + double animPosition = mDuration / mTotalDuration; + + if (animPosition >= 1.0) { + aFrameMetrics.SetZoom(mEndZoom); + mApzc.SetVisualScrollOffset(mEndOffset); + return false; + } + + // Sample the zoom at the current time point. The sampled zoom + // will affect the final computed resolution. + float sampledPosition = gZoomAnimationFunction->GetValue( + animPosition, ComputedTimingFunction::BeforeFlag::Unset); + + // We scale the scrollOffset linearly with sampledPosition, so the zoom + // needs to scale inversely to match. + if (mStartZoom == CSSToParentLayerScale2D(0, 0) || + mEndZoom == CSSToParentLayerScale2D(0, 0)) { + return false; + } + + aFrameMetrics.SetZoom(CSSToParentLayerScale2D( + 1 / (sampledPosition / mEndZoom.xScale + + (1 - sampledPosition) / mStartZoom.xScale), + 1 / (sampledPosition / mEndZoom.yScale + + (1 - sampledPosition) / mStartZoom.yScale))); + + mApzc.SetVisualScrollOffset(CSSPoint::FromUnknownPoint(gfx::Point( + mEndOffset.x * sampledPosition + mStartOffset.x * (1 - sampledPosition), + mEndOffset.y * sampledPosition + + mStartOffset.y * (1 - sampledPosition)))); + return true; + } + + virtual bool WantsRepaints() override { return false; } + + private: + AsyncPanZoomController& mApzc; + + TimeDuration mDuration; + const TimeDuration mTotalDuration; + + // Old metrics from before we started a zoom animation. This is only valid + // when we are in the "ANIMATED_ZOOM" state. This is used so that we can + // interpolate between the start and end frames. We only use the + // |mViewportScrollOffset| and |mResolution| fields on this. + CSSPoint mStartOffset; + CSSToParentLayerScale2D mStartZoom; + + // Target metrics for a zoom to animation. This is only valid when we are in + // the "ANIMATED_ZOOM" state. We only use the |mViewportScrollOffset| and + // |mResolution| fields on this. + CSSPoint mEndOffset; + CSSToParentLayerScale2D mEndZoom; +}; + +/*static*/ +void AsyncPanZoomController::InitializeGlobalState() { + static bool sInitialized = false; + if (sInitialized) return; + sInitialized = true; + + MOZ_ASSERT(NS_IsMainThread()); + + gZoomAnimationFunction = + new ComputedTimingFunction(nsTimingFunction(StyleTimingKeyword::Ease)); + ClearOnShutdown(&gZoomAnimationFunction); + gVelocityCurveFunction = new ComputedTimingFunction( + nsTimingFunction(StaticPrefs::apz_fling_curve_function_x1_AtStartup(), + StaticPrefs::apz_fling_curve_function_y1_AtStartup(), + StaticPrefs::apz_fling_curve_function_x2_AtStartup(), + StaticPrefs::apz_fling_curve_function_y2_AtStartup())); + ClearOnShutdown(&gVelocityCurveFunction); + + uint64_t sysmem = PR_GetPhysicalMemorySize(); + uint64_t threshold = 1LL << 32; // 4 GB in bytes + gIsHighMemSystem = sysmem >= threshold; + + PlatformSpecificState::InitializeGlobalState(); +} + +AsyncPanZoomController::AsyncPanZoomController( + LayersId aLayersId, APZCTreeManager* aTreeManager, + const RefPtr& aInputQueue, + GeckoContentController* aGeckoContentController, GestureBehavior aGestures) + : mLayersId(aLayersId), + mGeckoContentController(aGeckoContentController), + mRefPtrMonitor("RefPtrMonitor"), + // mTreeManager must be initialized before GetFrameTime() is called + mTreeManager(aTreeManager), + mRecursiveMutex("AsyncPanZoomController"), + mLastContentPaintMetrics(mLastContentPaintMetadata.GetMetrics()), + mX(this), + mY(this), + mPanDirRestricted(false), + mPinchLocked(false), + mPinchEventBuffer(TimeDuration::FromMilliseconds( + StaticPrefs::apz_pinch_lock_buffer_max_age_AtStartup())), + mZoomConstraints(false, false, + mScrollMetadata.GetMetrics().GetDevPixelsPerCSSPixel() * + kViewportMinScale / ParentLayerToScreenScale(1), + mScrollMetadata.GetMetrics().GetDevPixelsPerCSSPixel() * + kViewportMaxScale / ParentLayerToScreenScale(1)), + mLastSampleTime(GetFrameTime()), + mLastCheckerboardReport(GetFrameTime()), + mOverscrollEffect(MakeUnique(*this)), + mState(NOTHING), + mNotificationBlockers(0), + mInputQueue(aInputQueue), + mPinchPaintTimerSet(false), + mAPZCId(sAsyncPanZoomControllerCount++), + mSharedLock(nullptr), + mTestAttributeAppliers(0), + mAsyncTransformAppliedToContent(false), + mTestHasAsyncKeyScrolled(false), + mCheckerboardEventLock("APZCBELock") { + if (aGestures == USE_GESTURE_DETECTOR) { + mGestureEventListener = new GestureEventListener(this); + } + // Put one default-constructed sampled state in the queue. + RecursiveMutexAutoLock lock(mRecursiveMutex); + mSampledState.emplace_back(); +} + +AsyncPanZoomController::~AsyncPanZoomController() { MOZ_ASSERT(IsDestroyed()); } + +PlatformSpecificStateBase* AsyncPanZoomController::GetPlatformSpecificState() { + if (!mPlatformSpecificState) { + mPlatformSpecificState = MakeUnique(); + } + return mPlatformSpecificState.get(); +} + +already_AddRefed +AsyncPanZoomController::GetGeckoContentController() const { + MonitorAutoLock lock(mRefPtrMonitor); + RefPtr controller = mGeckoContentController; + return controller.forget(); +} + +already_AddRefed +AsyncPanZoomController::GetGestureEventListener() const { + MonitorAutoLock lock(mRefPtrMonitor); + RefPtr listener = mGestureEventListener; + return listener.forget(); +} + +const RefPtr& AsyncPanZoomController::GetInputQueue() const { + return mInputQueue; +} + +void AsyncPanZoomController::Destroy() { + AssertOnUpdaterThread(); + + CancelAnimation(CancelAnimationFlags::ScrollSnap); + + { // scope the lock + MonitorAutoLock lock(mRefPtrMonitor); + mGeckoContentController = nullptr; + mGestureEventListener = nullptr; + } + mParent = nullptr; + mTreeManager = nullptr; + + // Only send the release message if the SharedFrameMetrics has been created. + if (mMetricsSharingController && mSharedFrameMetricsBuffer) { + Unused << mMetricsSharingController->StopSharingMetrics(GetScrollId(), + mAPZCId); + } + + { // scope the lock + RecursiveMutexAutoLock lock(mRecursiveMutex); + mSharedFrameMetricsBuffer = nullptr; + delete mSharedLock; + mSharedLock = nullptr; + } +} + +bool AsyncPanZoomController::IsDestroyed() const { + return mTreeManager == nullptr; +} + +float AsyncPanZoomController::GetDPI() const { + if (APZCTreeManager* localPtr = mTreeManager) { + return localPtr->GetDPI(); + } + // If this APZC has been destroyed then this value is not going to be + // used for anything that the user will end up seeing, so we can just + // return 0. + return 0.0; +} + +ScreenCoord AsyncPanZoomController::GetTouchStartTolerance() const { + return (StaticPrefs::apz_touch_start_tolerance() * GetDPI()); +} + +ScreenCoord AsyncPanZoomController::GetTouchMoveTolerance() const { + return (StaticPrefs::apz_touch_move_tolerance() * GetDPI()); +} + +ScreenCoord AsyncPanZoomController::GetSecondTapTolerance() const { + return (StaticPrefs::apz_second_tap_tolerance() * GetDPI()); +} + +/* static */ AsyncPanZoomController::AxisLockMode +AsyncPanZoomController::GetAxisLockMode() { + return static_cast(StaticPrefs::apz_axis_lock_mode()); +} + +/* static */ AsyncPanZoomController::PinchLockMode +AsyncPanZoomController::GetPinchLockMode() { + return static_cast(StaticPrefs::apz_pinch_lock_mode()); +} + +bool AsyncPanZoomController::ArePointerEventsConsumable( + TouchBlockState* aBlock, const MultiTouchInput& aInput) { + uint32_t touchPoints = aInput.mTouches.Length(); + if (touchPoints == 0) { + // Cant' do anything with zero touch points + return false; + } + + // This logic is simplified, erring on the side of returning true if we're + // not sure. It's safer to pretend that we can consume the event and then + // not be able to than vice-versa. But at the same time, we should try hard + // to return an accurate result, because returning true can trigger a + // pointercancel event to web content, which can break certain features + // that are using touch-action and handling the pointermove events. + // + // Note that in particular this function can return true if APZ is waiting on + // the main thread for touch-action information. In this scenario, the + // APZEventState::MainThreadAgreesEventsAreConsumableByAPZ() function tries + // to use the main-thread touch-action information to filter out false + // positives. + // + // We could probably enhance this logic to determine things like "we're + // not pannable, so we can only zoom in, and the zoom is already maxed + // out, so we're not zoomable either" but no need for that at this point. + + bool pannableX = aBlock->TouchActionAllowsPanningX() && + aBlock->GetOverscrollHandoffChain()->CanScrollInDirection( + this, ScrollDirection::eHorizontal); + bool pannableY = + (aBlock->TouchActionAllowsPanningY() && + (aBlock->GetOverscrollHandoffChain()->CanScrollInDirection( + this, ScrollDirection::eVertical) || + // In the case of the root APZC with any dynamic toolbar, it + // shoule be pannable if there is room moving the dynamic + // toolbar. + (IsRootContent() && CanScrollDownwardsWithDynamicToolbar()))); + + bool pannable; + + Maybe panDirection = + aBlock->GetBestGuessPanDirection(aInput); + if (panDirection == Some(ScrollDirection::eVertical)) { + pannable = pannableY; + } else if (panDirection == Some(ScrollDirection::eHorizontal)) { + pannable = pannableX; + } else { + // If we don't have a guessed pan direction, err on the side of returning + // true. + pannable = pannableX || pannableY; + } + + if (touchPoints == 1) { + return pannable; + } + + bool zoomable = mZoomConstraints.mAllowZoom; + zoomable &= (aBlock->TouchActionAllowsPinchZoom()); + + return pannable || zoomable; +} + +nsEventStatus AsyncPanZoomController::HandleDragEvent( + const MouseInput& aEvent, const AsyncDragMetrics& aDragMetrics, + CSSCoord aInitialThumbPos) { + // RDM is a special case where touch events will be synthesized in response + // to mouse events, and APZ will receive both even though RDM prevent-defaults + // the mouse events. This is because mouse events don't opt into APZ waiting + // to check if the event has been prevent-defaulted and are still processed + // as a result. To handle this, have APZ ignore mouse events when RDM and + // touch simulation are active. + bool isRDMTouchSimulationActive = false; + { + RecursiveMutexAutoLock lock(mRecursiveMutex); + isRDMTouchSimulationActive = + mScrollMetadata.GetIsRDMTouchSimulationActive(); + } + + if (!StaticPrefs::apz_drag_enabled() || isRDMTouchSimulationActive) { + return nsEventStatus_eIgnore; + } + + if (!GetApzcTreeManager()) { + return nsEventStatus_eConsumeNoDefault; + } + + { + RecursiveMutexAutoLock lock(mRecursiveMutex); + if (aEvent.mType == MouseInput::MouseType::MOUSE_UP && + mState == SCROLLBAR_DRAG) { + APZC_LOG("%p ending drag\n", this); + SetState(NOTHING); + ScrollSnap(); + return nsEventStatus_eConsumeNoDefault; + } + } + + HitTestingTreeNodeAutoLock node; + GetApzcTreeManager()->FindScrollThumbNode(aDragMetrics, node); + if (!node) { + APZC_LOG("%p unable to find scrollthumb node with viewid %" PRIu64 "\n", + this, aDragMetrics.mViewId); + return nsEventStatus_eConsumeNoDefault; + } + + if (aEvent.mType == MouseInput::MouseType::MOUSE_DOWN) { + APZC_LOG("%p starting scrollbar drag\n", this); + SetState(SCROLLBAR_DRAG); + + mozilla::Telemetry::Accumulate( + mozilla::Telemetry::SCROLL_INPUT_METHODS, + (uint32_t)ScrollInputMethod::ApzScrollbarDrag); + } + + if (aEvent.mType != MouseInput::MouseType::MOUSE_MOVE) { + APZC_LOG("%p discarding event of type %d\n", this, aEvent.mType); + return nsEventStatus_eConsumeNoDefault; + } + + const ScrollbarData& scrollbarData = node->GetScrollbarData(); + MOZ_ASSERT(scrollbarData.mScrollbarLayerType == + layers::ScrollbarLayerType::Thumb); + MOZ_ASSERT(scrollbarData.mDirection.isSome()); + ScrollDirection direction = *scrollbarData.mDirection; + + bool isMouseAwayFromThumb = false; + if (int snapMultiplier = StaticPrefs::slider_snapMultiplier_AtStartup()) { + // It's fine to ignore the async component of the thumb's transform, + // because any async transform of the thumb will be in the direction of + // scrolling, but here we're interested in the other direction. + ParentLayerRect thumbRect = + (node->GetTransform() * AsyncTransformMatrix()) + .TransformBounds(LayerRect(node->GetVisibleRegion().GetBounds())); + ScrollDirection otherDirection = GetPerpendicularDirection(direction); + ParentLayerCoord distance = + GetAxisStart(otherDirection, thumbRect.DistanceTo(aEvent.mLocalOrigin)); + ParentLayerCoord thumbWidth = GetAxisLength(otherDirection, thumbRect); + // Avoid triggering this condition spuriously when the thumb is + // offscreen and its visible region is therefore empty. + if (thumbWidth > 0 && thumbWidth * snapMultiplier < distance) { + isMouseAwayFromThumb = true; + APZC_LOG("%p determined mouse is away from thumb, will snap\n", this); + } + } + + RecursiveMutexAutoLock lock(mRecursiveMutex); + CSSCoord thumbPosition; + if (isMouseAwayFromThumb) { + thumbPosition = aInitialThumbPos; + } else { + thumbPosition = ConvertScrollbarPoint(aEvent.mLocalOrigin, scrollbarData) - + aDragMetrics.mScrollbarDragOffset; + } + + CSSCoord maxThumbPos = scrollbarData.mScrollTrackLength; + maxThumbPos -= scrollbarData.mThumbLength; + + float scrollPercent = + maxThumbPos.value == 0.0f ? 0.0f : (float)(thumbPosition / maxThumbPos); + APZC_LOG("%p scrollbar dragged to %f percent\n", this, scrollPercent); + + CSSCoord minScrollPosition = + GetAxisStart(direction, Metrics().GetScrollableRect().TopLeft()); + CSSCoord maxScrollPosition = + GetAxisStart(direction, Metrics().GetScrollableRect().BottomRight()) - + GetAxisLength(direction, Metrics().CalculateCompositedSizeInCssPixels()); + CSSCoord scrollPosition = + minScrollPosition + + (scrollPercent * (maxScrollPosition - minScrollPosition)); + + scrollPosition = std::max(scrollPosition, minScrollPosition); + scrollPosition = std::min(scrollPosition, maxScrollPosition); + + CSSPoint scrollOffset = Metrics().GetVisualScrollOffset(); + if (direction == ScrollDirection::eHorizontal) { + scrollOffset.x = scrollPosition; + } else { + scrollOffset.y = scrollPosition; + } + APZC_LOG("%p set scroll offset to %s from scrollbar drag\n", this, + ToString(scrollOffset).c_str()); + SetVisualScrollOffset(scrollOffset); + ScheduleCompositeAndMaybeRepaint(); + UpdateSharedCompositorFrameMetrics(); + + return nsEventStatus_eConsumeNoDefault; +} + +nsEventStatus AsyncPanZoomController::HandleInputEvent( + const InputData& aEvent, + const ScreenToParentLayerMatrix4x4& aTransformToApzc) { + APZThreadUtils::AssertOnControllerThread(); + + nsEventStatus rv = nsEventStatus_eIgnore; + + switch (aEvent.mInputType) { + case MULTITOUCH_INPUT: { + MultiTouchInput multiTouchInput = aEvent.AsMultiTouchInput(); + RefPtr listener = GetGestureEventListener(); + if (listener) { + // We only care about screen coordinates in the gesture listener, + // so we don't bother transforming the event to parent layer coordinates + rv = listener->HandleInputEvent(multiTouchInput); + if (rv == nsEventStatus_eConsumeNoDefault) { + return rv; + } + } + + if (!multiTouchInput.TransformToLocal(aTransformToApzc)) { + return rv; + } + + switch (multiTouchInput.mType) { + case MultiTouchInput::MULTITOUCH_START: + rv = OnTouchStart(multiTouchInput); + break; + case MultiTouchInput::MULTITOUCH_MOVE: + rv = OnTouchMove(multiTouchInput); + break; + case MultiTouchInput::MULTITOUCH_END: + rv = OnTouchEnd(multiTouchInput); + break; + case MultiTouchInput::MULTITOUCH_CANCEL: + rv = OnTouchCancel(multiTouchInput); + break; + } + break; + } + case PANGESTURE_INPUT: { + PanGestureInput panGestureInput = aEvent.AsPanGestureInput(); + if (!panGestureInput.TransformToLocal(aTransformToApzc)) { + return rv; + } + + switch (panGestureInput.mType) { + case PanGestureInput::PANGESTURE_MAYSTART: + rv = OnPanMayBegin(panGestureInput); + break; + case PanGestureInput::PANGESTURE_CANCELLED: + rv = OnPanCancelled(panGestureInput); + break; + case PanGestureInput::PANGESTURE_START: + rv = OnPanBegin(panGestureInput); + break; + case PanGestureInput::PANGESTURE_PAN: + rv = OnPan(panGestureInput, true); + break; + case PanGestureInput::PANGESTURE_END: + rv = OnPanEnd(panGestureInput); + break; + case PanGestureInput::PANGESTURE_MOMENTUMSTART: + rv = OnPanMomentumStart(panGestureInput); + break; + case PanGestureInput::PANGESTURE_MOMENTUMPAN: + rv = OnPan(panGestureInput, false); + break; + case PanGestureInput::PANGESTURE_MOMENTUMEND: + rv = OnPanMomentumEnd(panGestureInput); + break; + } + break; + } + case MOUSE_INPUT: { + MouseInput mouseInput = aEvent.AsMouseInput(); + if (!mouseInput.TransformToLocal(aTransformToApzc)) { + return rv; + } + break; + } + case SCROLLWHEEL_INPUT: { + ScrollWheelInput scrollInput = aEvent.AsScrollWheelInput(); + if (!scrollInput.TransformToLocal(aTransformToApzc)) { + return rv; + } + + rv = OnScrollWheel(scrollInput); + break; + } + case PINCHGESTURE_INPUT: { + // The APZCTreeManager should take care of ensuring that only root-content + // APZCs get pinch inputs. + MOZ_ASSERT(IsRootContent()); + PinchGestureInput pinchInput = aEvent.AsPinchGestureInput(); + if (!pinchInput.TransformToLocal(aTransformToApzc)) { + return rv; + } + + rv = HandleGestureEvent(pinchInput); + break; + } + case TAPGESTURE_INPUT: { + TapGestureInput tapInput = aEvent.AsTapGestureInput(); + if (!tapInput.TransformToLocal(aTransformToApzc)) { + return rv; + } + + rv = HandleGestureEvent(tapInput); + break; + } + case KEYBOARD_INPUT: { + const KeyboardInput& keyInput = aEvent.AsKeyboardInput(); + rv = OnKeyboard(keyInput); + break; + } + } + + return rv; +} + +nsEventStatus AsyncPanZoomController::HandleGestureEvent( + const InputData& aEvent) { + APZThreadUtils::AssertOnControllerThread(); + + nsEventStatus rv = nsEventStatus_eIgnore; + + switch (aEvent.mInputType) { + case PINCHGESTURE_INPUT: { + // This may be invoked via a one-touch-pinch gesture from + // GestureEventListener. In that case we want redirect it to the enclosing + // root-content APZC. + if (!IsRootContent()) { + if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) { + if (RefPtr root = + treeManagerLocal->FindZoomableApzc(this)) { + rv = root->HandleGestureEvent(aEvent); + } + } + break; + } + PinchGestureInput pinchGestureInput = aEvent.AsPinchGestureInput(); + pinchGestureInput.TransformToLocal(GetTransformToThis()); + switch (pinchGestureInput.mType) { + case PinchGestureInput::PINCHGESTURE_START: + rv = OnScaleBegin(pinchGestureInput); + break; + case PinchGestureInput::PINCHGESTURE_SCALE: + rv = OnScale(pinchGestureInput); + break; + case PinchGestureInput::PINCHGESTURE_FINGERLIFTED: + case PinchGestureInput::PINCHGESTURE_END: + rv = OnScaleEnd(pinchGestureInput); + break; + } + break; + } + case TAPGESTURE_INPUT: { + TapGestureInput tapGestureInput = aEvent.AsTapGestureInput(); + tapGestureInput.TransformToLocal(GetTransformToThis()); + switch (tapGestureInput.mType) { + case TapGestureInput::TAPGESTURE_LONG: + rv = OnLongPress(tapGestureInput); + break; + case TapGestureInput::TAPGESTURE_LONG_UP: + rv = OnLongPressUp(tapGestureInput); + break; + case TapGestureInput::TAPGESTURE_UP: + rv = OnSingleTapUp(tapGestureInput); + break; + case TapGestureInput::TAPGESTURE_CONFIRMED: + rv = OnSingleTapConfirmed(tapGestureInput); + break; + case TapGestureInput::TAPGESTURE_DOUBLE: + rv = OnDoubleTap(tapGestureInput); + break; + case TapGestureInput::TAPGESTURE_SECOND: + rv = OnSecondTap(tapGestureInput); + break; + case TapGestureInput::TAPGESTURE_CANCEL: + rv = OnCancelTap(tapGestureInput); + break; + } + break; + } + default: + MOZ_ASSERT_UNREACHABLE("Unhandled input event"); + break; + } + + return rv; +} + +void AsyncPanZoomController::StartAutoscroll(const ScreenPoint& aPoint) { + // Cancel any existing animation. + CancelAnimation(); + + Telemetry::Accumulate(Telemetry::SCROLL_INPUT_METHODS, + (uint32_t)ScrollInputMethod::ApzAutoscrolling); + + SetState(AUTOSCROLL); + StartAnimation(new AutoscrollAnimation(*this, aPoint)); +} + +void AsyncPanZoomController::StopAutoscroll() { + if (mState == AUTOSCROLL) { + CancelAnimation(TriggeredExternally); + } +} + +nsEventStatus AsyncPanZoomController::OnTouchStart( + const MultiTouchInput& aEvent) { + APZC_LOG("%p got a touch-start in state %d\n", this, mState); + mPanDirRestricted = false; + + switch (mState) { + case FLING: + case ANIMATING_ZOOM: + case SMOOTH_SCROLL: + case SMOOTHMSD_SCROLL: + case OVERSCROLL_ANIMATION: + case WHEEL_SCROLL: + case KEYBOARD_SCROLL: + case PAN_MOMENTUM: + case AUTOSCROLL: + MOZ_ASSERT(GetCurrentTouchBlock()); + GetCurrentTouchBlock()->GetOverscrollHandoffChain()->CancelAnimations( + ExcludeOverscroll); + [[fallthrough]]; + case SCROLLBAR_DRAG: + case NOTHING: { + ParentLayerPoint point = GetFirstTouchPoint(aEvent); + mStartTouch = GetFirstExternalTouchPoint(aEvent); + StartTouch(point, aEvent.mTimeStamp); + if (RefPtr controller = + GetGeckoContentController()) { + MOZ_ASSERT(GetCurrentTouchBlock()); + controller->NotifyAPZStateChange( + GetGuid(), APZStateChange::eStartTouch, + GetCurrentTouchBlock()->GetOverscrollHandoffChain()->CanBePanned( + this)); + } + mTouchStartTime = aEvent.mTimeStamp; + SetState(TOUCHING); + break; + } + case TOUCHING: + case PANNING: + case PANNING_LOCKED_X: + case PANNING_LOCKED_Y: + case PINCHING: + NS_WARNING("Received impossible touch in OnTouchStart"); + break; + } + + return nsEventStatus_eConsumeNoDefault; +} + +nsEventStatus AsyncPanZoomController::OnTouchMove( + const MultiTouchInput& aEvent) { + APZC_LOG("%p got a touch-move in state %d\n", this, mState); + switch (mState) { + case FLING: + case SMOOTHMSD_SCROLL: + case NOTHING: + case ANIMATING_ZOOM: + // May happen if the user double-taps and drags without lifting after the + // second tap. Ignore the move if this happens. + return nsEventStatus_eIgnore; + + case TOUCHING: { + ScreenCoord panThreshold = GetTouchStartTolerance(); + ExternalPoint extPoint = GetFirstExternalTouchPoint(aEvent); + // We intentionally skip the UpdateWithTouchAtDevicePoint call when the + // panThreshold is zero. This ensures more deterministic behaviour during + // testing. If we call that, Axis::mPos gets updated to the point of this + // touchmove event, but we "consume" the move to overcome the + // panThreshold, so it's hard to pan a specific amount reliably from a + // mochitest. + if (panThreshold > 0.0f) { + UpdateWithTouchAtDevicePoint(aEvent); + if (PanVector(extPoint).Length() < panThreshold) { + return nsEventStatus_eIgnore; + } + } + + MOZ_ASSERT(GetCurrentTouchBlock()); + if (StaticPrefs::layout_css_touch_action_enabled() && + GetCurrentTouchBlock()->TouchActionAllowsPanningXY()) { + // User tries to trigger a touch behavior. If allowed touch behavior is + // vertical pan + // + horizontal pan (touch-action value is equal to AUTO) we can return + // ConsumeNoDefault status immediately to trigger cancel event further. + // It should happen independent of the parent type (whether it is + // scrolling or not). + StartPanning(extPoint, aEvent.mTimeStamp); + return nsEventStatus_eConsumeNoDefault; + } + + return StartPanning(extPoint, aEvent.mTimeStamp); + } + + case PANNING: + case PANNING_LOCKED_X: + case PANNING_LOCKED_Y: + case PAN_MOMENTUM: + TrackTouch(aEvent); + return nsEventStatus_eConsumeNoDefault; + + case PINCHING: + // The scale gesture listener should have handled this. + NS_WARNING( + "Gesture listener should have handled pinching in OnTouchMove."); + return nsEventStatus_eIgnore; + + case SMOOTH_SCROLL: + case WHEEL_SCROLL: + case KEYBOARD_SCROLL: + case OVERSCROLL_ANIMATION: + case AUTOSCROLL: + case SCROLLBAR_DRAG: + // Should not receive a touch-move in the OVERSCROLL_ANIMATION state + // as touch blocks that begin in an overscrolled state cancel the + // animation. The same is true for wheel scroll animations. + NS_WARNING("Received impossible touch in OnTouchMove"); + break; + } + + return nsEventStatus_eConsumeNoDefault; +} + +nsEventStatus AsyncPanZoomController::OnTouchEnd( + const MultiTouchInput& aEvent) { + APZC_LOG("%p got a touch-end in state %d\n", this, mState); + OnTouchEndOrCancel(); + + // In case no touch behavior triggered previously we can avoid sending + // scroll events or requesting content repaint. This condition is added + // to make tests consistent - in case touch-action is NONE (and therefore + // no pans/zooms can be performed) we expected neither scroll or repaint + // events. + if (mState != NOTHING) { + RecursiveMutexAutoLock lock(mRecursiveMutex); + } + + switch (mState) { + case FLING: + // Should never happen. + NS_WARNING("Received impossible touch end in OnTouchEnd."); + [[fallthrough]]; + case ANIMATING_ZOOM: + case SMOOTHMSD_SCROLL: + case NOTHING: + // May happen if the user double-taps and drags without lifting after the + // second tap. Ignore if this happens. + return nsEventStatus_eIgnore; + + case TOUCHING: + // We may have some velocity stored on the axis from move events + // that were not big enough to trigger scrolling. Clear that out. + SetVelocityVector(ParentLayerPoint(0, 0)); + MOZ_ASSERT(GetCurrentTouchBlock()); + APZC_LOG("%p still has %u touch points active\n", this, + GetCurrentTouchBlock()->GetActiveTouchCount()); + // In cases where the user is panning, then taps the second finger without + // entering a pinch, we will arrive here when the second finger is lifted. + // However the first finger is still down so we want to remain in state + // TOUCHING. + if (GetCurrentTouchBlock()->GetActiveTouchCount() == 0) { + // It's possible we may be overscrolled if the user tapped during a + // previous overscroll pan. Make sure to snap back in this situation. + // An ancestor APZC could be overscrolled instead of this APZC, so + // walk the handoff chain as well. + GetCurrentTouchBlock() + ->GetOverscrollHandoffChain() + ->SnapBackOverscrolledApzc(this); + mFlingAccelerator.Reset(); + // SnapBackOverscrolledApzc() will put any APZC it causes to snap back + // into the OVERSCROLL_ANIMATION state. If that's not us, since we're + // done TOUCHING enter the NOTHING state. + if (mState != OVERSCROLL_ANIMATION) { + SetState(NOTHING); + } + } + return nsEventStatus_eIgnore; + + case PANNING: + case PANNING_LOCKED_X: + case PANNING_LOCKED_Y: + case PAN_MOMENTUM: { + MOZ_ASSERT(GetCurrentTouchBlock()); + EndTouch(aEvent.mTimeStamp); + return HandleEndOfPan(); + } + case PINCHING: + SetState(NOTHING); + // Scale gesture listener should have handled this. + NS_WARNING( + "Gesture listener should have handled pinching in OnTouchEnd."); + return nsEventStatus_eIgnore; + + case SMOOTH_SCROLL: + case WHEEL_SCROLL: + case KEYBOARD_SCROLL: + case OVERSCROLL_ANIMATION: + case AUTOSCROLL: + case SCROLLBAR_DRAG: + // Should not receive a touch-end in the OVERSCROLL_ANIMATION state + // as touch blocks that begin in an overscrolled state cancel the + // animation. The same is true for WHEEL_SCROLL. + NS_WARNING("Received impossible touch in OnTouchEnd"); + break; + } + + return nsEventStatus_eConsumeNoDefault; +} + +nsEventStatus AsyncPanZoomController::OnTouchCancel( + const MultiTouchInput& aEvent) { + APZC_LOG("%p got a touch-cancel in state %d\n", this, mState); + OnTouchEndOrCancel(); + CancelAnimationAndGestureState(); + return nsEventStatus_eConsumeNoDefault; +} + +nsEventStatus AsyncPanZoomController::OnScaleBegin( + const PinchGestureInput& aEvent) { + APZC_LOG("%p got a scale-begin in state %d\n", this, mState); + + mPinchLocked = false; + mPinchPaintTimerSet = false; + // Note that there may not be a touch block at this point, if we received the + // PinchGestureEvent directly from widget code without any touch events. + if (HasReadyTouchBlock() && + !GetCurrentTouchBlock()->TouchActionAllowsPinchZoom()) { + return nsEventStatus_eIgnore; + } + + // If zooming is not allowed, this is a two-finger pan. + // Start tracking panning distance and velocity. + if (!mZoomConstraints.mAllowZoom) { + StartTouch(aEvent.mLocalFocusPoint, aEvent.mTimeStamp); + } + + // For platforms that don't support APZ zooming, dispatch a message to the + // content controller, it may want to do something else with this gesture. + // FIXME: bug 1525793 -- this may need to handle zooming or not on a + // per-document basis. + if (!StaticPrefs::apz_allow_zooming()) { + if (RefPtr controller = + GetGeckoContentController()) { + APZC_LOG("%p notifying controller of pinch gesture start\n", this); + controller->NotifyPinchGesture( + aEvent.mType, GetGuid(), + ViewAs( + aEvent.mFocusPoint, + PixelCastJustification:: + LayoutDeviceIsScreenForUntransformedEvent), + 0, aEvent.modifiers); + } + } + + SetState(PINCHING); + Telemetry::Accumulate(Telemetry::APZ_ZOOM_PINCHSOURCE, (int)aEvent.mSource); + SetVelocityVector(ParentLayerPoint(0, 0)); + RecursiveMutexAutoLock lock(mRecursiveMutex); + mLastZoomFocus = + aEvent.mLocalFocusPoint - Metrics().GetCompositionBounds().TopLeft(); + + mPinchEventBuffer.push(aEvent); + + return nsEventStatus_eConsumeNoDefault; +} + +nsEventStatus AsyncPanZoomController::OnScale(const PinchGestureInput& aEvent) { + APZC_LOG("%p got a scale in state %d\n", this, mState); + + if (HasReadyTouchBlock() && + !GetCurrentTouchBlock()->TouchActionAllowsPinchZoom()) { + return nsEventStatus_eIgnore; + } + + if (mState != PINCHING) { + return nsEventStatus_eConsumeNoDefault; + } + + mPinchEventBuffer.push(aEvent); + HandlePinchLocking(aEvent); + bool allowZoom = mZoomConstraints.mAllowZoom && !mPinchLocked; + + // If zooming is not allowed, this is a two-finger pan. + // Tracking panning distance and velocity. + // UpdateWithTouchAtDevicePoint() acquires the tree lock, so + // it cannot be called while the mRecursiveMutex lock is held. + if (!allowZoom) { + mX.UpdateWithTouchAtDevicePoint(aEvent.mLocalFocusPoint.x, + aEvent.mTimeStamp); + mY.UpdateWithTouchAtDevicePoint(aEvent.mLocalFocusPoint.y, + aEvent.mTimeStamp); + } + + // FIXME: bug 1525793 -- this may need to handle zooming or not on a + // per-document basis. + if (!StaticPrefs::apz_allow_zooming()) { + if (RefPtr controller = + GetGeckoContentController()) { + APZC_LOG("%p notifying controller of pinch gesture\n", this); + controller->NotifyPinchGesture( + aEvent.mType, GetGuid(), + ViewAs( + aEvent.mFocusPoint, + PixelCastJustification:: + LayoutDeviceIsScreenForUntransformedEvent), + ViewAs( + aEvent.mCurrentSpan - aEvent.mPreviousSpan, + PixelCastJustification:: + LayoutDeviceIsScreenForUntransformedEvent), + aEvent.modifiers); + } + } + + { + RecursiveMutexAutoLock lock(mRecursiveMutex); + // Only the root APZC is zoomable, and the root APZC is not allowed to have + // different x and y scales. If it did, the calculations in this function + // would have to be adjusted (as e.g. it would no longer be valid to take + // the minimum or maximum of the ratios of the widths and heights of the + // page rect and the composition bounds). + MOZ_ASSERT(Metrics().IsRootContent()); + MOZ_ASSERT(Metrics().GetZoom().AreScalesSame()); + + CSSToParentLayerScale userZoom = Metrics().GetZoom().ToScaleFactor(); + ParentLayerPoint focusPoint = + aEvent.mLocalFocusPoint - Metrics().GetCompositionBounds().TopLeft(); + CSSPoint cssFocusPoint; + if (Metrics().GetZoom() != CSSToParentLayerScale2D(0, 0)) { + cssFocusPoint = focusPoint / Metrics().GetZoom(); + } + + ParentLayerPoint focusChange = mLastZoomFocus - focusPoint; + mLastZoomFocus = focusPoint; + // If displacing by the change in focus point will take us off page bounds, + // then reduce the displacement such that it doesn't. + focusChange.x -= mX.DisplacementWillOverscrollAmount(focusChange.x); + focusChange.y -= mY.DisplacementWillOverscrollAmount(focusChange.y); + if (userZoom != CSSToParentLayerScale(0)) { + ScrollBy(focusChange / userZoom); + } + + // If the span is zero or close to it, we don't want to process this zoom + // change because we're going to get wonky numbers for the spanRatio. So + // let's bail out here. Note that we do this after the focus-change-scroll + // above, so that if we have a pinch with zero span but changing focus, + // such as generated by some Synaptics touchpads on Windows, we still + // scroll properly. + float prevSpan = aEvent.mPreviousSpan; + if (fabsf(prevSpan) <= EPSILON || fabsf(aEvent.mCurrentSpan) <= EPSILON) { + // We might have done a nonzero ScrollBy above, so update metrics and + // repaint/recomposite + ScheduleCompositeAndMaybeRepaint(); + UpdateSharedCompositorFrameMetrics(); + return nsEventStatus_eConsumeNoDefault; + } + float spanRatio = aEvent.mCurrentSpan / aEvent.mPreviousSpan; + + // When we zoom in with focus, we can zoom too much towards the boundaries + // that we actually go over them. These are the needed displacements along + // either axis such that we don't overscroll the boundaries when zooming. + CSSPoint neededDisplacement; + + CSSToParentLayerScale realMinZoom = mZoomConstraints.mMinZoom; + CSSToParentLayerScale realMaxZoom = mZoomConstraints.mMaxZoom; + realMinZoom.scale = + std::max(realMinZoom.scale, Metrics().GetCompositionBounds().Width() / + Metrics().GetScrollableRect().Width()); + realMinZoom.scale = + std::max(realMinZoom.scale, Metrics().GetCompositionBounds().Height() / + Metrics().GetScrollableRect().Height()); + if (realMaxZoom < realMinZoom) { + realMaxZoom = realMinZoom; + } + + bool doScale = allowZoom && ((spanRatio > 1.0 && userZoom < realMaxZoom) || + (spanRatio < 1.0 && userZoom > realMinZoom)); + + if (doScale) { + spanRatio = clamped(spanRatio, realMinZoom.scale / userZoom.scale, + realMaxZoom.scale / userZoom.scale); + + // Note that the spanRatio here should never put us into OVERSCROLL_BOTH + // because up above we clamped it. + neededDisplacement.x = + -mX.ScaleWillOverscrollAmount(spanRatio, cssFocusPoint.x); + neededDisplacement.y = + -mY.ScaleWillOverscrollAmount(spanRatio, cssFocusPoint.y); + + ScaleWithFocus(spanRatio, cssFocusPoint); + + if (neededDisplacement != CSSPoint()) { + ScrollBy(neededDisplacement); + } + + // We don't want to redraw on every scale, so throttle it. + if (!mPinchPaintTimerSet) { + const int delay = StaticPrefs::apz_scale_repaint_delay_ms(); + if (delay >= 0) { + if (RefPtr controller = + GetGeckoContentController()) { + mPinchPaintTimerSet = true; + controller->PostDelayedTask( + NewRunnableMethod( + "layers::AsyncPanZoomController::" + "DoDelayedRequestContentRepaint", + this, + &AsyncPanZoomController::DoDelayedRequestContentRepaint), + delay); + } + } + } else if (apz::AboutToCheckerboard(mLastContentPaintMetrics, + Metrics())) { + // If we already scheduled a throttled repaint request but are also + // in danger of checkerboarding soon, trigger the repaint request to + // go out immediately. This should reduce the amount of time we spend + // checkerboarding. + // + // Note that if we remain in this "about to + // checkerboard" state over a period of time with multiple pinch input + // events (which is quite likely), then we will flip-flop between taking + // the above branch (!mPinchPaintTimerSet) and this branch (which will + // flush the repaint request and reset mPinchPaintTimerSet to false). + // This is sort of desirable because it halves the number of repaint + // requests we send, and therefore reduces IPC traffic. + // Keep in mind that many of these repaint requests will be ignored on + // the main-thread anyway due to the resolution mismatch - the first + // repaint request will be honored because APZ's notion of the painted + // resolution matches the actual main thread resolution, but that first + // repaint request will change the resolution on the main thread. + // Subsequent repaint requests will be ignored in APZCCallbackHelper, at + // https://searchfox.org/mozilla-central/rev/e0eb861a187f0bb6d994228f2e0e49b2c9ee455e/gfx/layers/apz/util/APZCCallbackHelper.cpp#331-338, + // until we receive a NotifyLayersUpdated call that re-syncs APZ's + // notion of the painted resolution to the main thread. These ignored + // repaint requests are contributing to IPC traffic needlessly, and so + // halving the number of repaint requests (as mentioned above) seems + // desirable. + DoDelayedRequestContentRepaint(); + } + + UpdateSharedCompositorFrameMetrics(); + + } else { + // Trigger a repaint request after scrolling. + RequestContentRepaint(); + } + + // We did a ScrollBy call above even if we didn't do a scale, so we + // should composite for that. + ScheduleComposite(); + } + + return nsEventStatus_eConsumeNoDefault; +} + +nsEventStatus AsyncPanZoomController::OnScaleEnd( + const PinchGestureInput& aEvent) { + APZC_LOG("%p got a scale-end in state %d\n", this, mState); + + mPinchPaintTimerSet = false; + + if (HasReadyTouchBlock() && + !GetCurrentTouchBlock()->TouchActionAllowsPinchZoom()) { + return nsEventStatus_eIgnore; + } + + // FIXME: bug 1525793 -- this may need to handle zooming or not on a + // per-document basis. + if (!StaticPrefs::apz_allow_zooming()) { + if (RefPtr controller = + GetGeckoContentController()) { + controller->NotifyPinchGesture( + aEvent.mType, GetGuid(), + ViewAs( + aEvent.mFocusPoint, + PixelCastJustification:: + LayoutDeviceIsScreenForUntransformedEvent), + 0, aEvent.modifiers); + } + } + + { + RecursiveMutexAutoLock lock(mRecursiveMutex); + ScheduleComposite(); + RequestContentRepaint(); + UpdateSharedCompositorFrameMetrics(); + } + + mPinchEventBuffer.clear(); + + if (aEvent.mType == PinchGestureInput::PINCHGESTURE_FINGERLIFTED) { + // One finger is still down, so transition to a TOUCHING state + if (mZoomConstraints.mAllowZoom) { + mPanDirRestricted = false; + StartTouch(aEvent.mLocalFocusPoint, aEvent.mTimeStamp); + SetState(TOUCHING); + } else { + // If zooming isn't allowed, StartTouch() was already called + // in OnScaleBegin(). + StartPanning(ToExternalPoint(aEvent.mScreenOffset, aEvent.mFocusPoint), + aEvent.mTimeStamp); + } + } else { + // Otherwise, handle the gesture being completely done. + + // Some of the code paths below, like ScrollSnap() or HandleEndOfPan(), + // may start an animation, but otherwise we want to end up in the NOTHING + // state. To avoid state change notification churn, we use a + // notification blocker. + bool stateWasPinching = (mState == PINCHING); + StateChangeNotificationBlocker blocker(this); + SetState(NOTHING); + + if (mZoomConstraints.mAllowZoom) { + RecursiveMutexAutoLock lock(mRecursiveMutex); + + // We can get into a situation where we are overscrolled at the end of a + // pinch if we go into overscroll with a two-finger pan, and then turn + // that into a pinch by increasing the span sufficiently. In such a case, + // there is no snap-back animation to get us out of overscroll, so we need + // to get out of it somehow. + // Moreover, in cases of scroll handoff, the overscroll can be on an APZC + // further up in the handoff chain rather than on the current APZC, so + // we need to clear overscroll along the entire handoff chain. + if (HasReadyTouchBlock()) { + GetCurrentTouchBlock()->GetOverscrollHandoffChain()->ClearOverscroll(); + } else { + ClearOverscroll(); + } + // Along with clearing the overscroll, we also want to snap to the nearest + // snap point as appropriate. + ScrollSnap(); + } else { + // when zoom is not allowed + EndTouch(aEvent.mTimeStamp); + if (stateWasPinching) { + // still pinching + if (HasReadyTouchBlock()) { + return HandleEndOfPan(); + } + } + } + } + return nsEventStatus_eConsumeNoDefault; +} + +nsEventStatus AsyncPanZoomController::HandleEndOfPan() { + MOZ_ASSERT(GetCurrentTouchBlock() || GetCurrentPanGestureBlock()); + GetCurrentInputBlock()->GetOverscrollHandoffChain()->FlushRepaints(); + ParentLayerPoint flingVelocity = GetVelocityVector(); + + // Clear our velocities; if DispatchFling() gives the fling to us, + // the fling velocity gets *added* to our existing velocity in + // AcceptFling(). + SetVelocityVector(ParentLayerPoint(0, 0)); + // Clear our state so that we don't stay in the PANNING state + // if DispatchFling() gives the fling to somone else. However, + // don't send the state change notification until we've determined + // what our final state is to avoid notification churn. + StateChangeNotificationBlocker blocker(this); + SetState(NOTHING); + + APZC_LOG("%p starting a fling animation if %f > %f\n", this, + flingVelocity.Length().value, + StaticPrefs::apz_fling_min_velocity_threshold()); + + if (flingVelocity.Length() <= + StaticPrefs::apz_fling_min_velocity_threshold()) { + // Relieve overscroll now if needed, since we will not transition to a fling + // animation and then an overscroll animation, and relieve it then. + GetCurrentInputBlock() + ->GetOverscrollHandoffChain() + ->SnapBackOverscrolledApzc(this); + mFlingAccelerator.Reset(); + return nsEventStatus_eConsumeNoDefault; + } + + // Make a local copy of the tree manager pointer and check that it's not + // null before calling DispatchFling(). This is necessary because Destroy(), + // which nulls out mTreeManager, could be called concurrently. + if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) { + const FlingHandoffState handoffState{ + flingVelocity, + GetCurrentInputBlock()->GetOverscrollHandoffChain(), + Some(mTouchStartRestingTimeBeforePan), + mMinimumVelocityDuringPan.valueOr(0), + false /* not handoff */, + GetCurrentInputBlock()->GetScrolledApzc()}; + treeManagerLocal->DispatchFling(this, handoffState); + } + return nsEventStatus_eConsumeNoDefault; +} + +Maybe AsyncPanZoomController::ConvertToGecko( + const ScreenIntPoint& aPoint) { + if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) { + if (Maybe layoutPoint = + treeManagerLocal->ConvertToGecko(aPoint, this)) { + return Some(LayoutDevicePoint(ViewAs( + *layoutPoint, + PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent))); + } + } + return Nothing(); +} + +CSSCoord AsyncPanZoomController::ConvertScrollbarPoint( + const ParentLayerPoint& aScrollbarPoint, + const ScrollbarData& aThumbData) const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + + CSSPoint scrollbarPoint; + if (Metrics().GetZoom() != CSSToParentLayerScale2D(0, 0)) { + // First, get it into the right coordinate space. + scrollbarPoint = aScrollbarPoint / Metrics().GetZoom(); + } + + // The scrollbar can be transformed with the frame but the pres shell + // resolution is only applied to the scroll frame. + scrollbarPoint = scrollbarPoint * Metrics().GetPresShellResolution(); + + // Now, get it to be relative to the beginning of the scroll track. + CSSRect cssCompositionBound = + Metrics().CalculateCompositionBoundsInCssPixelsOfSurroundingContent(); + return GetAxisStart(*aThumbData.mDirection, scrollbarPoint) - + GetAxisStart(*aThumbData.mDirection, cssCompositionBound) - + aThumbData.mScrollTrackStart; +} + +static bool AllowsScrollingMoreThanOnePage(double aMultiplier) { + const int32_t kMinAllowPageScroll = + EventStateManager::MIN_MULTIPLIER_VALUE_ALLOWING_OVER_ONE_PAGE_SCROLL; + return Abs(aMultiplier) >= kMinAllowPageScroll; +} + +ParentLayerPoint AsyncPanZoomController::GetScrollWheelDelta( + const ScrollWheelInput& aEvent) const { + return GetScrollWheelDelta(aEvent, aEvent.mDeltaX, aEvent.mDeltaY, + aEvent.mUserDeltaMultiplierX, + aEvent.mUserDeltaMultiplierY); +} + +ParentLayerPoint AsyncPanZoomController::GetScrollWheelDelta( + const ScrollWheelInput& aEvent, double aDeltaX, double aDeltaY, + double aMultiplierX, double aMultiplierY) const { + ParentLayerSize scrollAmount; + ParentLayerSize pageScrollSize; + + { + // Grab the lock to access the frame metrics. + RecursiveMutexAutoLock lock(mRecursiveMutex); + LayoutDeviceIntSize scrollAmountLD = mScrollMetadata.GetLineScrollAmount(); + LayoutDeviceIntSize pageScrollSizeLD = + mScrollMetadata.GetPageScrollAmount(); + scrollAmount = scrollAmountLD / Metrics().GetDevPixelsPerCSSPixel() * + Metrics().GetZoom(); + pageScrollSize = pageScrollSizeLD / Metrics().GetDevPixelsPerCSSPixel() * + Metrics().GetZoom(); + } + + ParentLayerPoint delta; + switch (aEvent.mDeltaType) { + case ScrollWheelInput::SCROLLDELTA_LINE: { + delta.x = aDeltaX * scrollAmount.width; + delta.y = aDeltaY * scrollAmount.height; + break; + } + case ScrollWheelInput::SCROLLDELTA_PAGE: { + delta.x = aDeltaX * pageScrollSize.width; + delta.y = aDeltaY * pageScrollSize.height; + break; + } + case ScrollWheelInput::SCROLLDELTA_PIXEL: { + delta = ToParentLayerCoordinates(ScreenPoint(aDeltaX, aDeltaY), + aEvent.mOrigin); + break; + } + } + + // Apply user-set multipliers. + delta.x *= aMultiplierX; + delta.y *= aMultiplierY; + + // For the conditions under which we allow system scroll overrides, see + // EventStateManager::DeltaAccumulator::ComputeScrollAmountForDefaultAction + // and WheelTransaction::OverrideSystemScrollSpeed. Note that we do *not* + // restrict this to the root content, see bug 1217715 for discussion on this. + if (StaticPrefs:: + mousewheel_system_scroll_override_on_root_content_enabled() && + !aEvent.IsCustomizedByUserPrefs() && + aEvent.mDeltaType == ScrollWheelInput::SCROLLDELTA_LINE && + aEvent.mAllowToOverrideSystemScrollSpeed) { + delta.x = WidgetWheelEvent::ComputeOverriddenDelta(delta.x, false); + delta.y = WidgetWheelEvent::ComputeOverriddenDelta(delta.y, true); + } + + // If this is a line scroll, and this event was part of a scroll series, then + // it might need extra acceleration. See WheelHandlingHelper.cpp. + if (aEvent.mDeltaType == ScrollWheelInput::SCROLLDELTA_LINE && + aEvent.mScrollSeriesNumber > 0) { + int32_t start = StaticPrefs::mousewheel_acceleration_start(); + if (start >= 0 && aEvent.mScrollSeriesNumber >= uint32_t(start)) { + int32_t factor = StaticPrefs::mousewheel_acceleration_factor(); + if (factor > 0) { + delta.x = ComputeAcceleratedWheelDelta( + delta.x, aEvent.mScrollSeriesNumber, factor); + delta.y = ComputeAcceleratedWheelDelta( + delta.y, aEvent.mScrollSeriesNumber, factor); + } + } + } + + // We shouldn't scroll more than one page at once except when the + // user preference is large. + if (!AllowsScrollingMoreThanOnePage(aMultiplierX) && + Abs(delta.x) > pageScrollSize.width) { + delta.x = (delta.x >= 0) ? pageScrollSize.width : -pageScrollSize.width; + } + if (!AllowsScrollingMoreThanOnePage(aMultiplierY) && + Abs(delta.y) > pageScrollSize.height) { + delta.y = (delta.y >= 0) ? pageScrollSize.height : -pageScrollSize.height; + } + + return delta; +} + +static void ReportKeyboardScrollAction(const KeyboardScrollAction& aAction) { + ScrollInputMethod scrollMethod; + + switch (aAction.mType) { + case KeyboardScrollAction::eScrollLine: { + scrollMethod = ScrollInputMethod::ApzScrollLine; + break; + } + case KeyboardScrollAction::eScrollCharacter: { + scrollMethod = ScrollInputMethod::ApzScrollCharacter; + break; + } + case KeyboardScrollAction::eScrollPage: { + scrollMethod = ScrollInputMethod::ApzScrollPage; + break; + } + case KeyboardScrollAction::eScrollComplete: { + scrollMethod = ScrollInputMethod::ApzCompleteScroll; + break; + } + } + + mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS, + (uint32_t)scrollMethod); +} + +nsEventStatus AsyncPanZoomController::OnKeyboard(const KeyboardInput& aEvent) { + // Report the type of scroll action to telemetry + ReportKeyboardScrollAction(aEvent.mAction); + + // Mark that this APZC has async key scrolled + mTestHasAsyncKeyScrolled = true; + + // Calculate the destination for this keyboard scroll action + CSSPoint destination = GetKeyboardDestination(aEvent.mAction); + bool scrollSnapped = + MaybeAdjustDestinationForScrollSnapping(aEvent, destination); + + RecordScrollPayload(aEvent.mTimeStamp); + // If smooth scrolling is disabled, then scroll immediately to the destination + if (!StaticPrefs::general_smoothScroll()) { + CancelAnimation(); + + ParentLayerPoint startPoint, endPoint; + + { + RecursiveMutexAutoLock lock(mRecursiveMutex); + + // CallDispatchScroll interprets the start and end points as the start and + // end of a touch scroll so they need to be reversed. + startPoint = destination * Metrics().GetZoom(); + endPoint = Metrics().GetVisualScrollOffset() * Metrics().GetZoom(); + } + + ParentLayerPoint delta = endPoint - startPoint; + + ScreenPoint distance = ToScreenCoordinates( + ParentLayerPoint(fabs(delta.x), fabs(delta.y)), startPoint); + + OverscrollHandoffState handoffState( + *mInputQueue->GetCurrentKeyboardBlock()->GetOverscrollHandoffChain(), + distance, ScrollSource::Keyboard); + + CallDispatchScroll(startPoint, endPoint, handoffState); + + SetState(NOTHING); + + return nsEventStatus_eConsumeDoDefault; + } + + // The lock must be held across the entire update operation, so the + // compositor doesn't end the animation before we get a chance to + // update it. + RecursiveMutexAutoLock lock(mRecursiveMutex); + + if (scrollSnapped) { + // If we're scroll snapping, use a smooth scroll animation to get + // the desired physics. Note that SmoothMsdScrollTo() will re-use an + // existing smooth scroll animation if there is one. + APZC_LOG("%p keyboard scrolling to snap point %s\n", this, + ToString(destination).c_str()); + SmoothMsdScrollTo(destination); + return nsEventStatus_eConsumeDoDefault; + } + + // Use a keyboard scroll animation to scroll, reusing an existing one if it + // exists + if (mState != KEYBOARD_SCROLL) { + CancelAnimation(); + SetState(KEYBOARD_SCROLL); + + nsPoint initialPosition = + CSSPoint::ToAppUnits(Metrics().GetVisualScrollOffset()); + StartAnimation(new SmoothScrollAnimation( + *this, initialPosition, + SmoothScrollAnimation::GetScrollOriginForAction(aEvent.mAction.mType))); + } + + // Convert velocity from ParentLayerPoints/ms to ParentLayerPoints/s and then + // to appunits/second. + nsPoint velocity; + if (Metrics().GetZoom() != CSSToParentLayerScale2D(0, 0)) { + velocity = + CSSPoint::ToAppUnits(ParentLayerPoint(mX.GetVelocity() * 1000.0f, + mY.GetVelocity() * 1000.0f) / + Metrics().GetZoom()); + } + + SmoothScrollAnimation* animation = mAnimation->AsSmoothScrollAnimation(); + MOZ_ASSERT(animation); + + animation->UpdateDestination(aEvent.mTimeStamp, + CSSPixel::ToAppUnits(destination), + nsSize(velocity.x, velocity.y)); + + return nsEventStatus_eConsumeDoDefault; +} + +CSSPoint AsyncPanZoomController::GetKeyboardDestination( + const KeyboardScrollAction& aAction) const { + CSSSize lineScrollSize; + CSSSize pageScrollSize; + CSSPoint scrollOffset; + CSSRect scrollRect; + + { + // Grab the lock to access the frame metrics. + RecursiveMutexAutoLock lock(mRecursiveMutex); + + lineScrollSize = mScrollMetadata.GetLineScrollAmount() / + Metrics().GetDevPixelsPerCSSPixel(); + pageScrollSize = mScrollMetadata.GetPageScrollAmount() / + Metrics().GetDevPixelsPerCSSPixel(); + + scrollOffset = GetCurrentAnimationDestination(lock).valueOr( + Metrics().GetVisualScrollOffset()); + + scrollRect = Metrics().GetScrollableRect(); + } + + // Calculate the scroll destination based off of the scroll type and direction + CSSPoint scrollDestination = scrollOffset; + + switch (aAction.mType) { + case KeyboardScrollAction::eScrollCharacter: { + int32_t scrollDistance = + StaticPrefs::toolkit_scrollbox_horizontalScrollDistance(); + + if (aAction.mForward) { + scrollDestination.x += scrollDistance * lineScrollSize.width; + } else { + scrollDestination.x -= scrollDistance * lineScrollSize.width; + } + break; + } + case KeyboardScrollAction::eScrollLine: { + int32_t scrollDistance = + StaticPrefs::toolkit_scrollbox_verticalScrollDistance(); + + if (aAction.mForward) { + scrollDestination.y += scrollDistance * lineScrollSize.height; + } else { + scrollDestination.y -= scrollDistance * lineScrollSize.height; + } + break; + } + case KeyboardScrollAction::eScrollPage: { + if (aAction.mForward) { + scrollDestination.y += pageScrollSize.height; + } else { + scrollDestination.y -= pageScrollSize.height; + } + break; + } + case KeyboardScrollAction::eScrollComplete: { + if (aAction.mForward) { + scrollDestination.y = scrollRect.YMost(); + } else { + scrollDestination.y = scrollRect.Y(); + } + break; + } + } + + return scrollDestination; +} + +ParentLayerPoint AsyncPanZoomController::GetDeltaForEvent( + const InputData& aEvent) const { + ParentLayerPoint delta; + if (aEvent.mInputType == SCROLLWHEEL_INPUT) { + delta = GetScrollWheelDelta(aEvent.AsScrollWheelInput()); + } else if (aEvent.mInputType == PANGESTURE_INPUT) { + const PanGestureInput& panInput = aEvent.AsPanGestureInput(); + delta = ToParentLayerCoordinates(panInput.UserMultipliedPanDisplacement(), + panInput.mPanStartPoint); + } + return delta; +} + +// Return whether or not the underlying layer can be scrolled on either axis. +bool AsyncPanZoomController::CanScroll(const InputData& aEvent) const { + ParentLayerPoint delta = GetDeltaForEvent(aEvent); + if (!delta.x && !delta.y) { + return false; + } + + if (SCROLLWHEEL_INPUT == aEvent.mInputType) { + const ScrollWheelInput& scrollWheelInput = aEvent.AsScrollWheelInput(); + // If it's a wheel scroll, we first check if it is an auto-dir scroll. + // 1. For an auto-dir scroll, check if it's delta should be adjusted, if it + // is, then we can conclude it must be scrollable; otherwise, fall back + // to checking if it is scrollable without adjusting its delta. + // 2. For a non-auto-dir scroll, simply check if it is scrollable without + // adjusting its delta. + if (scrollWheelInput.IsAutoDir()) { + RecursiveMutexAutoLock lock(mRecursiveMutex); + auto deltaX = scrollWheelInput.mDeltaX; + auto deltaY = scrollWheelInput.mDeltaY; + bool isRTL = + IsContentOfHonouredTargetRightToLeft(scrollWheelInput.HonoursRoot()); + APZAutoDirWheelDeltaAdjuster adjuster(deltaX, deltaY, mX, mY, isRTL); + if (adjuster.ShouldBeAdjusted()) { + // If we detect that the delta values should be adjusted for an auto-dir + // wheel scroll, then it is impossible to be an unscrollable scroll. + return true; + } + } + return CanScrollWithWheel(delta); + } + return CanScroll(delta); +} + +ScrollDirections AsyncPanZoomController::GetAllowedHandoffDirections() const { + ScrollDirections result; + RecursiveMutexAutoLock lock(mRecursiveMutex); + if (mX.OverscrollBehaviorAllowsHandoff()) { + result += ScrollDirection::eHorizontal; + } + if (mY.OverscrollBehaviorAllowsHandoff()) { + result += ScrollDirection::eVertical; + } + return result; +} + +bool AsyncPanZoomController::CanScroll(const ParentLayerPoint& aDelta) const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + return mX.CanScroll(aDelta.x) || mY.CanScroll(aDelta.y); +} + +bool AsyncPanZoomController::CanScrollWithWheel( + const ParentLayerPoint& aDelta) const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + + // For more details about the concept of a disregarded direction, refer to the + // code in struct ScrollMetadata which defines mDisregardedDirection. + Maybe disregardedDirection = + mScrollMetadata.GetDisregardedDirection(); + if (mX.CanScroll(aDelta.x) && + disregardedDirection != Some(ScrollDirection::eHorizontal)) { + return true; + } + if (mY.CanScroll(aDelta.y) && + disregardedDirection != Some(ScrollDirection::eVertical)) { + return true; + } + return false; +} + +bool AsyncPanZoomController::CanScroll(ScrollDirection aDirection) const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + switch (aDirection) { + case ScrollDirection::eHorizontal: + return mX.CanScroll(); + case ScrollDirection::eVertical: + return mY.CanScroll(); + } + MOZ_ASSERT_UNREACHABLE("Invalid value"); + return false; +} + +bool AsyncPanZoomController::CanScrollDownwardsWithDynamicToolbar() const { + MOZ_ASSERT(IsRootContent()); + + RecursiveMutexAutoLock lock(mRecursiveMutex); + return mY.CanScrollDownwardsWithDynamicToolbar(); +} + +bool AsyncPanZoomController::CanScrollDownwards() const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + return mY.CanScrollTo(eSideBottom); +} + +bool AsyncPanZoomController::IsContentOfHonouredTargetRightToLeft( + bool aHonoursRoot) const { + if (aHonoursRoot) { + return mScrollMetadata.IsAutoDirRootContentRTL(); + } + RecursiveMutexAutoLock lock(mRecursiveMutex); + return Metrics().IsHorizontalContentRightToLeft(); +} + +bool AsyncPanZoomController::AllowScrollHandoffInCurrentBlock() const { + bool result = mInputQueue->AllowScrollHandoff(); + if (!StaticPrefs::apz_allow_immediate_handoff()) { + if (InputBlockState* currentBlock = GetCurrentInputBlock()) { + // Do not allow handoff beyond the first APZC to scroll. + if (currentBlock->GetScrolledApzc() == this) { + result = false; + APZC_LOG("%p dropping handoff; AllowImmediateHandoff=false\n", this); + } + } + } + return result; +} + +void AsyncPanZoomController::DoDelayedRequestContentRepaint() { + if (!IsDestroyed() && mPinchPaintTimerSet) { + RecursiveMutexAutoLock lock(mRecursiveMutex); + RequestContentRepaint(); + } + mPinchPaintTimerSet = false; +} + +static ScrollInputMethod ScrollInputMethodForWheelDeltaType( + ScrollWheelInput::ScrollDeltaType aDeltaType) { + switch (aDeltaType) { + case ScrollWheelInput::SCROLLDELTA_LINE: { + return ScrollInputMethod::ApzWheelLine; + } + case ScrollWheelInput::SCROLLDELTA_PAGE: { + return ScrollInputMethod::ApzWheelPage; + } + case ScrollWheelInput::SCROLLDELTA_PIXEL: { + return ScrollInputMethod::ApzWheelPixel; + } + } + MOZ_ASSERT_UNREACHABLE("Invalid value"); + return ScrollInputMethod::ApzWheelLine; +} + +static void AdjustDeltaForAllowedScrollDirections( + ParentLayerPoint& aDelta, + const ScrollDirections& aAllowedScrollDirections) { + if (!aAllowedScrollDirections.contains(ScrollDirection::eHorizontal)) { + aDelta.x = 0; + } + if (!aAllowedScrollDirections.contains(ScrollDirection::eVertical)) { + aDelta.y = 0; + } +} + +nsEventStatus AsyncPanZoomController::OnScrollWheel( + const ScrollWheelInput& aEvent) { + // Get the scroll wheel's delta values in parent-layer pixels. But before + // getting the values, we need to check if it is an auto-dir scroll and if it + // should be adjusted, if both answers are yes, let's adjust X and Y values + // first, and then get the delta values in parent-layer pixels based on the + // adjusted values. + bool adjustedByAutoDir = false; + auto deltaX = aEvent.mDeltaX; + auto deltaY = aEvent.mDeltaY; + ParentLayerPoint delta; + if (aEvent.IsAutoDir()) { + // It's an auto-dir scroll, so check if its delta should be adjusted, if so, + // adjust it. + RecursiveMutexAutoLock lock(mRecursiveMutex); + bool isRTL = IsContentOfHonouredTargetRightToLeft(aEvent.HonoursRoot()); + APZAutoDirWheelDeltaAdjuster adjuster(deltaX, deltaY, mX, mY, isRTL); + if (adjuster.ShouldBeAdjusted()) { + adjuster.Adjust(); + adjustedByAutoDir = true; + } + } + // Ensure the calls to GetScrollWheelDelta are outside the mRecursiveMutex + // lock since these calls may acquire the APZ tree lock. Holding + // mRecursiveMutex while acquiring the APZ tree lock is lock ordering + // violation. + if (adjustedByAutoDir) { + // If the original delta values have been adjusted, we pass them to + // replace the original delta values in |aEvent| so that the delta values + // in parent-layer pixels are caculated based on the adjusted values, not + // the original ones. + // Pay special attention to the last two parameters. They are in a swaped + // order so that they still correspond to their delta after adjustment. + delta = GetScrollWheelDelta(aEvent, deltaX, deltaY, + aEvent.mUserDeltaMultiplierY, + aEvent.mUserDeltaMultiplierX); + } else { + // If the original delta values haven't been adjusted by auto-dir, just pass + // the |aEvent| and caculate the delta values in parent-layer pixels based + // on the original delta values from |aEvent|. + delta = GetScrollWheelDelta(aEvent); + } + + APZC_LOG("%p got a scroll-wheel with delta in parent-layer pixels: %s\n", + this, ToString(delta).c_str()); + + if (adjustedByAutoDir) { + MOZ_ASSERT(delta.x || delta.y, + "Adjusted auto-dir delta values can never be all-zero."); + APZC_LOG("%p got a scroll-wheel with adjusted auto-dir delta values\n", + this); + } else if ((delta.x || delta.y) && !CanScrollWithWheel(delta)) { + // We can't scroll this apz anymore, so we simply drop the event. + if (mInputQueue->GetActiveWheelTransaction() && + StaticPrefs::test_mousescroll()) { + if (RefPtr controller = + GetGeckoContentController()) { + controller->NotifyMozMouseScrollEvent(GetScrollId(), + u"MozMouseScrollFailed"_ns); + } + } + return nsEventStatus_eConsumeNoDefault; + } + + MOZ_ASSERT(mInputQueue->GetCurrentWheelBlock()); + AdjustDeltaForAllowedScrollDirections( + delta, mInputQueue->GetCurrentWheelBlock()->GetAllowedScrollDirections()); + + if (delta.x == 0 && delta.y == 0) { + // Avoid spurious state changes and unnecessary work + return nsEventStatus_eIgnore; + } + + mozilla::Telemetry::Accumulate( + mozilla::Telemetry::SCROLL_INPUT_METHODS, + (uint32_t)ScrollInputMethodForWheelDeltaType(aEvent.mDeltaType)); + + switch (aEvent.mScrollMode) { + case ScrollWheelInput::SCROLLMODE_INSTANT: { + // Wheel events from "clicky" mouse wheels trigger scroll snapping to the + // next snap point. Check for this, and adjust the delta to take into + // account the snap point. + CSSPoint startPosition; + { + RecursiveMutexAutoLock lock(mRecursiveMutex); + startPosition = Metrics().GetVisualScrollOffset(); + } + MaybeAdjustDeltaForScrollSnappingOnWheelInput(aEvent, delta, + startPosition); + + ScreenPoint distance = ToScreenCoordinates( + ParentLayerPoint(fabs(delta.x), fabs(delta.y)), aEvent.mLocalOrigin); + + CancelAnimation(); + + OverscrollHandoffState handoffState( + *mInputQueue->GetCurrentWheelBlock()->GetOverscrollHandoffChain(), + distance, ScrollSource::Wheel); + ParentLayerPoint startPoint = aEvent.mLocalOrigin; + ParentLayerPoint endPoint = aEvent.mLocalOrigin - delta; + RecordScrollPayload(aEvent.mTimeStamp); + CallDispatchScroll(startPoint, endPoint, handoffState); + + SetState(NOTHING); + + // The calls above handle their own locking; moreover, + // ToScreenCoordinates() and CallDispatchScroll() can grab the tree lock. + RecursiveMutexAutoLock lock(mRecursiveMutex); + RequestContentRepaint(); + + break; + } + + case ScrollWheelInput::SCROLLMODE_SMOOTH: { + // The lock must be held across the entire update operation, so the + // compositor doesn't end the animation before we get a chance to + // update it. + RecursiveMutexAutoLock lock(mRecursiveMutex); + + RecordScrollPayload(aEvent.mTimeStamp); + // Perform scroll snapping if appropriate. + // If we're already in a wheel scroll or smooth scroll animation, + // the delta is applied to its destination, not to the current + // scroll position. Take this into account when finding a snap point. + CSSPoint startPosition = GetCurrentAnimationDestination(lock).valueOr( + Metrics().GetVisualScrollOffset()); + + if (MaybeAdjustDeltaForScrollSnappingOnWheelInput(aEvent, delta, + startPosition)) { + // If we're scroll snapping, use a smooth scroll animation to get + // the desired physics. Note that SmoothMsdScrollTo() will re-use an + // existing smooth scroll animation if there is one. + APZC_LOG("%p wheel scrolling to snap point %s\n", this, + ToString(startPosition).c_str()); + SmoothMsdScrollTo(startPosition); + break; + } + + // Otherwise, use a wheel scroll animation, also reusing one if possible. + if (mState != WHEEL_SCROLL) { + CancelAnimation(); + SetState(WHEEL_SCROLL); + + nsPoint initialPosition = + CSSPoint::ToAppUnits(Metrics().GetVisualScrollOffset()); + StartAnimation(new WheelScrollAnimation(*this, initialPosition, + aEvent.mDeltaType)); + } + // Convert velocity from ParentLayerPoints/ms to ParentLayerPoints/s and + // then to appunits/second. + + nsPoint deltaInAppUnits; + nsPoint velocity; + if (Metrics().GetZoom() != CSSToParentLayerScale2D(0, 0)) { + deltaInAppUnits = CSSPoint::ToAppUnits(delta / Metrics().GetZoom()); + velocity = + CSSPoint::ToAppUnits(ParentLayerPoint(mX.GetVelocity() * 1000.0f, + mY.GetVelocity() * 1000.0f) / + Metrics().GetZoom()); + } + + WheelScrollAnimation* animation = mAnimation->AsWheelScrollAnimation(); + animation->UpdateDelta(aEvent.mTimeStamp, deltaInAppUnits, + nsSize(velocity.x, velocity.y)); + break; + } + } + + return nsEventStatus_eConsumeNoDefault; +} + +void AsyncPanZoomController::NotifyMozMouseScrollEvent( + const nsString& aString) const { + RefPtr controller = GetGeckoContentController(); + if (!controller) { + return; + } + controller->NotifyMozMouseScrollEvent(GetScrollId(), aString); +} + +nsEventStatus AsyncPanZoomController::OnPanMayBegin( + const PanGestureInput& aEvent) { + APZC_LOG("%p got a pan-maybegin in state %d\n", this, mState); + + StartTouch(aEvent.mLocalPanStartPoint, aEvent.mTimeStamp); + MOZ_ASSERT(GetCurrentPanGestureBlock()); + GetCurrentPanGestureBlock()->GetOverscrollHandoffChain()->CancelAnimations(); + + return nsEventStatus_eConsumeNoDefault; +} + +nsEventStatus AsyncPanZoomController::OnPanCancelled( + const PanGestureInput& aEvent) { + APZC_LOG("%p got a pan-cancelled in state %d\n", this, mState); + + mX.CancelGesture(); + mY.CancelGesture(); + + return nsEventStatus_eConsumeNoDefault; +} + +nsEventStatus AsyncPanZoomController::OnPanBegin( + const PanGestureInput& aEvent) { + APZC_LOG("%p got a pan-begin in state %d\n", this, mState); + + if (mState == SMOOTHMSD_SCROLL) { + // SMOOTHMSD_SCROLL scrolls are cancelled by pan gestures. + CancelAnimation(); + } + + StartTouch(aEvent.mLocalPanStartPoint, aEvent.mTimeStamp); + + mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS, + (uint32_t)ScrollInputMethod::ApzPanGesture); + + if (GetAxisLockMode() == FREE) { + SetState(PANNING); + return nsEventStatus_eConsumeNoDefault; + } + + float dx = aEvent.mPanDisplacement.x, dy = aEvent.mPanDisplacement.y; + + if (dx || dy) { + double angle = atan2(dy, dx); // range [-pi, pi] + angle = fabs(angle); // range [0, pi] + HandlePanning(angle); + } else { + SetState(PANNING); + } + + // Call into OnPan in order to process any delta included in this event. + OnPan(aEvent, true); + + return nsEventStatus_eConsumeNoDefault; +} + +nsEventStatus AsyncPanZoomController::OnPan(const PanGestureInput& aEvent, + bool aFingersOnTouchpad) { + APZC_LOG("%p got a pan-pan in state %d\n", this, mState); + + if (mState == SMOOTHMSD_SCROLL) { + if (!aFingersOnTouchpad) { + // When a SMOOTHMSD_SCROLL scroll is being processed on a frame, mouse + // wheel and trackpad momentum scroll position updates will not cancel the + // SMOOTHMSD_SCROLL scroll animations, enabling scripts that depend on + // them to be responsive without forcing the user to wait for the momentum + // scrolling to completely stop. + return nsEventStatus_eConsumeNoDefault; + } + + // SMOOTHMSD_SCROLL scrolls are cancelled by pan gestures. + CancelAnimation(); + } + + if (mState == NOTHING) { + // This event block was interrupted by something else. If the user's fingers + // are still on on the touchpad we want to resume scrolling, otherwise we + // ignore the rest of the scroll gesture. + if (!aFingersOnTouchpad) { + return nsEventStatus_eConsumeNoDefault; + } + // Resume / restart the pan. + // PanBegin will call back into this function with mState == PANNING. + return OnPanBegin(aEvent); + } + + // Note that there is a multiplier that applies onto the "physical" pan + // displacement (how much the user's fingers moved) that produces the + // "logical" pan displacement (how much the page should move). For some of the + // code below it makes more sense to use the physical displacement rather than + // the logical displacement, and vice-versa. + ScreenPoint physicalPanDisplacement = aEvent.mPanDisplacement; + ParentLayerPoint logicalPanDisplacement = + aEvent.UserMultipliedLocalPanDisplacement(); + if (aEvent.mDeltaType == PanGestureInput::PANDELTA_PAGE) { + // Pan events with page units are used by Gtk, so this replicates Gtk: + // https://gitlab.gnome.org/GNOME/gtk/blob/c734c7e9188b56f56c3a504abee05fa40c5475ac/gtk/gtkrange.c#L3065-3073 + CSSSize pageScrollSize; + CSSToParentLayerScale2D zoom; + { + // Grab the lock to access the frame metrics. + RecursiveMutexAutoLock lock(mRecursiveMutex); + pageScrollSize = mScrollMetadata.GetPageScrollAmount() / + Metrics().GetDevPixelsPerCSSPixel(); + zoom = Metrics().GetZoom(); + } + // scrollUnit* is in units of "ParentLayer pixels per page proportion"... + auto scrollUnitWidth = std::min(std::pow(pageScrollSize.width, 2.0 / 3.0), + pageScrollSize.width / 2.0) * + zoom.xScale; + auto scrollUnitHeight = std::min(std::pow(pageScrollSize.height, 2.0 / 3.0), + pageScrollSize.height / 2.0) * + zoom.yScale; + // ... and pan displacements are in units of "page proportion count" + // here, so the products of them and scrollUnit* are in ParentLayer pixels + ParentLayerPoint physicalPanDisplacementPL( + physicalPanDisplacement.x * scrollUnitWidth, + physicalPanDisplacement.y * scrollUnitHeight); + physicalPanDisplacement = ToScreenCoordinates(physicalPanDisplacementPL, + aEvent.mLocalPanStartPoint); + logicalPanDisplacement.x *= scrollUnitWidth; + logicalPanDisplacement.y *= scrollUnitHeight; + + // Accelerate (decelerate) any pans by raising it to a user configurable + // power (apz.touch_acceleration_factor_x, apz.touch_acceleration_factor_y) + // + // Confine input for pow() to greater than or equal to 0 to avoid domain + // errors with non-integer exponents + if (mX.GetVelocity() != 0) { + float absVelocity = std::abs(mX.GetVelocity()); + logicalPanDisplacement.x *= + std::pow(absVelocity, + StaticPrefs::apz_touch_acceleration_factor_x()) / + absVelocity; + } + + if (mY.GetVelocity() != 0) { + float absVelocity = std::abs(mY.GetVelocity()); + logicalPanDisplacement.y *= + std::pow(absVelocity, + StaticPrefs::apz_touch_acceleration_factor_y()) / + absVelocity; + } + } + + MOZ_ASSERT(GetCurrentPanGestureBlock()); + AdjustDeltaForAllowedScrollDirections( + logicalPanDisplacement, + GetCurrentPanGestureBlock()->GetAllowedScrollDirections()); + + // We need to update the axis velocity in order to get a useful display port + // size and position. We need to do so even if this is a momentum pan (i.e. + // aFingersOnTouchpad == false); in that case the "with touch" part is not + // really appropriate, so we may want to rethink this at some point. + // Note that we have to make all simulated positions relative to + // Axis::GetPos(), because the current position is an invented position, and + // because resetting the position to the mouse position (e.g. + // aEvent.mLocalStartPoint) would mess up velocity calculation. (This is + // the only caller of UpdateWithTouchAtDevicePoint() for pan events, so + // there is no risk of other calls resetting the position.) + mX.UpdateWithTouchAtDevicePoint(mX.GetPos() - logicalPanDisplacement.x, + aEvent.mTimeStamp); + mY.UpdateWithTouchAtDevicePoint(mY.GetPos() - logicalPanDisplacement.y, + aEvent.mTimeStamp); + + HandlePanningUpdate(physicalPanDisplacement); + + ScreenPoint panDistance(fabs(physicalPanDisplacement.x), + fabs(physicalPanDisplacement.y)); + OverscrollHandoffState handoffState( + *GetCurrentPanGestureBlock()->GetOverscrollHandoffChain(), panDistance, + ScrollSource::Wheel); + + // Create fake "touch" positions that will result in the desired scroll + // motion. Note that the pan displacement describes the change in scroll + // position: positive displacement values mean that the scroll position + // increases. However, an increase in scroll position means that the scrolled + // contents are moved to the left / upwards. Since our simulated "touches" + // determine the motion of the scrolled contents, not of the scroll position, + // they need to move in the opposite direction of the pan displacement. + ParentLayerPoint startPoint = aEvent.mLocalPanStartPoint; + ParentLayerPoint endPoint = + aEvent.mLocalPanStartPoint - logicalPanDisplacement; + if (logicalPanDisplacement != ParentLayerPoint()) { + // Don't expect a composite to be triggered if the displacement is zero + RecordScrollPayload(aEvent.mTimeStamp); + } + bool consumed = CallDispatchScroll(startPoint, endPoint, handoffState); + + if (!consumed && !aFingersOnTouchpad) { + // If there is unconsumed scroll and we're in the momentum part of the + // pan gesture, terminate the momentum scroll. This prevents momentum + // scroll events from unexpectedly causing scrolling later if somehow + // the APZC becomes scrollable again in this direction (e.g. if the user + // uses some other input method to scroll in the opposite direction). + SetState(NOTHING); + } + + return nsEventStatus_eConsumeNoDefault; +} + +nsEventStatus AsyncPanZoomController::OnPanEnd(const PanGestureInput& aEvent) { + APZC_LOG("%p got a pan-end in state %d\n", this, mState); + + if (aEvent.mPanDisplacement != ScreenPoint{}) { + // Call into OnPan in order to process the delta included in this event. + OnPan(aEvent, true); + } + + EndTouch(aEvent.mTimeStamp); + + // Use HandleEndOfPan for fling on platforms that don't + // emit momentum events (Gtk). + if (aEvent.mSimulateMomentum) { + return HandleEndOfPan(); + } + + // Drop any velocity on axes where we don't have room to scroll anyways + // (in this APZC, or an APZC further in the handoff chain). + // This ensures that we don't enlarge the display port unnecessarily. + MOZ_ASSERT(GetCurrentPanGestureBlock()); + RefPtr overscrollHandoffChain = + GetCurrentPanGestureBlock()->GetOverscrollHandoffChain(); + { + RecursiveMutexAutoLock lock(mRecursiveMutex); + if (!overscrollHandoffChain->CanScrollInDirection( + this, ScrollDirection::eHorizontal)) { + mX.SetVelocity(0); + } + if (!overscrollHandoffChain->CanScrollInDirection( + this, ScrollDirection::eVertical)) { + mY.SetVelocity(0); + } + } + + SetState(NOTHING); + RequestContentRepaint(); + + if (!aEvent.mFollowedByMomentum) { + ScrollSnap(); + } + + return nsEventStatus_eConsumeNoDefault; +} + +nsEventStatus AsyncPanZoomController::OnPanMomentumStart( + const PanGestureInput& aEvent) { + APZC_LOG("%p got a pan-momentumstart in state %d\n", this, mState); + + if (mState == SMOOTHMSD_SCROLL) { + // SMOOTHMSD_SCROLL scrolls are cancelled by pan gestures. + CancelAnimation(); + } + + SetState(PAN_MOMENTUM); + ScrollSnapToDestination(); + + // Call into OnPan in order to process any delta included in this event. + OnPan(aEvent, false); + + return nsEventStatus_eConsumeNoDefault; +} + +nsEventStatus AsyncPanZoomController::OnPanMomentumEnd( + const PanGestureInput& aEvent) { + APZC_LOG("%p got a pan-momentumend in state %d\n", this, mState); + + // Call into OnPan in order to process any delta included in this event. + OnPan(aEvent, false); + + // We need to reset the velocity to zero. We don't really have a "touch" + // here because the touch has already ended long before the momentum + // animation started, but I guess it doesn't really matter for now. + mX.CancelGesture(); + mY.CancelGesture(); + SetState(NOTHING); + + RequestContentRepaint(); + + return nsEventStatus_eConsumeNoDefault; +} + +nsEventStatus AsyncPanZoomController::OnLongPress( + const TapGestureInput& aEvent) { + APZC_LOG("%p got a long-press in state %d\n", this, mState); + RefPtr controller = GetGeckoContentController(); + if (controller) { + if (Maybe geckoScreenPoint = + ConvertToGecko(aEvent.mPoint)) { + TouchBlockState* touch = GetCurrentTouchBlock(); + if (!touch) { + APZC_LOG( + "%p dropping long-press because some non-touch block interrupted " + "it\n", + this); + return nsEventStatus_eIgnore; + } + if (touch->IsDuringFastFling()) { + APZC_LOG("%p dropping long-press because of fast fling\n", this); + return nsEventStatus_eIgnore; + } + uint64_t blockId = GetInputQueue()->InjectNewTouchBlock(this); + controller->HandleTap(TapType::eLongTap, *geckoScreenPoint, + aEvent.modifiers, GetGuid(), blockId); + return nsEventStatus_eConsumeNoDefault; + } + } + return nsEventStatus_eIgnore; +} + +nsEventStatus AsyncPanZoomController::OnLongPressUp( + const TapGestureInput& aEvent) { + APZC_LOG("%p got a long-tap-up in state %d\n", this, mState); + return GenerateSingleTap(TapType::eLongTapUp, aEvent.mPoint, + aEvent.modifiers); +} + +nsEventStatus AsyncPanZoomController::GenerateSingleTap( + TapType aType, const ScreenIntPoint& aPoint, + mozilla::Modifiers aModifiers) { + RefPtr controller = GetGeckoContentController(); + if (controller) { + if (Maybe geckoScreenPoint = ConvertToGecko(aPoint)) { + TouchBlockState* touch = GetCurrentTouchBlock(); + // |touch| may be null in the case where this function is + // invoked by GestureEventListener on a timeout. In that case we already + // verified that the single tap is allowed so we let it through. + // XXX there is a bug here that in such a case the touch block that + // generated this tap will not get its mSingleTapOccurred flag set. + // See https://bugzilla.mozilla.org/show_bug.cgi?id=1256344#c6 + if (touch) { + if (touch->IsDuringFastFling()) { + APZC_LOG( + "%p dropping single-tap because it was during a fast-fling\n", + this); + return nsEventStatus_eIgnore; + } + touch->SetSingleTapOccurred(); + } + // Because this may be being running as part of + // APZCTreeManager::ReceiveInputEvent, calling controller->HandleTap + // directly might mean that content receives the single tap message before + // the corresponding touch-up. To avoid that we schedule the singletap + // message to run on the next spin of the event loop. See bug 965381 for + // the issue this was causing. + APZC_LOG("posting runnable for HandleTap from GenerateSingleTap"); + RefPtr runnable = + NewRunnableMethod( + "layers::GeckoContentController::HandleTap", controller, + &GeckoContentController::HandleTap, aType, *geckoScreenPoint, + aModifiers, GetGuid(), touch ? touch->GetBlockId() : 0); + + controller->PostDelayedTask(runnable.forget(), 0); + return nsEventStatus_eConsumeNoDefault; + } + } + return nsEventStatus_eIgnore; +} + +void AsyncPanZoomController::OnTouchEndOrCancel() { + if (RefPtr controller = GetGeckoContentController()) { + MOZ_ASSERT(GetCurrentTouchBlock()); + controller->NotifyAPZStateChange( + GetGuid(), APZStateChange::eEndTouch, + GetCurrentTouchBlock()->SingleTapOccurred()); + } +} + +nsEventStatus AsyncPanZoomController::OnSingleTapUp( + const TapGestureInput& aEvent) { + APZC_LOG("%p got a single-tap-up in state %d\n", this, mState); + // If mZoomConstraints.mAllowDoubleTapZoom is true we wait for a call to + // OnSingleTapConfirmed before sending event to content + MOZ_ASSERT(GetCurrentTouchBlock()); + if (!(mZoomConstraints.mAllowDoubleTapZoom && + GetCurrentTouchBlock()->TouchActionAllowsDoubleTapZoom())) { + return GenerateSingleTap(TapType::eSingleTap, aEvent.mPoint, + aEvent.modifiers); + } + return nsEventStatus_eIgnore; +} + +nsEventStatus AsyncPanZoomController::OnSingleTapConfirmed( + const TapGestureInput& aEvent) { + APZC_LOG("%p got a single-tap-confirmed in state %d\n", this, mState); + return GenerateSingleTap(TapType::eSingleTap, aEvent.mPoint, + aEvent.modifiers); +} + +nsEventStatus AsyncPanZoomController::OnDoubleTap( + const TapGestureInput& aEvent) { + APZC_LOG("%p got a double-tap in state %d\n", this, mState); + RefPtr controller = GetGeckoContentController(); + if (controller) { + MOZ_ASSERT(GetCurrentTouchBlock()); + if (mZoomConstraints.mAllowDoubleTapZoom && + GetCurrentTouchBlock()->TouchActionAllowsDoubleTapZoom()) { + if (Maybe geckoScreenPoint = + ConvertToGecko(aEvent.mPoint)) { + controller->HandleTap(TapType::eDoubleTap, *geckoScreenPoint, + aEvent.modifiers, GetGuid(), + GetCurrentTouchBlock()->GetBlockId()); + } + } + return nsEventStatus_eConsumeNoDefault; + } + return nsEventStatus_eIgnore; +} + +nsEventStatus AsyncPanZoomController::OnSecondTap( + const TapGestureInput& aEvent) { + APZC_LOG("%p got a second-tap in state %d\n", this, mState); + return GenerateSingleTap(TapType::eSecondTap, aEvent.mPoint, + aEvent.modifiers); +} + +nsEventStatus AsyncPanZoomController::OnCancelTap( + const TapGestureInput& aEvent) { + APZC_LOG("%p got a cancel-tap in state %d\n", this, mState); + // XXX: Implement this. + return nsEventStatus_eIgnore; +} + +ScreenToParentLayerMatrix4x4 AsyncPanZoomController::GetTransformToThis() + const { + if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) { + return treeManagerLocal->GetScreenToApzcTransform(this); + } + return ScreenToParentLayerMatrix4x4(); +} + +ScreenPoint AsyncPanZoomController::ToScreenCoordinates( + const ParentLayerPoint& aVector, const ParentLayerPoint& aAnchor) const { + return TransformVector(GetTransformToThis().Inverse(), aVector, aAnchor); +} + +// TODO: figure out a good way to check the w-coordinate is positive and return +// the result +ParentLayerPoint AsyncPanZoomController::ToParentLayerCoordinates( + const ScreenPoint& aVector, const ScreenPoint& aAnchor) const { + return TransformVector(GetTransformToThis(), aVector, aAnchor); +} + +ParentLayerPoint AsyncPanZoomController::ToParentLayerCoordinates( + const ScreenPoint& aVector, const ExternalPoint& aAnchor) const { + return ToParentLayerCoordinates( + aVector, + ViewAs(aAnchor, PixelCastJustification::ExternalIsScreen)); +} + +ExternalPoint AsyncPanZoomController::ToExternalPoint( + const ExternalPoint& aScreenOffset, const ScreenPoint& aScreenPoint) { + return aScreenOffset + + ViewAs(aScreenPoint, + PixelCastJustification::ExternalIsScreen); +} + +ScreenPoint AsyncPanZoomController::PanVector(const ExternalPoint& aPos) const { + return ScreenPoint(fabs(aPos.x - mStartTouch.x), + fabs(aPos.y - mStartTouch.y)); +} + +bool AsyncPanZoomController::Contains(const ScreenIntPoint& aPoint) const { + ScreenToParentLayerMatrix4x4 transformToThis = GetTransformToThis(); + Maybe point = UntransformBy(transformToThis, aPoint); + if (!point) { + return false; + } + + ParentLayerIntRect cb; + { + RecursiveMutexAutoLock lock(mRecursiveMutex); + GetFrameMetrics().GetCompositionBounds().ToIntRect(&cb); + } + return cb.Contains(*point); +} + +ParentLayerPoint AsyncPanZoomController::PanStart() const { + return ParentLayerPoint(mX.PanStart(), mY.PanStart()); +} + +const ParentLayerPoint AsyncPanZoomController::GetVelocityVector() const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + return ParentLayerPoint(mX.GetVelocity(), mY.GetVelocity()); +} + +void AsyncPanZoomController::SetVelocityVector( + const ParentLayerPoint& aVelocityVector) { + RecursiveMutexAutoLock lock(mRecursiveMutex); + mX.SetVelocity(aVelocityVector.x); + mY.SetVelocity(aVelocityVector.y); +} + +void AsyncPanZoomController::HandlePanningWithTouchAction(double aAngle) { + // Handling of cross sliding will need to be added in this method after + // touch-action released enabled by default. + MOZ_ASSERT(GetCurrentTouchBlock()); + RefPtr overscrollHandoffChain = + GetCurrentInputBlock()->GetOverscrollHandoffChain(); + bool canScrollHorizontal = + !mX.IsAxisLocked() && overscrollHandoffChain->CanScrollInDirection( + this, ScrollDirection::eHorizontal); + bool canScrollVertical = + !mY.IsAxisLocked() && overscrollHandoffChain->CanScrollInDirection( + this, ScrollDirection::eVertical); + if (GetCurrentTouchBlock()->TouchActionAllowsPanningXY()) { + if (canScrollHorizontal && canScrollVertical) { + if (apz::IsCloseToHorizontal(aAngle, + StaticPrefs::apz_axis_lock_lock_angle())) { + mY.SetAxisLocked(true); + SetState(PANNING_LOCKED_X); + } else if (apz::IsCloseToVertical( + aAngle, StaticPrefs::apz_axis_lock_lock_angle())) { + mX.SetAxisLocked(true); + SetState(PANNING_LOCKED_Y); + } else { + SetState(PANNING); + } + } else if (canScrollHorizontal || canScrollVertical) { + SetState(PANNING); + } else { + SetState(NOTHING); + } + } else if (GetCurrentTouchBlock()->TouchActionAllowsPanningX()) { + // Using bigger angle for panning to keep behavior consistent + // with IE. + if (apz::IsCloseToHorizontal( + aAngle, StaticPrefs::apz_axis_lock_direct_pan_angle())) { + mY.SetAxisLocked(true); + SetState(PANNING_LOCKED_X); + mPanDirRestricted = true; + } else { + // Don't treat these touches as pan/zoom movements since 'touch-action' + // value requires it. + SetState(NOTHING); + } + } else if (GetCurrentTouchBlock()->TouchActionAllowsPanningY()) { + if (apz::IsCloseToVertical(aAngle, + StaticPrefs::apz_axis_lock_direct_pan_angle())) { + mX.SetAxisLocked(true); + SetState(PANNING_LOCKED_Y); + mPanDirRestricted = true; + } else { + SetState(NOTHING); + } + } else { + SetState(NOTHING); + } + if (!IsInPanningState()) { + // If we didn't enter a panning state because touch-action disallowed it, + // make sure to clear any leftover velocity from the pre-threshold + // touchmoves. + mX.SetVelocity(0); + mY.SetVelocity(0); + } +} + +void AsyncPanZoomController::HandlePanning(double aAngle) { + RecursiveMutexAutoLock lock(mRecursiveMutex); + MOZ_ASSERT(GetCurrentInputBlock()); + RefPtr overscrollHandoffChain = + GetCurrentInputBlock()->GetOverscrollHandoffChain(); + bool canScrollHorizontal = + !mX.IsAxisLocked() && overscrollHandoffChain->CanScrollInDirection( + this, ScrollDirection::eHorizontal); + bool canScrollVertical = + !mY.IsAxisLocked() && overscrollHandoffChain->CanScrollInDirection( + this, ScrollDirection::eVertical); + + if (!canScrollHorizontal || !canScrollVertical) { + SetState(PANNING); + } else if (apz::IsCloseToHorizontal( + aAngle, StaticPrefs::apz_axis_lock_lock_angle())) { + mY.SetAxisLocked(true); + if (canScrollHorizontal) { + SetState(PANNING_LOCKED_X); + } + } else if (apz::IsCloseToVertical(aAngle, + StaticPrefs::apz_axis_lock_lock_angle())) { + mX.SetAxisLocked(true); + if (canScrollVertical) { + SetState(PANNING_LOCKED_Y); + } + } else { + SetState(PANNING); + } +} + +void AsyncPanZoomController::HandlePanningUpdate( + const ScreenPoint& aPanDistance) { + // If we're axis-locked, check if the user is trying to break the lock + if (GetAxisLockMode() == STICKY && !mPanDirRestricted) { + ParentLayerPoint vector = + ToParentLayerCoordinates(aPanDistance, mStartTouch); + + double angle = atan2(vector.y, vector.x); // range [-pi, pi] + angle = fabs(angle); // range [0, pi] + + float breakThreshold = + StaticPrefs::apz_axis_lock_breakout_threshold() * GetDPI(); + + if (fabs(aPanDistance.x) > breakThreshold || + fabs(aPanDistance.y) > breakThreshold) { + if (mState == PANNING_LOCKED_X) { + if (!apz::IsCloseToHorizontal( + angle, StaticPrefs::apz_axis_lock_breakout_angle())) { + mY.SetAxisLocked(false); + SetState(PANNING); + } + } else if (mState == PANNING_LOCKED_Y) { + if (!apz::IsCloseToVertical( + angle, StaticPrefs::apz_axis_lock_breakout_angle())) { + mX.SetAxisLocked(false); + SetState(PANNING); + } + } + } + } +} + +void AsyncPanZoomController::HandlePinchLocking( + const PinchGestureInput& aEvent) { + // Focus change and span distance calculated from an event buffer + // Used to handle pinch locking irrespective of touch screen sensitivity + // Note: both values fall back to the same value as + // their un-buffered counterparts if there is only one (the latest) + // event in the buffer. ie: when the touch screen is dispatching + // events slower than the lifetime of the buffer + ParentLayerCoord bufferedSpanDistance; + ParentLayerPoint focusPoint, bufferedFocusChange; + { + RecursiveMutexAutoLock lock(mRecursiveMutex); + + focusPoint = mPinchEventBuffer.back().mLocalFocusPoint - + Metrics().GetCompositionBounds().TopLeft(); + ParentLayerPoint bufferedLastZoomFocus = + (mPinchEventBuffer.size() > 1) + ? mPinchEventBuffer.front().mLocalFocusPoint - + Metrics().GetCompositionBounds().TopLeft() + : mLastZoomFocus; + + bufferedFocusChange = bufferedLastZoomFocus - focusPoint; + bufferedSpanDistance = fabsf(mPinchEventBuffer.front().mPreviousSpan - + mPinchEventBuffer.back().mCurrentSpan); + } + + // Convert to screen coordinates + ScreenCoord spanDistance = + ToScreenCoordinates(ParentLayerPoint(0, bufferedSpanDistance), focusPoint) + .Length(); + ScreenPoint focusChange = + ToScreenCoordinates(bufferedFocusChange, focusPoint); + + if (mPinchLocked) { + if (GetPinchLockMode() == PINCH_STICKY) { + ScreenCoord spanBreakoutThreshold = + StaticPrefs::apz_pinch_lock_span_breakout_threshold() * GetDPI(); + mPinchLocked = !(spanDistance > spanBreakoutThreshold); + } + } else { + if (GetPinchLockMode() != PINCH_FREE) { + ScreenCoord spanLockThreshold = + StaticPrefs::apz_pinch_lock_span_lock_threshold() * GetDPI(); + ScreenCoord scrollLockThreshold = + StaticPrefs::apz_pinch_lock_scroll_lock_threshold() * GetDPI(); + + if (spanDistance < spanLockThreshold && + focusChange.Length() > scrollLockThreshold) { + mPinchLocked = true; + + // We are transitioning to a two-finger pan that could trigger + // a fling at its end, so start tracking velocity. + StartTouch(aEvent.mLocalFocusPoint, aEvent.mTimeStamp); + } + } + } +} + +nsEventStatus AsyncPanZoomController::StartPanning( + const ExternalPoint& aStartPoint, const TimeStamp& aEventTime) { + ParentLayerPoint vector = + ToParentLayerCoordinates(PanVector(aStartPoint), mStartTouch); + double angle = atan2(vector.y, vector.x); // range [-pi, pi] + angle = fabs(angle); // range [0, pi] + + RecursiveMutexAutoLock lock(mRecursiveMutex); + if (StaticPrefs::layout_css_touch_action_enabled()) { + HandlePanningWithTouchAction(angle); + } else { + if (GetAxisLockMode() == FREE) { + SetState(PANNING); + } else { + HandlePanning(angle); + } + } + + if (IsInPanningState()) { + mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS, + (uint32_t)ScrollInputMethod::ApzTouch); + mTouchStartRestingTimeBeforePan = aEventTime - mTouchStartTime; + mMinimumVelocityDuringPan = Nothing(); + + if (RefPtr controller = + GetGeckoContentController()) { + controller->NotifyAPZStateChange(GetGuid(), + APZStateChange::eStartPanning); + } + return nsEventStatus_eConsumeNoDefault; + } + // Don't consume an event that didn't trigger a panning. + return nsEventStatus_eIgnore; +} + +void AsyncPanZoomController::UpdateWithTouchAtDevicePoint( + const MultiTouchInput& aEvent) { + const SingleTouchData& touchData = aEvent.mTouches[0]; + // Take historical touch data into account in order to improve the accuracy + // of the velocity estimate. On many Android devices, the touch screen samples + // at a higher rate than vsync (e.g. 100Hz vs 60Hz), and the historical data + // lets us take advantage of those high-rate samples. + for (const auto& historicalData : touchData.mHistoricalData) { + ParentLayerPoint historicalPoint = historicalData.mLocalScreenPoint; + mX.UpdateWithTouchAtDevicePoint(historicalPoint.x, + historicalData.mTimeStamp); + mY.UpdateWithTouchAtDevicePoint(historicalPoint.y, + historicalData.mTimeStamp); + } + ParentLayerPoint point = touchData.mLocalScreenPoint; + mX.UpdateWithTouchAtDevicePoint(point.x, aEvent.mTimeStamp); + mY.UpdateWithTouchAtDevicePoint(point.y, aEvent.mTimeStamp); +} + +Maybe AsyncPanZoomController::NotifyScrollSampling() { + RecursiveMutexAutoLock lock(mRecursiveMutex); + return mSampledState.front().TakeScrollPayload(); +} + +bool AsyncPanZoomController::AttemptScroll( + ParentLayerPoint& aStartPoint, ParentLayerPoint& aEndPoint, + OverscrollHandoffState& aOverscrollHandoffState) { + // "start - end" rather than "end - start" because e.g. moving your finger + // down (*positive* direction along y axis) causes the vertical scroll offset + // to *decrease* as the page follows your finger. + ParentLayerPoint displacement = aStartPoint - aEndPoint; + + ParentLayerPoint overscroll; // will be used outside monitor block + + // If the direction of panning is reversed within the same input block, + // a later event in the block could potentially scroll an APZC earlier + // in the handoff chain, than an earlier event in the block (because + // the earlier APZC was scrolled to its extent in the original direction). + // We want to disallow this. + bool scrollThisApzc = false; + if (InputBlockState* block = GetCurrentInputBlock()) { + scrollThisApzc = + !block->GetScrolledApzc() || block->IsDownchainOfScrolledApzc(this); + } + + if (scrollThisApzc) { + RecursiveMutexAutoLock lock(mRecursiveMutex); + bool forcesVerticalOverscroll = + ScrollSource::Wheel == aOverscrollHandoffState.mScrollSource && + mScrollMetadata.GetDisregardedDirection() == + Some(ScrollDirection::eVertical); + bool forcesHorizontalOverscroll = + ScrollSource::Wheel == aOverscrollHandoffState.mScrollSource && + mScrollMetadata.GetDisregardedDirection() == + Some(ScrollDirection::eHorizontal); + + ParentLayerPoint adjustedDisplacement; + bool yChanged = + mY.AdjustDisplacement(displacement.y, adjustedDisplacement.y, + overscroll.y, forcesVerticalOverscroll); + bool xChanged = + mX.AdjustDisplacement(displacement.x, adjustedDisplacement.x, + overscroll.x, forcesHorizontalOverscroll); + if (xChanged || yChanged) { + ScheduleComposite(); + } + + if (!IsZero(adjustedDisplacement) && + Metrics().GetZoom() != CSSToParentLayerScale2D(0, 0)) { + ScrollBy(adjustedDisplacement / Metrics().GetZoom()); + if (InputBlockState* block = GetCurrentInputBlock()) { + bool displacementIsUserVisible = true; + + { // Release the APZC lock before calling ToScreenCoordinates which + // acquires the APZ tree lock. Note that this just unlocks the mutex + // once, so if we're locking it multiple times on the callstack then + // this will be insufficient. + RecursiveMutexAutoUnlock unlock(mRecursiveMutex); + + ScreenIntPoint screenDisplacement = RoundedToInt( + ToScreenCoordinates(adjustedDisplacement, aStartPoint)); + // If the displacement we just applied rounds to zero in screen space, + // then it's probably not going to be visible to the user. In that + // case let's not mark this APZC as scrolled, so that even if the + // immediate handoff pref is disabled, we'll allow doing the handoff + // to the next APZC. + if (screenDisplacement == ScreenIntPoint()) { + displacementIsUserVisible = false; + } + } + if (displacementIsUserVisible) { + block->SetScrolledApzc(this); + } + } + ScheduleCompositeAndMaybeRepaint(); + UpdateSharedCompositorFrameMetrics(); + } + + // Adjust the start point to reflect the consumed portion of the scroll. + aStartPoint = aEndPoint + overscroll; + } else { + overscroll = displacement; + } + + // If we consumed the entire displacement as a normal scroll, great. + if (IsZero(overscroll)) { + return true; + } + + if (AllowScrollHandoffInCurrentBlock()) { + // If there is overscroll, first try to hand it off to an APZC later + // in the handoff chain to consume (either as a normal scroll or as + // overscroll). + // Note: "+ overscroll" rather than "- overscroll" because "overscroll" + // is what's left of "displacement", and "displacement" is "start - end". + ++aOverscrollHandoffState.mChainIndex; + bool consumed = + CallDispatchScroll(aStartPoint, aEndPoint, aOverscrollHandoffState); + if (consumed) { + return true; + } + + overscroll = aStartPoint - aEndPoint; + MOZ_ASSERT(!IsZero(overscroll)); + } + + // If there is no APZC later in the handoff chain that accepted the + // overscroll, try to accept it ourselves. We only accept it if we + // are pannable. + APZC_LOG("%p taking overscroll during panning\n", this); + OverscrollForPanning(overscroll, aOverscrollHandoffState.mPanDistance); + aStartPoint = aEndPoint + overscroll; + + return IsZero(overscroll); +} + +void AsyncPanZoomController::OverscrollForPanning( + ParentLayerPoint& aOverscroll, const ScreenPoint& aPanDistance) { + // Only allow entering overscroll along an axis if the pan distance along + // that axis is greater than the pan distance along the other axis by a + // configurable factor. If we are already overscrolled, don't check this. + if (!IsOverscrolled()) { + if (aPanDistance.x < + StaticPrefs::apz_overscroll_min_pan_distance_ratio() * aPanDistance.y) { + aOverscroll.x = 0; + } + if (aPanDistance.y < + StaticPrefs::apz_overscroll_min_pan_distance_ratio() * aPanDistance.x) { + aOverscroll.y = 0; + } + } + + OverscrollBy(aOverscroll); +} + +void AsyncPanZoomController::OverscrollBy(ParentLayerPoint& aOverscroll) { + if (!StaticPrefs::apz_overscroll_enabled()) { + return; + } + + RecursiveMutexAutoLock lock(mRecursiveMutex); + // Do not go into overscroll in a direction in which we have no room to + // scroll to begin with. + bool xCanScroll = mX.CanScroll(); + bool yCanScroll = mY.CanScroll(); + bool xConsumed = FuzzyEqualsAdditive(aOverscroll.x, 0.0f, COORDINATE_EPSILON); + bool yConsumed = FuzzyEqualsAdditive(aOverscroll.y, 0.0f, COORDINATE_EPSILON); + + bool shouldOverscrollX = + xCanScroll && !xConsumed && mX.OverscrollBehaviorAllowsOverscrollEffect(); + bool shouldOverscrollY = + yCanScroll && !yConsumed && mY.OverscrollBehaviorAllowsOverscrollEffect(); + + mOverscrollEffect->ConsumeOverscroll(aOverscroll, shouldOverscrollX, + shouldOverscrollY); +} + +RefPtr +AsyncPanZoomController::BuildOverscrollHandoffChain() { + if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) { + return treeManagerLocal->BuildOverscrollHandoffChain(this); + } + + // This APZC IsDestroyed(). To avoid callers having to special-case this + // scenario, just build a 1-element chain containing ourselves. + OverscrollHandoffChain* result = new OverscrollHandoffChain; + result->Add(this); + return result; +} + +ParentLayerPoint AsyncPanZoomController::AttemptFling( + const FlingHandoffState& aHandoffState) { + // The PLPPI computation acquires the tree lock, so it needs to be performed + // on the controller thread, and before the APZC lock is acquired. + APZThreadUtils::AssertOnControllerThread(); + float PLPPI = ComputePLPPI(PanStart(), aHandoffState.mVelocity); + + RecursiveMutexAutoLock lock(mRecursiveMutex); + + if (!IsPannable()) { + return aHandoffState.mVelocity; + } + + // We may have a pre-existing velocity for whatever reason (for example, + // a previously handed off fling). We don't want to clobber that. + APZC_LOG("%p accepting fling with velocity %s\n", this, + ToString(aHandoffState.mVelocity).c_str()); + ParentLayerPoint residualVelocity = aHandoffState.mVelocity; + if (mX.CanScroll()) { + mX.SetVelocity(mX.GetVelocity() + aHandoffState.mVelocity.x); + residualVelocity.x = 0; + } + if (mY.CanScroll()) { + mY.SetVelocity(mY.GetVelocity() + aHandoffState.mVelocity.y); + residualVelocity.y = 0; + } + + // If we're not scrollable in at least one of the directions in which we + // were handed velocity, don't start a fling animation. + // The |IsFinite()| condition should only fail when running some tests + // that generate events faster than the clock resolution. + ParentLayerPoint velocity = GetVelocityVector(); + if (!velocity.IsFinite() || + velocity.Length() <= StaticPrefs::apz_fling_min_velocity_threshold()) { + // Relieve overscroll now if needed, since we will not transition to a fling + // animation and then an overscroll animation, and relieve it then. + aHandoffState.mChain->SnapBackOverscrolledApzc(this); + return residualVelocity; + } + + // If there's a scroll snap point near the predicted fling destination, + // scroll there using a smooth scroll animation. Otherwise, start a + // fling animation. + ScrollSnapToDestination(); + if (mState != SMOOTHMSD_SCROLL) { + SetState(FLING); + AsyncPanZoomAnimation* fling = + GetPlatformSpecificState()->CreateFlingAnimation(*this, aHandoffState, + PLPPI); + StartAnimation(fling); + } + + return residualVelocity; +} + +float AsyncPanZoomController::ComputePLPPI(ParentLayerPoint aPoint, + ParentLayerPoint aDirection) const { + // Avoid division-by-zero. + if (aDirection == ParentLayerPoint()) { + return GetDPI(); + } + + // Convert |aDirection| into a unit vector. + aDirection = aDirection / aDirection.Length(); + + // Place the vector at |aPoint| and convert to screen coordinates. + // The length of the resulting vector is the number of Screen coordinates + // that equal 1 ParentLayer coordinate in the given direction. + float screenPerParent = ToScreenCoordinates(aDirection, aPoint).Length(); + + // Finally, factor in the DPI scale. + return GetDPI() / screenPerParent; +} + +Maybe AsyncPanZoomController::GetCurrentAnimationDestination( + const RecursiveMutexAutoLock& aProofOfLock) const { + if (mState == WHEEL_SCROLL) { + return Some(mAnimation->AsWheelScrollAnimation()->GetDestination()); + } + if (mState == SMOOTH_SCROLL) { + return Some(mAnimation->AsSmoothScrollAnimation()->GetDestination()); + } + if (mState == SMOOTHMSD_SCROLL) { + return Some(mAnimation->AsSmoothMsdScrollAnimation()->GetDestination()); + } + if (mState == KEYBOARD_SCROLL) { + return Some(mAnimation->AsSmoothScrollAnimation()->GetDestination()); + } + + return Nothing(); +} + +ParentLayerPoint +AsyncPanZoomController::AdjustHandoffVelocityForOverscrollBehavior( + ParentLayerPoint& aHandoffVelocity) const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + ParentLayerPoint residualVelocity; + if (!mX.OverscrollBehaviorAllowsHandoff()) { + residualVelocity.x = aHandoffVelocity.x; + aHandoffVelocity.x = 0; + } + if (!mY.OverscrollBehaviorAllowsHandoff()) { + residualVelocity.y = aHandoffVelocity.y; + aHandoffVelocity.y = 0; + } + return residualVelocity; +} + +bool AsyncPanZoomController::OverscrollBehaviorAllowsSwipe() const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + // Swipe navigation is a "non-local" overscroll behavior like handoff. + return mX.OverscrollBehaviorAllowsHandoff(); +} + +void AsyncPanZoomController::HandleFlingOverscroll( + const ParentLayerPoint& aVelocity, + const RefPtr& aOverscrollHandoffChain, + const RefPtr& aScrolledApzc) { + APZCTreeManager* treeManagerLocal = GetApzcTreeManager(); + if (treeManagerLocal) { + const FlingHandoffState handoffState{ + aVelocity, aOverscrollHandoffChain, Nothing(), + 0, true /* handoff */, aScrolledApzc}; + ParentLayerPoint residualVelocity = + treeManagerLocal->DispatchFling(this, handoffState); + FLING_LOG("APZC %p left with residual velocity %s\n", this, + ToString(residualVelocity).c_str()); + if (!IsZero(residualVelocity) && IsPannable() && + StaticPrefs::apz_overscroll_enabled()) { + // Obey overscroll-behavior. + RecursiveMutexAutoLock lock(mRecursiveMutex); + if (!mX.OverscrollBehaviorAllowsOverscrollEffect()) { + residualVelocity.x = 0; + } + if (!mY.OverscrollBehaviorAllowsOverscrollEffect()) { + residualVelocity.y = 0; + } + + if (!IsZero(residualVelocity)) { + mOverscrollEffect->HandleFlingOverscroll(residualVelocity); + } + } + } +} + +void AsyncPanZoomController::HandleSmoothScrollOverscroll( + const ParentLayerPoint& aVelocity) { + // We must call BuildOverscrollHandoffChain from this deferred callback + // function in order to avoid a deadlock when acquiring the tree lock. + HandleFlingOverscroll(aVelocity, BuildOverscrollHandoffChain(), nullptr); +} + +void AsyncPanZoomController::SmoothScrollTo(const CSSPoint& aDestination, + const ScrollOrigin& aOrigin) { + // Convert velocity from ParentLayerPoints/ms to ParentLayerPoints/s and then + // to appunits/second. + nsPoint destination = CSSPoint::ToAppUnits(aDestination); + nsSize velocity; + if (Metrics().GetZoom() != CSSToParentLayerScale2D(0, 0)) { + velocity = CSSSize::ToAppUnits(ParentLayerSize(mX.GetVelocity() * 1000.0f, + mY.GetVelocity() * 1000.0f) / + Metrics().GetZoom()); + } + + if (mState == SMOOTH_SCROLL && mAnimation) { + RefPtr animation( + mAnimation->AsSmoothScrollAnimation()); + if (animation->GetScrollOrigin() == aOrigin) { + APZC_LOG("%p updating destination on existing animation\n", this); + animation->UpdateDestination(GetFrameTime().Time(), destination, + velocity); + return; + } + } + + CancelAnimation(); + SetState(SMOOTH_SCROLL); + nsPoint initialPosition = + CSSPoint::ToAppUnits(Metrics().GetVisualScrollOffset()); + RefPtr animation = + new SmoothScrollAnimation(*this, initialPosition, aOrigin); + animation->UpdateDestination(GetFrameTime().Time(), destination, velocity); + StartAnimation(animation.get()); +} + +void AsyncPanZoomController::SmoothMsdScrollTo(const CSSPoint& aDestination) { + if (mState == SMOOTHMSD_SCROLL && mAnimation) { + APZC_LOG("%p updating destination on existing animation\n", this); + RefPtr animation( + static_cast(mAnimation.get())); + animation->SetDestination(aDestination); + } else { + CancelAnimation(); + SetState(SMOOTHMSD_SCROLL); + // Convert velocity from ParentLayerPoints/ms to ParentLayerPoints/s. + CSSPoint initialVelocity; + if (Metrics().GetZoom() != CSSToParentLayerScale2D(0, 0)) { + initialVelocity = ParentLayerPoint(mX.GetVelocity() * 1000.0f, + mY.GetVelocity() * 1000.0f) / + Metrics().GetZoom(); + } + + StartAnimation(new SmoothMsdScrollAnimation( + *this, Metrics().GetVisualScrollOffset(), initialVelocity, aDestination, + StaticPrefs::layout_css_scroll_behavior_spring_constant(), + StaticPrefs::layout_css_scroll_behavior_damping_ratio())); + } +} + +void AsyncPanZoomController::StartOverscrollAnimation( + const ParentLayerPoint& aVelocity) { + SetState(OVERSCROLL_ANIMATION); + StartAnimation(new OverscrollAnimation(*this, aVelocity)); +} + +bool AsyncPanZoomController::CallDispatchScroll( + ParentLayerPoint& aStartPoint, ParentLayerPoint& aEndPoint, + OverscrollHandoffState& aOverscrollHandoffState) { + // Make a local copy of the tree manager pointer and check if it's not + // null before calling DispatchScroll(). This is necessary because + // Destroy(), which nulls out mTreeManager, could be called concurrently. + APZCTreeManager* treeManagerLocal = GetApzcTreeManager(); + if (!treeManagerLocal) { + return false; + } + + // Obey overscroll-behavior. + ParentLayerPoint endPoint = aEndPoint; + if (aOverscrollHandoffState.mChainIndex > 0) { + RecursiveMutexAutoLock lock(mRecursiveMutex); + if (!mX.OverscrollBehaviorAllowsHandoff()) { + endPoint.x = aStartPoint.x; + } + if (!mY.OverscrollBehaviorAllowsHandoff()) { + endPoint.y = aStartPoint.y; + } + if (aStartPoint == endPoint) { + // Handoff not allowed in either direction - don't even bother. + return false; + } + } + + return treeManagerLocal->DispatchScroll(this, aStartPoint, endPoint, + aOverscrollHandoffState); +} + +void AsyncPanZoomController::RecordScrollPayload(const TimeStamp& aTimeStamp) { + RecursiveMutexAutoLock lock(mRecursiveMutex); + if (!mScrollPayload) { + mScrollPayload = Some( + CompositionPayload{CompositionPayloadType::eAPZScroll, aTimeStamp}); + } +} + +void AsyncPanZoomController::StartTouch(const ParentLayerPoint& aPoint, + TimeStamp aTimestamp) { + RecursiveMutexAutoLock lock(mRecursiveMutex); + mX.StartTouch(aPoint.x, aTimestamp); + mY.StartTouch(aPoint.y, aTimestamp); +} + +void AsyncPanZoomController::EndTouch(TimeStamp aTimestamp) { + RecursiveMutexAutoLock lock(mRecursiveMutex); + mX.EndTouch(aTimestamp); + mY.EndTouch(aTimestamp); +} + +void AsyncPanZoomController::TrackTouch(const MultiTouchInput& aEvent) { + ExternalPoint extPoint = GetFirstExternalTouchPoint(aEvent); + ScreenPoint panVector = PanVector(extPoint); + HandlePanningUpdate(panVector); + + ParentLayerPoint prevTouchPoint(mX.GetPos(), mY.GetPos()); + ParentLayerPoint touchPoint = GetFirstTouchPoint(aEvent); + + UpdateWithTouchAtDevicePoint(aEvent); + + auto velocity = GetVelocityVector().Length(); + if (mMinimumVelocityDuringPan) { + mMinimumVelocityDuringPan = + Some(std::min(*mMinimumVelocityDuringPan, velocity)); + } else { + mMinimumVelocityDuringPan = Some(velocity); + } + + if (prevTouchPoint != touchPoint) { + MOZ_ASSERT(GetCurrentTouchBlock()); + OverscrollHandoffState handoffState( + *GetCurrentTouchBlock()->GetOverscrollHandoffChain(), panVector, + ScrollSource::Touch); + RecordScrollPayload(aEvent.mTimeStamp); + CallDispatchScroll(prevTouchPoint, touchPoint, handoffState); + } +} + +ParentLayerPoint AsyncPanZoomController::GetFirstTouchPoint( + const MultiTouchInput& aEvent) { + return ((SingleTouchData&)aEvent.mTouches[0]).mLocalScreenPoint; +} + +ExternalPoint AsyncPanZoomController::GetFirstExternalTouchPoint( + const MultiTouchInput& aEvent) { + return ToExternalPoint(aEvent.mScreenOffset, + ((SingleTouchData&)aEvent.mTouches[0]).mScreenPoint); +} + +void AsyncPanZoomController::StartAnimation(AsyncPanZoomAnimation* aAnimation) { + RecursiveMutexAutoLock lock(mRecursiveMutex); + mAnimation = aAnimation; + mLastSampleTime = GetFrameTime(); + ScheduleComposite(); +} + +void AsyncPanZoomController::CancelAnimation(CancelAnimationFlags aFlags) { + RecursiveMutexAutoLock lock(mRecursiveMutex); + APZC_LOG("%p running CancelAnimation(0x%x) in state %d\n", this, aFlags, + mState); + + if ((aFlags & ExcludeWheel) && mState == WHEEL_SCROLL) { + return; + } + + if (mAnimation) { + mAnimation->Cancel(aFlags); + } + + SetState(NOTHING); + mAnimation = nullptr; + // Since there is no animation in progress now the axes should + // have no velocity either. If we are dropping the velocity from a non-zero + // value we should trigger a repaint as the displayport margins are dependent + // on the velocity and the last repaint request might not have good margins + // any more. + bool repaint = !IsZero(GetVelocityVector()); + mX.SetVelocity(0); + mY.SetVelocity(0); + mX.SetAxisLocked(false); + mY.SetAxisLocked(false); + // Setting the state to nothing and cancelling the animation can + // preempt normal mechanisms for relieving overscroll, so we need to clear + // overscroll here. + if (!(aFlags & ExcludeOverscroll) && IsOverscrolled()) { + ClearOverscroll(); + repaint = true; + } + // Similar to relieving overscroll, we also need to snap to any snap points + // if appropriate. + if (aFlags & CancelAnimationFlags::ScrollSnap) { + ScrollSnap(); + } + if (repaint) { + RequestContentRepaint(); + ScheduleComposite(); + UpdateSharedCompositorFrameMetrics(); + } +} + +void AsyncPanZoomController::ClearOverscroll() { + RecursiveMutexAutoLock lock(mRecursiveMutex); + mX.ClearOverscroll(); + mY.ClearOverscroll(); +} + +void AsyncPanZoomController::SetCompositorController( + CompositorController* aCompositorController) { + mCompositorController = aCompositorController; +} + +void AsyncPanZoomController::SetMetricsSharingController( + MetricsSharingController* aMetricsSharingController) { + mMetricsSharingController = aMetricsSharingController; +} + +void AsyncPanZoomController::SetVisualScrollOffset(const CSSPoint& aOffset) { + Metrics().SetVisualScrollOffset(aOffset); + Metrics().RecalculateLayoutViewportOffset(); +} + +void AsyncPanZoomController::ClampAndSetVisualScrollOffset( + const CSSPoint& aOffset) { + Metrics().ClampAndSetVisualScrollOffset(aOffset); + Metrics().RecalculateLayoutViewportOffset(); +} + +void AsyncPanZoomController::ScrollBy(const CSSPoint& aOffset) { + SetVisualScrollOffset(Metrics().GetVisualScrollOffset() + aOffset); +} + +void AsyncPanZoomController::ScrollByAndClamp(const CSSPoint& aOffset) { + ClampAndSetVisualScrollOffset(Metrics().GetVisualScrollOffset() + aOffset); +} + +void AsyncPanZoomController::ScaleWithFocus(float aScale, + const CSSPoint& aFocus) { + Metrics().ZoomBy(aScale); + // We want to adjust the scroll offset such that the CSS point represented by + // aFocus remains at the same position on the screen before and after the + // change in zoom. The below code accomplishes this; see + // https://bugzilla.mozilla.org/show_bug.cgi?id=923431#c6 for an in-depth + // explanation of how. + SetVisualScrollOffset((Metrics().GetVisualScrollOffset() + aFocus) - + (aFocus / aScale)); +} + +/*static*/ +gfx::IntSize AsyncPanZoomController::GetDisplayportAlignmentMultiplier( + const ScreenSize& aBaseSize) { + MOZ_ASSERT(gfx::gfxVars::UseWebRender()); + + gfx::IntSize multiplier(1, 1); + float baseWidth = aBaseSize.width; + while (baseWidth > 500) { + baseWidth /= 2; + multiplier.width *= 2; + if (multiplier.width >= 8) { + break; + } + } + float baseHeight = aBaseSize.height; + while (baseHeight > 500) { + baseHeight /= 2; + multiplier.height *= 2; + if (multiplier.height >= 8) { + break; + } + } + return multiplier; +} + +/** + * Enlarges the displayport along both axes based on the velocity. + */ +static CSSSize CalculateDisplayPortSize( + const CSSSize& aCompositionSize, const CSSPoint& aVelocity, + AsyncPanZoomController::ZoomInProgress aZoomInProgress, + const CSSToScreenScale2D& aDpPerCSS) { + bool xIsStationarySpeed = + fabsf(aVelocity.x) < StaticPrefs::apz_min_skate_speed(); + bool yIsStationarySpeed = + fabsf(aVelocity.y) < StaticPrefs::apz_min_skate_speed(); + float xMultiplier = xIsStationarySpeed + ? StaticPrefs::apz_x_stationary_size_multiplier() + : StaticPrefs::apz_x_skate_size_multiplier(); + float yMultiplier = yIsStationarySpeed + ? StaticPrefs::apz_y_stationary_size_multiplier() + : StaticPrefs::apz_y_skate_size_multiplier(); + + if (IsHighMemSystem() && !xIsStationarySpeed) { + xMultiplier += StaticPrefs::apz_x_skate_highmem_adjust(); + } + + if (IsHighMemSystem() && !yIsStationarySpeed) { + yMultiplier += StaticPrefs::apz_y_skate_highmem_adjust(); + } + + if (aZoomInProgress == AsyncPanZoomController::ZoomInProgress::Yes) { + // If a zoom is in progress, we will be making content visible on the + // x and y axes in equal proportion, because the zoom operation scales + // equally on the x and y axes. The default multipliers computed above are + // biased towards the y-axis since that's where most scrolling occurs, but + // in the case of zooming, we should really use equal multipliers on both + // axes. This does that while preserving the total displayport area + // quantity (aCompositionSize.Area() * xMultiplier * yMultiplier). + // Note that normally changing the shape of the displayport is expensive + // and should be avoided, but if a zoom is in progress the displayport + // is likely going to be fully repainted anyway due to changes in resolution + // so there should be no marginal cost to also changing the shape of it. + float areaMultiplier = xMultiplier * yMultiplier; + xMultiplier = sqrt(areaMultiplier); + yMultiplier = xMultiplier; + } + + if (gfx::gfxVars::UseWebRender()) { + // Scale down the margin multipliers by the alignment multiplier because + // the alignment code will expand the displayport outward to the multiplied + // alignment. This is not necessary for correctness, but for performance; + // if we don't do this the displayport can end up much larger. The math here + // is actually just scaling the part of the multipler that is > 1, so that + // we never end up with xMultiplier or yMultiplier being less than 1 (that + // would result in a guaranteed checkerboarding situation). Note that the + // calculation doesn't cancel exactly the increased margin from applying + // the alignment multiplier, but this is simple and should provide + // reasonable behaviour in most cases. + gfx::IntSize alignmentMultipler = + AsyncPanZoomController::GetDisplayportAlignmentMultiplier( + aCompositionSize * aDpPerCSS); + if (xMultiplier > 1) { + xMultiplier = ((xMultiplier - 1) / alignmentMultipler.width) + 1; + } + if (yMultiplier > 1) { + yMultiplier = ((yMultiplier - 1) / alignmentMultipler.height) + 1; + } + } + + return aCompositionSize * CSSSize(xMultiplier, yMultiplier); +} + +/** + * Ensures that the displayport is at least as large as the visible area + * inflated by the danger zone. If this is not the case then the + * "AboutToCheckerboard" function in TiledContentClient.cpp will return true + * even in the stable state. + */ +static CSSSize ExpandDisplayPortToDangerZone( + const CSSSize& aDisplayPortSize, const FrameMetrics& aFrameMetrics) { + CSSSize dangerZone(0.0f, 0.0f); + if (aFrameMetrics.LayersPixelsPerCSSPixel().xScale != 0 && + aFrameMetrics.LayersPixelsPerCSSPixel().yScale != 0) { + dangerZone = LayerSize(StaticPrefs::apz_danger_zone_x(), + StaticPrefs::apz_danger_zone_y()) / + aFrameMetrics.LayersPixelsPerCSSPixel(); + } + const CSSSize compositionSize = + aFrameMetrics.CalculateBoundedCompositedSizeInCssPixels(); + + const float xSize = std::max(aDisplayPortSize.width, + compositionSize.width + (2 * dangerZone.width)); + + const float ySize = + std::max(aDisplayPortSize.height, + compositionSize.height + (2 * dangerZone.height)); + + return CSSSize(xSize, ySize); +} + +/** + * Attempts to redistribute any area in the displayport that would get clipped + * by the scrollable rect, or be inaccessible due to disabled scrolling, to the + * other axis, while maintaining total displayport area. + */ +static void RedistributeDisplayPortExcess(CSSSize& aDisplayPortSize, + const CSSRect& aScrollableRect) { + // As aDisplayPortSize.height * aDisplayPortSize.width does not change, + // we are just scaling by the ratio and its inverse. + if (aDisplayPortSize.height > aScrollableRect.Height()) { + aDisplayPortSize.width *= + (aDisplayPortSize.height / aScrollableRect.Height()); + aDisplayPortSize.height = aScrollableRect.Height(); + } else if (aDisplayPortSize.width > aScrollableRect.Width()) { + aDisplayPortSize.height *= + (aDisplayPortSize.width / aScrollableRect.Width()); + aDisplayPortSize.width = aScrollableRect.Width(); + } +} + +/* static */ +const ScreenMargin AsyncPanZoomController::CalculatePendingDisplayPort( + const FrameMetrics& aFrameMetrics, const ParentLayerPoint& aVelocity, + ZoomInProgress aZoomInProgress) { + if (aFrameMetrics.IsScrollInfoLayer()) { + // Don't compute margins. Since we can't asynchronously scroll this frame, + // we don't want to paint anything more than the composition bounds. + return ScreenMargin(); + } + + CSSSize compositionSize = + aFrameMetrics.CalculateBoundedCompositedSizeInCssPixels(); + CSSPoint velocity; + if (aFrameMetrics.GetZoom() != CSSToParentLayerScale2D(0, 0)) { + velocity = aVelocity / aFrameMetrics.GetZoom(); // avoid division by zero + } + CSSRect scrollableRect = aFrameMetrics.GetExpandedScrollableRect(); + + // Calculate the displayport size based on how fast we're moving along each + // axis. + CSSSize displayPortSize = + CalculateDisplayPortSize(compositionSize, velocity, aZoomInProgress, + aFrameMetrics.DisplayportPixelsPerCSSPixel()); + + displayPortSize = + ExpandDisplayPortToDangerZone(displayPortSize, aFrameMetrics); + + if (StaticPrefs::apz_enlarge_displayport_when_clipped()) { + RedistributeDisplayPortExcess(displayPortSize, scrollableRect); + } + + // We calculate a "displayport" here which is relative to the scroll offset. + // Note that the scroll offset we have here in the APZ code may not be the + // same as the base rect that gets used on the layout side when the + // displayport margins are actually applied, so it is important to only + // consider the displayport as margins relative to a scroll offset rather than + // relative to something more unchanging like the scrollable rect origin. + + // Center the displayport based on its expansion over the composition size. + CSSRect displayPort((compositionSize.width - displayPortSize.width) / 2.0f, + (compositionSize.height - displayPortSize.height) / 2.0f, + displayPortSize.width, displayPortSize.height); + + // Offset the displayport, depending on how fast we're moving and the + // estimated time it takes to paint, to try to minimise checkerboarding. + float paintFactor = kDefaultEstimatedPaintDurationMs; + displayPort.MoveBy(velocity * paintFactor * StaticPrefs::apz_velocity_bias()); + + APZC_LOGV_FM(aFrameMetrics, + "Calculated displayport as %s from velocity %s zooming %d paint " + "time %f metrics", + ToString(displayPort).c_str(), ToString(aVelocity).c_str(), + (int)aZoomInProgress, paintFactor); + + CSSMargin cssMargins; + cssMargins.left = -displayPort.X(); + cssMargins.top = -displayPort.Y(); + cssMargins.right = + displayPort.Width() - compositionSize.width - cssMargins.left; + cssMargins.bottom = + displayPort.Height() - compositionSize.height - cssMargins.top; + + return cssMargins * aFrameMetrics.DisplayportPixelsPerCSSPixel(); +} + +void AsyncPanZoomController::ScheduleComposite() { + if (mCompositorController) { + mCompositorController->ScheduleRenderOnCompositorThread(); + } +} + +void AsyncPanZoomController::ScheduleCompositeAndMaybeRepaint() { + ScheduleComposite(); + RequestContentRepaint(); +} + +void AsyncPanZoomController::FlushRepaintForOverscrollHandoff() { + RecursiveMutexAutoLock lock(mRecursiveMutex); + RequestContentRepaint(); + UpdateSharedCompositorFrameMetrics(); +} + +void AsyncPanZoomController::FlushRepaintForNewInputBlock() { + APZC_LOG("%p flushing repaint for new input block\n", this); + + RecursiveMutexAutoLock lock(mRecursiveMutex); + RequestContentRepaint(); + UpdateSharedCompositorFrameMetrics(); +} + +bool AsyncPanZoomController::SnapBackIfOverscrolled() { + RecursiveMutexAutoLock lock(mRecursiveMutex); + // It's possible that we're already in the middle of an overscroll + // animation - if so, don't start a new one. + if (IsOverscrolled() && mState != OVERSCROLL_ANIMATION) { + APZC_LOG("%p is overscrolled, starting snap-back\n", this); + StartOverscrollAnimation(ParentLayerPoint(0, 0)); + return true; + } + // If we don't kick off an overscroll animation, we still need to ask the + // main thread to snap to any nearby snap points, assuming we haven't already + // done so when we started this fling + if (mState != FLING) { + ScrollSnap(); + } + return false; +} + +bool AsyncPanZoomController::IsFlingingFast() const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + if (mState == FLING && GetVelocityVector().Length() > + StaticPrefs::apz_fling_stop_on_tap_threshold()) { + APZC_LOG("%p is moving fast\n", this); + return true; + } + return false; +} + +bool AsyncPanZoomController::IsPannable() const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + return mX.CanScroll() || mY.CanScroll(); +} + +bool AsyncPanZoomController::IsScrollInfoLayer() const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + return Metrics().IsScrollInfoLayer(); +} + +int32_t AsyncPanZoomController::GetLastTouchIdentifier() const { + RefPtr listener = GetGestureEventListener(); + return listener ? listener->GetLastTouchIdentifier() : -1; +} + +void AsyncPanZoomController::RequestContentRepaint( + RepaintUpdateType aUpdateType) { + // Reinvoke this method on the repaint thread if it's not there already. It's + // important to do this before the call to CalculatePendingDisplayPort, so + // that CalculatePendingDisplayPort uses the most recent available version of + // Metrics(). just before the paint request is dispatched to content. + RefPtr controller = GetGeckoContentController(); + if (!controller) { + return; + } + if (!controller->IsRepaintThread()) { + // Even though we want to do the actual repaint request on the repaint + // thread, we want to update the expected gecko metrics synchronously. + // Otherwise we introduce a race condition where we might read from the + // expected gecko metrics on the controller thread before or after it gets + // updated on the repaint thread, when in fact we always want the updated + // version when reading. + { // scope lock + RecursiveMutexAutoLock lock(mRecursiveMutex); + mExpectedGeckoMetrics.UpdateFrom(Metrics()); + } + + // use the local variable to resolve the function overload. + auto func = + static_cast( + &AsyncPanZoomController::RequestContentRepaint); + controller->DispatchToRepaintThread(NewRunnableMethod( + "layers::AsyncPanZoomController::RequestContentRepaint", this, func, + aUpdateType)); + return; + } + + MOZ_ASSERT(controller->IsRepaintThread()); + + RecursiveMutexAutoLock lock(mRecursiveMutex); + ParentLayerPoint velocity = GetVelocityVector(); + ScreenMargin displayportMargins = CalculatePendingDisplayPort( + Metrics(), velocity, + mState == PINCHING ? ZoomInProgress::Yes : ZoomInProgress::No); + Metrics().SetPaintRequestTime(TimeStamp::Now()); + RequestContentRepaint(Metrics(), velocity, displayportMargins, aUpdateType); +} + +static CSSRect GetDisplayPortRect(const FrameMetrics& aFrameMetrics, + const ScreenMargin& aDisplayportMargins) { + // This computation is based on what happens in CalculatePendingDisplayPort. + // If that changes then this might need to change too. + // Note that the display port rect APZ computes is relative to the visual + // scroll offset. It's adjusted to be relative to the layout scroll offset + // when the main thread processes a repaint request (in + // APZCCallbackHelper::AdjustDisplayPortForScrollDelta()) and ultimately + // applied (in DisplayPortUtils::GetDisplayPort()) in this adjusted form. + CSSRect baseRect(aFrameMetrics.GetVisualScrollOffset(), + aFrameMetrics.CalculateBoundedCompositedSizeInCssPixels()); + baseRect.Inflate(aDisplayportMargins / + aFrameMetrics.DisplayportPixelsPerCSSPixel()); + return baseRect; +} + +void AsyncPanZoomController::RequestContentRepaint( + const FrameMetrics& aFrameMetrics, const ParentLayerPoint& aVelocity, + const ScreenMargin& aDisplayportMargins, RepaintUpdateType aUpdateType) { + RefPtr controller = GetGeckoContentController(); + if (!controller) { + return; + } + MOZ_ASSERT(controller->IsRepaintThread()); + + const bool isAnimationInProgress = !!mAnimation; + RepaintRequest request(aFrameMetrics, aDisplayportMargins, aUpdateType, + isAnimationInProgress); + + // If we're trying to paint what we already think is painted, discard this + // request since it's a pointless paint. + if (request.GetDisplayPortMargins().WithinEpsilonOf( + mLastPaintRequestMetrics.GetDisplayPortMargins(), EPSILON) && + request.GetVisualScrollOffset().WithinEpsilonOf( + mLastPaintRequestMetrics.GetVisualScrollOffset(), EPSILON) && + request.GetPresShellResolution() == + mLastPaintRequestMetrics.GetPresShellResolution() && + request.GetZoom() == mLastPaintRequestMetrics.GetZoom() && + request.GetLayoutViewport().WithinEpsilonOf( + mLastPaintRequestMetrics.GetLayoutViewport(), EPSILON) && + request.GetScrollGeneration() == + mLastPaintRequestMetrics.GetScrollGeneration() && + request.GetScrollUpdateType() == + mLastPaintRequestMetrics.GetScrollUpdateType() && + request.IsAnimationInProgress() == + mLastPaintRequestMetrics.IsAnimationInProgress()) { + return; + } + + APZC_LOGV("%p requesting content repaint %s", this, + ToString(request).c_str()); + { // scope lock + MutexAutoLock lock(mCheckerboardEventLock); + if (mCheckerboardEvent && mCheckerboardEvent->IsRecordingTrace()) { + std::stringstream info; + info << " velocity " << aVelocity; + std::string str = info.str(); + mCheckerboardEvent->UpdateRendertraceProperty( + CheckerboardEvent::RequestedDisplayPort, + GetDisplayPortRect(aFrameMetrics, aDisplayportMargins), str); + } + } + + controller->RequestContentRepaint(request); + mExpectedGeckoMetrics.UpdateFrom(aFrameMetrics); + mLastPaintRequestMetrics = request; + + // We're holding the APZC lock here, so redispatch this so we can get + // the tree lock without the APZC lock. + controller->DispatchToRepaintThread( + NewRunnableMethod( + "layers::APZCTreeManager::SendSubtreeTransformsToChromeMainThread", + GetApzcTreeManager(), + &APZCTreeManager::SendSubtreeTransformsToChromeMainThread, this)); +} + +bool AsyncPanZoomController::UpdateAnimation( + const RecursiveMutexAutoLock& aProofOfLock, const SampleTime& aSampleTime, + nsTArray>* aOutDeferredTasks) { + AssertOnSamplerThread(); + + // This function may get called multiple with the same sample time, if we + // composite multiple times at the same timestamp. + // However we only want to do one animation step per composition so we need + // to deduplicate these calls first. + if (mLastSampleTime == aSampleTime) { + return !!mAnimation; + } + + // We're at a new timestamp, so advance to the next sample in the deque, if + // there is one. That one will be used for all the code that reads the + // eForCompositing transforms in this vsync interval. + AdvanceToNextSample(); + + // And then create a new sample, which will be used in the *next* vsync + // interval. We do the sample at this point and not later in order to try + // and enforce one frame delay between computing the async transform and + // compositing it to the screen. This one-frame delay gives code running on + // the main thread a chance to try and respond to the scroll position change, + // so that e.g. a main-thread animation can stay in sync with user-driven + // scrolling or a compositor animation. + bool needComposite = SampleCompositedAsyncTransform(aProofOfLock); + + TimeDuration sampleTimeDelta = aSampleTime - mLastSampleTime; + mLastSampleTime = aSampleTime; + + if (mAnimation) { + bool continueAnimation = mAnimation->Sample(Metrics(), sampleTimeDelta); + bool wantsRepaints = mAnimation->WantsRepaints(); + *aOutDeferredTasks = mAnimation->TakeDeferredTasks(); + if (!continueAnimation) { + mAnimation = nullptr; + SetState(NOTHING); + } + // Request a repaint at the end of the animation in case something such as a + // call to NotifyLayersUpdated was invoked during the animation and Gecko's + // current state is some intermediate point of the animation. + if (!continueAnimation || wantsRepaints) { + RequestContentRepaint(); + } + UpdateSharedCompositorFrameMetrics(); + needComposite = true; + } + return needComposite; +} + +AsyncTransformComponentMatrix AsyncPanZoomController::GetOverscrollTransform( + AsyncTransformConsumer aMode) const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + + if (aMode == eForCompositing && mScrollMetadata.IsApzForceDisabled()) { + return AsyncTransformComponentMatrix(); + } + + if (!IsOverscrolled()) { + return AsyncTransformComponentMatrix(); + } + + // The overscroll effect is a simple translation by the overscroll offset. + ParentLayerPoint overscrollOffset(-mX.GetOverscroll(), -mY.GetOverscroll()); + return AsyncTransformComponentMatrix().PostTranslate(overscrollOffset.x, + overscrollOffset.y, 0); +} + +bool AsyncPanZoomController::AdvanceAnimations(const SampleTime& aSampleTime) { + AssertOnSamplerThread(); + + // Don't send any state-change notifications until the end of the function, + // because we may go through some intermediate states while we finish + // animations and start new ones. + StateChangeNotificationBlocker blocker(this); + + // The eventual return value of this function. The compositor needs to know + // whether or not to advance by a frame as soon as it can. For example, if a + // fling is happening, it has to keep compositing so that the animation is + // smooth. If an animation frame is requested, it is the compositor's + // responsibility to schedule a composite. + mAsyncTransformAppliedToContent = false; + bool requestAnimationFrame = false; + nsTArray> deferredTasks; + + { + RecursiveMutexAutoLock lock(mRecursiveMutex); + { // scope lock + MutexAutoLock lock2(mCheckerboardEventLock); + // Update RendertraceProperty before UpdateAnimation() call, since + // the UpdateAnimation() updates effective ScrollOffset for next frame + // if APZFrameDelay is enabled. + if (mCheckerboardEvent) { + mCheckerboardEvent->UpdateRendertraceProperty( + CheckerboardEvent::UserVisible, GetVisibleRect(lock)); + } + } + + requestAnimationFrame = UpdateAnimation(lock, aSampleTime, &deferredTasks); + } + // Execute any deferred tasks queued up by mAnimation's Sample() (called by + // UpdateAnimation()). This needs to be done after the monitor is released + // since the tasks are allowed to call APZCTreeManager methods which can grab + // the tree lock. + for (uint32_t i = 0; i < deferredTasks.Length(); ++i) { + APZThreadUtils::RunOnControllerThread(std::move(deferredTasks[i])); + } + + // If any of the deferred tasks starts a new animation, it will request a + // new composite directly, so we can just return requestAnimationFrame here. + return requestAnimationFrame; +} + +CSSRect AsyncPanZoomController::GetCurrentAsyncLayoutViewport( + AsyncTransformConsumer aMode) const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + AutoApplyAsyncTestAttributes testAttributeApplier(this, lock); + MOZ_ASSERT(Metrics().IsRootContent(), + "Only the root content APZC has a layout viewport"); + return GetEffectiveLayoutViewport(aMode, lock); +} + +ParentLayerPoint AsyncPanZoomController::GetCurrentAsyncScrollOffset( + AsyncTransformConsumer aMode) const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + AutoApplyAsyncTestAttributes testAttributeApplier(this, lock); + + return GetEffectiveScrollOffset(aMode, lock) * GetEffectiveZoom(aMode, lock); +} + +CSSPoint AsyncPanZoomController::GetCurrentAsyncScrollOffsetInCssPixels( + AsyncTransformConsumer aMode) const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + AutoApplyAsyncTestAttributes testAttributeApplier(this, lock); + + return GetEffectiveScrollOffset(aMode, lock); +} + +AsyncTransform AsyncPanZoomController::GetCurrentAsyncTransform( + AsyncTransformConsumer aMode, AsyncTransformComponents aComponents) const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + AutoApplyAsyncTestAttributes testAttributeApplier(this, lock); + + CSSToParentLayerScale2D effectiveZoom; + if (aComponents.contains(AsyncTransformComponent::eVisual)) { + effectiveZoom = GetEffectiveZoom(aMode, lock); + } else { + effectiveZoom = + Metrics().LayersPixelsPerCSSPixel() * LayerToParentLayerScale(1.0f); + } + + LayerToParentLayerScale compositedAsyncZoom = + (effectiveZoom / Metrics().LayersPixelsPerCSSPixel()).ToScaleFactor(); + + ParentLayerPoint translation; + if (aComponents.contains(AsyncTransformComponent::eVisual)) { + // There is no "lastPaintVisualOffset" to subtract here; the visual offset + // is entirely async. + + CSSPoint currentVisualOffset = + GetEffectiveScrollOffset(aMode, lock) - + GetEffectiveLayoutViewport(aMode, lock).TopLeft(); + + translation += currentVisualOffset * effectiveZoom; + } + if (aComponents.contains(AsyncTransformComponent::eLayout)) { + CSSPoint lastPaintLayoutOffset; + if (mLastContentPaintMetrics.IsScrollable()) { + lastPaintLayoutOffset = mLastContentPaintMetrics.GetLayoutScrollOffset(); + } + + CSSPoint currentLayoutOffset = + GetEffectiveLayoutViewport(aMode, lock).TopLeft(); + + translation += + (currentLayoutOffset - lastPaintLayoutOffset) * effectiveZoom; + } + + return AsyncTransform(compositedAsyncZoom, -translation); +} + +AsyncTransformComponentMatrix +AsyncPanZoomController::GetCurrentAsyncTransformWithOverscroll( + AsyncTransformConsumer aMode, AsyncTransformComponents aComponents) const { + return AsyncTransformComponentMatrix( + GetCurrentAsyncTransform(aMode, aComponents)) * + GetOverscrollTransform(aMode); +} + +LayoutDeviceToParentLayerScale AsyncPanZoomController::GetCurrentPinchZoomScale( + AsyncTransformConsumer aMode) const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + AutoApplyAsyncTestAttributes testAttributeApplier(this, lock); + CSSToParentLayerScale2D scale = GetEffectiveZoom(aMode, lock); + // Note that in general the zoom might have different x- and y-scales. + // However, this function in particular is only used on the WebRender codepath + // for which the scales should always be the same. + return scale.ToScaleFactor() / Metrics().GetDevPixelsPerCSSPixel(); +} + +CSSRect AsyncPanZoomController::GetEffectiveLayoutViewport( + AsyncTransformConsumer aMode, + const RecursiveMutexAutoLock& aProofOfLock) const { + if (aMode == eForCompositing && mScrollMetadata.IsApzForceDisabled()) { + return mLastContentPaintMetrics.GetLayoutViewport(); + } + if (aMode == eForCompositing) { + return mSampledState.front().GetLayoutViewport(); + } + return Metrics().GetLayoutViewport(); +} + +CSSPoint AsyncPanZoomController::GetEffectiveScrollOffset( + AsyncTransformConsumer aMode, + const RecursiveMutexAutoLock& aProofOfLock) const { + if (aMode == eForCompositing && mScrollMetadata.IsApzForceDisabled()) { + return mLastContentPaintMetrics.GetVisualScrollOffset(); + } + if (aMode == eForCompositing) { + return mSampledState.front().GetVisualScrollOffset(); + } + return Metrics().GetVisualScrollOffset(); +} + +CSSToParentLayerScale2D AsyncPanZoomController::GetEffectiveZoom( + AsyncTransformConsumer aMode, + const RecursiveMutexAutoLock& aProofOfLock) const { + if (aMode == eForCompositing && mScrollMetadata.IsApzForceDisabled()) { + return mLastContentPaintMetrics.GetZoom(); + } + if (aMode == eForCompositing) { + return mSampledState.front().GetZoom(); + } + return Metrics().GetZoom(); +} + +void AsyncPanZoomController::AdvanceToNextSample() { + AssertOnSamplerThread(); + RecursiveMutexAutoLock lock(mRecursiveMutex); + // Always keep at least one state in mSampledState. + if (mSampledState.size() > 1) { + mSampledState.pop_front(); + } +} + +bool AsyncPanZoomController::SampleCompositedAsyncTransform( + const RecursiveMutexAutoLock& aProofOfLock) { + MOZ_ASSERT(mSampledState.size() <= 2); + bool sampleChanged = (mSampledState.back() != SampledAPZCState(Metrics())); + mSampledState.emplace_back(Metrics(), std::move(mScrollPayload)); + return sampleChanged; +} + +void AsyncPanZoomController::ResampleCompositedAsyncTransform( + const RecursiveMutexAutoLock& aProofOfLock) { + // This only gets called during testing situations, so the fact that this + // drops the scroll payload from mSampledState.front() is not really a + // problem. + mSampledState.front() = SampledAPZCState(Metrics()); +} + +void AsyncPanZoomController::ApplyAsyncTestAttributes( + const RecursiveMutexAutoLock& aProofOfLock) { + if (mTestAttributeAppliers == 0) { + if (mTestAsyncScrollOffset != CSSPoint() || + mTestAsyncZoom != LayerToParentLayerScale()) { + // TODO Currently we update Metrics() and resample, which will cause + // the very latest user input to get immediately captured in the sample, + // and may defeat our attempt at "frame delay" (i.e. delaying the user + // input from affecting composition by one frame). + // Instead, maybe we should just apply the mTest* stuff directly to + // mSampledState.front(). We can even save/restore that SampledAPZCState + // instance in the AutoApplyAsyncTestAttributes instead of Metrics(). + Metrics().ZoomBy(mTestAsyncZoom.scale); + ScrollBy(mTestAsyncScrollOffset); + ResampleCompositedAsyncTransform(aProofOfLock); + } + } + ++mTestAttributeAppliers; +} + +void AsyncPanZoomController::UnapplyAsyncTestAttributes( + const RecursiveMutexAutoLock& aProofOfLock, + const FrameMetrics& aPrevFrameMetrics) { + MOZ_ASSERT(mTestAttributeAppliers >= 1); + --mTestAttributeAppliers; + if (mTestAttributeAppliers == 0) { + if (mTestAsyncScrollOffset != CSSPoint() || + mTestAsyncZoom != LayerToParentLayerScale()) { + Metrics() = aPrevFrameMetrics; + ResampleCompositedAsyncTransform(aProofOfLock); + } + } +} + +Matrix4x4 AsyncPanZoomController::GetTransformToLastDispatchedPaint() const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + + LayerPoint scrollChange = (mLastContentPaintMetrics.GetLayoutScrollOffset() - + mExpectedGeckoMetrics.GetVisualScrollOffset()) * + mLastContentPaintMetrics.GetDevPixelsPerCSSPixel() * + mLastContentPaintMetrics.GetCumulativeResolution(); + + // We're interested in the async zoom change. Factor out the content scale + // that may change when dragging the window to a monitor with a different + // content scale. + LayoutDeviceToParentLayerScale2D lastContentZoom = + mLastContentPaintMetrics.GetZoom() / + mLastContentPaintMetrics.GetDevPixelsPerCSSPixel(); + LayoutDeviceToParentLayerScale2D lastDispatchedZoom = + mExpectedGeckoMetrics.GetZoom() / + mExpectedGeckoMetrics.GetDevPixelsPerCSSPixel(); + gfxSize zoomChange(1.0, 1.0); + if (lastDispatchedZoom != LayoutDeviceToParentLayerScale2D(0, 0)) { + zoomChange = lastContentZoom / lastDispatchedZoom; + } + return Matrix4x4::Translation(scrollChange.x, scrollChange.y, 0) + .PostScale(zoomChange.width, zoomChange.height, 1); +} + +CSSRect AsyncPanZoomController::GetVisibleRect( + const RecursiveMutexAutoLock& aProofOfLock) const { + AutoApplyAsyncTestAttributes testAttributeApplier(this, aProofOfLock); + CSSPoint currentScrollOffset = GetEffectiveScrollOffset( + AsyncPanZoomController::eForCompositing, aProofOfLock); + CSSRect visible = CSSRect(currentScrollOffset, + Metrics().CalculateCompositedSizeInCssPixels()); + return visible; +} + +uint32_t AsyncPanZoomController::GetCheckerboardMagnitude( + const ParentLayerRect& aClippedCompositionBounds) const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + + CSSRect painted = mLastContentPaintMetrics.GetDisplayPort() + + mLastContentPaintMetrics.GetLayoutScrollOffset(); + painted.Inflate(CSSMargin::FromAppUnits( + nsMargin(1, 1, 1, 1))); // fuzz for rounding error + + CSSRect visible = GetVisibleRect(lock); // relative to scrolled frame origin + if (visible.IsEmpty() || painted.Contains(visible)) { + // early-exit if we're definitely not checkerboarding + return 0; + } + + // aClippedCompositionBounds and Metrics().GetCompositionBounds() are both + // relative to the layer tree origin. + // The "*RelativeToItself*" variables are relative to the comp bounds origin + ParentLayerRect visiblePartOfCompBoundsRelativeToItself = + aClippedCompositionBounds - Metrics().GetCompositionBounds().TopLeft(); + CSSRect visiblePartOfCompBoundsRelativeToItselfInCssSpace; + if (Metrics().GetZoom() != CSSToParentLayerScale2D(0, 0)) { + visiblePartOfCompBoundsRelativeToItselfInCssSpace = + (visiblePartOfCompBoundsRelativeToItself / Metrics().GetZoom()); + } + + // This one is relative to the scrolled frame origin, same as `visible` + CSSRect visiblePartOfCompBoundsInCssSpace = + visiblePartOfCompBoundsRelativeToItselfInCssSpace + visible.TopLeft(); + + visible = visible.Intersect(visiblePartOfCompBoundsInCssSpace); + + CSSIntRegion checkerboard; + // Round so as to minimize checkerboarding; if we're only showing fractional + // pixels of checkerboarding it's not really worth counting + checkerboard.Sub(RoundedIn(visible), RoundedOut(painted)); + uint32_t area = checkerboard.Area(); + if (area) { + APZC_LOG_FM(Metrics(), + "%p is currently checkerboarding (painted %s visible %s)", this, + ToString(painted).c_str(), ToString(visible).c_str()); + } + return area; +} + +void AsyncPanZoomController::ReportCheckerboard( + const SampleTime& aSampleTime, + const ParentLayerRect& aClippedCompositionBounds) { + if (mLastCheckerboardReport == aSampleTime) { + // This function will get called multiple times for each APZC on a single + // composite (once for each layer it is attached to). Only report the + // checkerboard once per composite though. + return; + } + mLastCheckerboardReport = aSampleTime; + + bool recordTrace = StaticPrefs::apz_record_checkerboarding(); + bool forTelemetry = Telemetry::CanRecordExtended(); + uint32_t magnitude = GetCheckerboardMagnitude(aClippedCompositionBounds); + + // IsInTransformingState() acquires the APZC lock and thus needs to + // be called before acquiring mCheckerboardEventLock. + bool inTransformingState = IsInTransformingState(); + + MutexAutoLock lock(mCheckerboardEventLock); + if (!mCheckerboardEvent && (recordTrace || forTelemetry)) { + mCheckerboardEvent = MakeUnique(recordTrace); + } + mPotentialCheckerboardTracker.InTransform(inTransformingState); + if (magnitude) { + mPotentialCheckerboardTracker.CheckerboardSeen(); + } + UpdateCheckerboardEvent(lock, magnitude); +} + +void AsyncPanZoomController::UpdateCheckerboardEvent( + const MutexAutoLock& aProofOfLock, uint32_t aMagnitude) { + if (mCheckerboardEvent && mCheckerboardEvent->RecordFrameInfo(aMagnitude)) { + // This checkerboard event is done. Report some metrics to telemetry. + mozilla::Telemetry::Accumulate(mozilla::Telemetry::CHECKERBOARD_SEVERITY, + mCheckerboardEvent->GetSeverity()); + mozilla::Telemetry::Accumulate(mozilla::Telemetry::CHECKERBOARD_PEAK, + mCheckerboardEvent->GetPeak()); + mozilla::Telemetry::Accumulate( + mozilla::Telemetry::CHECKERBOARD_DURATION, + (uint32_t)mCheckerboardEvent->GetDuration().ToMilliseconds()); + + mPotentialCheckerboardTracker.CheckerboardDone(); + + if (StaticPrefs::apz_record_checkerboarding()) { + // if the pref is enabled, also send it to the storage class. it may be + // chosen for public display on about:checkerboard, the hall of fame for + // checkerboard events. + uint32_t severity = mCheckerboardEvent->GetSeverity(); + std::string log = mCheckerboardEvent->GetLog(); + CheckerboardEventStorage::Report(severity, log); + } + mCheckerboardEvent = nullptr; + } +} + +void AsyncPanZoomController::FlushActiveCheckerboardReport() { + MutexAutoLock lock(mCheckerboardEventLock); + // Pretend like we got a frame with 0 pixels checkerboarded. This will + // terminate the checkerboard event and flush it out + UpdateCheckerboardEvent(lock, 0); +} + +void AsyncPanZoomController::NotifyLayersUpdated( + const ScrollMetadata& aScrollMetadata, bool aIsFirstPaint, + bool aThisLayerTreeUpdated) { + AssertOnUpdaterThread(); + + RecursiveMutexAutoLock lock(mRecursiveMutex); + bool isDefault = mScrollMetadata.IsDefault(); + + const FrameMetrics& aLayerMetrics = aScrollMetadata.GetMetrics(); + + if ((aScrollMetadata == mLastContentPaintMetadata) && !isDefault) { + // No new information here, skip it. + APZC_LOGV("%p NotifyLayersUpdated short-circuit\n", this); + return; + } + + // If the Metrics scroll offset is different from the last scroll offset + // that the main-thread sent us, then we know that the user has been doing + // something that triggers a scroll. This check is the APZ equivalent of the + // check on the main-thread at + // https://hg.mozilla.org/mozilla-central/file/97a52326b06a/layout/generic/nsGfxScrollFrame.cpp#l4050 + // There is code below (the use site of userScrolled) that prevents a + // restored- scroll-position update from overwriting a user scroll, again + // equivalent to how the main thread code does the same thing. + // XXX Suspicious comparison between layout and visual scroll offsets. + // This may not do the right thing when we're zoomed in. + CSSPoint lastScrollOffset = mLastContentPaintMetrics.GetLayoutScrollOffset(); + bool userScrolled = !FuzzyEqualsAdditive(Metrics().GetVisualScrollOffset().x, + lastScrollOffset.x) || + !FuzzyEqualsAdditive(Metrics().GetVisualScrollOffset().y, + lastScrollOffset.y); + + if (aScrollMetadata.DidContentGetPainted()) { + mLastContentPaintMetadata = aScrollMetadata; + } + + mScrollMetadata.SetScrollParentId(aScrollMetadata.GetScrollParentId()); + APZC_LOGV_FM(aLayerMetrics, + "%p got a NotifyLayersUpdated with aIsFirstPaint=%d, " + "aThisLayerTreeUpdated=%d", + this, aIsFirstPaint, aThisLayerTreeUpdated); + + { // scope lock + MutexAutoLock lock(mCheckerboardEventLock); + if (mCheckerboardEvent && mCheckerboardEvent->IsRecordingTrace()) { + std::string str; + if (aThisLayerTreeUpdated) { + if (!aLayerMetrics.GetPaintRequestTime().IsNull()) { + // Note that we might get the paint request time as non-null, but with + // aThisLayerTreeUpdated false. That can happen if we get a layer + // transaction from a different process right after we get the layer + // transaction with aThisLayerTreeUpdated == true. In this case we + // want to ignore the paint request time because it was already dumped + // in the previous layer transaction. + TimeDuration paintTime = + TimeStamp::Now() - aLayerMetrics.GetPaintRequestTime(); + std::stringstream info; + info << " painttime " << paintTime.ToMilliseconds(); + str = info.str(); + } else { + // This might be indicative of a wasted paint particularly if it + // happens during a checkerboard event. + str = " (this layertree updated)"; + } + } + mCheckerboardEvent->UpdateRendertraceProperty( + CheckerboardEvent::Page, aLayerMetrics.GetScrollableRect()); + mCheckerboardEvent->UpdateRendertraceProperty( + CheckerboardEvent::PaintedDisplayPort, + aLayerMetrics.GetDisplayPort() + + aLayerMetrics.GetLayoutScrollOffset(), + str); + if (!aLayerMetrics.GetCriticalDisplayPort().IsEmpty()) { + mCheckerboardEvent->UpdateRendertraceProperty( + CheckerboardEvent::PaintedCriticalDisplayPort, + aLayerMetrics.GetCriticalDisplayPort() + + aLayerMetrics.GetLayoutScrollOffset()); + } + } + } + + // The main thread may send us a visual scroll offset update. This is + // different from a layout viewport offset update in that the layout viewport + // offset is limited to the layout scroll range, while the visual viewport + // offset is not. + // However, there are some conditions in which the layout update will clobber + // the visual update, and we want to ignore the visual update in those cases. + // This variable tracks that. + bool ignoreVisualUpdate = false; + + // TODO if we're in a drag and scrollOffsetUpdated is set then we want to + // ignore it + + bool needContentRepaint = false; + RepaintUpdateType contentRepaintType = RepaintUpdateType::eNone; + bool viewportSizeUpdated = false; + bool needToReclampScroll = false; + + if ((aIsFirstPaint && aThisLayerTreeUpdated) || isDefault) { + // Initialize our internal state to something sane when the content + // that was just painted is something we knew nothing about previously + CancelAnimation(); + + // Keep our existing scroll generation, if there are scroll updates. In this + // case we'll update our scroll generation when processing the scroll update + // array below. If there are no scroll updates, take the generation from the + // incoming metrics. Bug 1662019 will simplify this later. + ScrollGeneration oldScrollGeneration = Metrics().GetScrollGeneration(); + mScrollMetadata = aScrollMetadata; + if (!aScrollMetadata.GetScrollUpdates().IsEmpty()) { + Metrics().SetScrollGeneration(oldScrollGeneration); + } + + mExpectedGeckoMetrics.UpdateFrom(aLayerMetrics); + ShareCompositorFrameMetrics(); + + for (auto& sampledState : mSampledState) { + sampledState.UpdateScrollProperties(Metrics()); + sampledState.UpdateZoomProperties(Metrics()); + } + + // Make sure we have an up-to-date set of displayport margins. + needContentRepaint = true; + } else { + // If we're not taking the aLayerMetrics wholesale we still need to pull + // in some things into our local Metrics() because these things are + // determined by Gecko and our copy in Metrics() may be stale. + + if (Metrics().GetLayoutViewport().Size() != + aLayerMetrics.GetLayoutViewport().Size()) { + CSSRect layoutViewport = Metrics().GetLayoutViewport(); + // The offset will be updated if necessary via + // RecalculateLayoutViewportOffset(). + layoutViewport.SizeTo(aLayerMetrics.GetLayoutViewport().Size()); + Metrics().SetLayoutViewport(layoutViewport); + + needContentRepaint = true; + viewportSizeUpdated = true; + } + + // TODO: Rely entirely on |aScrollMetadata.IsResolutionUpdated()| to + // determine which branch to take, and drop the other conditions. + if (FuzzyEqualsAdditive(Metrics().GetCompositionBounds().Width(), + aLayerMetrics.GetCompositionBounds().Width()) && + Metrics().GetDevPixelsPerCSSPixel() == + aLayerMetrics.GetDevPixelsPerCSSPixel() && + !viewportSizeUpdated && !aScrollMetadata.IsResolutionUpdated()) { + // Any change to the pres shell resolution was requested by APZ and is + // already included in our zoom; however, other components of the + // cumulative resolution (a parent document's pres-shell resolution, or + // the css-driven resolution) may have changed, and we need to update + // our zoom to reflect that. Note that we can't just take + // aLayerMetrics.mZoom because the APZ may have additional async zoom + // since the repaint request. + gfxSize totalResolutionChange(1.0, 1.0); + + if (Metrics().GetCumulativeResolution() != + LayoutDeviceToLayerScale2D(0, 0)) { + totalResolutionChange = aLayerMetrics.GetCumulativeResolution() / + Metrics().GetCumulativeResolution(); + } + + float presShellResolutionChange = aLayerMetrics.GetPresShellResolution() / + Metrics().GetPresShellResolution(); + if (presShellResolutionChange != 1.0f) { + needContentRepaint = true; + } + Metrics().ZoomBy(totalResolutionChange / presShellResolutionChange); + for (auto& sampledState : mSampledState) { + sampledState.ZoomBy(totalResolutionChange / presShellResolutionChange); + } + } else { + // Take the new zoom as either device scale or composition width or + // viewport size got changed (e.g. due to orientation change, or content + // changing the meta-viewport tag). + Metrics().SetZoom(aLayerMetrics.GetZoom()); + for (auto& sampledState : mSampledState) { + sampledState.UpdateZoomProperties(aLayerMetrics); + } + Metrics().SetDevPixelsPerCSSPixel( + aLayerMetrics.GetDevPixelsPerCSSPixel()); + } + if (!Metrics().GetScrollableRect().IsEqualEdges( + aLayerMetrics.GetScrollableRect())) { + Metrics().SetScrollableRect(aLayerMetrics.GetScrollableRect()); + needContentRepaint = true; + needToReclampScroll = true; + } + if (!Metrics().GetCompositionBounds().IsEqualEdges( + aLayerMetrics.GetCompositionBounds())) { + Metrics().SetCompositionBounds(aLayerMetrics.GetCompositionBounds()); + needToReclampScroll = true; + } + + if (Metrics().IsRootContent() && + Metrics().GetCompositionSizeWithoutDynamicToolbar() != + aLayerMetrics.GetCompositionSizeWithoutDynamicToolbar()) { + Metrics().SetCompositionSizeWithoutDynamicToolbar( + aLayerMetrics.GetCompositionSizeWithoutDynamicToolbar()); + needToReclampScroll = true; + } + Metrics().SetRootCompositionSize(aLayerMetrics.GetRootCompositionSize()); + Metrics().SetPresShellResolution(aLayerMetrics.GetPresShellResolution()); + Metrics().SetCumulativeResolution(aLayerMetrics.GetCumulativeResolution()); + mScrollMetadata.SetHasScrollgrab(aScrollMetadata.GetHasScrollgrab()); + mScrollMetadata.SetLineScrollAmount(aScrollMetadata.GetLineScrollAmount()); + mScrollMetadata.SetPageScrollAmount(aScrollMetadata.GetPageScrollAmount()); + mScrollMetadata.SetSnapInfo(ScrollSnapInfo(aScrollMetadata.GetSnapInfo())); + // The scroll clip can differ between layers associated a given scroll + // frame, so APZC (which keeps a single copy of ScrollMetadata per scroll + // frame) has no business using it. + mScrollMetadata.SetScrollClip(Nothing()); + mScrollMetadata.SetIsLayersIdRoot(aScrollMetadata.IsLayersIdRoot()); + mScrollMetadata.SetIsAutoDirRootContentRTL( + aScrollMetadata.IsAutoDirRootContentRTL()); + Metrics().SetIsScrollInfoLayer(aLayerMetrics.IsScrollInfoLayer()); + mScrollMetadata.SetForceDisableApz(aScrollMetadata.IsApzForceDisabled()); + mScrollMetadata.SetIsRDMTouchSimulationActive( + aScrollMetadata.GetIsRDMTouchSimulationActive()); + mScrollMetadata.SetDisregardedDirection( + aScrollMetadata.GetDisregardedDirection()); + mScrollMetadata.SetOverscrollBehavior( + aScrollMetadata.GetOverscrollBehavior()); + } + + bool scrollOffsetUpdated = false; + for (const auto& scrollUpdate : aScrollMetadata.GetScrollUpdates()) { + APZC_LOG("%p processing scroll update %s\n", this, + ToString(scrollUpdate).c_str()); + if (!(Metrics().GetScrollGeneration() < scrollUpdate.GetGeneration())) { + // This is stale, let's ignore it + APZC_LOG("%p scrollupdate generation stale, dropping\n", this); + continue; + } + Metrics().SetScrollGeneration(scrollUpdate.GetGeneration()); + + MOZ_ASSERT(scrollUpdate.GetOrigin() != ScrollOrigin::Apz); + if (userScrolled && + !nsLayoutUtils::CanScrollOriginClobberApz(scrollUpdate.GetOrigin())) { + APZC_LOG("%p scrollupdate cannot clobber APZ userScrolled\n", this); + continue; + } + // XXX: if we get here, |scrollUpdate| is clobbering APZ, so we may want + // to reset |userScrolled| back to false so that subsequent scrollUpdates + // in this loop don't get dropped by the check above. Need to add a test + // that exercises this scenario, as we don't currently have one. + + scrollOffsetUpdated = true; + + if (scrollUpdate.GetMode() == ScrollMode::Smooth || + scrollUpdate.GetMode() == ScrollMode::SmoothMsd) { + // Requests to animate the visual scroll position override requests to + // simply update the visual scroll offset to a particular point. Since + // we have an animation request, we set ignoreVisualUpdate to true to + // indicate we don't need to apply the visual scroll update in + // aLayerMetrics. + ignoreVisualUpdate = true; + + // For relative updates we want to add the relative offset to any existing + // destination, or the current visual offset if there is no existing + // destination. + CSSPoint base = GetCurrentAnimationDestination(lock).valueOr( + Metrics().GetVisualScrollOffset()); + + CSSPoint destination; + if (scrollUpdate.GetType() == ScrollUpdateType::Relative) { + CSSPoint delta = + scrollUpdate.GetDestination() - scrollUpdate.GetSource(); + APZC_LOG("%p relative smooth scrolling from %s by %s\n", this, + ToString(base).c_str(), ToString(delta).c_str()); + destination = Metrics().CalculateScrollRange().ClampPoint(base + delta); + } else if (scrollUpdate.GetType() == ScrollUpdateType::PureRelative) { + CSSPoint delta = scrollUpdate.GetDelta(); + APZC_LOG("%p pure-relative smooth scrolling from %s by %s\n", this, + ToString(base).c_str(), ToString(delta).c_str()); + destination = Metrics().CalculateScrollRange().ClampPoint(base + delta); + } else { + APZC_LOG("%p smooth scrolling to %s\n", this, + ToString(scrollUpdate.GetDestination()).c_str()); + destination = scrollUpdate.GetDestination(); + } + + if (scrollUpdate.GetMode() == ScrollMode::SmoothMsd) { + SmoothMsdScrollTo(destination); + } else { + MOZ_ASSERT(scrollUpdate.GetMode() == ScrollMode::Smooth); + SmoothScrollTo(destination, scrollUpdate.GetOrigin()); + } + continue; + } + + MOZ_ASSERT(scrollUpdate.GetMode() == ScrollMode::Instant || + scrollUpdate.GetMode() == ScrollMode::Normal); + + // If the layout update is of a higher priority than the visual update, then + // we don't want to apply the visual update. + // If the layout update is of a clobbering type (or a smooth scroll request, + // which is handled above) then it takes precedence over an eRestore visual + // update. But we also allow the possibility for the main thread to ask us + // to scroll both the layout and visual viewports to distinct (but + // compatible) locations (via e.g. both updates being of a non-clobbering/ + // eRestore type). + if (nsLayoutUtils::CanScrollOriginClobberApz(scrollUpdate.GetOrigin()) && + aLayerMetrics.GetVisualScrollUpdateType() != + FrameMetrics::eMainThread) { + ignoreVisualUpdate = true; + } + + Maybe relativeDelta; + + if (scrollUpdate.GetType() == ScrollUpdateType::Relative) { + APZC_LOG( + "%p relative updating scroll offset from %s by %s\n", this, + ToString(Metrics().GetVisualScrollOffset()).c_str(), + ToString(scrollUpdate.GetDestination() - scrollUpdate.GetSource()) + .c_str()); + + // It's possible that the main thread has ignored an APZ scroll offset + // update for the pending relative scroll that we have just received. + // When this happens, we need to send a new scroll offset update with + // the combined scroll offset or else the main thread may have an + // incorrect scroll offset for a period of time. + if (Metrics().HasPendingScroll(aLayerMetrics)) { + needContentRepaint = true; + contentRepaintType = RepaintUpdateType::eUserAction; + } + + relativeDelta = + Some(Metrics().ApplyRelativeScrollUpdateFrom(scrollUpdate)); + Metrics().RecalculateLayoutViewportOffset(); + } else if (scrollUpdate.GetType() == ScrollUpdateType::PureRelative) { + APZC_LOG("%p pure-relative updating scroll offset from %s by %s\n", this, + ToString(Metrics().GetVisualScrollOffset()).c_str(), + ToString(scrollUpdate.GetDelta()).c_str()); + + // Always need a repaint request with a repaint type for pure relative + // scrolls because apz is doing the scroll at the main thread's request. + // The main thread has not updated it's scroll offset yet, it is depending + // on apz to tell it where to scroll. + needContentRepaint = true; + contentRepaintType = RepaintUpdateType::eVisualUpdate; + + // We have to ignore a visual scroll offset update otherwise it will + // clobber the relative scrolling we are about to do. We perform + // visualScrollOffset = visualScrollOffset + delta. Then the + // visualScrollOffsetUpdated block below will do visualScrollOffset = + // aLayerMetrics.GetVisualDestination(). We need visual scroll offset + // updates to be incorporated into this scroll update loop to properly fix + // this. + ignoreVisualUpdate = true; + + relativeDelta = + Some(Metrics().ApplyPureRelativeScrollUpdateFrom(scrollUpdate)); + Metrics().RecalculateLayoutViewportOffset(); + } else { + APZC_LOG("%p updating scroll offset from %s to %s\n", this, + ToString(Metrics().GetVisualScrollOffset()).c_str(), + ToString(scrollUpdate.GetDestination()).c_str()); + Metrics().ApplyScrollUpdateFrom(scrollUpdate); + Metrics().RecalculateLayoutViewportOffset(); + } + + // If an animation is underway, tell it about the scroll offset update. + // Some animations can handle some scroll offset updates and continue + // running. Those that can't will return false, and we cancel them. + if (ShouldCancelAnimationForScrollUpdate(relativeDelta)) { + // Cancel the animation (which might also trigger a repaint request) + // after we update the scroll offset above. Otherwise we can be left + // in a state where things are out of sync. + CancelAnimation(); + } + } + + if (scrollOffsetUpdated) { + for (auto& sampledState : mSampledState) { + sampledState.UpdateScrollProperties(Metrics()); + } + + // Because of the scroll generation update, any inflight paint requests + // are going to be ignored by layout, and so mExpectedGeckoMetrics becomes + // incorrect for the purposes of calculating the LD transform. To correct + // this we need to update mExpectedGeckoMetrics to be the last thing we + // know was painted by Gecko. + mExpectedGeckoMetrics.UpdateFrom(aLayerMetrics); + + // Since the scroll offset has changed, we need to recompute the + // displayport margins and send them to layout. Otherwise there might be + // scenarios where for example we scroll from the top of a page (where the + // top displayport margin is zero) to the bottom of a page, which will + // result in a displayport that doesn't extend upwards at all. + // Note that even if the CancelAnimation call above requested a repaint + // this is fine because we already have repaint request deduplication. + needContentRepaint = true; + // Since the main-thread scroll offset changed we should trigger a + // recomposite to make sure it becomes user-visible. + ScheduleComposite(); + } else if (needToReclampScroll) { + // Even if we didn't accept a new scroll offset from content, the + // scrollable rect or composition bounds may have changed in a way that + // makes our local scroll offset out of bounds, so re-clamp it. + ClampAndSetVisualScrollOffset(Metrics().GetVisualScrollOffset()); + for (auto& sampledState : mSampledState) { + sampledState.ClampVisualScrollOffset(Metrics()); + } + } + + // If `isDefault` is true, this APZC is a "new" one (this is the first time + // it's getting a NotifyLayersUpdated call). In this case we want to apply the + // visual scroll offset from the main thread to our scroll offset. + // The main thread may also ask us to scroll the visual viewport to a + // particular location. However, in all cases, we want to ignore the visual + // offset update if ignoreVisualUpdate is true, because we're clobbering + // the visual update with a layout update. + bool visualScrollOffsetUpdated = + !ignoreVisualUpdate && + (isDefault || + aLayerMetrics.GetVisualScrollUpdateType() != FrameMetrics::eNone); + + if (visualScrollOffsetUpdated) { + APZC_LOG("%p updating visual scroll offset from %s to %s (updateType %d)\n", + this, ToString(Metrics().GetVisualScrollOffset()).c_str(), + ToString(aLayerMetrics.GetVisualDestination()).c_str(), + (int)aLayerMetrics.GetVisualScrollUpdateType()); + Metrics().ClampAndSetVisualScrollOffset( + aLayerMetrics.GetVisualDestination()); + + // The rest of this branch largely follows the code in the + // |if (scrollOffsetUpdated)| branch above. Eventually it should get + // merged into that branch. + Metrics().RecalculateLayoutViewportOffset(); + for (auto& sampledState : mSampledState) { + sampledState.UpdateScrollProperties(Metrics()); + } + mExpectedGeckoMetrics.UpdateFrom(aLayerMetrics); + if (ShouldCancelAnimationForScrollUpdate(Nothing())) { + CancelAnimation(); + } + // The main thread did not actually paint a displayport at the target + // visual offset, so we need to ask it to repaint. We need to set the + // contentRepaintType to something other than eNone, otherwise the main + // thread will short-circuit the repaint request. + // Don't do this for eRestore visual updates as a repaint coming from APZ + // breaks the scroll offset restoration mechanism. + needContentRepaint = true; + if (aLayerMetrics.GetVisualScrollUpdateType() == + FrameMetrics::eMainThread) { + contentRepaintType = RepaintUpdateType::eVisualUpdate; + } + ScheduleComposite(); + } + + if (viewportSizeUpdated) { + // While we want to accept the main thread's layout viewport _size_, + // its position may be out of date in light of async scrolling, to + // adjust it if necessary to make sure it continues to enclose the + // visual viewport. + // Note: it's important to do this _after_ we've accepted any + // updated composition bounds. + Metrics().RecalculateLayoutViewportOffset(); + } + + if (needContentRepaint) { + // This repaint request could be driven by a user action if we accept a + // relative scroll offset update + RequestContentRepaint(contentRepaintType); + } + UpdateSharedCompositorFrameMetrics(); +} + +FrameMetrics& AsyncPanZoomController::Metrics() { + mRecursiveMutex.AssertCurrentThreadIn(); + return mScrollMetadata.GetMetrics(); +} + +const FrameMetrics& AsyncPanZoomController::Metrics() const { + mRecursiveMutex.AssertCurrentThreadIn(); + return mScrollMetadata.GetMetrics(); +} + +GeckoViewMetrics AsyncPanZoomController::GetGeckoViewMetrics() const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + return GeckoViewMetrics{GetEffectiveScrollOffset(eForCompositing, lock), + GetEffectiveZoom(eForCompositing, lock)}; +} + +bool AsyncPanZoomController::UpdateRootFrameMetricsIfChanged( + GeckoViewMetrics& aMetrics) { + RecursiveMutexAutoLock lock(mRecursiveMutex); + + if (!Metrics().IsRootContent()) { + return false; + } + + GeckoViewMetrics newMetrics = GetGeckoViewMetrics(); + bool hasChanged = RoundedToInt(aMetrics.mVisualScrollOffset) != + RoundedToInt(newMetrics.mVisualScrollOffset) || + aMetrics.mZoom != newMetrics.mZoom; + + if (hasChanged) { + aMetrics = newMetrics; + } + + return hasChanged; +} + +const FrameMetrics& AsyncPanZoomController::GetFrameMetrics() const { + return Metrics(); +} + +const ScrollMetadata& AsyncPanZoomController::GetScrollMetadata() const { + mRecursiveMutex.AssertCurrentThreadIn(); + return mScrollMetadata; +} + +void AsyncPanZoomController::AssertOnSamplerThread() const { + if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) { + treeManagerLocal->AssertOnSamplerThread(); + } +} + +void AsyncPanZoomController::AssertOnUpdaterThread() const { + if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) { + treeManagerLocal->AssertOnUpdaterThread(); + } +} + +APZCTreeManager* AsyncPanZoomController::GetApzcTreeManager() const { + mRecursiveMutex.AssertNotCurrentThreadIn(); + return mTreeManager; +} + +void AsyncPanZoomController::ZoomToRect(CSSRect aRect, const uint32_t aFlags) { + if (!aRect.IsFinite()) { + NS_WARNING("ZoomToRect got called with a non-finite rect; ignoring..."); + return; + } + + if (aRect.IsEmpty() && (aFlags & DISABLE_ZOOM_OUT)) { + // Double-tap-to-zooming uses an empty rect to mean "zoom out". + // If zooming out is disabled, an empty rect is nonsensical + // and will produce undesirable scrolling. + NS_WARNING( + "ZoomToRect got called with an empty rect and zoom out disabled; " + "ignoring..."); + return; + } + + SetState(ANIMATING_ZOOM); + + { + RecursiveMutexAutoLock lock(mRecursiveMutex); + + // Only the root APZC is zoomable, and the root APZC is not allowed to have + // different x and y scales. If it did, the calculations in this function + // would have to be adjusted (as e.g. it would no longer be valid to take + // the minimum or maximum of the ratios of the widths and heights of the + // page rect and the composition bounds). + MOZ_ASSERT(Metrics().IsRootContent()); + MOZ_ASSERT(Metrics().GetZoom().AreScalesSame()); + + ParentLayerRect compositionBounds = Metrics().GetCompositionBounds(); + CSSRect cssPageRect = Metrics().GetScrollableRect(); + CSSPoint scrollOffset = Metrics().GetVisualScrollOffset(); + CSSToParentLayerScale currentZoom = Metrics().GetZoom().ToScaleFactor(); + CSSToParentLayerScale targetZoom; + + // The minimum zoom to prevent over-zoom-out. + // If the zoom factor is lower than this (i.e. we are zoomed more into the + // page), then the CSS content rect, in layers pixels, will be smaller than + // the composition bounds. If this happens, we can't fill the target + // composited area with this frame. + CSSToParentLayerScale localMinZoom( + std::max(mZoomConstraints.mMinZoom.scale, + std::max(compositionBounds.Width() / cssPageRect.Width(), + compositionBounds.Height() / cssPageRect.Height()))); + CSSToParentLayerScale localMaxZoom = + std::max(localMinZoom, mZoomConstraints.mMaxZoom); + + if (!aRect.IsEmpty()) { + // Intersect the zoom-to-rect to the CSS rect to make sure it fits. + aRect = aRect.Intersect(cssPageRect); + targetZoom = CSSToParentLayerScale( + std::min(compositionBounds.Width() / aRect.Width(), + compositionBounds.Height() / aRect.Height())); + if (aFlags & DISABLE_ZOOM_OUT) { + targetZoom = std::max(targetZoom, currentZoom); + } + } + + // 1. If the rect is empty, the content-side logic for handling a double-tap + // requested that we zoom out. + // 2. currentZoom is equal to mZoomConstraints.mMaxZoom and user still + // double-tapping it + // 3. currentZoom is equal to localMinZoom and user still double-tapping it + // Treat these three cases as a request to zoom out as much as possible. + bool zoomOut; + if (aFlags & DISABLE_ZOOM_OUT) { + zoomOut = false; + } else { + zoomOut = aRect.IsEmpty() || + (currentZoom == localMaxZoom && targetZoom >= localMaxZoom) || + (currentZoom == localMinZoom && targetZoom <= localMinZoom); + } + + if (zoomOut) { + CSSSize compositedSize = Metrics().CalculateCompositedSizeInCssPixels(); + float y = scrollOffset.y; + float newHeight = + cssPageRect.Width() * (compositedSize.height / compositedSize.width); + float dh = compositedSize.height - newHeight; + + aRect = CSSRect(0.0f, y + dh / 2, cssPageRect.Width(), newHeight); + aRect = aRect.Intersect(cssPageRect); + targetZoom = CSSToParentLayerScale( + std::min(compositionBounds.Width() / aRect.Width(), + compositionBounds.Height() / aRect.Height())); + } + + targetZoom.scale = + clamped(targetZoom.scale, localMinZoom.scale, localMaxZoom.scale); + FrameMetrics endZoomToMetrics = Metrics(); + if (aFlags & PAN_INTO_VIEW_ONLY) { + targetZoom = currentZoom; + } else if (aFlags & ONLY_ZOOM_TO_DEFAULT_SCALE) { + CSSToParentLayerScale zoomAtDefaultScale = + Metrics().GetDevPixelsPerCSSPixel() * + LayoutDeviceToParentLayerScale(1.0); + if (targetZoom.scale > zoomAtDefaultScale.scale) { + // Only change the zoom if we are less than the default zoom + if (currentZoom.scale < zoomAtDefaultScale.scale) { + targetZoom = zoomAtDefaultScale; + } else { + targetZoom = currentZoom; + } + } + } + endZoomToMetrics.SetZoom(CSSToParentLayerScale2D(targetZoom)); + + // Adjust the zoomToRect to a sensible position to prevent overscrolling. + CSSSize sizeAfterZoom = + endZoomToMetrics.CalculateCompositedSizeInCssPixels(); + + // Vertically center the zoomed element in the screen. + if (!zoomOut && (sizeAfterZoom.height > aRect.Height())) { + aRect.MoveByY(-(sizeAfterZoom.height - aRect.Height()) * 0.5f); + if (aRect.Y() < 0.0f) { + aRect.MoveToY(0.0f); + } + } + + // If either of these conditions are met, the page will be + // overscrolled after zoomed + if (aRect.Y() + sizeAfterZoom.height > cssPageRect.Height()) { + aRect.MoveToY(std::max(0.f, cssPageRect.Height() - sizeAfterZoom.height)); + } + if (aRect.X() + sizeAfterZoom.width > cssPageRect.Width()) { + aRect.MoveToX(std::max(0.f, cssPageRect.Width() - sizeAfterZoom.width)); + } + + endZoomToMetrics.SetVisualScrollOffset(aRect.TopLeft()); + endZoomToMetrics.RecalculateLayoutViewportOffset(); + + StartAnimation(new ZoomAnimation( + *this, Metrics().GetVisualScrollOffset(), Metrics().GetZoom(), + endZoomToMetrics.GetVisualScrollOffset(), endZoomToMetrics.GetZoom())); + + // Schedule a repaint now, so the new displayport will be painted before the + // animation finishes. + ParentLayerPoint velocity(0, 0); + ScreenMargin displayportMargins = CalculatePendingDisplayPort( + endZoomToMetrics, velocity, ZoomInProgress::Yes); + endZoomToMetrics.SetPaintRequestTime(TimeStamp::Now()); + + RefPtr controller = GetGeckoContentController(); + if (!controller) { + return; + } + if (controller->IsRepaintThread()) { + RequestContentRepaint(endZoomToMetrics, velocity, displayportMargins, + RepaintUpdateType::eUserAction); + } else { + // See comment on similar code in RequestContentRepaint + mExpectedGeckoMetrics.UpdateFrom(endZoomToMetrics); + + // use a local var to resolve the function overload + auto func = static_cast(&AsyncPanZoomController::RequestContentRepaint); + controller->DispatchToRepaintThread( + NewRunnableMethod( + "layers::AsyncPanZoomController::ZoomToRect", this, func, + endZoomToMetrics, velocity, displayportMargins, + RepaintUpdateType::eUserAction)); + } + } +} + +InputBlockState* AsyncPanZoomController::GetCurrentInputBlock() const { + return GetInputQueue()->GetCurrentBlock(); +} + +TouchBlockState* AsyncPanZoomController::GetCurrentTouchBlock() const { + return GetInputQueue()->GetCurrentTouchBlock(); +} + +PanGestureBlockState* AsyncPanZoomController::GetCurrentPanGestureBlock() + const { + return GetInputQueue()->GetCurrentPanGestureBlock(); +} + +PinchGestureBlockState* AsyncPanZoomController::GetCurrentPinchGestureBlock() + const { + return GetInputQueue()->GetCurrentPinchGestureBlock(); +} + +void AsyncPanZoomController::ResetTouchInputState() { + MultiTouchInput cancel(MultiTouchInput::MULTITOUCH_CANCEL, 0, + TimeStamp::Now(), 0); + RefPtr listener = GetGestureEventListener(); + if (listener) { + listener->HandleInputEvent(cancel); + } + CancelAnimationAndGestureState(); + // Clear overscroll along the entire handoff chain, in case an APZC + // later in the chain is overscrolled. + if (TouchBlockState* block = GetCurrentTouchBlock()) { + block->GetOverscrollHandoffChain()->ClearOverscroll(); + } +} + +void AsyncPanZoomController::CancelAnimationAndGestureState() { + mX.CancelGesture(); + mY.CancelGesture(); + CancelAnimation(CancelAnimationFlags::ScrollSnap); +} + +bool AsyncPanZoomController::HasReadyTouchBlock() const { + return GetInputQueue()->HasReadyTouchBlock(); +} + +bool AsyncPanZoomController::CanHandleScrollOffsetUpdate(PanZoomState aState) { + return aState == PAN_MOMENTUM || aState == TOUCHING || IsPanningState(aState); +} + +bool AsyncPanZoomController::ShouldCancelAnimationForScrollUpdate( + const Maybe& aRelativeDelta) { + // Never call CancelAnimation() for a no-op relative update. + if (aRelativeDelta == Some(CSSPoint())) { + return false; + } + + if (mAnimation) { + return !mAnimation->HandleScrollOffsetUpdate(aRelativeDelta); + } + + return !CanHandleScrollOffsetUpdate(mState); +} + +void AsyncPanZoomController::SetState(PanZoomState aNewState) { + PanZoomState oldState; + + // Intentional scoping for mutex + { + RecursiveMutexAutoLock lock(mRecursiveMutex); + APZC_LOG("%p changing from state %d to %d\n", this, mState, aNewState); + oldState = mState; + mState = aNewState; + } + + DispatchStateChangeNotification(oldState, aNewState); +} + +void AsyncPanZoomController::DispatchStateChangeNotification( + PanZoomState aOldState, PanZoomState aNewState) { + { // scope the lock + RecursiveMutexAutoLock lock(mRecursiveMutex); + if (mNotificationBlockers > 0) { + return; + } + } + + if (RefPtr controller = GetGeckoContentController()) { + if (!IsTransformingState(aOldState) && IsTransformingState(aNewState)) { + controller->NotifyAPZStateChange(GetGuid(), + APZStateChange::eTransformBegin); +#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) + // Let the compositor know about scroll state changes so it can manage + // windowed plugins. + if (StaticPrefs::gfx_e10s_hide_plugins_for_scroll_AtStartup() && + mCompositorController) { + mCompositorController->ScheduleHideAllPluginWindows(); + } +#endif + } else if (IsTransformingState(aOldState) && + !IsTransformingState(aNewState)) { + controller->NotifyAPZStateChange(GetGuid(), + APZStateChange::eTransformEnd); +#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) + if (StaticPrefs::gfx_e10s_hide_plugins_for_scroll_AtStartup() && + mCompositorController) { + mCompositorController->ScheduleShowAllPluginWindows(); + } +#endif + } + } +} + +bool AsyncPanZoomController::IsInTransformingState() const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + return IsTransformingState(mState); +} + +bool AsyncPanZoomController::IsTransformingState(PanZoomState aState) { + return !(aState == NOTHING || aState == TOUCHING); +} + +bool AsyncPanZoomController::IsPanningState(PanZoomState aState) { + return (aState == PANNING || aState == PANNING_LOCKED_X || + aState == PANNING_LOCKED_Y); +} + +bool AsyncPanZoomController::IsInPanningState() const { + return IsPanningState(mState); +} + +void AsyncPanZoomController::UpdateZoomConstraints( + const ZoomConstraints& aConstraints) { + APZC_LOG("%p updating zoom constraints to %d %d %f %f\n", this, + aConstraints.mAllowZoom, aConstraints.mAllowDoubleTapZoom, + aConstraints.mMinZoom.scale, aConstraints.mMaxZoom.scale); + if (IsNaN(aConstraints.mMinZoom.scale) || + IsNaN(aConstraints.mMaxZoom.scale)) { + NS_WARNING("APZC received zoom constraints with NaN values; dropping..."); + return; + } + + RecursiveMutexAutoLock lock(mRecursiveMutex); + CSSToParentLayerScale min = Metrics().GetDevPixelsPerCSSPixel() * + kViewportMinScale / ParentLayerToScreenScale(1); + CSSToParentLayerScale max = Metrics().GetDevPixelsPerCSSPixel() * + kViewportMaxScale / ParentLayerToScreenScale(1); + + // inf float values and other bad cases should be sanitized by the code below. + mZoomConstraints.mAllowZoom = aConstraints.mAllowZoom; + mZoomConstraints.mAllowDoubleTapZoom = aConstraints.mAllowDoubleTapZoom; + mZoomConstraints.mMinZoom = + (min > aConstraints.mMinZoom ? min : aConstraints.mMinZoom); + mZoomConstraints.mMaxZoom = + (max > aConstraints.mMaxZoom ? aConstraints.mMaxZoom : max); + if (mZoomConstraints.mMaxZoom < mZoomConstraints.mMinZoom) { + mZoomConstraints.mMaxZoom = mZoomConstraints.mMinZoom; + } +} + +ZoomConstraints AsyncPanZoomController::GetZoomConstraints() const { + return mZoomConstraints; +} + +void AsyncPanZoomController::PostDelayedTask(already_AddRefed aTask, + int aDelayMs) { + APZThreadUtils::AssertOnControllerThread(); + RefPtr task = aTask; + RefPtr controller = GetGeckoContentController(); + if (controller) { + controller->PostDelayedTask(task.forget(), aDelayMs); + } + // If there is no controller, that means this APZC has been destroyed, and + // we probably don't need to run the task. It will get destroyed when the + // RefPtr goes out of scope. +} + +bool AsyncPanZoomController::Matches(const ScrollableLayerGuid& aGuid) { + return aGuid == GetGuid(); +} + +bool AsyncPanZoomController::HasTreeManager( + const APZCTreeManager* aTreeManager) const { + return GetApzcTreeManager() == aTreeManager; +} + +void AsyncPanZoomController::GetGuid(ScrollableLayerGuid* aGuidOut) const { + if (aGuidOut) { + *aGuidOut = GetGuid(); + } +} + +ScrollableLayerGuid AsyncPanZoomController::GetGuid() const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + return ScrollableLayerGuid(mLayersId, Metrics().GetPresShellId(), + Metrics().GetScrollId()); +} + +void AsyncPanZoomController::UpdateSharedCompositorFrameMetrics() { + mRecursiveMutex.AssertCurrentThreadIn(); + + FrameMetrics* frame = + mSharedFrameMetricsBuffer + ? static_cast(mSharedFrameMetricsBuffer->memory()) + : nullptr; + + if (frame && mSharedLock && StaticPrefs::layers_progressive_paint()) { + mSharedLock->Lock(); + *frame = Metrics(); + mSharedLock->Unlock(); + } +} + +void AsyncPanZoomController::ShareCompositorFrameMetrics() { + AssertOnUpdaterThread(); + + // Only create the shared memory buffer if it hasn't already been created, + // we are using progressive tile painting, and we have a + // controller to pass the shared memory back to the content process/thread. + if (!mSharedFrameMetricsBuffer && mMetricsSharingController && + StaticPrefs::layers_progressive_paint()) { + // Create shared memory and initialize it with the current FrameMetrics + // value + mSharedFrameMetricsBuffer = new ipc::SharedMemoryBasic; + FrameMetrics* frame = nullptr; + mSharedFrameMetricsBuffer->Create(sizeof(FrameMetrics)); + mSharedFrameMetricsBuffer->Map(sizeof(FrameMetrics)); + frame = static_cast(mSharedFrameMetricsBuffer->memory()); + + if (frame) { + { // scope the monitor, only needed to copy the FrameMetrics. + RecursiveMutexAutoLock lock(mRecursiveMutex); + *frame = Metrics(); + } + + // Get the process id of the content process + base::ProcessId otherPid = mMetricsSharingController->RemotePid(); + ipc::SharedMemoryBasic::Handle mem = ipc::SharedMemoryBasic::NULLHandle(); + + // Get the shared memory handle to share with the content process + mSharedFrameMetricsBuffer->ShareToProcess(otherPid, &mem); + + // Get the cross process mutex handle to share with the content process + mSharedLock = new CrossProcessMutex("AsyncPanZoomControlLock"); + CrossProcessMutexHandle handle = mSharedLock->ShareToProcess(otherPid); + + // Send the shared memory handle and cross process handle to the content + // process by an asynchronous ipc call. Include the APZC unique ID + // so the content process know which APZC sent this shared FrameMetrics. + if (!mMetricsSharingController->StartSharingMetrics(mem, handle, + mLayersId, mAPZCId)) { + APZC_LOG("%p failed to share FrameMetrics with content process.", this); + } + } + } +} + +void AsyncPanZoomController::SetTestAsyncScrollOffset(const CSSPoint& aPoint) { + mTestAsyncScrollOffset = aPoint; + ScheduleComposite(); +} + +void AsyncPanZoomController::SetTestAsyncZoom( + const LayerToParentLayerScale& aZoom) { + mTestAsyncZoom = aZoom; + ScheduleComposite(); +} + +Maybe AsyncPanZoomController::FindSnapPointNear( + const CSSPoint& aDestination, ScrollUnit aUnit) { + mRecursiveMutex.AssertCurrentThreadIn(); + APZC_LOG("%p scroll snapping near %s\n", this, + ToString(aDestination).c_str()); + CSSRect scrollRange = Metrics().CalculateScrollRange(); + if (Maybe snapPoint = ScrollSnapUtils::GetSnapPointForDestination( + mScrollMetadata.GetSnapInfo(), aUnit, + CSSRect::ToAppUnits(scrollRange), + CSSPoint::ToAppUnits(Metrics().GetVisualScrollOffset()), + CSSPoint::ToAppUnits(aDestination))) { + CSSPoint cssSnapPoint = CSSPoint::FromAppUnits(snapPoint.ref()); + // GetSnapPointForDestination() can produce a destination that's outside + // of the scroll frame's scroll range. Clamp it here (this matches the + // behaviour of the main-thread code path, which clamps it in + // nsGfxScrollFrame::ScrollTo()). + return Some(scrollRange.ClampPoint(cssSnapPoint)); + } + return Nothing(); +} + +void AsyncPanZoomController::ScrollSnapNear(const CSSPoint& aDestination) { + if (Maybe snapPoint = + FindSnapPointNear(aDestination, ScrollUnit::DEVICE_PIXELS)) { + if (*snapPoint != Metrics().GetVisualScrollOffset()) { + APZC_LOG("%p smooth scrolling to snap point %s\n", this, + ToString(*snapPoint).c_str()); + SmoothMsdScrollTo(*snapPoint); + } + } +} + +void AsyncPanZoomController::ScrollSnap() { + RecursiveMutexAutoLock lock(mRecursiveMutex); + ScrollSnapNear(Metrics().GetVisualScrollOffset()); +} + +void AsyncPanZoomController::ScrollSnapToDestination() { + RecursiveMutexAutoLock lock(mRecursiveMutex); + + float friction = StaticPrefs::apz_fling_friction(); + ParentLayerPoint velocity(mX.GetVelocity(), mY.GetVelocity()); + ParentLayerPoint predictedDelta; + // "-velocity / log(1.0 - friction)" is the integral of the deceleration + // curve modeled for flings in the "Axis" class. + if (velocity.x != 0.0f && friction != 0.0f) { + predictedDelta.x = -velocity.x / log(1.0 - friction); + } + if (velocity.y != 0.0f && friction != 0.0f) { + predictedDelta.y = -velocity.y / log(1.0 - friction); + } + + // If the fling will overscroll, don't scroll snap, because then the user + // user would not see any overscroll animation. + bool flingWillOverscroll = + IsOverscrolled() && ((velocity.x * mX.GetOverscroll() >= 0) || + (velocity.y * mY.GetOverscroll() >= 0)); + if (flingWillOverscroll) { + return; + } + + CSSPoint startPosition = Metrics().GetVisualScrollOffset(); + if (MaybeAdjustDeltaForScrollSnapping(ScrollUnit::LINES, predictedDelta, + startPosition)) { + APZC_LOG( + "%p fling snapping. friction: %f velocity: %f, %f " + "predictedDelta: %f, %f position: %f, %f " + "snapDestination: %f, %f\n", + this, friction, velocity.x, velocity.y, (float)predictedDelta.x, + (float)predictedDelta.y, (float)Metrics().GetVisualScrollOffset().x, + (float)Metrics().GetVisualScrollOffset().y, (float)startPosition.x, + (float)startPosition.y); + + SmoothMsdScrollTo(startPosition); + } +} + +bool AsyncPanZoomController::MaybeAdjustDeltaForScrollSnapping( + ScrollUnit aUnit, ParentLayerPoint& aDelta, CSSPoint& aStartPosition) { + RecursiveMutexAutoLock lock(mRecursiveMutex); + CSSToParentLayerScale2D zoom = Metrics().GetZoom(); + if (zoom == CSSToParentLayerScale2D(0, 0)) { + return false; + } + CSSPoint destination = Metrics().CalculateScrollRange().ClampPoint( + aStartPosition + (aDelta / zoom)); + + if (Maybe snapPoint = FindSnapPointNear(destination, aUnit)) { + aDelta = (*snapPoint - aStartPosition) * zoom; + aStartPosition = *snapPoint; + return true; + } + return false; +} + +bool AsyncPanZoomController::MaybeAdjustDeltaForScrollSnappingOnWheelInput( + const ScrollWheelInput& aEvent, ParentLayerPoint& aDelta, + CSSPoint& aStartPosition) { + // Don't scroll snap for pixel scrolls. This matches the main thread + // behaviour in EventStateManager::DoScrollText(). + if (aEvent.mDeltaType == ScrollWheelInput::SCROLLDELTA_PIXEL) { + return false; + } + + return MaybeAdjustDeltaForScrollSnapping( + ScrollWheelInput::ScrollUnitForDeltaType(aEvent.mDeltaType), aDelta, + aStartPosition); +} + +bool AsyncPanZoomController::MaybeAdjustDestinationForScrollSnapping( + const KeyboardInput& aEvent, CSSPoint& aDestination) { + RecursiveMutexAutoLock lock(mRecursiveMutex); + ScrollUnit unit = KeyboardScrollAction::GetScrollUnit(aEvent.mAction.mType); + + if (Maybe snapPoint = FindSnapPointNear(aDestination, unit)) { + aDestination = *snapPoint; + return true; + } + return false; +} + +void AsyncPanZoomController::SetZoomAnimationId( + const Maybe& aZoomAnimationId) { + mZoomAnimationId = aZoomAnimationId; +} + +Maybe AsyncPanZoomController::GetZoomAnimationId() const { + return mZoomAnimationId; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/src/AsyncPanZoomController.h b/gfx/layers/apz/src/AsyncPanZoomController.h new file mode 100644 index 0000000000..60874b5edf --- /dev/null +++ b/gfx/layers/apz/src/AsyncPanZoomController.h @@ -0,0 +1,1767 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_AsyncPanZoomController_h +#define mozilla_layers_AsyncPanZoomController_h + +#include "CrossProcessMutex.h" +#include "mozilla/layers/GeckoContentController.h" +#include "mozilla/layers/RepaintRequest.h" +#include "mozilla/layers/SampleTime.h" +#include "mozilla/layers/ZoomConstraints.h" +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" +#include "mozilla/EventForwards.h" +#include "mozilla/Monitor.h" +#include "mozilla/RecursiveMutex.h" +#include "mozilla/RefPtr.h" +#include "mozilla/ScrollTypes.h" +#include "mozilla/StaticPrefs_apz.h" +#include "mozilla/UniquePtr.h" +#include "InputData.h" +#include "Axis.h" // for Axis, Side, etc. +#include "ExpectedGeckoMetrics.h" +#include "FlingAccelerator.h" +#include "InputQueue.h" +#include "APZUtils.h" +#include "Layers.h" // for Layer::ScrollDirection +#include "LayersTypes.h" +#include "mozilla/gfx/Matrix.h" +#include "nsRegion.h" +#include "nsTArray.h" +#include "PotentialCheckerboardDurationTracker.h" +#include "RecentEventsBuffer.h" // for RecentEventsBuffer +#include "SampledAPZCState.h" + +namespace mozilla { + +namespace ipc { + +class SharedMemoryBasic; + +} // namespace ipc + +namespace layers { + +class AsyncDragMetrics; +class APZCTreeManager; +struct ScrollableLayerGuid; +class CompositorController; +class MetricsSharingController; +class GestureEventListener; +struct AsyncTransform; +class AsyncPanZoomAnimation; +class StackScrollerFlingAnimation; +template +class GenericFlingAnimation; +class AndroidFlingPhysics; +class DesktopFlingPhysics; +class InputBlockState; +struct FlingHandoffState; +class TouchBlockState; +class PanGestureBlockState; +class OverscrollHandoffChain; +struct OverscrollHandoffState; +class StateChangeNotificationBlocker; +class CheckerboardEvent; +class OverscrollEffectBase; +class WidgetOverscrollEffect; +class GenericOverscrollEffect; +class AndroidSpecificState; +struct KeyboardScrollAction; + +// Base class for grouping platform-specific APZC state variables. +class PlatformSpecificStateBase { + public: + virtual ~PlatformSpecificStateBase() = default; + virtual AndroidSpecificState* AsAndroidSpecificState() { return nullptr; } + // PLPPI = "ParentLayer pixels per (Screen) inch" + virtual AsyncPanZoomAnimation* CreateFlingAnimation( + AsyncPanZoomController& aApzc, const FlingHandoffState& aHandoffState, + float aPLPPI); + virtual UniquePtr CreateVelocityTracker(Axis* aAxis); + + static void InitializeGlobalState() {} +}; + +/* + * Represents a transform from the ParentLayer coordinate space of an APZC + * to the ParentLayer coordinate space of its parent APZC. + * Each layer along the way contributes to the transform. We track + * contributions that are perspective transforms separately, as sometimes + * these require special handling. + */ +struct AncestorTransform { + gfx::Matrix4x4 mTransform; + gfx::Matrix4x4 mPerspectiveTransform; + + AncestorTransform() = default; + + AncestorTransform(const gfx::Matrix4x4& aTransform, + bool aTransformIsPerspective) { + (aTransformIsPerspective ? mPerspectiveTransform : mTransform) = aTransform; + } + + AncestorTransform(const gfx::Matrix4x4& aTransform, + const gfx::Matrix4x4& aPerspectiveTransform) + : mTransform(aTransform), mPerspectiveTransform(aPerspectiveTransform) {} + + gfx::Matrix4x4 CombinedTransform() const { + return mTransform * mPerspectiveTransform; + } + + bool ContainsPerspectiveTransform() const { + return !mPerspectiveTransform.IsIdentity(); + } + + gfx::Matrix4x4 GetPerspectiveTransform() const { + return mPerspectiveTransform; + } + + friend AncestorTransform operator*(const AncestorTransform& aA, + const AncestorTransform& aB) { + return AncestorTransform{ + aA.mTransform * aB.mTransform, + aA.mPerspectiveTransform * aB.mPerspectiveTransform}; + } +}; + +/** + * Controller for all panning and zooming logic. Any time a user input is + * detected and it must be processed in some way to affect what the user sees, + * it goes through here. Listens for any input event from InputData and can + * optionally handle WidgetGUIEvent-derived touch events, but this must be done + * on the main thread. Note that this class completely cross-platform. + * + * Input events originate on the UI thread of the platform that this runs on, + * and are then sent to this class. This class processes the event in some way; + * for example, a touch move will usually lead to a panning of content (though + * of course there are exceptions, such as if content preventDefaults the event, + * or if the target frame is not scrollable). The compositor interacts with this + * class by locking it and querying it for the current transform matrix based on + * the panning and zooming logic that was invoked on the UI thread. + * + * Currently, each outer DOM window (i.e. a website in a tab, but not any + * subframes) has its own AsyncPanZoomController. In the future, to support + * asynchronously scrolled subframes, we want to have one AsyncPanZoomController + * per frame. + */ +class AsyncPanZoomController { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AsyncPanZoomController) + + typedef mozilla::MonitorAutoLock MonitorAutoLock; + typedef mozilla::gfx::Matrix4x4 Matrix4x4; + typedef mozilla::layers::RepaintRequest::ScrollOffsetUpdateType + RepaintUpdateType; + + public: + enum GestureBehavior { + // The platform code is responsible for forwarding gesture events here. We + // will not attempt to generate gesture events from MultiTouchInputs. + DEFAULT_GESTURES, + // An instance of GestureEventListener is used to detect gestures. This is + // handled completely internally within this class. + USE_GESTURE_DETECTOR + }; + + /** + * Gets the DPI from the tree manager. + */ + float GetDPI() const; + + /** + * Constant describing the tolerance in distance we use, multiplied by the + * device DPI, before we start panning the screen. This is to prevent us from + * accidentally processing taps as touch moves, and from very short/accidental + * touches moving the screen. + * Note: It's an abuse of the 'Coord' class to use it to represent a 2D + * distance, but it's the closest thing we currently have. + */ + ScreenCoord GetTouchStartTolerance() const; + /** + * Same as GetTouchStartTolerance, but the tolerance for how far the touch + * has to move before it starts allowing touchmove events to be dispatched + * to content, for non-scrollable content. + */ + ScreenCoord GetTouchMoveTolerance() const; + /** + * Same as GetTouchStartTolerance, but the tolerance for how close the second + * tap has to be to the first tap in order to be counted as part of a + * multi-tap gesture (double-tap or one-touch-pinch). + */ + ScreenCoord GetSecondTapTolerance() const; + + AsyncPanZoomController(LayersId aLayersId, APZCTreeManager* aTreeManager, + const RefPtr& aInputQueue, + GeckoContentController* aController, + GestureBehavior aGestures = DEFAULT_GESTURES); + + // -------------------------------------------------------------------------- + // These methods must only be called on the gecko thread. + // + + /** + * Read the various prefs and do any global initialization for all APZC + * instances. This must be run on the gecko thread before any APZC instances + * are actually used for anything meaningful. + */ + static void InitializeGlobalState(); + + // -------------------------------------------------------------------------- + // These methods must only be called on the controller/UI thread. + // + + /** + * Kicks an animation to zoom to a rect. This may be either a zoom out or zoom + * in. The actual animation is done on the sampler thread after being set + * up. + */ + void ZoomToRect(CSSRect aRect, const uint32_t aFlags); + + /** + * Updates any zoom constraints contained in the tag. + */ + void UpdateZoomConstraints(const ZoomConstraints& aConstraints); + + /** + * Return the zoom constraints last set for this APZC (in the constructor + * or in UpdateZoomConstraints()). + */ + ZoomConstraints GetZoomConstraints() const; + + /** + * Schedules a runnable to run on the controller/UI thread at some time + * in the future. + */ + void PostDelayedTask(already_AddRefed aTask, int aDelayMs); + + // -------------------------------------------------------------------------- + // These methods must only be called on the sampler thread. + // + + /** + * Advances any animations currently running to the given timestamp. + * This may be called multiple times with the same timestamp. + * + * The return value indicates whether or not any currently running animation + * should continue. If true, the compositor should schedule another composite. + */ + bool AdvanceAnimations(const SampleTime& aSampleTime); + + bool UpdateAnimation(const RecursiveMutexAutoLock& aProofOfLock, + const SampleTime& aSampleTime, + nsTArray>* aOutDeferredTasks); + + // -------------------------------------------------------------------------- + // These methods must only be called on the updater thread. + // + + /** + * A shadow layer update has arrived. |aScrollMetdata| is the new + * ScrollMetadata for the container layer corresponding to this APZC. + * |aIsFirstPaint| is a flag passed from the shadow + * layers code indicating that the scroll metadata being sent with this call + * are the initial metadata and the initial paint of the frame has just + * happened. + */ + void NotifyLayersUpdated(const ScrollMetadata& aScrollMetadata, + bool aIsFirstPaint, bool aThisLayerTreeUpdated); + + /** + * The platform implementation must set the compositor controller so that we + * can request composites. + */ + void SetCompositorController(CompositorController* aCompositorController); + + /** + * If we need to share the frame metrics with some other thread, this + * controller needs to be set and provides relevant information/APIs. + */ + void SetMetricsSharingController( + MetricsSharingController* aMetricsSharingController); + + // -------------------------------------------------------------------------- + // These methods can be called from any thread. + // + + /** + * Shut down the controller/UI thread state and prepare to be + * deleted (which may happen from any thread). + */ + void Destroy(); + + /** + * Returns true if Destroy() has already been called on this APZC instance. + */ + bool IsDestroyed() const; + + /** + * Returns the transform to take something from the coordinate space of the + * last thing we know gecko painted, to the coordinate space of the last thing + * we asked gecko to paint. In cases where that last request has not yet been + * processed, this is needed to transform input events properly into a space + * gecko will understand. + */ + Matrix4x4 GetTransformToLastDispatchedPaint() const; + + /** + * Returns the number of CSS pixels of checkerboard according to the metrics + * in this APZC. The argument provided by the caller is the composition bounds + * of this APZC, additionally clipped by the composition bounds of any + * ancestor APZCs, accounting for all the async transforms. + */ + uint32_t GetCheckerboardMagnitude( + const ParentLayerRect& aClippedCompositionBounds) const; + + /** + * Report the number of CSSPixel-milliseconds of checkerboard to telemetry. + * See GetCheckerboardMagnitude for documentation of the + * aClippedCompositionBounds argument that needs to be provided by the caller. + */ + void ReportCheckerboard(const SampleTime& aSampleTime, + const ParentLayerRect& aClippedCompositionBounds); + + /** + * Flush any active checkerboard report that's in progress. This basically + * pretends like any in-progress checkerboard event has terminated, and pushes + * out the report to the checkerboard reporting service and telemetry. If the + * checkerboard event has not really finished, it will start a new event + * on the next composite. + */ + void FlushActiveCheckerboardReport(); + + /** + * See documentation on corresponding method in APZPublicUtils.h + */ + static gfx::IntSize GetDisplayportAlignmentMultiplier( + const ScreenSize& aBaseSize); + + enum class ZoomInProgress { + No, + Yes, + }; + + /** + * Recalculates the displayport. Ideally, this should paint an area bigger + * than the composite-to dimensions so that when you scroll down, you don't + * checkerboard immediately. This includes a bunch of logic, including + * algorithms to bias painting in the direction of the velocity and other + * such things. + */ + static const ScreenMargin CalculatePendingDisplayPort( + const FrameMetrics& aFrameMetrics, const ParentLayerPoint& aVelocity, + ZoomInProgress aZoomInProgress); + + nsEventStatus HandleDragEvent(const MouseInput& aEvent, + const AsyncDragMetrics& aDragMetrics, + CSSCoord aInitialThumbPos); + + /** + * Handler for events which should not be intercepted by the touch listener. + */ + nsEventStatus HandleInputEvent( + const InputData& aEvent, + const ScreenToParentLayerMatrix4x4& aTransformToApzc); + + /** + * Handler for gesture events. + * Currently some gestures are detected in GestureEventListener that calls + * APZC back through this handler in order to avoid recursive calls to + * APZC::HandleInputEvent() which is supposed to do the work for + * ReceiveInputEvent(). + */ + nsEventStatus HandleGestureEvent(const InputData& aEvent); + + /** + * Start autoscrolling this APZC, anchored at the provided location. + */ + void StartAutoscroll(const ScreenPoint& aAnchorLocation); + + /** + * Stop autoscrolling this APZC. + */ + void StopAutoscroll(); + + /** + * Populates the provided object (if non-null) with the scrollable guid of + * this apzc. + */ + void GetGuid(ScrollableLayerGuid* aGuidOut) const; + + /** + * Returns the scrollable guid of this apzc. + */ + ScrollableLayerGuid GetGuid() const; + + /** + * Returns true if this APZC instance is for the layer identified by the guid. + */ + bool Matches(const ScrollableLayerGuid& aGuid); + + /** + * Returns true if the tree manager of this APZC is the same as the one + * passed in. + */ + bool HasTreeManager(const APZCTreeManager* aTreeManager) const; + + void StartAnimation(AsyncPanZoomAnimation* aAnimation); + + /** + * Cancels any currently running animation. + * aFlags is a bit-field to provide specifics of how to cancel the animation. + * See CancelAnimationFlags. + */ + void CancelAnimation(CancelAnimationFlags aFlags = Default); + + /** + * Clear any overscroll on this APZC. + */ + void ClearOverscroll(); + + /** + * Returns whether this APZC is for an element marked with the 'scrollgrab' + * attribute. + */ + bool HasScrollgrab() const { return mScrollMetadata.GetHasScrollgrab(); } + + /** + * Returns whether this APZC has scroll snap points. + */ + bool HasScrollSnapping() const { + return mScrollMetadata.GetSnapInfo().HasScrollSnapping(); + } + + /** + * Returns whether this APZC has room to be panned (in any direction). + */ + bool IsPannable() const; + + /** + * Returns whether this APZC represents a scroll info layer. + */ + bool IsScrollInfoLayer() const; + + /** + * Returns true if the APZC has been flung with a velocity greater than the + * stop-on-tap fling velocity threshold (which is pref-controlled). + */ + bool IsFlingingFast() const; + + /** + * Returns the identifier of the touch in the last touch event processed by + * this APZC. This should only be called when the last touch event contained + * only one touch. + */ + int32_t GetLastTouchIdentifier() const; + + /** + * Returns the matrix that transforms points from global screen space into + * this APZC's ParentLayer space. + * To respect the lock ordering, mRecursiveMutex must NOT be held when calling + * this function (since this function acquires the tree lock). + */ + ScreenToParentLayerMatrix4x4 GetTransformToThis() const; + + /** + * Convert the vector |aVector|, rooted at the point |aAnchor|, from + * this APZC's ParentLayer coordinates into screen coordinates. + * The anchor is necessary because with 3D tranforms, the location of the + * vector can affect the result of the transform. + * To respect the lock ordering, mRecursiveMutex must NOT be held when calling + * this function (since this function acquires the tree lock). + */ + ScreenPoint ToScreenCoordinates(const ParentLayerPoint& aVector, + const ParentLayerPoint& aAnchor) const; + + /** + * Convert the vector |aVector|, rooted at the point |aAnchor|, from + * screen coordinates into this APZC's ParentLayer coordinates. + * The anchor is necessary because with 3D tranforms, the location of the + * vector can affect the result of the transform. + * To respect the lock ordering, mRecursiveMutex must NOT be held when calling + * this function (since this function acquires the tree lock). + */ + ParentLayerPoint ToParentLayerCoordinates(const ScreenPoint& aVector, + const ScreenPoint& aAnchor) const; + + /** + * Same as above, but uses an ExternalPoint as the anchor. + */ + ParentLayerPoint ToParentLayerCoordinates(const ScreenPoint& aVector, + const ExternalPoint& aAnchor) const; + + /** + * Combines an offset defined as an external point, with a window-relative + * offset to give an absolute external point. + */ + static ExternalPoint ToExternalPoint(const ExternalPoint& aScreenOffset, + const ScreenPoint& aScreenPoint); + + /** + * Gets a vector where the head is the given point, and the tail is + * the touch start position. + */ + ScreenPoint PanVector(const ExternalPoint& aPos) const; + + // Return whether or not a wheel event will be able to scroll in either + // direction. + bool CanScroll(const InputData& aEvent) const; + + // Return the directions in which this APZC allows handoff (as governed by + // overscroll-behavior). + ScrollDirections GetAllowedHandoffDirections() const; + + // Return whether or not a scroll delta will be able to scroll in either + // direction. + bool CanScroll(const ParentLayerPoint& aDelta) const; + + // Return whether or not a scroll delta will be able to scroll in either + // direction with wheel. + bool CanScrollWithWheel(const ParentLayerPoint& aDelta) const; + + // Return whether or not there is room to scroll this APZC + // in the given direction. + bool CanScroll(ScrollDirection aDirection) const; + + // Return true if there is room to scroll downwards along with moving the + // dynamic toolbar. + // + // NOTE: This function should be used only for the root content APZC. + bool CanScrollDownwardsWithDynamicToolbar() const; + + // Return true if there is room to scroll downwards. + bool CanScrollDownwards() const; + + /** + * Convert a point on the scrollbar from this APZC's ParentLayer coordinates + * to CSS coordinates relative to the beginning of the scroll track. + * Only the component in the direction of scrolling is returned. + */ + CSSCoord ConvertScrollbarPoint(const ParentLayerPoint& aScrollbarPoint, + const ScrollbarData& aThumbData) const; + + void NotifyMozMouseScrollEvent(const nsString& aString) const; + + bool OverscrollBehaviorAllowsSwipe() const; + + //|Metrics()| and |Metrics() const| are getter functions that both return + // mScrollMetadata.mMetrics + + const FrameMetrics& Metrics() const; + FrameMetrics& Metrics(); + + /** + * Get the GeckoViewMetrics to be sent to Gecko for the current composite. + */ + GeckoViewMetrics GetGeckoViewMetrics() const; + + // Helper function to compare root frame metrics and update them + // Returns true when the metrics have changed and were updated. + bool UpdateRootFrameMetricsIfChanged(GeckoViewMetrics& aMetrics); + + // Returns the cached current frame time. + SampleTime GetFrameTime() const; + + private: + // Get whether the horizontal content of the honoured target of auto-dir + // scrolling starts from right to left. If you don't know of auto-dir + // scrolling or what a honoured target means, + // @see mozilla::WheelDeltaAdjustmentStrategy + bool IsContentOfHonouredTargetRightToLeft(bool aHonoursRoot) const; + + protected: + // Protected destructor, to discourage deletion outside of Release(): + virtual ~AsyncPanZoomController(); + + /** + * Helper method for touches beginning. Sets everything up for panning and any + * multitouch gestures. + */ + nsEventStatus OnTouchStart(const MultiTouchInput& aEvent); + + /** + * Helper method for touches moving. Does any transforms needed when panning. + */ + nsEventStatus OnTouchMove(const MultiTouchInput& aEvent); + + /** + * Helper method for touches ending. Redraws the screen if necessary and does + * any cleanup after a touch has ended. + */ + nsEventStatus OnTouchEnd(const MultiTouchInput& aEvent); + + /** + * Helper method for touches being cancelled. Treated roughly the same as a + * touch ending (OnTouchEnd()). + */ + nsEventStatus OnTouchCancel(const MultiTouchInput& aEvent); + + /** + * Helper method for scales beginning. Distinct from the OnTouch* handlers in + * that this implies some outside implementation has determined that the user + * is pinching. + */ + nsEventStatus OnScaleBegin(const PinchGestureInput& aEvent); + + /** + * Helper method for scaling. As the user moves their fingers when pinching, + * this changes the scale of the page. + */ + nsEventStatus OnScale(const PinchGestureInput& aEvent); + + /** + * Helper method for scales ending. Redraws the screen if necessary and does + * any cleanup after a scale has ended. + */ + nsEventStatus OnScaleEnd(const PinchGestureInput& aEvent); + + /** + * Helper methods for handling pan events. + */ + nsEventStatus OnPanMayBegin(const PanGestureInput& aEvent); + nsEventStatus OnPanCancelled(const PanGestureInput& aEvent); + nsEventStatus OnPanBegin(const PanGestureInput& aEvent); + nsEventStatus OnPan(const PanGestureInput& aEvent, bool aFingersOnTouchpad); + nsEventStatus OnPanEnd(const PanGestureInput& aEvent); + nsEventStatus OnPanMomentumStart(const PanGestureInput& aEvent); + nsEventStatus OnPanMomentumEnd(const PanGestureInput& aEvent); + nsEventStatus HandleEndOfPan(); + + /** + * Helper methods for handling scroll wheel events. + */ + nsEventStatus OnScrollWheel(const ScrollWheelInput& aEvent); + + /** + * Gets the scroll wheel delta's values in parent-layer pixels from the + * original delta's values of a wheel input. + */ + ParentLayerPoint GetScrollWheelDelta(const ScrollWheelInput& aEvent) const; + + /** + * This function is like GetScrollWheelDelta(aEvent). + * The difference is the four added parameters provide values as alternatives + * to the original wheel input's delta values, so |aEvent|'s delta values are + * ignored in this function, we only use some other member variables and + * functions of |aEvent|. + */ + ParentLayerPoint GetScrollWheelDelta(const ScrollWheelInput& aEvent, + double aDeltaX, double aDeltaY, + double aMultiplierX, + double aMultiplierY) const; + + /** + * This deleted function is used for: + * 1. avoiding accidental implicit value type conversions of input delta + * values when callers intend to call the above function; + * 2. decoupling the manual relationship between the delta value type and the + * above function. If by any chance the defined delta value type in + * ScrollWheelInput has changed, this will automatically result in build + * time failure, so we can learn of it the first time and accordingly + * redefine those parameters' value types in the above function. + */ + template + ParentLayerPoint GetScrollWheelDelta(ScrollWheelInput&, T, T, T, T) = delete; + + /** + * Helper methods for handling keyboard events. + */ + nsEventStatus OnKeyboard(const KeyboardInput& aEvent); + + CSSPoint GetKeyboardDestination(const KeyboardScrollAction& aAction) const; + + /** + * Helper methods for long press gestures. + */ + MOZ_CAN_RUN_SCRIPT_BOUNDARY + nsEventStatus OnLongPress(const TapGestureInput& aEvent); + nsEventStatus OnLongPressUp(const TapGestureInput& aEvent); + + /** + * Helper method for single tap gestures. + */ + nsEventStatus OnSingleTapUp(const TapGestureInput& aEvent); + + /** + * Helper method for a single tap confirmed. + */ + nsEventStatus OnSingleTapConfirmed(const TapGestureInput& aEvent); + + /** + * Helper method for double taps. + */ + MOZ_CAN_RUN_SCRIPT_BOUNDARY + nsEventStatus OnDoubleTap(const TapGestureInput& aEvent); + + /** + * Helper method for double taps where the double-tap gesture is disabled. + */ + nsEventStatus OnSecondTap(const TapGestureInput& aEvent); + + /** + * Helper method to cancel any gesture currently going to Gecko. Used + * primarily when a user taps the screen over some clickable content but then + * pans down instead of letting go (i.e. to cancel a previous touch so that a + * new one can properly take effect. + */ + nsEventStatus OnCancelTap(const TapGestureInput& aEvent); + + /** + * The following five methods modify the scroll offset. For the APZC + * representing the RCD-RSF, they also recalculate the offset of the layout + * viewport. + */ + + /** + * Scroll the scroll frame to an X,Y offset. + */ + void SetVisualScrollOffset(const CSSPoint& aOffset); + + /** + * Scroll the scroll frame to an X,Y offset, clamping the resulting scroll + * offset to the scroll range. + */ + void ClampAndSetVisualScrollOffset(const CSSPoint& aOffset); + + /** + * Scroll the scroll frame by an X,Y offset. + * The resulting scroll offset is not clamped to the scrollable rect; + * the caller must ensure it stays within range. + */ + void ScrollBy(const CSSPoint& aOffset); + + /** + * Scroll the scroll frame by an X,Y offset, clamping the resulting + * scroll offset to the scroll range. + */ + void ScrollByAndClamp(const CSSPoint& aOffset); + + /** + * Scales the viewport by an amount (note that it multiplies this scale in to + * the current scale, it doesn't set it to |aScale|). Also considers a focus + * point so that the page zooms inward/outward from that point. + */ + void ScaleWithFocus(float aScale, const CSSPoint& aFocus); + + /** + * Schedules a composite on the compositor thread. + */ + void ScheduleComposite(); + + /** + * Schedules a composite, and if enough time has elapsed since the last + * paint, a paint. + */ + void ScheduleCompositeAndMaybeRepaint(); + + /** + * Gets the start point of the current touch. + * This only makes sense if a touch is currently happening and OnTouchMove() + * or the equivalent for pan gestures is being invoked. + */ + ParentLayerPoint PanStart() const; + + /** + * Gets a vector of the velocities of each axis. + */ + const ParentLayerPoint GetVelocityVector() const; + + /** + * Sets the velocities of each axis. + */ + void SetVelocityVector(const ParentLayerPoint& aVelocityVector); + + /** + * Gets the first touch point from a MultiTouchInput. This gets only + * the first one and assumes the rest are either missing or not relevant. + */ + ParentLayerPoint GetFirstTouchPoint(const MultiTouchInput& aEvent); + + /** + * Gets the relevant point in the event + * (eg. first touch, or pinch focus point) of the given InputData. + */ + ExternalPoint GetExternalPoint(const InputData& aEvent); + + /** + * Gets the relevant point in the event, in external screen coordinates. + */ + ExternalPoint GetFirstExternalTouchPoint(const MultiTouchInput& aEvent); + + /** + * Sets the panning state basing on the pan direction angle and current + * touch-action value. + */ + void HandlePanningWithTouchAction(double angle); + + /** + * Sets the panning state ignoring the touch action value. + */ + void HandlePanning(double angle); + + /** + * Update the panning state and axis locks. + */ + void HandlePanningUpdate(const ScreenPoint& aDelta); + + /** + * Set and update the pinch lock + */ + void HandlePinchLocking(const PinchGestureInput& aEvent); + + /** + * Sets up anything needed for panning. This takes us out of the "TOUCHING" + * state and starts actually panning us. We provide the physical pixel + * position of the start point so that the pan gesture is calculated + * regardless of if the window/GeckoView moved during the pan. + */ + nsEventStatus StartPanning(const ExternalPoint& aStartPoint, + const TimeStamp& aEventTime); + + /** + * Wrapper for Axis::UpdateWithTouchAtDevicePoint(). Calls this function for + * both axes and factors in the time delta from the last update. + */ + void UpdateWithTouchAtDevicePoint(const MultiTouchInput& aEvent); + + /** + * Does any panning required due to a new touch event. + */ + void TrackTouch(const MultiTouchInput& aEvent); + + /** + * Register the start of a touch or pan gesture at the given position and + * time. + */ + void StartTouch(const ParentLayerPoint& aPoint, TimeStamp aTimestamp); + + /** + * Register the end of a touch or pan gesture at the given time. + */ + void EndTouch(TimeStamp aTimestamp); + + /** + * Utility function to send updated FrameMetrics to Gecko so that it can paint + * the displayport area. Calls into GeckoContentController to do the actual + * work. This call will use the current metrics. If this function is called + * from a non-main thread, it will redispatch itself to the main thread, and + * use the latest metrics during the redispatch. + */ + void RequestContentRepaint( + RepaintUpdateType aUpdateType = RepaintUpdateType::eUserAction); + + /** + * Send the provided metrics to Gecko to trigger a repaint. This function + * may filter duplicate calls with the same metrics. This function must be + * called on the main thread. + */ + void RequestContentRepaint(const FrameMetrics& aFrameMetrics, + const ParentLayerPoint& aVelocity, + const ScreenMargin& aDisplayportMargins, + RepaintUpdateType aUpdateType); + + /** + * Gets the current frame metrics. This is *not* the Gecko copy stored in the + * layers code. + */ + const FrameMetrics& GetFrameMetrics() const; + + /** + * Gets the current scroll metadata. This is *not* the Gecko copy stored in + * the layers code/ + */ + const ScrollMetadata& GetScrollMetadata() const; + + /** + * Gets the pointer to the apzc tree manager. All the access to tree manager + * should be made via this method and not via private variable since this + * method ensures that no lock is set. + */ + APZCTreeManager* GetApzcTreeManager() const; + + void AssertOnSamplerThread() const; + void AssertOnUpdaterThread() const; + + /** + * Convert ScreenPoint relative to the screen to LayoutDevicePoint relative + * to the parent document. This excludes the transient compositor transform. + * NOTE: This must be converted to LayoutDevicePoint relative to the child + * document before sending over IPC to a child process. + */ + Maybe ConvertToGecko(const ScreenIntPoint& aPoint); + + enum AxisLockMode { + FREE, /* No locking at all */ + STANDARD, /* Default axis locking mode that remains locked until pan ends*/ + STICKY, /* Allow lock to be broken, with hysteresis */ + }; + + static AxisLockMode GetAxisLockMode(); + + enum PinchLockMode { + PINCH_FREE, /* No locking at all */ + PINCH_STANDARD, /* Default pinch locking mode that remains locked until + pinch gesture ends*/ + PINCH_STICKY, /* Allow lock to be broken, with hysteresis */ + }; + + static PinchLockMode GetPinchLockMode(); + + // Helper function for OnSingleTapUp(), OnSingleTapConfirmed(), and + // OnLongPressUp(). + nsEventStatus GenerateSingleTap(GeckoContentController::TapType aType, + const ScreenIntPoint& aPoint, + mozilla::Modifiers aModifiers); + + // Common processing at the end of a touch block. + void OnTouchEndOrCancel(); + + LayersId mLayersId; + RefPtr mCompositorController; + RefPtr mMetricsSharingController; + + /* Access to the following two fields is protected by the mRefPtrMonitor, + since they are accessed on the UI thread but can be cleared on the + updater thread. */ + RefPtr mGeckoContentController; + RefPtr mGestureEventListener; + mutable Monitor mRefPtrMonitor; + + // This is a raw pointer to avoid introducing a reference cycle between + // AsyncPanZoomController and APZCTreeManager. Since these objects don't + // live on the main thread, we can't use the cycle collector with them. + // The APZCTreeManager owns the lifetime of the APZCs, so nulling this + // pointer out in Destroy() will prevent accessing deleted memory. + Atomic mTreeManager; + + /* Utility functions that return a addrefed pointer to the corresponding + * fields. */ + already_AddRefed GetGeckoContentController() const; + already_AddRefed GetGestureEventListener() const; + + PlatformSpecificStateBase* GetPlatformSpecificState(); + + protected: + // Both |mScrollMetadata| and |mLastContentPaintMetrics| are protected by the + // monitor. Do not read from or modify them without locking. + ScrollMetadata mScrollMetadata; + + // Protects |mScrollMetadata|, |mLastContentPaintMetrics| and |mState|. + // Before manipulating |mScrollMetadata| or |mLastContentPaintMetrics| the + // monitor should be held. When setting |mState|, either the SetState() + // function can be used, or the monitor can be held and then |mState| updated. + // IMPORTANT: See the note about lock ordering at the top of + // APZCTreeManager.h. This is mutable to allow entering it from 'const' + // methods; doing otherwise would significantly limit what methods could be + // 'const'. + // FIXME: Please keep in mind that due to some existing coupled relationships + // among the class members, we should be aware of indirect usage of the + // monitor-protected members. That is, although this monitor isn't required to + // be held before manipulating non-protected class members, some functions on + // those members might indirectly manipulate the protected members; in such + // cases, the monitor should still be held. Let's take mX.CanScroll for + // example: + // Axis::CanScroll(ParentLayerCoord) calls Axis::CanScroll() which calls + // Axis::GetPageLength() which calls Axis::GetFrameMetrics() which calls + // AsyncPanZoomController::GetFrameMetrics(), therefore, this monitor should + // be held before calling the CanScroll function of |mX| and |mY|. These + // coupled relationships bring us the burden of taking care of when the + // monitor should be held, so they should be decoupled in the future. + mutable RecursiveMutex mRecursiveMutex; + + private: + // Metadata of the container layer corresponding to this APZC. This is + // stored here so that it is accessible from the UI/controller thread. + // These are the metrics at last content paint, the most recent + // values we were notified of in NotifyLayersUpdate(). Since it represents + // the Gecko state, it should be used as a basis for untransformation when + // sending messages back to Gecko. + ScrollMetadata mLastContentPaintMetadata; + FrameMetrics& mLastContentPaintMetrics; // for convenience, refers to + // mLastContentPaintMetadata.mMetrics + // The last content repaint request. + RepaintRequest mLastPaintRequestMetrics; + // The metrics that we expect content to have. This is updated when we + // request a content repaint, and when we receive a shadow layers update. + // This allows us to transform events into Gecko's coordinate space. + ExpectedGeckoMetrics mExpectedGeckoMetrics; + + // This holds important state from the Metrics() at previous times + // SampleCompositedAsyncTransform() was called. This will always have at least + // one item. mRecursiveMutex must be held when using or modifying this member. + // Samples should be inserted to the "back" of the deque and extracted from + // the "front". + std::deque mSampledState; + + // Groups state variables that are specific to a platform. + // Initialized on first use. + UniquePtr mPlatformSpecificState; + + AxisX mX; + AxisY mY; + + // This flag is set to true when we are in a axis-locked pan as a result of + // the touch-action CSS property. + bool mPanDirRestricted; + + // This flag is set to true when we are in a pinch-locked state. ie: user + // is performing a two-finger pan rather than a pinch gesture + bool mPinchLocked; + + // Stores the pinch events that occured within a given timeframe. Used to + // calculate the focusChange and spanDistance within a fixed timeframe. + // RecentEventsBuffer is not threadsafe. Should only be accessed on the + // controller thread. + RecentEventsBuffer mPinchEventBuffer; + + // Most up-to-date constraints on zooming. These should always be reasonable + // values; for example, allowing a min zoom of 0.0 can cause very bad things + // to happen. + ZoomConstraints mZoomConstraints; + + // The last time the compositor has sampled the content transform for this + // frame. + SampleTime mLastSampleTime; + + // The last sample time at which we submitted a checkerboarding report. + SampleTime mLastCheckerboardReport; + + // Stores the previous focus point if there is a pinch gesture happening. Used + // to allow panning by moving multiple fingers (thus moving the focus point). + ParentLayerPoint mLastZoomFocus; + + RefPtr mAnimation; + + UniquePtr mOverscrollEffect; + + // Zoom animation id, used for zooming in WebRender. This should only be + // set on the APZC instance for the root content document (i.e. the one we + // support zooming on), and is only used if WebRender is enabled. The + // animation id itself refers to the transform animation id that was set on + // the stacking context in the WR display list. By changing the transform + // associated with this id, we can adjust the scaling that WebRender applies, + // thereby controlling the zoom. + Maybe mZoomAnimationId; + + // Position on screen where user first put their finger down. + ExternalPoint mStartTouch; + + // Accessing mScrollPayload needs to be protected by mRecursiveMutex + Maybe mScrollPayload; + + friend class Axis; + + public: + Maybe NotifyScrollSampling(); + + /** + * Invoke |callable|, passing |mLastContentPaintMetrics| as argument, + * while holding the APZC lock required to access |mLastContentPaintMetrics|. + * This allows code outside of an AsyncPanZoomController method implementation + * to access |mLastContentPaintMetrics| without having to make a copy of it. + * Passes through the return value of |callable|. + */ + template + auto CallWithLastContentPaintMetrics(const Callable& callable) const + -> decltype(callable(mLastContentPaintMetrics)) { + RecursiveMutexAutoLock lock(mRecursiveMutex); + return callable(mLastContentPaintMetrics); + } + + void SetZoomAnimationId(const Maybe& aZoomAnimationId); + Maybe GetZoomAnimationId() const; + + /* =================================================================== + * The functions and members in this section are used to expose + * the current async transform state to callers. + */ + public: + /** + * Allows consumers of async transforms to specify for what purpose they are + * using the async transform: + * + * |eForHitTesting| is intended for hit-testing and other uses that need + * the most up-to-date transform, reflecting all events + * that have been processed so far, even if the transform + * is not yet reflected visually. + * |eForCompositing| is intended for the transform that should be reflected + * visually. + * + * For example, if an APZC has metrics with the mForceDisableApz flag set, + * then the |eForCompositing| async transform will be empty, while the + * |eForHitTesting| async transform will reflect processed input events + * regardless of mForceDisableApz. + */ + enum AsyncTransformConsumer { + eForHitTesting, + eForCompositing, + }; + + /** + * Get the current layout viewport of the scrollable frame corresponding to + * this APZC. + */ + CSSRect GetCurrentAsyncLayoutViewport(AsyncTransformConsumer aMode) const; + + /** + * Get the current scroll offset of the scrollable frame corresponding + * to this APZC, including the effects of any asynchronous panning and + * zooming, in ParentLayer pixels. + */ + ParentLayerPoint GetCurrentAsyncScrollOffset( + AsyncTransformConsumer aMode) const; + + /** + * Get the current scroll offset of the scrollable frame corresponding + * to this APZC, including the effects of any asynchronous panning, in + * CSS pixels. + */ + CSSPoint GetCurrentAsyncScrollOffsetInCssPixels( + AsyncTransformConsumer aMode) const; + + /** + * Return a visual effect that reflects this apzc's + * overscrolled state, if any. + */ + AsyncTransformComponentMatrix GetOverscrollTransform( + AsyncTransformConsumer aMode) const; + + /** + * Returns the incremental transformation corresponding to the async pan/zoom + * in progress. That is, when this transform is multiplied with the layer's + * existing transform, it will make the layer appear with the desired pan/zoom + * amount. + * The transform can have both scroll and zoom components; the caller can + * request just one or the other, or both, via the |aComponents| parameter. + * When only the eLayout component is requested, the returned translation + * should really be a LayerPoint, rather than a ParentLayerPoint, as it will + * not be scaled by the asynchronous zoom. + */ + AsyncTransform GetCurrentAsyncTransform( + AsyncTransformConsumer aMode, + AsyncTransformComponents aComponents = LayoutAndVisual) const; + + /** + * Returns the same transform as GetCurrentAsyncTransform(), but includes + * any transform due to axis over-scroll. + */ + AsyncTransformComponentMatrix GetCurrentAsyncTransformWithOverscroll( + AsyncTransformConsumer aMode, + AsyncTransformComponents aComponents = LayoutAndVisual) const; + + /** + * Returns the "zoom" bits of the transform. This includes both the rasterized + * (layout device to layer scale) and async (layer scale to parent layer + * scale) components of the zoom. + */ + LayoutDeviceToParentLayerScale GetCurrentPinchZoomScale( + AsyncTransformConsumer aMode) const; + + ParentLayerRect GetCompositionBounds() const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + return mScrollMetadata.GetMetrics().GetCompositionBounds(); + } + + LayoutDeviceToLayerScale2D GetCumulativeResolution() const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + return mScrollMetadata.GetMetrics().GetCumulativeResolution(); + } + + private: + /** + * Advances to the next sample, if there is one, the list of sampled states + * stored in mSampledState. This will make the result of + * |GetCurrentAsyncTransform(eForCompositing)| and similar functions reflect + * the async scroll offset and zoom of the next sample. See also + * SampleCompositedAsyncTransform which creates the samples. + */ + void AdvanceToNextSample(); + + /** + * Samples the composited async transform, storing the result into + * mSampledState. This will make the result of + * |GetCurrentAsyncTransform(eForCompositing)| and similar functions reflect + * the async scroll offset and zoom stored in |Metrics()| when the sample + * is activated via some future call to |AdvanceToNextSample|. + * + * Returns true if the newly sampled value is different from the last + * sampled value. + */ + bool SampleCompositedAsyncTransform( + const RecursiveMutexAutoLock& aProofOfLock); + + /** + * Updates the sample at the front of mSampledState with the latest + * metrics. This makes the result of + * |GetCurrentAsyncTransform(eForCompositing)| reflect the current Metrics(). + */ + void ResampleCompositedAsyncTransform( + const RecursiveMutexAutoLock& aProofOfLock); + + /* + * Helper functions to query the async layout viewport, scroll offset, and + * zoom either directly from |Metrics()|, or from cached variables that + * store the required value from the last time it was sampled by calling + * SampleCompositedAsyncTransform(), depending on who is asking. + */ + CSSRect GetEffectiveLayoutViewport( + AsyncTransformConsumer aMode, + const RecursiveMutexAutoLock& aProofOfLock) const; + CSSPoint GetEffectiveScrollOffset( + AsyncTransformConsumer aMode, + const RecursiveMutexAutoLock& aProofOfLock) const; + CSSToParentLayerScale2D GetEffectiveZoom( + AsyncTransformConsumer aMode, + const RecursiveMutexAutoLock& aProofOfLock) const; + + /** + * Returns the visible portion of the content scrolled by this APZC, in + * CSS pixels. The caller must have acquired the mRecursiveMutex lock. + */ + CSSRect GetVisibleRect(const RecursiveMutexAutoLock& aProofOfLock) const; + + private: + friend class AutoApplyAsyncTestAttributes; + + /** + * Applies |mTestAsyncScrollOffset| and |mTestAsyncZoom| to this + * AsyncPanZoomController. Calls |SampleCompositedAsyncTransform| to ensure + * that the GetCurrentAsync* functions consider the test offset and zoom in + * their computations. + */ + void ApplyAsyncTestAttributes(const RecursiveMutexAutoLock& aProofOfLock); + + /** + * Sets this AsyncPanZoomController's FrameMetrics to |aPrevFrameMetrics| and + * calls |SampleCompositedAsyncTransform| to unapply any test values applied + * by |ApplyAsyncTestAttributes|. + */ + void UnapplyAsyncTestAttributes(const RecursiveMutexAutoLock& aProofOfLock, + const FrameMetrics& aPrevFrameMetrics); + + /* =================================================================== + * The functions and members in this section are used to manage + * the state that tracks what this APZC is doing with the input events. + */ + protected: + enum PanZoomState { + NOTHING, /* no touch-start events received */ + FLING, /* all touches removed, but we're still scrolling page */ + TOUCHING, /* one touch-start event received */ + + PANNING, /* panning the frame */ + PANNING_LOCKED_X, /* touch-start followed by move (i.e. panning with axis + lock) X axis */ + PANNING_LOCKED_Y, /* as above for Y axis */ + + PAN_MOMENTUM, /* like PANNING, but controlled by momentum PanGestureInput + events */ + + PINCHING, /* nth touch-start, where n > 1. this mode allows pan and zoom */ + ANIMATING_ZOOM, /* animated zoom to a new rect */ + OVERSCROLL_ANIMATION, /* Spring-based animation used to relieve overscroll + once the finger is lifted. */ + SMOOTH_SCROLL, /* Smooth scrolling to destination, with physics + controlled by prefs specific to the scroll origin. */ + SMOOTHMSD_SCROLL, /* SmoothMSD scrolling to destination. Used by + CSSOM-View smooth scroll-behavior */ + WHEEL_SCROLL, /* Smooth scrolling to a destination for a wheel event. */ + KEYBOARD_SCROLL, /* Smooth scrolling to a destination for a keyboard event. + */ + AUTOSCROLL, /* Autoscroll animation. */ + SCROLLBAR_DRAG /* Async scrollbar drag. */ + }; + // This is in theory protected by |mRecursiveMutex|; that is, it should be + // held whenever this is updated. In practice though... see bug 897017. + PanZoomState mState; + + static bool IsPanningState(PanZoomState aState); + + /** + * Returns whether the specified PanZoomState does not need to be reset when + * a scroll offset update is processed. + */ + static bool CanHandleScrollOffsetUpdate(PanZoomState aState); + + /** + * Determine whether a main-thread scroll offset update should result in + * a call to CancelAnimation() (which interrupts in-progress animations and + * gestures). + * + * If the update is a relative update, |aRelativeDelta| contains its amount. + * If the update is not a relative update, GetMetrics() should already reflect + * the new offset at the time of the call. + */ + bool ShouldCancelAnimationForScrollUpdate( + const Maybe& aRelativeDelta); + + private: + friend class StateChangeNotificationBlocker; + /** + * A counter of how many StateChangeNotificationBlockers are active. + * A non-zero count will prevent state change notifications from + * being dispatched. Only code that holds mRecursiveMutex should touch this. + */ + int mNotificationBlockers; + + /** + * Helper to set the current state. Holds the monitor before actually setting + * it and fires content controller events based on state changes. Always set + * the state using this call, do not set it directly. + */ + void SetState(PanZoomState aState); + /** + * Fire content controller notifications about state changes, assuming no + * StateChangeNotificationBlocker has been activated. + */ + void DispatchStateChangeNotification(PanZoomState aOldState, + PanZoomState aNewState); + /** + * Internal helpers for checking general state of this apzc. + */ + bool IsInTransformingState() const; + static bool IsTransformingState(PanZoomState aState); + + /* =================================================================== + * The functions and members in this section are used to manage + * blocks of touch events and the state needed to deal with content + * listeners. + */ + public: + /** + * Flush a repaint request if one is needed, without throttling it with the + * paint throttler. + */ + void FlushRepaintForNewInputBlock(); + + /** + * Given an input event and the touch block it belongs to, check if the + * event can lead to a panning/zooming behavior. + * This is primarily used to figure out when to dispatch the pointercancel + * event for the pointer events spec. + */ + bool ArePointerEventsConsumable(TouchBlockState* aBlock, + const MultiTouchInput& aInput); + + /** + * Clear internal state relating to touch input handling. + */ + void ResetTouchInputState(); + + /** + * Gets a ref to the input queue that is shared across the entire tree + * manager. + */ + const RefPtr& GetInputQueue() const; + + private: + void CancelAnimationAndGestureState(); + + RefPtr mInputQueue; + InputBlockState* GetCurrentInputBlock() const; + TouchBlockState* GetCurrentTouchBlock() const; + bool HasReadyTouchBlock() const; + + PanGestureBlockState* GetCurrentPanGestureBlock() const; + PinchGestureBlockState* GetCurrentPinchGestureBlock() const; + + private: + /* =================================================================== + * The functions and members in this section are used to manage + * fling animations, smooth scroll animations, and overscroll + * during a fling or smooth scroll. + */ + public: + /** + * Attempt a fling with the velocity specified in |aHandoffState|. + * |aHandoffState.mIsHandoff| should be true iff. the fling was handed off + * from a previous APZC, and determines whether acceleration is applied + * to the fling. + * We only accept the fling in the direction(s) in which we are pannable. + * Returns the "residual velocity", i.e. the portion of + * |aHandoffState.mVelocity| that this APZC did not consume. + */ + ParentLayerPoint AttemptFling(const FlingHandoffState& aHandoffState); + + ParentLayerPoint AdjustHandoffVelocityForOverscrollBehavior( + ParentLayerPoint& aHandoffVelocity) const; + + private: + friend class StackScrollerFlingAnimation; + friend class AutoscrollAnimation; + template + friend class GenericFlingAnimation; + friend class AndroidFlingPhysics; + friend class DesktopFlingPhysics; + friend class OverscrollAnimation; + friend class SmoothMsdScrollAnimation; + friend class GenericScrollAnimation; + friend class WheelScrollAnimation; + friend class KeyboardScrollAnimation; + friend class ZoomAnimation; + + friend class GenericOverscrollEffect; + friend class WidgetOverscrollEffect; + + FlingAccelerator mFlingAccelerator; + + // Indicates if the repaint-during-pinch timer is currently set + bool mPinchPaintTimerSet; + + // Deal with overscroll resulting from a fling animation. This is only ever + // called on APZC instances that were actually performing a fling. + // The overscroll is handled by trying to hand the fling off to an APZC + // later in the handoff chain, or if there are no takers, continuing the + // fling and entering an overscrolled state. + void HandleFlingOverscroll( + const ParentLayerPoint& aVelocity, + const RefPtr& aOverscrollHandoffChain, + const RefPtr& aScrolledApzc); + + void HandleSmoothScrollOverscroll(const ParentLayerPoint& aVelocity); + + // Start an overscroll animation with the given initial velocity. + void StartOverscrollAnimation(const ParentLayerPoint& aVelocity); + + // Start a smooth-scrolling animation to the given destination, with physics + // based on the prefs for the indicated origin. + void SmoothScrollTo(const CSSPoint& aDestination, + const ScrollOrigin& aOrigin); + + // Start a smooth-scrolling animation to the given destination, with MSD + // physics that is suited for scroll-snapping. + void SmoothMsdScrollTo(const CSSPoint& aDestination); + + // Returns whether overscroll is allowed during an event. + bool AllowScrollHandoffInCurrentBlock() const; + + // Invoked by the pinch repaint timer. + void DoDelayedRequestContentRepaint(); + + // Compute the number of ParentLayer pixels per (Screen) inch at the given + // point and in the given direction. + float ComputePLPPI(ParentLayerPoint aPoint, + ParentLayerPoint aDirection) const; + + Maybe GetCurrentAnimationDestination( + const RecursiveMutexAutoLock& aProofOfLock) const; + + /* =================================================================== + * The functions and members in this section are used to make ancestor chains + * out of APZC instances. These chains can only be walked or manipulated + * while holding the lock in the associated APZCTreeManager instance. + */ + public: + void SetParent(AsyncPanZoomController* aParent) { mParent = aParent; } + + AsyncPanZoomController* GetParent() const { return mParent; } + + /* Returns true if there is no APZC higher in the tree with the same + * layers id. + */ + bool HasNoParentWithSameLayersId() const { + return !mParent || (mParent->mLayersId != mLayersId); + } + + bool IsRootForLayersId() const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + return mScrollMetadata.IsLayersIdRoot(); + } + + bool IsRootContent() const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + return Metrics().IsRootContent(); + } + + private: + // |mTreeManager| belongs in this section but it's declaration is a bit + // further above due to initialization-order constraints. + + RefPtr mParent; + + /* =================================================================== + * The functions and members in this section are used for scrolling, + * including handing off scroll to another APZC, and overscrolling. + */ + + ScrollableLayerGuid::ViewID GetScrollId() const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + return Metrics().GetScrollId(); + } + + public: + ScrollableLayerGuid::ViewID GetScrollHandoffParentId() const { + return mScrollMetadata.GetScrollParentId(); + } + + /** + * Attempt to scroll in response to a touch-move from |aStartPoint| to + * |aEndPoint|, which are in our (transformed) screen coordinates. + * Due to overscroll handling, there may not actually have been a touch-move + * at these points, but this function will scroll as if there had been. + * If this attempt causes overscroll (i.e. the layer cannot be scrolled + * by the entire amount requested), the overscroll is passed back to the + * tree manager via APZCTreeManager::DispatchScroll(). If the tree manager + * does not find an APZC further in the handoff chain to accept the + * overscroll, and this APZC is pannable, this APZC enters an overscrolled + * state. + * |aOverscrollHandoffChain| and |aOverscrollHandoffChainIndex| are used by + * the tree manager to keep track of which APZC to hand off the overscroll + * to; this function increments the chain and the index and passes it on to + * APZCTreeManager::DispatchScroll() in the event of overscroll. + * Returns true iff. this APZC, or an APZC further down the + * handoff chain, accepted the scroll (possibly entering an overscrolled + * state). If this returns false, the caller APZC knows that it should enter + * an overscrolled state itself if it can. + * aStartPoint and aEndPoint are modified depending on how much of the + * scroll gesture was consumed by APZCs in the handoff chain. + */ + bool AttemptScroll(ParentLayerPoint& aStartPoint, ParentLayerPoint& aEndPoint, + OverscrollHandoffState& aOverscrollHandoffState); + + void FlushRepaintForOverscrollHandoff(); + + /** + * If overscrolled, start a snap-back animation and return true. + * Otherwise return false. + */ + bool SnapBackIfOverscrolled(); + + /** + * Build the chain of APZCs along which scroll will be handed off when + * this APZC receives input events. + * + * Notes on lifetime and const-correctness: + * - The returned handoff chain is |const|, to indicate that it cannot be + * changed after being built. + * - When passing the chain to a function that uses it without storing it, + * pass it by reference-to-const (as in |const OverscrollHandoffChain&|). + * - When storing the chain, store it by RefPtr-to-const (as in + * |RefPtr|). This ensures the chain is + * kept alive. Note that queueing a task that uses the chain as an + * argument constitutes storing, as the task may outlive its queuer. + * - When passing the chain to a function that will store it, pass it as + * |const RefPtr&|. This allows the + * function to copy it into the |RefPtr| + * that will store it, while avoiding an unnecessary copy (and thus + * AddRef() and Release()) when passing it. + */ + RefPtr BuildOverscrollHandoffChain(); + + private: + /** + * A helper function for calling APZCTreeManager::DispatchScroll(). + * Guards against the case where the APZC is being concurrently destroyed + * (and thus mTreeManager is being nulled out). + */ + bool CallDispatchScroll(ParentLayerPoint& aStartPoint, + ParentLayerPoint& aEndPoint, + OverscrollHandoffState& aOverscrollHandoffState); + + void RecordScrollPayload(const TimeStamp& aTimeStamp); + + /** + * A helper function for overscrolling during panning. This is a wrapper + * around OverscrollBy() that also implements restrictions on entering + * overscroll based on the pan angle. + */ + void OverscrollForPanning(ParentLayerPoint& aOverscroll, + const ScreenPoint& aPanDistance); + + /** + * Try to overscroll by 'aOverscroll'. + * If we are pannable on a particular axis, that component of 'aOverscroll' + * is transferred to any existing overscroll. + */ + void OverscrollBy(ParentLayerPoint& aOverscroll); + + // Helper function for CanScroll(). + ParentLayerPoint GetDeltaForEvent(const InputData& aEvent) const; + + /* =================================================================== + * The functions and members in this section are used to maintain the + * area that this APZC instance is responsible for. This is used when + * hit-testing to see which APZC instance should handle touch events. + */ + public: + void SetAncestorTransform(const AncestorTransform& aAncestorTransform) { + mAncestorTransform = aAncestorTransform; + } + + Matrix4x4 GetAncestorTransform() const { + return mAncestorTransform.CombinedTransform(); + } + + bool AncestorTransformContainsPerspective() const { + return mAncestorTransform.ContainsPerspectiveTransform(); + } + + // Return the perspective transform component of the ancestor transform. + Matrix4x4 GetAncestorTransformPerspective() const { + return mAncestorTransform.GetPerspectiveTransform(); + } + + // Returns whether or not this apzc contains the given screen point within + // its composition bounds. + bool Contains(const ScreenIntPoint& aPoint) const; + + bool IsOverscrolled() const { + return mX.IsOverscrolled() || mY.IsOverscrolled(); + } + + bool IsInPanningState() const; + + private: + /* This is the cumulative CSS transform for all the layers from (and + * including) the parent APZC down to (but excluding) this one, and excluding + * any perspective transforms. */ + AncestorTransform mAncestorTransform; + + /* =================================================================== + * The functions and members in this section are used for sharing the + * FrameMetrics across processes for the progressive tiling code. + */ + private: + /* Unique id assigned to each APZC. Used with ViewID to uniquely identify + * shared FrameMeterics used in progressive tile painting. */ + const uint32_t mAPZCId; + + RefPtr mSharedFrameMetricsBuffer; + CrossProcessMutex* mSharedLock; + /** + * Called when ever Metrics() is updated so that if it is being + * shared with the content process the shared FrameMetrics may be updated. + */ + void UpdateSharedCompositorFrameMetrics(); + /** + * Create a shared memory buffer for containing the FrameMetrics and + * a CrossProcessMutex that may be shared with the content process + * for use in progressive tiled update calculations. + */ + void ShareCompositorFrameMetrics(); + + /* =================================================================== + * The functions and members in this section are used for testing + * and assertion purposes only. + */ + public: + /** + * Gets whether this APZC has performed async key scrolling. + */ + bool TestHasAsyncKeyScrolled() const { return mTestHasAsyncKeyScrolled; } + + /** + * Set an extra offset for testing async scrolling. + */ + void SetTestAsyncScrollOffset(const CSSPoint& aPoint); + /** + * Set an extra offset for testing async scrolling. + */ + void SetTestAsyncZoom(const LayerToParentLayerScale& aZoom); + + void MarkAsyncTransformAppliedToContent() { + mAsyncTransformAppliedToContent = true; + } + + bool GetAsyncTransformAppliedToContent() const { + return mAsyncTransformAppliedToContent; + } + + LayersId GetLayersId() const { return mLayersId; } + + bool IsAsyncZooming() const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + return mState == PINCHING || mState == ANIMATING_ZOOM; + } + + private: + // The timestamp of the latest touch start event. + TimeStamp mTouchStartTime; + // The time duration between mTouchStartTime and the touchmove event that + // started the pan (the touchmove event that transitioned this APZC from the + // TOUCHING state to one of the PANNING* states). Only valid while this APZC + // is in a panning state. + TimeDuration mTouchStartRestingTimeBeforePan; + Maybe mMinimumVelocityDuringPan; + // Extra offset to add to the async scroll position for testing + CSSPoint mTestAsyncScrollOffset; + // Extra zoom to include in the aync zoom for testing + LayerToParentLayerScale mTestAsyncZoom; + uint8_t mTestAttributeAppliers; + // Flag to track whether or not the APZ transform is not used. This + // flag is recomputed for every composition frame. + bool mAsyncTransformAppliedToContent; + // Flag to track whether or not this APZC has ever async key scrolled. + bool mTestHasAsyncKeyScrolled; + + /* =================================================================== + * The functions and members in this section are used for checkerboard + * recording. + */ + private: + // Helper function to update the in-progress checkerboard event, if any. + void UpdateCheckerboardEvent(const MutexAutoLock& aProofOfLock, + uint32_t aMagnitude); + + // Mutex protecting mCheckerboardEvent + Mutex mCheckerboardEventLock; + // This is created when this APZC instance is first included as part of a + // composite. If a checkerboard event takes place, this is destroyed at the + // end of the event, and a new one is created on the next composite. + UniquePtr mCheckerboardEvent; + // This is used to track the total amount of time that we could reasonably + // be checkerboarding. Combined with other info, this allows us to + // meaningfully say how frequently users actually encounter checkerboarding. + PotentialCheckerboardDurationTracker mPotentialCheckerboardTracker; + + /* =================================================================== + * The functions in this section are used for CSS scroll snapping. + */ + + // If moving |aStartPosition| by |aDelta| should trigger scroll snapping, + // adjust |aDelta| to reflect the snapping (that is, make it a delta that will + // take us to the desired snap point). The delta is interpreted as being + // relative to |aStartPosition|, and if a target snap point is found, + // |aStartPosition| is also updated, to the value of the snap point. + // |aUnit| affects the snapping behaviour (see ScrollSnapUtils:: + // GetSnapPointForDestination). + // Returns true iff. a target snap point was found. + bool MaybeAdjustDeltaForScrollSnapping(ScrollUnit aUnit, + ParentLayerPoint& aDelta, + CSSPoint& aStartPosition); + + // A wrapper function of MaybeAdjustDeltaForScrollSnapping for + // ScrollWheelInput. + bool MaybeAdjustDeltaForScrollSnappingOnWheelInput( + const ScrollWheelInput& aEvent, ParentLayerPoint& aDelta, + CSSPoint& aStartPosition); + + bool MaybeAdjustDestinationForScrollSnapping(const KeyboardInput& aEvent, + CSSPoint& aDestination); + + // Snap to a snap position nearby the current scroll position, if appropriate. + void ScrollSnap(); + + // Snap to a snap position nearby the destination predicted based on the + // current velocity, if appropriate. + void ScrollSnapToDestination(); + + // Snap to a snap position nearby the provided destination, if appropriate. + void ScrollSnapNear(const CSSPoint& aDestination); + + // Find a snap point near |aDestination| that we should snap to. + // Returns the snap point if one was found, or an empty Maybe otherwise. + // |aUnit| affects the snapping behaviour (see ScrollSnapUtils:: + // GetSnapPointForDestination). It should generally be determined by the + // type of event that's triggering the scroll. + Maybe FindSnapPointNear(const CSSPoint& aDestination, + ScrollUnit aUnit); +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_PanZoomController_h diff --git a/gfx/layers/apz/src/AutoDirWheelDeltaAdjuster.h b/gfx/layers/apz/src/AutoDirWheelDeltaAdjuster.h new file mode 100644 index 0000000000..187641514a --- /dev/null +++ b/gfx/layers/apz/src/AutoDirWheelDeltaAdjuster.h @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __mozilla_layers_AutoDirWheelDeltaAdjuster_h__ +#define __mozilla_layers_AutoDirWheelDeltaAdjuster_h__ + +#include "Axis.h" // for AxisX, AxisY, Side +#include "mozilla/WheelHandlingHelper.h" // for AutoDirWheelDeltaAdjuster + +namespace mozilla { +namespace layers { + +/** + * About AutoDirWheelDeltaAdjuster: + * For an AutoDir wheel scroll, there's some situations where we should adjust a + * wheel event's delta values. AutoDirWheelDeltaAdjuster converts delta values + * for AutoDir scrolling. An AutoDir wheel scroll lets the user scroll a frame + * with only one scrollbar, using either a vertical or a horzizontal wheel. + * For more detail about the concept of AutoDir scrolling, see the comments in + * AutoDirWheelDeltaAdjuster. + * + * This is the APZ implementation of AutoDirWheelDeltaAdjuster. + */ +class MOZ_STACK_CLASS APZAutoDirWheelDeltaAdjuster final + : public AutoDirWheelDeltaAdjuster { + public: + /** + * @param aDeltaX DeltaX for a wheel event whose delta values will + * be adjusted upon calling adjust() when + * ShouldBeAdjusted() returns true. + * @param aDeltaY DeltaY for a wheel event, like DeltaX. + * @param aAxisX The X axis information provider for the current + * frame, such as whether the frame can be scrolled + * horizontally, leftwards or rightwards. + * @param aAxisY The Y axis information provider for the current + * frame, such as whether the frame can be scrolled + * vertically, upwards or downwards. + * @param aIsHorizontalContentRightToLeft + * Indicates whether the horizontal content starts + * at rightside. This value will decide which edge + * the adjusted scroll goes towards, in other words, + * it will decide the sign of the adjusted delta + * values). For detailed information, see + * IsHorizontalContentRightToLeft() in + * the base class AutoDirWheelDeltaAdjuster. + */ + APZAutoDirWheelDeltaAdjuster(double& aDeltaX, double& aDeltaY, + const AxisX& aAxisX, const AxisY& aAxisY, + bool aIsHorizontalContentRightToLeft) + : AutoDirWheelDeltaAdjuster(aDeltaX, aDeltaY), + mAxisX(aAxisX), + mAxisY(aAxisY), + mIsHorizontalContentRightToLeft(aIsHorizontalContentRightToLeft) {} + + private: + virtual bool CanScrollAlongXAxis() const override { + return mAxisX.CanScroll(); + } + virtual bool CanScrollAlongYAxis() const override { + return mAxisY.CanScroll(); + } + virtual bool CanScrollUpwards() const override { + return mAxisY.CanScrollTo(eSideTop); + } + virtual bool CanScrollDownwards() const override { + return mAxisY.CanScrollTo(eSideBottom); + } + virtual bool CanScrollLeftwards() const override { + return mAxisX.CanScrollTo(eSideLeft); + } + virtual bool CanScrollRightwards() const override { + return mAxisX.CanScrollTo(eSideRight); + } + virtual bool IsHorizontalContentRightToLeft() const override { + return mIsHorizontalContentRightToLeft; + } + + const AxisX& mAxisX; + const AxisY& mAxisY; + bool mIsHorizontalContentRightToLeft; +}; + +} // namespace layers +} // namespace mozilla + +#endif // __mozilla_layers_AutoDirWheelDeltaAdjuster_h__ diff --git a/gfx/layers/apz/src/AutoscrollAnimation.cpp b/gfx/layers/apz/src/AutoscrollAnimation.cpp new file mode 100644 index 0000000000..8d4b8fca10 --- /dev/null +++ b/gfx/layers/apz/src/AutoscrollAnimation.cpp @@ -0,0 +1,93 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AutoscrollAnimation.h" + +#include // for sqrtf() + +#include "AsyncPanZoomController.h" +#include "APZCTreeManager.h" +#include "FrameMetrics.h" +#include "mozilla/Telemetry.h" // for Telemetry + +namespace mozilla { +namespace layers { + +// Helper function for AutoscrollAnimation::DoSample(). +// Basically copied as-is from toolkit/actors/AutoScrollChild.jsm. +static float Accelerate(ScreenCoord curr, ScreenCoord start) { + static const int speed = 12; + float val = (curr - start) / speed; + if (val > 1) { + return val * sqrtf(val) - 1; + } + if (val < -1) { + return val * sqrtf(-val) + 1; + } + return 0; +} + +AutoscrollAnimation::AutoscrollAnimation(AsyncPanZoomController& aApzc, + const ScreenPoint& aAnchorLocation) + : mApzc(aApzc), mAnchorLocation(aAnchorLocation) {} + +bool AutoscrollAnimation::DoSample(FrameMetrics& aFrameMetrics, + const TimeDuration& aDelta) { + APZCTreeManager* treeManager = mApzc.GetApzcTreeManager(); + if (!treeManager) { + return false; + } + + ScreenPoint mouseLocation = treeManager->GetCurrentMousePosition(); + + // The implementation of this function closely mirrors that of its main- + // thread equivalent, the autoscrollLoop() function in + // toolkit/actors/AutoScrollChild.jsm. + + // Avoid long jumps when the browser hangs for more than |maxTimeDelta| ms. + static const TimeDuration maxTimeDelta = TimeDuration::FromMilliseconds(100); + TimeDuration timeDelta = TimeDuration::Min(aDelta, maxTimeDelta); + + float timeCompensation = timeDelta.ToMilliseconds() / 20; + + // Notes: + // - The main-thread implementation rounds the scroll delta to an integer, + // and keeps track of the fractional part as an "error". It does this + // because it uses Window.scrollBy() or Element.scrollBy() to perform + // the scrolling, and those functions truncate the fractional part of + // the offset. APZ does no such truncation, so there's no need to keep + // track of the fractional part separately. + // - The Accelerate() function takes Screen coordinates as inputs, but + // its output is interpreted as CSS coordinates. This is intentional, + // insofar as autoscrollLoop() does the same thing. + CSSPoint scrollDelta{ + Accelerate(mouseLocation.x, mAnchorLocation.x) * timeCompensation, + Accelerate(mouseLocation.y, mAnchorLocation.y) * timeCompensation}; + + mApzc.ScrollByAndClamp(scrollDelta); + + // An autoscroll animation never ends of its own accord. + // It can be stopped in response to various input events, in which case + // AsyncPanZoomController::StopAutoscroll() will stop it via + // CancelAnimation(). + return true; +} + +void AutoscrollAnimation::Cancel(CancelAnimationFlags aFlags) { + // The cancellation was initiated by browser.js, so there's no need to + // notify it. + if (aFlags & TriggeredExternally) { + return; + } + + if (RefPtr controller = + mApzc.GetGeckoContentController()) { + controller->CancelAutoscroll(mApzc.GetGuid()); + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/src/AutoscrollAnimation.h b/gfx/layers/apz/src/AutoscrollAnimation.h new file mode 100644 index 0000000000..a37f6d473a --- /dev/null +++ b/gfx/layers/apz/src/AutoscrollAnimation.h @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_AutocrollAnimation_h_ +#define mozilla_layers_AutocrollAnimation_h_ + +#include "AsyncPanZoomAnimation.h" + +namespace mozilla { +namespace layers { + +class AsyncPanZoomController; + +class AutoscrollAnimation : public AsyncPanZoomAnimation { + public: + AutoscrollAnimation(AsyncPanZoomController& aApzc, + const ScreenPoint& aAnchorLocation); + + bool DoSample(FrameMetrics& aFrameMetrics, + const TimeDuration& aDelta) override; + + bool HandleScrollOffsetUpdate( + const Maybe& aRelativeDelta) override { + // Autoscroll works using screen space coordinates, so there's no work we + // need to do to handle either a relative or an absolute scroll update. + return true; + } + + void Cancel(CancelAnimationFlags aFlags) override; + + private: + AsyncPanZoomController& mApzc; + ScreenPoint mAnchorLocation; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_AutoscrollAnimation_h_ diff --git a/gfx/layers/apz/src/Axis.cpp b/gfx/layers/apz/src/Axis.cpp new file mode 100644 index 0000000000..c880426d3a --- /dev/null +++ b/gfx/layers/apz/src/Axis.cpp @@ -0,0 +1,525 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Axis.h" + +#include // for fabsf, pow, powf +#include // for max + +#include "APZCTreeManager.h" // for APZCTreeManager +#include "AsyncPanZoomController.h" // for AsyncPanZoomController +#include "FrameMetrics.h" // for FrameMetrics +#include "SimpleVelocityTracker.h" // for FrameMetrics +#include "mozilla/Attributes.h" // for final +#include "mozilla/Preferences.h" // for Preferences +#include "mozilla/gfx/Rect.h" // for RoundedIn +#include "mozilla/layers/APZThreadUtils.h" // for AssertOnControllerThread +#include "mozilla/mozalloc.h" // for operator new +#include "mozilla/FloatingPoint.h" // for FuzzyEqualsAdditive +#include "nsMathUtils.h" // for NS_lround +#include "nsPrintfCString.h" // for nsPrintfCString +#include "nsThreadUtils.h" // for NS_DispatchToMainThread, etc +#include "nscore.h" // for NS_IMETHOD + +static mozilla::LazyLogModule sApzAxsLog("apz.axis"); +#define AXIS_LOG(...) MOZ_LOG(sApzAxsLog, LogLevel::Debug, (__VA_ARGS__)) + +namespace mozilla { +namespace layers { + +bool FuzzyEqualsCoordinate(float aValue1, float aValue2) { + return FuzzyEqualsAdditive(aValue1, aValue2, COORDINATE_EPSILON) || + FuzzyEqualsMultiplicative(aValue1, aValue2); +} + +Axis::Axis(AsyncPanZoomController* aAsyncPanZoomController) + : mPos(0), + mVelocity(0.0f, "Axis::mVelocity"), + mAxisLocked(false), + mAsyncPanZoomController(aAsyncPanZoomController), + mOverscroll(0), + mMSDModel(0.0, 0.0, 0.0, 400.0, 1.2), + mVelocityTracker(mAsyncPanZoomController->GetPlatformSpecificState() + ->CreateVelocityTracker(this)) {} + +float Axis::ToLocalVelocity(float aVelocityInchesPerMs) const { + ScreenPoint velocity = + MakePoint(aVelocityInchesPerMs * mAsyncPanZoomController->GetDPI()); + // Use ToScreenCoordinates() to convert a point rather than a vector by + // treating the point as a vector, and using (0, 0) as the anchor. + ScreenPoint panStart = mAsyncPanZoomController->ToScreenCoordinates( + mAsyncPanZoomController->PanStart(), ParentLayerPoint()); + ParentLayerPoint localVelocity = + mAsyncPanZoomController->ToParentLayerCoordinates(velocity, panStart); + return localVelocity.Length(); +} + +void Axis::UpdateWithTouchAtDevicePoint(ParentLayerCoord aPos, + TimeStamp aTimestamp) { + // mVelocityTracker is controller-thread only + APZThreadUtils::AssertOnControllerThread(); + + mPos = aPos; + + AXIS_LOG("%p|%s got position %f\n", mAsyncPanZoomController, Name(), + mPos.value); + if (Maybe newVelocity = + mVelocityTracker->AddPosition(aPos, aTimestamp)) { + DoSetVelocity(mAxisLocked ? 0 : *newVelocity); + AXIS_LOG("%p|%s velocity from tracker is %f%s\n", mAsyncPanZoomController, + Name(), *newVelocity, + mAxisLocked ? ", but we are axis locked" : ""); + } +} + +void Axis::StartTouch(ParentLayerCoord aPos, TimeStamp aTimestamp) { + mStartPos = aPos; + mPos = aPos; + mVelocityTracker->StartTracking(aPos, aTimestamp); + mAxisLocked = false; +} + +bool Axis::AdjustDisplacement( + ParentLayerCoord aDisplacement, + /* ParentLayerCoord */ float& aDisplacementOut, + /* ParentLayerCoord */ float& aOverscrollAmountOut, + bool aForceOverscroll /* = false */) { + if (mAxisLocked) { + aOverscrollAmountOut = 0; + aDisplacementOut = 0; + return false; + } + if (aForceOverscroll) { + aOverscrollAmountOut = aDisplacement; + aDisplacementOut = 0; + return false; + } + + EndOverscrollAnimation(); + + ParentLayerCoord displacement = aDisplacement; + + // First consume any overscroll in the opposite direction along this axis. + ParentLayerCoord consumedOverscroll = 0; + if (mOverscroll > 0 && aDisplacement < 0) { + consumedOverscroll = std::min(mOverscroll, -aDisplacement); + } else if (mOverscroll < 0 && aDisplacement > 0) { + consumedOverscroll = 0.f - std::min(-mOverscroll, aDisplacement); + } + mOverscroll -= consumedOverscroll; + displacement += consumedOverscroll; + + // Split the requested displacement into an allowed displacement that does + // not overscroll, and an overscroll amount. + aOverscrollAmountOut = DisplacementWillOverscrollAmount(displacement); + if (aOverscrollAmountOut != 0.0f) { + // No need to have a velocity along this axis anymore; it won't take us + // anywhere, so we're just spinning needlessly. + AXIS_LOG("%p|%s has overscrolled, clearing velocity\n", + mAsyncPanZoomController, Name()); + DoSetVelocity(0.0f); + displacement -= aOverscrollAmountOut; + } + aDisplacementOut = displacement; + return fabsf(consumedOverscroll) > EPSILON; +} + +ParentLayerCoord Axis::ApplyResistance( + ParentLayerCoord aRequestedOverscroll) const { + // 'resistanceFactor' is a value between 0 and 1/16, which: + // - tends to 1/16 as the existing overscroll tends to 0 + // - tends to 0 as the existing overscroll tends to the composition length + // The actual overscroll is the requested overscroll multiplied by this + // factor. + float resistanceFactor = + (1 - fabsf(GetOverscroll()) / GetCompositionLength()) / 16; + float result = resistanceFactor < 0 ? ParentLayerCoord(0) + : aRequestedOverscroll * resistanceFactor; + result = clamped(result, -8.0f, 8.0f); + return result; +} + +void Axis::OverscrollBy(ParentLayerCoord aOverscroll) { + MOZ_ASSERT(CanScroll()); + // We can get some spurious calls to OverscrollBy() with near-zero values + // due to rounding error. Ignore those (they might trip the asserts below.) + if (FuzzyEqualsAdditive(aOverscroll.value, 0.0f, COORDINATE_EPSILON)) { + return; + } + EndOverscrollAnimation(); + aOverscroll = ApplyResistance(aOverscroll); + if (aOverscroll > 0) { +#ifdef DEBUG + if (!FuzzyEqualsCoordinate(GetCompositionEnd().value, GetPageEnd().value)) { + nsPrintfCString message( + "composition end (%f) is not equal (within error) to page end (%f)\n", + GetCompositionEnd().value, GetPageEnd().value); + NS_ASSERTION(false, message.get()); + MOZ_CRASH("GFX: Overscroll issue > 0"); + } +#endif + MOZ_ASSERT(mOverscroll >= 0); + } else if (aOverscroll < 0) { +#ifdef DEBUG + if (!FuzzyEqualsCoordinate(GetOrigin().value, GetPageStart().value)) { + nsPrintfCString message( + "composition origin (%f) is not equal (within error) to page origin " + "(%f)\n", + GetOrigin().value, GetPageStart().value); + NS_ASSERTION(false, message.get()); + MOZ_CRASH("GFX: Overscroll issue < 0"); + } +#endif + MOZ_ASSERT(mOverscroll <= 0); + } + mOverscroll += aOverscroll; +} + +ParentLayerCoord Axis::GetOverscroll() const { return mOverscroll; } + +void Axis::StartOverscrollAnimation(float aVelocity) { + aVelocity = clamped(aVelocity / 2.0f, -20.0f, 20.0f); + SetVelocity(aVelocity); + mMSDModel.SetPosition(mOverscroll); + // Convert velocity from ParentLayerCoords/millisecond to + // ParentLayerCoords/second. + mMSDModel.SetVelocity(DoGetVelocity() * 1000.0); +} + +void Axis::EndOverscrollAnimation() { + mMSDModel.SetPosition(0.0); + mMSDModel.SetVelocity(0.0); +} + +bool Axis::SampleOverscrollAnimation(const TimeDuration& aDelta) { + mMSDModel.Simulate(aDelta); + mOverscroll = mMSDModel.GetPosition(); + + if (mMSDModel.IsFinished(1.0)) { + // "Jump" to the at-rest state. The jump shouldn't be noticeable as the + // velocity and overscroll are already low. + AXIS_LOG("%p|%s oscillation dropped below threshold, going to rest\n", + mAsyncPanZoomController, Name()); + ClearOverscroll(); + DoSetVelocity(0); + return false; + } + + // Otherwise, continue the animation. + return true; +} + +bool Axis::IsOverscrolled() const { return mOverscroll != 0.f; } + +void Axis::ClearOverscroll() { + EndOverscrollAnimation(); + mOverscroll = 0; +} + +ParentLayerCoord Axis::PanStart() const { return mStartPos; } + +ParentLayerCoord Axis::PanDistance() const { return fabs(mPos - mStartPos); } + +ParentLayerCoord Axis::PanDistance(ParentLayerCoord aPos) const { + return fabs(aPos - mStartPos); +} + +void Axis::EndTouch(TimeStamp aTimestamp) { + // mVelocityQueue is controller-thread only + APZThreadUtils::AssertOnControllerThread(); + + // If the velocity tracker wasn't able to compute a velocity, zero out + // the velocity to make sure we don't get a fling based on some old and + // no-longer-relevant value of mVelocity. Also if the axis is locked then + // just reset the velocity to 0 since we don't need any velocity to carry + // into the fling. + if (mAxisLocked) { + DoSetVelocity(0); + } else if (Maybe velocity = + mVelocityTracker->ComputeVelocity(aTimestamp)) { + DoSetVelocity(*velocity); + } else { + DoSetVelocity(0); + } + mAxisLocked = false; + AXIS_LOG("%p|%s ending touch, computed velocity %f\n", + mAsyncPanZoomController, Name(), DoGetVelocity()); +} + +void Axis::CancelGesture() { + // mVelocityQueue is controller-thread only + APZThreadUtils::AssertOnControllerThread(); + + AXIS_LOG("%p|%s cancelling touch, clearing velocity queue\n", + mAsyncPanZoomController, Name()); + DoSetVelocity(0.0f); + mVelocityTracker->Clear(); +} + +bool Axis::CanScroll() const { + return GetPageLength() - GetCompositionLength() > COORDINATE_EPSILON; +} + +bool Axis::CanScroll(ParentLayerCoord aDelta) const { + if (!CanScroll() || mAxisLocked) { + return false; + } + + return fabs(DisplacementWillOverscrollAmount(aDelta) - aDelta) > + COORDINATE_EPSILON; +} + +CSSCoord Axis::ClampOriginToScrollableRect(CSSCoord aOrigin) const { + CSSToParentLayerScale zoom = GetScaleForAxis(GetFrameMetrics().GetZoom()); + ParentLayerCoord origin = aOrigin * zoom; + ParentLayerCoord result; + if (origin < GetPageStart()) { + result = GetPageStart(); + } else if (origin + GetCompositionLength() > GetPageEnd()) { + result = GetPageEnd() - GetCompositionLength(); + } else { + return aOrigin; + } + if (zoom == CSSToParentLayerScale(0)) { + return aOrigin; + } + return result / zoom; +} + +bool Axis::CanScrollNow() const { return !mAxisLocked && CanScroll(); } + +ParentLayerCoord Axis::DisplacementWillOverscrollAmount( + ParentLayerCoord aDisplacement) const { + ParentLayerCoord newOrigin = GetOrigin() + aDisplacement; + ParentLayerCoord newCompositionEnd = GetCompositionEnd() + aDisplacement; + // If the current pan plus a displacement takes the window to the left of or + // above the current page rect. + bool minus = newOrigin < GetPageStart(); + // If the current pan plus a displacement takes the window to the right of or + // below the current page rect. + bool plus = newCompositionEnd > GetPageEnd(); + if (minus && plus) { + // Don't handle overscrolled in both directions; a displacement can't cause + // this, it must have already been zoomed out too far. + return 0; + } + if (minus) { + return newOrigin - GetPageStart(); + } + if (plus) { + return newCompositionEnd - GetPageEnd(); + } + return 0; +} + +CSSCoord Axis::ScaleWillOverscrollAmount(float aScale, CSSCoord aFocus) const { + // Internally, do computations in ParentLayer coordinates *before* the scale + // is applied. + CSSToParentLayerScale zoom = GetFrameMetrics().GetZoom().ToScaleFactor(); + ParentLayerCoord focus = aFocus * zoom; + ParentLayerCoord originAfterScale = (GetOrigin() + focus) - (focus / aScale); + + bool both = ScaleWillOverscrollBothSides(aScale); + bool minus = GetPageStart() - originAfterScale > COORDINATE_EPSILON; + bool plus = + (originAfterScale + (GetCompositionLength() / aScale)) - GetPageEnd() > + COORDINATE_EPSILON; + + if ((minus && plus) || both) { + // If we ever reach here it's a bug in the client code. + MOZ_ASSERT(false, + "In an OVERSCROLL_BOTH condition in ScaleWillOverscrollAmount"); + return 0; + } + if (minus && zoom != CSSToParentLayerScale(0)) { + return (originAfterScale - GetPageStart()) / zoom; + } + if (plus && zoom != CSSToParentLayerScale(0)) { + return (originAfterScale + (GetCompositionLength() / aScale) - + GetPageEnd()) / + zoom; + } + return 0; +} + +bool Axis::IsAxisLocked() const { return mAxisLocked; } + +float Axis::GetVelocity() const { return mAxisLocked ? 0 : DoGetVelocity(); } + +void Axis::SetVelocity(float aVelocity) { + AXIS_LOG("%p|%s direct-setting velocity to %f\n", mAsyncPanZoomController, + Name(), aVelocity); + DoSetVelocity(aVelocity); +} + +ParentLayerCoord Axis::GetCompositionEnd() const { + return GetOrigin() + GetCompositionLength(); +} + +ParentLayerCoord Axis::GetPageEnd() const { + return GetPageStart() + GetPageLength(); +} + +ParentLayerCoord Axis::GetScrollRangeEnd() const { + return GetPageEnd() - GetCompositionLength(); +} + +ParentLayerCoord Axis::GetOrigin() const { + ParentLayerPoint origin = + GetFrameMetrics().GetVisualScrollOffset() * GetFrameMetrics().GetZoom(); + return GetPointOffset(origin); +} + +ParentLayerCoord Axis::GetCompositionLength() const { + return GetRectLength(GetFrameMetrics().GetCompositionBounds()); +} + +ParentLayerCoord Axis::GetPageStart() const { + ParentLayerRect pageRect = GetFrameMetrics().GetExpandedScrollableRect() * + GetFrameMetrics().GetZoom(); + return GetRectOffset(pageRect); +} + +ParentLayerCoord Axis::GetPageLength() const { + ParentLayerRect pageRect = GetFrameMetrics().GetExpandedScrollableRect() * + GetFrameMetrics().GetZoom(); + return GetRectLength(pageRect); +} + +bool Axis::ScaleWillOverscrollBothSides(float aScale) const { + const FrameMetrics& metrics = GetFrameMetrics(); + ParentLayerRect screenCompositionBounds = + metrics.GetCompositionBounds() / ParentLayerToParentLayerScale(aScale); + return GetRectLength(screenCompositionBounds) - GetPageLength() > + COORDINATE_EPSILON; +} + +float Axis::DoGetVelocity() const { + auto velocity = mVelocity.Lock(); + return velocity.ref(); +} +void Axis::DoSetVelocity(float aVelocity) { + auto velocity = mVelocity.Lock(); + velocity.ref() = aVelocity; +} + +const FrameMetrics& Axis::GetFrameMetrics() const { + return mAsyncPanZoomController->GetFrameMetrics(); +} + +const ScrollMetadata& Axis::GetScrollMetadata() const { + return mAsyncPanZoomController->GetScrollMetadata(); +} + +bool Axis::OverscrollBehaviorAllowsHandoff() const { + // Scroll handoff is a "non-local" overscroll behavior, so it's allowed + // with "auto" and disallowed with "contain" and "none". + return GetOverscrollBehavior() == OverscrollBehavior::Auto; +} + +bool Axis::OverscrollBehaviorAllowsOverscrollEffect() const { + // An overscroll effect is a "local" overscroll behavior, so it's allowed + // with "auto" and "contain" and disallowed with "none". + return GetOverscrollBehavior() != OverscrollBehavior::None; +} + +AxisX::AxisX(AsyncPanZoomController* aAsyncPanZoomController) + : Axis(aAsyncPanZoomController) {} + +ParentLayerCoord AxisX::GetPointOffset(const ParentLayerPoint& aPoint) const { + return aPoint.x; +} + +ParentLayerCoord AxisX::GetRectLength(const ParentLayerRect& aRect) const { + return aRect.Width(); +} + +ParentLayerCoord AxisX::GetRectOffset(const ParentLayerRect& aRect) const { + return aRect.X(); +} + +CSSToParentLayerScale AxisX::GetScaleForAxis( + const CSSToParentLayerScale2D& aScale) const { + return CSSToParentLayerScale(aScale.xScale); +} + +ScreenPoint AxisX::MakePoint(ScreenCoord aCoord) const { + return ScreenPoint(aCoord, 0); +} + +const char* AxisX::Name() const { return "X"; } + +bool AxisX::CanScrollTo(Side aSide) const { + switch (aSide) { + case eSideLeft: + return CanScroll(-COORDINATE_EPSILON * 2); + case eSideRight: + return CanScroll(COORDINATE_EPSILON * 2); + default: + MOZ_ASSERT_UNREACHABLE("aSide is out of valid values"); + return false; + } +} + +OverscrollBehavior AxisX::GetOverscrollBehavior() const { + return GetScrollMetadata().GetOverscrollBehavior().mBehaviorX; +} + +AxisY::AxisY(AsyncPanZoomController* aAsyncPanZoomController) + : Axis(aAsyncPanZoomController) {} + +ParentLayerCoord AxisY::GetPointOffset(const ParentLayerPoint& aPoint) const { + return aPoint.y; +} + +ParentLayerCoord AxisY::GetRectLength(const ParentLayerRect& aRect) const { + return aRect.Height(); +} + +ParentLayerCoord AxisY::GetRectOffset(const ParentLayerRect& aRect) const { + return aRect.Y(); +} + +CSSToParentLayerScale AxisY::GetScaleForAxis( + const CSSToParentLayerScale2D& aScale) const { + return CSSToParentLayerScale(aScale.yScale); +} + +ScreenPoint AxisY::MakePoint(ScreenCoord aCoord) const { + return ScreenPoint(0, aCoord); +} + +const char* AxisY::Name() const { return "Y"; } + +bool AxisY::CanScrollTo(Side aSide) const { + switch (aSide) { + case eSideTop: + return CanScroll(-COORDINATE_EPSILON * 2); + case eSideBottom: + return CanScroll(COORDINATE_EPSILON * 2); + default: + MOZ_ASSERT_UNREACHABLE("aSide is out of valid values"); + return false; + } +} + +bool AxisY::CanScrollDownwardsWithDynamicToolbar() const { + return GetCompositionLengthWithoutDynamicToolbar() == ParentLayerCoord(0) + ? CanScroll() + : GetPageLength() - GetCompositionLengthWithoutDynamicToolbar() > + COORDINATE_EPSILON; +} + +OverscrollBehavior AxisY::GetOverscrollBehavior() const { + return GetScrollMetadata().GetOverscrollBehavior().mBehaviorY; +} + +ParentLayerCoord AxisY::GetCompositionLengthWithoutDynamicToolbar() const { + return GetFrameMetrics().GetCompositionSizeWithoutDynamicToolbar().Height(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/src/Axis.h b/gfx/layers/apz/src/Axis.h new file mode 100644 index 0000000000..77dd302a9f --- /dev/null +++ b/gfx/layers/apz/src/Axis.h @@ -0,0 +1,379 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_Axis_h +#define mozilla_layers_Axis_h + +#include // for int32_t + +#include "APZUtils.h" +#include "AxisPhysicsMSDModel.h" +#include "mozilla/DataMutex.h" // for DataMutex +#include "mozilla/gfx/Types.h" // for Side +#include "mozilla/TimeStamp.h" // for TimeDuration +#include "nsTArray.h" // for nsTArray +#include "Units.h" + +namespace mozilla { +namespace layers { + +const float EPSILON = 0.0001f; + +/** + * Compare two coordinates for equality, accounting for rounding error. + * Use both FuzzyEqualsAdditive() with COORDINATE_EPISLON, which accounts for + * things like the error introduced by rounding during a round-trip to app + * units, and FuzzyEqualsMultiplicative(), which accounts for accumulated error + * due to floating-point operations (which can be larger than COORDINATE_EPISLON + * for sufficiently large coordinate values). + */ +bool FuzzyEqualsCoordinate(float aValue1, float aValue2); + +struct FrameMetrics; +class AsyncPanZoomController; + +/** + * Interface for computing velocities along the axis based on + * position samples. + */ +class VelocityTracker { + public: + virtual ~VelocityTracker() = default; + + /** + * Start tracking velocity along this axis, starting with the given + * initial position and corresponding timestamp. + */ + virtual void StartTracking(ParentLayerCoord aPos, TimeStamp aTimestamp) = 0; + /** + * Record a new position along this axis, at the given timestamp. + * Returns the average velocity between the last sample and this one, or + * or Nothing() if a reasonable average cannot be computed. + */ + virtual Maybe AddPosition(ParentLayerCoord aPos, + TimeStamp aTimestamp) = 0; + /** + * Compute an estimate of the axis's current velocity, based on recent + * position samples. It's up to implementation how many samples to consider + * and how to perform the computation. + * If the tracker doesn't have enough samples to compute a result, it + * may return Nothing{}. + */ + virtual Maybe ComputeVelocity(TimeStamp aTimestamp) = 0; + /** + * Clear all state in the velocity tracker. + */ + virtual void Clear() = 0; +}; + +/** + * Helper class to maintain each axis of movement (X,Y) for panning and zooming. + * Note that everything here is specific to one axis; that is, the X axis knows + * nothing about the Y axis and vice versa. + */ +class Axis { + public: + explicit Axis(AsyncPanZoomController* aAsyncPanZoomController); + + /** + * Notify this Axis that a new touch has been received, including a timestamp + * for when the touch was received. This triggers a recalculation of velocity. + * This can also used for pan gesture events. For those events, |aPos| is + * an invented position corresponding to the mouse position plus any + * accumulated displacements over the course of the pan gesture. + */ + void UpdateWithTouchAtDevicePoint(ParentLayerCoord aPos, + TimeStamp aTimestamp); + + public: + /** + * Notify this Axis that a touch has begun, i.e. the user has put their finger + * on the screen but has not yet tried to pan. + */ + void StartTouch(ParentLayerCoord aPos, TimeStamp aTimestamp); + + /** + * Notify this Axis that a touch has ended gracefully. This may perform + * recalculations of the axis velocity. + */ + void EndTouch(TimeStamp aTimestamp); + + /** + * Notify this Axis that the gesture has ended forcefully. Useful for stopping + * flings when a user puts their finger down in the middle of one (i.e. to + * stop a previous touch including its fling so that a new one can take its + * place). + */ + void CancelGesture(); + + /** + * Takes a requested displacement to the position of this axis, and adjusts it + * to account for overscroll (which might decrease the displacement; this is + * to prevent the viewport from overscrolling the page rect), and axis locking + * (which might prevent any displacement from happening). If overscroll + * ocurred, its amount is written to |aOverscrollAmountOut|. + * The |aDisplacementOut| parameter is set to the adjusted displacement, and + * the function returns true if and only if internal overscroll amounts were + * changed. + */ + bool AdjustDisplacement(ParentLayerCoord aDisplacement, + /* ParentLayerCoord */ float& aDisplacementOut, + /* ParentLayerCoord */ float& aOverscrollAmountOut, + bool aForceOverscroll = false); + + /** + * Overscrolls this axis by the requested amount in the requested direction. + * The axis must be at the end of its scroll range in this direction. + */ + void OverscrollBy(ParentLayerCoord aOverscroll); + + /** + * Return the amount of overscroll on this axis, in ParentLayer pixels. + * + * If this amount is nonzero, the relevant component of + * mAsyncPanZoomController->Metrics().mScrollOffset must be at its + * extreme allowed value in the relevant direction (that is, it must be at + * its maximum value if we are overscrolled at our composition length, and + * at its minimum value if we are overscrolled at the origin). + */ + ParentLayerCoord GetOverscroll() const; + + /** + * Start an overscroll animation with the given initial velocity. + */ + void StartOverscrollAnimation(float aVelocity); + + /** + * Sample the snap-back animation to relieve overscroll. + * |aDelta| is the time since the last sample. + */ + bool SampleOverscrollAnimation(const TimeDuration& aDelta); + + /** + * Stop an overscroll animation. + */ + void EndOverscrollAnimation(); + + /** + * Return whether this axis is overscrolled in either direction. + */ + bool IsOverscrolled() const; + + /** + * Clear any overscroll amount on this axis. + */ + void ClearOverscroll(); + + /** + * Gets the starting position of the touch supplied in StartTouch(). + */ + ParentLayerCoord PanStart() const; + + /** + * Gets the distance between the starting position of the touch supplied in + * StartTouch() and the current touch from the last + * UpdateWithTouchAtDevicePoint(). + */ + ParentLayerCoord PanDistance() const; + + /** + * Gets the distance between the starting position of the touch supplied in + * StartTouch() and the supplied position. + */ + ParentLayerCoord PanDistance(ParentLayerCoord aPos) const; + + /** + * Returns true if the page has room to be scrolled along this axis. + */ + bool CanScroll() const; + + /** + * Returns whether this axis can scroll any more in a particular direction. + */ + bool CanScroll(ParentLayerCoord aDelta) const; + + /** + * Returns true if the page has room to be scrolled along this axis + * and this axis is not scroll-locked. + */ + bool CanScrollNow() const; + + /** + * Clamp a point to the page's scrollable bounds. That is, a scroll + * destination to the returned point will not contain any overscroll. + */ + CSSCoord ClampOriginToScrollableRect(CSSCoord aOrigin) const; + + void SetAxisLocked(bool aAxisLocked) { mAxisLocked = aAxisLocked; } + + /** + * Gets the raw velocity of this axis at this moment. + */ + float GetVelocity() const; + + /** + * Sets the raw velocity of this axis at this moment. + * Intended to be called only when the axis "takes over" a velocity from + * another APZC, in which case there are no touch points available to call + * UpdateWithTouchAtDevicePoint. In other circumstances, + * UpdateWithTouchAtDevicePoint should be used and the velocity calculated + * there. + */ + void SetVelocity(float aVelocity); + + /** + * If a displacement will overscroll the axis, this returns the amount and in + * what direction. + */ + ParentLayerCoord DisplacementWillOverscrollAmount( + ParentLayerCoord aDisplacement) const; + + /** + * If a scale will overscroll the axis, this returns the amount and in what + * direction. + * + * |aFocus| is the point at which the scale is focused at. We will offset the + * scroll offset in such a way that it remains in the same place on the page + * relative. + * + * Note: Unlike most other functions in Axis, this functions operates in + * CSS coordinates so there is no confusion as to whether the + * ParentLayer coordinates it operates in are before or after the scale + * is applied. + */ + CSSCoord ScaleWillOverscrollAmount(float aScale, CSSCoord aFocus) const; + + /** + * Checks if an axis will overscroll in both directions by computing the + * content rect and checking that its height/width (depending on the axis) + * does not overextend past the viewport. + * + * This gets called by ScaleWillOverscroll(). + */ + bool ScaleWillOverscrollBothSides(float aScale) const; + + /** + * Returns true if movement on this axis is locked. + */ + bool IsAxisLocked() const; + + ParentLayerCoord GetOrigin() const; + ParentLayerCoord GetCompositionLength() const; + ParentLayerCoord GetPageStart() const; + ParentLayerCoord GetPageLength() const; + ParentLayerCoord GetCompositionEnd() const; + ParentLayerCoord GetPageEnd() const; + ParentLayerCoord GetScrollRangeEnd() const; + + ParentLayerCoord GetPos() const { return mPos; } + + bool OverscrollBehaviorAllowsHandoff() const; + bool OverscrollBehaviorAllowsOverscrollEffect() const; + + virtual ParentLayerCoord GetPointOffset( + const ParentLayerPoint& aPoint) const = 0; + virtual ParentLayerCoord GetRectLength( + const ParentLayerRect& aRect) const = 0; + virtual ParentLayerCoord GetRectOffset( + const ParentLayerRect& aRect) const = 0; + virtual CSSToParentLayerScale GetScaleForAxis( + const CSSToParentLayerScale2D& aScale) const = 0; + + virtual ScreenPoint MakePoint(ScreenCoord aCoord) const = 0; + + const void* OpaqueApzcPointer() const { return mAsyncPanZoomController; } + + virtual const char* Name() const = 0; + + // Convert a velocity from global inches/ms into ParentLayerCoords/ms. + float ToLocalVelocity(float aVelocityInchesPerMs) const; + + protected: + // A position along the axis, used during input event processing to + // track velocities (and for touch gestures, to track the length of + // the gesture). For touch events, this represents the position of + // the finger (or in the case of two-finger scrolling, the midpoint + // of the two fingers). For pan gesture events, this represents an + // invented position corresponding to the mouse position at the start + // of the pan, plus deltas representing the displacement of the pan. + ParentLayerCoord mPos; + + ParentLayerCoord mStartPos; + // The velocity can be accessed from multiple threads (e.g. APZ + // controller thread and APZ sampler thread), so needs to be + // protected by a mutex. + // Units: ParentLayerCoords per millisecond + mutable DataMutex mVelocity; + bool mAxisLocked; // Whether movement on this axis is locked. + AsyncPanZoomController* mAsyncPanZoomController; + + // The amount by which we are overscrolled; see GetOverscroll(). + ParentLayerCoord mOverscroll; + + // The mass-spring-damper model for overscroll physics. + AxisPhysicsMSDModel mMSDModel; + + // Used to track velocity over a series of input events and compute + // a resulting velocity to use for e.g. starting a fling animation. + // This member can only be accessed on the controller/UI thread. + UniquePtr mVelocityTracker; + + float DoGetVelocity() const; + void DoSetVelocity(float aVelocity); + + const FrameMetrics& GetFrameMetrics() const; + const ScrollMetadata& GetScrollMetadata() const; + + virtual OverscrollBehavior GetOverscrollBehavior() const = 0; + + // Adjust a requested overscroll amount for resistance, yielding a smaller + // actual overscroll amount. + ParentLayerCoord ApplyResistance(ParentLayerCoord aOverscroll) const; + + // Helper function for SampleOverscrollAnimation(). + void StepOverscrollAnimation(double aStepDurationMilliseconds); +}; + +class AxisX : public Axis { + public: + explicit AxisX(AsyncPanZoomController* mAsyncPanZoomController); + ParentLayerCoord GetPointOffset( + const ParentLayerPoint& aPoint) const override; + ParentLayerCoord GetRectLength(const ParentLayerRect& aRect) const override; + ParentLayerCoord GetRectOffset(const ParentLayerRect& aRect) const override; + CSSToParentLayerScale GetScaleForAxis( + const CSSToParentLayerScale2D& aScale) const override; + ScreenPoint MakePoint(ScreenCoord aCoord) const override; + const char* Name() const override; + bool CanScrollTo(Side aSide) const; + + private: + OverscrollBehavior GetOverscrollBehavior() const override; +}; + +class AxisY : public Axis { + public: + explicit AxisY(AsyncPanZoomController* mAsyncPanZoomController); + ParentLayerCoord GetPointOffset( + const ParentLayerPoint& aPoint) const override; + ParentLayerCoord GetRectLength(const ParentLayerRect& aRect) const override; + ParentLayerCoord GetRectOffset(const ParentLayerRect& aRect) const override; + CSSToParentLayerScale GetScaleForAxis( + const CSSToParentLayerScale2D& aScale) const override; + ScreenPoint MakePoint(ScreenCoord aCoord) const override; + const char* Name() const override; + bool CanScrollTo(Side aSide) const; + bool CanScrollDownwardsWithDynamicToolbar() const; + + private: + OverscrollBehavior GetOverscrollBehavior() const override; + ParentLayerCoord GetCompositionLengthWithoutDynamicToolbar() const; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/apz/src/CheckerboardEvent.cpp b/gfx/layers/apz/src/CheckerboardEvent.cpp new file mode 100644 index 0000000000..fdfcc2add8 --- /dev/null +++ b/gfx/layers/apz/src/CheckerboardEvent.cpp @@ -0,0 +1,193 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "CheckerboardEvent.h" +#include "mozilla/Logging.h" + +#include // for std::sort + +static mozilla::LazyLogModule sApzCheckLog("apz.checkerboard"); + +namespace mozilla { +namespace layers { + +// Relatively arbitrary limit to prevent a perma-checkerboard event from +// eating up gobs of memory. Ideally we shouldn't have perma-checkerboarding +// but better to guard against it. +#define LOG_LENGTH_LIMIT (50 * 1024) + +const char* CheckerboardEvent::sDescriptions[] = { + "page", + "painted critical displayport", + "painted displayport", + "requested displayport", + "viewport", +}; + +const char* CheckerboardEvent::sColors[] = { + "brown", "darkgreen", "lightgreen", "yellow", "red", +}; + +CheckerboardEvent::CheckerboardEvent(bool aRecordTrace) + : mRecordTrace(aRecordTrace), + mOriginTime(TimeStamp::Now()), + mCheckerboardingActive(false), + mLastSampleTime(mOriginTime), + mFrameCount(0), + mTotalPixelMs(0), + mPeakPixels(0), + mRendertraceLock("Rendertrace") {} + +uint32_t CheckerboardEvent::GetSeverity() { + // Scale the total into a 32-bit value + return (uint32_t)sqrt((double)mTotalPixelMs); +} + +uint32_t CheckerboardEvent::GetPeak() { return mPeakPixels; } + +TimeDuration CheckerboardEvent::GetDuration() { return mEndTime - mStartTime; } + +std::string CheckerboardEvent::GetLog() { + MonitorAutoLock lock(mRendertraceLock); + return mRendertraceInfo.str(); +} + +bool CheckerboardEvent::IsRecordingTrace() { return mRecordTrace; } + +void CheckerboardEvent::UpdateRendertraceProperty( + RendertraceProperty aProperty, const CSSRect& aRect, + const std::string& aExtraInfo) { + if (!mRecordTrace) { + return; + } + MonitorAutoLock lock(mRendertraceLock); + if (!mCheckerboardingActive) { + mBufferedProperties[aProperty].Update(aProperty, aRect, aExtraInfo, lock); + } else { + LogInfo(aProperty, TimeStamp::Now(), aRect, aExtraInfo, lock); + } +} + +void CheckerboardEvent::LogInfo(RendertraceProperty aProperty, + const TimeStamp& aTimestamp, + const CSSRect& aRect, + const std::string& aExtraInfo, + const MonitorAutoLock& aProofOfLock) { + MOZ_ASSERT(mRecordTrace); + if (mRendertraceInfo.tellp() >= LOG_LENGTH_LIMIT) { + // The log is already long enough, don't put more things into it. We'll + // append a truncation message when this event ends. + return; + } + // The log is consumed by the page at about:checkerboard. The format is not + // formally specced, but an informal description can be found at + // https://searchfox.org/mozilla-central/rev/d866b96d74ec2a63f09ee418f048d23f4fd379a2/toolkit/components/aboutcheckerboard/content/aboutCheckerboard.js#86 + mRendertraceInfo << "RENDERTRACE " + << (aTimestamp - mOriginTime).ToMilliseconds() << " rect " + << sColors[aProperty] << " " << aRect.X() << " " << aRect.Y() + << " " << aRect.Width() << " " << aRect.Height() << " " + << "// " << sDescriptions[aProperty] << aExtraInfo + << std::endl; +} + +bool CheckerboardEvent::RecordFrameInfo(uint32_t aCssPixelsCheckerboarded) { + TimeStamp sampleTime = TimeStamp::Now(); + bool eventEnding = false; + if (aCssPixelsCheckerboarded > 0) { + if (!mCheckerboardingActive) { + StartEvent(); + } + MOZ_ASSERT(mCheckerboardingActive); + MOZ_ASSERT(sampleTime >= mLastSampleTime); + mTotalPixelMs += + (uint64_t)((sampleTime - mLastSampleTime).ToMilliseconds() * + aCssPixelsCheckerboarded); + if (aCssPixelsCheckerboarded > mPeakPixels) { + mPeakPixels = aCssPixelsCheckerboarded; + } + mFrameCount++; + } else { + if (mCheckerboardingActive) { + StopEvent(); + eventEnding = true; + } + MOZ_ASSERT(!mCheckerboardingActive); + } + mLastSampleTime = sampleTime; + return eventEnding; +} + +void CheckerboardEvent::StartEvent() { + MOZ_LOG(sApzCheckLog, LogLevel::Debug, ("Starting checkerboard event")); + MOZ_ASSERT(!mCheckerboardingActive); + mCheckerboardingActive = true; + mStartTime = TimeStamp::Now(); + + if (!mRecordTrace) { + return; + } + MonitorAutoLock lock(mRendertraceLock); + std::vector history; + for (PropertyBuffer& bufferedProperty : mBufferedProperties) { + bufferedProperty.Flush(history, lock); + } + std::sort(history.begin(), history.end()); + for (const PropertyValue& p : history) { + LogInfo(p.mProperty, p.mTimeStamp, p.mRect, p.mExtraInfo, lock); + } + mRendertraceInfo << " -- checkerboarding starts below --" << std::endl; +} + +void CheckerboardEvent::StopEvent() { + MOZ_LOG(sApzCheckLog, LogLevel::Debug, ("Stopping checkerboard event")); + mCheckerboardingActive = false; + mEndTime = TimeStamp::Now(); + + if (!mRecordTrace) { + return; + } + MonitorAutoLock lock(mRendertraceLock); + if (mRendertraceInfo.tellp() >= LOG_LENGTH_LIMIT) { + mRendertraceInfo << "[logging aborted due to length limitations]\n"; + } + mRendertraceInfo << "Checkerboarded for " << mFrameCount << " frames (" + << (mEndTime - mStartTime).ToMilliseconds() << " ms), " + << mPeakPixels << " peak, " << GetSeverity() << " severity." + << std::endl; +} + +bool CheckerboardEvent::PropertyValue::operator<( + const PropertyValue& aOther) const { + if (mTimeStamp < aOther.mTimeStamp) { + return true; + } else if (mTimeStamp > aOther.mTimeStamp) { + return false; + } + return mProperty < aOther.mProperty; +} + +CheckerboardEvent::PropertyBuffer::PropertyBuffer() : mIndex(0) {} + +void CheckerboardEvent::PropertyBuffer::Update( + RendertraceProperty aProperty, const CSSRect& aRect, + const std::string& aExtraInfo, const MonitorAutoLock& aProofOfLock) { + mValues[mIndex] = {aProperty, TimeStamp::Now(), aRect, aExtraInfo}; + mIndex = (mIndex + 1) % BUFFER_SIZE; +} + +void CheckerboardEvent::PropertyBuffer::Flush( + std::vector& aOut, const MonitorAutoLock& aProofOfLock) { + for (uint32_t i = 0; i < BUFFER_SIZE; i++) { + uint32_t ix = (mIndex + i) % BUFFER_SIZE; + if (!mValues[ix].mTimeStamp.IsNull()) { + aOut.push_back(mValues[ix]); + mValues[ix].mTimeStamp = TimeStamp(); + } + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/src/CheckerboardEvent.h b/gfx/layers/apz/src/CheckerboardEvent.h new file mode 100644 index 0000000000..ac77750264 --- /dev/null +++ b/gfx/layers/apz/src/CheckerboardEvent.h @@ -0,0 +1,219 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_CheckerboardEvent_h +#define mozilla_layers_CheckerboardEvent_h + +#include "mozilla/DefineEnum.h" +#include "mozilla/Monitor.h" +#include "mozilla/TimeStamp.h" +#include +#include "Units.h" +#include + +namespace mozilla { +namespace layers { + +/** + * This class records information relevant to one "checkerboard event", which is + * a contiguous set of frames where a given APZC was checkerboarding. The intent + * of this class is to record enough information that it can provide actionable + * steps to reduce the occurrence of checkerboarding. Furthermore, it records + * information about the severity of the checkerboarding so as to allow + * prioritizing the debugging of some checkerboarding events over others. + */ +class CheckerboardEvent final { + public: + // clang-format off + MOZ_DEFINE_ENUM_AT_CLASS_SCOPE( + RendertraceProperty, ( + Page, + PaintedCriticalDisplayPort, + PaintedDisplayPort, + RequestedDisplayPort, + UserVisible + )); + // clang-format on + + static const char* sDescriptions[sRendertracePropertyCount]; + static const char* sColors[sRendertracePropertyCount]; + + public: + explicit CheckerboardEvent(bool aRecordTrace); + + /** + * Gets the "severity" of the checkerboard event. This doesn't have units, + * it's just useful for comparing two checkerboard events to see which one + * is worse, for some implementation-specific definition of "worse". + */ + uint32_t GetSeverity(); + + /** + * Gets the number of CSS pixels that were checkerboarded at the peak of the + * checkerboard event. + */ + uint32_t GetPeak(); + + /** + * Gets the length of the checkerboard event. + */ + TimeDuration GetDuration(); + + /** + * Gets the raw log of the checkerboard event. This can be called any time, + * although it really only makes sense to pull once the event is done, after + * RecordFrameInfo returns true. + */ + std::string GetLog(); + + /** + * Returns true iff this event is recording a detailed trace of the event. + * This is the argument passed in to the constructor. + */ + bool IsRecordingTrace(); + + /** + * Provide a new value for one of the rects that is tracked for + * checkerboard events. + */ + void UpdateRendertraceProperty(RendertraceProperty aProperty, + const CSSRect& aRect, + const std::string& aExtraInfo = std::string()); + + /** + * Provide the number of CSS pixels that are checkerboarded in a composite + * at the current time. + * @return true if the checkerboard event has completed. The caller should + * stop updating this object once this happens. + */ + bool RecordFrameInfo(uint32_t aCssPixelsCheckerboarded); + + private: + /** + * Helper method to do stuff when checkeboarding starts. + */ + void StartEvent(); + /** + * Helper method to do stuff when checkerboarding stops. + */ + void StopEvent(); + + /** + * Helper method to log a rendertrace property and its value to the + * rendertrace info buffer (mRendertraceInfo). + */ + void LogInfo(RendertraceProperty aProperty, const TimeStamp& aTimestamp, + const CSSRect& aRect, const std::string& aExtraInfo, + const MonitorAutoLock& aProofOfLock); + + /** + * Helper struct that holds a single rendertrace property value. + */ + struct PropertyValue { + RendertraceProperty mProperty; + TimeStamp mTimeStamp; + CSSRect mRect; + std::string mExtraInfo; + + bool operator<(const PropertyValue& aOther) const; + }; + + /** + * A circular buffer that stores the most recent BUFFER_SIZE values of a + * given property. + */ + class PropertyBuffer { + public: + PropertyBuffer(); + /** + * Add a new value to the buffer, overwriting the oldest one if needed. + */ + void Update(RendertraceProperty aProperty, const CSSRect& aRect, + const std::string& aExtraInfo, + const MonitorAutoLock& aProofOfLock); + /** + * Dump the recorded values, oldest to newest, to the given vector, and + * remove them from this buffer. + */ + void Flush(std::vector& aOut, + const MonitorAutoLock& aProofOfLock); + + private: + static const uint32_t BUFFER_SIZE = 5; + + /** + * The index of the oldest value in the buffer. This is the next index + * that will be written to. + */ + uint32_t mIndex; + PropertyValue mValues[BUFFER_SIZE]; + }; + + private: + /** + * If true, we should log the various properties during the checkerboard + * event. If false, we only need to record things we need for telemetry + * measures. + */ + const bool mRecordTrace; + /** + * A base time so that the other timestamps can be turned into durations. + */ + const TimeStamp mOriginTime; + /** + * Whether or not a checkerboard event is currently occurring. + */ + bool mCheckerboardingActive; + + /** + * The start time of the checkerboard event. + */ + TimeStamp mStartTime; + /** + * The end time of the checkerboard event. + */ + TimeStamp mEndTime; + /** + * The sample time of the last frame recorded. + */ + TimeStamp mLastSampleTime; + /** + * The number of contiguous frames with checkerboard. + */ + uint32_t mFrameCount; + /** + * The total number of pixel-milliseconds of checkerboarding visible to + * the user during the checkerboarding event. + */ + uint64_t mTotalPixelMs; + /** + * The largest number of pixels of checkerboarding visible to the user + * during any one frame, during this checkerboarding event. + */ + uint32_t mPeakPixels; + + /** + * Monitor that needs to be acquired before touching mBufferedProperties + * or mRendertraceInfo. + */ + mutable Monitor mRendertraceLock; + /** + * A circular buffer to store some properties. This is used before the + * checkerboarding actually starts, so that we have some data on what + * was happening before the checkerboarding started. + */ + PropertyBuffer mBufferedProperties[sRendertracePropertyCount]; + /** + * The rendertrace info buffer that gives us info on what was happening + * during the checkerboard event. + */ + std::ostringstream mRendertraceInfo; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_CheckerboardEvent_h diff --git a/gfx/layers/apz/src/DesktopFlingPhysics.h b/gfx/layers/apz/src/DesktopFlingPhysics.h new file mode 100644 index 0000000000..e93cc07a23 --- /dev/null +++ b/gfx/layers/apz/src/DesktopFlingPhysics.h @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_DesktopFlingPhysics_h_ +#define mozilla_layers_DesktopFlingPhysics_h_ + +#include "AsyncPanZoomController.h" +#include "Units.h" +#include "mozilla/Assertions.h" +#include "mozilla/StaticPrefs_apz.h" + +namespace mozilla { +namespace layers { + +class DesktopFlingPhysics { + public: + void Init(const ParentLayerPoint& aStartingVelocity, + float aPLPPI /* unused */) { + mVelocity = aStartingVelocity; + } + void Sample(const TimeDuration& aDelta, ParentLayerPoint* aOutVelocity, + ParentLayerPoint* aOutOffset) { + float friction = StaticPrefs::apz_fling_friction(); + float threshold = StaticPrefs::apz_fling_stopped_threshold(); + + mVelocity = ParentLayerPoint( + ApplyFrictionOrCancel(mVelocity.x, aDelta, friction, threshold), + ApplyFrictionOrCancel(mVelocity.y, aDelta, friction, threshold)); + + *aOutVelocity = mVelocity; + *aOutOffset = mVelocity * aDelta.ToMilliseconds(); + } + + private: + /** + * Applies friction to the given velocity and returns the result, or + * returns zero if the velocity is too low. + * |aVelocity| is the incoming velocity. + * |aDelta| is the amount of time that has passed since the last time + * friction was applied. + * |aFriction| is the amount of friction to apply. + * |aThreshold| is the velocity below which the fling is cancelled. + */ + static float ApplyFrictionOrCancel(float aVelocity, + const TimeDuration& aDelta, + float aFriction, float aThreshold) { + if (fabsf(aVelocity) <= aThreshold) { + // If the velocity is very low, just set it to 0 and stop the fling, + // otherwise we'll just asymptotically approach 0 and the user won't + // actually see any changes. + return 0.0f; + } + + aVelocity *= pow(1.0f - aFriction, float(aDelta.ToMilliseconds())); + return aVelocity; + } + + ParentLayerPoint mVelocity; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_DesktopFlingPhysics_h_ diff --git a/gfx/layers/apz/src/DragTracker.cpp b/gfx/layers/apz/src/DragTracker.cpp new file mode 100644 index 0000000000..aa3b2a34f7 --- /dev/null +++ b/gfx/layers/apz/src/DragTracker.cpp @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DragTracker.h" + +#include "InputData.h" +#include "mozilla/Logging.h" + +static mozilla::LazyLogModule sApzDrgLog("apz.drag"); +#define DRAG_LOG(...) MOZ_LOG(sApzDrgLog, LogLevel::Debug, (__VA_ARGS__)) + +namespace mozilla { +namespace layers { + +DragTracker::DragTracker() : mInDrag(false) {} + +/*static*/ +bool DragTracker::StartsDrag(const MouseInput& aInput) { + return aInput.IsLeftButton() && aInput.mType == MouseInput::MOUSE_DOWN; +} + +/*static*/ +bool DragTracker::EndsDrag(const MouseInput& aInput) { + // On Windows, we don't receive a MOUSE_UP at the end of a drag if an + // actual drag session took place. As a backup, we detect the end of the + // drag using the MOUSE_DRAG_END event, which normally is routed directly + // to content, but we're specially routing to APZ for this purpose. Bug + // 1265105 tracks a solution to this at the Windows widget layer; once + // that is implemented, this workaround can be removed. + return (aInput.IsLeftButton() && aInput.mType == MouseInput::MOUSE_UP) || + aInput.mType == MouseInput::MOUSE_DRAG_END; +} + +void DragTracker::Update(const MouseInput& aInput) { + if (StartsDrag(aInput)) { + DRAG_LOG("Starting drag\n"); + mInDrag = true; + } else if (EndsDrag(aInput)) { + DRAG_LOG("Ending drag\n"); + mInDrag = false; + mOnScrollbar = Nothing(); + } +} + +bool DragTracker::InDrag() const { return mInDrag; } + +bool DragTracker::IsOnScrollbar(bool aOnScrollbar) { + if (!mOnScrollbar) { + DRAG_LOG("Setting hitscrollbar %d\n", aOnScrollbar); + mOnScrollbar = Some(aOnScrollbar); + } + return mOnScrollbar.value(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/src/DragTracker.h b/gfx/layers/apz/src/DragTracker.h new file mode 100644 index 0000000000..92678d53c1 --- /dev/null +++ b/gfx/layers/apz/src/DragTracker.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_DragTracker_h +#define mozilla_layers_DragTracker_h + +#include "mozilla/EventForwards.h" +#include "mozilla/Maybe.h" + +namespace mozilla { + +class MouseInput; + +namespace layers { + +// DragTracker simply tracks a sequence of mouse inputs and allows us to tell +// if we are in a drag or not (i.e. the left mouse button went down and hasn't +// gone up yet). +class DragTracker { + public: + DragTracker(); + static bool StartsDrag(const MouseInput& aInput); + static bool EndsDrag(const MouseInput& aInput); + void Update(const MouseInput& aInput); + bool InDrag() const; + bool IsOnScrollbar(bool aOnScrollbar); + + private: + Maybe mOnScrollbar; + bool mInDrag; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* mozilla_layers_DragTracker_h */ diff --git a/gfx/layers/apz/src/ExpectedGeckoMetrics.cpp b/gfx/layers/apz/src/ExpectedGeckoMetrics.cpp new file mode 100644 index 0000000000..6056eb6456 --- /dev/null +++ b/gfx/layers/apz/src/ExpectedGeckoMetrics.cpp @@ -0,0 +1,20 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ExpectedGeckoMetrics.h" +#include "FrameMetrics.h" + +namespace mozilla { +namespace layers { + +void ExpectedGeckoMetrics::UpdateFrom(const FrameMetrics& aMetrics) { + mVisualScrollOffset = aMetrics.GetVisualScrollOffset(); + mZoom = aMetrics.GetZoom(); + mDevPixelsPerCSSPixel = aMetrics.GetDevPixelsPerCSSPixel(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/src/ExpectedGeckoMetrics.h b/gfx/layers/apz/src/ExpectedGeckoMetrics.h new file mode 100644 index 0000000000..b8295a4eb9 --- /dev/null +++ b/gfx/layers/apz/src/ExpectedGeckoMetrics.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_ExpectedGeckoMetrics_h +#define mozilla_layers_ExpectedGeckoMetrics_h + +#include "Units.h" + +namespace mozilla { +namespace layers { + +struct FrameMetrics; + +// A class that stores a subset of the FrameMetrics information +// than an APZC instance expects Gecko to have (either the +// metrics that were most recently sent to Gecko, or the ones +// most recently received from Gecko). +class ExpectedGeckoMetrics { + public: + ExpectedGeckoMetrics() = default; + void UpdateFrom(const FrameMetrics& aMetrics); + + const CSSPoint& GetVisualScrollOffset() const { return mVisualScrollOffset; } + const CSSToParentLayerScale2D& GetZoom() const { return mZoom; } + const CSSToLayoutDeviceScale& GetDevPixelsPerCSSPixel() const { + return mDevPixelsPerCSSPixel; + } + + private: + CSSPoint mVisualScrollOffset; + CSSToParentLayerScale2D mZoom; + CSSToLayoutDeviceScale mDevPixelsPerCSSPixel; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/apz/src/FlingAccelerator.cpp b/gfx/layers/apz/src/FlingAccelerator.cpp new file mode 100644 index 0000000000..769e9c37df --- /dev/null +++ b/gfx/layers/apz/src/FlingAccelerator.cpp @@ -0,0 +1,128 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FlingAccelerator.h" + +#include "mozilla/StaticPrefs_apz.h" + +#include "GenericFlingAnimation.h" // for FLING_LOG and FlingHandoffState + +namespace mozilla { +namespace layers { + +void FlingAccelerator::Reset() { + mPreviousFlingStartingVelocity = ParentLayerPoint{}; + mPreviousFlingCancelVelocity = ParentLayerPoint{}; + mIsTracking = false; +} + +static bool SameDirection(float aVelocity1, float aVelocity2) { + return (aVelocity1 == 0.0f) || (aVelocity2 == 0.0f) || + (IsNegative(aVelocity1) == IsNegative(aVelocity2)); +} + +static float Accelerate(float aBase, float aSupplemental) { + return (aBase * StaticPrefs::apz_fling_accel_base_mult()) + + (aSupplemental * StaticPrefs::apz_fling_accel_supplemental_mult()); +} + +ParentLayerPoint FlingAccelerator::GetFlingStartingVelocity( + const SampleTime& aNow, const ParentLayerPoint& aVelocity, + const FlingHandoffState& aHandoffState) { + // If the fling should be accelerated and is in the same direction as the + // previous fling, boost the velocity to be the sum of the two. Check separate + // axes separately because we could have two vertical flings with small + // horizontal components on the opposite side of zero, and we still want the + // y-fling to get accelerated. + ParentLayerPoint velocity = aVelocity; + if (ShouldAccelerate(aNow, aVelocity, aHandoffState)) { + if (velocity.x != 0 && + SameDirection(velocity.x, mPreviousFlingStartingVelocity.x)) { + velocity.x = Accelerate(velocity.x, mPreviousFlingStartingVelocity.x); + FLING_LOG("%p Applying fling x-acceleration from %f to %f (delta %f)\n", + this, aVelocity.x, velocity.x, + mPreviousFlingStartingVelocity.x); + } + if (velocity.y != 0 && + SameDirection(velocity.y, mPreviousFlingStartingVelocity.y)) { + velocity.y = Accelerate(velocity.y, mPreviousFlingStartingVelocity.y); + FLING_LOG("%p Applying fling y-acceleration from %f to %f (delta %f)\n", + this, aVelocity.y, velocity.y, + mPreviousFlingStartingVelocity.y); + } + } + + Reset(); + + mPreviousFlingStartingVelocity = velocity; + mIsTracking = true; + + return velocity; +} + +bool FlingAccelerator::ShouldAccelerate( + const SampleTime& aNow, const ParentLayerPoint& aVelocity, + const FlingHandoffState& aHandoffState) const { + if (!IsTracking()) { + FLING_LOG("%p Fling accelerator was reset, not accelerating.\n", this); + return false; + } + + if (!aHandoffState.mTouchStartRestingTime) { + FLING_LOG("%p Don't have a touch start resting time, not accelerating.\n", + this); + return false; + } + + double msBetweenTouchStartAndPanStart = + aHandoffState.mTouchStartRestingTime->ToMilliseconds(); + FLING_LOG( + "%p ShouldAccelerate with pan velocity %f pixels/ms, min pan velocity %f " + "pixels/ms, previous fling cancel velocity %f pixels/ms, time elapsed " + "since starting previous time between touch start and pan " + "start %fms.\n", + this, float(aVelocity.Length()), float(aHandoffState.mMinPanVelocity), + float(mPreviousFlingCancelVelocity.Length()), + float(msBetweenTouchStartAndPanStart)); + + if (aVelocity.Length() < StaticPrefs::apz_fling_accel_min_fling_velocity()) { + FLING_LOG("%p Fling velocity too low (%f), not accelerating.\n", this, + float(aVelocity.Length())); + return false; + } + + if (aHandoffState.mMinPanVelocity < + StaticPrefs::apz_fling_accel_min_pan_velocity()) { + FLING_LOG( + "%p Panning velocity was too slow at some point during the pan (%f), " + "not accelerating.\n", + this, float(aHandoffState.mMinPanVelocity)); + return false; + } + + if (mPreviousFlingCancelVelocity.Length() < + StaticPrefs::apz_fling_accel_min_fling_velocity()) { + FLING_LOG( + "%p The previous fling animation had slowed down too much when it was " + "interrupted (%f), not accelerating.\n", + this, float(mPreviousFlingCancelVelocity.Length())); + return false; + } + + if (msBetweenTouchStartAndPanStart >= + StaticPrefs::apz_fling_accel_max_pause_interval_ms()) { + FLING_LOG( + "%p Too much time (%fms) elapsed between touch start and pan start, " + "not accelerating.\n", + this, msBetweenTouchStartAndPanStart); + return false; + } + + return true; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/src/FlingAccelerator.h b/gfx/layers/apz/src/FlingAccelerator.h new file mode 100644 index 0000000000..49e9a7b257 --- /dev/null +++ b/gfx/layers/apz/src/FlingAccelerator.h @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_FlingAccelerator_h +#define mozilla_layers_FlingAccelerator_h + +#include "mozilla/layers/SampleTime.h" +#include "Units.h" + +namespace mozilla { +namespace layers { + +struct FlingHandoffState; + +/** + * This class is used to track state that is used when determining whether a + * fling should be accelerated. + */ +class FlingAccelerator final { + public: + FlingAccelerator() {} + + // Resets state so that the next fling will not be accelerated. + void Reset(); + + // Returns false after a reset or before the first fling. + bool IsTracking() const { return mIsTracking; } + + // Starts a new fling, and returns the (potentially accelerated) velocity that + // should be used for that fling. + ParentLayerPoint GetFlingStartingVelocity( + const SampleTime& aNow, const ParentLayerPoint& aVelocity, + const FlingHandoffState& aHandoffState); + + void ObserveFlingCanceled(const ParentLayerPoint& aVelocity) { + mPreviousFlingCancelVelocity = aVelocity; + } + + protected: + bool ShouldAccelerate(const SampleTime& aNow, + const ParentLayerPoint& aVelocity, + const FlingHandoffState& aHandoffState) const; + + // The initial velocity of the most recent fling. + ParentLayerPoint mPreviousFlingStartingVelocity; + // The velocity that the previous fling animation had at the point it was + // interrupted. + ParentLayerPoint mPreviousFlingCancelVelocity; + // Whether the upcoming fling is eligible for acceleration. + bool mIsTracking = false; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_FlingAccelerator_h diff --git a/gfx/layers/apz/src/FocusState.cpp b/gfx/layers/apz/src/FocusState.cpp new file mode 100644 index 0000000000..b7510bcef4 --- /dev/null +++ b/gfx/layers/apz/src/FocusState.cpp @@ -0,0 +1,225 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FocusState.h" + +#include "mozilla/Logging.h" +#include "mozilla/layers/APZThreadUtils.h" + +static mozilla::LazyLogModule sApzFstLog("apz.focusstate"); +#define FS_LOG(...) MOZ_LOG(sApzFstLog, LogLevel::Debug, (__VA_ARGS__)) + +namespace mozilla { +namespace layers { + +FocusState::FocusState() + : mMutex("FocusStateMutex"), + mLastAPZProcessedEvent(1), + mLastContentProcessedEvent(0), + mFocusHasKeyEventListeners(false), + mReceivedUpdate(false), + mFocusLayersId{0}, + mFocusHorizontalTarget(ScrollableLayerGuid::NULL_SCROLL_ID), + mFocusVerticalTarget(ScrollableLayerGuid::NULL_SCROLL_ID) {} + +uint64_t FocusState::LastAPZProcessedEvent() const { + APZThreadUtils::AssertOnControllerThread(); + MutexAutoLock lock(mMutex); + + return mLastAPZProcessedEvent; +} + +bool FocusState::IsCurrent(const MutexAutoLock& aProofOfLock) const { + FS_LOG("Checking IsCurrent() with cseq=%" PRIu64 ", aseq=%" PRIu64 "\n", + mLastContentProcessedEvent, mLastAPZProcessedEvent); + + MOZ_ASSERT(mLastContentProcessedEvent <= mLastAPZProcessedEvent); + return mLastContentProcessedEvent == mLastAPZProcessedEvent; +} + +void FocusState::ReceiveFocusChangingEvent() { + APZThreadUtils::AssertOnControllerThread(); + MutexAutoLock lock(mMutex); + + if (!mReceivedUpdate) { + // In the initial state don't advance mLastAPZProcessedEvent because we + // might blow away the information that we're in a freshly-restarted GPU + // process. This information (i.e. that mLastAPZProcessedEvent == 1) needs + // to be preserved until the first call to Update() which will then advance + // mLastAPZProcessedEvent to match the content-side sequence number. + return; + } + mLastAPZProcessedEvent += 1; + FS_LOG("Focus changing event incremented aseq to %" PRIu64 "\n", + mLastAPZProcessedEvent); +} + +void FocusState::Update(LayersId aRootLayerTreeId, + LayersId aOriginatingLayersId, + const FocusTarget& aState) { + // This runs on the updater thread, it's not worth passing around extra raw + // pointers just to assert it. + + MutexAutoLock lock(mMutex); + + FS_LOG("Update with rlt=%" PRIu64 ", olt=%" PRIu64 ", ft=(%s, %" PRIu64 ")\n", + aRootLayerTreeId.mId, aOriginatingLayersId.mId, aState.Type(), + aState.mSequenceNumber); + mReceivedUpdate = true; + + // Update the focus tree with the latest target + mFocusTree[aOriginatingLayersId] = aState; + + // Reset our internal state so we can recalculate it + mFocusHasKeyEventListeners = false; + mFocusLayersId = aRootLayerTreeId; + mFocusHorizontalTarget = ScrollableLayerGuid::NULL_SCROLL_ID; + mFocusVerticalTarget = ScrollableLayerGuid::NULL_SCROLL_ID; + + // To update the focus state for the entire APZCTreeManager, we need + // to traverse the focus tree to find the current leaf which is the global + // focus target we can use for async keyboard scrolling + while (true) { + auto currentNode = mFocusTree.find(mFocusLayersId); + if (currentNode == mFocusTree.end()) { + FS_LOG("Setting target to nil (cannot find lt=%" PRIu64 ")\n", + mFocusLayersId.mId); + return; + } + + const FocusTarget& target = currentNode->second; + + // Accumulate event listener flags on the path to the focus target + mFocusHasKeyEventListeners |= target.mFocusHasKeyEventListeners; + + // Match on the data stored in mData + // The match functions return true or false depending on whether the + // enclosing method, FocusState::Update, should return or continue to the + // next iteration of the while loop, respectively. + struct FocusTargetDataMatcher { + FocusState& mFocusState; + const uint64_t mSequenceNumber; + + bool operator()(const FocusTarget::NoFocusTarget& aNoFocusTarget) { + FS_LOG("Setting target to nil (reached a nil target) with seq=%" PRIu64 + "\n", + mSequenceNumber); + + // Mark what sequence number this target has for debugging purposes so + // we can always accurately report on whether we are stale or not + mFocusState.mLastContentProcessedEvent = mSequenceNumber; + + // If this focus state was just created and content has experienced more + // events then us, then assume we were recreated and sync focus sequence + // numbers. + if (mFocusState.mLastAPZProcessedEvent == 1 && + mFocusState.mLastContentProcessedEvent > + mFocusState.mLastAPZProcessedEvent) { + mFocusState.mLastAPZProcessedEvent = + mFocusState.mLastContentProcessedEvent; + } + return true; + } + + bool operator()(const LayersId& aRefLayerId) { + // Guard against infinite loops + MOZ_ASSERT(mFocusState.mFocusLayersId != aRefLayerId); + if (mFocusState.mFocusLayersId == aRefLayerId) { + FS_LOG( + "Setting target to nil (bailing out of infinite loop, lt=%" PRIu64 + ")\n", + mFocusState.mFocusLayersId.mId); + return true; + } + + FS_LOG("Looking for target in lt=%" PRIu64 "\n", aRefLayerId.mId); + + // The focus target is in a child layer tree + mFocusState.mFocusLayersId = aRefLayerId; + return false; + } + + bool operator()(const FocusTarget::ScrollTargets& aScrollTargets) { + FS_LOG("Setting target to h=%" PRIu64 ", v=%" PRIu64 + ", and seq=%" PRIu64 "\n", + aScrollTargets.mHorizontal, aScrollTargets.mVertical, + mSequenceNumber); + + // This is the global focus target + mFocusState.mFocusHorizontalTarget = aScrollTargets.mHorizontal; + mFocusState.mFocusVerticalTarget = aScrollTargets.mVertical; + + // Mark what sequence number this target has so we can determine whether + // it is stale or not + mFocusState.mLastContentProcessedEvent = mSequenceNumber; + + // If this focus state was just created and content has experienced more + // events then us, then assume we were recreated and sync focus sequence + // numbers. + if (mFocusState.mLastAPZProcessedEvent == 1 && + mFocusState.mLastContentProcessedEvent > + mFocusState.mLastAPZProcessedEvent) { + mFocusState.mLastAPZProcessedEvent = + mFocusState.mLastContentProcessedEvent; + } + return true; + } + }; // struct FocusTargetDataMatcher + + if (target.mData.match( + FocusTargetDataMatcher{*this, target.mSequenceNumber})) { + return; + } + } +} + +void FocusState::RemoveFocusTarget(LayersId aLayersId) { + // This runs on the updater thread, it's not worth passing around extra raw + // pointers just to assert it. + MutexAutoLock lock(mMutex); + + mFocusTree.erase(aLayersId); +} + +Maybe FocusState::GetHorizontalTarget() const { + APZThreadUtils::AssertOnControllerThread(); + MutexAutoLock lock(mMutex); + + // There is not a scrollable layer to async scroll if + // 1. We aren't current + // 2. There are event listeners that could change the focus + // 3. The target has not been layerized + if (!IsCurrent(lock) || mFocusHasKeyEventListeners || + mFocusHorizontalTarget == ScrollableLayerGuid::NULL_SCROLL_ID) { + return Nothing(); + } + return Some(ScrollableLayerGuid(mFocusLayersId, 0, mFocusHorizontalTarget)); +} + +Maybe FocusState::GetVerticalTarget() const { + APZThreadUtils::AssertOnControllerThread(); + MutexAutoLock lock(mMutex); + + // There is not a scrollable layer to async scroll if: + // 1. We aren't current + // 2. There are event listeners that could change the focus + // 3. The target has not been layerized + if (!IsCurrent(lock) || mFocusHasKeyEventListeners || + mFocusVerticalTarget == ScrollableLayerGuid::NULL_SCROLL_ID) { + return Nothing(); + } + return Some(ScrollableLayerGuid(mFocusLayersId, 0, mFocusVerticalTarget)); +} + +bool FocusState::CanIgnoreKeyboardShortcutMisses() const { + APZThreadUtils::AssertOnControllerThread(); + MutexAutoLock lock(mMutex); + + return IsCurrent(lock) && !mFocusHasKeyEventListeners; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/src/FocusState.h b/gfx/layers/apz/src/FocusState.h new file mode 100644 index 0000000000..37c44ec698 --- /dev/null +++ b/gfx/layers/apz/src/FocusState.h @@ -0,0 +1,175 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_FocusState_h +#define mozilla_layers_FocusState_h + +#include // for std::unordered_map +#include // for std::unordered_set + +#include "mozilla/layers/FocusTarget.h" // for FocusTarget +#include "mozilla/layers/ScrollableLayerGuid.h" // for ViewID +#include "mozilla/Mutex.h" // for Mutex + +namespace mozilla { +namespace layers { + +/** + * This class is used for tracking chrome and content focus targets and + * calculating global focus information from them for use by APZCTreeManager + * for async keyboard scrolling. + * + * # Calculating the element to scroll + * + * Chrome and content processes have independently focused elements. This makes + * it difficult to calculate the global focused element and its scrollable + * frame from the chrome or content side. So instead we send the local focus + * information from each process to here and then calculate the global focus + * information. This local information resides in a `focus target`. + * + * A focus target indicates that either: + * 1. The focused element is a remote browser along with its layer tree ID + * 2. The focused element is not scrollable + * 3. The focused element is scrollable along with the ViewID's of its + scrollable layers + * + * Using this information we can determine the global focus information by + * starting at the focus target of the root layer tree ID and following remote + * browsers until we reach a scrollable or non-scrollable focus target. + * + * # Determinism and sequence numbers + * + * The focused element in content can be changed within any javascript code. And + * javascript can run in response to an event or at any moment from `setTimeout` + * and others. This makes it impossible to always have the current focus + * information in APZ as it can be changed asynchronously at any moment. If we + * don't have the latest focus information, we may incorrectly scroll a target + * when we shouldn't. + * + * A tradeoff is designed here whereby we will maintain deterministic focus + * changes for user input, but not for other javascript code. The reasoning + * here is that `setTimeout` and others are already non-deterministic and so it + * might not be as breaking to web content. + * + * To maintain deterministic focus changes for a given stream of user inputs, + * we invalidate our focus state whenever we receive a user input that may + * trigger event listeners. We then attach a new sequence number to these + * events and dispatch them to content. Content will then include the latest + * sequence number it has processed to every focus update. Using this we can + * determine whether any potentially focus changing events have yet to be + * handled by content. + * + * Once we have received the latest focus sequence number from content, we know + * that all event listeners triggered by user inputs, and their resulting focus + * changes, have been processed and so we have a current target that we can use + * again. + */ +class FocusState final { + public: + FocusState(); + + /** + * The sequence number of the last potentially focus changing event processed + * by APZ. This number starts at one and increases monotonically. This number + * will never be zero as that is used to catch uninitialized focus sequence + * numbers on input events. + */ + uint64_t LastAPZProcessedEvent() const; + + /** + * Notify focus state of a potentially focus changing event. This will + * increment the current focus sequence number. The new value can be gotten + * from LastAPZProcessedEvent(). + */ + void ReceiveFocusChangingEvent(); + + /** + * Update the internal focus tree and recalculate the global focus target for + * a focus target update received from chrome or content. + * + * @param aRootLayerTreeId the layer tree ID of the root layer for the + parent APZCTreeManager + * @param aOriginatingLayersId the layer tree ID that this focus target + belongs to + */ + void Update(LayersId aRootLayerTreeId, LayersId aOriginatingLayersId, + const FocusTarget& aTarget); + + /** + * Removes a focus target by its layer tree ID. + */ + void RemoveFocusTarget(LayersId aLayersId); + + /** + * Gets the scrollable layer that should be horizontally scrolled for a key + * event, if any. The returned ScrollableLayerGuid doesn't contain a + * presShellId, and so it should not be used in comparisons. + * + * No scrollable layer is returned if any of the following are true: + * 1. We don't have a current focus target + * 2. There are event listeners that could change the focus + * 3. The target has not been layerized + */ + Maybe GetHorizontalTarget() const; + /** + * The same as GetHorizontalTarget() but for vertical scrolling. + */ + Maybe GetVerticalTarget() const; + + /** + * Gets whether it is safe to not increment the focus sequence number for an + * unmatched keyboard event. + */ + bool CanIgnoreKeyboardShortcutMisses() const; + + private: + /** + * Whether the current focus state is known to be current or else if an event + * has been processed that could change the focus but we have not received an + * update with a new confirmed target. + * This can only be called by methods that have already acquired mMutex; they + * have to pass their lock as compile-time proof. + */ + bool IsCurrent(const MutexAutoLock& aLock) const; + + private: + // All methods should hold this lock, since this class is accessed via both + // the updater and controller threads. + mutable Mutex mMutex; + + // The set of focus targets received indexed by their layer tree ID + std::unordered_map mFocusTree; + + // The focus sequence number of the last potentially focus changing event + // processed by APZ. This number starts at one and increases monotonically. + // We don't worry about wrap around here because at a pace of 100 + // increments/sec, it would take 5.85*10^9 years before we would wrap around. + // This number will never be zero as that is used to catch uninitialized focus + // sequence numbers on input events. + uint64_t mLastAPZProcessedEvent; + // The focus sequence number last received in a focus update. + uint64_t mLastContentProcessedEvent; + + // A flag whether there is a key listener on the event target chain for the + // focused element + bool mFocusHasKeyEventListeners; + // A flag that is false until the first call to Update(). + bool mReceivedUpdate; + + // The layer tree ID which contains the scrollable frame of the focused + // element + LayersId mFocusLayersId; + // The scrollable layer corresponding to the scrollable frame that is used to + // scroll the focused element. This depends on the direction the user is + // scrolling. + ScrollableLayerGuid::ViewID mFocusHorizontalTarget; + ScrollableLayerGuid::ViewID mFocusVerticalTarget; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_FocusState_h diff --git a/gfx/layers/apz/src/FocusTarget.cpp b/gfx/layers/apz/src/FocusTarget.cpp new file mode 100644 index 0000000000..245829667d --- /dev/null +++ b/gfx/layers/apz/src/FocusTarget.cpp @@ -0,0 +1,229 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/FocusTarget.h" +#include "mozilla/dom/BrowserBridgeChild.h" // for BrowserBridgeChild +#include "mozilla/dom/EventTarget.h" // for EventTarget +#include "mozilla/dom/RemoteBrowser.h" // For RemoteBrowser +#include "mozilla/EventDispatcher.h" // for EventDispatcher +#include "mozilla/PresShell.h" // For PresShell +#include "mozilla/StaticPrefs_apz.h" +#include "nsIContentInlines.h" // for nsINode::IsEditable() +#include "nsLayoutUtils.h" // for nsLayoutUtils + +static mozilla::LazyLogModule sApzFtgLog("apz.focustarget"); +#define FT_LOG(...) MOZ_LOG(sApzFtgLog, LogLevel::Debug, (__VA_ARGS__)) + +using namespace mozilla::dom; +using namespace mozilla::layout; + +namespace mozilla { +namespace layers { + +static PresShell* GetRetargetEventPresShell(PresShell* aRootPresShell) { + MOZ_ASSERT(aRootPresShell); + + // Use the last focused window in this PresShell and its + // associated PresShell + nsCOMPtr window = + aRootPresShell->GetFocusedDOMWindowInOurWindow(); + if (!window) { + return nullptr; + } + + RefPtr retargetEventDoc = window->GetExtantDoc(); + if (!retargetEventDoc) { + return nullptr; + } + + return retargetEventDoc->GetPresShell(); +} + +static bool HasListenersForKeyEvents(nsIContent* aContent) { + if (!aContent) { + return false; + } + + WidgetEvent event(true, eVoidEvent); + nsTArray targets; + nsresult rv = EventDispatcher::Dispatch(aContent, nullptr, &event, nullptr, + nullptr, nullptr, &targets); + NS_ENSURE_SUCCESS(rv, false); + for (size_t i = 0; i < targets.Length(); i++) { + if (targets[i]->HasNonSystemGroupListenersForUntrustedKeyEvents()) { + return true; + } + } + return false; +} + +static bool HasListenersForNonPassiveKeyEvents(nsIContent* aContent) { + if (!aContent) { + return false; + } + + WidgetEvent event(true, eVoidEvent); + nsTArray targets; + nsresult rv = EventDispatcher::Dispatch(aContent, nullptr, &event, nullptr, + nullptr, nullptr, &targets); + NS_ENSURE_SUCCESS(rv, false); + for (size_t i = 0; i < targets.Length(); i++) { + if (targets[i] + ->HasNonPassiveNonSystemGroupListenersForUntrustedKeyEvents()) { + return true; + } + } + return false; +} + +static bool IsEditableNode(nsINode* aNode) { + return aNode && aNode->IsEditable(); +} + +FocusTarget::FocusTarget() + : mSequenceNumber(0), + mFocusHasKeyEventListeners(false), + mData(AsVariant(NoFocusTarget())) {} + +FocusTarget::FocusTarget(PresShell* aRootPresShell, + uint64_t aFocusSequenceNumber) + : mSequenceNumber(aFocusSequenceNumber), + mFocusHasKeyEventListeners(false), + mData(AsVariant(NoFocusTarget())) { + MOZ_ASSERT(aRootPresShell); + MOZ_ASSERT(NS_IsMainThread()); + + // Key events can be retargeted to a child PresShell when there is an iframe + RefPtr presShell = GetRetargetEventPresShell(aRootPresShell); + + if (!presShell) { + FT_LOG("Creating nil target with seq=%" PRIu64 + " (can't find retargeted presshell)\n", + aFocusSequenceNumber); + + return; + } + + RefPtr document = presShell->GetDocument(); + if (!document) { + FT_LOG("Creating nil target with seq=%" PRIu64 " (no document)\n", + aFocusSequenceNumber); + + return; + } + + // Find the focused content and use it to determine whether there are key + // event listeners or whether key events will be targeted at a different + // process through a remote browser. + nsCOMPtr focusedContent = + presShell->GetFocusedContentInOurWindow(); + nsCOMPtr keyEventTarget = focusedContent; + + // If there is no focused element then event dispatch goes to the body of + // the page if it exists or the root element. + if (!keyEventTarget) { + keyEventTarget = document->GetUnfocusedKeyEventTarget(); + } + + // Check if there are key event listeners that could prevent default or change + // the focus or selection of the page. + if (StaticPrefs::apz_keyboard_passive_listeners()) { + mFocusHasKeyEventListeners = + HasListenersForNonPassiveKeyEvents(keyEventTarget.get()); + } else { + mFocusHasKeyEventListeners = HasListenersForKeyEvents(keyEventTarget.get()); + } + + // Check if the key event target is content editable or if the document + // is in design mode. + if (IsEditableNode(keyEventTarget) || IsEditableNode(document)) { + FT_LOG("Creating nil target with seq=%" PRIu64 + ", kl=%d (disabling for editable node)\n", + aFocusSequenceNumber, static_cast(mFocusHasKeyEventListeners)); + + return; + } + + // Check if the key event target is a remote browser + if (RemoteBrowser* remoteBrowser = RemoteBrowser::GetFrom(keyEventTarget)) { + LayersId layersId = remoteBrowser->GetLayersId(); + + // The globally focused element for scrolling is in a remote layer tree + if (layersId.IsValid()) { + FT_LOG("Creating reflayer target with seq=%" PRIu64 ", kl=%d, lt=%" PRIu64 + "\n", + aFocusSequenceNumber, mFocusHasKeyEventListeners, layersId.mId); + + mData = AsVariant(std::move(layersId)); + return; + } + + FT_LOG("Creating nil target with seq=%" PRIu64 + ", kl=%d (remote browser missing layers id)\n", + aFocusSequenceNumber, mFocusHasKeyEventListeners); + + return; + } + + // The content to scroll is either the focused element or the focus node of + // the selection. It's difficult to determine if an element is an interactive + // element requiring async keyboard scrolling to be disabled. So we only + // allow async key scrolling based on the selection, which doesn't have + // this problem and is more common. + if (focusedContent) { + FT_LOG("Creating nil target with seq=%" PRIu64 + ", kl=%d (disabling for focusing an element)\n", + aFocusSequenceNumber, mFocusHasKeyEventListeners); + + return; + } + + nsCOMPtr selectedContent = + presShell->GetSelectedContentForScrolling(); + + // Gather the scrollable frames that would be scrolled in each direction + // for this scroll target + nsIScrollableFrame* horizontal = + presShell->GetScrollableFrameToScrollForContent( + selectedContent.get(), HorizontalScrollDirection); + nsIScrollableFrame* vertical = + presShell->GetScrollableFrameToScrollForContent(selectedContent.get(), + VerticalScollDirection); + + // We might have the globally focused element for scrolling. Gather a ViewID + // for the horizontal and vertical scroll targets of this element. + ScrollTargets target; + target.mHorizontal = nsLayoutUtils::FindIDForScrollableFrame(horizontal); + target.mVertical = nsLayoutUtils::FindIDForScrollableFrame(vertical); + mData = AsVariant(target); + + FT_LOG("Creating scroll target with seq=%" PRIu64 ", kl=%d, h=%" PRIu64 + ", v=%" PRIu64 "\n", + aFocusSequenceNumber, mFocusHasKeyEventListeners, target.mHorizontal, + target.mVertical); +} + +bool FocusTarget::operator==(const FocusTarget& aRhs) const { + return mSequenceNumber == aRhs.mSequenceNumber && + mFocusHasKeyEventListeners == aRhs.mFocusHasKeyEventListeners && + mData == aRhs.mData; +} + +const char* FocusTarget::Type() const { + if (mData.is()) { + return "LayersId"; + } + if (mData.is()) { + return "ScrollTargets"; + } + if (mData.is()) { + return "NoFocusTarget"; + } + return ""; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/src/FocusTarget.h b/gfx/layers/apz/src/FocusTarget.h new file mode 100644 index 0000000000..f4caa5d070 --- /dev/null +++ b/gfx/layers/apz/src/FocusTarget.h @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_FocusTarget_h +#define mozilla_layers_FocusTarget_h + +#include // for int32_t, uint32_t + +#include "mozilla/DefineEnum.h" // for MOZ_DEFINE_ENUM +#include "mozilla/layers/ScrollableLayerGuid.h" // for ViewID +#include "mozilla/Variant.h" // for Variant +#include "mozilla/Maybe.h" // for Maybe + +namespace mozilla { + +class PresShell; + +namespace layers { + +/** + * This class is used for communicating information about the currently focused + * element of a document and the scrollable frames to use when keyboard + * scrolling it. It is created on the main thread at paint-time, but is then + * passed over IPC to the compositor/APZ code. + */ +class FocusTarget final { + public: + struct ScrollTargets { + ScrollableLayerGuid::ViewID mHorizontal; + ScrollableLayerGuid::ViewID mVertical; + + bool operator==(const ScrollTargets& aRhs) const { + return (mHorizontal == aRhs.mHorizontal && mVertical == aRhs.mVertical); + } + }; + + // We need this to represent the case where mData has no focus target data + // because we can't have an empty variant + struct NoFocusTarget { + bool operator==(const NoFocusTarget& aRhs) const { return true; } + }; + + FocusTarget(); + + /** + * Construct a focus target for the specified top level PresShell + */ + FocusTarget(PresShell* aRootPresShell, uint64_t aFocusSequenceNumber); + + bool operator==(const FocusTarget& aRhs) const; + + const char* Type() const; + + public: + // The content sequence number recorded at the time of this class's creation + uint64_t mSequenceNumber; + + // Whether there are keydown, keypress, or keyup event listeners + // in the event target chain of the focused element + bool mFocusHasKeyEventListeners; + + mozilla::Variant mData; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_FocusTarget_h diff --git a/gfx/layers/apz/src/GenericFlingAnimation.h b/gfx/layers/apz/src/GenericFlingAnimation.h new file mode 100644 index 0000000000..d67334f7ec --- /dev/null +++ b/gfx/layers/apz/src/GenericFlingAnimation.h @@ -0,0 +1,202 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_GenericFlingAnimation_h_ +#define mozilla_layers_GenericFlingAnimation_h_ + +#include "APZUtils.h" +#include "AsyncPanZoomAnimation.h" +#include "AsyncPanZoomController.h" +#include "FrameMetrics.h" +#include "Layers.h" +#include "Units.h" +#include "OverscrollHandoffState.h" +#include "mozilla/Assertions.h" +#include "mozilla/Monitor.h" +#include "mozilla/RefPtr.h" +#include "mozilla/StaticPrefs_apz.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/ToString.h" +#include "nsThreadUtils.h" + +static mozilla::LazyLogModule sApzFlgLog("apz.fling"); +#define FLING_LOG(...) MOZ_LOG(sApzFlgLog, LogLevel::Debug, (__VA_ARGS__)) + +namespace mozilla { +namespace layers { + +/** + * The FlingPhysics template parameter determines the physics model + * that the fling animation follows. It must have the following methods: + * + * - Default constructor. + * + * - Init(const ParentLayerPoint& aStartingVelocity, float aPLPPI). + * Called at the beginning of the fling, with the fling's starting velocity, + * and the number of ParentLayer pixels per (Screen) inch at the point of + * the fling's start in the fling's direction. + * + * - Sample(const TimeDuration& aDelta, + * ParentLayerPoint* aOutVelocity, + * ParentLayerPoint* aOutOffset); + * Called on each sample of the fling. + * |aDelta| is the time elapsed since the last sample. + * |aOutVelocity| should be the desired velocity after the current sample, + * in ParentLayer pixels per millisecond. + * |aOutOffset| should be the desired _delta_ to the scroll offset after + * the current sample. |aOutOffset| should _not_ be clamped to the APZC's + * scrollable bounds; the caller will do the clamping, and it needs to + * know the unclamped value to handle handoff/overscroll correctly. + */ +template +class GenericFlingAnimation : public AsyncPanZoomAnimation, + public FlingPhysics { + public: + GenericFlingAnimation(AsyncPanZoomController& aApzc, + const FlingHandoffState& aHandoffState, float aPLPPI) + : mApzc(aApzc), + mOverscrollHandoffChain(aHandoffState.mChain), + mScrolledApzc(aHandoffState.mScrolledApzc) { + MOZ_ASSERT(mOverscrollHandoffChain); + + // Drop any velocity on axes where we don't have room to scroll anyways + // (in this APZC, or an APZC further in the handoff chain). + // This ensures that we don't take the 'overscroll' path in Sample() + // on account of one axis which can't scroll having a velocity. + if (!mOverscrollHandoffChain->CanScrollInDirection( + &mApzc, ScrollDirection::eHorizontal)) { + RecursiveMutexAutoLock lock(mApzc.mRecursiveMutex); + mApzc.mX.SetVelocity(0); + } + if (!mOverscrollHandoffChain->CanScrollInDirection( + &mApzc, ScrollDirection::eVertical)) { + RecursiveMutexAutoLock lock(mApzc.mRecursiveMutex); + mApzc.mY.SetVelocity(0); + } + + if (aHandoffState.mIsHandoff) { + // Only apply acceleration in the APZC that originated the fling, not in + // APZCs further down the handoff chain during handoff. + mApzc.mFlingAccelerator.Reset(); + } + + ParentLayerPoint velocity = + mApzc.mFlingAccelerator.GetFlingStartingVelocity( + aApzc.GetFrameTime(), mApzc.GetVelocityVector(), aHandoffState); + + mApzc.SetVelocityVector(velocity); + + FlingPhysics::Init(mApzc.GetVelocityVector(), aPLPPI); + } + + /** + * Advances a fling by an interpolated amount based on the passed in |aDelta|. + * This should be called whenever sampling the content transform for this + * frame. Returns true if the fling animation should be advanced by one frame, + * or false if there is no fling or the fling has ended. + */ + virtual bool DoSample(FrameMetrics& aFrameMetrics, + const TimeDuration& aDelta) override { + ParentLayerPoint velocity; + ParentLayerPoint offset; + FlingPhysics::Sample(aDelta, &velocity, &offset); + + mApzc.SetVelocityVector(velocity); + + // If we shouldn't continue the fling, let's just stop and repaint. + if (IsZero(velocity)) { + FLING_LOG("%p ending fling animation. overscrolled=%d\n", &mApzc, + mApzc.IsOverscrolled()); + // This APZC or an APZC further down the handoff chain may be be + // overscrolled. Start a snap-back animation on the overscrolled APZC. + // Note: + // This needs to be a deferred task even though it can safely run + // while holding mRecursiveMutex, because otherwise, if the overscrolled + // APZC is this one, then the SetState(NOTHING) in UpdateAnimation will + // stomp on the SetState(SNAP_BACK) it does. + mDeferredTasks.AppendElement(NewRunnableMethod( + "layers::OverscrollHandoffChain::SnapBackOverscrolledApzc", + mOverscrollHandoffChain.get(), + &OverscrollHandoffChain::SnapBackOverscrolledApzc, &mApzc)); + return false; + } + + // Ordinarily we might need to do a ScheduleComposite if either of + // the following AdjustDisplacement calls returns true, but this + // is already running as part of a FlingAnimation, so we'll be compositing + // per frame of animation anyway. + ParentLayerPoint overscroll; + ParentLayerPoint adjustedOffset; + mApzc.mX.AdjustDisplacement(offset.x, adjustedOffset.x, overscroll.x); + mApzc.mY.AdjustDisplacement(offset.y, adjustedOffset.y, overscroll.y); + if (aFrameMetrics.GetZoom() != CSSToParentLayerScale2D(0, 0)) { + mApzc.ScrollBy(adjustedOffset / aFrameMetrics.GetZoom()); + } + + // The fling may have caused us to reach the end of our scroll range. + if (!IsZero(overscroll)) { + // Hand off the fling to the next APZC in the overscroll handoff chain. + + // We may have reached the end of the scroll range along one axis but + // not the other. In such a case we only want to hand off the relevant + // component of the fling. + if (FuzzyEqualsAdditive(overscroll.x, 0.0f, COORDINATE_EPSILON)) { + velocity.x = 0; + } else if (FuzzyEqualsAdditive(overscroll.y, 0.0f, COORDINATE_EPSILON)) { + velocity.y = 0; + } + + // To hand off the fling, we attempt to find a target APZC and start a new + // fling with the same velocity on that APZC. For simplicity, the actual + // overscroll of the current sample is discarded rather than being handed + // off. The compositor should sample animations sufficiently frequently + // that this is not noticeable. The target APZC is chosen by seeing if + // there is an APZC further in the handoff chain which is pannable; if + // there isn't, we take the new fling ourselves, entering an overscrolled + // state. + // Note: APZC is holding mRecursiveMutex, so directly calling + // HandleFlingOverscroll() (which acquires the tree lock) would violate + // the lock ordering. Instead we schedule HandleFlingOverscroll() to be + // called after mRecursiveMutex is released. + FLING_LOG("%p fling went into overscroll, handing off with velocity %s\n", + &mApzc, ToString(velocity).c_str()); + mDeferredTasks.AppendElement( + NewRunnableMethod, + RefPtr>( + "layers::AsyncPanZoomController::HandleFlingOverscroll", &mApzc, + &AsyncPanZoomController::HandleFlingOverscroll, velocity, + mOverscrollHandoffChain, mScrolledApzc)); + + // If there is a remaining velocity on this APZC, continue this fling + // as well. (This fling and the handed-off fling will run concurrently.) + // Note that AdjustDisplacement() will have zeroed out the velocity + // along the axes where we're overscrolled. + return !IsZero(mApzc.GetVelocityVector()); + } + + return true; + } + + void Cancel(CancelAnimationFlags aFlags) override { + mApzc.mFlingAccelerator.ObserveFlingCanceled(mApzc.GetVelocityVector()); + } + + virtual bool HandleScrollOffsetUpdate( + const Maybe& aRelativeDelta) override { + return true; + } + + private: + AsyncPanZoomController& mApzc; + RefPtr mOverscrollHandoffChain; + RefPtr mScrolledApzc; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_GenericFlingAnimation_h_ diff --git a/gfx/layers/apz/src/GenericScrollAnimation.cpp b/gfx/layers/apz/src/GenericScrollAnimation.cpp new file mode 100644 index 0000000000..4af29d99c5 --- /dev/null +++ b/gfx/layers/apz/src/GenericScrollAnimation.cpp @@ -0,0 +1,116 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GenericScrollAnimation.h" + +#include "AsyncPanZoomController.h" +#include "FrameMetrics.h" +#include "nsPoint.h" +#include "ScrollAnimationPhysics.h" +#include "ScrollAnimationBezierPhysics.h" +#include "ScrollAnimationMSDPhysics.h" +#include "mozilla/StaticPrefs_general.h" + +namespace mozilla { +namespace layers { + +GenericScrollAnimation::GenericScrollAnimation( + AsyncPanZoomController& aApzc, const nsPoint& aInitialPosition, + const ScrollAnimationBezierPhysicsSettings& aSettings) + : mApzc(aApzc), mFinalDestination(aInitialPosition) { + if (StaticPrefs::general_smoothScroll_msdPhysics_enabled()) { + mAnimationPhysics = MakeUnique(aInitialPosition); + } else { + mAnimationPhysics = + MakeUnique(aInitialPosition, aSettings); + } +} + +void GenericScrollAnimation::UpdateDelta(TimeStamp aTime, const nsPoint& aDelta, + const nsSize& aCurrentVelocity) { + mFinalDestination += aDelta; + + Update(aTime, aCurrentVelocity); +} + +void GenericScrollAnimation::UpdateDestination(TimeStamp aTime, + const nsPoint& aDestination, + const nsSize& aCurrentVelocity) { + mFinalDestination = aDestination; + + Update(aTime, aCurrentVelocity); +} + +void GenericScrollAnimation::Update(TimeStamp aTime, + const nsSize& aCurrentVelocity) { + // Clamp the final destination to the scrollable area. + CSSPoint clamped = CSSPoint::FromAppUnits(mFinalDestination); + clamped.x = mApzc.mX.ClampOriginToScrollableRect(clamped.x); + clamped.y = mApzc.mY.ClampOriginToScrollableRect(clamped.y); + mFinalDestination = CSSPoint::ToAppUnits(clamped); + + mAnimationPhysics->Update(aTime, mFinalDestination, aCurrentVelocity); +} + +bool GenericScrollAnimation::DoSample(FrameMetrics& aFrameMetrics, + const TimeDuration& aDelta) { + TimeStamp now = mApzc.GetFrameTime().Time(); + CSSToParentLayerScale2D zoom = aFrameMetrics.GetZoom(); + if (zoom == CSSToParentLayerScale2D(0, 0)) { + return false; + } + + // If the animation is finished, make sure the final position is correct by + // using one last displacement. Otherwise, compute the delta via the timing + // function as normal. + bool finished = mAnimationPhysics->IsFinished(now); + nsPoint sampledDest = mAnimationPhysics->PositionAt(now); + ParentLayerPoint displacement = (CSSPoint::FromAppUnits(sampledDest) - + aFrameMetrics.GetVisualScrollOffset()) * + zoom; + + if (finished) { + mApzc.mX.SetVelocity(0); + mApzc.mY.SetVelocity(0); + } else if (!IsZero(displacement)) { + // Convert velocity from AppUnits/Seconds to ParentLayerCoords/Milliseconds + nsSize velocity = mAnimationPhysics->VelocityAt(now); + ParentLayerPoint velocityPL = + CSSPoint::FromAppUnits(nsPoint(velocity.width, velocity.height)) * zoom; + mApzc.mX.SetVelocity(velocityPL.x / 1000.0); + mApzc.mY.SetVelocity(velocityPL.y / 1000.0); + } + // Note: we ignore overscroll for generic animations. + ParentLayerPoint adjustedOffset, overscroll; + mApzc.mX.AdjustDisplacement( + displacement.x, adjustedOffset.x, overscroll.x, + mDirectionForcedToOverscroll == Some(ScrollDirection::eHorizontal)); + mApzc.mY.AdjustDisplacement( + displacement.y, adjustedOffset.y, overscroll.y, + mDirectionForcedToOverscroll == Some(ScrollDirection::eVertical)); + // If we expected to scroll, but there's no more scroll range on either axis, + // then end the animation early. Note that the initial displacement could be 0 + // if the compositor ran very quickly (<1ms) after the animation was created. + // When that happens we want to make sure the animation continues. + if (!IsZero(displacement) && IsZero(adjustedOffset)) { + // Nothing more to do - end the animation. + return false; + } + mApzc.ScrollBy(adjustedOffset / zoom); + return !finished; +} + +bool GenericScrollAnimation::HandleScrollOffsetUpdate( + const Maybe& aRelativeDelta) { + if (aRelativeDelta) { + mAnimationPhysics->ApplyContentShift(*aRelativeDelta); + return true; + } + return false; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/src/GenericScrollAnimation.h b/gfx/layers/apz/src/GenericScrollAnimation.h new file mode 100644 index 0000000000..56a64dc5ec --- /dev/null +++ b/gfx/layers/apz/src/GenericScrollAnimation.h @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_GenericScrollAnimation_h_ +#define mozilla_layers_GenericScrollAnimation_h_ + +#include "AsyncPanZoomAnimation.h" + +namespace mozilla { + +struct ScrollAnimationBezierPhysicsSettings; +class ScrollAnimationPhysics; + +namespace layers { + +class AsyncPanZoomController; + +class GenericScrollAnimation : public AsyncPanZoomAnimation { + public: + GenericScrollAnimation(AsyncPanZoomController& aApzc, + const nsPoint& aInitialPosition, + const ScrollAnimationBezierPhysicsSettings& aSettings); + + bool DoSample(FrameMetrics& aFrameMetrics, + const TimeDuration& aDelta) override; + + bool HandleScrollOffsetUpdate(const Maybe& aRelativeDelta) override; + + void UpdateDelta(TimeStamp aTime, const nsPoint& aDelta, + const nsSize& aCurrentVelocity); + void UpdateDestination(TimeStamp aTime, const nsPoint& aDestination, + const nsSize& aCurrentVelocity); + + CSSPoint GetDestination() const { + return CSSPoint::FromAppUnits(mFinalDestination); + } + + private: + void Update(TimeStamp aTime, const nsSize& aCurrentVelocity); + + protected: + AsyncPanZoomController& mApzc; + UniquePtr mAnimationPhysics; + nsPoint mFinalDestination; + // If a direction is forced to overscroll, it means it's axis in that + // direction is locked, and scroll in that direction is treated as overscroll + // of an equal amount, which, for example, may then bubble up a scroll action + // to its parent, or may behave as whatever an overscroll occurence requires + // to behave + Maybe mDirectionForcedToOverscroll; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_GenericScrollAnimation_h_ diff --git a/gfx/layers/apz/src/GestureEventListener.cpp b/gfx/layers/apz/src/GestureEventListener.cpp new file mode 100644 index 0000000000..c21dc11ba4 --- /dev/null +++ b/gfx/layers/apz/src/GestureEventListener.cpp @@ -0,0 +1,666 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GestureEventListener.h" +#include // for max +#include // for fabsf +#include // for size_t +#include "AsyncPanZoomController.h" // for AsyncPanZoomController +#include "InputBlockState.h" // for TouchBlockState +#include "base/task.h" // for CancelableTask, etc +#include "InputBlockState.h" // for TouchBlockState +#include "mozilla/StaticPrefs_apz.h" +#include "mozilla/StaticPrefs_ui.h" +#include "nsDebug.h" // for NS_WARNING +#include "nsMathUtils.h" // for NS_hypot + +static mozilla::LazyLogModule sApzGelLog("apz.gesture"); +#define GEL_LOG(...) MOZ_LOG(sApzGelLog, LogLevel::Debug, (__VA_ARGS__)) + +namespace mozilla { +namespace layers { + +/** + * Amount of span or focus change needed to take us from the + * GESTURE_WAITING_PINCH state to the GESTURE_PINCH state. This is measured as + * either a change in distance between the fingers used to compute the span + * ratio, or the a change in position of the focus point between the two + * fingers. + */ +static const float PINCH_START_THRESHOLD = 35.0f; + +/** + * Determines how fast a one touch pinch zooms in and out. The greater the + * value, the faster it zooms. + */ +static const float ONE_TOUCH_PINCH_SPEED = 0.005f; + +static bool sLongTapEnabled = true; + +static ScreenPoint GetCurrentFocus(const MultiTouchInput& aEvent) { + const ScreenPoint& firstTouch = aEvent.mTouches[0].mScreenPoint; + const ScreenPoint& secondTouch = aEvent.mTouches[1].mScreenPoint; + return (firstTouch + secondTouch) / 2; +} + +static ScreenCoord GetCurrentSpan(const MultiTouchInput& aEvent) { + const ScreenPoint& firstTouch = aEvent.mTouches[0].mScreenPoint; + const ScreenPoint& secondTouch = aEvent.mTouches[1].mScreenPoint; + ScreenPoint delta = secondTouch - firstTouch; + return delta.Length(); +} + +ScreenCoord GestureEventListener::GetYSpanFromGestureStartPoint() { + // use the position that began the one-touch-pinch gesture rather + // mTouchStartPosition + const ScreenPoint start = mOneTouchPinchStartPosition; + const ScreenPoint& current = mTouches[0].mScreenPoint; + return current.y - start.y; +} + +static TapGestureInput CreateTapEvent(const MultiTouchInput& aTouch, + TapGestureInput::TapGestureType aType) { + return TapGestureInput(aType, aTouch.mTime, aTouch.mTimeStamp, + aTouch.mTouches[0].mScreenPoint, aTouch.modifiers); +} + +GestureEventListener::GestureEventListener( + AsyncPanZoomController* aAsyncPanZoomController) + : mAsyncPanZoomController(aAsyncPanZoomController), + mState(GESTURE_NONE), + mSpanChange(0.0f), + mPreviousSpan(0.0f), + mFocusChange(0.0f), + mLastTouchInput(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0), + mLastTapInput(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0), + mLongTapTimeoutTask(nullptr), + mMaxTapTimeoutTask(nullptr) {} + +GestureEventListener::~GestureEventListener() = default; + +nsEventStatus GestureEventListener::HandleInputEvent( + const MultiTouchInput& aEvent) { + GEL_LOG("Receiving event type %d with %zu touches in state %d\n", + aEvent.mType, aEvent.mTouches.Length(), mState); + + nsEventStatus rv = nsEventStatus_eIgnore; + + // Cache the current event since it may become the single or long tap that we + // send. + mLastTouchInput = aEvent; + + switch (aEvent.mType) { + case MultiTouchInput::MULTITOUCH_START: + mTouches.Clear(); + // Cache every touch. + for (size_t i = 0; i < aEvent.mTouches.Length(); i++) { + mTouches.AppendElement(aEvent.mTouches[i]); + } + + if (aEvent.mTouches.Length() == 1) { + rv = HandleInputTouchSingleStart(); + } else { + rv = HandleInputTouchMultiStart(); + } + break; + case MultiTouchInput::MULTITOUCH_MOVE: + // Update the screen points of the cached touches. + for (size_t i = 0; i < aEvent.mTouches.Length(); i++) { + for (size_t j = 0; j < mTouches.Length(); j++) { + if (aEvent.mTouches[i].mIdentifier == mTouches[j].mIdentifier) { + mTouches[j].mScreenPoint = aEvent.mTouches[i].mScreenPoint; + mTouches[j].mLocalScreenPoint = + aEvent.mTouches[i].mLocalScreenPoint; + } + } + } + rv = HandleInputTouchMove(); + break; + case MultiTouchInput::MULTITOUCH_END: + // Remove the cache of the touch that ended. + for (size_t i = 0; i < aEvent.mTouches.Length(); i++) { + for (size_t j = 0; j < mTouches.Length(); j++) { + if (aEvent.mTouches[i].mIdentifier == mTouches[j].mIdentifier) { + mTouches.RemoveElementAt(j); + break; + } + } + } + + rv = HandleInputTouchEnd(); + break; + case MultiTouchInput::MULTITOUCH_CANCEL: + mTouches.Clear(); + rv = HandleInputTouchCancel(); + break; + } + + return rv; +} + +int32_t GestureEventListener::GetLastTouchIdentifier() const { + if (mTouches.Length() != 1) { + NS_WARNING( + "GetLastTouchIdentifier() called when last touch event " + "did not have one touch"); + } + return mTouches.IsEmpty() ? -1 : mTouches[0].mIdentifier; +} + +/* static */ +void GestureEventListener::SetLongTapEnabled(bool aLongTapEnabled) { + sLongTapEnabled = aLongTapEnabled; +} + +/* static */ +bool GestureEventListener::IsLongTapEnabled() { return sLongTapEnabled; } + +void GestureEventListener::EnterFirstSingleTouchDown() { + SetState(GESTURE_FIRST_SINGLE_TOUCH_DOWN); + mTouchStartPosition = mLastTouchInput.mTouches[0].mScreenPoint; + mTouchStartOffset = mLastTouchInput.mScreenOffset; + + if (sLongTapEnabled) { + CreateLongTapTimeoutTask(); + } + CreateMaxTapTimeoutTask(); +} + +nsEventStatus GestureEventListener::HandleInputTouchSingleStart() { + switch (mState) { + case GESTURE_NONE: + EnterFirstSingleTouchDown(); + break; + case GESTURE_FIRST_SINGLE_TOUCH_UP: + if (SecondTapIsFar()) { + // If the second tap goes down far away from the first, then bail out + // of any gesture that includes the first tap. + CancelLongTapTimeoutTask(); + CancelMaxTapTimeoutTask(); + mSingleTapSent = Nothing(); + + // But still allow the second tap to participate in a gesture + // (e.g. lead to a single tap, or a double tap if an additional + // tap occurs near the same location). + EnterFirstSingleTouchDown(); + } else { + // Otherwise, reset the touch start position so that, if this turns into + // a one-touch-pinch gesture, it uses the second tap's down position as + // the focus, rather than the first tap's. + mTouchStartPosition = mLastTouchInput.mTouches[0].mScreenPoint; + mTouchStartOffset = mLastTouchInput.mScreenOffset; + SetState(GESTURE_SECOND_SINGLE_TOUCH_DOWN); + } + break; + default: + NS_WARNING("Unhandled state upon single touch start"); + SetState(GESTURE_NONE); + break; + } + + return nsEventStatus_eIgnore; +} + +nsEventStatus GestureEventListener::HandleInputTouchMultiStart() { + nsEventStatus rv = nsEventStatus_eIgnore; + + switch (mState) { + case GESTURE_NONE: + SetState(GESTURE_MULTI_TOUCH_DOWN); + break; + case GESTURE_FIRST_SINGLE_TOUCH_DOWN: + CancelLongTapTimeoutTask(); + CancelMaxTapTimeoutTask(); + SetState(GESTURE_MULTI_TOUCH_DOWN); + // Prevent APZC::OnTouchStart() from handling MULTITOUCH_START event + rv = nsEventStatus_eConsumeNoDefault; + break; + case GESTURE_FIRST_SINGLE_TOUCH_MAX_TAP_DOWN: + CancelLongTapTimeoutTask(); + SetState(GESTURE_MULTI_TOUCH_DOWN); + // Prevent APZC::OnTouchStart() from handling MULTITOUCH_START event + rv = nsEventStatus_eConsumeNoDefault; + break; + case GESTURE_FIRST_SINGLE_TOUCH_UP: + case GESTURE_SECOND_SINGLE_TOUCH_DOWN: + // Cancel wait for double tap + CancelMaxTapTimeoutTask(); + MOZ_ASSERT(mSingleTapSent.isSome()); + if (!mSingleTapSent.value()) { + TriggerSingleTapConfirmedEvent(); + } + mSingleTapSent = Nothing(); + SetState(GESTURE_MULTI_TOUCH_DOWN); + // Prevent APZC::OnTouchStart() from handling MULTITOUCH_START event + rv = nsEventStatus_eConsumeNoDefault; + break; + case GESTURE_LONG_TOUCH_DOWN: + SetState(GESTURE_MULTI_TOUCH_DOWN); + break; + case GESTURE_MULTI_TOUCH_DOWN: + case GESTURE_PINCH: + // Prevent APZC::OnTouchStart() from handling MULTITOUCH_START event + rv = nsEventStatus_eConsumeNoDefault; + break; + default: + NS_WARNING("Unhandled state upon multitouch start"); + SetState(GESTURE_NONE); + break; + } + + return rv; +} + +bool GestureEventListener::MoveDistanceExceeds(ScreenCoord aThreshold) const { + ExternalPoint start = AsyncPanZoomController::ToExternalPoint( + mTouchStartOffset, mTouchStartPosition); + ExternalPoint end = AsyncPanZoomController::ToExternalPoint( + mLastTouchInput.mScreenOffset, mLastTouchInput.mTouches[0].mScreenPoint); + return (start - end).Length() > aThreshold; +} + +bool GestureEventListener::MoveDistanceIsLarge() const { + return MoveDistanceExceeds(mAsyncPanZoomController->GetTouchStartTolerance()); +} + +bool GestureEventListener::SecondTapIsFar() const { + // Allow a little more room here, because the is actually lifting their finger + // off the screen before replacing it, and that tends to have more error than + // wiggling the finger while on the screen. + return MoveDistanceExceeds(mAsyncPanZoomController->GetSecondTapTolerance()); +} + +nsEventStatus GestureEventListener::HandleInputTouchMove() { + nsEventStatus rv = nsEventStatus_eIgnore; + + switch (mState) { + case GESTURE_NONE: + // Ignore this input signal as the corresponding events get handled by + // APZC + break; + + case GESTURE_LONG_TOUCH_DOWN: + if (MoveDistanceIsLarge()) { + // So that we don't fire a long-tap-up if the user moves around after a + // long-tap + SetState(GESTURE_NONE); + } + break; + + case GESTURE_FIRST_SINGLE_TOUCH_DOWN: + case GESTURE_FIRST_SINGLE_TOUCH_MAX_TAP_DOWN: { + // If we move too much, bail out of the tap. + if (MoveDistanceIsLarge()) { + CancelLongTapTimeoutTask(); + CancelMaxTapTimeoutTask(); + mSingleTapSent = Nothing(); + SetState(GESTURE_NONE); + } + break; + } + + // The user has performed a double tap, but not lifted her finger. + case GESTURE_SECOND_SINGLE_TOUCH_DOWN: { + // If touch has moved noticeably (within StaticPrefs::apz_max_tap_time()), + // change state. + if (MoveDistanceIsLarge()) { + CancelLongTapTimeoutTask(); + CancelMaxTapTimeoutTask(); + mSingleTapSent = Nothing(); + if (!StaticPrefs::apz_one_touch_pinch_enabled()) { + // If the one-touch-pinch feature is disabled, bail out of the double- + // tap gesture instead. + SetState(GESTURE_NONE); + break; + } + + SetState(GESTURE_ONE_TOUCH_PINCH); + + ScreenCoord currentSpan = 1.0f; + ScreenPoint currentFocus = mTouchStartPosition; + + // save the position that the one-touch-pinch gesture actually begins + mOneTouchPinchStartPosition = mLastTouchInput.mTouches[0].mScreenPoint; + + PinchGestureInput pinchEvent( + PinchGestureInput::PINCHGESTURE_START, PinchGestureInput::ONE_TOUCH, + mLastTouchInput.mTime, mLastTouchInput.mTimeStamp, + mLastTouchInput.mScreenOffset, currentFocus, currentSpan, + currentSpan, mLastTouchInput.modifiers); + + rv = mAsyncPanZoomController->HandleGestureEvent(pinchEvent); + + mPreviousSpan = currentSpan; + mPreviousFocus = currentFocus; + } + break; + } + + case GESTURE_MULTI_TOUCH_DOWN: { + if (mLastTouchInput.mTouches.Length() < 2) { + NS_WARNING( + "Wrong input: less than 2 moving points in " + "GESTURE_MULTI_TOUCH_DOWN state"); + break; + } + + ScreenCoord currentSpan = GetCurrentSpan(mLastTouchInput); + ScreenPoint currentFocus = GetCurrentFocus(mLastTouchInput); + + mSpanChange += fabsf(currentSpan - mPreviousSpan); + mFocusChange += (currentFocus - mPreviousFocus).Length(); + if (mSpanChange > PINCH_START_THRESHOLD || + mFocusChange > PINCH_START_THRESHOLD) { + SetState(GESTURE_PINCH); + PinchGestureInput pinchEvent( + PinchGestureInput::PINCHGESTURE_START, PinchGestureInput::TOUCH, + mLastTouchInput.mTime, mLastTouchInput.mTimeStamp, + mLastTouchInput.mScreenOffset, currentFocus, currentSpan, + currentSpan, mLastTouchInput.modifiers); + + rv = mAsyncPanZoomController->HandleGestureEvent(pinchEvent); + } else { + // Prevent APZC::OnTouchMove from processing a move event when two + // touches are active + rv = nsEventStatus_eConsumeNoDefault; + } + + mPreviousSpan = currentSpan; + mPreviousFocus = currentFocus; + break; + } + + case GESTURE_PINCH: { + if (mLastTouchInput.mTouches.Length() < 2) { + NS_WARNING( + "Wrong input: less than 2 moving points in GESTURE_PINCH state"); + // Prevent APZC::OnTouchMove() from handling this wrong input + rv = nsEventStatus_eConsumeNoDefault; + break; + } + + ScreenCoord currentSpan = GetCurrentSpan(mLastTouchInput); + + PinchGestureInput pinchEvent( + PinchGestureInput::PINCHGESTURE_SCALE, PinchGestureInput::TOUCH, + mLastTouchInput.mTime, mLastTouchInput.mTimeStamp, + mLastTouchInput.mScreenOffset, GetCurrentFocus(mLastTouchInput), + currentSpan, mPreviousSpan, mLastTouchInput.modifiers); + + rv = mAsyncPanZoomController->HandleGestureEvent(pinchEvent); + mPreviousSpan = currentSpan; + + break; + } + + case GESTURE_ONE_TOUCH_PINCH: { + ScreenCoord currentSpan = GetYSpanFromGestureStartPoint(); + float effectiveSpan = + 1.0f + (fabsf(currentSpan.value) * ONE_TOUCH_PINCH_SPEED); + ScreenPoint currentFocus = mTouchStartPosition; + + // Invert zoom. + if (currentSpan.value < 0) { + effectiveSpan = 1.0f / effectiveSpan; + } + + PinchGestureInput pinchEvent( + PinchGestureInput::PINCHGESTURE_SCALE, PinchGestureInput::ONE_TOUCH, + mLastTouchInput.mTime, mLastTouchInput.mTimeStamp, + mLastTouchInput.mScreenOffset, currentFocus, effectiveSpan, + mPreviousSpan, mLastTouchInput.modifiers); + + rv = mAsyncPanZoomController->HandleGestureEvent(pinchEvent); + mPreviousSpan = effectiveSpan; + + break; + } + + default: + NS_WARNING("Unhandled state upon touch move"); + SetState(GESTURE_NONE); + break; + } + + return rv; +} + +nsEventStatus GestureEventListener::HandleInputTouchEnd() { + // We intentionally do not pass apzc return statuses up since + // it may cause apzc stay in the touching state even after + // gestures are completed (please see Bug 1013378 for reference). + + nsEventStatus rv = nsEventStatus_eIgnore; + + switch (mState) { + case GESTURE_NONE: + // GEL doesn't have a dedicated state for PANNING handled in APZC thus + // ignore. + break; + + case GESTURE_FIRST_SINGLE_TOUCH_DOWN: { + CancelLongTapTimeoutTask(); + CancelMaxTapTimeoutTask(); + nsEventStatus tapupStatus = mAsyncPanZoomController->HandleGestureEvent( + CreateTapEvent(mLastTouchInput, TapGestureInput::TAPGESTURE_UP)); + mSingleTapSent = Some(tapupStatus != nsEventStatus_eIgnore); + SetState(GESTURE_FIRST_SINGLE_TOUCH_UP); + CreateMaxTapTimeoutTask(); + break; + } + + case GESTURE_SECOND_SINGLE_TOUCH_DOWN: { + CancelMaxTapTimeoutTask(); + MOZ_ASSERT(mSingleTapSent.isSome()); + mAsyncPanZoomController->HandleGestureEvent(CreateTapEvent( + mLastTouchInput, mSingleTapSent.value() + ? TapGestureInput::TAPGESTURE_SECOND + : TapGestureInput::TAPGESTURE_DOUBLE)); + mSingleTapSent = Nothing(); + SetState(GESTURE_NONE); + break; + } + + case GESTURE_FIRST_SINGLE_TOUCH_MAX_TAP_DOWN: + CancelLongTapTimeoutTask(); + SetState(GESTURE_NONE); + TriggerSingleTapConfirmedEvent(); + break; + + case GESTURE_LONG_TOUCH_DOWN: { + SetState(GESTURE_NONE); + mAsyncPanZoomController->HandleGestureEvent( + CreateTapEvent(mLastTouchInput, TapGestureInput::TAPGESTURE_LONG_UP)); + break; + } + + case GESTURE_MULTI_TOUCH_DOWN: + if (mTouches.Length() < 2) { + SetState(GESTURE_NONE); + } + break; + + case GESTURE_PINCH: + if (mTouches.Length() < 2) { + SetState(GESTURE_NONE); + PinchGestureInput::PinchGestureType type = + PinchGestureInput::PINCHGESTURE_END; + ScreenPoint point; + if (mTouches.Length() == 1) { + // As user still keeps one finger down the event's focus point should + // contain meaningful data. + type = PinchGestureInput::PINCHGESTURE_FINGERLIFTED; + point = mTouches[0].mScreenPoint; + } + PinchGestureInput pinchEvent( + type, PinchGestureInput::TOUCH, mLastTouchInput.mTime, + mLastTouchInput.mTimeStamp, mLastTouchInput.mScreenOffset, point, + 1.0f, 1.0f, mLastTouchInput.modifiers); + mAsyncPanZoomController->HandleGestureEvent(pinchEvent); + } + + rv = nsEventStatus_eConsumeNoDefault; + + break; + + case GESTURE_ONE_TOUCH_PINCH: { + SetState(GESTURE_NONE); + PinchGestureInput pinchEvent( + PinchGestureInput::PINCHGESTURE_END, PinchGestureInput::ONE_TOUCH, + mLastTouchInput.mTime, mLastTouchInput.mTimeStamp, + mLastTouchInput.mScreenOffset, ScreenPoint(), 1.0f, 1.0f, + mLastTouchInput.modifiers); + mAsyncPanZoomController->HandleGestureEvent(pinchEvent); + + rv = nsEventStatus_eConsumeNoDefault; + + break; + } + + default: + NS_WARNING("Unhandled state upon touch end"); + SetState(GESTURE_NONE); + break; + } + + return rv; +} + +nsEventStatus GestureEventListener::HandleInputTouchCancel() { + mSingleTapSent = Nothing(); + SetState(GESTURE_NONE); + CancelMaxTapTimeoutTask(); + CancelLongTapTimeoutTask(); + return nsEventStatus_eIgnore; +} + +void GestureEventListener::HandleInputTimeoutLongTap() { + GEL_LOG("Running long-tap timeout task in state %d\n", mState); + + mLongTapTimeoutTask = nullptr; + + switch (mState) { + case GESTURE_FIRST_SINGLE_TOUCH_DOWN: + // just in case MaxTapTime > ContextMenuDelay cancel MaxTap timer + // and fall through + CancelMaxTapTimeoutTask(); + [[fallthrough]]; + case GESTURE_FIRST_SINGLE_TOUCH_MAX_TAP_DOWN: { + SetState(GESTURE_LONG_TOUCH_DOWN); + mAsyncPanZoomController->HandleGestureEvent( + CreateTapEvent(mLastTouchInput, TapGestureInput::TAPGESTURE_LONG)); + break; + } + default: + NS_WARNING("Unhandled state upon long tap timeout"); + SetState(GESTURE_NONE); + break; + } +} + +void GestureEventListener::HandleInputTimeoutMaxTap(bool aDuringFastFling) { + GEL_LOG("Running max-tap timeout task in state %d\n", mState); + + mMaxTapTimeoutTask = nullptr; + + if (mState == GESTURE_FIRST_SINGLE_TOUCH_DOWN) { + SetState(GESTURE_FIRST_SINGLE_TOUCH_MAX_TAP_DOWN); + } else if (mState == GESTURE_FIRST_SINGLE_TOUCH_UP || + mState == GESTURE_SECOND_SINGLE_TOUCH_DOWN) { + MOZ_ASSERT(mSingleTapSent.isSome()); + if (!aDuringFastFling && !mSingleTapSent.value()) { + TriggerSingleTapConfirmedEvent(); + } + mSingleTapSent = Nothing(); + SetState(GESTURE_NONE); + } else { + NS_WARNING("Unhandled state upon MAX_TAP timeout"); + SetState(GESTURE_NONE); + } +} + +void GestureEventListener::TriggerSingleTapConfirmedEvent() { + mAsyncPanZoomController->HandleGestureEvent( + CreateTapEvent(mLastTapInput, TapGestureInput::TAPGESTURE_CONFIRMED)); +} + +void GestureEventListener::SetState(GestureState aState) { + mState = aState; + + if (mState == GESTURE_NONE) { + mSpanChange = 0.0f; + mPreviousSpan = 0.0f; + mFocusChange = 0.0f; + } else if (mState == GESTURE_MULTI_TOUCH_DOWN) { + mPreviousSpan = GetCurrentSpan(mLastTouchInput); + mPreviousFocus = GetCurrentFocus(mLastTouchInput); + } +} + +void GestureEventListener::CancelLongTapTimeoutTask() { + if (mState == GESTURE_SECOND_SINGLE_TOUCH_DOWN) { + // being in this state means the task has been canceled already + return; + } + + if (mLongTapTimeoutTask) { + mLongTapTimeoutTask->Cancel(); + mLongTapTimeoutTask = nullptr; + } +} + +void GestureEventListener::CreateLongTapTimeoutTask() { + RefPtr task = NewCancelableRunnableMethod( + "layers::GestureEventListener::HandleInputTimeoutLongTap", this, + &GestureEventListener::HandleInputTimeoutLongTap); + + mLongTapTimeoutTask = task; + + TouchBlockState* block = + mAsyncPanZoomController->GetInputQueue()->GetCurrentTouchBlock(); + MOZ_ASSERT(block); + long alreadyElapsed = + static_cast(block->GetTimeSinceBlockStart().ToMilliseconds()); + long remainingDelay = + StaticPrefs::ui_click_hold_context_menus_delay() - alreadyElapsed; + mAsyncPanZoomController->PostDelayedTask(task.forget(), + std::max(0L, remainingDelay)); +} + +void GestureEventListener::CancelMaxTapTimeoutTask() { + if (mState == GESTURE_FIRST_SINGLE_TOUCH_MAX_TAP_DOWN) { + // being in this state means the timer has just been triggered + return; + } + + if (mMaxTapTimeoutTask) { + mMaxTapTimeoutTask->Cancel(); + mMaxTapTimeoutTask = nullptr; + } +} + +void GestureEventListener::CreateMaxTapTimeoutTask() { + mLastTapInput = mLastTouchInput; + + TouchBlockState* block = + mAsyncPanZoomController->GetInputQueue()->GetCurrentTouchBlock(); + MOZ_ASSERT(block); + RefPtr task = NewCancelableRunnableMethod( + "layers::GestureEventListener::HandleInputTimeoutMaxTap", this, + &GestureEventListener::HandleInputTimeoutMaxTap, + block->IsDuringFastFling()); + + mMaxTapTimeoutTask = task; + + long alreadyElapsed = + static_cast(block->GetTimeSinceBlockStart().ToMilliseconds()); + long remainingDelay = StaticPrefs::apz_max_tap_time() - alreadyElapsed; + mAsyncPanZoomController->PostDelayedTask(task.forget(), + std::max(0L, remainingDelay)); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/src/GestureEventListener.h b/gfx/layers/apz/src/GestureEventListener.h new file mode 100644 index 0000000000..aa51889fdd --- /dev/null +++ b/gfx/layers/apz/src/GestureEventListener.h @@ -0,0 +1,285 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_GestureEventListener_h +#define mozilla_layers_GestureEventListener_h + +#include "InputData.h" // for MultiTouchInput, etc +#include "Units.h" +#include "mozilla/EventForwards.h" // for nsEventStatus +#include "mozilla/RefPtr.h" // for RefPtr +#include "nsISupportsImpl.h" +#include "nsTArray.h" // for nsTArray + +namespace mozilla { + +class CancelableRunnable; + +namespace layers { + +class AsyncPanZoomController; + +/** + * Platform-non-specific, generalized gesture event listener. This class + * intercepts all touches events on their way to AsyncPanZoomController and + * determines whether or not they are part of a gesture. + * + * For example, seeing that two fingers are on the screen means that the user + * wants to do a pinch gesture, so we don't forward the touches along to + * AsyncPanZoomController since it will think that they are just trying to pan + * the screen. Instead, we generate a PinchGestureInput and send that. If the + * touch event is not part of a gesture, we just return nsEventStatus_eIgnore + * and AsyncPanZoomController is expected to handle it. + */ +class GestureEventListener final { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GestureEventListener) + + explicit GestureEventListener( + AsyncPanZoomController* aAsyncPanZoomController); + + // -------------------------------------------------------------------------- + // These methods must only be called on the controller/UI thread. + // + + /** + * General input handler for a touch event. If the touch event is not a part + * of a gesture, then we pass it along to AsyncPanZoomController. Otherwise, + * it gets consumed here and never forwarded along. + */ + nsEventStatus HandleInputEvent(const MultiTouchInput& aEvent); + + /** + * Returns the identifier of the touch in the last touch event processed by + * this GestureEventListener. This should only be called when the last touch + * event contained only one touch. + */ + int32_t GetLastTouchIdentifier() const; + + /** + * Function used to disable long tap gestures. + * + * On slow running tests, drags and touch events can be misinterpreted + * as a long tap. This allows tests to disable long tap gesture detection. + */ + static void SetLongTapEnabled(bool aLongTapEnabled); + static bool IsLongTapEnabled(); + + private: + // Private destructor, to discourage deletion outside of Release(): + ~GestureEventListener(); + + /** + * States of GEL finite-state machine. + */ + enum GestureState { + // This is the initial and final state of any gesture. + // In this state there's no gesture going on, and we don't think we're + // about to enter one. + // Allowed next states: GESTURE_FIRST_SINGLE_TOUCH_DOWN, + // GESTURE_MULTI_TOUCH_DOWN. + GESTURE_NONE, + + // A touch start with a single touch point has just happened. + // After having gotten into this state we start timers for MAX_TAP_TIME and + // StaticPrefs::ui_click_hold_context_menus_delay(). + // Allowed next states: GESTURE_MULTI_TOUCH_DOWN, GESTURE_NONE, + // GESTURE_FIRST_SINGLE_TOUCH_UP, + // GESTURE_LONG_TOUCH_DOWN, + // GESTURE_FIRST_SINGLE_TOUCH_MAX_TAP_DOWN. + GESTURE_FIRST_SINGLE_TOUCH_DOWN, + + // While in GESTURE_FIRST_SINGLE_TOUCH_DOWN state a MAX_TAP_TIME timer got + // triggered. Now we'll trigger either a single tap if a user lifts her + // finger or a long tap if StaticPrefs::ui_click_hold_context_menus_delay() + // happens first. + // Allowed next states: GESTURE_MULTI_TOUCH_DOWN, GESTURE_NONE, + // GESTURE_LONG_TOUCH_DOWN. + GESTURE_FIRST_SINGLE_TOUCH_MAX_TAP_DOWN, + + // A user put her finger down and lifted it up quickly enough. + // After having gotten into this state we clear the timer for MAX_TAP_TIME. + // Allowed next states: GESTURE_SECOND_SINGLE_TOUCH_DOWN, GESTURE_NONE, + // GESTURE_MULTI_TOUCH_DOWN. + GESTURE_FIRST_SINGLE_TOUCH_UP, + + // A user put down her finger again right after a single tap thus the + // gesture can't be a single tap, but rather a double tap. But we're + // still not sure about that until the user lifts her finger again. + // Allowed next states: GESTURE_MULTI_TOUCH_DOWN, GESTURE_ONE_TOUCH_PINCH, + // GESTURE_NONE. + GESTURE_SECOND_SINGLE_TOUCH_DOWN, + + // A long touch has happened, but the user still keeps her finger down. + // We'll trigger a "long tap up" event when the finger is up. + // Allowed next states: GESTURE_NONE, GESTURE_MULTI_TOUCH_DOWN. + GESTURE_LONG_TOUCH_DOWN, + + // We have detected that two or more fingers are on the screen, but there + // hasn't been enough movement yet to make us start actually zooming the + // screen. + // Allowed next states: GESTURE_PINCH, GESTURE_NONE + GESTURE_MULTI_TOUCH_DOWN, + + // There are two or more fingers on the screen, and the user has already + // pinched enough for us to start zooming the screen. + // Allowed next states: GESTURE_NONE + GESTURE_PINCH, + + // The user has double tapped, but not lifted her finger, and moved her + // finger more than PINCH_START_THRESHOLD. + // Allowed next states: GESTURE_NONE. + GESTURE_ONE_TOUCH_PINCH + }; + + /** + * These HandleInput* functions comprise input alphabet of the GEL + * finite-state machine triggering state transitions. + */ + nsEventStatus HandleInputTouchSingleStart(); + nsEventStatus HandleInputTouchMultiStart(); + nsEventStatus HandleInputTouchEnd(); + nsEventStatus HandleInputTouchMove(); + nsEventStatus HandleInputTouchCancel(); + void HandleInputTimeoutLongTap(); + void HandleInputTimeoutMaxTap(bool aDuringFastFling); + + void EnterFirstSingleTouchDown(); + + void TriggerSingleTapConfirmedEvent(); + + bool MoveDistanceExceeds(ScreenCoord aThreshold) const; + bool MoveDistanceIsLarge() const; + bool SecondTapIsFar() const; + + /** + * Returns current vertical span, counting from the where the gesture first + * began (after a brief delay detecting the gesture from first touch). + */ + ScreenCoord GetYSpanFromGestureStartPoint(); + + /** + * Do actual state transition and reset substates. + */ + void SetState(GestureState aState); + + RefPtr mAsyncPanZoomController; + + /** + * Array containing all active touches. When a touch happens it, gets added to + * this array, even if we choose not to handle it. When it ends, we remove it. + * We need to maintain this array in order to detect the end of the + * "multitouch" states because touch start events contain all current touches, + * but touch end events contain only those touches that have gone. + */ + nsTArray mTouches; + + /** + * Current state we're dealing with. + */ + GestureState mState; + + /** + * Total change in span since we detected a pinch gesture. Only used when we + * are in the |GESTURE_WAITING_PINCH| state and need to know how far zoomed + * out we are compared to our original pinch span. Note that this does _not_ + * continue to be updated once we jump into the |GESTURE_PINCH| state. + */ + ScreenCoord mSpanChange; + + /** + * Previous span calculated for the purposes of setting inside a + * PinchGestureInput. + */ + ScreenCoord mPreviousSpan; + + /* Properties similar to mSpanChange and mPreviousSpan, but for the focus */ + ScreenCoord mFocusChange; + ScreenPoint mPreviousFocus; + + /** + * Cached copy of the last touch input. + */ + MultiTouchInput mLastTouchInput; + + /** + * Cached copy of the last tap gesture input. + * In the situation when we have a tap followed by a pinch we lose info + * about tap since we keep only last input and to dispatch it correctly + * we save last tap copy into this variable. + * For more info see bug 947892. + */ + MultiTouchInput mLastTapInput; + + /** + * Position of the last touch that exceeds the GetTouchStartTolerance when + * performing a one-touch-pinch gesture; using the mTouchStartPosition is + * slightly inaccurate because by the time the touch position has passed + * the threshold for the gesture, there is already a span that the zoom + * is calculated from, instead of starting at 1.0 when the threshold gets + * passed. + */ + ScreenPoint mOneTouchPinchStartPosition; + + /** + * Position of the last touch starting. This is only valid during an attempt + * to determine if a touch is a tap. If a touch point moves away from + * mTouchStartPosition to the distance greater than + * AsyncPanZoomController::GetTouchStartTolerance() while in + * GESTURE_FIRST_SINGLE_TOUCH_DOWN, GESTURE_FIRST_SINGLE_TOUCH_MAX_TAP_DOWN + * or GESTURE_SECOND_SINGLE_TOUCH_DOWN then we're certain the gesture is + * not tap. + */ + ScreenPoint mTouchStartPosition; + + /** + * We store the window/GeckoView's display offset as well, so we can + * track the user's physical touch movements in absolute display coordinates. + */ + ExternalPoint mTouchStartOffset; + + /** + * Task used to timeout a long tap. This gets posted to the UI thread such + * that it runs a time when a single tap happens. We cache it so that + * we can cancel it if any other touch event happens. + * + * The task is supposed to be non-null if in GESTURE_FIRST_SINGLE_TOUCH_DOWN + * and GESTURE_FIRST_SINGLE_TOUCH_MAX_TAP_DOWN states. + * + * CancelLongTapTimeoutTask: Cancel the mLongTapTimeoutTask and also set + * it to null. + */ + RefPtr mLongTapTimeoutTask; + void CancelLongTapTimeoutTask(); + void CreateLongTapTimeoutTask(); + + /** + * Task used to timeout a single tap or a double tap. + * + * The task is supposed to be non-null if in GESTURE_FIRST_SINGLE_TOUCH_DOWN, + * GESTURE_FIRST_SINGLE_TOUCH_UP and GESTURE_SECOND_SINGLE_TOUCH_DOWN states. + * + * CancelMaxTapTimeoutTask: Cancel the mMaxTapTimeoutTask and also set + * it to null. + */ + RefPtr mMaxTapTimeoutTask; + void CancelMaxTapTimeoutTask(); + void CreateMaxTapTimeoutTask(); + + /** + * Tracks whether the single-tap event was already sent to content. This is + * needed because it affects how the double-tap gesture, if detected, is + * handled. The value is only valid in states GESTURE_FIRST_SINGLE_TOUCH_UP + * and GESTURE_SECOND_SINGLE_TOUCH_DOWN; to more easily catch violations it is + * stored in a Maybe which is set to Nothing() at all other times. + */ + Maybe mSingleTapSent; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/apz/src/HitTestingTreeNode.cpp b/gfx/layers/apz/src/HitTestingTreeNode.cpp new file mode 100644 index 0000000000..41c4d73a34 --- /dev/null +++ b/gfx/layers/apz/src/HitTestingTreeNode.cpp @@ -0,0 +1,517 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "HitTestingTreeNode.h" +#include + +#include "AsyncPanZoomController.h" // for AsyncPanZoomController +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/gfx/Point.h" // for Point4D +#include "mozilla/layers/APZUtils.h" // for AsyncTransform, CompleteAsyncTransform +#include "mozilla/layers/AsyncDragMetrics.h" // for AsyncDragMetrics +#include "mozilla/ToString.h" // for ToString +#include "nsPrintfCString.h" // for nsPrintfCString +#include "UnitTransforms.h" // for ViewAs + +static mozilla::LazyLogModule sApzMgrLog("apz.manager"); + +namespace mozilla { +namespace layers { + +using gfx::CompositorHitTestFlags; +using gfx::CompositorHitTestInfo; +using gfx::CompositorHitTestInvisibleToHit; +using gfx::CompositorHitTestTouchActionMask; + +HitTestingTreeNode::HitTestingTreeNode(AsyncPanZoomController* aApzc, + bool aIsPrimaryHolder, + LayersId aLayersId) + : mApzc(aApzc), + mIsPrimaryApzcHolder(aIsPrimaryHolder), + mLockCount(0), + mLayersId(aLayersId), + mFixedPosTarget(ScrollableLayerGuid::NULL_SCROLL_ID), + mStickyPosTarget(ScrollableLayerGuid::NULL_SCROLL_ID), + mIsBackfaceHidden(false), + mIsAsyncZoomContainer(false), + mOverride(EventRegionsOverride::NoOverride) { + if (mIsPrimaryApzcHolder) { + MOZ_ASSERT(mApzc); + } + MOZ_ASSERT(!mApzc || mApzc->GetLayersId() == mLayersId); +} + +void HitTestingTreeNode::RecycleWith( + const RecursiveMutexAutoLock& aProofOfTreeLock, + AsyncPanZoomController* aApzc, LayersId aLayersId) { + MOZ_ASSERT(IsRecyclable(aProofOfTreeLock)); + Destroy(); // clear out tree pointers + mApzc = aApzc; + mLayersId = aLayersId; + MOZ_ASSERT(!mApzc || mApzc->GetLayersId() == mLayersId); + // The caller is expected to call appropriate setters (SetHitTestData, + // SetScrollbarData, SetFixedPosData, SetStickyPosData, etc.) to repopulate + // all the data fields before using this node for "real work". Otherwise + // those data fields may contain stale information from the previous use + // of this node object. +} + +HitTestingTreeNode::~HitTestingTreeNode() = default; + +void HitTestingTreeNode::Destroy() { + // This runs on the updater thread, it's not worth passing around extra raw + // pointers just to assert it. + + mPrevSibling = nullptr; + mLastChild = nullptr; + mParent = nullptr; + + if (mApzc) { + if (mIsPrimaryApzcHolder) { + mApzc->Destroy(); + } + mApzc = nullptr; + } +} + +bool HitTestingTreeNode::IsRecyclable( + const RecursiveMutexAutoLock& aProofOfTreeLock) { + return !(IsPrimaryHolder() || (mLockCount > 0)); +} + +void HitTestingTreeNode::SetLastChild(HitTestingTreeNode* aChild) { + mLastChild = aChild; + if (aChild) { + aChild->mParent = this; + + if (aChild->GetApzc()) { + AsyncPanZoomController* parent = GetNearestContainingApzc(); + // We assume that HitTestingTreeNodes with an ancestor/descendant + // relationship cannot both point to the same APZC instance. This + // assertion only covers a subset of cases in which that might occur, + // but it's better than nothing. + MOZ_ASSERT(aChild->GetApzc() != parent); + aChild->SetApzcParent(parent); + } + } +} + +void HitTestingTreeNode::SetScrollbarData( + const Maybe& aScrollbarAnimationId, + const ScrollbarData& aScrollbarData) { + mScrollbarAnimationId = aScrollbarAnimationId; + mScrollbarData = aScrollbarData; +} + +bool HitTestingTreeNode::MatchesScrollDragMetrics( + const AsyncDragMetrics& aDragMetrics) const { + return IsScrollThumbNode() && + mScrollbarData.mDirection == aDragMetrics.mDirection && + mScrollbarData.mTargetViewId == aDragMetrics.mViewId; +} + +bool HitTestingTreeNode::IsScrollThumbNode() const { + return mScrollbarData.mScrollbarLayerType == + layers::ScrollbarLayerType::Thumb; +} + +bool HitTestingTreeNode::IsScrollbarNode() const { + return mScrollbarData.mScrollbarLayerType != layers::ScrollbarLayerType::None; +} + +bool HitTestingTreeNode::IsScrollbarContainerNode() const { + return mScrollbarData.mScrollbarLayerType == + layers::ScrollbarLayerType::Container; +} + +ScrollDirection HitTestingTreeNode::GetScrollbarDirection() const { + MOZ_ASSERT(IsScrollbarNode()); + MOZ_ASSERT(mScrollbarData.mDirection.isSome()); + return *mScrollbarData.mDirection; +} + +ScrollableLayerGuid::ViewID HitTestingTreeNode::GetScrollTargetId() const { + return mScrollbarData.mTargetViewId; +} + +Maybe HitTestingTreeNode::GetScrollbarAnimationId() const { + return mScrollbarAnimationId; +} + +const ScrollbarData& HitTestingTreeNode::GetScrollbarData() const { + return mScrollbarData; +} + +void HitTestingTreeNode::SetFixedPosData( + ScrollableLayerGuid::ViewID aFixedPosTarget, SideBits aFixedPosSides, + const Maybe& aFixedPositionAnimationId) { + mFixedPosTarget = aFixedPosTarget; + mFixedPosSides = aFixedPosSides; + mFixedPositionAnimationId = aFixedPositionAnimationId; +} + +ScrollableLayerGuid::ViewID HitTestingTreeNode::GetFixedPosTarget() const { + return mFixedPosTarget; +} + +SideBits HitTestingTreeNode::GetFixedPosSides() const { return mFixedPosSides; } + +Maybe HitTestingTreeNode::GetFixedPositionAnimationId() const { + return mFixedPositionAnimationId; +} + +void HitTestingTreeNode::SetPrevSibling(HitTestingTreeNode* aSibling) { + mPrevSibling = aSibling; + if (aSibling) { + aSibling->mParent = mParent; + + if (aSibling->GetApzc()) { + AsyncPanZoomController* parent = + mParent ? mParent->GetNearestContainingApzc() : nullptr; + aSibling->SetApzcParent(parent); + } + } +} + +void HitTestingTreeNode::SetStickyPosData( + ScrollableLayerGuid::ViewID aStickyPosTarget, + const LayerRectAbsolute& aScrollRangeOuter, + const LayerRectAbsolute& aScrollRangeInner, + const Maybe& aStickyPositionAnimationId) { + mStickyPosTarget = aStickyPosTarget; + mStickyScrollRangeOuter = aScrollRangeOuter; + mStickyScrollRangeInner = aScrollRangeInner; + mStickyPositionAnimationId = aStickyPositionAnimationId; +} + +ScrollableLayerGuid::ViewID HitTestingTreeNode::GetStickyPosTarget() const { + return mStickyPosTarget; +} + +const LayerRectAbsolute& HitTestingTreeNode::GetStickyScrollRangeOuter() const { + return mStickyScrollRangeOuter; +} + +const LayerRectAbsolute& HitTestingTreeNode::GetStickyScrollRangeInner() const { + return mStickyScrollRangeInner; +} + +Maybe HitTestingTreeNode::GetStickyPositionAnimationId() const { + return mStickyPositionAnimationId; +} + +void HitTestingTreeNode::MakeRoot() { + mParent = nullptr; + + if (GetApzc()) { + SetApzcParent(nullptr); + } +} + +HitTestingTreeNode* HitTestingTreeNode::GetFirstChild() const { + HitTestingTreeNode* child = GetLastChild(); + while (child && child->GetPrevSibling()) { + child = child->GetPrevSibling(); + } + return child; +} + +HitTestingTreeNode* HitTestingTreeNode::GetLastChild() const { + return mLastChild; +} + +HitTestingTreeNode* HitTestingTreeNode::GetPrevSibling() const { + return mPrevSibling; +} + +HitTestingTreeNode* HitTestingTreeNode::GetParent() const { return mParent; } + +bool HitTestingTreeNode::IsAncestorOf(const HitTestingTreeNode* aOther) const { + for (const HitTestingTreeNode* cur = aOther; cur; cur = cur->GetParent()) { + if (cur == this) { + return true; + } + } + return false; +} + +AsyncPanZoomController* HitTestingTreeNode::GetApzc() const { return mApzc; } + +AsyncPanZoomController* HitTestingTreeNode::GetNearestContainingApzc() const { + for (const HitTestingTreeNode* n = this; n; n = n->GetParent()) { + if (n->GetApzc()) { + return n->GetApzc(); + } + } + return nullptr; +} + +bool HitTestingTreeNode::IsPrimaryHolder() const { + return mIsPrimaryApzcHolder; +} + +LayersId HitTestingTreeNode::GetLayersId() const { return mLayersId; } + +void HitTestingTreeNode::SetHitTestData( + const EventRegions& aRegions, const LayerIntRegion& aVisibleRegion, + const LayerIntSize& aRemoteDocumentSize, + const CSSTransformMatrix& aTransform, + const Maybe& aClipRegion, + const EventRegionsOverride& aOverride, bool aIsBackfaceHidden, + bool aIsAsyncZoomContainer) { + mEventRegions = aRegions; + mVisibleRegion = aVisibleRegion; + mRemoteDocumentSize = aRemoteDocumentSize; + mTransform = aTransform; + mClipRegion = aClipRegion; + mOverride = aOverride; + mIsBackfaceHidden = aIsBackfaceHidden; + mIsAsyncZoomContainer = aIsAsyncZoomContainer; +} + +bool HitTestingTreeNode::IsOutsideClip(const ParentLayerPoint& aPoint) const { + // test against clip rect in ParentLayer coordinate space + return (mClipRegion.isSome() && !mClipRegion->Contains(aPoint.x, aPoint.y)); +} + +Maybe HitTestingTreeNode::Untransform( + const ParentLayerPoint& aPoint, + const LayerToParentLayerMatrix4x4& aTransform) const { + Maybe inverse = aTransform.MaybeInverse(); + if (inverse) { + return UntransformBy(inverse.ref(), aPoint); + } + return Nothing(); +} + +CompositorHitTestInfo HitTestingTreeNode::HitTest( + const LayerPoint& aPoint) const { + CompositorHitTestInfo result = CompositorHitTestInvisibleToHit; + + if (mOverride & EventRegionsOverride::ForceEmptyHitRegion) { + return result; + } + + auto point = LayerIntPoint::Round(aPoint); + + // If the layer's backface is showing and it's hidden, don't hit it. + // This matches the behavior of main-thread hit testing in + // nsDisplayTransform::HitTest(). + if (mIsBackfaceHidden) { + return result; + } + + // test against event regions in Layer coordinate space + if (!mEventRegions.mHitRegion.Contains(point.x, point.y)) { + return result; + } + + result = CompositorHitTestFlags::eVisibleToHitTest; + + if (mOverride & EventRegionsOverride::ForceDispatchToContent) { + result += CompositorHitTestFlags::eApzAwareListeners; + } + if (mEventRegions.mDispatchToContentHitRegion.Contains(point.x, point.y)) { + // Technically this might be some combination of eInactiveScrollframe, + // eApzAwareListeners, and eIrregularArea, because the round-trip through + // mEventRegions is lossy. We just convert it back to eIrregularArea + // because that's the most conservative option (i.e. eIrregularArea makes + // APZ rely on the main thread for everything). + result += CompositorHitTestFlags::eIrregularArea; + if (mEventRegions.mDTCRequiresTargetConfirmation) { + result += CompositorHitTestFlags::eRequiresTargetConfirmation; + } + } else if (StaticPrefs::layout_css_touch_action_enabled()) { + if (mEventRegions.mNoActionRegion.Contains(point.x, point.y)) { + // set all the touch-action flags as disabled + result += CompositorHitTestTouchActionMask; + } else { + bool panX = mEventRegions.mHorizontalPanRegion.Contains(point.x, point.y); + bool panY = mEventRegions.mVerticalPanRegion.Contains(point.x, point.y); + if (panX && panY) { + // touch-action: pan-x pan-y + result += CompositorHitTestFlags::eTouchActionDoubleTapZoomDisabled; + result += CompositorHitTestFlags::eTouchActionPinchZoomDisabled; + } else if (panX) { + // touch-action: pan-x + result += CompositorHitTestFlags::eTouchActionPanYDisabled; + result += CompositorHitTestFlags::eTouchActionPinchZoomDisabled; + result += CompositorHitTestFlags::eTouchActionDoubleTapZoomDisabled; + } else if (panY) { + // touch-action: pan-y + result += CompositorHitTestFlags::eTouchActionPanXDisabled; + result += CompositorHitTestFlags::eTouchActionPinchZoomDisabled; + result += CompositorHitTestFlags::eTouchActionDoubleTapZoomDisabled; + } // else we're in the touch-action: auto or touch-action: manipulation + // cases and we'll allow all actions. Technically we shouldn't allow + // double-tap zooming in the manipulation case but apparently this has + // been broken since the dawn of time. + } + } + + // The scrollbar flags are set at the call site in GetAPZCAtPoint, because + // those require walking up the tree to see if we are contained inside a + // scrollbar or scrollthumb, and we do that there anyway to get the scrollbar + // node. + + return result; +} + +EventRegionsOverride HitTestingTreeNode::GetEventRegionsOverride() const { + return mOverride; +} + +const CSSTransformMatrix& HitTestingTreeNode::GetTransform() const { + return mTransform; +} + +LayerToScreenMatrix4x4 HitTestingTreeNode::GetTransformToGecko() const { + if (mParent) { + LayerToParentLayerMatrix4x4 thisToParent = + mTransform * AsyncTransformMatrix(); + if (mApzc) { + thisToParent = + thisToParent * ViewAs( + mApzc->GetTransformToLastDispatchedPaint()); + } + ParentLayerToScreenMatrix4x4 parentToRoot = + ViewAs( + mParent->GetTransformToGecko(), + PixelCastJustification::MovingDownToChildren); + return thisToParent * parentToRoot; + } + + return ViewAs( + mTransform * AsyncTransformMatrix(), + PixelCastJustification::ScreenIsParentLayerForRoot); +} + +const LayerIntRegion& HitTestingTreeNode::GetVisibleRegion() const { + return mVisibleRegion; +} + +ScreenRect HitTestingTreeNode::GetRemoteDocumentScreenRect() const { + ScreenRect result = TransformBy( + GetTransformToGecko(), + IntRectToRect(LayerIntRect(LayerIntPoint(), mRemoteDocumentSize))); + + for (const HitTestingTreeNode* node = this; node; node = node->GetParent()) { + if (!node->GetApzc()) { + continue; + } + + ParentLayerRect compositionBounds = node->GetApzc()->GetCompositionBounds(); + if (compositionBounds.IsEmpty()) { + return ScreenRect(); + } + + ScreenRect scrollPortOnScreenCoordinate = TransformBy( + node->GetParent() ? node->GetParent()->GetTransformToGecko() + : LayerToScreenMatrix4x4(), + ViewAs(compositionBounds, + PixelCastJustification::MovingDownToChildren)); + if (scrollPortOnScreenCoordinate.IsEmpty()) { + return ScreenRect(); + } + + result = result.Intersect(scrollPortOnScreenCoordinate); + if (result.IsEmpty()) { + return ScreenRect(); + } + } + return result; +} + +bool HitTestingTreeNode::IsAsyncZoomContainer() const { + return mIsAsyncZoomContainer; +} + +void HitTestingTreeNode::Dump(const char* aPrefix) const { + MOZ_LOG( + sApzMgrLog, LogLevel::Debug, + ("%sHitTestingTreeNode (%p) APZC (%p) g=(%s) %s%s%sr=(%s) t=(%s) " + "c=(%s)%s%s\n", + aPrefix, this, mApzc.get(), + mApzc ? ToString(mApzc->GetGuid()).c_str() + : nsPrintfCString("l=0x%" PRIx64, uint64_t(mLayersId)).get(), + (mOverride & EventRegionsOverride::ForceDispatchToContent) ? "fdtc " + : "", + (mOverride & EventRegionsOverride::ForceEmptyHitRegion) ? "fehr " : "", + (mFixedPosTarget != ScrollableLayerGuid::NULL_SCROLL_ID) + ? nsPrintfCString("fixed=%" PRIu64 " ", mFixedPosTarget).get() + : "", + ToString(mEventRegions).c_str(), ToString(mTransform).c_str(), + mClipRegion ? ToString(mClipRegion.ref()).c_str() : "none", + mScrollbarData.mDirection.isSome() ? " scrollbar" : "", + IsScrollThumbNode() ? " scrollthumb" : "")); + + if (!mLastChild) { + return; + } + + // Dump the children in order from first child to last child + std::stack children; + for (HitTestingTreeNode* child = mLastChild.get(); child; + child = child->mPrevSibling) { + children.push(child); + } + nsPrintfCString childPrefix("%s ", aPrefix); + while (!children.empty()) { + children.top()->Dump(childPrefix.get()); + children.pop(); + } +} + +void HitTestingTreeNode::SetApzcParent(AsyncPanZoomController* aParent) { + // precondition: GetApzc() is non-null + MOZ_ASSERT(GetApzc() != nullptr); + if (IsPrimaryHolder()) { + GetApzc()->SetParent(aParent); + } else { + MOZ_ASSERT(GetApzc()->GetParent() == aParent); + } +} + +void HitTestingTreeNode::Lock(const RecursiveMutexAutoLock& aProofOfTreeLock) { + mLockCount++; +} + +void HitTestingTreeNode::Unlock( + const RecursiveMutexAutoLock& aProofOfTreeLock) { + MOZ_ASSERT(mLockCount > 0); + mLockCount--; +} + +HitTestingTreeNodeAutoLock::HitTestingTreeNodeAutoLock() + : mTreeMutex(nullptr) {} + +HitTestingTreeNodeAutoLock::~HitTestingTreeNodeAutoLock() { Clear(); } + +void HitTestingTreeNodeAutoLock::Initialize( + const RecursiveMutexAutoLock& aProofOfTreeLock, + already_AddRefed aNode, RecursiveMutex& aTreeMutex) { + MOZ_ASSERT(!mNode); + + mNode = aNode; + mTreeMutex = &aTreeMutex; + + mNode->Lock(aProofOfTreeLock); +} + +void HitTestingTreeNodeAutoLock::Clear() { + if (!mNode) { + return; + } + MOZ_ASSERT(mTreeMutex); + + { // scope lock + RecursiveMutexAutoLock lock(*mTreeMutex); + mNode->Unlock(lock); + } + mNode = nullptr; + mTreeMutex = nullptr; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/src/HitTestingTreeNode.h b/gfx/layers/apz/src/HitTestingTreeNode.h new file mode 100644 index 0000000000..17233ff755 --- /dev/null +++ b/gfx/layers/apz/src/HitTestingTreeNode.h @@ -0,0 +1,302 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_HitTestingTreeNode_h +#define mozilla_layers_HitTestingTreeNode_h + +#include "Layers.h" +#include "mozilla/gfx/CompositorHitTestInfo.h" +#include "mozilla/gfx/Matrix.h" // for Matrix4x4 +#include "mozilla/layers/LayersTypes.h" // for EventRegions +#include "mozilla/layers/ScrollableLayerGuid.h" // for ScrollableLayerGuid +#include "mozilla/Maybe.h" // for Maybe +#include "mozilla/RefPtr.h" // for nsRefPtr + +namespace mozilla { +namespace layers { + +class AsyncDragMetrics; +class AsyncPanZoomController; + +/** + * This class represents a node in a tree that is used by the APZCTreeManager + * to do hit testing. The tree is roughly a copy of the layer tree, but will + * contain multiple nodes in cases where the layer has multiple FrameMetrics. + * In other words, the structure of this tree should be identical to the + * LayerMetrics tree (see documentation in LayerMetricsWrapper.h). + * + * Not all HitTestingTreeNode instances will have an APZC associated with them; + * only HitTestingTreeNodes that correspond to layers with scrollable metrics + * have APZCs. + * Multiple HitTestingTreeNode instances may share the same underlying APZC + * instance if the layers they represent share the same scrollable metrics (i.e. + * are part of the same animated geometry root). If this happens, exactly one of + * the HitTestingTreeNode instances will be designated as the "primary holder" + * of the APZC. When this primary holder is destroyed, it will destroy the APZC + * along with it; in contrast, destroying non-primary-holder nodes will not + * destroy the APZC. + * Code should not make assumptions about which of the nodes will be the + * primary holder, only that that there will be exactly one for each APZC in + * the tree. + * + * The reason this tree exists at all is so that we can do hit-testing on the + * thread that we receive input on (referred to the as the controller thread in + * APZ terminology), which may be different from the compositor thread. + * Accessing the compositor layer tree can only be done on the compositor + * thread, and so it is simpler to make a copy of the hit-testing related + * properties into a separate tree. + * + * The tree pointers on the node (mLastChild, etc.) can only be manipulated + * while holding the APZ tree lock. Any code that wishes to use a + * HitTestingTreeNode outside of holding the tree lock should do so by using + * the HitTestingTreeNodeAutoLock wrapper, which prevents the node from + * being recycled (and also holds a RefPtr to the node to prevent it from + * getting freed). + */ +class HitTestingTreeNode { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(HitTestingTreeNode); + + private: + ~HitTestingTreeNode(); + + public: + HitTestingTreeNode(AsyncPanZoomController* aApzc, bool aIsPrimaryHolder, + LayersId aLayersId); + void RecycleWith(const RecursiveMutexAutoLock& aProofOfTreeLock, + AsyncPanZoomController* aApzc, LayersId aLayersId); + // Clears the tree pointers on the node, thereby breaking RefPtr cycles. This + // can trigger free'ing of this and other HitTestingTreeNode instances. + void Destroy(); + + // Returns true if and only if the node is available for recycling as part + // of a hit-testing tree update. Note that this node can have Destroy() called + // on it whether or not it is recyclable. + bool IsRecyclable(const RecursiveMutexAutoLock& aProofOfTreeLock); + + /* Tree construction methods */ + + void SetLastChild(HitTestingTreeNode* aChild); + void SetPrevSibling(HitTestingTreeNode* aSibling); + void MakeRoot(); + + /* Tree walking methods. GetFirstChild is O(n) in the number of children. The + * other tree walking methods are all O(1). */ + + HitTestingTreeNode* GetFirstChild() const; + HitTestingTreeNode* GetLastChild() const; + HitTestingTreeNode* GetPrevSibling() const; + HitTestingTreeNode* GetParent() const; + + bool IsAncestorOf(const HitTestingTreeNode* aOther) const; + + /* APZC related methods */ + + AsyncPanZoomController* GetApzc() const; + AsyncPanZoomController* GetNearestContainingApzc() const; + bool IsPrimaryHolder() const; + LayersId GetLayersId() const; + + /* Hit test related methods */ + + void SetHitTestData(const EventRegions& aRegions, + const LayerIntRegion& aVisibleRegion, + const LayerIntSize& aRemoteDocumentSize, + const CSSTransformMatrix& aTransform, + const Maybe& aClipRegion, + const EventRegionsOverride& aOverride, + bool aIsBackfaceHidden, bool aIsAsyncZoomContainer); + bool IsOutsideClip(const ParentLayerPoint& aPoint) const; + + /* Scrollbar info */ + + void SetScrollbarData(const Maybe& aScrollbarAnimationId, + const ScrollbarData& aScrollbarData); + bool MatchesScrollDragMetrics(const AsyncDragMetrics& aDragMetrics) const; + bool IsScrollbarNode() const; // Scroll thumb or scrollbar container layer. + bool IsScrollbarContainerNode() const; // Scrollbar container layer. + // This can only be called if IsScrollbarNode() is true + ScrollDirection GetScrollbarDirection() const; + bool IsScrollThumbNode() const; // Scroll thumb container layer. + ScrollableLayerGuid::ViewID GetScrollTargetId() const; + const ScrollbarData& GetScrollbarData() const; + Maybe GetScrollbarAnimationId() const; + + /* Fixed pos info */ + + void SetFixedPosData(ScrollableLayerGuid::ViewID aFixedPosTarget, + SideBits aFixedPosSides, + const Maybe& aFixedPositionAnimationId); + ScrollableLayerGuid::ViewID GetFixedPosTarget() const; + SideBits GetFixedPosSides() const; + Maybe GetFixedPositionAnimationId() const; + + /* Sticky pos info */ + void SetStickyPosData(ScrollableLayerGuid::ViewID aStickyPosTarget, + const LayerRectAbsolute& aScrollRangeOuter, + const LayerRectAbsolute& aScrollRangeInner, + const Maybe& aStickyPositionAnimationId); + ScrollableLayerGuid::ViewID GetStickyPosTarget() const; + const LayerRectAbsolute& GetStickyScrollRangeOuter() const; + const LayerRectAbsolute& GetStickyScrollRangeInner() const; + Maybe GetStickyPositionAnimationId() const; + + /* Convert |aPoint| into the LayerPixel space for the layer corresponding to + * this node. |aTransform| is the complete (content + async) transform for + * this node. */ + Maybe Untransform( + const ParentLayerPoint& aPoint, + const LayerToParentLayerMatrix4x4& aTransform) const; + /* Assuming aPoint is inside the clip region for this node, check which of the + * event region spaces it falls inside. */ + gfx::CompositorHitTestInfo HitTest(const LayerPoint& aPoint) const; + /* Returns the mOverride flag. */ + EventRegionsOverride GetEventRegionsOverride() const; + const CSSTransformMatrix& GetTransform() const; + /* This is similar to APZCTreeManager::GetApzcToGeckoTransform but without + * the async bits. It's used on the main-thread for transforming coordinates + * across a BrowserParent/BrowserChild interface.*/ + LayerToScreenMatrix4x4 GetTransformToGecko() const; + const LayerIntRegion& GetVisibleRegion() const; + + /* Returns the screen coordinate rectangle of remote iframe corresponding to + * this node. The rectangle is the result of clipped by ancestor async + * scrolling. */ + ScreenRect GetRemoteDocumentScreenRect() const; + + bool IsAsyncZoomContainer() const; + + /* Debug helpers */ + void Dump(const char* aPrefix = "") const; + + private: + friend class HitTestingTreeNodeAutoLock; + // Functions that are private but called from HitTestingTreeNodeAutoLock + void Lock(const RecursiveMutexAutoLock& aProofOfTreeLock); + void Unlock(const RecursiveMutexAutoLock& aProofOfTreeLock); + + void SetApzcParent(AsyncPanZoomController* aApzc); + + RefPtr mLastChild; + RefPtr mPrevSibling; + RefPtr mParent; + + RefPtr mApzc; + bool mIsPrimaryApzcHolder; + int mLockCount; + + LayersId mLayersId; + + // This is only set if WebRender is enabled, and only for HTTNs + // where IsScrollThumbNode() returns true. It holds the animation id that we + // use to move the thumb node to reflect async scrolling. + Maybe mScrollbarAnimationId; + + // This is set for scrollbar Container and Thumb layers. + ScrollbarData mScrollbarData; + + // This is only set if WebRender is enabled. It holds the animation id that + // we use to adjust fixed position content for the toolbar. + Maybe mFixedPositionAnimationId; + + ScrollableLayerGuid::ViewID mFixedPosTarget; + SideBits mFixedPosSides; + + ScrollableLayerGuid::ViewID mStickyPosTarget; + LayerRectAbsolute mStickyScrollRangeOuter; + LayerRectAbsolute mStickyScrollRangeInner; + // This is only set if WebRender is enabled. It holds the animation id that + // we use to adjust sticky position content for the toolbar. + Maybe mStickyPositionAnimationId; + + /* Let {L,M} be the {layer, scrollable metrics} pair that this node + * corresponds to in the layer tree. mEventRegions contains the event regions + * from L, in the case where event-regions are enabled. If event-regions are + * disabled, it will contain the visible region of L, which we use as an + * approximation to the hit region for the purposes of obscuring other layers. + * This value is in L's LayerPixels. + */ + EventRegions mEventRegions; + + LayerIntRegion mVisibleRegion; + + /* The size of remote iframe on the corresponding layer coordinate. + * It's empty if this node is not for remote iframe. */ + LayerIntSize mRemoteDocumentSize; + + /* This is the transform from layer L. This does NOT include any async + * transforms. */ + CSSTransformMatrix mTransform; + + /* Whether layer L is backface-visibility:hidden, and its backface is + * currently visible. It's true that the latter depends on the layer's + * shadow transform, but the sorts of changes APZ makes to the shadow + * transform shouldn't change the backface from hidden to visible or + * vice versa, so it's sufficient to record this at hit test tree + * building time. */ + bool mIsBackfaceHidden; + + /* Whether layer L is the async zoom container layer. */ + bool mIsAsyncZoomContainer; + + /* This is clip rect for L that we wish to use for hit-testing purposes. Note + * that this may not be exactly the same as the clip rect on layer L because + * of the touch-sensitive region provided by the GeckoContentController, or + * because we may use the composition bounds of the layer if the clip is not + * present. This value is in L's ParentLayerPixels. */ + Maybe mClipRegion; + + /* Indicates whether or not the event regions on this node need to be + * overridden in a certain way. */ + EventRegionsOverride mOverride; +}; + +/** + * A class that allows safe usage of a HitTestingTreeNode outside of the APZ + * tree lock. In general, this class should be Initialize()'d inside the tree + * lock (enforced by the proof-of-lock to Initialize), and then can be returned + * to a scope outside the tree lock and used safely. Upon destruction or + * Clear() being called, it unlocks the underlying node at which point it can + * be recycled or freed. + */ +class HitTestingTreeNodeAutoLock final { + public: + HitTestingTreeNodeAutoLock(); + ~HitTestingTreeNodeAutoLock(); + // Make it move-only. Note that the default implementations of the move + // constructor and assignment operator are correct: they'll call the + // move constructor of mNode, which will null out mNode on the moved-from + // object, and Clear() will early-exit when the moved-from object's + // destructor is called. + HitTestingTreeNodeAutoLock(HitTestingTreeNodeAutoLock&&) = default; + HitTestingTreeNodeAutoLock& operator=(HitTestingTreeNodeAutoLock&&) = default; + + void Initialize(const RecursiveMutexAutoLock& aProofOfTreeLock, + already_AddRefed aNode, + RecursiveMutex& aTreeMutex); + void Clear(); + + // Convenience operators to simplify the using code. + explicit operator bool() const { return !!mNode; } + bool operator!() const { return !mNode; } + HitTestingTreeNode* operator->() const { return mNode.get(); } + + // Allow getting back a raw pointer to the node, but only inside the scope + // of the tree lock. The caller is responsible for ensuring that they do not + // use the raw pointer outside that scope. + HitTestingTreeNode* Get( + mozilla::RecursiveMutexAutoLock& aProofOfTreeLock) const { + return mNode.get(); + } + + private: + RefPtr mNode; + RecursiveMutex* mTreeMutex; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_HitTestingTreeNode_h diff --git a/gfx/layers/apz/src/InputBlockState.cpp b/gfx/layers/apz/src/InputBlockState.cpp new file mode 100644 index 0000000000..20f29d7d99 --- /dev/null +++ b/gfx/layers/apz/src/InputBlockState.cpp @@ -0,0 +1,837 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "InputBlockState.h" + +#include "APZUtils.h" +#include "AsyncPanZoomController.h" // for AsyncPanZoomController +#include "ScrollAnimationPhysics.h" // for kScrollSeriesTimeoutMs + +#include "mozilla/MouseEvents.h" +#include "mozilla/StaticPrefs_apz.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/StaticPrefs_mousewheel.h" +#include "mozilla/StaticPrefs_test.h" +#include "mozilla/Telemetry.h" // for Telemetry +#include "mozilla/ToString.h" +#include "mozilla/layers/IAPZCTreeManager.h" // for AllowedTouchBehavior +#include "OverscrollHandoffState.h" +#include "QueuedInput.h" + +static mozilla::LazyLogModule sApzIbsLog("apz.inputstate"); +#define TBS_LOG(...) MOZ_LOG(sApzIbsLog, LogLevel::Debug, (__VA_ARGS__)) + +namespace mozilla { +namespace layers { + +static uint64_t sBlockCounter = InputBlockState::NO_BLOCK_ID + 1; + +InputBlockState::InputBlockState( + const RefPtr& aTargetApzc, + TargetConfirmationFlags aFlags) + : mTargetApzc(aTargetApzc), + mRequiresTargetConfirmation(aFlags.mRequiresTargetConfirmation), + mBlockId(sBlockCounter++), + mTransformToApzc(aTargetApzc->GetTransformToThis()) { + // We should never be constructed with a nullptr target. + MOZ_ASSERT(mTargetApzc); + mOverscrollHandoffChain = mTargetApzc->BuildOverscrollHandoffChain(); + // If a new block starts on a scrollthumb and we have APZ scrollbar + // dragging enabled, defer confirmation until we get the drag metrics + // for the thumb. + bool startingDrag = StaticPrefs::apz_drag_enabled() && aFlags.mHitScrollThumb; + mTargetConfirmed = aFlags.mTargetConfirmed && !startingDrag + ? TargetConfirmationState::eConfirmed + : TargetConfirmationState::eUnconfirmed; +} + +bool InputBlockState::SetConfirmedTargetApzc( + const RefPtr& aTargetApzc, + TargetConfirmationState aState, InputData* aFirstInput, + bool aForScrollbarDrag) { + MOZ_ASSERT(aState == TargetConfirmationState::eConfirmed || + aState == TargetConfirmationState::eTimedOut); + + if (mTargetConfirmed == TargetConfirmationState::eTimedOut && + aState == TargetConfirmationState::eConfirmed) { + // The main thread finally responded. We had already timed out the + // confirmation, but we want to update the state internally so that we + // can record the time for telemetry purposes. + mTargetConfirmed = TargetConfirmationState::eTimedOutAndMainThreadResponded; + } + // Sometimes, bugs in compositor hit testing can lead to APZ confirming + // a different target than the main thread. If this happens for a drag + // block created for a scrollbar drag, the consequences can be fairly + // user-unfriendly, such as the scrollbar not being draggable at all, + // or it scrolling the contents of the wrong scrollframe. In debug + // builds, we assert in this situation, so that the + // underlying compositor hit testing bug can be fixed. In release builds, + // however, we just silently accept the main thread's confirmed target, + // which will produce the expected behaviour (apart from drag events + // received so far being dropped). + if (AsDragBlock() && aForScrollbarDrag && + mTargetConfirmed == TargetConfirmationState::eConfirmed && + aState == TargetConfirmationState::eConfirmed && mTargetApzc && + aTargetApzc && mTargetApzc->GetGuid() != aTargetApzc->GetGuid()) { + MOZ_ASSERT(false, + "APZ and main thread confirmed scrollbar drag block with " + "different targets"); + UpdateTargetApzc(aTargetApzc); + return true; + } + + if (mTargetConfirmed != TargetConfirmationState::eUnconfirmed) { + return false; + } + mTargetConfirmed = aState; + + TBS_LOG("%p got confirmed target APZC %p\n", this, mTargetApzc.get()); + if (mTargetApzc == aTargetApzc) { + // The confirmed target is the same as the tentative one, so we're done. + return true; + } + + TBS_LOG("%p replacing unconfirmed target %p with real target %p\n", this, + mTargetApzc.get(), aTargetApzc.get()); + + UpdateTargetApzc(aTargetApzc); + return true; +} + +void InputBlockState::UpdateTargetApzc( + const RefPtr& aTargetApzc) { + // note that aTargetApzc MAY be null here. + mTargetApzc = aTargetApzc; + mTransformToApzc = aTargetApzc ? aTargetApzc->GetTransformToThis() + : ScreenToParentLayerMatrix4x4(); + mOverscrollHandoffChain = + (mTargetApzc ? mTargetApzc->BuildOverscrollHandoffChain() : nullptr); +} + +const RefPtr& InputBlockState::GetTargetApzc() const { + return mTargetApzc; +} + +const RefPtr& +InputBlockState::GetOverscrollHandoffChain() const { + return mOverscrollHandoffChain; +} + +uint64_t InputBlockState::GetBlockId() const { return mBlockId; } + +bool InputBlockState::IsTargetConfirmed() const { + return mTargetConfirmed != TargetConfirmationState::eUnconfirmed; +} + +bool InputBlockState::HasReceivedRealConfirmedTarget() const { + return mTargetConfirmed == TargetConfirmationState::eConfirmed || + mTargetConfirmed == + TargetConfirmationState::eTimedOutAndMainThreadResponded; +} + +bool InputBlockState::ShouldDropEvents() const { + return mRequiresTargetConfirmation && + (mTargetConfirmed != TargetConfirmationState::eConfirmed); +} + +bool InputBlockState::IsDownchainOf(AsyncPanZoomController* aA, + AsyncPanZoomController* aB) const { + if (aA == aB) { + return true; + } + + bool seenA = false; + for (size_t i = 0; i < mOverscrollHandoffChain->Length(); ++i) { + AsyncPanZoomController* apzc = mOverscrollHandoffChain->GetApzcAtIndex(i); + if (apzc == aB) { + return seenA; + } + if (apzc == aA) { + seenA = true; + } + } + return false; +} + +void InputBlockState::SetScrolledApzc(AsyncPanZoomController* aApzc) { + // An input block should only have one scrolled APZC. + MOZ_ASSERT(!mScrolledApzc || (StaticPrefs::apz_allow_immediate_handoff() + ? IsDownchainOf(mScrolledApzc, aApzc) + : mScrolledApzc == aApzc)); + + mScrolledApzc = aApzc; +} + +AsyncPanZoomController* InputBlockState::GetScrolledApzc() const { + return mScrolledApzc; +} + +bool InputBlockState::IsDownchainOfScrolledApzc( + AsyncPanZoomController* aApzc) const { + MOZ_ASSERT(aApzc && mScrolledApzc); + + return IsDownchainOf(mScrolledApzc, aApzc); +} + +void InputBlockState::DispatchEvent(const InputData& aEvent) const { + GetTargetApzc()->HandleInputEvent(aEvent, mTransformToApzc); +} + +CancelableBlockState::CancelableBlockState( + const RefPtr& aTargetApzc, + TargetConfirmationFlags aFlags) + : InputBlockState(aTargetApzc, aFlags), + mPreventDefault(false), + mContentResponded(false), + mContentResponseTimerExpired(false) {} + +bool CancelableBlockState::SetContentResponse(bool aPreventDefault) { + if (mContentResponded) { + return false; + } + TBS_LOG("%p got content response %d with timer expired %d\n", this, + aPreventDefault, mContentResponseTimerExpired); + mPreventDefault = aPreventDefault; + mContentResponded = true; + return true; +} + +bool CancelableBlockState::TimeoutContentResponse() { + if (mContentResponseTimerExpired) { + return false; + } + TBS_LOG("%p got content timer expired with response received %d\n", this, + mContentResponded); + if (!mContentResponded) { + mPreventDefault = false; + } + mContentResponseTimerExpired = true; + return true; +} + +bool CancelableBlockState::IsContentResponseTimerExpired() const { + return mContentResponseTimerExpired; +} + +bool CancelableBlockState::IsDefaultPrevented() const { + MOZ_ASSERT(mContentResponded || mContentResponseTimerExpired); + return mPreventDefault; +} + +bool CancelableBlockState::HasReceivedAllContentNotifications() const { + return HasReceivedRealConfirmedTarget() && mContentResponded; +} + +bool CancelableBlockState::IsReadyForHandling() const { + if (!IsTargetConfirmed()) { + return false; + } + return mContentResponded || mContentResponseTimerExpired; +} + +bool CancelableBlockState::ShouldDropEvents() const { + return InputBlockState::ShouldDropEvents() || IsDefaultPrevented(); +} + +DragBlockState::DragBlockState( + const RefPtr& aTargetApzc, + TargetConfirmationFlags aFlags, const MouseInput& aInitialEvent) + : CancelableBlockState(aTargetApzc, aFlags), mReceivedMouseUp(false) {} + +bool DragBlockState::HasReceivedMouseUp() { return mReceivedMouseUp; } + +void DragBlockState::MarkMouseUpReceived() { mReceivedMouseUp = true; } + +void DragBlockState::SetInitialThumbPos(CSSCoord aThumbPos) { + mInitialThumbPos = aThumbPos; +} + +void DragBlockState::SetDragMetrics(const AsyncDragMetrics& aDragMetrics) { + mDragMetrics = aDragMetrics; +} + +void DragBlockState::DispatchEvent(const InputData& aEvent) const { + MouseInput mouseInput = aEvent.AsMouseInput(); + if (!mouseInput.TransformToLocal(mTransformToApzc)) { + return; + } + + GetTargetApzc()->HandleDragEvent(mouseInput, mDragMetrics, mInitialThumbPos); +} + +bool DragBlockState::MustStayActive() { return !mReceivedMouseUp; } + +const char* DragBlockState::Type() { return "drag"; } +// This is used to track the current wheel transaction. +static uint64_t sLastWheelBlockId = InputBlockState::NO_BLOCK_ID; + +WheelBlockState::WheelBlockState( + const RefPtr& aTargetApzc, + TargetConfirmationFlags aFlags, const ScrollWheelInput& aInitialEvent) + : CancelableBlockState(aTargetApzc, aFlags), + mScrollSeriesCounter(0), + mTransactionEnded(false) { + sLastWheelBlockId = GetBlockId(); + + if (aFlags.mTargetConfirmed) { + // Find the nearest APZC in the overscroll handoff chain that is scrollable. + // If we get a content confirmation later that the apzc is different, then + // content should have found a scrollable apzc, so we don't need to handle + // that case. + RefPtr apzc = + mOverscrollHandoffChain->FindFirstScrollable(aInitialEvent, + &mAllowedScrollDirections); + + // If nothing is scrollable, we don't consider this block as starting a + // transaction. + if (!apzc) { + EndTransaction(); + return; + } + + if (apzc != GetTargetApzc()) { + UpdateTargetApzc(apzc); + } + } +} + +bool WheelBlockState::SetContentResponse(bool aPreventDefault) { + if (aPreventDefault) { + EndTransaction(); + } + return CancelableBlockState::SetContentResponse(aPreventDefault); +} + +bool WheelBlockState::SetConfirmedTargetApzc( + const RefPtr& aTargetApzc, + TargetConfirmationState aState, InputData* aFirstInput, + bool aForScrollbarDrag) { + // The APZC that we find via APZCCallbackHelpers may not be the same APZC + // ESM or OverscrollHandoff would have computed. Make sure we get the right + // one by looking for the first apzc the next pending event can scroll. + RefPtr apzc = aTargetApzc; + if (apzc && aFirstInput) { + apzc = apzc->BuildOverscrollHandoffChain()->FindFirstScrollable( + *aFirstInput, &mAllowedScrollDirections); + } + + InputBlockState::SetConfirmedTargetApzc(apzc, aState, aFirstInput, + aForScrollbarDrag); + return true; +} + +void WheelBlockState::Update(ScrollWheelInput& aEvent) { + // We might not be in a transaction if the block never started in a + // transaction - for example, if nothing was scrollable. + if (!InTransaction()) { + return; + } + + // The current "scroll series" is a like a sub-transaction. It has a separate + // timeout of 80ms. Since we need to compute wheel deltas at different phases + // of a transaction (for example, when it is updated, and later when the + // event action is taken), we affix the scroll series counter to the event. + // This makes GetScrollWheelDelta() consistent. + if (!mLastEventTime.IsNull() && + (aEvent.mTimeStamp - mLastEventTime).ToMilliseconds() > + kScrollSeriesTimeoutMs) { + mScrollSeriesCounter = 0; + } + aEvent.mScrollSeriesNumber = ++mScrollSeriesCounter; + + // If we can't scroll in the direction of the wheel event, we don't update + // the last move time. This allows us to timeout a transaction even if the + // mouse isn't moving. + // + // We skip this check if the target is not yet confirmed, so that when it is + // confirmed, we don't timeout the transaction. + RefPtr apzc = GetTargetApzc(); + if (IsTargetConfirmed() && !apzc->CanScroll(aEvent)) { + return; + } + + // Update the time of the last known good event, and reset the mouse move + // time to null. This will reset the delays on both the general transaction + // timeout and the mouse-move-in-frame timeout. + mLastEventTime = aEvent.mTimeStamp; + mLastMouseMove = TimeStamp(); +} + +bool WheelBlockState::MustStayActive() { return !mTransactionEnded; } + +const char* WheelBlockState::Type() { return "scroll wheel"; } + +bool WheelBlockState::ShouldAcceptNewEvent() const { + if (!InTransaction()) { + // If we're not in a transaction, start a new one. + return false; + } + + RefPtr apzc = GetTargetApzc(); + if (apzc->IsDestroyed()) { + return false; + } + + return true; +} + +bool WheelBlockState::MaybeTimeout(const ScrollWheelInput& aEvent) { + MOZ_ASSERT(InTransaction()); + + if (MaybeTimeout(aEvent.mTimeStamp)) { + return true; + } + + if (!mLastMouseMove.IsNull()) { + // If there's a recent mouse movement, we can time out the transaction + // early. + TimeDuration duration = TimeStamp::Now() - mLastMouseMove; + if (duration.ToMilliseconds() >= + StaticPrefs::mousewheel_transaction_ignoremovedelay()) { + TBS_LOG("%p wheel transaction timed out after mouse move\n", this); + EndTransaction(); + return true; + } + } + + return false; +} + +bool WheelBlockState::MaybeTimeout(const TimeStamp& aTimeStamp) { + MOZ_ASSERT(InTransaction()); + + // End the transaction if the event occurred > 1.5s after the most recently + // seen wheel event. + TimeDuration duration = aTimeStamp - mLastEventTime; + if (duration.ToMilliseconds() < + StaticPrefs::mousewheel_transaction_timeout()) { + return false; + } + + TBS_LOG("%p wheel transaction timed out\n", this); + + if (StaticPrefs::test_mousescroll()) { + RefPtr apzc = GetTargetApzc(); + apzc->NotifyMozMouseScrollEvent(u"MozMouseScrollTransactionTimeout"_ns); + } + + EndTransaction(); + return true; +} + +void WheelBlockState::OnMouseMove(const ScreenIntPoint& aPoint) { + MOZ_ASSERT(InTransaction()); + + if (!GetTargetApzc()->Contains(aPoint)) { + EndTransaction(); + return; + } + + if (mLastMouseMove.IsNull()) { + // If the cursor is moving inside the frame, and it is more than the + // ignoremovedelay time since the last scroll operation, we record + // this as the most recent mouse movement. + TimeStamp now = TimeStamp::Now(); + TimeDuration duration = now - mLastEventTime; + if (duration.ToMilliseconds() >= + StaticPrefs::mousewheel_transaction_ignoremovedelay()) { + mLastMouseMove = now; + } + } +} + +void WheelBlockState::UpdateTargetApzc( + const RefPtr& aTargetApzc) { + InputBlockState::UpdateTargetApzc(aTargetApzc); + + // If we found there was no target apzc, then we end the transaction. + if (!GetTargetApzc()) { + EndTransaction(); + } +} + +bool WheelBlockState::InTransaction() const { + // We consider a wheel block to be in a transaction if it has a confirmed + // target and is the most recent wheel input block to be created. + if (GetBlockId() != sLastWheelBlockId) { + return false; + } + + if (mTransactionEnded) { + return false; + } + + MOZ_ASSERT(GetTargetApzc()); + return true; +} + +bool WheelBlockState::AllowScrollHandoff() const { + // If we're in a wheel transaction, we do not allow overscroll handoff until + // a new event ends the wheel transaction. + return !IsTargetConfirmed() || !InTransaction(); +} + +void WheelBlockState::EndTransaction() { + TBS_LOG("%p ending wheel transaction\n", this); + mTransactionEnded = true; +} + +PanGestureBlockState::PanGestureBlockState( + const RefPtr& aTargetApzc, + TargetConfirmationFlags aFlags, const PanGestureInput& aInitialEvent) + : CancelableBlockState(aTargetApzc, aFlags), + mInterrupted(false), + mWaitingForContentResponse(false) { + if (aFlags.mTargetConfirmed) { + // Find the nearest APZC in the overscroll handoff chain that is scrollable. + // If we get a content confirmation later that the apzc is different, then + // content should have found a scrollable apzc, so we don't need to handle + // that case. + RefPtr apzc = + mOverscrollHandoffChain->FindFirstScrollable(aInitialEvent, + &mAllowedScrollDirections); + + if (apzc && apzc != GetTargetApzc()) { + UpdateTargetApzc(apzc); + } + } +} + +bool PanGestureBlockState::SetConfirmedTargetApzc( + const RefPtr& aTargetApzc, + TargetConfirmationState aState, InputData* aFirstInput, + bool aForScrollbarDrag) { + // The APZC that we find via APZCCallbackHelpers may not be the same APZC + // ESM or OverscrollHandoff would have computed. Make sure we get the right + // one by looking for the first apzc the next pending event can scroll. + RefPtr apzc = aTargetApzc; + if (apzc && aFirstInput) { + RefPtr scrollableApzc = + apzc->BuildOverscrollHandoffChain()->FindFirstScrollable( + *aFirstInput, &mAllowedScrollDirections); + if (scrollableApzc) { + apzc = scrollableApzc; + } + } + + InputBlockState::SetConfirmedTargetApzc(apzc, aState, aFirstInput, + aForScrollbarDrag); + return true; +} + +bool PanGestureBlockState::MustStayActive() { return !mInterrupted; } + +const char* PanGestureBlockState::Type() { return "pan gesture"; } + +bool PanGestureBlockState::SetContentResponse(bool aPreventDefault) { + if (aPreventDefault) { + TBS_LOG("%p setting interrupted flag\n", this); + mInterrupted = true; + } + bool stateChanged = CancelableBlockState::SetContentResponse(aPreventDefault); + if (mWaitingForContentResponse) { + mWaitingForContentResponse = false; + stateChanged = true; + } + return stateChanged; +} + +bool PanGestureBlockState::HasReceivedAllContentNotifications() const { + return CancelableBlockState::HasReceivedAllContentNotifications() && + !mWaitingForContentResponse; +} + +bool PanGestureBlockState::IsReadyForHandling() const { + if (!CancelableBlockState::IsReadyForHandling()) { + return false; + } + return !mWaitingForContentResponse || IsContentResponseTimerExpired(); +} + +bool PanGestureBlockState::AllowScrollHandoff() const { return false; } + +void PanGestureBlockState::SetNeedsToWaitForContentResponse( + bool aWaitForContentResponse) { + mWaitingForContentResponse = aWaitForContentResponse; +} + +PinchGestureBlockState::PinchGestureBlockState( + const RefPtr& aTargetApzc, + TargetConfirmationFlags aFlags) + : CancelableBlockState(aTargetApzc, aFlags), + mInterrupted(false), + mWaitingForContentResponse(false) {} + +bool PinchGestureBlockState::MustStayActive() { return true; } + +const char* PinchGestureBlockState::Type() { return "pinch gesture"; } + +bool PinchGestureBlockState::SetContentResponse(bool aPreventDefault) { + if (aPreventDefault) { + TBS_LOG("%p setting interrupted flag\n", this); + mInterrupted = true; + } + bool stateChanged = CancelableBlockState::SetContentResponse(aPreventDefault); + if (mWaitingForContentResponse) { + mWaitingForContentResponse = false; + stateChanged = true; + } + return stateChanged; +} + +bool PinchGestureBlockState::HasReceivedAllContentNotifications() const { + return CancelableBlockState::HasReceivedAllContentNotifications() && + !mWaitingForContentResponse; +} + +bool PinchGestureBlockState::IsReadyForHandling() const { + if (!CancelableBlockState::IsReadyForHandling()) { + return false; + } + return !mWaitingForContentResponse || IsContentResponseTimerExpired(); +} + +void PinchGestureBlockState::SetNeedsToWaitForContentResponse( + bool aWaitForContentResponse) { + mWaitingForContentResponse = aWaitForContentResponse; +} + +TouchBlockState::TouchBlockState( + const RefPtr& aTargetApzc, + TargetConfirmationFlags aFlags, TouchCounter& aCounter) + : CancelableBlockState(aTargetApzc, aFlags), + mAllowedTouchBehaviorSet(false), + mDuringFastFling(false), + mSingleTapOccurred(false), + mInSlop(false), + mTouchCounter(aCounter), + mStartTime(GetTargetApzc()->GetFrameTime().Time()) { + TBS_LOG("Creating %p\n", this); + if (!StaticPrefs::layout_css_touch_action_enabled()) { + mAllowedTouchBehaviorSet = true; + } +} + +bool TouchBlockState::SetAllowedTouchBehaviors( + const nsTArray& aBehaviors) { + if (mAllowedTouchBehaviorSet) { + return false; + } + TBS_LOG("%p got allowed touch behaviours for %zu points\n", this, + aBehaviors.Length()); + mAllowedTouchBehaviors.AppendElements(aBehaviors); + mAllowedTouchBehaviorSet = true; + return true; +} + +bool TouchBlockState::GetAllowedTouchBehaviors( + nsTArray& aOutBehaviors) const { + if (!mAllowedTouchBehaviorSet) { + return false; + } + aOutBehaviors.AppendElements(mAllowedTouchBehaviors); + return true; +} + +bool TouchBlockState::HasAllowedTouchBehaviors() const { + return mAllowedTouchBehaviorSet; +} + +void TouchBlockState::CopyPropertiesFrom(const TouchBlockState& aOther) { + TBS_LOG("%p copying properties from %p\n", this, &aOther); + if (StaticPrefs::layout_css_touch_action_enabled()) { + MOZ_ASSERT(aOther.mAllowedTouchBehaviorSet || + aOther.IsContentResponseTimerExpired()); + SetAllowedTouchBehaviors(aOther.mAllowedTouchBehaviors); + } + mTransformToApzc = aOther.mTransformToApzc; +} + +bool TouchBlockState::HasReceivedAllContentNotifications() const { + return CancelableBlockState::HasReceivedAllContentNotifications() + // See comment in TouchBlockState::IsReadyforHandling() + && (!StaticPrefs::layout_css_touch_action_enabled() || + mAllowedTouchBehaviorSet); +} + +bool TouchBlockState::IsReadyForHandling() const { + if (!CancelableBlockState::IsReadyForHandling()) { + return false; + } + + if (!StaticPrefs::layout_css_touch_action_enabled()) { + // If layout_css_touch_action_enabled() was false when this block was + // created, then mAllowedTouchBehaviorSet is guaranteed to the true. + // However, the pref may have been flipped to false after the block was + // created. In that case, we should eventually get the touch-behaviour + // notification, or expire the content response timeout, but we don't really + // need to wait for those, since we don't care about the touch-behaviour + // values any more. + return true; + } + + return mAllowedTouchBehaviorSet || IsContentResponseTimerExpired(); +} + +void TouchBlockState::SetDuringFastFling() { + TBS_LOG("%p setting fast-motion flag\n", this); + mDuringFastFling = true; +} + +bool TouchBlockState::IsDuringFastFling() const { return mDuringFastFling; } + +void TouchBlockState::SetSingleTapOccurred() { + TBS_LOG("%p setting single-tap-occurred flag\n", this); + mSingleTapOccurred = true; +} + +bool TouchBlockState::SingleTapOccurred() const { return mSingleTapOccurred; } + +bool TouchBlockState::MustStayActive() { return true; } + +const char* TouchBlockState::Type() { return "touch"; } + +TimeDuration TouchBlockState::GetTimeSinceBlockStart() const { + return GetTargetApzc()->GetFrameTime().Time() - mStartTime; +} + +void TouchBlockState::DispatchEvent(const InputData& aEvent) const { + MOZ_ASSERT(aEvent.mInputType == MULTITOUCH_INPUT); + mTouchCounter.Update(aEvent.AsMultiTouchInput()); + CancelableBlockState::DispatchEvent(aEvent); +} + +bool TouchBlockState::TouchActionAllowsPinchZoom() const { + if (!StaticPrefs::layout_css_touch_action_enabled()) { + return true; + } + // Pointer events specification requires that all touch points allow zoom. + for (size_t i = 0; i < mAllowedTouchBehaviors.Length(); i++) { + if (!(mAllowedTouchBehaviors[i] & AllowedTouchBehavior::PINCH_ZOOM)) { + return false; + } + } + return true; +} + +bool TouchBlockState::TouchActionAllowsDoubleTapZoom() const { + if (!StaticPrefs::layout_css_touch_action_enabled()) { + return true; + } + for (size_t i = 0; i < mAllowedTouchBehaviors.Length(); i++) { + if (!(mAllowedTouchBehaviors[i] & AllowedTouchBehavior::DOUBLE_TAP_ZOOM)) { + return false; + } + } + return true; +} + +bool TouchBlockState::TouchActionAllowsPanningX() const { + if (!StaticPrefs::layout_css_touch_action_enabled()) { + return true; + } + if (mAllowedTouchBehaviors.IsEmpty()) { + // Default to allowed + return true; + } + TouchBehaviorFlags flags = mAllowedTouchBehaviors[0]; + return (flags & AllowedTouchBehavior::HORIZONTAL_PAN); +} + +bool TouchBlockState::TouchActionAllowsPanningY() const { + if (!StaticPrefs::layout_css_touch_action_enabled()) { + return true; + } + if (mAllowedTouchBehaviors.IsEmpty()) { + // Default to allowed + return true; + } + TouchBehaviorFlags flags = mAllowedTouchBehaviors[0]; + return (flags & AllowedTouchBehavior::VERTICAL_PAN); +} + +bool TouchBlockState::TouchActionAllowsPanningXY() const { + if (!StaticPrefs::layout_css_touch_action_enabled()) { + return true; + } + if (mAllowedTouchBehaviors.IsEmpty()) { + // Default to allowed + return true; + } + TouchBehaviorFlags flags = mAllowedTouchBehaviors[0]; + return (flags & AllowedTouchBehavior::HORIZONTAL_PAN) && + (flags & AllowedTouchBehavior::VERTICAL_PAN); +} + +bool TouchBlockState::UpdateSlopState(const MultiTouchInput& aInput, + bool aApzcCanConsumeEvents) { + if (aInput.mType == MultiTouchInput::MULTITOUCH_START) { + // this is by definition the first event in this block. If it's the first + // touch, then we enter a slop state. + mInSlop = (aInput.mTouches.Length() == 1); + if (mInSlop) { + mSlopOrigin = aInput.mTouches[0].mScreenPoint; + TBS_LOG("%p entering slop with origin %s\n", this, + ToString(mSlopOrigin).c_str()); + } + return false; + } + if (mInSlop) { + ScreenCoord threshold = 0; + // If the target was confirmed to null then the threshold doesn't + // matter anyway since the events will never be processed. + if (const RefPtr& apzc = GetTargetApzc()) { + threshold = aApzcCanConsumeEvents ? apzc->GetTouchStartTolerance() + : apzc->GetTouchMoveTolerance(); + } + bool stayInSlop = + (aInput.mType == MultiTouchInput::MULTITOUCH_MOVE) && + (aInput.mTouches.Length() == 1) && + ((aInput.mTouches[0].mScreenPoint - mSlopOrigin).Length() < threshold); + if (!stayInSlop) { + // we're out of the slop zone, and will stay out for the remainder of + // this block + TBS_LOG("%p exiting slop\n", this); + mInSlop = false; + } + } + return mInSlop; +} + +bool TouchBlockState::IsInSlop() const { return mInSlop; } + +Maybe TouchBlockState::GetBestGuessPanDirection( + const MultiTouchInput& aInput) { + if (aInput.mType != MultiTouchInput::MULTITOUCH_MOVE || + aInput.mTouches.Length() != 1) { + return Nothing(); + } + ScreenPoint vector = aInput.mTouches[0].mScreenPoint - mSlopOrigin; + double angle = atan2(vector.y, vector.x); // range [-pi, pi] + angle = fabs(angle); // range [0, pi] + + double angleThreshold = TouchActionAllowsPanningXY() + ? StaticPrefs::apz_axis_lock_lock_angle() + : StaticPrefs::apz_axis_lock_direct_pan_angle(); + if (apz::IsCloseToHorizontal(angle, angleThreshold)) { + return Some(ScrollDirection::eHorizontal); + } + if (apz::IsCloseToVertical(angle, angleThreshold)) { + return Some(ScrollDirection::eVertical); + } + return Nothing(); +} + +uint32_t TouchBlockState::GetActiveTouchCount() const { + return mTouchCounter.GetActiveTouchCount(); +} + +KeyboardBlockState::KeyboardBlockState( + const RefPtr& aTargetApzc) + : InputBlockState(aTargetApzc, TargetConfirmationFlags{true}) {} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/src/InputBlockState.h b/gfx/layers/apz/src/InputBlockState.h new file mode 100644 index 0000000000..fe98575508 --- /dev/null +++ b/gfx/layers/apz/src/InputBlockState.h @@ -0,0 +1,541 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_InputBlockState_h +#define mozilla_layers_InputBlockState_h + +#include "InputData.h" // for MultiTouchInput +#include "mozilla/RefCounted.h" // for RefCounted +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/StaticPrefs_apz.h" +#include "mozilla/gfx/Matrix.h" // for Matrix4x4 +#include "mozilla/layers/APZUtils.h" +#include "mozilla/layers/LayersTypes.h" // for TouchBehaviorFlags +#include "mozilla/layers/AsyncDragMetrics.h" +#include "mozilla/layers/TouchCounter.h" +#include "mozilla/TimeStamp.h" // for TimeStamp +#include "nsTArray.h" // for nsTArray + +namespace mozilla { +namespace layers { + +class AsyncPanZoomController; +class OverscrollHandoffChain; +class CancelableBlockState; +class TouchBlockState; +class WheelBlockState; +class DragBlockState; +class PanGestureBlockState; +class PinchGestureBlockState; +class KeyboardBlockState; + +/** + * A base class that stores state common to various input blocks. + * Note that the InputBlockState constructor acquires the tree lock, so callers + * from inside AsyncPanZoomController should ensure that the APZC lock is not + * held. + */ +class InputBlockState : public RefCounted { + public: + MOZ_DECLARE_REFCOUNTED_TYPENAME(InputBlockState) + + static const uint64_t NO_BLOCK_ID = 0; + + enum class TargetConfirmationState : uint8_t { + eUnconfirmed, + eTimedOut, + eTimedOutAndMainThreadResponded, + eConfirmed + }; + + InputBlockState(const RefPtr& aTargetApzc, + TargetConfirmationFlags aFlags); + virtual ~InputBlockState() = default; + + virtual CancelableBlockState* AsCancelableBlock() { return nullptr; } + virtual TouchBlockState* AsTouchBlock() { return nullptr; } + virtual WheelBlockState* AsWheelBlock() { return nullptr; } + virtual DragBlockState* AsDragBlock() { return nullptr; } + virtual PanGestureBlockState* AsPanGestureBlock() { return nullptr; } + virtual PinchGestureBlockState* AsPinchGestureBlock() { return nullptr; } + virtual KeyboardBlockState* AsKeyboardBlock() { return nullptr; } + + virtual bool SetConfirmedTargetApzc( + const RefPtr& aTargetApzc, + TargetConfirmationState aState, InputData* aFirstInput, + bool aForScrollbarDrag); + const RefPtr& GetTargetApzc() const; + const RefPtr& GetOverscrollHandoffChain() const; + uint64_t GetBlockId() const; + + bool IsTargetConfirmed() const; + bool HasReceivedRealConfirmedTarget() const; + + virtual bool ShouldDropEvents() const; + + void SetScrolledApzc(AsyncPanZoomController* aApzc); + AsyncPanZoomController* GetScrolledApzc() const; + bool IsDownchainOfScrolledApzc(AsyncPanZoomController* aApzc) const; + + /** + * Dispatch the event to the target APZC. Mostly this is a hook for + * subclasses to do any per-event processing they need to. + */ + virtual void DispatchEvent(const InputData& aEvent) const; + + /** + * Return true if this input block must stay active if it would otherwise + * be removed as the last item in the pending queue. + */ + virtual bool MustStayActive() = 0; + + protected: + virtual void UpdateTargetApzc( + const RefPtr& aTargetApzc); + + private: + // Checks whether |aA| is an ancestor of |aB| (or the same as |aB|) in + // |mOverscrollHandoffChain|. + bool IsDownchainOf(AsyncPanZoomController* aA, + AsyncPanZoomController* aB) const; + + private: + RefPtr mTargetApzc; + TargetConfirmationState mTargetConfirmed; + bool mRequiresTargetConfirmation; + const uint64_t mBlockId; + + // The APZC that was actually scrolled by events in this input block. + // This is used in configurations where a single input block is only + // allowed to scroll a single APZC (configurations where + // StaticPrefs::apz_allow_immediate_handoff() is false). Set the first time an + // input event in this block scrolls an APZC. + RefPtr mScrolledApzc; + + protected: + RefPtr mOverscrollHandoffChain; + + // Used to transform events from global screen space to |mTargetApzc|'s + // screen space. It's cached at the beginning of the input block so that + // all events in the block are in the same coordinate space. + ScreenToParentLayerMatrix4x4 mTransformToApzc; +}; + +/** + * This class represents a set of events that can be cancelled by web content + * via event listeners. + * + * Each cancelable input block can be cancelled by web content, and + * this information is stored in the mPreventDefault flag. Because web + * content runs on the Gecko main thread, we cannot always wait for web + * content's response. Instead, there is a timeout that sets this flag in the + * case where web content doesn't respond in time. The mContentResponded and + * mContentResponseTimerExpired flags indicate which of these scenarios + * occurred. + */ +class CancelableBlockState : public InputBlockState { + public: + CancelableBlockState(const RefPtr& aTargetApzc, + TargetConfirmationFlags aFlags); + + CancelableBlockState* AsCancelableBlock() override { return this; } + + /** + * Record whether or not content cancelled this block of events. + * @param aPreventDefault true iff the block is cancelled. + * @return false if this block has already received a response from + * web content, true if not. + */ + virtual bool SetContentResponse(bool aPreventDefault); + + /** + * Record that content didn't respond in time. + * @return false if this block already timed out, true if not. + */ + bool TimeoutContentResponse(); + + /** + * Checks if the content response timer has already expired. + */ + bool IsContentResponseTimerExpired() const; + + /** + * @return true iff web content cancelled this block of events. + */ + bool IsDefaultPrevented() const; + + /** + * @return true iff this block has received all the information it could + * have gotten from the content thread. + */ + virtual bool HasReceivedAllContentNotifications() const; + + /** + * @return true iff this block has received all the information needed + * to properly dispatch the events in the block. + */ + virtual bool IsReadyForHandling() const; + + /** + * Return a descriptive name for the block kind. + */ + virtual const char* Type() = 0; + + bool ShouldDropEvents() const override; + + private: + bool mPreventDefault; + bool mContentResponded; + bool mContentResponseTimerExpired; +}; + +/** + * A single block of wheel events. + */ +class WheelBlockState : public CancelableBlockState { + public: + WheelBlockState(const RefPtr& aTargetApzc, + TargetConfirmationFlags aFlags, + const ScrollWheelInput& aEvent); + + bool SetContentResponse(bool aPreventDefault) override; + bool MustStayActive() override; + const char* Type() override; + bool SetConfirmedTargetApzc(const RefPtr& aTargetApzc, + TargetConfirmationState aState, + InputData* aFirstInput, + bool aForScrollbarDrag) override; + + WheelBlockState* AsWheelBlock() override { return this; } + + /** + * Determine whether this wheel block is accepting new events. + */ + bool ShouldAcceptNewEvent() const; + + /** + * Call to check whether a wheel event will cause the current transaction to + * timeout. + */ + bool MaybeTimeout(const ScrollWheelInput& aEvent); + + /** + * Called from APZCTM when a mouse move or drag+drop event occurs, before + * the event has been processed. + */ + void OnMouseMove(const ScreenIntPoint& aPoint); + + /** + * Returns whether or not the block is participating in a wheel transaction. + * This means that the block is the most recent input block to be created, + * and no events have occurred that would require scrolling a different + * frame. + * + * @return True if in a transaction, false otherwise. + */ + bool InTransaction() const; + + /** + * Mark the block as no longer participating in a wheel transaction. This + * will force future wheel events to begin a new input block. + */ + void EndTransaction(); + + /** + * @return Whether or not overscrolling is prevented for this wheel block. + */ + bool AllowScrollHandoff() const; + + /** + * Called to check and possibly end the transaction due to a timeout. + * + * @return True if the transaction ended, false otherwise. + */ + bool MaybeTimeout(const TimeStamp& aTimeStamp); + + /** + * Update the wheel transaction state for a new event. + */ + void Update(ScrollWheelInput& aEvent); + + ScrollDirections GetAllowedScrollDirections() const { + return mAllowedScrollDirections; + } + + protected: + void UpdateTargetApzc( + const RefPtr& aTargetApzc) override; + + private: + TimeStamp mLastEventTime; + TimeStamp mLastMouseMove; + uint32_t mScrollSeriesCounter; + bool mTransactionEnded; + ScrollDirections mAllowedScrollDirections; +}; + +/** + * A block of mouse events that are part of a drag + */ +class DragBlockState : public CancelableBlockState { + public: + DragBlockState(const RefPtr& aTargetApzc, + TargetConfirmationFlags aFlags, const MouseInput& aEvent); + + bool MustStayActive() override; + const char* Type() override; + + bool HasReceivedMouseUp(); + void MarkMouseUpReceived(); + + DragBlockState* AsDragBlock() override { return this; } + + void SetInitialThumbPos(CSSCoord aThumbPos); + void SetDragMetrics(const AsyncDragMetrics& aDragMetrics); + + void DispatchEvent(const InputData& aEvent) const override; + + private: + AsyncDragMetrics mDragMetrics; + CSSCoord mInitialThumbPos; + bool mReceivedMouseUp; +}; + +/** + * A single block of pan gesture events. + */ +class PanGestureBlockState : public CancelableBlockState { + public: + PanGestureBlockState(const RefPtr& aTargetApzc, + TargetConfirmationFlags aFlags, + const PanGestureInput& aEvent); + + bool SetContentResponse(bool aPreventDefault) override; + bool HasReceivedAllContentNotifications() const override; + bool IsReadyForHandling() const override; + bool MustStayActive() override; + const char* Type() override; + bool SetConfirmedTargetApzc(const RefPtr& aTargetApzc, + TargetConfirmationState aState, + InputData* aFirstInput, + bool aForScrollbarDrag) override; + + PanGestureBlockState* AsPanGestureBlock() override { return this; } + + /** + * @return Whether or not overscrolling is prevented for this block. + */ + bool AllowScrollHandoff() const; + + bool WasInterrupted() const { return mInterrupted; } + + void SetNeedsToWaitForContentResponse(bool aWaitForContentResponse); + + ScrollDirections GetAllowedScrollDirections() const { + return mAllowedScrollDirections; + } + + private: + bool mInterrupted; + bool mWaitingForContentResponse; + ScrollDirections mAllowedScrollDirections; +}; + +/** + * A single block of pinch gesture events. + */ +class PinchGestureBlockState : public CancelableBlockState { + public: + PinchGestureBlockState(const RefPtr& aTargetApzc, + TargetConfirmationFlags aFlags); + + bool SetContentResponse(bool aPreventDefault) override; + bool HasReceivedAllContentNotifications() const override; + bool IsReadyForHandling() const override; + bool MustStayActive() override; + const char* Type() override; + + PinchGestureBlockState* AsPinchGestureBlock() override { return this; } + + bool WasInterrupted() const { return mInterrupted; } + + void SetNeedsToWaitForContentResponse(bool aWaitForContentResponse); + + private: + bool mInterrupted; + bool mWaitingForContentResponse; +}; + +/** + * This class represents a single touch block. A touch block is + * a set of touch events that can be cancelled by web content via + * touch event listeners. + * + * Every touch-start event creates a new touch block. In this case, the + * touch block consists of the touch-start, followed by all touch events + * up to but not including the next touch-start (except in the case where + * a long-tap happens, see below). Note that in particular we cannot know + * when a touch block ends until the next one is started. Most touch + * blocks are created by receipt of a touch-start event. + * + * Every long-tap event also creates a new touch block, since it can also + * be consumed by web content. In this case, when the long-tap event is + * dispatched to web content, a new touch block is started to hold the remaining + * touch events, up to but not including the next touch start (or long-tap). + * + * Additionally, if touch-action is enabled, each touch block should + * have a set of allowed touch behavior flags; one for each touch point. + * This also requires running code on the Gecko main thread, and so may + * be populated with some latency. The mAllowedTouchBehaviorSet and + * mAllowedTouchBehaviors variables track this information. + */ +class TouchBlockState : public CancelableBlockState { + public: + explicit TouchBlockState(const RefPtr& aTargetApzc, + TargetConfirmationFlags aFlags, + TouchCounter& aTouchCounter); + + TouchBlockState* AsTouchBlock() override { return this; } + + /** + * Set the allowed touch behavior flags for this block. + * @return false if this block already has these flags set, true if not. + */ + bool SetAllowedTouchBehaviors(const nsTArray& aBehaviors); + /** + * If the allowed touch behaviors have been set, populate them into + * |aOutBehaviors| and return true. Else, return false. + */ + bool GetAllowedTouchBehaviors( + nsTArray& aOutBehaviors) const; + + /** + * Returns true if the allowed touch behaviours have been set, or if touch + * action is disabled. + */ + bool HasAllowedTouchBehaviors() const; + + /** + * Copy various properties from another block. + */ + void CopyPropertiesFrom(const TouchBlockState& aOther); + + /* + * @return true iff this block has received all the information it could + * have gotten from the content thread. + */ + bool HasReceivedAllContentNotifications() const override; + + /** + * @return true iff this block has received all the information needed + * to properly dispatch the events in the block. + */ + bool IsReadyForHandling() const override; + + /** + * Sets a flag that indicates this input block occurred while the APZ was + * in a state of fast flinging. This affects gestures that may be produced + * from input events in this block. + */ + void SetDuringFastFling(); + /** + * @return true iff SetDuringFastFling was called on this block. + */ + bool IsDuringFastFling() const; + /** + * Set the single-tap-occurred flag that indicates that this touch block + * triggered a single tap event. + */ + void SetSingleTapOccurred(); + /** + * @return true iff the single-tap-occurred flag is set on this block. + */ + bool SingleTapOccurred() const; + + /** + * @return false iff touch-action is enabled and the allowed touch behaviors + * for this touch block do not allow pinch-zooming. + */ + bool TouchActionAllowsPinchZoom() const; + /** + * @return false iff touch-action is enabled and the allowed touch behaviors + * for this touch block do not allow double-tap zooming. + */ + bool TouchActionAllowsDoubleTapZoom() const; + /** + * @return false iff touch-action is enabled and the allowed touch behaviors + * for the first touch point do not allow panning in the specified + * direction(s). + */ + bool TouchActionAllowsPanningX() const; + bool TouchActionAllowsPanningY() const; + bool TouchActionAllowsPanningXY() const; + + /** + * Notifies the input block of an incoming touch event so that the block can + * update its internal slop state. "Slop" refers to the area around the + * initial touchstart where we drop touchmove events so that content doesn't + * see them. The |aApzcCanConsumeEvents| parameter is factored into how large + * the slop area is - if this is true the slop area is larger. + * @return true iff the provided event is a touchmove in the slop area and + * so should not be sent to content. + */ + bool UpdateSlopState(const MultiTouchInput& aInput, + bool aApzcCanConsumeEvents); + bool IsInSlop() const; + + /** + * Based on the slop origin and the given input event, return a best guess + * as to the pan direction of this touch block. Returns Nothing() if no guess + * can be made. + */ + Maybe GetBestGuessPanDirection( + const MultiTouchInput& aInput); + + /** + * Returns the number of touch points currently active. + */ + uint32_t GetActiveTouchCount() const; + + void DispatchEvent(const InputData& aEvent) const override; + bool MustStayActive() override; + const char* Type() override; + TimeDuration GetTimeSinceBlockStart() const; + + private: + nsTArray mAllowedTouchBehaviors; + bool mAllowedTouchBehaviorSet; + bool mDuringFastFling; + bool mSingleTapOccurred; + bool mInSlop; + ScreenIntPoint mSlopOrigin; + // A reference to the InputQueue's touch counter + TouchCounter& mTouchCounter; + TimeStamp mStartTime; +}; + +/** + * This class represents a set of keyboard inputs targeted at the same Apzc. + */ +class KeyboardBlockState : public InputBlockState { + public: + explicit KeyboardBlockState( + const RefPtr& aTargetApzc); + + KeyboardBlockState* AsKeyboardBlock() override { return this; } + + bool MustStayActive() override { return false; } + + /** + * @return Whether or not overscrolling is prevented for this keyboard block. + */ + bool AllowScrollHandoff() const { return false; } +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_InputBlockState_h diff --git a/gfx/layers/apz/src/InputQueue.cpp b/gfx/layers/apz/src/InputQueue.cpp new file mode 100644 index 0000000000..787b3aa120 --- /dev/null +++ b/gfx/layers/apz/src/InputQueue.cpp @@ -0,0 +1,1018 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "InputQueue.h" + +#include "AsyncPanZoomController.h" + +#include "GestureEventListener.h" +#include "InputBlockState.h" +#include "mozilla/layers/APZInputBridge.h" +#include "mozilla/layers/APZThreadUtils.h" +#include "mozilla/ToString.h" +#include "OverscrollHandoffState.h" +#include "QueuedInput.h" +#include "mozilla/StaticPrefs_apz.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/StaticPrefs_ui.h" + +static mozilla::LazyLogModule sApzInpLog("apz.inputqueue"); +#define INPQ_LOG(...) MOZ_LOG(sApzInpLog, LogLevel::Debug, (__VA_ARGS__)) + +namespace mozilla { +namespace layers { + +InputQueue::InputQueue() = default; + +InputQueue::~InputQueue() { mQueuedInputs.Clear(); } + +nsEventStatus InputQueue::ReceiveInputEvent( + const RefPtr& aTarget, + TargetConfirmationFlags aFlags, const InputData& aEvent, + uint64_t* aOutInputBlockId, Maybe* aOutputHandledResult, + const Maybe>& aTouchBehaviors) { + APZThreadUtils::AssertOnControllerThread(); + + AutoRunImmediateTimeout timeoutRunner{this}; + + switch (aEvent.mInputType) { + case MULTITOUCH_INPUT: { + const MultiTouchInput& event = aEvent.AsMultiTouchInput(); + return ReceiveTouchInput(aTarget, aFlags, event, aOutInputBlockId, + aOutputHandledResult, aTouchBehaviors); + } + + case SCROLLWHEEL_INPUT: { + const ScrollWheelInput& event = aEvent.AsScrollWheelInput(); + return ReceiveScrollWheelInput(aTarget, aFlags, event, aOutInputBlockId); + } + + case PANGESTURE_INPUT: { + const PanGestureInput& event = aEvent.AsPanGestureInput(); + return ReceivePanGestureInput(aTarget, aFlags, event, aOutInputBlockId); + } + + case PINCHGESTURE_INPUT: { + const PinchGestureInput& event = aEvent.AsPinchGestureInput(); + return ReceivePinchGestureInput(aTarget, aFlags, event, aOutInputBlockId); + } + + case MOUSE_INPUT: { + const MouseInput& event = aEvent.AsMouseInput(); + return ReceiveMouseInput(aTarget, aFlags, event, aOutInputBlockId); + } + + case KEYBOARD_INPUT: { + // Every keyboard input must have a confirmed target + MOZ_ASSERT(aTarget && aFlags.mTargetConfirmed); + + const KeyboardInput& event = aEvent.AsKeyboardInput(); + return ReceiveKeyboardInput(aTarget, event, aOutInputBlockId); + } + + default: + // The return value for non-touch input is only used by tests, so just + // pass through the return value for now. This can be changed later if + // needed. + // TODO (bug 1098430): we will eventually need to have smarter handling + // for non-touch events as well. + return aTarget->HandleInputEvent(aEvent, aTarget->GetTransformToThis()); + } +} + +nsEventStatus InputQueue::ReceiveTouchInput( + const RefPtr& aTarget, + TargetConfirmationFlags aFlags, const MultiTouchInput& aEvent, + uint64_t* aOutInputBlockId, Maybe* aOutputHandledResult, + const Maybe>& aTouchBehaviors) { + TouchBlockState* block = nullptr; + bool waitingForContentResponse = false; + if (aEvent.mType == MultiTouchInput::MULTITOUCH_START) { + nsTArray currentBehaviors; + bool haveBehaviors = false; + if (!StaticPrefs::layout_css_touch_action_enabled()) { + haveBehaviors = true; + } else if (mActiveTouchBlock) { + haveBehaviors = + mActiveTouchBlock->GetAllowedTouchBehaviors(currentBehaviors); + // If the behaviours aren't set, but the main-thread response timer on + // the block is expired we still treat it as though it has behaviors, + // because in that case we still want to interrupt the fast-fling and + // use the default behaviours. + haveBehaviors |= mActiveTouchBlock->IsContentResponseTimerExpired(); + } + + block = StartNewTouchBlock(aTarget, aFlags, false); + INPQ_LOG("started new touch block %p id %" PRIu64 " for target %p\n", block, + block->GetBlockId(), aTarget.get()); + + // XXX using the chain from |block| here may be wrong in cases where the + // target isn't confirmed and the real target turns out to be something + // else. For now assume this is rare enough that it's not an issue. + if (mQueuedInputs.IsEmpty() && aEvent.mTouches.Length() == 1 && + block->GetOverscrollHandoffChain()->HasFastFlungApzc() && + haveBehaviors) { + // If we're already in a fast fling, and a single finger goes down, then + // we want special handling for the touch event, because it shouldn't get + // delivered to content. Note that we don't set this flag when going + // from a fast fling to a pinch state (i.e. second finger goes down while + // the first finger is moving). + block->SetDuringFastFling(); + block->SetConfirmedTargetApzc( + aTarget, InputBlockState::TargetConfirmationState::eConfirmed, + nullptr /* the block was just created so it has no events */, + false /* not a scrollbar drag */); + if (StaticPrefs::layout_css_touch_action_enabled()) { + block->SetAllowedTouchBehaviors(currentBehaviors); + } + INPQ_LOG("block %p tagged as fast-motion\n", block); + } else if (aTouchBehaviors) { + // If this block isn't started during a fast-fling, and APZCTM has + // provided touch behavior information, then put it on the block so + // that the ArePointerEventsConsumable call below can use it. + block->SetAllowedTouchBehaviors(*aTouchBehaviors); + } + + CancelAnimationsForNewBlock(block); + + waitingForContentResponse = MaybeRequestContentResponse(aTarget, block); + } else { + // for touch inputs that don't start a block, APZCTM shouldn't be giving + // us any touch behaviors. + MOZ_ASSERT(aTouchBehaviors.isNothing()); + + block = mActiveTouchBlock.get(); + if (!block) { + NS_WARNING( + "Received a non-start touch event while no touch blocks active!"); + return nsEventStatus_eIgnore; + } + + INPQ_LOG("received new touch event (type=%d) in block %p\n", aEvent.mType, + block); + } + + if (aOutInputBlockId) { + *aOutInputBlockId = block->GetBlockId(); + } + + // Note that the |aTarget| the APZCTM sent us may contradict the confirmed + // target set on the block. In this case the confirmed target (which may be + // null) should take priority. This is equivalent to just always using the + // target (confirmed or not) from the block. + RefPtr target = block->GetTargetApzc(); + + nsEventStatus result = nsEventStatus_eIgnore; + + // XXX calling ArePointerEventsConsumable on |target| may be wrong here if + // the target isn't confirmed and the real target turns out to be something + // else. For now assume this is rare enough that it's not an issue. + if (block->IsDuringFastFling()) { + INPQ_LOG("dropping event due to block %p being in fast motion\n", block); + result = nsEventStatus_eConsumeNoDefault; + } else if (target && target->ArePointerEventsConsumable(block, aEvent)) { + if (block->UpdateSlopState(aEvent, true)) { + INPQ_LOG("dropping event due to block %p being in slop\n", block); + result = nsEventStatus_eConsumeNoDefault; + } else { + if (aOutputHandledResult && + *aOutputHandledResult == Some(APZHandledResult::HandledByContent) && + !target->IsRootContent() && + block->GetOverscrollHandoffChain() + ->ScrollingDownWillMoveDynamicToolbar(target)) { + // The event is actually consumed by a non-root APZC but scroll + // positions in all relevant APZCs are at the bottom edge, so if there's + // still contents covered by the dynamic toolbar we need to move the + // dynamic toolbar to make the covered contents visible, thus we need + // to tell it to GeckoView so we handle it as if it's consumed in the + // root APZC. + // IMPORTANT NOTE: If the incoming TargetConfirmationFlags has + // mDispatchToContent, we need to change it to Nothing() so that + // GeckoView can properly wait for results from the content on the + // main-thread. + INPQ_LOG( + "changing handledByRootApzc from Some(HandledByContent) to %s\n", + aFlags.mDispatchToContent ? "Nothing()" : "Some(HandledByRoot)"); + *aOutputHandledResult = aFlags.mDispatchToContent + ? Nothing() + : Some(APZHandledResult::HandledByRoot); + } + result = nsEventStatus_eConsumeDoDefault; + } + } else if (block->UpdateSlopState(aEvent, false)) { + INPQ_LOG("dropping event due to block %p being in mini-slop\n", block); + result = nsEventStatus_eConsumeNoDefault; + } + mQueuedInputs.AppendElement(MakeUnique(aEvent, *block)); + ProcessQueue(); + + // If this block just started and is waiting for a content response, but + // also in a slop state (i.e. touchstart gets delivered to content but + // not any touchmoves), then we might end up in a situation where we don't + // get the content response until the timeout is hit because we never exit + // the slop state. But if that timeout is longer than the long-press timeout, + // then the long-press gets delayed too. Avoid that by scheduling a callback + // with the long-press timeout that will force the block to get processed. + int32_t longTapTimeout = StaticPrefs::ui_click_hold_context_menus_delay(); + int32_t contentTimeout = StaticPrefs::apz_content_response_timeout(); + if (waitingForContentResponse && longTapTimeout < contentTimeout && + block->IsInSlop() && GestureEventListener::IsLongTapEnabled()) { + MOZ_ASSERT(aEvent.mType == MultiTouchInput::MULTITOUCH_START); + MOZ_ASSERT(!block->IsDuringFastFling()); + RefPtr maybeLongTap = NewRunnableMethod( + "layers::InputQueue::MaybeLongTapTimeout", this, + &InputQueue::MaybeLongTapTimeout, block->GetBlockId()); + INPQ_LOG("scheduling maybe-long-tap timeout for target %p\n", + aTarget.get()); + aTarget->PostDelayedTask(maybeLongTap.forget(), longTapTimeout); + } + + return result; +} + +nsEventStatus InputQueue::ReceiveMouseInput( + const RefPtr& aTarget, + TargetConfirmationFlags aFlags, const MouseInput& aEvent, + uint64_t* aOutInputBlockId) { + // On a new mouse down we can have a new target so we must force a new block + // with a new target. + bool newBlock = DragTracker::StartsDrag(aEvent); + + DragBlockState* block = newBlock ? nullptr : mActiveDragBlock.get(); + if (block && block->HasReceivedMouseUp()) { + block = nullptr; + } + + if (!block && mDragTracker.InDrag()) { + // If there's no current drag block, but we're getting a move with a button + // down, we need to start a new drag block because we're obviously already + // in the middle of a drag (it probably got interrupted by something else). + INPQ_LOG( + "got a drag event outside a drag block, need to create a block to hold " + "it\n"); + newBlock = true; + } + + mDragTracker.Update(aEvent); + + if (!newBlock && !block) { + // This input event is not in a drag block, so we're not doing anything + // with it, return eIgnore. + return nsEventStatus_eIgnore; + } + + if (!block) { + MOZ_ASSERT(newBlock); + block = new DragBlockState(aTarget, aFlags, aEvent); + + INPQ_LOG( + "started new drag block %p id %" PRIu64 + "for %sconfirmed target %p; on scrollbar: %d; on scrollthumb: %d\n", + block, block->GetBlockId(), aFlags.mTargetConfirmed ? "" : "un", + aTarget.get(), aFlags.mHitScrollbar, aFlags.mHitScrollThumb); + + mActiveDragBlock = block; + + if (aFlags.mHitScrollThumb || !aFlags.mHitScrollbar) { + CancelAnimationsForNewBlock(block); + } + MaybeRequestContentResponse(aTarget, block); + } + + if (aOutInputBlockId) { + *aOutInputBlockId = block->GetBlockId(); + } + + mQueuedInputs.AppendElement(MakeUnique(aEvent, *block)); + ProcessQueue(); + + if (DragTracker::EndsDrag(aEvent)) { + block->MarkMouseUpReceived(); + } + + // The event is part of a drag block and could potentially cause + // scrolling, so return DoDefault. + return nsEventStatus_eConsumeDoDefault; +} + +nsEventStatus InputQueue::ReceiveScrollWheelInput( + const RefPtr& aTarget, + TargetConfirmationFlags aFlags, const ScrollWheelInput& aEvent, + uint64_t* aOutInputBlockId) { + WheelBlockState* block = mActiveWheelBlock.get(); + // If the block is not accepting new events we'll create a new input block + // (and therefore a new wheel transaction). + if (block && + (!block->ShouldAcceptNewEvent() || block->MaybeTimeout(aEvent))) { + block = nullptr; + } + + MOZ_ASSERT(!block || block->InTransaction()); + + if (!block) { + block = new WheelBlockState(aTarget, aFlags, aEvent); + INPQ_LOG("started new scroll wheel block %p id %" PRIu64 + " for %starget %p\n", + block, block->GetBlockId(), + aFlags.mTargetConfirmed ? "confirmed " : "", aTarget.get()); + + mActiveWheelBlock = block; + + CancelAnimationsForNewBlock(block, ExcludeWheel); + MaybeRequestContentResponse(aTarget, block); + } else { + INPQ_LOG("received new wheel event in block %p\n", block); + } + + if (aOutInputBlockId) { + *aOutInputBlockId = block->GetBlockId(); + } + + // Note that the |aTarget| the APZCTM sent us may contradict the confirmed + // target set on the block. In this case the confirmed target (which may be + // null) should take priority. This is equivalent to just always using the + // target (confirmed or not) from the block, which is what + // ProcessQueue() does. + mQueuedInputs.AppendElement(MakeUnique(aEvent, *block)); + + // The WheelBlockState needs to affix a counter to the event before we process + // it. Note that the counter is affixed to the copy in the queue rather than + // |aEvent|. + block->Update(mQueuedInputs.LastElement()->Input()->AsScrollWheelInput()); + + ProcessQueue(); + + return nsEventStatus_eConsumeDoDefault; +} + +nsEventStatus InputQueue::ReceiveKeyboardInput( + const RefPtr& aTarget, const KeyboardInput& aEvent, + uint64_t* aOutInputBlockId) { + KeyboardBlockState* block = mActiveKeyboardBlock.get(); + + // If the block is targeting a different Apzc than this keyboard event then + // we'll create a new input block + if (block && block->GetTargetApzc() != aTarget) { + block = nullptr; + } + + if (!block) { + block = new KeyboardBlockState(aTarget); + INPQ_LOG("started new keyboard block %p id %" PRIu64 " for target %p\n", + block, block->GetBlockId(), aTarget.get()); + + mActiveKeyboardBlock = block; + } else { + INPQ_LOG("received new keyboard event in block %p\n", block); + } + + if (aOutInputBlockId) { + *aOutInputBlockId = block->GetBlockId(); + } + + mQueuedInputs.AppendElement(MakeUnique(aEvent, *block)); + + ProcessQueue(); + + // If APZ is allowing passive listeners then we must dispatch the event to + // content, otherwise we can consume the event. + return StaticPrefs::apz_keyboard_passive_listeners() + ? nsEventStatus_eConsumeDoDefault + : nsEventStatus_eConsumeNoDefault; +} + +static bool CanScrollTargetHorizontally(const PanGestureInput& aInitialEvent, + PanGestureBlockState* aBlock) { + PanGestureInput horizontalComponent = aInitialEvent; + horizontalComponent.mPanDisplacement.y = 0; + ScrollDirections allowedScrollDirections; + RefPtr horizontallyScrollableAPZC = + aBlock->GetOverscrollHandoffChain()->FindFirstScrollable( + horizontalComponent, &allowedScrollDirections); + return horizontallyScrollableAPZC && + horizontallyScrollableAPZC == aBlock->GetTargetApzc() && + allowedScrollDirections.contains(ScrollDirection::eHorizontal); +} + +nsEventStatus InputQueue::ReceivePanGestureInput( + const RefPtr& aTarget, + TargetConfirmationFlags aFlags, const PanGestureInput& aEvent, + uint64_t* aOutInputBlockId) { + if (aEvent.mType == PanGestureInput::PANGESTURE_MAYSTART || + aEvent.mType == PanGestureInput::PANGESTURE_CANCELLED) { + // Ignore these events for now. + return nsEventStatus_eConsumeDoDefault; + } + + PanGestureBlockState* block = nullptr; + if (aEvent.mType != PanGestureInput::PANGESTURE_START) { + block = mActivePanGestureBlock.get(); + } + + PanGestureInput event = aEvent; + nsEventStatus result = nsEventStatus_eConsumeDoDefault; + + if (!block || block->WasInterrupted()) { + if (event.mType != PanGestureInput::PANGESTURE_START) { + // Only PANGESTURE_START events are allowed to start a new pan gesture + // block, but we really want to start a new block here, so we magically + // turn this input into a PANGESTURE_START. + INPQ_LOG( + "transmogrifying pan input %d to PANGESTURE_START for new block\n", + event.mType); + event.mType = PanGestureInput::PANGESTURE_START; + } + block = new PanGestureBlockState(aTarget, aFlags, event); + INPQ_LOG("started new pan gesture block %p id %" PRIu64 " for target %p\n", + block, block->GetBlockId(), aTarget.get()); + + if (aFlags.mTargetConfirmed && + event + .mRequiresContentResponseIfCannotScrollHorizontallyInStartDirection && + !CanScrollTargetHorizontally(event, block)) { + // This event may trigger a swipe gesture, depending on what our caller + // wants to do it. We need to suspend handling of this block until we get + // a content response which will tell us whether to proceed or abort the + // block. + block->SetNeedsToWaitForContentResponse(true); + + // Inform our caller that we haven't scrolled in response to the event + // and that a swipe can be started from this event if desired. + result = nsEventStatus_eIgnore; + } + + mActivePanGestureBlock = block; + + CancelAnimationsForNewBlock(block); + MaybeRequestContentResponse(aTarget, block); + } else { + INPQ_LOG("received new pan event (type=%d) in block %p\n", aEvent.mType, + block); + } + + if (aOutInputBlockId) { + *aOutInputBlockId = block->GetBlockId(); + } + + // Note that the |aTarget| the APZCTM sent us may contradict the confirmed + // target set on the block. In this case the confirmed target (which may be + // null) should take priority. This is equivalent to just always using the + // target (confirmed or not) from the block, which is what + // ProcessQueue() does. + mQueuedInputs.AppendElement(MakeUnique(event, *block)); + ProcessQueue(); + + return result; +} + +nsEventStatus InputQueue::ReceivePinchGestureInput( + const RefPtr& aTarget, + TargetConfirmationFlags aFlags, const PinchGestureInput& aEvent, + uint64_t* aOutInputBlockId) { + PinchGestureBlockState* block = nullptr; + if (aEvent.mType != PinchGestureInput::PINCHGESTURE_START) { + block = mActivePinchGestureBlock.get(); + } + + nsEventStatus result = nsEventStatus_eConsumeDoDefault; + + if (!block || block->WasInterrupted()) { + if (aEvent.mType != PinchGestureInput::PINCHGESTURE_START) { + // Only PINCHGESTURE_START events are allowed to start a new pinch gesture + // block. + INPQ_LOG("pinchgesture block %p was interrupted %d\n", block, + block ? block->WasInterrupted() : 0); + return nsEventStatus_eConsumeDoDefault; + } + block = new PinchGestureBlockState(aTarget, aFlags); + INPQ_LOG("started new pinch gesture block %p id %" PRIu64 + " for target %p\n", + block, block->GetBlockId(), aTarget.get()); + + mActivePinchGestureBlock = block; + block->SetNeedsToWaitForContentResponse(true); + + CancelAnimationsForNewBlock(block); + MaybeRequestContentResponse(aTarget, block); + } else { + INPQ_LOG("received new pinch event (type=%d) in block %p\n", aEvent.mType, + block); + } + + if (aOutInputBlockId) { + *aOutInputBlockId = block->GetBlockId(); + } + + // Note that the |aTarget| the APZCTM sent us may contradict the confirmed + // target set on the block. In this case the confirmed target (which may be + // null) should take priority. This is equivalent to just always using the + // target (confirmed or not) from the block, which is what + // ProcessQueue() does. + mQueuedInputs.AppendElement(MakeUnique(aEvent, *block)); + ProcessQueue(); + + return result; +} + +void InputQueue::CancelAnimationsForNewBlock(InputBlockState* aBlock, + CancelAnimationFlags aExtraFlags) { + // We want to cancel animations here as soon as possible (i.e. without waiting + // for content responses) because a finger has gone down and we don't want to + // keep moving the content under the finger. However, to prevent "future" + // touchstart events from interfering with "past" animations (i.e. from a + // previous touch block that is still being processed) we only do this + // animation-cancellation if there are no older touch blocks still in the + // queue. + if (mQueuedInputs.IsEmpty()) { + aBlock->GetOverscrollHandoffChain()->CancelAnimations( + aExtraFlags | ExcludeOverscroll | ScrollSnap); + } +} + +bool InputQueue::MaybeRequestContentResponse( + const RefPtr& aTarget, + CancelableBlockState* aBlock) { + bool waitForMainThread = false; + if (aBlock->IsTargetConfirmed()) { + // Content won't prevent-default this, so we can just set the flag directly. + INPQ_LOG("not waiting for content response on block %p\n", aBlock); + aBlock->SetContentResponse(false); + } else { + waitForMainThread = true; + } + if (aBlock->AsTouchBlock() && + !aBlock->AsTouchBlock()->HasAllowedTouchBehaviors()) { + INPQ_LOG("waiting for main thread touch-action info on block %p\n", aBlock); + waitForMainThread = true; + } + if (waitForMainThread) { + // We either don't know for sure if aTarget is the right APZC, or we may + // need to wait to give content the opportunity to prevent-default the + // touch events. Either way we schedule a timeout so the main thread stuff + // can run. + ScheduleMainThreadTimeout(aTarget, aBlock); + } + return waitForMainThread; +} + +uint64_t InputQueue::InjectNewTouchBlock(AsyncPanZoomController* aTarget) { + AutoRunImmediateTimeout timeoutRunner{this}; + TouchBlockState* block = + StartNewTouchBlock(aTarget, TargetConfirmationFlags{true}, + /* aCopyPropertiesFromCurrent = */ true); + INPQ_LOG("injecting new touch block %p with id %" PRIu64 " and target %p\n", + block, block->GetBlockId(), aTarget); + ScheduleMainThreadTimeout(aTarget, block); + return block->GetBlockId(); +} + +TouchBlockState* InputQueue::StartNewTouchBlock( + const RefPtr& aTarget, + TargetConfirmationFlags aFlags, bool aCopyPropertiesFromCurrent) { + TouchBlockState* newBlock = + new TouchBlockState(aTarget, aFlags, mTouchCounter); + if (aCopyPropertiesFromCurrent) { + // We should never enter here without a current touch block, because this + // codepath is invoked from the OnLongPress handler in + // AsyncPanZoomController, which should bail out if there is no current + // touch block. + MOZ_ASSERT(GetCurrentTouchBlock()); + newBlock->CopyPropertiesFrom(*GetCurrentTouchBlock()); + } + + mActiveTouchBlock = newBlock; + return newBlock; +} + +InputBlockState* InputQueue::GetCurrentBlock() const { + APZThreadUtils::AssertOnControllerThread(); + return mQueuedInputs.IsEmpty() ? nullptr : mQueuedInputs[0]->Block(); +} + +TouchBlockState* InputQueue::GetCurrentTouchBlock() const { + InputBlockState* block = GetCurrentBlock(); + return block ? block->AsTouchBlock() : mActiveTouchBlock.get(); +} + +WheelBlockState* InputQueue::GetCurrentWheelBlock() const { + InputBlockState* block = GetCurrentBlock(); + return block ? block->AsWheelBlock() : mActiveWheelBlock.get(); +} + +DragBlockState* InputQueue::GetCurrentDragBlock() const { + InputBlockState* block = GetCurrentBlock(); + return block ? block->AsDragBlock() : mActiveDragBlock.get(); +} + +PanGestureBlockState* InputQueue::GetCurrentPanGestureBlock() const { + InputBlockState* block = GetCurrentBlock(); + return block ? block->AsPanGestureBlock() : mActivePanGestureBlock.get(); +} + +PinchGestureBlockState* InputQueue::GetCurrentPinchGestureBlock() const { + InputBlockState* block = GetCurrentBlock(); + return block ? block->AsPinchGestureBlock() : mActivePinchGestureBlock.get(); +} + +KeyboardBlockState* InputQueue::GetCurrentKeyboardBlock() const { + InputBlockState* block = GetCurrentBlock(); + return block ? block->AsKeyboardBlock() : mActiveKeyboardBlock.get(); +} + +WheelBlockState* InputQueue::GetActiveWheelTransaction() const { + WheelBlockState* block = mActiveWheelBlock.get(); + if (!block || !block->InTransaction()) { + return nullptr; + } + return block; +} + +bool InputQueue::HasReadyTouchBlock() const { + return !mQueuedInputs.IsEmpty() && + mQueuedInputs[0]->Block()->AsTouchBlock() && + mQueuedInputs[0]->Block()->AsTouchBlock()->IsReadyForHandling(); +} + +bool InputQueue::AllowScrollHandoff() const { + if (GetCurrentWheelBlock()) { + return GetCurrentWheelBlock()->AllowScrollHandoff(); + } + if (GetCurrentPanGestureBlock()) { + return GetCurrentPanGestureBlock()->AllowScrollHandoff(); + } + if (GetCurrentKeyboardBlock()) { + return GetCurrentKeyboardBlock()->AllowScrollHandoff(); + } + return true; +} + +bool InputQueue::IsDragOnScrollbar(bool aHitScrollbar) { + if (!mDragTracker.InDrag()) { + return false; + } + // Now that we know we are in a drag, get the info from the drag tracker. + // We keep it in the tracker rather than the block because the block can get + // interrupted by something else (like a wheel event) and then a new block + // will get created without the info we want. The tracker will persist though. + return mDragTracker.IsOnScrollbar(aHitScrollbar); +} + +void InputQueue::ScheduleMainThreadTimeout( + const RefPtr& aTarget, + CancelableBlockState* aBlock) { + INPQ_LOG("scheduling main thread timeout for target %p\n", aTarget.get()); + RefPtr timeoutTask = NewRunnableMethod( + "layers::InputQueue::MainThreadTimeout", this, + &InputQueue::MainThreadTimeout, aBlock->GetBlockId()); + int32_t timeout = StaticPrefs::apz_content_response_timeout(); + if (timeout == 0) { + // If the timeout is zero, treat it as a request to ignore any main + // thread confirmation and unconditionally use fallback behaviour for + // when a timeout is reached. This codepath is used by tests that + // want to exercise the fallback behaviour. + // To ensure the fallback behaviour is used unconditionally, the timeout + // is run right away instead of using PostDelayedTask(). However, + // we can't run it right here, because MainThreadTimeout() expects that + // the input block has at least one input event in mQueuedInputs, and + // the event that triggered this call may not have been added to + // mQueuedInputs yet. + mImmediateTimeout = std::move(timeoutTask); + } else { + aTarget->PostDelayedTask(timeoutTask.forget(), timeout); + } +} + +InputBlockState* InputQueue::GetBlockForId(uint64_t aInputBlockId) { + return FindBlockForId(aInputBlockId, nullptr); +} + +void InputQueue::AddInputBlockCallback(uint64_t aInputBlockId, + InputBlockCallback&& aCallback) { + mInputBlockCallbacks.insert( + InputBlockCallbackMap::value_type(aInputBlockId, std::move(aCallback))); +} + +InputBlockState* InputQueue::FindBlockForId(uint64_t aInputBlockId, + InputData** aOutFirstInput) { + for (const auto& queuedInput : mQueuedInputs) { + if (queuedInput->Block()->GetBlockId() == aInputBlockId) { + if (aOutFirstInput) { + *aOutFirstInput = queuedInput->Input(); + } + return queuedInput->Block(); + } + } + + InputBlockState* block = nullptr; + if (mActiveTouchBlock && mActiveTouchBlock->GetBlockId() == aInputBlockId) { + block = mActiveTouchBlock.get(); + } else if (mActiveWheelBlock && + mActiveWheelBlock->GetBlockId() == aInputBlockId) { + block = mActiveWheelBlock.get(); + } else if (mActiveDragBlock && + mActiveDragBlock->GetBlockId() == aInputBlockId) { + block = mActiveDragBlock.get(); + } else if (mActivePanGestureBlock && + mActivePanGestureBlock->GetBlockId() == aInputBlockId) { + block = mActivePanGestureBlock.get(); + } else if (mActivePinchGestureBlock && + mActivePinchGestureBlock->GetBlockId() == aInputBlockId) { + block = mActivePinchGestureBlock.get(); + } else if (mActiveKeyboardBlock && + mActiveKeyboardBlock->GetBlockId() == aInputBlockId) { + block = mActiveKeyboardBlock.get(); + } + // Since we didn't encounter this block while iterating through mQueuedInputs, + // it must have no events associated with it at the moment. + if (aOutFirstInput) { + *aOutFirstInput = nullptr; + } + return block; +} + +void InputQueue::MainThreadTimeout(uint64_t aInputBlockId) { + // It's possible that this function gets called after the controller thread + // was discarded during shutdown. + if (!APZThreadUtils::IsControllerThreadAlive()) { + return; + } + APZThreadUtils::AssertOnControllerThread(); + + INPQ_LOG("got a main thread timeout; block=%" PRIu64 "\n", aInputBlockId); + bool success = false; + InputData* firstInput = nullptr; + InputBlockState* inputBlock = FindBlockForId(aInputBlockId, &firstInput); + if (inputBlock && inputBlock->AsCancelableBlock()) { + CancelableBlockState* block = inputBlock->AsCancelableBlock(); + // time out the touch-listener response and also confirm the existing + // target apzc in the case where the main thread doesn't get back to us + // fast enough. + success = block->TimeoutContentResponse(); + success |= block->SetConfirmedTargetApzc( + block->GetTargetApzc(), + InputBlockState::TargetConfirmationState::eTimedOut, firstInput, + // This actually could be a scrollbar drag, but we pass + // aForScrollbarDrag=false because for scrollbar drags, + // SetConfirmedTargetApzc() will also be called by ConfirmDragBlock(), + // and we pass aForScrollbarDrag=true there. + false); + } else if (inputBlock) { + NS_WARNING("input block is not a cancelable block"); + } + if (success) { + ProcessQueue(); + } +} + +void InputQueue::MaybeLongTapTimeout(uint64_t aInputBlockId) { + // It's possible that this function gets called after the controller thread + // was discarded during shutdown. + if (!APZThreadUtils::IsControllerThreadAlive()) { + return; + } + APZThreadUtils::AssertOnControllerThread(); + + INPQ_LOG("got a maybe-long-tap timeout; block=%" PRIu64 "\n", aInputBlockId); + + InputBlockState* inputBlock = FindBlockForId(aInputBlockId, nullptr); + MOZ_ASSERT(!inputBlock || inputBlock->AsTouchBlock()); + if (inputBlock && inputBlock->AsTouchBlock()->IsInSlop()) { + // If the block is still in slop, it won't have sent a touchmove to content + // and so content will not have sent a content response. But also it means + // the touchstart should trigger a long-press gesture so let's force the + // block to get processed now. + MainThreadTimeout(aInputBlockId); + } +} + +void InputQueue::ContentReceivedInputBlock(uint64_t aInputBlockId, + bool aPreventDefault) { + APZThreadUtils::AssertOnControllerThread(); + + INPQ_LOG("got a content response; block=%" PRIu64 "\n", aInputBlockId); + bool success = false; + InputBlockState* inputBlock = FindBlockForId(aInputBlockId, nullptr); + if (inputBlock && inputBlock->AsCancelableBlock()) { + CancelableBlockState* block = inputBlock->AsCancelableBlock(); + success = block->SetContentResponse(aPreventDefault); + } else if (inputBlock) { + NS_WARNING("input block is not a cancelable block"); + } + if (success) { + ProcessQueue(); + } +} + +void InputQueue::SetConfirmedTargetApzc( + uint64_t aInputBlockId, const RefPtr& aTargetApzc) { + APZThreadUtils::AssertOnControllerThread(); + + INPQ_LOG("got a target apzc; block=%" PRIu64 " guid=%s\n", aInputBlockId, + aTargetApzc ? ToString(aTargetApzc->GetGuid()).c_str() : ""); + bool success = false; + InputData* firstInput = nullptr; + InputBlockState* inputBlock = FindBlockForId(aInputBlockId, &firstInput); + if (inputBlock && inputBlock->AsCancelableBlock()) { + CancelableBlockState* block = inputBlock->AsCancelableBlock(); + success = block->SetConfirmedTargetApzc( + aTargetApzc, InputBlockState::TargetConfirmationState::eConfirmed, + firstInput, + // This actually could be a scrollbar drag, but we pass + // aForScrollbarDrag=false because for scrollbar drags, + // SetConfirmedTargetApzc() will also be called by ConfirmDragBlock(), + // and we pass aForScrollbarDrag=true there. + false); + } else if (inputBlock) { + NS_WARNING("input block is not a cancelable block"); + } + if (success) { + ProcessQueue(); + } +} + +void InputQueue::ConfirmDragBlock( + uint64_t aInputBlockId, const RefPtr& aTargetApzc, + const AsyncDragMetrics& aDragMetrics) { + APZThreadUtils::AssertOnControllerThread(); + + INPQ_LOG("got a target apzc; block=%" PRIu64 " guid=%s dragtarget=%" PRIu64 + "\n", + aInputBlockId, + aTargetApzc ? ToString(aTargetApzc->GetGuid()).c_str() : "", + aDragMetrics.mViewId); + bool success = false; + InputData* firstInput = nullptr; + InputBlockState* inputBlock = FindBlockForId(aInputBlockId, &firstInput); + if (inputBlock && inputBlock->AsDragBlock()) { + DragBlockState* block = inputBlock->AsDragBlock(); + block->SetDragMetrics(aDragMetrics); + success = block->SetConfirmedTargetApzc( + aTargetApzc, InputBlockState::TargetConfirmationState::eConfirmed, + firstInput, + /* aForScrollbarDrag = */ true); + } + if (success) { + ProcessQueue(); + } +} + +void InputQueue::SetAllowedTouchBehavior( + uint64_t aInputBlockId, const nsTArray& aBehaviors) { + APZThreadUtils::AssertOnControllerThread(); + + INPQ_LOG("got allowed touch behaviours; block=%" PRIu64 "\n", aInputBlockId); + bool success = false; + InputBlockState* inputBlock = FindBlockForId(aInputBlockId, nullptr); + if (inputBlock && inputBlock->AsTouchBlock()) { + TouchBlockState* block = inputBlock->AsTouchBlock(); + success = block->SetAllowedTouchBehaviors(aBehaviors); + } else if (inputBlock) { + NS_WARNING("input block is not a touch block"); + } + if (success) { + ProcessQueue(); + } +} + +static APZHandledResult GetHandledResultFor( + const AsyncPanZoomController* aApzc, + const InputBlockState& aCurrentInputBlock) { + if (aCurrentInputBlock.ShouldDropEvents()) { + return APZHandledResult::HandledByContent; + } + + if (aApzc && aApzc->IsRootContent()) { + return aApzc->CanScrollDownwardsWithDynamicToolbar() + ? APZHandledResult::HandledByRoot + : APZHandledResult::Unhandled; + } + + // Return `HandledByRoot` if scroll positions in all relevant APZC are at the + // bottom edge and if there are contents covered by the dynamic toolbar. + return aApzc && aCurrentInputBlock.GetOverscrollHandoffChain() + ->ScrollingDownWillMoveDynamicToolbar(aApzc) + ? APZHandledResult::HandledByRoot + : APZHandledResult::HandledByContent; +} + +void InputQueue::ProcessQueue() { + APZThreadUtils::AssertOnControllerThread(); + + while (!mQueuedInputs.IsEmpty()) { + InputBlockState* curBlock = mQueuedInputs[0]->Block(); + CancelableBlockState* cancelable = curBlock->AsCancelableBlock(); + if (cancelable && !cancelable->IsReadyForHandling()) { + break; + } + + INPQ_LOG( + "processing input from block %p; preventDefault %d shouldDropEvents %d " + "target %p\n", + curBlock, cancelable && cancelable->IsDefaultPrevented(), + curBlock->ShouldDropEvents(), curBlock->GetTargetApzc().get()); + RefPtr target = curBlock->GetTargetApzc(); + + // If there is an input block callback registered for this + // input block, invoke it. + auto it = mInputBlockCallbacks.find(curBlock->GetBlockId()); + if (it != mInputBlockCallbacks.end()) { + APZHandledResult handledResult = GetHandledResultFor(target, *curBlock); + it->second(curBlock->GetBlockId(), handledResult); + // The callback is one-shot; discard it after calling it. + mInputBlockCallbacks.erase(it); + } + + // target may be null here if the initial target was unconfirmed and then + // we later got a confirmed null target. in that case drop the events. + if (target) { + // If the event is targeting a different APZC than the previous one, + // we want to clear the previous APZC's gesture state regardless of + // whether we're actually dispatching the event or not. + if (mLastActiveApzc && mLastActiveApzc != target && + mTouchCounter.GetActiveTouchCount() > 0) { + mLastActiveApzc->ResetTouchInputState(); + } + if (curBlock->ShouldDropEvents()) { + if (curBlock->AsTouchBlock()) { + target->ResetTouchInputState(); + } + } else { + UpdateActiveApzc(target); + curBlock->DispatchEvent(*(mQueuedInputs[0]->Input())); + } + } + mQueuedInputs.RemoveElementAt(0); + } + + if (CanDiscardBlock(mActiveTouchBlock)) { + mActiveTouchBlock = nullptr; + } + if (CanDiscardBlock(mActiveWheelBlock)) { + mActiveWheelBlock = nullptr; + } + if (CanDiscardBlock(mActiveDragBlock)) { + mActiveDragBlock = nullptr; + } + if (CanDiscardBlock(mActivePanGestureBlock)) { + mActivePanGestureBlock = nullptr; + } + if (CanDiscardBlock(mActivePinchGestureBlock)) { + mActivePinchGestureBlock = nullptr; + } + if (CanDiscardBlock(mActiveKeyboardBlock)) { + mActiveKeyboardBlock = nullptr; + } +} + +bool InputQueue::CanDiscardBlock(InputBlockState* aBlock) { + if (!aBlock || + (aBlock->AsCancelableBlock() && + !aBlock->AsCancelableBlock()->IsReadyForHandling()) || + aBlock->MustStayActive()) { + return false; + } + InputData* firstInput = nullptr; + FindBlockForId(aBlock->GetBlockId(), &firstInput); + if (firstInput) { + // The block has at least one input event still in the queue, so it's + // not depleted + return false; + } + return true; +} + +void InputQueue::UpdateActiveApzc( + const RefPtr& aNewActive) { + mLastActiveApzc = aNewActive; +} + +void InputQueue::Clear() { + APZThreadUtils::AssertOnControllerThread(); + + mQueuedInputs.Clear(); + mActiveTouchBlock = nullptr; + mActiveWheelBlock = nullptr; + mActiveDragBlock = nullptr; + mActivePanGestureBlock = nullptr; + mActivePinchGestureBlock = nullptr; + mActiveKeyboardBlock = nullptr; + mLastActiveApzc = nullptr; +} + +InputQueue::AutoRunImmediateTimeout::AutoRunImmediateTimeout(InputQueue* aQueue) + : mQueue(aQueue) { + MOZ_ASSERT(!mQueue->mImmediateTimeout); +} + +InputQueue::AutoRunImmediateTimeout::~AutoRunImmediateTimeout() { + if (mQueue->mImmediateTimeout) { + mQueue->mImmediateTimeout->Run(); + mQueue->mImmediateTimeout = nullptr; + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/src/InputQueue.h b/gfx/layers/apz/src/InputQueue.h new file mode 100644 index 0000000000..52b244681c --- /dev/null +++ b/gfx/layers/apz/src/InputQueue.h @@ -0,0 +1,273 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_InputQueue_h +#define mozilla_layers_InputQueue_h + +#include "APZUtils.h" +#include "DragTracker.h" +#include "InputData.h" +#include "mozilla/EventForwards.h" +#include "mozilla/layers/TouchCounter.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" +#include "nsTArray.h" + +#include + +namespace mozilla { + +class InputData; +class MultiTouchInput; +class ScrollWheelInput; + +namespace layers { + +class AsyncPanZoomController; +class InputBlockState; +class CancelableBlockState; +class TouchBlockState; +class WheelBlockState; +class DragBlockState; +class PanGestureBlockState; +class PinchGestureBlockState; +class KeyboardBlockState; +class AsyncDragMetrics; +class QueuedInput; +enum class APZHandledResult : uint8_t; + +/** + * This class stores incoming input events, associated with "input blocks", + * until they are ready for handling. + */ +class InputQueue { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(InputQueue) + + public: + InputQueue(); + + /** + * Notifies the InputQueue of a new incoming input event. The APZC that the + * input event was targeted to should be provided in the |aTarget| parameter. + * See the documentation on APZCTreeManager::ReceiveInputEvent for info on + * return values from this function, including |aOutInputBlockId|. + */ + nsEventStatus ReceiveInputEvent( + const RefPtr& aTarget, + TargetConfirmationFlags aFlags, const InputData& aEvent, + uint64_t* aOutInputBlockId, + Maybe* aOutputHandledResult = nullptr, + const Maybe>& aTouchBehaviors = Nothing()); + /** + * This function should be invoked to notify the InputQueue when web content + * decides whether or not it wants to cancel a block of events. The block + * id to which this applies should be provided in |aInputBlockId|. + */ + void ContentReceivedInputBlock(uint64_t aInputBlockId, bool aPreventDefault); + /** + * This function should be invoked to notify the InputQueue once the target + * APZC to handle an input block has been confirmed. In practice this should + * generally be decidable upon receipt of the input event, but in some cases + * we may need to query the layout engine to know for sure. The input block + * this applies to should be specified via the |aInputBlockId| parameter. + */ + void SetConfirmedTargetApzc( + uint64_t aInputBlockId, + const RefPtr& aTargetApzc); + /** + * This function is invoked to confirm that the drag block should be handled + * by the APZ. + */ + void ConfirmDragBlock(uint64_t aInputBlockId, + const RefPtr& aTargetApzc, + const AsyncDragMetrics& aDragMetrics); + /** + * This function should be invoked to notify the InputQueue of the touch- + * action properties for the different touch points in an input block. The + * input block this applies to should be specified by the |aInputBlockId| + * parameter. If touch-action is not enabled on the platform, this function + * does nothing and need not be called. + */ + void SetAllowedTouchBehavior(uint64_t aInputBlockId, + const nsTArray& aBehaviors); + /** + * Adds a new touch block at the end of the input queue that has the same + * allowed touch behaviour flags as the the touch block currently being + * processed. This should only be called when processing of a touch block + * triggers the creation of a new touch block. Returns the input block id + * of the the newly-created block. + */ + uint64_t InjectNewTouchBlock(AsyncPanZoomController* aTarget); + /** + * Returns the pending input block at the head of the queue, if there is one. + * This may return null if there all input events have been processed. + */ + InputBlockState* GetCurrentBlock() const; + /* + * Returns the current pending input block as a specific kind of block. If + * GetCurrentBlock() returns null, these functions additionally check the + * mActiveXXXBlock field of the corresponding input type to see if there is + * a depleted but still active input block, and returns that if found. These + * functions may return null if no block is found. + */ + TouchBlockState* GetCurrentTouchBlock() const; + WheelBlockState* GetCurrentWheelBlock() const; + DragBlockState* GetCurrentDragBlock() const; + PanGestureBlockState* GetCurrentPanGestureBlock() const; + PinchGestureBlockState* GetCurrentPinchGestureBlock() const; + KeyboardBlockState* GetCurrentKeyboardBlock() const; + /** + * Returns true iff the pending block at the head of the queue is a touch + * block and is ready for handling. + */ + bool HasReadyTouchBlock() const; + /** + * If there is an active wheel transaction, returns the WheelBlockState + * representing the transaction. Otherwise, returns null. "Active" in this + * function name is the same kind of "active" as in mActiveWheelBlock - that + * is, new incoming wheel events will go into the "active" block. + */ + WheelBlockState* GetActiveWheelTransaction() const; + /** + * Remove all input blocks from the input queue. + */ + void Clear(); + /** + * Whether the current pending block allows scroll handoff. + */ + bool AllowScrollHandoff() const; + /** + * If there is currently a drag in progress, return whether or not it was + * targeted at a scrollbar. If the drag was newly-created and doesn't know, + * use the provided |aOnScrollbar| to populate that information. + */ + bool IsDragOnScrollbar(bool aOnScrollbar); + + InputBlockState* GetBlockForId(uint64_t aInputBlockId); + + using InputBlockCallback = std::function; + void AddInputBlockCallback(uint64_t aInputBlockId, + InputBlockCallback&& aCallback); + + private: + ~InputQueue(); + + // RAII class for automatically running a timeout task that may + // need to be run immediately after an event has been queued. + class AutoRunImmediateTimeout final { + public: + explicit AutoRunImmediateTimeout(InputQueue* aQueue); + ~AutoRunImmediateTimeout(); + + private: + InputQueue* mQueue; + }; + + TouchBlockState* StartNewTouchBlock( + const RefPtr& aTarget, + TargetConfirmationFlags aFlags, bool aCopyPropertiesFromCurrent); + + /** + * If animations are present for the current pending input block, cancel + * them as soon as possible. + */ + void CancelAnimationsForNewBlock(InputBlockState* aBlock, + CancelAnimationFlags aExtraFlags = Default); + + /** + * If we need to wait for a content response, schedule that now. Returns true + * if the timeout was scheduled, false otherwise. + */ + bool MaybeRequestContentResponse( + const RefPtr& aTarget, + CancelableBlockState* aBlock); + + nsEventStatus ReceiveTouchInput( + const RefPtr& aTarget, + TargetConfirmationFlags aFlags, const MultiTouchInput& aEvent, + uint64_t* aOutInputBlockId, Maybe* aOutputHandledResult, + const Maybe>& aTouchBehaviors); + nsEventStatus ReceiveMouseInput(const RefPtr& aTarget, + TargetConfirmationFlags aFlags, + const MouseInput& aEvent, + uint64_t* aOutInputBlockId); + nsEventStatus ReceiveScrollWheelInput( + const RefPtr& aTarget, + TargetConfirmationFlags aFlags, const ScrollWheelInput& aEvent, + uint64_t* aOutInputBlockId); + nsEventStatus ReceivePanGestureInput( + const RefPtr& aTarget, + TargetConfirmationFlags aFlags, const PanGestureInput& aEvent, + uint64_t* aOutInputBlockId); + nsEventStatus ReceivePinchGestureInput( + const RefPtr& aTarget, + TargetConfirmationFlags aFlags, const PinchGestureInput& aEvent, + uint64_t* aOutInputBlockId); + nsEventStatus ReceiveKeyboardInput( + const RefPtr& aTarget, + const KeyboardInput& aEvent, uint64_t* aOutInputBlockId); + + /** + * Helper function that searches mQueuedInputs for the first block matching + * the given id, and returns it. If |aOutFirstInput| is non-null, it is + * populated with a pointer to the first input in mQueuedInputs that + * corresponds to the block, or null if no such input was found. Note that + * even if there are no inputs in mQueuedInputs, this function can return + * non-null if the block id provided matches one of the depleted-but-still- + * active blocks (mActiveTouchBlock, mActiveWheelBlock, etc.). + */ + InputBlockState* FindBlockForId(uint64_t aInputBlockId, + InputData** aOutFirstInput); + void ScheduleMainThreadTimeout(const RefPtr& aTarget, + CancelableBlockState* aBlock); + void MainThreadTimeout(uint64_t aInputBlockId); + void MaybeLongTapTimeout(uint64_t aInputBlockId); + void ProcessQueue(); + bool CanDiscardBlock(InputBlockState* aBlock); + void UpdateActiveApzc(const RefPtr& aNewActive); + + private: + // The queue of input events that have not yet been fully processed. + // This member must only be accessed on the controller/UI thread. + nsTArray> mQueuedInputs; + + // These are the most recently created blocks of each input type. They are + // "active" in the sense that new inputs of that type are associated with + // them. Note that these pointers may be null if no inputs of the type have + // arrived, or if the inputs for the type formed a complete block that was + // then discarded. + RefPtr mActiveTouchBlock; + RefPtr mActiveWheelBlock; + RefPtr mActiveDragBlock; + RefPtr mActivePanGestureBlock; + RefPtr mActivePinchGestureBlock; + RefPtr mActiveKeyboardBlock; + + // The APZC to which the last event was delivered + RefPtr mLastActiveApzc; + + // Track touches so we know when to clear mLastActiveApzc + TouchCounter mTouchCounter; + + // Track mouse inputs so we know if we're in a drag or not + DragTracker mDragTracker; + + // Temporarily stores a timeout task that needs to be run as soon as + // as the event that triggered it has been queued. + RefPtr mImmediateTimeout; + + // Maps input block ids to callbacks that will be invoked when the input block + // is ready for handling. + using InputBlockCallbackMap = + std::unordered_map; + InputBlockCallbackMap mInputBlockCallbacks; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_InputQueue_h diff --git a/gfx/layers/apz/src/KeyboardMap.cpp b/gfx/layers/apz/src/KeyboardMap.cpp new file mode 100644 index 0000000000..9444037be6 --- /dev/null +++ b/gfx/layers/apz/src/KeyboardMap.cpp @@ -0,0 +1,170 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/KeyboardMap.h" + +#include "mozilla/TextEvents.h" // for IgnoreModifierState, ShortcutKeyCandidate + +namespace mozilla { +namespace layers { + +KeyboardShortcut::KeyboardShortcut() + : mKeyCode(0), + mCharCode(0), + mModifiers(0), + mModifiersMask(0), + mEventType(KeyboardInput::KeyboardEventType::KEY_OTHER), + mDispatchToContent(false) {} + +KeyboardShortcut::KeyboardShortcut(KeyboardInput::KeyboardEventType aEventType, + uint32_t aKeyCode, uint32_t aCharCode, + Modifiers aModifiers, + Modifiers aModifiersMask, + const KeyboardScrollAction& aAction) + : mAction(aAction), + mKeyCode(aKeyCode), + mCharCode(aCharCode), + mModifiers(aModifiers), + mModifiersMask(aModifiersMask), + mEventType(aEventType), + mDispatchToContent(false) {} + +KeyboardShortcut::KeyboardShortcut(KeyboardInput::KeyboardEventType aEventType, + uint32_t aKeyCode, uint32_t aCharCode, + Modifiers aModifiers, + Modifiers aModifiersMask) + : mKeyCode(aKeyCode), + mCharCode(aCharCode), + mModifiers(aModifiers), + mModifiersMask(aModifiersMask), + mEventType(aEventType), + mDispatchToContent(true) {} + +/* static */ +void KeyboardShortcut::AppendHardcodedShortcuts( + nsTArray& aShortcuts) { + // Tab + KeyboardShortcut tab1; + tab1.mDispatchToContent = true; + tab1.mKeyCode = NS_VK_TAB; + tab1.mCharCode = 0; + tab1.mModifiers = 0; + tab1.mModifiersMask = 0; + tab1.mEventType = KeyboardInput::KEY_PRESS; + aShortcuts.AppendElement(tab1); + + // F6 + KeyboardShortcut tab2; + tab2.mDispatchToContent = true; + tab2.mKeyCode = NS_VK_F6; + tab2.mCharCode = 0; + tab2.mModifiers = 0; + tab2.mModifiersMask = 0; + tab2.mEventType = KeyboardInput::KEY_PRESS; + aShortcuts.AppendElement(tab2); +} + +bool KeyboardShortcut::Matches(const KeyboardInput& aInput, + const IgnoreModifierState& aIgnore, + uint32_t aOverrideCharCode) const { + return mEventType == aInput.mType && MatchesKey(aInput, aOverrideCharCode) && + MatchesModifiers(aInput, aIgnore); +} + +bool KeyboardShortcut::MatchesKey(const KeyboardInput& aInput, + uint32_t aOverrideCharCode) const { + // Compare by the key code if we have one + if (!mCharCode) { + return mKeyCode == aInput.mKeyCode; + } + + // We are comparing by char code + uint32_t charCode; + + // If we are comparing against a shortcut candidate then we might + // have an override char code + if (aOverrideCharCode) { + charCode = aOverrideCharCode; + } else { + charCode = aInput.mCharCode; + } + + // Both char codes must be in lowercase to compare correctly + if (IS_IN_BMP(charCode)) { + charCode = ToLowerCase(static_cast(charCode)); + } + + return mCharCode == charCode; +} + +bool KeyboardShortcut::MatchesModifiers( + const KeyboardInput& aInput, const IgnoreModifierState& aIgnore) const { + Modifiers modifiersMask = mModifiersMask; + + // If we are ignoring Shift or OS, then unset that part of the mask + if (aIgnore.mOS) { + modifiersMask &= ~MODIFIER_OS; + } + if (aIgnore.mShift) { + modifiersMask &= ~MODIFIER_SHIFT; + } + + // Mask off the modifiers we are ignoring from the keyboard input + return (aInput.modifiers & modifiersMask) == mModifiers; +} + +KeyboardMap::KeyboardMap(nsTArray&& aShortcuts) + : mShortcuts(aShortcuts) {} + +KeyboardMap::KeyboardMap() = default; + +Maybe KeyboardMap::FindMatch( + const KeyboardInput& aEvent) const { + // If there are no shortcut candidates, then just search with with the + // keyboard input + if (aEvent.mShortcutCandidates.IsEmpty()) { + return FindMatchInternal(aEvent, IgnoreModifierState()); + } + + // Otherwise do a search with each shortcut candidate in order + for (auto& key : aEvent.mShortcutCandidates) { + IgnoreModifierState ignoreModifierState; + ignoreModifierState.mShift = key.mIgnoreShift; + + auto match = FindMatchInternal(aEvent, ignoreModifierState, key.mCharCode); + if (match) { + return match; + } + } + return Nothing(); +} + +Maybe KeyboardMap::FindMatchInternal( + const KeyboardInput& aEvent, const IgnoreModifierState& aIgnore, + uint32_t aOverrideCharCode) const { + for (auto& shortcut : mShortcuts) { + if (shortcut.Matches(aEvent, aIgnore, aOverrideCharCode)) { + return Some(shortcut); + } + } + +#ifdef XP_WIN + // Windows native applications ignore Windows-Logo key state when checking + // shortcut keys even if the key is pressed. Therefore, if there is no + // shortcut key which exactly matches current modifier state, we should + // retry to look for a shortcut key without the Windows-Logo key press. + if (!aIgnore.mOS && (aEvent.modifiers & MODIFIER_OS)) { + IgnoreModifierState ignoreModifierState(aIgnore); + ignoreModifierState.mOS = true; + return FindMatchInternal(aEvent, ignoreModifierState, aOverrideCharCode); + } +#endif + + return Nothing(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/src/KeyboardMap.h b/gfx/layers/apz/src/KeyboardMap.h new file mode 100644 index 0000000000..32ec8ea61d --- /dev/null +++ b/gfx/layers/apz/src/KeyboardMap.h @@ -0,0 +1,118 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_KeyboardMap_h +#define mozilla_layers_KeyboardMap_h + +#include // for uint32_t + +#include "InputData.h" // for KeyboardInput +#include "nsIScrollableFrame.h" // for nsIScrollableFrame::ScrollUnit +#include "nsTArray.h" // for nsTArray +#include "mozilla/Maybe.h" // for mozilla::Maybe +#include "KeyboardScrollAction.h" // for KeyboardScrollAction + +namespace mozilla { + +struct IgnoreModifierState; + +namespace layers { + +class KeyboardMap; + +/** + * This class is an off main-thread for scrolling commands. + */ +class KeyboardShortcut final { + public: + KeyboardShortcut(); + + /** + * Create a keyboard shortcut that when matched can be handled by executing + * the specified keyboard action. + */ + KeyboardShortcut(KeyboardInput::KeyboardEventType aEventType, + uint32_t aKeyCode, uint32_t aCharCode, Modifiers aModifiers, + Modifiers aModifiersMask, + const KeyboardScrollAction& aAction); + + /** + * Create a keyboard shortcut that when matched should be handled by ignoring + * the keyboard event and dispatching it to content. + */ + KeyboardShortcut(KeyboardInput::KeyboardEventType aEventType, + uint32_t aKeyCode, uint32_t aCharCode, Modifiers aModifiers, + Modifiers aModifiersMask); + + /** + * There are some default actions for keyboard inputs that are hardcoded in + * EventStateManager instead of being represented as XBL handlers. This adds + * keyboard shortcuts to match these inputs and dispatch them to content. + */ + static void AppendHardcodedShortcuts(nsTArray& aShortcuts); + + protected: + friend mozilla::layers::KeyboardMap; + + bool Matches(const KeyboardInput& aInput, const IgnoreModifierState& aIgnore, + uint32_t aOverrideCharCode = 0) const; + + private: + bool MatchesKey(const KeyboardInput& aInput, + uint32_t aOverrideCharCode) const; + bool MatchesModifiers(const KeyboardInput& aInput, + const IgnoreModifierState& aIgnore) const; + + public: + // The action to perform when this shortcut is matched, + // and not flagged to be dispatched to content + KeyboardScrollAction mAction; + + // Only one of mKeyCode or mCharCode may be non-zero + // whichever one is non-zero is the one to compare when matching + uint32_t mKeyCode; + uint32_t mCharCode; + + // The modifiers that must be active for this shortcut + Modifiers mModifiers; + // The modifiers to compare when matching this shortcut + Modifiers mModifiersMask; + + // The type of keyboard event to match against + KeyboardInput::KeyboardEventType mEventType; + + // Whether events matched by this must be dispatched to content + bool mDispatchToContent; +}; + +/** + * A keyboard map is an off main-thread for scrolling commands. + */ +class KeyboardMap final { + public: + KeyboardMap(); + explicit KeyboardMap(nsTArray&& aShortcuts); + + const nsTArray& Shortcuts() const { return mShortcuts; } + + /** + * Search through the internal list of shortcuts for a match for the input + * event + */ + Maybe FindMatch(const KeyboardInput& aEvent) const; + + private: + Maybe FindMatchInternal( + const KeyboardInput& aEvent, const IgnoreModifierState& aIgnore, + uint32_t aOverrideCharCode = 0) const; + + CopyableTArray mShortcuts; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_KeyboardMap_h diff --git a/gfx/layers/apz/src/KeyboardScrollAction.cpp b/gfx/layers/apz/src/KeyboardScrollAction.cpp new file mode 100644 index 0000000000..42d9a8bff2 --- /dev/null +++ b/gfx/layers/apz/src/KeyboardScrollAction.cpp @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/KeyboardScrollAction.h" + +namespace mozilla { +namespace layers { + +/* static */ ScrollUnit KeyboardScrollAction::GetScrollUnit( + KeyboardScrollAction::KeyboardScrollActionType aDeltaType) { + switch (aDeltaType) { + case KeyboardScrollAction::eScrollCharacter: + return ScrollUnit::LINES; + case KeyboardScrollAction::eScrollLine: + return ScrollUnit::LINES; + case KeyboardScrollAction::eScrollPage: + return ScrollUnit::PAGES; + case KeyboardScrollAction::eScrollComplete: + return ScrollUnit::WHOLE; + } + + // Silence an overzealous warning + return ScrollUnit::WHOLE; +} + +KeyboardScrollAction::KeyboardScrollAction() + : mType(KeyboardScrollAction::eScrollCharacter), mForward(false) {} + +KeyboardScrollAction::KeyboardScrollAction(KeyboardScrollActionType aType, + bool aForward) + : mType(aType), mForward(aForward) {} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/src/KeyboardScrollAction.h b/gfx/layers/apz/src/KeyboardScrollAction.h new file mode 100644 index 0000000000..780006c1b3 --- /dev/null +++ b/gfx/layers/apz/src/KeyboardScrollAction.h @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_KeyboardScrollAction_h +#define mozilla_layers_KeyboardScrollAction_h + +#include // for uint8_t + +#include "mozilla/ScrollTypes.h" +#include "mozilla/DefineEnum.h" // for MOZ_DEFINE_ENUM + +namespace mozilla { +namespace layers { + +/** + * This class represents a scrolling action to be performed on a scrollable + * layer. + */ +struct KeyboardScrollAction final { + public: + // clang-format off + MOZ_DEFINE_ENUM_WITH_BASE_AT_CLASS_SCOPE( + KeyboardScrollActionType, uint8_t, ( + eScrollCharacter, + eScrollLine, + eScrollPage, + eScrollComplete + )); + // clang-format on + + static ScrollUnit GetScrollUnit(KeyboardScrollActionType aDeltaType); + + KeyboardScrollAction(); + KeyboardScrollAction(KeyboardScrollActionType aType, bool aForward); + + // The type of scroll to perform for this action + KeyboardScrollActionType mType; + // Whether to scroll forward or backward along the axis of this action type + bool mForward; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_KeyboardScrollAction_h diff --git a/gfx/layers/apz/src/Overscroll.h b/gfx/layers/apz/src/Overscroll.h new file mode 100644 index 0000000000..b74dcdc782 --- /dev/null +++ b/gfx/layers/apz/src/Overscroll.h @@ -0,0 +1,137 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_Overscroll_h +#define mozilla_layers_Overscroll_h + +#include "AsyncPanZoomAnimation.h" +#include "AsyncPanZoomController.h" +#include "mozilla/TimeStamp.h" +#include "nsThreadUtils.h" + +namespace mozilla { +namespace layers { + +// Animation used by GenericOverscrollEffect. +class OverscrollAnimation : public AsyncPanZoomAnimation { + public: + OverscrollAnimation(AsyncPanZoomController& aApzc, + const ParentLayerPoint& aVelocity) + : mApzc(aApzc) { + mApzc.mX.StartOverscrollAnimation(aVelocity.x); + mApzc.mY.StartOverscrollAnimation(aVelocity.y); + } + virtual ~OverscrollAnimation() { + mApzc.mX.EndOverscrollAnimation(); + mApzc.mY.EndOverscrollAnimation(); + } + + virtual bool DoSample(FrameMetrics& aFrameMetrics, + const TimeDuration& aDelta) override { + // Can't inline these variables due to short-circuit evaluation. + bool continueX = mApzc.mX.SampleOverscrollAnimation(aDelta); + bool continueY = mApzc.mY.SampleOverscrollAnimation(aDelta); + if (!continueX && !continueY) { + // If we got into overscroll from a fling, that fling did not request a + // fling snap to avoid a resulting scrollTo from cancelling the overscroll + // animation too early. We do still want to request a fling snap, though, + // in case the end of the axis at which we're overscrolled is not a valid + // snap point, so we request one now. If there are no snap points, this + // will do nothing. If there are snap points, we'll get a scrollTo that + // snaps us back to the nearest valid snap point. The scroll snapping is + // done in a deferred task, otherwise the state change to NOTHING caused + // by the overscroll animation ending would clobber a possible state + // change to SMOOTH_SCROLL in ScrollSnap(). + mDeferredTasks.AppendElement( + NewRunnableMethod("layers::AsyncPanZoomController::ScrollSnap", + &mApzc, &AsyncPanZoomController::ScrollSnap)); + return false; + } + return true; + } + + virtual bool WantsRepaints() override { return false; } + + private: + AsyncPanZoomController& mApzc; +}; + +// Base class for different overscroll effects; +class OverscrollEffectBase { + public: + virtual ~OverscrollEffectBase() = default; + virtual void ConsumeOverscroll(ParentLayerPoint& aOverscroll, + bool aShouldOverscrollX, + bool aShouldOverscrollY) = 0; + virtual void HandleFlingOverscroll(const ParentLayerPoint& aVelocity) = 0; +}; + +// A generic overscroll effect, implemented by AsyncPanZoomController itself. +class GenericOverscrollEffect : public OverscrollEffectBase { + public: + explicit GenericOverscrollEffect(AsyncPanZoomController& aApzc) + : mApzc(aApzc) {} + + void ConsumeOverscroll(ParentLayerPoint& aOverscroll, bool aShouldOverscrollX, + bool aShouldOverscrollY) override { + if (aShouldOverscrollX) { + mApzc.mX.OverscrollBy(aOverscroll.x); + aOverscroll.x = 0; + } + + if (aShouldOverscrollY) { + mApzc.mY.OverscrollBy(aOverscroll.y); + aOverscroll.y = 0; + } + + if (aShouldOverscrollX || aShouldOverscrollY) { + mApzc.ScheduleComposite(); + } + } + + void HandleFlingOverscroll(const ParentLayerPoint& aVelocity) override { + mApzc.StartOverscrollAnimation(aVelocity); + } + + private: + AsyncPanZoomController& mApzc; +}; + +// A widget-specific overscroll effect, implemented by the widget via +// GeckoContentController. +class WidgetOverscrollEffect : public OverscrollEffectBase { + public: + explicit WidgetOverscrollEffect(AsyncPanZoomController& aApzc) + : mApzc(aApzc) {} + + void ConsumeOverscroll(ParentLayerPoint& aOverscroll, bool aShouldOverscrollX, + bool aShouldOverscrollY) override { + RefPtr controller = + mApzc.GetGeckoContentController(); + if (controller && (aShouldOverscrollX || aShouldOverscrollY)) { + controller->UpdateOverscrollOffset(mApzc.GetGuid(), aOverscroll.x, + aOverscroll.y, mApzc.IsRootContent()); + aOverscroll = ParentLayerPoint(); + } + } + + void HandleFlingOverscroll(const ParentLayerPoint& aVelocity) override { + RefPtr controller = + mApzc.GetGeckoContentController(); + if (controller) { + controller->UpdateOverscrollVelocity(mApzc.GetGuid(), aVelocity.x, + aVelocity.y, mApzc.IsRootContent()); + } + } + + private: + AsyncPanZoomController& mApzc; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_Overscroll_h diff --git a/gfx/layers/apz/src/OverscrollHandoffState.cpp b/gfx/layers/apz/src/OverscrollHandoffState.cpp new file mode 100644 index 0000000000..c8575ef68b --- /dev/null +++ b/gfx/layers/apz/src/OverscrollHandoffState.cpp @@ -0,0 +1,178 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "OverscrollHandoffState.h" + +#include // for std::stable_sort +#include "mozilla/Assertions.h" +#include "AsyncPanZoomController.h" + +namespace mozilla { +namespace layers { + +OverscrollHandoffChain::~OverscrollHandoffChain() = default; + +void OverscrollHandoffChain::Add(AsyncPanZoomController* aApzc) { + mChain.push_back(aApzc); +} + +struct CompareByScrollPriority { + bool operator()(const RefPtr& a, + const RefPtr& b) const { + return a->HasScrollgrab() && !b->HasScrollgrab(); + } +}; + +void OverscrollHandoffChain::SortByScrollPriority() { + // The sorting being stable ensures that the relative order between + // non-scrollgrabbing APZCs remains child -> parent. + // (The relative order between scrollgrabbing APZCs will also remain + // child -> parent, though that's just an artefact of the implementation + // and users of 'scrollgrab' should not rely on this.) + std::stable_sort(mChain.begin(), mChain.end(), CompareByScrollPriority()); +} + +const RefPtr& OverscrollHandoffChain::GetApzcAtIndex( + uint32_t aIndex) const { + MOZ_ASSERT(aIndex < Length()); + return mChain[aIndex]; +} + +uint32_t OverscrollHandoffChain::IndexOf( + const AsyncPanZoomController* aApzc) const { + uint32_t i; + for (i = 0; i < Length(); ++i) { + if (mChain[i] == aApzc) { + break; + } + } + return i; +} + +void OverscrollHandoffChain::ForEachApzc(APZCMethod aMethod) const { + for (uint32_t i = 0; i < Length(); ++i) { + (mChain[i]->*aMethod)(); + } +} + +bool OverscrollHandoffChain::AnyApzc(APZCPredicate aPredicate) const { + MOZ_ASSERT(Length() > 0); + for (uint32_t i = 0; i < Length(); ++i) { + if ((mChain[i]->*aPredicate)()) { + return true; + } + } + return false; +} + +void OverscrollHandoffChain::FlushRepaints() const { + ForEachApzc(&AsyncPanZoomController::FlushRepaintForOverscrollHandoff); +} + +void OverscrollHandoffChain::CancelAnimations( + CancelAnimationFlags aFlags) const { + MOZ_ASSERT(Length() > 0); + for (uint32_t i = 0; i < Length(); ++i) { + mChain[i]->CancelAnimation(aFlags); + } +} + +void OverscrollHandoffChain::ClearOverscroll() const { + ForEachApzc(&AsyncPanZoomController::ClearOverscroll); +} + +void OverscrollHandoffChain::SnapBackOverscrolledApzc( + const AsyncPanZoomController* aStart) const { + uint32_t i = IndexOf(aStart); + for (; i < Length(); ++i) { + AsyncPanZoomController* apzc = mChain[i]; + if (!apzc->IsDestroyed()) { + apzc->SnapBackIfOverscrolled(); + } + } +} + +bool OverscrollHandoffChain::CanBePanned( + const AsyncPanZoomController* aApzc) const { + // Find |aApzc| in the handoff chain. + uint32_t i = IndexOf(aApzc); + + // See whether any APZC in the handoff chain starting from |aApzc| + // has room to be panned. + for (uint32_t j = i; j < Length(); ++j) { + if (mChain[j]->IsPannable()) { + return true; + } + } + + return false; +} + +bool OverscrollHandoffChain::CanScrollInDirection( + const AsyncPanZoomController* aApzc, ScrollDirection aDirection) const { + // Find |aApzc| in the handoff chain. + uint32_t i = IndexOf(aApzc); + + // See whether any APZC in the handoff chain starting from |aApzc| + // has room to scroll in the given direction. + for (uint32_t j = i; j < Length(); ++j) { + if (mChain[j]->CanScroll(aDirection)) { + return true; + } + } + + return false; +} + +bool OverscrollHandoffChain::HasOverscrolledApzc() const { + return AnyApzc(&AsyncPanZoomController::IsOverscrolled); +} + +bool OverscrollHandoffChain::HasFastFlungApzc() const { + return AnyApzc(&AsyncPanZoomController::IsFlingingFast); +} + +RefPtr OverscrollHandoffChain::FindFirstScrollable( + const InputData& aInput, + ScrollDirections* aOutAllowedScrollDirections) const { + // Start by allowing scrolling in both directions. As we do handoff + // overscroll-behavior may restrict one or both of the directions. + *aOutAllowedScrollDirections += ScrollDirection::eVertical; + *aOutAllowedScrollDirections += ScrollDirection::eHorizontal; + + for (size_t i = 0; i < Length(); i++) { + if (mChain[i]->CanScroll(aInput)) { + return mChain[i]; + } + + *aOutAllowedScrollDirections &= mChain[i]->GetAllowedHandoffDirections(); + if (aOutAllowedScrollDirections->isEmpty()) { + return nullptr; + } + } + return nullptr; +} + +bool OverscrollHandoffChain::ScrollingDownWillMoveDynamicToolbar( + const AsyncPanZoomController* aApzc) const { + MOZ_ASSERT(aApzc && !aApzc->IsRootContent(), + "Should be used for non-root APZC"); + + for (uint32_t i = IndexOf(aApzc); i < Length(); i++) { + if (mChain[i]->IsRootContent()) { + return mChain[i]->CanScrollDownwardsWithDynamicToolbar(); + } + + if (mChain[i]->CanScrollDownwards()) { + return false; + } + } + + return false; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/src/OverscrollHandoffState.h b/gfx/layers/apz/src/OverscrollHandoffState.h new file mode 100644 index 0000000000..ec29e0a97c --- /dev/null +++ b/gfx/layers/apz/src/OverscrollHandoffState.h @@ -0,0 +1,182 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_OverscrollHandoffChain_h +#define mozilla_layers_OverscrollHandoffChain_h + +#include +#include "mozilla/RefPtr.h" // for RefPtr +#include "nsISupportsImpl.h" // for NS_INLINE_DECL_THREADSAFE_REFCOUNTING +#include "APZUtils.h" // for CancelAnimationFlags +#include "mozilla/layers/LayersTypes.h" // for Layer::ScrollDirection +#include "Units.h" // for ScreenPoint + +namespace mozilla { + +class InputData; + +namespace layers { + +class AsyncPanZoomController; + +/** + * This class represents the chain of APZCs along which overscroll is handed + * off. It is created by APZCTreeManager by starting from an initial APZC which + * is the target for input events, and following the scroll parent ID links + * (often but not always corresponding to parent pointers in the APZC tree), + * then adjusting for scrollgrab. + */ +class OverscrollHandoffChain { + protected: + // Reference-counted classes cannot have public destructors. + ~OverscrollHandoffChain(); + + public: + // Threadsafe so that the controller and sampler threads can both maintain + // nsRefPtrs to the same handoff chain. + // Mutable so that we can pass around the class by + // RefPtr and thus enforce that, once built, + // the chain is not modified. + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(OverscrollHandoffChain) + + /* + * Methods for building the handoff chain. + * These should be used only by + * AsyncPanZoomController::BuildOverscrollHandoffChain(). + */ + void Add(AsyncPanZoomController* aApzc); + void SortByScrollPriority(); + + /* + * Methods for accessing the handoff chain. + */ + uint32_t Length() const { return mChain.size(); } + const RefPtr& GetApzcAtIndex(uint32_t aIndex) const; + // Returns Length() if |aApzc| is not on this chain. + uint32_t IndexOf(const AsyncPanZoomController* aApzc) const; + + /* + * Convenience methods for performing operations on APZCs in the chain. + */ + + // Flush repaints all the way up the chain. + void FlushRepaints() const; + + // Cancel animations all the way up the chain. + void CancelAnimations(CancelAnimationFlags aFlags = Default) const; + + // Clear overscroll all the way up the chain. + void ClearOverscroll() const; + + // Snap back the APZC that is overscrolled on the subset of the chain from + // |aStart| onwards, if any. + void SnapBackOverscrolledApzc(const AsyncPanZoomController* aStart) const; + + // Determine whether the given APZC, or any APZC further in the chain, + // has room to be panned. + bool CanBePanned(const AsyncPanZoomController* aApzc) const; + + // Determine whether the given APZC, or any APZC further in the chain, + // can scroll in the given direction. + bool CanScrollInDirection(const AsyncPanZoomController* aApzc, + ScrollDirection aDirection) const; + + // Determine whether any APZC along this handoff chain is overscrolled. + bool HasOverscrolledApzc() const; + + // Determine whether any APZC along this handoff chain has been flung fast. + bool HasFastFlungApzc() const; + + // Find the first APZC in this handoff chain that can be scrolled by |aInput|. + // Since overscroll-behavior can restrict handoff in some directions, + // |aOutAllowedScrollDirections| is populated with the scroll directions + // in which scrolling of the returned APZC is allowed. + RefPtr FindFirstScrollable( + const InputData& aInput, + ScrollDirections* aOutAllowedScrollDirections) const; + + // Return true if all non-root APZCs in this handoff chain starting from + // |aApzc| are not able to scroll downwards (i.e. there is no room to scroll + // downwards in each APZC respectively) and there is any contents covered by + // the dynamic toolbar. + bool ScrollingDownWillMoveDynamicToolbar( + const AsyncPanZoomController* aApzc) const; + + private: + std::vector> mChain; + + typedef void (AsyncPanZoomController::*APZCMethod)(); + typedef bool (AsyncPanZoomController::*APZCPredicate)() const; + void ForEachApzc(APZCMethod aMethod) const; + bool AnyApzc(APZCPredicate aPredicate) const; +}; + +/** + * This class groups the state maintained during overscroll handoff. + */ +struct OverscrollHandoffState { + OverscrollHandoffState(const OverscrollHandoffChain& aChain, + const ScreenPoint& aPanDistance, + ScrollSource aScrollSource) + : mChain(aChain), + mChainIndex(0), + mPanDistance(aPanDistance), + mScrollSource(aScrollSource) {} + + // The chain of APZCs along which we hand off scroll. + // This is const to indicate that the chain does not change over the + // course of handoff. + const OverscrollHandoffChain& mChain; + + // The index of the APZC in the chain that we are currently giving scroll to. + // This is non-const to indicate that this changes over the course of handoff. + uint32_t mChainIndex; + + // The total distance since touch-start of the pan that triggered the + // handoff. This is const to indicate that it does not change over the + // course of handoff. + // The x/y components of this are non-negative. + const ScreenPoint mPanDistance; + + ScrollSource mScrollSource; +}; + +/* + * This class groups the state maintained during fling handoff. + */ +struct FlingHandoffState { + // The velocity of the fling being handed off. + ParentLayerPoint mVelocity; + + // The chain of APZCs along which we hand off the fling. + // Unlike in OverscrollHandoffState, this is stored by RefPtr because + // otherwise it may not stay alive for the entire handoff. + RefPtr mChain; + + // The time duration between the touch start and the touch move that started + // the pan gesture which triggered this fling. In other words, the time it + // took for the finger to move enough to cross the touch slop threshold. + // Nothing if this fling was not immediately caused by a touch pan. + Maybe mTouchStartRestingTime; + + // The slowest panning velocity encountered during the pan that triggered this + // fling. + ParentLayerCoord mMinPanVelocity; + + // Whether handoff has happened by this point, or we're still process + // the original fling. + bool mIsHandoff; + + // The single APZC that was scrolled by the pan that started this fling. + // The fling is only allowed to scroll this APZC, too. + // Used only if immediate scroll handoff is disallowed. + RefPtr mScrolledApzc; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* mozilla_layers_OverscrollHandoffChain_h */ diff --git a/gfx/layers/apz/src/OvershootDetector.cpp b/gfx/layers/apz/src/OvershootDetector.cpp new file mode 100644 index 0000000000..c713ce8d1a --- /dev/null +++ b/gfx/layers/apz/src/OvershootDetector.cpp @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "OvershootDetector.h" + +#include "InputData.h" +#include "mozilla/Telemetry.h" // for Telemetry + +namespace mozilla { +namespace layers { + +const TimeDuration kOvershootInterval = TimeDuration::FromMilliseconds(500); + +static Maybe GetScrollDirection(const ScrollWheelInput& aInput) { + // If the wheel input is scrolling on both axes, we just take the y-axis. + // This is mostly out of laziness, because this code is for experiment + // purposes and just needs to handle the cases that will dominate in the data. + if (aInput.mDeltaY > 0) { + return Some(Side::eSideBottom); + } + if (aInput.mDeltaY < 0) { + return Some(Side::eSideTop); + } + if (aInput.mDeltaX > 0) { + return Some(Side::eSideRight); + } + if (aInput.mDeltaX < 0) { + return Some(Side::eSideLeft); + } + return Nothing(); +} + +void OvershootDetector::Update(const ScrollWheelInput& aInput) { + TimeStamp inputTime = aInput.mTimeStamp; + Maybe inputDirection = GetScrollDirection(aInput); + if (mLastTimeStamp && (inputTime - mLastTimeStamp) < kOvershootInterval && + mLastDirection && inputDirection) { + // The new input we got happened within the kOvershootInterval of the last + // input. Let's see if the direction is reversed; if so, we accumulate to + // telemetry. + bool reversed = false; + switch (*mLastDirection) { + case Side::eSideBottom: + reversed = (*inputDirection == Side::eSideTop); + break; + case Side::eSideTop: + reversed = (*inputDirection == Side::eSideBottom); + break; + case Side::eSideRight: + reversed = (*inputDirection == Side::eSideLeft); + break; + case Side::eSideLeft: + reversed = (*inputDirection == Side::eSideRight); + break; + } + if (reversed) { + Telemetry::ScalarAdd(Telemetry::ScalarID::APZ_SCROLLWHEEL_OVERSHOOT, 1); + } + } + + mLastTimeStamp = inputTime; + mLastDirection = inputDirection; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/src/OvershootDetector.h b/gfx/layers/apz/src/OvershootDetector.h new file mode 100644 index 0000000000..670c848af1 --- /dev/null +++ b/gfx/layers/apz/src/OvershootDetector.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_OvershootDetector_h_ +#define mozilla_layers_OvershootDetector_h_ + +#include "mozilla/gfx/Types.h" // for Side +#include "mozilla/Maybe.h" +#include "mozilla/TimeStamp.h" + +namespace mozilla { + +class ScrollWheelInput; + +namespace layers { + +class OvershootDetector { + public: + OvershootDetector() = default; + + // Updates the internal state machine with a new incoming scrollwheel input. + void Update(const ScrollWheelInput& aInput); + + private: + TimeStamp mLastTimeStamp; + Maybe mLastDirection; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_OvershootDetector_h_ diff --git a/gfx/layers/apz/src/PotentialCheckerboardDurationTracker.cpp b/gfx/layers/apz/src/PotentialCheckerboardDurationTracker.cpp new file mode 100644 index 0000000000..9b3e6390b8 --- /dev/null +++ b/gfx/layers/apz/src/PotentialCheckerboardDurationTracker.cpp @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PotentialCheckerboardDurationTracker.h" + +#include "mozilla/Telemetry.h" // for Telemetry + +namespace mozilla { +namespace layers { + +PotentialCheckerboardDurationTracker::PotentialCheckerboardDurationTracker() + : mInCheckerboard(false), mInTransform(false) {} + +void PotentialCheckerboardDurationTracker::CheckerboardSeen() { + // This might get called while mInCheckerboard is already true + if (!Tracking()) { + mCurrentPeriodStart = TimeStamp::Now(); + } + mInCheckerboard = true; +} + +void PotentialCheckerboardDurationTracker::CheckerboardDone() { + MOZ_ASSERT(Tracking()); + mInCheckerboard = false; + if (!Tracking()) { + mozilla::Telemetry::AccumulateTimeDelta( + mozilla::Telemetry::CHECKERBOARD_POTENTIAL_DURATION, + mCurrentPeriodStart); + } +} + +void PotentialCheckerboardDurationTracker::InTransform(bool aInTransform) { + if (aInTransform == mInTransform) { + // no-op + return; + } + + if (!Tracking()) { + // Because !Tracking(), mInTransform must be false, and so aInTransform + // must be true (or we would have early-exited this function already). + // Therefore, we are starting a potential checkerboard period. + mInTransform = aInTransform; + mCurrentPeriodStart = TimeStamp::Now(); + return; + } + + mInTransform = aInTransform; + + if (!Tracking()) { + // Tracking() must have been true at the start of this function, or we + // would have taken the other !Tracking branch above. If it's false now, + // it means we just stopped tracking, so we are ending a potential + // checkerboard period. + mozilla::Telemetry::AccumulateTimeDelta( + mozilla::Telemetry::CHECKERBOARD_POTENTIAL_DURATION, + mCurrentPeriodStart); + } +} + +bool PotentialCheckerboardDurationTracker::Tracking() const { + return mInTransform || mInCheckerboard; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/src/PotentialCheckerboardDurationTracker.h b/gfx/layers/apz/src/PotentialCheckerboardDurationTracker.h new file mode 100644 index 0000000000..2b8b1b155b --- /dev/null +++ b/gfx/layers/apz/src/PotentialCheckerboardDurationTracker.h @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_PotentialCheckerboardDurationTracker_h +#define mozilla_layers_PotentialCheckerboardDurationTracker_h + +#include "mozilla/TimeStamp.h" + +namespace mozilla { +namespace layers { + +/** + * This class allows the owner to track the duration of time considered + * "potentially checkerboarding". This is the union of two possibly-intersecting + * sets of time periods. The first set is that in which checkerboarding was + * actually happening, since by definition it could potentially be happening. + * The second set is that in which the APZC is actively transforming content + * in the compositor, since it could potentially transform it so as to display + * checkerboarding to the user. + * The caller of this class calls the appropriate methods to indicate the start + * and stop of these two sets, and this class manages accumulating the union + * of the various durations. + */ +class PotentialCheckerboardDurationTracker { + public: + PotentialCheckerboardDurationTracker(); + + /** + * This should be called if checkerboarding is encountered. It can be called + * multiple times during a checkerboard event. + */ + void CheckerboardSeen(); + /** + * This should be called when checkerboarding is done. It must have been + * preceded by one or more calls to CheckerboardSeen(). + */ + void CheckerboardDone(); + + /** + * This should be called at composition time, to indicate if the APZC is in + * a transforming state or not. + */ + void InTransform(bool aInTransform); + + private: + bool Tracking() const; + + private: + bool mInCheckerboard; + bool mInTransform; + + TimeStamp mCurrentPeriodStart; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_PotentialCheckerboardDurationTracker_h diff --git a/gfx/layers/apz/src/QueuedInput.cpp b/gfx/layers/apz/src/QueuedInput.cpp new file mode 100644 index 0000000000..87ffe7250e --- /dev/null +++ b/gfx/layers/apz/src/QueuedInput.cpp @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "QueuedInput.h" + +#include "AsyncPanZoomController.h" +#include "InputBlockState.h" +#include "InputData.h" +#include "OverscrollHandoffState.h" + +namespace mozilla { +namespace layers { + +QueuedInput::QueuedInput(const MultiTouchInput& aInput, TouchBlockState& aBlock) + : mInput(MakeUnique(aInput)), mBlock(&aBlock) {} + +QueuedInput::QueuedInput(const ScrollWheelInput& aInput, + WheelBlockState& aBlock) + : mInput(MakeUnique(aInput)), mBlock(&aBlock) {} + +QueuedInput::QueuedInput(const MouseInput& aInput, DragBlockState& aBlock) + : mInput(MakeUnique(aInput)), mBlock(&aBlock) {} + +QueuedInput::QueuedInput(const PanGestureInput& aInput, + PanGestureBlockState& aBlock) + : mInput(MakeUnique(aInput)), mBlock(&aBlock) {} + +QueuedInput::QueuedInput(const PinchGestureInput& aInput, + PinchGestureBlockState& aBlock) + : mInput(MakeUnique(aInput)), mBlock(&aBlock) {} + +QueuedInput::QueuedInput(const KeyboardInput& aInput, + KeyboardBlockState& aBlock) + : mInput(MakeUnique(aInput)), mBlock(&aBlock) {} + +InputData* QueuedInput::Input() { return mInput.get(); } + +InputBlockState* QueuedInput::Block() { return mBlock.get(); } + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/src/QueuedInput.h b/gfx/layers/apz/src/QueuedInput.h new file mode 100644 index 0000000000..fcc2f2090a --- /dev/null +++ b/gfx/layers/apz/src/QueuedInput.h @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_QueuedInput_h +#define mozilla_layers_QueuedInput_h + +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { + +class InputData; +class MultiTouchInput; +class ScrollWheelInput; +class MouseInput; +class PanGestureInput; +class PinchGestureInput; +class KeyboardInput; + +namespace layers { + +class InputBlockState; +class TouchBlockState; +class WheelBlockState; +class DragBlockState; +class PanGestureBlockState; +class PinchGestureBlockState; +class KeyboardBlockState; + +/** + * This lightweight class holds a pointer to an input event that has not yet + * been completely processed, along with the input block that the input event + * is associated with. + */ +class QueuedInput { + public: + QueuedInput(const MultiTouchInput& aInput, TouchBlockState& aBlock); + QueuedInput(const ScrollWheelInput& aInput, WheelBlockState& aBlock); + QueuedInput(const MouseInput& aInput, DragBlockState& aBlock); + QueuedInput(const PanGestureInput& aInput, PanGestureBlockState& aBlock); + QueuedInput(const PinchGestureInput& aInput, PinchGestureBlockState& aBlock); + QueuedInput(const KeyboardInput& aInput, KeyboardBlockState& aBlock); + + InputData* Input(); + InputBlockState* Block(); + + private: + // A copy of the input event that is provided to the constructor. This must + // be non-null, and is owned by this QueuedInput instance (hence the + // UniquePtr). + UniquePtr mInput; + // A pointer to the block that the input event is associated with. This must + // be non-null. + RefPtr mBlock; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_QueuedInput_h diff --git a/gfx/layers/apz/src/RecentEventsBuffer.h b/gfx/layers/apz/src/RecentEventsBuffer.h new file mode 100644 index 0000000000..d1ae5797af --- /dev/null +++ b/gfx/layers/apz/src/RecentEventsBuffer.h @@ -0,0 +1,83 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_RecentEventsBuffer_h +#define mozilla_layers_RecentEventsBuffer_h + +#include + +#include "mozilla/TimeStamp.h" + +namespace mozilla { +namespace layers { +/** + * RecentEventsBuffer: maintains an age constrained buffer of events + * + * Intended for use with elements of type InputData, but the only requirement + * is a member "mTimeStamp" of type TimeStamp + */ +template +class RecentEventsBuffer { + public: + explicit RecentEventsBuffer(TimeDuration maxAge); + + void push(Event event); + void clear(); + + typedef typename std::deque::size_type size_type; + size_type size() { return mBuffer.size(); } + + // Delegate to container for iterators + typedef typename std::deque::iterator iterator; + typedef typename std::deque::const_iterator const_iterator; + iterator begin() { return mBuffer.begin(); } + iterator end() { return mBuffer.end(); } + const_iterator cbegin() const { return mBuffer.cbegin(); } + const_iterator cend() const { return mBuffer.cend(); } + + // Also delegate for front/back + typedef typename std::deque::reference reference; + typedef typename std::deque::const_reference const_reference; + reference front() { return mBuffer.front(); } + reference back() { return mBuffer.back(); } + const_reference front() const { return mBuffer.front(); } + const_reference back() const { return mBuffer.back(); } + + private: + TimeDuration mMaxAge; + std::deque mBuffer; +}; + +template +RecentEventsBuffer::RecentEventsBuffer(TimeDuration maxAge) + : mMaxAge(maxAge), mBuffer() {} + +template +void RecentEventsBuffer::push(Event event) { + // Events must be pushed in chronological order + MOZ_ASSERT(mBuffer.empty() || mBuffer.back().mTimeStamp <= event.mTimeStamp); + + mBuffer.push_back(event); + + // Flush all events older than the given lifetime + TimeStamp bound = event.mTimeStamp - mMaxAge; + while (!mBuffer.empty()) { + if (mBuffer.front().mTimeStamp >= bound) { + break; + } + mBuffer.pop_front(); + } +} + +template +void RecentEventsBuffer::clear() { + mBuffer.clear(); +} + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_RecentEventsBuffer_h diff --git a/gfx/layers/apz/src/SampledAPZCState.cpp b/gfx/layers/apz/src/SampledAPZCState.cpp new file mode 100644 index 0000000000..b90f60d4e0 --- /dev/null +++ b/gfx/layers/apz/src/SampledAPZCState.cpp @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SampledAPZCState.h" +#include "APZUtils.h" + +namespace mozilla { +namespace layers { + +SampledAPZCState::SampledAPZCState() {} + +SampledAPZCState::SampledAPZCState(const FrameMetrics& aMetrics) + : mLayoutViewport(aMetrics.GetLayoutViewport()), + mVisualScrollOffset(aMetrics.GetVisualScrollOffset()), + mZoom(aMetrics.GetZoom()) { + RemoveFractionalAsyncDelta(); +} + +SampledAPZCState::SampledAPZCState(const FrameMetrics& aMetrics, + Maybe&& aPayload) + : mLayoutViewport(aMetrics.GetLayoutViewport()), + mVisualScrollOffset(aMetrics.GetVisualScrollOffset()), + mZoom(aMetrics.GetZoom()), + mScrollPayload(std::move(aPayload)) { + RemoveFractionalAsyncDelta(); +} + +bool SampledAPZCState::operator==(const SampledAPZCState& aOther) const { + // The payload doesn't factor into equality, that just comes along for + // the ride. + return mLayoutViewport.IsEqualEdges(aOther.mLayoutViewport) && + mVisualScrollOffset == aOther.mVisualScrollOffset && + mZoom == aOther.mZoom; +} + +bool SampledAPZCState::operator!=(const SampledAPZCState& aOther) const { + return !(*this == aOther); +} + +Maybe SampledAPZCState::TakeScrollPayload() { + return std::move(mScrollPayload); +} + +void SampledAPZCState::UpdateScrollProperties(const FrameMetrics& aMetrics) { + mLayoutViewport = aMetrics.GetLayoutViewport(); + mVisualScrollOffset = aMetrics.GetVisualScrollOffset(); +} + +void SampledAPZCState::UpdateZoomProperties(const FrameMetrics& aMetrics) { + mZoom = aMetrics.GetZoom(); +} + +void SampledAPZCState::ClampVisualScrollOffset(const FrameMetrics& aMetrics) { + mVisualScrollOffset = + aMetrics.CalculateScrollRange().ClampPoint(mVisualScrollOffset); + FrameMetrics::KeepLayoutViewportEnclosingVisualViewport( + CSSRect(mVisualScrollOffset, + aMetrics.CalculateCompositedSizeInCssPixels()), + aMetrics.GetScrollableRect(), mLayoutViewport); +} + +void SampledAPZCState::ZoomBy(const gfxSize& aScale) { + mZoom.xScale *= aScale.width; + mZoom.yScale *= aScale.height; +} + +void SampledAPZCState::RemoveFractionalAsyncDelta() { + // This function is a performance hack. With non-WebRender, having small + // fractional deltas between the layout offset and scroll offset on + // container layers can trigger the creation of a temporary surface during + // composition, because it produces a non-integer translation that doesn't + // play well with layer clips. So we detect the case where the delta is + // uselessly small (0.01 parentlayer pixels or less) and tweak the sampled + // scroll offset to eliminate it. By doing this here at sample time rather + // than elsewhere in the pipeline we are least likely to break assumptions + // and invariants elsewhere in the code, since sampling effectively takes + // a snapshot of APZ state (decoupling it from APZ assumptions) and provides + // it as an input to the compositor (so all compositor state should be + // internally consistent based on this input). + if (mLayoutViewport.TopLeft() == mVisualScrollOffset) { + return; + } + ParentLayerPoint paintedOffset = mLayoutViewport.TopLeft() * mZoom; + ParentLayerPoint asyncOffset = mVisualScrollOffset * mZoom; + if (FuzzyEqualsAdditive(paintedOffset.x, asyncOffset.x, COORDINATE_EPSILON) && + FuzzyEqualsAdditive(paintedOffset.y, asyncOffset.y, COORDINATE_EPSILON)) { + mVisualScrollOffset = mLayoutViewport.TopLeft(); + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/src/SampledAPZCState.h b/gfx/layers/apz/src/SampledAPZCState.h new file mode 100644 index 0000000000..8e432aae5d --- /dev/null +++ b/gfx/layers/apz/src/SampledAPZCState.h @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_SampledAPZCState_h +#define mozilla_layers_SampledAPZCState_h + +#include "FrameMetrics.h" +#include "mozilla/Maybe.h" + +namespace mozilla { +namespace layers { + +class SampledAPZCState { + public: + SampledAPZCState(); + explicit SampledAPZCState(const FrameMetrics& aMetrics); + explicit SampledAPZCState(const FrameMetrics& aMetrics, + Maybe&& aPayload); + + bool operator==(const SampledAPZCState& aOther) const; + bool operator!=(const SampledAPZCState& aOther) const; + + CSSRect GetLayoutViewport() const { return mLayoutViewport; } + CSSPoint GetVisualScrollOffset() const { return mVisualScrollOffset; } + CSSToParentLayerScale2D GetZoom() const { return mZoom; } + Maybe TakeScrollPayload(); + + void UpdateScrollProperties(const FrameMetrics& aMetrics); + void UpdateZoomProperties(const FrameMetrics& aMetrics); + + /** + * Re-clamp mVisualScrollOffset to the scroll range specified by the provided + * metrics. This only needs to be called if the scroll offset changes + * outside of AsyncPanZoomController::SampleCompositedAsyncTransform(). + * It also recalculates mLayoutViewport so that it continues to enclose + * the visual viewport. This only needs to be called if the + * layout viewport changes outside of SampleCompositedAsyncTransform(). + */ + void ClampVisualScrollOffset(const FrameMetrics& aMetrics); + + void ZoomBy(const gfxSize& aScale); + + private: + // These variables cache the layout viewport, scroll offset, and zoom stored + // in |Metrics()| at the time this class was constructed. + CSSRect mLayoutViewport; + CSSPoint mVisualScrollOffset; + CSSToParentLayerScale2D mZoom; + // An optional payload that rides along with the sampled state. + Maybe mScrollPayload; + + void RemoveFractionalAsyncDelta(); +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_SampledAPZCState_h diff --git a/gfx/layers/apz/src/SimpleVelocityTracker.cpp b/gfx/layers/apz/src/SimpleVelocityTracker.cpp new file mode 100644 index 0000000000..8c88690cd5 --- /dev/null +++ b/gfx/layers/apz/src/SimpleVelocityTracker.cpp @@ -0,0 +1,135 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SimpleVelocityTracker.h" + +#include "mozilla/ComputedTimingFunction.h" // for ComputedTimingFunction +#include "mozilla/StaticPrefs_apz.h" +#include "mozilla/StaticPtr.h" // for StaticAutoPtr + +static mozilla::LazyLogModule sApzSvtLog("apz.simplevelocitytracker"); +#define SVT_LOG(...) MOZ_LOG(sApzSvtLog, LogLevel::Debug, (__VA_ARGS__)) + +namespace mozilla { +namespace layers { + +// When we compute the velocity we do so by taking two input events and +// dividing the distance delta over the time delta. In some cases the time +// delta can be really small, which can make the velocity computation very +// volatile. To avoid this we impose a minimum time delta below which we do +// not recompute the velocity. +const TimeDuration MIN_VELOCITY_SAMPLE_TIME = TimeDuration::FromMilliseconds(5); + +extern StaticAutoPtr gVelocityCurveFunction; + +SimpleVelocityTracker::SimpleVelocityTracker(Axis* aAxis) + : mAxis(aAxis), mVelocitySamplePos(0) {} + +void SimpleVelocityTracker::StartTracking(ParentLayerCoord aPos, + TimeStamp aTimestamp) { + Clear(); + mVelocitySampleTime = aTimestamp; + mVelocitySamplePos = aPos; +} + +Maybe SimpleVelocityTracker::AddPosition(ParentLayerCoord aPos, + TimeStamp aTimestamp) { + if (aTimestamp <= mVelocitySampleTime + MIN_VELOCITY_SAMPLE_TIME) { + // See also the comment on MIN_VELOCITY_SAMPLE_TIME. + // We don't update either mVelocitySampleTime or mVelocitySamplePos so that + // eventually when we do get an event with the required time delta we use + // the corresponding distance delta as well. + SVT_LOG("%p|%s skipping velocity computation for small time delta %f ms\n", + mAxis->OpaqueApzcPointer(), mAxis->Name(), + (aTimestamp - mVelocitySampleTime).ToMilliseconds()); + return Nothing(); + } + + float newVelocity = + (float)(mVelocitySamplePos - aPos) / + (float)(aTimestamp - mVelocitySampleTime).ToMilliseconds(); + + newVelocity = ApplyFlingCurveToVelocity(newVelocity); + + SVT_LOG("%p|%s updating velocity to %f with touch\n", + mAxis->OpaqueApzcPointer(), mAxis->Name(), newVelocity); + mVelocitySampleTime = aTimestamp; + mVelocitySamplePos = aPos; + + AddVelocityToQueue(aTimestamp, newVelocity); + + return Some(newVelocity); +} + +Maybe SimpleVelocityTracker::ComputeVelocity(TimeStamp aTimestamp) { + float velocity = 0; + int count = 0; + for (const auto& e : mVelocityQueue) { + TimeDuration timeDelta = (aTimestamp - e.first); + if (timeDelta < TimeDuration::FromMilliseconds( + StaticPrefs::apz_velocity_relevance_time_ms())) { + count++; + velocity += e.second; + } + } + mVelocityQueue.Clear(); + if (count > 1) { + velocity /= count; + } + return Some(velocity); +} + +void SimpleVelocityTracker::Clear() { mVelocityQueue.Clear(); } + +void SimpleVelocityTracker::AddVelocityToQueue(TimeStamp aTimestamp, + float aVelocity) { + mVelocityQueue.AppendElement(std::make_pair(aTimestamp, aVelocity)); + if (mVelocityQueue.Length() > + StaticPrefs::apz_max_velocity_queue_size_AtStartup()) { + mVelocityQueue.RemoveElementAt(0); + } +} + +float SimpleVelocityTracker::ApplyFlingCurveToVelocity(float aVelocity) const { + float newVelocity = aVelocity; + if (StaticPrefs::apz_max_velocity_inches_per_ms() > 0.0f) { + bool velocityIsNegative = (newVelocity < 0); + newVelocity = fabs(newVelocity); + + float maxVelocity = + mAxis->ToLocalVelocity(StaticPrefs::apz_max_velocity_inches_per_ms()); + newVelocity = std::min(newVelocity, maxVelocity); + + if (StaticPrefs::apz_fling_curve_threshold_inches_per_ms() > 0.0f && + StaticPrefs::apz_fling_curve_threshold_inches_per_ms() < + StaticPrefs::apz_max_velocity_inches_per_ms()) { + float curveThreshold = mAxis->ToLocalVelocity( + StaticPrefs::apz_fling_curve_threshold_inches_per_ms()); + if (newVelocity > curveThreshold) { + // here, 0 < curveThreshold < newVelocity <= maxVelocity, so we apply + // the curve + float scale = maxVelocity - curveThreshold; + float funcInput = (newVelocity - curveThreshold) / scale; + float funcOutput = gVelocityCurveFunction->GetValue( + funcInput, ComputedTimingFunction::BeforeFlag::Unset); + float curvedVelocity = (funcOutput * scale) + curveThreshold; + SVT_LOG("%p|%s curving up velocity from %f to %f\n", + mAxis->OpaqueApzcPointer(), mAxis->Name(), newVelocity, + curvedVelocity); + newVelocity = curvedVelocity; + } + } + + if (velocityIsNegative) { + newVelocity = -newVelocity; + } + } + + return newVelocity; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/src/SimpleVelocityTracker.h b/gfx/layers/apz/src/SimpleVelocityTracker.h new file mode 100644 index 0000000000..1778dee065 --- /dev/null +++ b/gfx/layers/apz/src/SimpleVelocityTracker.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_VelocityTracker_h +#define mozilla_layers_VelocityTracker_h + +#include +#include + +#include "Axis.h" +#include "mozilla/Attributes.h" +#include "nsTArray.h" + +namespace mozilla { +namespace layers { + +class SimpleVelocityTracker : public VelocityTracker { + public: + explicit SimpleVelocityTracker(Axis* aAxis); + void StartTracking(ParentLayerCoord aPos, TimeStamp aTimestamp) override; + Maybe AddPosition(ParentLayerCoord aPos, + TimeStamp aTimestamp) override; + Maybe ComputeVelocity(TimeStamp aTimestamp) override; + void Clear() override; + + private: + void AddVelocityToQueue(TimeStamp aTimestamp, float aVelocity); + float ApplyFlingCurveToVelocity(float aVelocity) const; + + // The Axis that uses this velocity tracker. + // This is a raw pointer because the Axis owns the velocity tracker + // by UniquePtr, so the velocity tracker cannot outlive the Axis. + Axis* MOZ_NON_OWNING_REF mAxis; + + // A queue of (timestamp, velocity) pairs; these are the historical + // velocities at the given timestamps. Velocities are in screen pixels per ms. + // This member can only be accessed on the controller/UI thread. + nsTArray> mVelocityQueue; + + // mVelocitySampleTime and mVelocitySamplePos are the time and position + // used in the last velocity sampling. They get updated when a new sample is + // taken (which may not happen on every input event, if the time delta is too + // small). + TimeStamp mVelocitySampleTime; + ParentLayerCoord mVelocitySamplePos; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/apz/src/SmoothMsdScrollAnimation.cpp b/gfx/layers/apz/src/SmoothMsdScrollAnimation.cpp new file mode 100644 index 0000000000..ab57f345da --- /dev/null +++ b/gfx/layers/apz/src/SmoothMsdScrollAnimation.cpp @@ -0,0 +1,130 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "SmoothMsdScrollAnimation.h" +#include "AsyncPanZoomController.h" + +namespace mozilla { +namespace layers { + +SmoothMsdScrollAnimation::SmoothMsdScrollAnimation( + AsyncPanZoomController& aApzc, const CSSPoint& aInitialPosition, + const CSSPoint& aInitialVelocity, const CSSPoint& aDestination, + double aSpringConstant, double aDampingRatio) + : mApzc(aApzc), + mXAxisModel(aInitialPosition.x, aDestination.x, aInitialVelocity.x, + aSpringConstant, aDampingRatio), + mYAxisModel(aInitialPosition.y, aDestination.y, aInitialVelocity.y, + aSpringConstant, aDampingRatio) {} + +bool SmoothMsdScrollAnimation::DoSample(FrameMetrics& aFrameMetrics, + const TimeDuration& aDelta) { + CSSToParentLayerScale2D zoom = aFrameMetrics.GetZoom(); + if (zoom == CSSToParentLayerScale2D(0, 0)) { + return false; + } + CSSPoint oneParentLayerPixel = + ParentLayerPoint(1, 1) / aFrameMetrics.GetZoom(); + if (mXAxisModel.IsFinished(oneParentLayerPixel.x) && + mYAxisModel.IsFinished(oneParentLayerPixel.y)) { + // Set the scroll offset to the exact destination. If we allow the scroll + // offset to end up being a bit off from the destination, we can get + // artefacts like "scroll to the next snap point in this direction" + // scrolling to the snap point we're already supposed to be at. + mApzc.ClampAndSetVisualScrollOffset( + CSSPoint(mXAxisModel.GetDestination(), mYAxisModel.GetDestination())); + return false; + } + + mXAxisModel.Simulate(aDelta); + mYAxisModel.Simulate(aDelta); + + CSSPoint position = + CSSPoint(mXAxisModel.GetPosition(), mYAxisModel.GetPosition()); + CSSPoint css_velocity = + CSSPoint(mXAxisModel.GetVelocity(), mYAxisModel.GetVelocity()); + + // Convert from pixels/second to pixels/ms + ParentLayerPoint velocity = + ParentLayerPoint(css_velocity.x, css_velocity.y) / 1000.0f; + + // Keep the velocity updated for the Axis class so that any animations + // chained off of the smooth scroll will inherit it. + if (mXAxisModel.IsFinished(oneParentLayerPixel.x)) { + mApzc.mX.SetVelocity(0); + } else { + mApzc.mX.SetVelocity(velocity.x); + } + if (mYAxisModel.IsFinished(oneParentLayerPixel.y)) { + mApzc.mY.SetVelocity(0); + } else { + mApzc.mY.SetVelocity(velocity.y); + } + // If we overscroll, hand off to a fling animation that will complete the + // spring back. + ParentLayerPoint displacement = + (position - aFrameMetrics.GetVisualScrollOffset()) * zoom; + + ParentLayerPoint overscroll; + ParentLayerPoint adjustedOffset; + mApzc.mX.AdjustDisplacement(displacement.x, adjustedOffset.x, overscroll.x); + mApzc.mY.AdjustDisplacement(displacement.y, adjustedOffset.y, overscroll.y); + mApzc.ScrollBy(adjustedOffset / zoom); + // The smooth scroll may have caused us to reach the end of our scroll + // range. This can happen if either the + // layout.css.scroll-behavior.damping-ratio preference is set to less than 1 + // (underdamped) or if a smooth scroll inherits velocity from a fling + // gesture. + if (!IsZero(overscroll)) { + // Hand off a fling with the remaining momentum to the next APZC in the + // overscroll handoff chain. + + // We may have reached the end of the scroll range along one axis but + // not the other. In such a case we only want to hand off the relevant + // component of the fling. + if (FuzzyEqualsAdditive(overscroll.x, 0.0f, COORDINATE_EPSILON)) { + velocity.x = 0; + } else if (FuzzyEqualsAdditive(overscroll.y, 0.0f, COORDINATE_EPSILON)) { + velocity.y = 0; + } + + // To hand off the fling, we attempt to find a target APZC and start a new + // fling with the same velocity on that APZC. For simplicity, the actual + // overscroll of the current sample is discarded rather than being handed + // off. The compositor should sample animations sufficiently frequently + // that this is not noticeable. The target APZC is chosen by seeing if + // there is an APZC further in the handoff chain which is pannable; if + // there isn't, we take the new fling ourselves, entering an overscrolled + // state. + // Note: APZC is holding mRecursiveMutex, so directly calling + // HandleSmoothScrollOverscroll() (which acquires the tree lock) would + // violate the lock ordering. Instead we schedule + // HandleSmoothScrollOverscroll() to be called after mRecursiveMutex is + // released. + mDeferredTasks.AppendElement(NewRunnableMethod( + "layers::AsyncPanZoomController::HandleSmoothScrollOverscroll", &mApzc, + &AsyncPanZoomController::HandleSmoothScrollOverscroll, velocity)); + return false; + } + + return true; +} + +void SmoothMsdScrollAnimation::SetDestination(const CSSPoint& aNewDestination) { + mXAxisModel.SetDestination(aNewDestination.x); + mYAxisModel.SetDestination(aNewDestination.y); +} + +CSSPoint SmoothMsdScrollAnimation::GetDestination() const { + return CSSPoint(mXAxisModel.GetDestination(), mYAxisModel.GetDestination()); +} + +SmoothMsdScrollAnimation* +SmoothMsdScrollAnimation::AsSmoothMsdScrollAnimation() { + return this; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/src/SmoothMsdScrollAnimation.h b/gfx/layers/apz/src/SmoothMsdScrollAnimation.h new file mode 100644 index 0000000000..956672a03f --- /dev/null +++ b/gfx/layers/apz/src/SmoothMsdScrollAnimation.h @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_SmoothMsdScrollAnimation_h_ +#define mozilla_layers_SmoothMsdScrollAnimation_h_ + +#include "AsyncPanZoomAnimation.h" +#include "mozilla/layers/AxisPhysicsMSDModel.h" + +namespace mozilla { +namespace layers { + +class AsyncPanZoomController; + +class SmoothMsdScrollAnimation : public AsyncPanZoomAnimation { + public: + SmoothMsdScrollAnimation(AsyncPanZoomController& aApzc, + const CSSPoint& aInitialPosition, + const CSSPoint& aInitialVelocity, + const CSSPoint& aDestination, double aSpringConstant, + double aDampingRatio); + + /** + * Advances a smooth scroll simulation based on the time passed in |aDelta|. + * This should be called whenever sampling the content transform for this + * frame. Returns true if the smooth scroll should be advanced by one frame, + * or false if the smooth scroll has ended. + */ + bool DoSample(FrameMetrics& aFrameMetrics, + const TimeDuration& aDelta) override; + + void SetDestination(const CSSPoint& aNewDestination); + CSSPoint GetDestination() const; + SmoothMsdScrollAnimation* AsSmoothMsdScrollAnimation() override; + + private: + AsyncPanZoomController& mApzc; + AxisPhysicsMSDModel mXAxisModel; + AxisPhysicsMSDModel mYAxisModel; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/apz/src/SmoothScrollAnimation.cpp b/gfx/layers/apz/src/SmoothScrollAnimation.cpp new file mode 100644 index 0000000000..266c027a55 --- /dev/null +++ b/gfx/layers/apz/src/SmoothScrollAnimation.cpp @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SmoothScrollAnimation.h" +#include "ScrollAnimationBezierPhysics.h" +#include "mozilla/layers/APZPublicUtils.h" + +namespace mozilla { +namespace layers { + +SmoothScrollAnimation::SmoothScrollAnimation(AsyncPanZoomController& aApzc, + const nsPoint& aInitialPosition, + ScrollOrigin aOrigin) + : GenericScrollAnimation( + aApzc, aInitialPosition, + apz::ComputeBezierAnimationSettingsForOrigin(aOrigin)), + mOrigin(aOrigin) {} + +SmoothScrollAnimation* SmoothScrollAnimation::AsSmoothScrollAnimation() { + return this; +} + +ScrollOrigin SmoothScrollAnimation::GetScrollOrigin() const { return mOrigin; } + +ScrollOrigin SmoothScrollAnimation::GetScrollOriginForAction( + KeyboardScrollAction::KeyboardScrollActionType aAction) { + switch (aAction) { + case KeyboardScrollAction::eScrollCharacter: + case KeyboardScrollAction::eScrollLine: { + return ScrollOrigin::Lines; + } + case KeyboardScrollAction::eScrollPage: + return ScrollOrigin::Pages; + case KeyboardScrollAction::eScrollComplete: + return ScrollOrigin::Other; + default: + MOZ_ASSERT(false, "Unknown keyboard scroll action type"); + return ScrollOrigin::Other; + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/src/SmoothScrollAnimation.h b/gfx/layers/apz/src/SmoothScrollAnimation.h new file mode 100644 index 0000000000..1143744cc1 --- /dev/null +++ b/gfx/layers/apz/src/SmoothScrollAnimation.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_SmoothScrollAnimation_h_ +#define mozilla_layers_SmoothScrollAnimation_h_ + +#include "GenericScrollAnimation.h" +#include "mozilla/ScrollOrigin.h" +#include "mozilla/layers/KeyboardScrollAction.h" + +namespace mozilla { +namespace layers { + +class AsyncPanZoomController; + +class SmoothScrollAnimation : public GenericScrollAnimation { + public: + SmoothScrollAnimation(AsyncPanZoomController& aApzc, + const nsPoint& aInitialPosition, + ScrollOrigin aScrollOrigin); + + SmoothScrollAnimation* AsSmoothScrollAnimation() override; + ScrollOrigin GetScrollOrigin() const; + static ScrollOrigin GetScrollOriginForAction( + KeyboardScrollAction::KeyboardScrollActionType aAction); + + private: + ScrollOrigin mOrigin; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_SmoothScrollAnimation_h_ diff --git a/gfx/layers/apz/src/WheelScrollAnimation.cpp b/gfx/layers/apz/src/WheelScrollAnimation.cpp new file mode 100644 index 0000000000..d00004d127 --- /dev/null +++ b/gfx/layers/apz/src/WheelScrollAnimation.cpp @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WheelScrollAnimation.h" + +#include +#include "AsyncPanZoomController.h" +#include "mozilla/StaticPrefs_general.h" +#include "mozilla/layers/APZPublicUtils.h" +#include "nsPoint.h" +#include "ScrollAnimationBezierPhysics.h" + +namespace mozilla { +namespace layers { + +static ScrollAnimationBezierPhysicsSettings SettingsForDeltaType( + ScrollWheelInput::ScrollDeltaType aDeltaType) { + int32_t minMS = 0; + int32_t maxMS = 0; + + switch (aDeltaType) { + case ScrollWheelInput::SCROLLDELTA_PAGE: + maxMS = clamped(StaticPrefs::general_smoothScroll_pages_durationMaxMS(), + 0, 10000); + minMS = clamped(StaticPrefs::general_smoothScroll_pages_durationMinMS(), + 0, maxMS); + break; + case ScrollWheelInput::SCROLLDELTA_PIXEL: + maxMS = clamped(StaticPrefs::general_smoothScroll_pixels_durationMaxMS(), + 0, 10000); + minMS = clamped(StaticPrefs::general_smoothScroll_pixels_durationMinMS(), + 0, maxMS); + break; + case ScrollWheelInput::SCROLLDELTA_LINE: + std::tie(minMS, maxMS) = apz::GetMouseWheelAnimationDurations(); + maxMS = clamped(maxMS, 0, 10000); + minMS = clamped(minMS, 0, maxMS); + break; + } + + // The pref is 100-based int percentage, while mIntervalRatio is 1-based ratio + double intervalRatio = + ((double)StaticPrefs::general_smoothScroll_durationToIntervalRatio()) / + 100.0; + intervalRatio = std::max(1.0, intervalRatio); + return ScrollAnimationBezierPhysicsSettings{minMS, maxMS, intervalRatio}; +} + +WheelScrollAnimation::WheelScrollAnimation( + AsyncPanZoomController& aApzc, const nsPoint& aInitialPosition, + ScrollWheelInput::ScrollDeltaType aDeltaType) + : GenericScrollAnimation(aApzc, aInitialPosition, + SettingsForDeltaType(aDeltaType)) { + mDirectionForcedToOverscroll = + mApzc.mScrollMetadata.GetDisregardedDirection(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/src/WheelScrollAnimation.h b/gfx/layers/apz/src/WheelScrollAnimation.h new file mode 100644 index 0000000000..7c039ef3fd --- /dev/null +++ b/gfx/layers/apz/src/WheelScrollAnimation.h @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_WheelScrollAnimation_h_ +#define mozilla_layers_WheelScrollAnimation_h_ + +#include "GenericScrollAnimation.h" +#include "InputData.h" + +namespace mozilla { +namespace layers { + +class AsyncPanZoomController; + +class WheelScrollAnimation : public GenericScrollAnimation { + public: + WheelScrollAnimation(AsyncPanZoomController& aApzc, + const nsPoint& aInitialPosition, + ScrollWheelInput::ScrollDeltaType aDeltaType); + + WheelScrollAnimation* AsWheelScrollAnimation() override { return this; } +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_WheelScrollAnimation_h_ diff --git a/gfx/layers/apz/test/gtest/APZCBasicTester.h b/gfx/layers/apz/test/gtest/APZCBasicTester.h new file mode 100644 index 0000000000..a95663fce7 --- /dev/null +++ b/gfx/layers/apz/test/gtest/APZCBasicTester.h @@ -0,0 +1,123 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_APZCBasicTester_h +#define mozilla_layers_APZCBasicTester_h + +/** + * Defines a test fixture used for testing a single APZC. + */ + +#include "APZTestCommon.h" + +#include "mozilla/layers/APZSampler.h" +#include "mozilla/layers/APZUpdater.h" + +class APZCBasicTester : public APZCTesterBase { + public: + explicit APZCBasicTester( + AsyncPanZoomController::GestureBehavior aGestureBehavior = + AsyncPanZoomController::DEFAULT_GESTURES) + : mGestureBehavior(aGestureBehavior) {} + + protected: + virtual void SetUp() { + APZCTesterBase::SetUp(); + APZThreadUtils::SetThreadAssertionsEnabled(false); + APZThreadUtils::SetControllerThread(NS_GetCurrentThread()); + + tm = new TestAPZCTreeManager(mcc); + updater = new APZUpdater(tm, false); + sampler = new APZSampler(tm, false); + apzc = + new TestAsyncPanZoomController(LayersId{0}, mcc, tm, mGestureBehavior); + apzc->SetFrameMetrics(TestFrameMetrics()); + apzc->GetScrollMetadata().SetIsLayersIdRoot(true); + } + + /** + * Get the APZC's scroll range in CSS pixels. + */ + CSSRect GetScrollRange() const { + const FrameMetrics& metrics = apzc->GetFrameMetrics(); + return CSSRect(metrics.GetScrollableRect().TopLeft(), + metrics.GetScrollableRect().Size() - + metrics.CalculateCompositedSizeInCssPixels()); + } + + virtual void TearDown() { + while (mcc->RunThroughDelayedTasks()) + ; + apzc->Destroy(); + tm->ClearTree(); + tm->ClearContentController(); + } + + void MakeApzcWaitForMainThread() { apzc->SetWaitForMainThread(); } + + void MakeApzcZoomable() { + apzc->UpdateZoomConstraints(ZoomConstraints( + true, true, CSSToParentLayerScale(0.25f), CSSToParentLayerScale(4.0f))); + } + + void MakeApzcUnzoomable() { + apzc->UpdateZoomConstraints(ZoomConstraints(false, false, + CSSToParentLayerScale(1.0f), + CSSToParentLayerScale(1.0f))); + } + + void PanIntoOverscroll(); + + /** + * Sample animations once, 1 ms later than the last sample. + */ + void SampleAnimationOnce() { + const TimeDuration increment = TimeDuration::FromMilliseconds(1); + ParentLayerPoint pointOut; + AsyncTransform viewTransformOut; + mcc->AdvanceBy(increment); + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + } + + /** + * Sample animations until we recover from overscroll. + * @param aExpectedScrollOffset the expected reported scroll offset + * throughout the animation + */ + void SampleAnimationUntilRecoveredFromOverscroll( + const ParentLayerPoint& aExpectedScrollOffset) { + const TimeDuration increment = TimeDuration::FromMilliseconds(1); + bool recoveredFromOverscroll = false; + ParentLayerPoint pointOut; + AsyncTransform viewTransformOut; + while (apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut)) { + // The reported scroll offset should be the same throughout. + EXPECT_EQ(aExpectedScrollOffset, pointOut); + + // Trigger computation of the overscroll tranform, to make sure + // no assetions fire during the calculation. + apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting); + + if (!apzc->IsOverscrolled()) { + recoveredFromOverscroll = true; + } + + mcc->AdvanceBy(increment); + } + EXPECT_TRUE(recoveredFromOverscroll); + apzc->AssertStateIsReset(); + } + + void TestOverscroll(); + + AsyncPanZoomController::GestureBehavior mGestureBehavior; + RefPtr tm; + RefPtr sampler; + RefPtr updater; + RefPtr apzc; +}; + +#endif // mozilla_layers_APZCBasicTester_h diff --git a/gfx/layers/apz/test/gtest/APZCTreeManagerTester.h b/gfx/layers/apz/test/gtest/APZCTreeManagerTester.h new file mode 100644 index 0000000000..504857b4f4 --- /dev/null +++ b/gfx/layers/apz/test/gtest/APZCTreeManagerTester.h @@ -0,0 +1,161 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_APZCTreeManagerTester_h +#define mozilla_layers_APZCTreeManagerTester_h + +/** + * Defines a test fixture used for testing multiple APZCs interacting in + * an APZCTreeManager. + */ + +#include "APZTestCommon.h" +#include "gfxPlatform.h" + +#include "mozilla/layers/APZSampler.h" +#include "mozilla/layers/APZUpdater.h" + +class APZCTreeManagerTester : public APZCTesterBase { + protected: + virtual void SetUp() { + APZCTesterBase::SetUp(); + APZThreadUtils::SetThreadAssertionsEnabled(false); + APZThreadUtils::SetControllerThread(NS_GetCurrentThread()); + + manager = new TestAPZCTreeManager(mcc); + updater = new APZUpdater(manager, false); + sampler = new APZSampler(manager, false); + } + + virtual void TearDown() { + while (mcc->RunThroughDelayedTasks()) + ; + manager->ClearTree(); + manager->ClearContentController(); + } + + /** + * Sample animations once for all APZCs, 1 ms later than the last sample. + */ + void SampleAnimationsOnce() { + const TimeDuration increment = TimeDuration::FromMilliseconds(1); + ParentLayerPoint pointOut; + AsyncTransform viewTransformOut; + mcc->AdvanceBy(increment); + + for (const RefPtr& layer : layers) { + if (TestAsyncPanZoomController* apzc = ApzcOf(layer)) { + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + } + } + } + + // A convenience function for letting a test modify the frame metrics + // stored on a particular layer. The layer doesn't let us modify it in-place, + // so we take care of the copying in this function. + template + void ModifyFrameMetrics(Layer* aLayer, Callback aCallback) { + ScrollMetadata metadata = aLayer->GetScrollMetadata(0); + aCallback(metadata, metadata.GetMetrics()); + aLayer->SetScrollMetadata(metadata); + } + + // A convenience wrapper for manager->UpdateHitTestingTree(). + void UpdateHitTestingTree(uint32_t aPaintSequenceNumber = 0) { + manager->UpdateHitTestingTree(root, /* is first paint = */ false, + LayersId{0}, aPaintSequenceNumber); + } + + nsTArray > layers; + RefPtr lm; + RefPtr root; + + RefPtr manager; + RefPtr sampler; + RefPtr updater; + + protected: + static ScrollMetadata BuildScrollMetadata( + ScrollableLayerGuid::ViewID aScrollId, const CSSRect& aScrollableRect, + const ParentLayerRect& aCompositionBounds) { + ScrollMetadata metadata; + FrameMetrics& metrics = metadata.GetMetrics(); + metrics.SetScrollId(aScrollId); + // By convention in this test file, START_SCROLL_ID is the root, so mark it + // as such. + if (aScrollId == ScrollableLayerGuid::START_SCROLL_ID) { + metadata.SetIsLayersIdRoot(true); + } + metrics.SetCompositionBounds(aCompositionBounds); + metrics.SetScrollableRect(aScrollableRect); + metrics.SetLayoutScrollOffset(CSSPoint(0, 0)); + metadata.SetPageScrollAmount(LayoutDeviceIntSize(50, 100)); + metadata.SetLineScrollAmount(LayoutDeviceIntSize(5, 10)); + return metadata; + } + + static void SetEventRegionsBasedOnBottommostMetrics(Layer* aLayer) { + const FrameMetrics& metrics = aLayer->GetScrollMetadata(0).GetMetrics(); + CSSRect scrollableRect = metrics.GetScrollableRect(); + if (!scrollableRect.IsEqualEdges(CSSRect(-1, -1, -1, -1))) { + // The purpose of this is to roughly mimic what layout would do in the + // case of a scrollable frame with the event regions and clip. This lets + // us exercise the hit-testing code in APZCTreeManager + EventRegions er = aLayer->GetEventRegions(); + IntRect scrollRect = + RoundedToInt(scrollableRect * metrics.LayersPixelsPerCSSPixel()) + .ToUnknownRect(); + er.mHitRegion = nsIntRegion(IntRect( + RoundedToInt( + metrics.GetCompositionBounds().TopLeft().ToUnknownPoint()), + scrollRect.Size())); + aLayer->SetEventRegions(er); + } + } + + static void SetScrollableFrameMetrics( + Layer* aLayer, ScrollableLayerGuid::ViewID aScrollId, + CSSRect aScrollableRect = CSSRect(-1, -1, -1, -1)) { + ParentLayerIntRect compositionBounds = + RoundedToInt(aLayer->GetLocalTransformTyped().TransformBounds( + LayerRect(aLayer->GetVisibleRegion().GetBounds()))); + ScrollMetadata metadata = BuildScrollMetadata( + aScrollId, aScrollableRect, ParentLayerRect(compositionBounds)); + aLayer->SetScrollMetadata(metadata); + aLayer->SetClipRect(Some(compositionBounds)); + SetEventRegionsBasedOnBottommostMetrics(aLayer); + } + + void SetScrollHandoff(Layer* aChild, Layer* aParent) { + ScrollMetadata metadata = aChild->GetScrollMetadata(0); + metadata.SetScrollParentId(aParent->GetFrameMetrics(0).GetScrollId()); + aChild->SetScrollMetadata(metadata); + } + + static TestAsyncPanZoomController* ApzcOf(Layer* aLayer) { + EXPECT_EQ(1u, aLayer->GetScrollMetadataCount()); + return (TestAsyncPanZoomController*)aLayer->GetAsyncPanZoomController(0); + } + + static TestAsyncPanZoomController* ApzcOf(Layer* aLayer, uint32_t aIndex) { + EXPECT_LT(aIndex, aLayer->GetScrollMetadataCount()); + return (TestAsyncPanZoomController*)aLayer->GetAsyncPanZoomController( + aIndex); + } + + void CreateSimpleScrollingLayer() { + const char* layerTreeSyntax = "t"; + nsIntRegion layerVisibleRegion[] = { + nsIntRegion(IntRect(0, 0, 200, 200)), + }; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, + layers); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 500, 500)); + } +}; + +#endif // mozilla_layers_APZCTreeManagerTester_h diff --git a/gfx/layers/apz/test/gtest/APZTestCommon.h b/gfx/layers/apz/test/gtest/APZTestCommon.h new file mode 100644 index 0000000000..3d34bd943a --- /dev/null +++ b/gfx/layers/apz/test/gtest/APZTestCommon.h @@ -0,0 +1,954 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_APZTestCommon_h +#define mozilla_layers_APZTestCommon_h + +/** + * Defines a set of mock classes and utility functions/classes for + * writing APZ gtests. + */ + +#include "gtest/gtest.h" +#include "gmock/gmock.h" + +#include "mozilla/Attributes.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/layers/AsyncCompositionManager.h" // for ViewTransform +#include "mozilla/layers/GeckoContentController.h" +#include "mozilla/layers/CompositorBridgeParent.h" +#include "mozilla/layers/LayerMetricsWrapper.h" +#include "mozilla/layers/APZThreadUtils.h" +#include "mozilla/layers/MatrixMessage.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/TypedEnumBits.h" +#include "mozilla/UniquePtr.h" +#include "apz/src/APZCTreeManager.h" +#include "apz/src/AsyncPanZoomController.h" +#include "apz/src/HitTestingTreeNode.h" +#include "base/task.h" +#include "Layers.h" +#include "TestLayers.h" +#include "UnitTransforms.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::layers; +using ::testing::_; +using ::testing::AtLeast; +using ::testing::AtMost; +using ::testing::InSequence; +using ::testing::MockFunction; +using ::testing::NiceMock; +typedef mozilla::layers::GeckoContentController::TapType TapType; + +static TimeStamp GetStartupTime() { + static TimeStamp sStartupTime = TimeStamp::Now(); + return sStartupTime; +} + +inline uint32_t MillisecondsSinceStartup(TimeStamp aTime) { + return (aTime - GetStartupTime()).ToMilliseconds(); +} + +// Some helper functions for constructing input event objects suitable to be +// passed either to an APZC (which expects an transformed point), or to an APZTM +// (which expects an untransformed point). We handle both cases by setting both +// the transformed and untransformed fields to the same value. +inline SingleTouchData CreateSingleTouchData(int32_t aIdentifier, + const ScreenIntPoint& aPoint) { + SingleTouchData touch(aIdentifier, aPoint, ScreenSize(0, 0), 0, 0); + touch.mLocalScreenPoint = ParentLayerPoint(aPoint.x, aPoint.y); + return touch; +} + +// Convenience wrapper for CreateSingleTouchData() that takes loose coordinates. +inline SingleTouchData CreateSingleTouchData(int32_t aIdentifier, + ScreenIntCoord aX, + ScreenIntCoord aY) { + return CreateSingleTouchData(aIdentifier, ScreenIntPoint(aX, aY)); +} + +inline PinchGestureInput CreatePinchGestureInput( + PinchGestureInput::PinchGestureType aType, const ScreenPoint& aFocus, + float aCurrentSpan, float aPreviousSpan, TimeStamp timestamp) { + ParentLayerPoint localFocus(aFocus.x, aFocus.y); + PinchGestureInput result( + aType, PinchGestureInput::UNKNOWN, MillisecondsSinceStartup(timestamp), + timestamp, ExternalPoint(0, 0), aFocus, aCurrentSpan, aPreviousSpan, 0); + return result; +} + +template +class ScopedGfxSetting { + public: + ScopedGfxSetting(const std::function& aGetPrefFunc, + const std::function& aSetPrefFunc, SetArg aVal) + : mSetPrefFunc(aSetPrefFunc) { + mOldVal = aGetPrefFunc(); + aSetPrefFunc(aVal); + } + + ~ScopedGfxSetting() { mSetPrefFunc(mOldVal); } + + private: + std::function mSetPrefFunc; + Storage mOldVal; +}; + +#define FRESH_PREF_VAR_PASTE(id, line) id##line +#define FRESH_PREF_VAR_EXPAND(id, line) FRESH_PREF_VAR_PASTE(id, line) +#define FRESH_PREF_VAR FRESH_PREF_VAR_EXPAND(pref, __LINE__) + +#define SCOPED_GFX_PREF_BOOL(prefName, prefValue) \ + ScopedGfxSetting FRESH_PREF_VAR( \ + [=]() { return Preferences::GetBool(prefName); }, \ + [=](bool aPrefValue) { Preferences::SetBool(prefName, aPrefValue); }, \ + prefValue) + +#define SCOPED_GFX_PREF_INT(prefName, prefValue) \ + ScopedGfxSetting FRESH_PREF_VAR( \ + [=]() { return Preferences::GetInt(prefName); }, \ + [=](int32_t aPrefValue) { Preferences::SetInt(prefName, aPrefValue); }, \ + prefValue) + +#define SCOPED_GFX_PREF_FLOAT(prefName, prefValue) \ + ScopedGfxSetting FRESH_PREF_VAR( \ + [=]() { return Preferences::GetFloat(prefName); }, \ + [=](float aPrefValue) { Preferences::SetFloat(prefName, aPrefValue); }, \ + prefValue) + +#define SCOPED_GFX_VAR(varBase, varType, varValue) \ + ScopedGfxSetting var_##varBase( \ + &(gfxVars::varBase), &(gfxVars::Set##varBase), varValue) + +class MockContentController : public GeckoContentController { + public: + MOCK_METHOD1(NotifyLayerTransforms, void(nsTArray&&)); + MOCK_METHOD1(RequestContentRepaint, void(const RepaintRequest&)); + MOCK_METHOD5(HandleTap, void(TapType, const LayoutDevicePoint&, Modifiers, + const ScrollableLayerGuid&, uint64_t)); + MOCK_METHOD5(NotifyPinchGesture, + void(PinchGestureInput::PinchGestureType, + const ScrollableLayerGuid&, const LayoutDevicePoint&, + LayoutDeviceCoord, Modifiers)); + // Can't use the macros with already_AddRefed :( + void PostDelayedTask(already_AddRefed aTask, int aDelayMs) { + RefPtr task = aTask; + } + bool IsRepaintThread() { return NS_IsMainThread(); } + void DispatchToRepaintThread(already_AddRefed aTask) { + NS_DispatchToMainThread(std::move(aTask)); + } + MOCK_METHOD3(NotifyAPZStateChange, void(const ScrollableLayerGuid& aGuid, + APZStateChange aChange, int aArg)); + MOCK_METHOD0(NotifyFlushComplete, void()); + MOCK_METHOD3(NotifyAsyncScrollbarDragInitiated, + void(uint64_t, const ScrollableLayerGuid::ViewID&, + ScrollDirection aDirection)); + MOCK_METHOD1(NotifyAsyncScrollbarDragRejected, + void(const ScrollableLayerGuid::ViewID&)); + MOCK_METHOD1(NotifyAsyncAutoscrollRejected, + void(const ScrollableLayerGuid::ViewID&)); + MOCK_METHOD1(CancelAutoscroll, void(const ScrollableLayerGuid&)); +}; + +class MockContentControllerDelayed : public MockContentController { + public: + MockContentControllerDelayed() + : mTime(SampleTime::FromTest(GetStartupTime())) {} + + const TimeStamp& Time() { return mTime.Time(); } + const SampleTime& GetSampleTime() { return mTime; } + + void AdvanceByMillis(int aMillis) { + AdvanceBy(TimeDuration::FromMilliseconds(aMillis)); + } + + void AdvanceBy(const TimeDuration& aIncrement) { + SampleTime target = mTime + aIncrement; + while (mTaskQueue.Length() > 0 && mTaskQueue[0].second <= target) { + RunNextDelayedTask(); + } + mTime = target; + } + + void PostDelayedTask(already_AddRefed aTask, int aDelayMs) { + RefPtr task = aTask; + SampleTime runAtTime = mTime + TimeDuration::FromMilliseconds(aDelayMs); + int insIndex = mTaskQueue.Length(); + while (insIndex > 0) { + if (mTaskQueue[insIndex - 1].second <= runAtTime) { + break; + } + insIndex--; + } + mTaskQueue.InsertElementAt(insIndex, std::make_pair(task, runAtTime)); + } + + // Run all the tasks in the queue, returning the number of tasks + // run. Note that if a task queues another task while running, that + // new task will not be run. Therefore, there may be still be tasks + // in the queue after this function is called. Only when the return + // value is 0 is the queue guaranteed to be empty. + int RunThroughDelayedTasks() { + nsTArray, SampleTime>> runQueue = + std::move(mTaskQueue); + int numTasks = runQueue.Length(); + for (int i = 0; i < numTasks; i++) { + mTime = runQueue[i].second; + runQueue[i].first->Run(); + + // Deleting the task is important in order to release the reference to + // the callee object. + runQueue[i].first = nullptr; + } + return numTasks; + } + + private: + void RunNextDelayedTask() { + std::pair, SampleTime> next = mTaskQueue[0]; + mTaskQueue.RemoveElementAt(0); + mTime = next.second; + next.first->Run(); + // Deleting the task is important in order to release the reference to + // the callee object. + next.first = nullptr; + } + + // The following array is sorted by timestamp (tasks are inserted in order by + // timestamp). + nsTArray, SampleTime>> mTaskQueue; + SampleTime mTime; +}; + +class TestAPZCTreeManager : public APZCTreeManager { + public: + explicit TestAPZCTreeManager(MockContentControllerDelayed* aMcc) + : APZCTreeManager(LayersId{0}, gfx::gfxVars::UseWebRender()), mcc(aMcc) {} + + RefPtr GetInputQueue() const { return mInputQueue; } + + void ClearContentController() { mcc = nullptr; } + + /** + * This function is not currently implemented. + * See bug 1468804 for more information. + **/ + void CancelAnimation() { EXPECT_TRUE(false); } + + protected: + AsyncPanZoomController* NewAPZCInstance( + LayersId aLayersId, GeckoContentController* aController) override; + + SampleTime GetFrameTime() override { return mcc->GetSampleTime(); } + + private: + RefPtr mcc; +}; + +class TestAsyncPanZoomController : public AsyncPanZoomController { + public: + TestAsyncPanZoomController(LayersId aLayersId, + MockContentControllerDelayed* aMcc, + TestAPZCTreeManager* aTreeManager, + GestureBehavior aBehavior = DEFAULT_GESTURES) + : AsyncPanZoomController(aLayersId, aTreeManager, + aTreeManager->GetInputQueue(), aMcc, aBehavior), + mWaitForMainThread(false), + mcc(aMcc) {} + + APZEventResult ReceiveInputEvent(const InputData& aEvent) { + // This is a function whose signature matches exactly the ReceiveInputEvent + // on APZCTreeManager. This allows us to templates for functions like + // TouchDown, TouchUp, etc so that we can reuse the code for dispatching + // events into both APZC and APZCTM. + APZEventResult result; + result.mStatus = ReceiveInputEvent(aEvent, &result.mInputBlockId); + return result; + } + + nsEventStatus ReceiveInputEvent(const InputData& aEvent, + uint64_t* aOutInputBlockId) { + return GetInputQueue()->ReceiveInputEvent( + this, TargetConfirmationFlags{!mWaitForMainThread}, aEvent, + aOutInputBlockId); + } + + void ContentReceivedInputBlock(uint64_t aInputBlockId, bool aPreventDefault) { + GetInputQueue()->ContentReceivedInputBlock(aInputBlockId, aPreventDefault); + } + + void ConfirmTarget(uint64_t aInputBlockId) { + RefPtr target = this; + GetInputQueue()->SetConfirmedTargetApzc(aInputBlockId, target); + } + + void SetAllowedTouchBehavior(uint64_t aInputBlockId, + const nsTArray& aBehaviors) { + GetInputQueue()->SetAllowedTouchBehavior(aInputBlockId, aBehaviors); + } + + void SetFrameMetrics(const FrameMetrics& metrics) { + RecursiveMutexAutoLock lock(mRecursiveMutex); + Metrics() = metrics; + } + + FrameMetrics& GetFrameMetrics() { + RecursiveMutexAutoLock lock(mRecursiveMutex); + return mScrollMetadata.GetMetrics(); + } + + ScrollMetadata& GetScrollMetadata() { + RecursiveMutexAutoLock lock(mRecursiveMutex); + return mScrollMetadata; + } + + const FrameMetrics& GetFrameMetrics() const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + return mScrollMetadata.GetMetrics(); + } + + using AsyncPanZoomController::GetVelocityVector; + + void AssertStateIsReset() const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + EXPECT_EQ(NOTHING, mState); + } + + void AssertStateIsFling() const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + EXPECT_EQ(FLING, mState); + } + + void AssertStateIsSmoothMsdScroll() const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + EXPECT_EQ(SMOOTHMSD_SCROLL, mState); + } + + void AssertNotAxisLocked() const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + EXPECT_EQ(PANNING, mState); + } + + void AssertAxisLocked(ScrollDirection aDirection) const { + RecursiveMutexAutoLock lock(mRecursiveMutex); + switch (aDirection) { + case ScrollDirection::eHorizontal: + EXPECT_EQ(PANNING_LOCKED_X, mState); + break; + case ScrollDirection::eVertical: + EXPECT_EQ(PANNING_LOCKED_Y, mState); + break; + } + } + + void AdvanceAnimationsUntilEnd( + const TimeDuration& aIncrement = TimeDuration::FromMilliseconds(10)) { + while (AdvanceAnimations(mcc->GetSampleTime())) { + mcc->AdvanceBy(aIncrement); + } + } + + bool SampleContentTransformForFrame( + AsyncTransform* aOutTransform, ParentLayerPoint& aScrollOffset, + const TimeDuration& aIncrement = TimeDuration::FromMilliseconds(0)) { + mcc->AdvanceBy(aIncrement); + bool ret = AdvanceAnimations(mcc->GetSampleTime()); + if (aOutTransform) { + *aOutTransform = + GetCurrentAsyncTransform(AsyncPanZoomController::eForHitTesting); + } + aScrollOffset = + GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting); + return ret; + } + + CSSPoint GetCompositedScrollOffset() const { + return GetCurrentAsyncScrollOffset( + AsyncPanZoomController::eForCompositing) / + GetFrameMetrics().GetZoom(); + } + + void SetWaitForMainThread() { mWaitForMainThread = true; } + + private: + bool mWaitForMainThread; + MockContentControllerDelayed* mcc; +}; + +class APZCTesterBase : public ::testing::Test { + public: + APZCTesterBase() { mcc = new NiceMock(); } + + virtual void SetUp() { gfxPlatform::GetPlatform(); } + + enum class PanOptions { + None = 0, + KeepFingerDown = 0x1, + /* + * Do not adjust the touch-start coordinates to overcome the touch-start + * tolerance threshold. If this option is passed, it's up to the caller + * to pass in coordinates that are sufficient to overcome the touch-start + * tolerance *and* cause the desired amount of scrolling. + */ + ExactCoordinates = 0x2, + NoFling = 0x4 + }; + + enum class PinchOptions { + None = 0, + LiftFinger1 = 0x1, + LiftFinger2 = 0x2, + /* + * The bitwise OR result of (LiftFinger1 | LiftFinger2). + * Defined explicitly here because it is used as the default + * argument for PinchWithTouchInput which is defined BEFORE the + * definition of operator| for this class. + */ + LiftBothFingers = 0x3 + }; + + template + void Tap(const RefPtr& aTarget, const ScreenIntPoint& aPoint, + TimeDuration aTapLength, + nsEventStatus (*aOutEventStatuses)[2] = nullptr, + uint64_t* aOutInputBlockId = nullptr); + + template + void TapAndCheckStatus(const RefPtr& aTarget, + const ScreenIntPoint& aPoint, TimeDuration aTapLength); + + template + void Pan(const RefPtr& aTarget, + const ScreenIntPoint& aTouchStart, const ScreenIntPoint& aTouchEnd, + PanOptions aOptions = PanOptions::None, + nsTArray* aAllowedTouchBehaviors = nullptr, + nsEventStatus (*aOutEventStatuses)[4] = nullptr, + uint64_t* aOutInputBlockId = nullptr); + + /* + * A version of Pan() that only takes y coordinates rather than (x, y) points + * for the touch start and end points, and uses 10 for the x coordinates. + * This is for convenience, as most tests only need to pan in one direction. + */ + template + void Pan(const RefPtr& aTarget, int aTouchStartY, + int aTouchEndY, PanOptions aOptions = PanOptions::None, + nsTArray* aAllowedTouchBehaviors = nullptr, + nsEventStatus (*aOutEventStatuses)[4] = nullptr, + uint64_t* aOutInputBlockId = nullptr); + + /* + * Dispatches mock touch events to the apzc and checks whether apzc properly + * consumed them and triggered scrolling behavior. + */ + template + void PanAndCheckStatus(const RefPtr& aTarget, int aTouchStartY, + int aTouchEndY, bool aExpectConsumed, + nsTArray* aAllowedTouchBehaviors, + uint64_t* aOutInputBlockId = nullptr); + + template + void DoubleTap(const RefPtr& aTarget, + const ScreenIntPoint& aPoint, + nsEventStatus (*aOutEventStatuses)[4] = nullptr, + uint64_t (*aOutInputBlockIds)[2] = nullptr); + + template + void DoubleTapAndCheckStatus(const RefPtr& aTarget, + const ScreenIntPoint& aPoint, + uint64_t (*aOutInputBlockIds)[2] = nullptr); + + template + void PinchWithTouchInput( + const RefPtr& aTarget, const ScreenIntPoint& aFocus, + const ScreenIntPoint& aSecondFocus, float aScale, int& inputId, + nsTArray* aAllowedTouchBehaviors = nullptr, + nsEventStatus (*aOutEventStatuses)[4] = nullptr, + uint64_t* aOutInputBlockId = nullptr, + PinchOptions aOptions = PinchOptions::LiftBothFingers); + + // Pinch with one focus point. Zooms in place with no panning + template + void PinchWithTouchInput( + const RefPtr& aTarget, const ScreenIntPoint& aFocus, + float aScale, int& inputId, + nsTArray* aAllowedTouchBehaviors = nullptr, + nsEventStatus (*aOutEventStatuses)[4] = nullptr, + uint64_t* aOutInputBlockId = nullptr, + PinchOptions aOptions = PinchOptions::LiftBothFingers); + + template + void PinchWithTouchInputAndCheckStatus( + const RefPtr& aTarget, const ScreenIntPoint& aFocus, + float aScale, int& inputId, bool aShouldTriggerPinch, + nsTArray* aAllowedTouchBehaviors); + + template + void PinchWithPinchInput(const RefPtr& aTarget, + const ScreenIntPoint& aFocus, + const ScreenIntPoint& aSecondFocus, float aScale, + nsEventStatus (*aOutEventStatuses)[3] = nullptr); + + template + void PinchWithPinchInputAndCheckStatus(const RefPtr& aTarget, + const ScreenIntPoint& aFocus, + float aScale, + bool aShouldTriggerPinch); + + protected: + RefPtr mcc; +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(APZCTesterBase::PanOptions) +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(APZCTesterBase::PinchOptions) + +template +void APZCTesterBase::Tap(const RefPtr& aTarget, + const ScreenIntPoint& aPoint, TimeDuration aTapLength, + nsEventStatus (*aOutEventStatuses)[2], + uint64_t* aOutInputBlockId) { + APZEventResult result = TouchDown(aTarget, aPoint, mcc->Time()); + if (aOutEventStatuses) { + (*aOutEventStatuses)[0] = result.mStatus; + } + if (aOutInputBlockId) { + *aOutInputBlockId = result.mInputBlockId; + } + mcc->AdvanceBy(aTapLength); + + // If touch-action is enabled then simulate the allowed touch behaviour + // notification that the main thread is supposed to deliver. + if (StaticPrefs::layout_css_touch_action_enabled() && + result.mStatus != nsEventStatus_eConsumeNoDefault) { + SetDefaultAllowedTouchBehavior(aTarget, result.mInputBlockId); + } + + result.mStatus = TouchUp(aTarget, aPoint, mcc->Time()); + if (aOutEventStatuses) { + (*aOutEventStatuses)[1] = result.mStatus; + } +} + +template +void APZCTesterBase::TapAndCheckStatus(const RefPtr& aTarget, + const ScreenIntPoint& aPoint, + TimeDuration aTapLength) { + nsEventStatus statuses[2]; + Tap(aTarget, aPoint, aTapLength, &statuses); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[1]); +} + +template +void APZCTesterBase::Pan(const RefPtr& aTarget, + const ScreenIntPoint& aTouchStart, + const ScreenIntPoint& aTouchEnd, PanOptions aOptions, + nsTArray* aAllowedTouchBehaviors, + nsEventStatus (*aOutEventStatuses)[4], + uint64_t* aOutInputBlockId) { + // Reduce the touch start and move tolerance to a tiny value. + // We can't use a scoped pref because this value might be read at some later + // time when the events are actually processed, rather than when we deliver + // them. + Preferences::SetFloat("apz.touch_start_tolerance", 1.0f / 1000.0f); + Preferences::SetFloat("apz.touch_move_tolerance", 0.0f); + int overcomeTouchToleranceX = 0; + int overcomeTouchToleranceY = 0; + if (!(aOptions & PanOptions::ExactCoordinates)) { + // Have the direction of the adjustment to overcome the touch tolerance + // match the direction of the entire gesture, otherwise we run into + // trouble such as accidentally activating the axis lock. + if (aTouchStart.x != aTouchEnd.x) { + overcomeTouchToleranceX = 1; + } + if (aTouchStart.y != aTouchEnd.y) { + overcomeTouchToleranceY = 1; + } + } + + const TimeDuration TIME_BETWEEN_TOUCH_EVENT = + TimeDuration::FromMilliseconds(20); + + // Even if the caller doesn't care about the block id, we need it to set the + // allowed touch behaviour below, so make sure aOutInputBlockId is non-null. + uint64_t blockId; + if (!aOutInputBlockId) { + aOutInputBlockId = &blockId; + } + + // Make sure the move is large enough to not be handled as a tap + APZEventResult result = + TouchDown(aTarget, + ScreenIntPoint(aTouchStart.x + overcomeTouchToleranceX, + aTouchStart.y + overcomeTouchToleranceY), + mcc->Time()); + if (aOutInputBlockId) { + *aOutInputBlockId = result.mInputBlockId; + } + if (aOutEventStatuses) { + (*aOutEventStatuses)[0] = result.mStatus; + } + + mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + + // Allowed touch behaviours must be set after sending touch-start. + if (result.mStatus != nsEventStatus_eConsumeNoDefault) { + if (aAllowedTouchBehaviors) { + EXPECT_EQ(1UL, aAllowedTouchBehaviors->Length()); + aTarget->SetAllowedTouchBehavior(*aOutInputBlockId, + *aAllowedTouchBehaviors); + } else if (StaticPrefs::layout_css_touch_action_enabled()) { + SetDefaultAllowedTouchBehavior(aTarget, *aOutInputBlockId); + } + } + + result.mStatus = TouchMove(aTarget, aTouchStart, mcc->Time()); + if (aOutEventStatuses) { + (*aOutEventStatuses)[1] = result.mStatus; + } + + mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + + const int numSteps = 3; + auto stepVector = (aTouchEnd - aTouchStart) / numSteps; + for (int k = 1; k < numSteps; k++) { + auto stepPoint = aTouchStart + stepVector * k; + Unused << TouchMove(aTarget, stepPoint, mcc->Time()); + + mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + } + + result.mStatus = TouchMove(aTarget, aTouchEnd, mcc->Time()); + if (aOutEventStatuses) { + (*aOutEventStatuses)[2] = result.mStatus; + } + + mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + + if (!(aOptions & PanOptions::KeepFingerDown)) { + result.mStatus = TouchUp(aTarget, aTouchEnd, mcc->Time()); + } else { + result.mStatus = nsEventStatus_eIgnore; + } + if (aOutEventStatuses) { + (*aOutEventStatuses)[3] = result.mStatus; + } + + if ((aOptions & PanOptions::NoFling)) { + aTarget->CancelAnimation(); + } + + // Don't increment the time here. Animations started on touch-up, such as + // flings, are affected by elapsed time, and we want to be able to sample + // them immediately after they start, without time having elapsed. +} + +template +void APZCTesterBase::Pan(const RefPtr& aTarget, int aTouchStartY, + int aTouchEndY, PanOptions aOptions, + nsTArray* aAllowedTouchBehaviors, + nsEventStatus (*aOutEventStatuses)[4], + uint64_t* aOutInputBlockId) { + Pan(aTarget, ScreenIntPoint(10, aTouchStartY), ScreenIntPoint(10, aTouchEndY), + aOptions, aAllowedTouchBehaviors, aOutEventStatuses, aOutInputBlockId); +} + +template +void APZCTesterBase::PanAndCheckStatus( + const RefPtr& aTarget, int aTouchStartY, int aTouchEndY, + bool aExpectConsumed, nsTArray* aAllowedTouchBehaviors, + uint64_t* aOutInputBlockId) { + nsEventStatus statuses[4]; // down, move, move, up + Pan(aTarget, aTouchStartY, aTouchEndY, PanOptions::None, + aAllowedTouchBehaviors, &statuses, aOutInputBlockId); + + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]); + + nsEventStatus touchMoveStatus; + if (aExpectConsumed) { + touchMoveStatus = nsEventStatus_eConsumeDoDefault; + } else { + touchMoveStatus = nsEventStatus_eIgnore; + } + EXPECT_EQ(touchMoveStatus, statuses[1]); + EXPECT_EQ(touchMoveStatus, statuses[2]); +} + +template +void APZCTesterBase::DoubleTap(const RefPtr& aTarget, + const ScreenIntPoint& aPoint, + nsEventStatus (*aOutEventStatuses)[4], + uint64_t (*aOutInputBlockIds)[2]) { + APZEventResult result = TouchDown(aTarget, aPoint, mcc->Time()); + if (aOutEventStatuses) { + (*aOutEventStatuses)[0] = result.mStatus; + } + if (aOutInputBlockIds) { + (*aOutInputBlockIds)[0] = result.mInputBlockId; + } + mcc->AdvanceByMillis(10); + + // If touch-action is enabled then simulate the allowed touch behaviour + // notification that the main thread is supposed to deliver. + if (StaticPrefs::layout_css_touch_action_enabled() && + result.mStatus != nsEventStatus_eConsumeNoDefault) { + SetDefaultAllowedTouchBehavior(aTarget, result.mInputBlockId); + } + + result.mStatus = TouchUp(aTarget, aPoint, mcc->Time()); + if (aOutEventStatuses) { + (*aOutEventStatuses)[1] = result.mStatus; + } + mcc->AdvanceByMillis(10); + result = TouchDown(aTarget, aPoint, mcc->Time()); + if (aOutEventStatuses) { + (*aOutEventStatuses)[2] = result.mStatus; + } + if (aOutInputBlockIds) { + (*aOutInputBlockIds)[1] = result.mInputBlockId; + } + mcc->AdvanceByMillis(10); + + if (StaticPrefs::layout_css_touch_action_enabled() && + result.mStatus != nsEventStatus_eConsumeNoDefault) { + SetDefaultAllowedTouchBehavior(aTarget, result.mInputBlockId); + } + + result.mStatus = TouchUp(aTarget, aPoint, mcc->Time()); + if (aOutEventStatuses) { + (*aOutEventStatuses)[3] = result.mStatus; + } +} + +template +void APZCTesterBase::DoubleTapAndCheckStatus( + const RefPtr& aTarget, const ScreenIntPoint& aPoint, + uint64_t (*aOutInputBlockIds)[2]) { + nsEventStatus statuses[4]; + DoubleTap(aTarget, aPoint, &statuses, aOutInputBlockIds); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[1]); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[2]); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[3]); +} + +template +void APZCTesterBase::PinchWithTouchInput( + const RefPtr& aTarget, const ScreenIntPoint& aFocus, + float aScale, int& inputId, nsTArray* aAllowedTouchBehaviors, + nsEventStatus (*aOutEventStatuses)[4], uint64_t* aOutInputBlockId, + PinchOptions aOptions) { + // Perform a pinch gesture with the same start & end focus point + PinchWithTouchInput(aTarget, aFocus, aFocus, aScale, inputId, + aAllowedTouchBehaviors, aOutEventStatuses, + aOutInputBlockId, aOptions); +} + +template +void APZCTesterBase::PinchWithTouchInput( + const RefPtr& aTarget, const ScreenIntPoint& aFocus, + const ScreenIntPoint& aSecondFocus, float aScale, int& inputId, + nsTArray* aAllowedTouchBehaviors, + nsEventStatus (*aOutEventStatuses)[4], uint64_t* aOutInputBlockId, + PinchOptions aOptions) { + // Having pinch coordinates in float type may cause problems with + // high-precision scale values since SingleTouchData accepts integer value. + // But for trivial tests it should be ok. + float pinchLength = 100.0; + float pinchLengthScaled = pinchLength * aScale; + + // Even if the caller doesn't care about the block id, we need it to set the + // allowed touch behaviour below, so make sure aOutInputBlockId is non-null. + uint64_t blockId; + if (!aOutInputBlockId) { + aOutInputBlockId = &blockId; + } + + const TimeDuration TIME_BETWEEN_TOUCH_EVENT = + TimeDuration::FromMilliseconds(20); + + MultiTouchInput mtiStart = + MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, mcc->Time(), 0); + mtiStart.mTouches.AppendElement(CreateSingleTouchData(inputId, aFocus)); + mtiStart.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, aFocus)); + nsEventStatus status = aTarget->ReceiveInputEvent(mtiStart, aOutInputBlockId); + if (aOutEventStatuses) { + (*aOutEventStatuses)[0] = status; + } + + mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + + if (aAllowedTouchBehaviors) { + EXPECT_EQ(2UL, aAllowedTouchBehaviors->Length()); + aTarget->SetAllowedTouchBehavior(*aOutInputBlockId, + *aAllowedTouchBehaviors); + } else if (StaticPrefs::layout_css_touch_action_enabled()) { + SetDefaultAllowedTouchBehavior(aTarget, *aOutInputBlockId, 2); + } + + ScreenIntPoint pinchStartPoint1(aFocus.x - int32_t(pinchLength), aFocus.y); + ScreenIntPoint pinchStartPoint2(aFocus.x + int32_t(pinchLength), aFocus.y); + + MultiTouchInput mtiMove1 = + MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, mcc->Time(), 0); + mtiMove1.mTouches.AppendElement( + CreateSingleTouchData(inputId, pinchStartPoint1)); + mtiMove1.mTouches.AppendElement( + CreateSingleTouchData(inputId + 1, pinchStartPoint2)); + status = aTarget->ReceiveInputEvent(mtiMove1, nullptr); + if (aOutEventStatuses) { + (*aOutEventStatuses)[1] = status; + } + + mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + + // Pinch instantly but move in steps. + const int numSteps = 3; + auto stepVector = (aSecondFocus - aFocus) / numSteps; + for (int k = 1; k < numSteps; k++) { + ScreenIntPoint stepFocus = aFocus + stepVector * k; + ScreenIntPoint stepPoint1(stepFocus.x - int32_t(pinchLengthScaled), + stepFocus.y); + ScreenIntPoint stepPoint2(stepFocus.x + int32_t(pinchLengthScaled), + stepFocus.y); + MultiTouchInput mtiMoveStep = + MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, mcc->Time(), 0); + mtiMoveStep.mTouches.AppendElement( + CreateSingleTouchData(inputId, stepPoint1)); + mtiMoveStep.mTouches.AppendElement( + CreateSingleTouchData(inputId + 1, stepPoint2)); + Unused << aTarget->ReceiveInputEvent(mtiMoveStep, nullptr); + + mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + } + + ScreenIntPoint pinchEndPoint1(aSecondFocus.x - int32_t(pinchLengthScaled), + aSecondFocus.y); + ScreenIntPoint pinchEndPoint2(aSecondFocus.x + int32_t(pinchLengthScaled), + aSecondFocus.y); + + MultiTouchInput mtiMove2 = + MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, mcc->Time(), 0); + mtiMove2.mTouches.AppendElement( + CreateSingleTouchData(inputId, pinchEndPoint1)); + mtiMove2.mTouches.AppendElement( + CreateSingleTouchData(inputId + 1, pinchEndPoint2)); + status = aTarget->ReceiveInputEvent(mtiMove2, nullptr); + if (aOutEventStatuses) { + (*aOutEventStatuses)[2] = status; + } + + if (aOptions & (PinchOptions::LiftFinger1 | PinchOptions::LiftFinger2)) { + mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + + MultiTouchInput mtiEnd = + MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, mcc->Time(), 0); + if (aOptions & PinchOptions::LiftFinger1) { + mtiEnd.mTouches.AppendElement( + CreateSingleTouchData(inputId, pinchEndPoint1)); + } + if (aOptions & PinchOptions::LiftFinger2) { + mtiEnd.mTouches.AppendElement( + CreateSingleTouchData(inputId + 1, pinchEndPoint2)); + } + status = aTarget->ReceiveInputEvent(mtiEnd, nullptr); + if (aOutEventStatuses) { + (*aOutEventStatuses)[3] = status; + } + } + + inputId += 2; +} + +template +void APZCTesterBase::PinchWithTouchInputAndCheckStatus( + const RefPtr& aTarget, const ScreenIntPoint& aFocus, + float aScale, int& inputId, bool aShouldTriggerPinch, + nsTArray* aAllowedTouchBehaviors) { + nsEventStatus statuses[4]; // down, move, move, up + PinchWithTouchInput(aTarget, aFocus, aScale, inputId, aAllowedTouchBehaviors, + &statuses); + + nsEventStatus expectedMoveStatus = aShouldTriggerPinch + ? nsEventStatus_eConsumeDoDefault + : nsEventStatus_eIgnore; + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]); + EXPECT_EQ(expectedMoveStatus, statuses[1]); + EXPECT_EQ(expectedMoveStatus, statuses[2]); +} + +template +void APZCTesterBase::PinchWithPinchInput( + const RefPtr& aTarget, const ScreenIntPoint& aFocus, + const ScreenIntPoint& aSecondFocus, float aScale, + nsEventStatus (*aOutEventStatuses)[3]) { + const TimeDuration TIME_BETWEEN_PINCH_INPUT = + TimeDuration::FromMilliseconds(50); + + nsEventStatus actualStatus = aTarget->ReceiveInputEvent( + CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_START, aFocus, + 10.0, 10.0, mcc->Time()), + nullptr); + if (aOutEventStatuses) { + (*aOutEventStatuses)[0] = actualStatus; + } + mcc->AdvanceBy(TIME_BETWEEN_PINCH_INPUT); + + actualStatus = aTarget->ReceiveInputEvent( + CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_SCALE, + aSecondFocus, 10.0 * aScale, 10.0, mcc->Time()), + nullptr); + if (aOutEventStatuses) { + (*aOutEventStatuses)[1] = actualStatus; + } + mcc->AdvanceBy(TIME_BETWEEN_PINCH_INPUT); + + actualStatus = aTarget->ReceiveInputEvent( + CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_END, aSecondFocus, + 10.0 * aScale, 10.0 * aScale, mcc->Time()), + nullptr); + if (aOutEventStatuses) { + (*aOutEventStatuses)[2] = actualStatus; + } +} + +template +void APZCTesterBase::PinchWithPinchInputAndCheckStatus( + const RefPtr& aTarget, const ScreenIntPoint& aFocus, + float aScale, bool aShouldTriggerPinch) { + nsEventStatus statuses[3]; // scalebegin, scale, scaleend + PinchWithPinchInput(aTarget, aFocus, aFocus, aScale, &statuses); + + nsEventStatus expectedStatus = aShouldTriggerPinch + ? nsEventStatus_eConsumeDoDefault + : nsEventStatus_eIgnore; + EXPECT_EQ(expectedStatus, statuses[0]); + EXPECT_EQ(expectedStatus, statuses[1]); +} + +AsyncPanZoomController* TestAPZCTreeManager::NewAPZCInstance( + LayersId aLayersId, GeckoContentController* aController) { + MockContentControllerDelayed* mcc = + static_cast(aController); + return new TestAsyncPanZoomController( + aLayersId, mcc, this, AsyncPanZoomController::USE_GESTURE_DETECTOR); +} + +inline FrameMetrics TestFrameMetrics() { + FrameMetrics fm; + + fm.SetDisplayPort(CSSRect(0, 0, 10, 10)); + fm.SetCompositionBounds(ParentLayerRect(0, 0, 10, 10)); + fm.SetCriticalDisplayPort(CSSRect(0, 0, 10, 10)); + fm.SetScrollableRect(CSSRect(0, 0, 100, 100)); + + return fm; +} + +#endif // mozilla_layers_APZTestCommon_h diff --git a/gfx/layers/apz/test/gtest/InputUtils.h b/gfx/layers/apz/test/gtest/InputUtils.h new file mode 100644 index 0000000000..291b98c186 --- /dev/null +++ b/gfx/layers/apz/test/gtest/InputUtils.h @@ -0,0 +1,138 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_InputUtils_h +#define mozilla_layers_InputUtils_h + +/** + * Defines a set of utility functions for generating input events + * to an APZC/APZCTM during APZ gtests. + */ + +#include "APZTestCommon.h" + +/* The InputReceiver template parameter used in the helper functions below needs + * to be a class that implements functions with the signatures: + * APZEventResult ReceiveInputEvent(const InputData& aEvent); + * void SetAllowedTouchBehavior(uint64_t aInputBlockId, + * const nsTArray& aBehaviours); + * The classes that currently implement these are APZCTreeManager and + * TestAsyncPanZoomController. Using this template allows us to test individual + * APZC instances in isolation and also an entire APZ tree, while using the same + * code to dispatch input events. + */ + +template +void SetDefaultAllowedTouchBehavior(const RefPtr& aTarget, + uint64_t aInputBlockId, + int touchPoints = 1) { + nsTArray defaultBehaviors; + // use the default value where everything is allowed + for (int i = 0; i < touchPoints; i++) { + defaultBehaviors.AppendElement( + mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN | + mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN | + mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM | + mozilla::layers::AllowedTouchBehavior::DOUBLE_TAP_ZOOM); + } + aTarget->SetAllowedTouchBehavior(aInputBlockId, defaultBehaviors); +} + +inline MultiTouchInput CreateMultiTouchInput( + MultiTouchInput::MultiTouchType aType, TimeStamp aTime) { + return MultiTouchInput(aType, MillisecondsSinceStartup(aTime), aTime, 0); +} + +template +APZEventResult TouchDown(const RefPtr& aTarget, + const ScreenIntPoint& aPoint, TimeStamp aTime) { + MultiTouchInput mti = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, aTime); + mti.mTouches.AppendElement(CreateSingleTouchData(0, aPoint)); + return aTarget->ReceiveInputEvent(mti); +} + +template +nsEventStatus TouchMove(const RefPtr& aTarget, + const ScreenIntPoint& aPoint, TimeStamp aTime) { + MultiTouchInput mti = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, aTime); + mti.mTouches.AppendElement(CreateSingleTouchData(0, aPoint)); + return aTarget->ReceiveInputEvent(mti).mStatus; +} + +template +nsEventStatus TouchUp(const RefPtr& aTarget, + const ScreenIntPoint& aPoint, TimeStamp aTime) { + MultiTouchInput mti = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_END, aTime); + mti.mTouches.AppendElement(CreateSingleTouchData(0, aPoint)); + return aTarget->ReceiveInputEvent(mti).mStatus; +} + +template +APZEventResult Wheel(const RefPtr& aTarget, + const ScreenIntPoint& aPoint, const ScreenPoint& aDelta, + TimeStamp aTime) { + ScrollWheelInput input(MillisecondsSinceStartup(aTime), aTime, 0, + ScrollWheelInput::SCROLLMODE_INSTANT, + ScrollWheelInput::SCROLLDELTA_PIXEL, aPoint, aDelta.x, + aDelta.y, false, WheelDeltaAdjustmentStrategy::eNone); + return aTarget->ReceiveInputEvent(input); +} + +template +APZEventResult SmoothWheel(const RefPtr& aTarget, + const ScreenIntPoint& aPoint, + const ScreenPoint& aDelta, TimeStamp aTime) { + ScrollWheelInput input(MillisecondsSinceStartup(aTime), aTime, 0, + ScrollWheelInput::SCROLLMODE_SMOOTH, + ScrollWheelInput::SCROLLDELTA_LINE, aPoint, aDelta.x, + aDelta.y, false, WheelDeltaAdjustmentStrategy::eNone); + return aTarget->ReceiveInputEvent(input); +} + +template +APZEventResult MouseDown(const RefPtr& aTarget, + const ScreenIntPoint& aPoint, TimeStamp aTime) { + MouseInput input(MouseInput::MOUSE_DOWN, + MouseInput::ButtonType::PRIMARY_BUTTON, 0, 0, aPoint, + MillisecondsSinceStartup(aTime), aTime, 0); + return aTarget->ReceiveInputEvent(input); +} + +template +APZEventResult MouseMove(const RefPtr& aTarget, + const ScreenIntPoint& aPoint, TimeStamp aTime) { + MouseInput input(MouseInput::MOUSE_MOVE, + MouseInput::ButtonType::PRIMARY_BUTTON, 0, 0, aPoint, + MillisecondsSinceStartup(aTime), aTime, 0); + return aTarget->ReceiveInputEvent(input); +} + +template +APZEventResult MouseUp(const RefPtr& aTarget, + const ScreenIntPoint& aPoint, TimeStamp aTime) { + MouseInput input(MouseInput::MOUSE_UP, MouseInput::ButtonType::PRIMARY_BUTTON, + 0, 0, aPoint, MillisecondsSinceStartup(aTime), aTime, 0); + return aTarget->ReceiveInputEvent(input); +} + +template +APZEventResult PanGesture(PanGestureInput::PanGestureType aType, + const RefPtr& aTarget, + const ScreenIntPoint& aPoint, + const ScreenPoint& aDelta, TimeStamp aTime) { + PanGestureInput input(aType, MillisecondsSinceStartup(aTime), aTime, aPoint, + aDelta, 0 /* Modifiers */); + if (aType == PanGestureInput::PANGESTURE_END) { + input.mFollowedByMomentum = true; + } + + return aTarget->ReceiveInputEvent(input); +} + +#endif // mozilla_layers_InputUtils_h diff --git a/gfx/layers/apz/test/gtest/TestBasic.cpp b/gfx/layers/apz/test/gtest/TestBasic.cpp new file mode 100644 index 0000000000..9877b6d120 --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestBasic.cpp @@ -0,0 +1,530 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "APZCBasicTester.h" +#include "APZTestCommon.h" + +#include "InputUtils.h" + +TEST_F(APZCBasicTester, Overzoom) { + // the visible area of the document in CSS pixels is x=10 y=0 w=100 h=100 + FrameMetrics fm; + fm.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100)); + fm.SetScrollableRect(CSSRect(0, 0, 125, 150)); + fm.SetVisualScrollOffset(CSSPoint(10, 0)); + fm.SetZoom(CSSToParentLayerScale2D(1.0, 1.0)); + fm.SetIsRootContent(true); + apzc->SetFrameMetrics(fm); + + MakeApzcZoomable(); + + EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1); + + PinchWithPinchInputAndCheckStatus(apzc, ScreenIntPoint(50, 50), 0.5, true); + + fm = apzc->GetFrameMetrics(); + EXPECT_EQ(0.8f, fm.GetZoom().ToScaleFactor().scale); + // bug 936721 - PGO builds introduce rounding error so + // use a fuzzy match instead + EXPECT_LT(std::abs(fm.GetVisualScrollOffset().x), 1e-5); + EXPECT_LT(std::abs(fm.GetVisualScrollOffset().y), 1e-5); +} + +TEST_F(APZCBasicTester, SimpleTransform) { + ParentLayerPoint pointOut; + AsyncTransform viewTransformOut; + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + + EXPECT_EQ(ParentLayerPoint(), pointOut); + EXPECT_EQ(AsyncTransform(), viewTransformOut); +} + +TEST_F(APZCBasicTester, ComplexTransform) { + // This test assumes there is a page that gets rendered to + // two layers. In CSS pixels, the first layer is 50x50 and + // the second layer is 25x50. The widget scale factor is 3.0 + // and the presShell resolution is 2.0. Therefore, these layers + // end up being 300x300 and 150x300 in layer pixels. + // + // The second (child) layer has an additional CSS transform that + // stretches it by 2.0 on the x-axis. Therefore, after applying + // CSS transforms, the two layers are the same size in screen + // pixels. + // + // The screen itself is 24x24 in screen pixels (therefore 4x4 in + // CSS pixels). The displayport is 1 extra CSS pixel on all + // sides. + + RefPtr childApzc = + new TestAsyncPanZoomController(LayersId{0}, mcc, tm); + + const char* layerTreeSyntax = "c(c)"; + // LayerID 0 1 + nsIntRegion layerVisibleRegion[] = { + nsIntRegion(IntRect(0, 0, 300, 300)), + nsIntRegion(IntRect(0, 0, 150, 300)), + }; + Matrix4x4 transforms[] = { + Matrix4x4(), + Matrix4x4(), + }; + transforms[0].PostScale( + 0.5f, 0.5f, + 1.0f); // this results from the 2.0 resolution on the root layer + transforms[1].PostScale( + 2.0f, 1.0f, + 1.0f); // this is the 2.0 x-axis CSS transform on the child layer + + nsTArray > layers; + RefPtr lm; + RefPtr root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, + transforms, lm, layers); + + ScrollMetadata metadata; + FrameMetrics& metrics = metadata.GetMetrics(); + metrics.SetCompositionBounds(ParentLayerRect(0, 0, 24, 24)); + metrics.SetDisplayPort(CSSRect(-1, -1, 6, 6)); + metrics.SetVisualScrollOffset(CSSPoint(10, 10)); + metrics.SetLayoutViewport(CSSRect(10, 10, 8, 8)); + metrics.SetScrollableRect(CSSRect(0, 0, 50, 50)); + metrics.SetCumulativeResolution(LayoutDeviceToLayerScale2D(2, 2)); + metrics.SetPresShellResolution(2.0f); + metrics.SetZoom(CSSToParentLayerScale2D(6, 6)); + metrics.SetDevPixelsPerCSSPixel(CSSToLayoutDeviceScale(3)); + metrics.SetScrollId(ScrollableLayerGuid::START_SCROLL_ID); + + ScrollMetadata childMetadata = metadata; + FrameMetrics& childMetrics = childMetadata.GetMetrics(); + childMetrics.SetScrollId(ScrollableLayerGuid::START_SCROLL_ID + 1); + + layers[0]->SetScrollMetadata(metadata); + layers[1]->SetScrollMetadata(childMetadata); + + ParentLayerPoint pointOut; + AsyncTransform viewTransformOut; + + // Both the parent and child layer should behave exactly the same here, + // because the CSS transform on the child layer does not affect the + // SampleContentTransformForFrame code + + // initial transform + apzc->SetFrameMetrics(metrics); + apzc->NotifyLayersUpdated(metadata, true, true); + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint()), + viewTransformOut); + EXPECT_EQ(ParentLayerPoint(60, 60), pointOut); + + childApzc->SetFrameMetrics(childMetrics); + childApzc->NotifyLayersUpdated(childMetadata, true, true); + childApzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint()), + viewTransformOut); + EXPECT_EQ(ParentLayerPoint(60, 60), pointOut); + + // do an async scroll by 5 pixels and check the transform + metrics.ScrollBy(CSSPoint(5, 0)); + apzc->SetFrameMetrics(metrics); + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + EXPECT_EQ( + AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint(-30, 0)), + viewTransformOut); + EXPECT_EQ(ParentLayerPoint(90, 60), pointOut); + + childMetrics.ScrollBy(CSSPoint(5, 0)); + childApzc->SetFrameMetrics(childMetrics); + childApzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + EXPECT_EQ( + AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint(-30, 0)), + viewTransformOut); + EXPECT_EQ(ParentLayerPoint(90, 60), pointOut); + + // do an async zoom of 1.5x and check the transform + metrics.ZoomBy(1.5f); + apzc->SetFrameMetrics(metrics); + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + EXPECT_EQ( + AsyncTransform(LayerToParentLayerScale(1.5), ParentLayerPoint(-45, 0)), + viewTransformOut); + EXPECT_EQ(ParentLayerPoint(135, 90), pointOut); + + childMetrics.ZoomBy(1.5f); + childApzc->SetFrameMetrics(childMetrics); + childApzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + EXPECT_EQ( + AsyncTransform(LayerToParentLayerScale(1.5), ParentLayerPoint(-45, 0)), + viewTransformOut); + EXPECT_EQ(ParentLayerPoint(135, 90), pointOut); + + childApzc->Destroy(); +} + +TEST_F(APZCBasicTester, Fling) { + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); + int touchStart = 50; + int touchEnd = 10; + ParentLayerPoint pointOut; + AsyncTransform viewTransformOut; + + // Fling down. Each step scroll further down + Pan(apzc, touchStart, touchEnd); + ParentLayerPoint lastPoint; + for (int i = 1; i < 50; i += 1) { + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut, + TimeDuration::FromMilliseconds(1)); + EXPECT_GT(pointOut.y, lastPoint.y); + lastPoint = pointOut; + } +} + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +TEST_F(APZCBasicTester, FlingIntoOverscroll) { + // Enable overscrolling. + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); + + // Scroll down by 25 px. Don't fling for simplicity. + Pan(apzc, 50, 25, PanOptions::NoFling); + + // Now scroll back up by 20px, this time flinging after. + // The fling should cover the remaining 5 px of room to scroll, then + // go into overscroll, and finally snap-back to recover from overscroll. + Pan(apzc, 25, 45); + const TimeDuration increment = TimeDuration::FromMilliseconds(1); + bool reachedOverscroll = false; + bool recoveredFromOverscroll = false; + while (apzc->AdvanceAnimations(mcc->GetSampleTime())) { + if (!reachedOverscroll && apzc->IsOverscrolled()) { + reachedOverscroll = true; + } + if (reachedOverscroll && !apzc->IsOverscrolled()) { + recoveredFromOverscroll = true; + } + mcc->AdvanceBy(increment); + } + EXPECT_TRUE(reachedOverscroll); + EXPECT_TRUE(recoveredFromOverscroll); +} +#endif + +TEST_F(APZCBasicTester, PanningTransformNotifications) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + // Scroll down by 25 px. Ensure we only get one set of + // state change notifications. + // + // Then, scroll back up by 20px, this time flinging after. + // The fling should cover the remaining 5 px of room to scroll, then + // go into overscroll, and finally snap-back to recover from overscroll. + // Again, ensure we only get one set of state change notifications for + // this entire procedure. + + MockFunction check; + { + InSequence s; + EXPECT_CALL(check, Call("Simple pan")); + EXPECT_CALL(*mcc, + NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eStartTouch, _)) + .Times(1); + EXPECT_CALL( + *mcc, + NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eTransformBegin, _)) + .Times(1); + EXPECT_CALL( + *mcc, NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eStartPanning, _)) + .Times(1); + EXPECT_CALL(*mcc, + NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eEndTouch, _)) + .Times(1); + EXPECT_CALL( + *mcc, NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eTransformEnd, _)) + .Times(1); + EXPECT_CALL(check, Call("Complex pan")); + EXPECT_CALL(*mcc, + NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eStartTouch, _)) + .Times(1); + EXPECT_CALL( + *mcc, + NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eTransformBegin, _)) + .Times(1); + EXPECT_CALL( + *mcc, NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eStartPanning, _)) + .Times(1); + EXPECT_CALL(*mcc, + NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eEndTouch, _)) + .Times(1); + EXPECT_CALL( + *mcc, NotifyAPZStateChange( + _, GeckoContentController::APZStateChange::eTransformEnd, _)) + .Times(1); + EXPECT_CALL(check, Call("Done")); + } + + check.Call("Simple pan"); + Pan(apzc, 50, 25, PanOptions::NoFling); + check.Call("Complex pan"); + Pan(apzc, 25, 45); + apzc->AdvanceAnimationsUntilEnd(); + check.Call("Done"); +} + +void APZCBasicTester::PanIntoOverscroll() { + int touchStart = 500; + int touchEnd = 10; + Pan(apzc, touchStart, touchEnd); + EXPECT_TRUE(apzc->IsOverscrolled()); +} + +void APZCBasicTester::TestOverscroll() { + // Pan sufficiently to hit overscroll behavior + PanIntoOverscroll(); + + // Check that we recover from overscroll via an animation. + ParentLayerPoint expectedScrollOffset(0, GetScrollRange().YMost()); + SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset); +} + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +TEST_F(APZCBasicTester, OverScrollPanning) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + TestOverscroll(); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +// Tests that an overscroll animation doesn't trigger an assertion failure +// in the case where a sample has a velocity of zero. +TEST_F(APZCBasicTester, OverScroll_Bug1152051a) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + // Doctor the prefs to make the velocity zero at the end of the first sample. + + // This ensures our incoming velocity to the overscroll animation is + // a round(ish) number, 4.9 (that being the distance of the pan before + // overscroll, which is 500 - 10 = 490 pixels, divided by the duration of + // the pan, which is 100 ms). + SCOPED_GFX_PREF_FLOAT("apz.fling_friction", 0); + + TestOverscroll(); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +// Tests that ending an overscroll animation doesn't leave around state that +// confuses the next overscroll animation. +TEST_F(APZCBasicTester, OverScroll_Bug1152051b) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + SCOPED_GFX_PREF_FLOAT("apz.overscroll.stop_distance_threshold", 0.1f); + + // Pan sufficiently to hit overscroll behavior + PanIntoOverscroll(); + + // Sample animations once, to give the fling animation started on touch-up + // a chance to realize it's overscrolled, and schedule a call to + // HandleFlingOverscroll(). + SampleAnimationOnce(); + + // This advances the time and runs the HandleFlingOverscroll task scheduled in + // the previous call, which starts an overscroll animation. It then samples + // the overscroll animation once, to get it to initialize the first overscroll + // sample. + SampleAnimationOnce(); + + // Do a touch-down to cancel the overscroll animation, and then a touch-up + // to schedule a new one since we're still overscrolled. We don't pan because + // panning can trigger functions that clear the overscroll animation state + // in other ways. + APZEventResult result = TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time()); + if (StaticPrefs::layout_css_touch_action_enabled() && + result.mStatus != nsEventStatus_eConsumeNoDefault) { + SetDefaultAllowedTouchBehavior(apzc, result.mInputBlockId); + } + TouchUp(apzc, ScreenIntPoint(10, 10), mcc->Time()); + + // Sample the second overscroll animation to its end. + // If the ending of the first overscroll animation fails to clear state + // properly, this will assert. + ParentLayerPoint expectedScrollOffset(0, GetScrollRange().YMost()); + SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +// Tests that the page doesn't get stuck in an +// overscroll animation after a low-velocity pan. +TEST_F(APZCBasicTester, OverScrollAfterLowVelocityPan_Bug1343775) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + // Pan into overscroll with a velocity less than the + // apz.fling_min_velocity_threshold preference. + Pan(apzc, 10, 30); + + EXPECT_TRUE(apzc->IsOverscrolled()); + + apzc->AdvanceAnimationsUntilEnd(); + + // Check that we recovered from overscroll. + EXPECT_FALSE(apzc->IsOverscrolled()); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +TEST_F(APZCBasicTester, OverScrollAbort) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + // Pan sufficiently to hit overscroll behavior + int touchStart = 500; + int touchEnd = 10; + Pan(apzc, touchStart, touchEnd); + EXPECT_TRUE(apzc->IsOverscrolled()); + + ParentLayerPoint pointOut; + AsyncTransform viewTransformOut; + + // This sample call will run to the end of the fling animation + // and will schedule the overscroll animation. + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut, + TimeDuration::FromMilliseconds(10000)); + EXPECT_TRUE(apzc->IsOverscrolled()); + + // At this point, we have an active overscroll animation. + // Check that cancelling the animation clears the overscroll. + apzc->CancelAnimation(); + EXPECT_FALSE(apzc->IsOverscrolled()); + apzc->AssertStateIsReset(); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +TEST_F(APZCBasicTester, OverScrollPanningAbort) { + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + // Pan sufficiently to hit overscroll behaviour. Keep the finger down so + // the pan does not end. + int touchStart = 500; + int touchEnd = 10; + Pan(apzc, touchStart, touchEnd, PanOptions::KeepFingerDown); + EXPECT_TRUE(apzc->IsOverscrolled()); + + // Check that calling CancelAnimation() while the user is still panning + // (and thus no fling or snap-back animation has had a chance to start) + // clears the overscroll. + apzc->CancelAnimation(); + EXPECT_FALSE(apzc->IsOverscrolled()); + apzc->AssertStateIsReset(); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Maybe fails on Android +TEST_F(APZCBasicTester, ResumeInterruptedTouchDrag_Bug1592435) { + // Start a touch-drag and scroll some amount, not lifting the finger. + SCOPED_GFX_PREF_FLOAT("apz.touch_start_tolerance", 1.0f / 1000.0f); + ScreenIntPoint touchPos(10, 50); + uint64_t touchBlock = TouchDown(apzc, touchPos, mcc->Time()).mInputBlockId; + SetDefaultAllowedTouchBehavior(apzc, touchBlock); + for (int i = 0; i < 20; ++i) { + touchPos.y -= 1; + mcc->AdvanceByMillis(1); + TouchMove(apzc, touchPos, mcc->Time()); + } + + // Take note of the scroll offset before the interruption. + CSSPoint scrollOffsetBeforeInterruption = + apzc->GetFrameMetrics().GetVisualScrollOffset(); + + // Have the main thread interrupt the touch-drag by sending + // a main thread scroll update to a nearby location. + CSSPoint mainThreadOffset = scrollOffsetBeforeInterruption; + mainThreadOffset.y -= 5; + ScrollMetadata metadata = apzc->GetScrollMetadata(); + metadata.GetMetrics().SetLayoutScrollOffset(mainThreadOffset); + nsTArray scrollUpdates; + scrollUpdates.AppendElement(ScrollPositionUpdate::NewScroll( + ScrollOrigin::Other, CSSPoint::ToAppUnits(mainThreadOffset))); + metadata.SetScrollUpdates(scrollUpdates); + metadata.GetMetrics().SetScrollGeneration( + scrollUpdates.LastElement().GetGeneration()); + apzc->NotifyLayersUpdated(metadata, false, true); + + // Continue and finish the touch-drag gesture. + for (int i = 0; i < 20; ++i) { + touchPos.y -= 1; + mcc->AdvanceByMillis(1); + TouchMove(apzc, touchPos, mcc->Time()); + } + + // Check that the portion of the touch-drag that occurred after + // the interruption caused additional scrolling. + CSSPoint finalScrollOffset = apzc->GetFrameMetrics().GetVisualScrollOffset(); + EXPECT_GT(finalScrollOffset.y, scrollOffsetBeforeInterruption.y); + + // Now do the same thing, but for a visual scroll update. + scrollOffsetBeforeInterruption = + apzc->GetFrameMetrics().GetVisualScrollOffset(); + mainThreadOffset = scrollOffsetBeforeInterruption; + mainThreadOffset.y -= 5; + metadata = apzc->GetScrollMetadata(); + metadata.GetMetrics().SetVisualDestination(mainThreadOffset); + metadata.GetMetrics().SetScrollGeneration(ScrollGeneration::New()); + metadata.GetMetrics().SetVisualScrollUpdateType(FrameMetrics::eMainThread); + scrollUpdates.Clear(); + metadata.SetScrollUpdates(scrollUpdates); + apzc->NotifyLayersUpdated(metadata, false, true); + for (int i = 0; i < 20; ++i) { + touchPos.y -= 1; + mcc->AdvanceByMillis(1); + TouchMove(apzc, touchPos, mcc->Time()); + } + finalScrollOffset = apzc->GetFrameMetrics().GetVisualScrollOffset(); + EXPECT_GT(finalScrollOffset.y, scrollOffsetBeforeInterruption.y); + + // Clean up by ending the touch gesture. + mcc->AdvanceByMillis(1); + TouchUp(apzc, touchPos, mcc->Time()); +} +#endif + +TEST_F(APZCBasicTester, RelativeScrollOffset) { + // Set up initial conditions: zoomed in, layout offset at (100, 100), + // visual offset at (120, 120); the relative offset is therefore (20, 20). + ScrollMetadata metadata; + FrameMetrics& metrics = metadata.GetMetrics(); + metrics.SetScrollableRect(CSSRect(0, 0, 1000, 1000)); + metrics.SetLayoutViewport(CSSRect(100, 100, 100, 100)); + metrics.SetZoom(CSSToParentLayerScale2D(2.0, 2.0)); + metrics.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100)); + metrics.SetVisualScrollOffset(CSSPoint(120, 120)); + metrics.SetIsRootContent(true); + apzc->SetFrameMetrics(metrics); + + // Scroll the layout viewport to (200, 200). + ScrollMetadata mainThreadMetadata = metadata; + FrameMetrics& mainThreadMetrics = mainThreadMetadata.GetMetrics(); + mainThreadMetrics.SetLayoutScrollOffset(CSSPoint(200, 200)); + nsTArray scrollUpdates; + scrollUpdates.AppendElement(ScrollPositionUpdate::NewScroll( + ScrollOrigin::Other, CSSPoint::ToAppUnits(CSSPoint(200, 200)))); + mainThreadMetadata.SetScrollUpdates(scrollUpdates); + mainThreadMetrics.SetScrollGeneration( + scrollUpdates.LastElement().GetGeneration()); + apzc->NotifyLayersUpdated(mainThreadMetadata, /*isFirstPaint=*/false, + /*thisLayerTreeUpdated=*/true); + + // Check that the relative offset has been preserved. + metrics = apzc->GetFrameMetrics(); + EXPECT_EQ(metrics.GetLayoutScrollOffset(), CSSPoint(200, 200)); + EXPECT_EQ(metrics.GetVisualScrollOffset(), CSSPoint(220, 220)); +} diff --git a/gfx/layers/apz/test/gtest/TestEventRegions.cpp b/gfx/layers/apz/test/gtest/TestEventRegions.cpp new file mode 100644 index 0000000000..ee7fab12bf --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestEventRegions.cpp @@ -0,0 +1,410 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "APZCTreeManagerTester.h" +#include "APZTestCommon.h" +#include "InputUtils.h" +#include "mozilla/layers/LayersTypes.h" + +class APZEventRegionsTester : public APZCTreeManagerTester { + protected: + UniquePtr registration; + TestAsyncPanZoomController* rootApzc; + + void CreateEventRegionsLayerTree1() { + const char* layerTreeSyntax = "c(tt)"; + nsIntRegion layerVisibleRegions[] = { + nsIntRegion(IntRect(0, 0, 200, 200)), // root + nsIntRegion(IntRect(0, 0, 100, 200)), // left half + nsIntRegion(IntRect(0, 100, 200, 100)), // bottom half + }; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, + layers); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID); + SetScrollableFrameMetrics(layers[1], + ScrollableLayerGuid::START_SCROLL_ID + 1); + SetScrollableFrameMetrics(layers[2], + ScrollableLayerGuid::START_SCROLL_ID + 2); + SetScrollHandoff(layers[1], root); + SetScrollHandoff(layers[2], root); + + // Set up the event regions over a 200x200 area. The root layer has the + // whole 200x200 as the hit region; layers[1] has the left half and + // layers[2] has the bottom half. The bottom-left 100x100 area is also + // in the d-t-c region for both layers[1] and layers[2] (but layers[2] is + // on top so it gets the events by default if the main thread doesn't + // respond). + EventRegions regions(nsIntRegion(IntRect(0, 0, 200, 200))); + root->SetEventRegions(regions); + regions.mDispatchToContentHitRegion = + nsIntRegion(IntRect(0, 100, 100, 100)); + regions.mHitRegion = nsIntRegion(IntRect(0, 0, 100, 200)); + layers[1]->SetEventRegions(regions); + regions.mHitRegion = nsIntRegion(IntRect(0, 100, 200, 100)); + layers[2]->SetEventRegions(regions); + + registration = MakeUnique(manager, LayersId{0}, + root, mcc); + UpdateHitTestingTree(); + rootApzc = ApzcOf(root); + } + + void CreateEventRegionsLayerTree2() { + const char* layerTreeSyntax = "c(t)"; + nsIntRegion layerVisibleRegions[] = { + nsIntRegion(IntRect(0, 0, 100, 500)), + nsIntRegion(IntRect(0, 150, 100, 100)), + }; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, + layers); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID); + + // Set up the event regions so that the child thebes layer is positioned far + // away from the scrolling container layer. + EventRegions regions(nsIntRegion(IntRect(0, 0, 100, 100))); + root->SetEventRegions(regions); + regions.mHitRegion = nsIntRegion(IntRect(0, 150, 100, 100)); + layers[1]->SetEventRegions(regions); + + registration = MakeUnique(manager, LayersId{0}, + root, mcc); + UpdateHitTestingTree(); + rootApzc = ApzcOf(root); + } + + void CreateObscuringLayerTree() { + const char* layerTreeSyntax = "c(c(t)t)"; + // LayerID 0 1 2 3 + // 0 is the root. + // 1 is a parent scrollable layer. + // 2 is a child scrollable layer. + // 3 is the Obscurer, who ruins everything. + nsIntRegion layerVisibleRegions[] = { + // x coordinates are uninteresting + nsIntRegion(IntRect(0, 0, 200, 200)), // [0, 200] + nsIntRegion(IntRect(0, 0, 200, 200)), // [0, 200] + nsIntRegion(IntRect(0, 100, 200, 50)), // [100, 150] + nsIntRegion(IntRect(0, 100, 200, 100)) // [100, 200] + }; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, + layers); + + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 200, 200)); + SetScrollableFrameMetrics(layers[1], + ScrollableLayerGuid::START_SCROLL_ID + 1, + CSSRect(0, 0, 200, 300)); + SetScrollableFrameMetrics(layers[2], + ScrollableLayerGuid::START_SCROLL_ID + 2, + CSSRect(0, 0, 200, 100)); + SetScrollHandoff(layers[2], layers[1]); + SetScrollHandoff(layers[1], root); + + EventRegions regions(nsIntRegion(IntRect(0, 0, 200, 200))); + root->SetEventRegions(regions); + regions.mHitRegion = nsIntRegion(IntRect(0, 0, 200, 300)); + layers[1]->SetEventRegions(regions); + regions.mHitRegion = nsIntRegion(IntRect(0, 100, 200, 100)); + layers[2]->SetEventRegions(regions); + + registration = MakeUnique(manager, LayersId{0}, + root, mcc); + UpdateHitTestingTree(); + rootApzc = ApzcOf(root); + } + + void CreateBug1119497LayerTree() { + const char* layerTreeSyntax = "c(tt)"; + // LayerID 0 12 + // 0 is the root and has an APZC + // 1 is behind 2 and has an APZC + // 2 entirely covers 1 and should take all the input events, but has no APZC + // so hits to 2 should go to to the root APZC + nsIntRegion layerVisibleRegions[] = { + nsIntRegion(IntRect(0, 0, 100, 100)), + nsIntRegion(IntRect(0, 0, 100, 100)), + nsIntRegion(IntRect(0, 0, 100, 100)), + }; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, + layers); + + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID); + SetScrollableFrameMetrics(layers[1], + ScrollableLayerGuid::START_SCROLL_ID + 1); + + registration = MakeUnique(manager, LayersId{0}, + root, mcc); + UpdateHitTestingTree(); + } + + void CreateBug1117712LayerTree() { + const char* layerTreeSyntax = "c(c(t)t)"; + // LayerID 0 1 2 3 + // 0 is the root + // 1 is a container layer whose sole purpose to make a non-empty ancestor + // transform for 2, so that 2's screen-to-apzc and apzc-to-gecko + // transforms are different from 3's. + // 2 is a small layer that is the actual target + // 3 is a big layer obscuring 2 with a dispatch-to-content region + nsIntRegion layerVisibleRegions[] = { + nsIntRegion(IntRect(0, 0, 100, 100)), + nsIntRegion(IntRect(0, 0, 0, 0)), + nsIntRegion(IntRect(0, 0, 10, 10)), + nsIntRegion(IntRect(0, 0, 100, 100)), + }; + Matrix4x4 layerTransforms[] = { + Matrix4x4(), + Matrix4x4::Translation(50, 0, 0), + Matrix4x4(), + Matrix4x4(), + }; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, + layerTransforms, lm, layers); + + SetScrollableFrameMetrics(layers[2], ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 10, 10)); + SetScrollableFrameMetrics(layers[3], + ScrollableLayerGuid::START_SCROLL_ID + 1, + CSSRect(0, 0, 100, 100)); + SetScrollHandoff(layers[3], layers[2]); + + EventRegions regions(nsIntRegion(IntRect(0, 0, 10, 10))); + layers[2]->SetEventRegions(regions); + regions.mHitRegion = nsIntRegion(IntRect(0, 0, 100, 100)); + regions.mDispatchToContentHitRegion = nsIntRegion(IntRect(0, 0, 100, 100)); + layers[3]->SetEventRegions(regions); + + registration = MakeUnique(manager, LayersId{0}, + root, mcc); + UpdateHitTestingTree(); + } +}; + +TEST_F(APZEventRegionsTester, HitRegionImmediateResponse) { + SCOPED_GFX_VAR(UseWebRender, bool, false); + + CreateEventRegionsLayerTree1(); + + TestAsyncPanZoomController* root = ApzcOf(layers[0]); + TestAsyncPanZoomController* left = ApzcOf(layers[1]); + TestAsyncPanZoomController* bottom = ApzcOf(layers[2]); + + MockFunction check; + { + InSequence s; + EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, _, _, left->GetGuid(), _)) + .Times(1); + EXPECT_CALL(check, Call("Tapped on left")); + EXPECT_CALL(*mcc, + HandleTap(TapType::eSingleTap, _, _, bottom->GetGuid(), _)) + .Times(1); + EXPECT_CALL(check, Call("Tapped on bottom")); + EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, _, _, root->GetGuid(), _)) + .Times(1); + EXPECT_CALL(check, Call("Tapped on root")); + EXPECT_CALL(check, Call("Tap pending on d-t-c region")); + EXPECT_CALL(*mcc, + HandleTap(TapType::eSingleTap, _, _, bottom->GetGuid(), _)) + .Times(1); + EXPECT_CALL(check, Call("Tapped on bottom again")); + EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, _, _, left->GetGuid(), _)) + .Times(1); + EXPECT_CALL(check, Call("Tapped on left this time")); + } + + TimeDuration tapDuration = TimeDuration::FromMilliseconds(100); + + // Tap in the exposed hit regions of each of the layers once and ensure + // the clicks are dispatched right away + Tap(manager, ScreenIntPoint(10, 10), tapDuration); + mcc->RunThroughDelayedTasks(); // this runs the tap event + check.Call("Tapped on left"); + Tap(manager, ScreenIntPoint(110, 110), tapDuration); + mcc->RunThroughDelayedTasks(); // this runs the tap event + check.Call("Tapped on bottom"); + Tap(manager, ScreenIntPoint(110, 10), tapDuration); + mcc->RunThroughDelayedTasks(); // this runs the tap event + check.Call("Tapped on root"); + + // Now tap on the dispatch-to-content region where the layers overlap + Tap(manager, ScreenIntPoint(10, 110), tapDuration); + mcc->RunThroughDelayedTasks(); // this runs the main-thread timeout + check.Call("Tap pending on d-t-c region"); + mcc->RunThroughDelayedTasks(); // this runs the tap event + check.Call("Tapped on bottom again"); + + // Now let's do that again, but simulate a main-thread response + uint64_t inputBlockId = 0; + Tap(manager, ScreenIntPoint(10, 110), tapDuration, nullptr, &inputBlockId); + nsTArray targets; + targets.AppendElement(left->GetGuid()); + manager->SetTargetAPZC(inputBlockId, targets); + while (mcc->RunThroughDelayedTasks()) + ; // this runs the tap event + check.Call("Tapped on left this time"); +} + +TEST_F(APZEventRegionsTester, HitRegionAccumulatesChildren) { + CreateEventRegionsLayerTree2(); + + // Tap in the area of the child layer that's not directly included in the + // parent layer's hit region. Verify that it comes out of the APZC's + // content controller, which indicates the input events got routed correctly + // to the APZC. + EXPECT_CALL(*mcc, + HandleTap(TapType::eSingleTap, _, _, rootApzc->GetGuid(), _)) + .Times(1); + Tap(manager, ScreenIntPoint(10, 160), TimeDuration::FromMilliseconds(100)); +} + +TEST_F(APZEventRegionsTester, Obscuration) { + SCOPED_GFX_VAR(UseWebRender, bool, false); + + CreateObscuringLayerTree(); + ScopedLayerTreeRegistration registration(manager, LayersId{0}, root, mcc); + + UpdateHitTestingTree(); + + RefPtr parent = ApzcOf(layers[1]); + TestAsyncPanZoomController* child = ApzcOf(layers[2]); + + Pan(parent, 75, 25, PanOptions::NoFling); + + APZCTreeManager::HitTestResult hit = + manager->GetTargetAPZC(ScreenPoint(50, 75)); + EXPECT_EQ(child, hit.mTargetApzc.get()); + EXPECT_EQ(hit.mHitResult, CompositorHitTestFlags::eVisibleToHitTest); +} + +TEST_F(APZEventRegionsTester, Bug1119497) { + CreateBug1119497LayerTree(); + + APZCTreeManager::HitTestResult hit = + manager->GetTargetAPZC(ScreenPoint(50, 50)); + // We should hit layers[2], so |result| will be eVisibleToHitTest but there's + // no actual APZC on layers[2], so it will be the APZC of the root layer. + EXPECT_EQ(ApzcOf(layers[0]), hit.mTargetApzc.get()); + EXPECT_EQ(hit.mHitResult, CompositorHitTestFlags::eVisibleToHitTest); +} + +TEST_F(APZEventRegionsTester, Bug1117712) { + CreateBug1117712LayerTree(); + + TestAsyncPanZoomController* apzc2 = ApzcOf(layers[2]); + + // These touch events should hit the dispatch-to-content region of layers[3] + // and so get queued with that APZC as the tentative target. + uint64_t inputBlockId = 0; + Tap(manager, ScreenIntPoint(55, 5), TimeDuration::FromMilliseconds(100), + nullptr, &inputBlockId); + // But now we tell the APZ that really it hit layers[2], and expect the tap + // to be delivered at the correct coordinates. + EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(55, 5), 0, + apzc2->GetGuid(), _)) + .Times(1); + + nsTArray targets; + targets.AppendElement(apzc2->GetGuid()); + manager->SetTargetAPZC(inputBlockId, targets); +} + +// Test that APZEventResult::mHandledResult is correctly +// populated. +TEST_F(APZEventRegionsTester, HandledByRootApzcFlag) { + // Create simple layer tree containing a dispatch-to-content region + // that covers part but not all of its area. + const char* layerTreeSyntax = "c"; + nsIntRegion layerVisibleRegions[] = { + nsIntRegion(IntRect(0, 0, 100, 100)), + }; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, + layers); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 100, 200)); + ModifyFrameMetrics(root, [](ScrollMetadata& sm, FrameMetrics& metrics) { + metrics.SetIsRootContent(true); + }); + // away from the scrolling container layer. + EventRegions regions(nsIntRegion(IntRect(0, 0, 100, 100))); + // bottom half is dispatch-to-content + regions.mDispatchToContentHitRegion = nsIntRegion(IntRect(0, 50, 100, 50)); + root->SetEventRegions(regions); + registration = + MakeUnique(manager, LayersId{0}, root, mcc); + UpdateHitTestingTree(); + + // Tap the top half and check that we report that the event was + // handled by the root APZC. + APZEventResult result = + TouchDown(manager, ScreenIntPoint(50, 25), mcc->Time()); + TouchUp(manager, ScreenIntPoint(50, 25), mcc->Time()); + EXPECT_EQ(result.mHandledResult, Some(APZHandledResult::HandledByRoot)); + + // Tap the bottom half and check that we report that we're not + // sure whether the event was handled by the root APZC. + result = TouchDown(manager, ScreenIntPoint(50, 75), mcc->Time()); + TouchUp(manager, ScreenIntPoint(50, 75), mcc->Time()); + EXPECT_EQ(result.mHandledResult, Nothing()); + + // Register an input block callback that will tell us the + // delayed answer. + APZHandledResult delayedAnswer = APZHandledResult::Invalid; + manager->AddInputBlockCallback(result.mInputBlockId, + [&](uint64_t id, APZHandledResult answer) { + EXPECT_EQ(id, result.mInputBlockId); + delayedAnswer = answer; + }); + + // Send APZ the relevant notifications to allow it to process the + // input block. + manager->SetAllowedTouchBehavior(result.mInputBlockId, + {AllowedTouchBehavior::VERTICAL_PAN}); + manager->SetTargetAPZC(result.mInputBlockId, {result.mTargetGuid}); + manager->ContentReceivedInputBlock(result.mInputBlockId, + /*preventDefault=*/false); + + // Check that we received the delayed answer and it is what we expect. + EXPECT_EQ(delayedAnswer, APZHandledResult::HandledByRoot); + + // Now repeat the tap on the bottom half, but simulate a prevent-default. + // This time, we expect a delayed answer of `HandledByContent`. + result = TouchDown(manager, ScreenIntPoint(50, 75), mcc->Time()); + TouchUp(manager, ScreenIntPoint(50, 75), mcc->Time()); + EXPECT_EQ(result.mHandledResult, Nothing()); + manager->AddInputBlockCallback(result.mInputBlockId, + [&](uint64_t id, APZHandledResult answer) { + EXPECT_EQ(id, result.mInputBlockId); + delayedAnswer = answer; + }); + manager->SetAllowedTouchBehavior(result.mInputBlockId, + {AllowedTouchBehavior::VERTICAL_PAN}); + manager->SetTargetAPZC(result.mInputBlockId, {result.mTargetGuid}); + manager->ContentReceivedInputBlock(result.mInputBlockId, + /*preventDefault=*/true); + EXPECT_EQ(delayedAnswer, APZHandledResult::HandledByContent); + + // Shrink the scrollable area, now it's no longer scrollable. + ModifyFrameMetrics(root, [](ScrollMetadata& sm, FrameMetrics& metrics) { + metrics.SetScrollableRect(CSSRect(0, 0, 100, 100)); + }); + UpdateHitTestingTree(); + // Now repeat the tap on the bottom half with an event handler. + // This time, we expect a delayed answer of `Unhandled`. + result = TouchDown(manager, ScreenIntPoint(50, 75), mcc->Time()); + TouchUp(manager, ScreenIntPoint(50, 75), mcc->Time()); + EXPECT_EQ(result.mHandledResult, Nothing()); + manager->AddInputBlockCallback(result.mInputBlockId, + [&](uint64_t id, APZHandledResult answer) { + EXPECT_EQ(id, result.mInputBlockId); + delayedAnswer = answer; + }); + manager->SetAllowedTouchBehavior(result.mInputBlockId, + {AllowedTouchBehavior::VERTICAL_PAN}); + manager->SetTargetAPZC(result.mInputBlockId, {result.mTargetGuid}); + manager->ContentReceivedInputBlock(result.mInputBlockId, + /*preventDefault=*/false); + EXPECT_EQ(delayedAnswer, APZHandledResult::Unhandled); +} diff --git a/gfx/layers/apz/test/gtest/TestFlingAcceleration.cpp b/gfx/layers/apz/test/gtest/TestFlingAcceleration.cpp new file mode 100644 index 0000000000..e2048e6b7a --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestFlingAcceleration.cpp @@ -0,0 +1,255 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#include "APZCTreeManagerTester.h" +#include "APZTestCommon.h" +#include "InputUtils.h" + +class APZCFlingAccelerationTester : public APZCTreeManagerTester { + protected: + void SetUp() { + APZCTreeManagerTester::SetUp(); + const char* layerTreeSyntax = "c"; + nsIntRegion layerVisibleRegion[] = { + nsIntRegion(IntRect(0, 0, 800, 1000)), + }; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, + layers); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 800, 50000)); + // Scroll somewhere into the middle of the scroll range, so that we have + // lots of space to scroll in both directions. + ModifyFrameMetrics(root, [](ScrollMetadata& aSm, FrameMetrics& aMetrics) { + aMetrics.SetVisualScrollUpdateType( + FrameMetrics::ScrollOffsetUpdateType::eMainThread); + aMetrics.SetVisualDestination(CSSPoint(0, 25000)); + }); + + registration = MakeUnique(manager, LayersId{0}, + root, mcc); + UpdateHitTestingTree(); + + apzc = ApzcOf(root); + } + + void ExecutePanGesture100Hz(const ScreenIntPoint& aStartPoint, + std::initializer_list aYDeltas) { + APZEventResult result = TouchDown(apzc, aStartPoint, mcc->Time()); + + // Allowed touch behaviours must be set after sending touch-start. + if (result.mStatus != nsEventStatus_eConsumeNoDefault && + StaticPrefs::layout_css_touch_action_enabled()) { + SetDefaultAllowedTouchBehavior(apzc, result.mInputBlockId); + } + + const TimeDuration kTouchTimeDelta100Hz = + TimeDuration::FromMilliseconds(10); + + ScreenIntPoint currentLocation = aStartPoint; + for (int32_t delta : aYDeltas) { + mcc->AdvanceBy(kTouchTimeDelta100Hz); + if (delta != 0) { + currentLocation.y += delta; + Unused << TouchMove(apzc, currentLocation, mcc->Time()); + } + } + + Unused << TouchUp(apzc, currentLocation, mcc->Time()); + } + + void ExecuteWait(const TimeDuration& aDuration) { + TimeDuration remaining = aDuration; + const TimeDuration TIME_BETWEEN_FRAMES = + TimeDuration::FromSeconds(1) / int64_t(60); + while (remaining.ToMilliseconds() > 0) { + mcc->AdvanceBy(TIME_BETWEEN_FRAMES); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + remaining -= TIME_BETWEEN_FRAMES; + } + } + + RefPtr apzc; + UniquePtr registration; +}; + +enum class UpOrDown : uint8_t { Up, Down }; + +// This is a macro so that the assertions print useful line numbers. +#define CHECK_VELOCITY(aUpOrDown, aLowerBound, aUpperBound) \ + do { \ + auto vel = apzc->GetVelocityVector(); \ + if (UpOrDown::aUpOrDown == UpOrDown::Up) { \ + EXPECT_LT(vel.y, 0.0); \ + } else { \ + EXPECT_GT(vel.y, 0.0); \ + } \ + EXPECT_GE(vel.Length(), aLowerBound); \ + EXPECT_LE(vel.Length(), aUpperBound); \ + } while (0) + +// These tests have the following pattern: Two flings are executed, with a bit +// of wait time in between. The deltas in each pan gesture have been captured +// from a real phone, from touch events triggered by real fingers. +// We check the velocity at the end to detect whether the fling was accelerated +// or not. As an additional safety precaution, we also check the velocities for +// the first fling, so that changes in behavior are easier to analyze. +// One added challenge of this test is the fact that it has to work with on +// multiple platforms, and we use different velocity estimation strategies and +// different fling physics depending on the platform. +// The upper and lower bounds for the velocities were chosen in such a way that +// the test passes on all platforms. At the time of writing, we usually end up +// with higher velocities on Android than on Desktop, so the observed velocities +// on Android became the upper bounds and the observed velocities on Desktop +// becaume the lower bounds, each rounded out to a multiple of 0.1. + +TEST_F(APZCFlingAccelerationTester, TwoNormalFlingsShouldAccelerate) { + ExecutePanGesture100Hz(ScreenIntPoint{665, 1244}, + {0, 0, -21, -44, -52, -55, -53, -49, -46, -47}); + CHECK_VELOCITY(Down, 4.5, 6.8); + + ExecuteWait(TimeDuration::FromMilliseconds(375)); + CHECK_VELOCITY(Down, 2.2, 5.1); + + ExecutePanGesture100Hz(ScreenIntPoint{623, 1211}, + {-6, -51, -55, 0, -53, -57, -60, -60, -56}); + CHECK_VELOCITY(Down, 9.0, 14.0); +} + +TEST_F(APZCFlingAccelerationTester, TwoFastFlingsShouldAccelerate) { + ExecutePanGesture100Hz(ScreenIntPoint{764, 714}, + {9, 30, 49, 60, 64, 64, 62, 59, 51}); + CHECK_VELOCITY(Up, 5.0, 7.5); + + ExecuteWait(TimeDuration::FromMilliseconds(447)); + CHECK_VELOCITY(Up, 2.3, 5.2); + + ExecutePanGesture100Hz(ScreenIntPoint{743, 739}, + {7, 0, 38, 66, 75, 146, 0, 119}); + CHECK_VELOCITY(Up, 13.0, 20.0); +} + +TEST_F(APZCFlingAccelerationTester, + FlingsInOppositeDirectionShouldNotAccelerate) { + ExecutePanGesture100Hz(ScreenIntPoint{728, 1381}, + {0, 0, 0, -12, -24, -32, -43, -46, 0}); + CHECK_VELOCITY(Down, 2.9, 5.3); + + ExecuteWait(TimeDuration::FromMilliseconds(153)); + CHECK_VELOCITY(Down, 2.1, 4.8); + + ExecutePanGesture100Hz(ScreenIntPoint{698, 1059}, + {0, 0, 14, 61, 41, 0, 45, 35}); + CHECK_VELOCITY(Up, 3.2, 4.2); +} + +TEST_F(APZCFlingAccelerationTester, + ShouldNotAccelerateWhenPreviousFlingHasSlowedDown) { + ExecutePanGesture100Hz(ScreenIntPoint{748, 1046}, + {0, 9, 15, 23, 31, 30, 0, 34, 31, 29, 28, 24, 24, 11}); + CHECK_VELOCITY(Up, 2.2, 3.0); + ExecuteWait(TimeDuration::FromMilliseconds(498)); + CHECK_VELOCITY(Up, 0.5, 1.0); + ExecutePanGesture100Hz(ScreenIntPoint{745, 1056}, + {0, 10, 17, 29, 29, 33, 33, 0, 31, 27, 13}); + CHECK_VELOCITY(Up, 1.8, 2.7); +} + +TEST_F(APZCFlingAccelerationTester, ShouldNotAccelerateWhenPausedAtStartOfPan) { + ExecutePanGesture100Hz( + ScreenIntPoint{711, 1468}, + {0, 0, 0, 0, -8, 0, -18, -32, -50, -57, -66, -68, -63, -60}); + CHECK_VELOCITY(Down, 6.2, 8.5); + + ExecuteWait(TimeDuration::FromMilliseconds(285)); + CHECK_VELOCITY(Down, 3.4, 7.3); + + ExecutePanGesture100Hz( + ScreenIntPoint{658, 1352}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, -8, -18, -34, -53, -70, -75, -75, -64}); + CHECK_VELOCITY(Down, 6.7, 9.1); +} + +TEST_F(APZCFlingAccelerationTester, ShouldNotAccelerateWhenPausedDuringPan) { + ExecutePanGesture100Hz( + ScreenIntPoint{732, 1423}, + {0, 0, 0, -5, 0, -15, -41, -71, -90, -93, -85, -64, -44}); + CHECK_VELOCITY(Down, 7.5, 10.0); + + ExecuteWait(TimeDuration::FromMilliseconds(204)); + CHECK_VELOCITY(Down, 4.8, 9.3); + + ExecutePanGesture100Hz( + ScreenIntPoint{651, 1372}, + {0, 0, 0, -6, 0, -16, -26, -41, -49, -65, -66, -61, -50, -35, -24, + -17, -11, -8, -6, -5, -4, -3, -2, -2, -2, -2, -2, -2, -2, -2, + -3, -4, -5, -7, -9, -10, -10, -12, -18, -25, -23, -28, -30, -24}); + CHECK_VELOCITY(Down, 2.5, 3.4); +} + +TEST_F(APZCFlingAccelerationTester, + ShouldNotAccelerateWhenOppositeDirectionDuringPan) { + ExecutePanGesture100Hz(ScreenIntPoint{663, 1371}, + {0, 0, 0, -5, -18, -31, -49, -56, -61, -54, -55}); + CHECK_VELOCITY(Down, 5.4, 7.0); + + ExecuteWait(TimeDuration::FromMilliseconds(255)); + CHECK_VELOCITY(Down, 3.1, 6.0); + + ExecutePanGesture100Hz( + ScreenIntPoint{726, 930}, + {0, 0, 0, 0, 30, 0, 19, 24, 32, 30, 37, 33, + 33, 32, 25, 23, 23, 18, 13, 9, 5, 3, 1, 0, + -7, -19, -38, -53, -68, -79, -85, -73, -64, -54}); + CHECK_VELOCITY(Down, 7.0, 10.0); +} + +TEST_F(APZCFlingAccelerationTester, + ShouldAccelerateAfterLongWaitIfVelocityStillHigh) { + // Reduce friction with the "Desktop" fling physics a little, so that it + // behaves more similarly to the Android fling physics, and has enough + // velocity after the wait time to allow for acceleration. + SCOPED_GFX_PREF_FLOAT("apz.fling_friction", 0.0012); + + ExecutePanGesture100Hz(ScreenIntPoint{739, 1424}, + {0, 0, -5, -10, -20, 0, -110, -86, 0, -102, -105}); + CHECK_VELOCITY(Down, 6.3, 9.4); + + ExecuteWait(TimeDuration::FromMilliseconds(1117)); + CHECK_VELOCITY(Down, 1.6, 3.3); + + ExecutePanGesture100Hz(ScreenIntPoint{726, 1380}, + {0, -8, 0, -30, -60, -87, -104, -111}); + CHECK_VELOCITY(Down, 13.0, 23.0); +} + +TEST_F(APZCFlingAccelerationTester, ShouldNotAccelerateAfterCanceledWithTap) { + // First, build up a lot of speed. + ExecutePanGesture100Hz(ScreenIntPoint{569, 710}, + {11, 2, 107, 18, 148, 57, 133, 159, 21}); + ExecuteWait(TimeDuration::FromMilliseconds(154)); + ExecutePanGesture100Hz(ScreenIntPoint{581, 650}, + {12, 68, 0, 162, 78, 140, 167}); + ExecuteWait(TimeDuration::FromMilliseconds(123)); + ExecutePanGesture100Hz(ScreenIntPoint{568, 723}, {11, 0, 79, 91, 131, 171}); + ExecuteWait(TimeDuration::FromMilliseconds(123)); + ExecutePanGesture100Hz(ScreenIntPoint{598, 678}, + {8, 55, 22, 87, 117, 220, 54}); + ExecuteWait(TimeDuration::FromMilliseconds(134)); + ExecutePanGesture100Hz(ScreenIntPoint{585, 854}, {45, 137, 107, 102, 79}); + ExecuteWait(TimeDuration::FromMilliseconds(246)); + + // Then, interrupt with a tap. + ExecutePanGesture100Hz(ScreenIntPoint{566, 812}, {0, 0, 0, 0}); + ExecuteWait(TimeDuration::FromMilliseconds(869)); + + // Then do a regular fling. + ExecutePanGesture100Hz(ScreenIntPoint{599, 819}, + {0, 0, 8, 35, 8, 38, 29, 37}); + + CHECK_VELOCITY(Up, 2.8, 4.2); +} diff --git a/gfx/layers/apz/test/gtest/TestGestureDetector.cpp b/gfx/layers/apz/test/gtest/TestGestureDetector.cpp new file mode 100644 index 0000000000..ba7626cb1f --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestGestureDetector.cpp @@ -0,0 +1,877 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "APZCBasicTester.h" +#include "APZTestCommon.h" +#include "InputUtils.h" +#include "mozilla/StaticPrefs_apz.h" + +// Note: There are additional tests that test gesture detection behaviour +// with multiple APZCs in TestTreeManager.cpp. + +class APZCGestureDetectorTester : public APZCBasicTester { + public: + APZCGestureDetectorTester() + : APZCBasicTester(AsyncPanZoomController::USE_GESTURE_DETECTOR) {} + + protected: + FrameMetrics GetPinchableFrameMetrics() { + FrameMetrics fm; + fm.SetCompositionBounds(ParentLayerRect(200, 200, 100, 200)); + fm.SetScrollableRect(CSSRect(0, 0, 980, 1000)); + fm.SetVisualScrollOffset(CSSPoint(300, 300)); + fm.SetZoom(CSSToParentLayerScale2D(2.0, 2.0)); + // APZC only allows zooming on the root scrollable frame. + fm.SetIsRootContent(true); + // the visible area of the document in CSS pixels is x=300 y=300 w=50 h=100 + return fm; + } +}; + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +TEST_F(APZCGestureDetectorTester, Pan_After_Pinch) { + SCOPED_GFX_PREF_BOOL("layout.css.touch_action.enabled", false); + + FrameMetrics originalMetrics = GetPinchableFrameMetrics(); + apzc->SetFrameMetrics(originalMetrics); + + MakeApzcZoomable(); + + // Test parameters + float zoomAmount = 1.25; + float pinchLength = 100.0; + float pinchLengthScaled = pinchLength * zoomAmount; + int focusX = 250; + int focusY = 300; + int panDistance = 20; + const TimeDuration TIME_BETWEEN_TOUCH_EVENT = + TimeDuration::FromMilliseconds(50); + + int firstFingerId = 0; + int secondFingerId = firstFingerId + 1; + + // Put fingers down + MultiTouchInput mti = + MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, mcc->Time(), 0); + mti.mTouches.AppendElement( + CreateSingleTouchData(firstFingerId, focusX, focusY)); + mti.mTouches.AppendElement( + CreateSingleTouchData(secondFingerId, focusX, focusY)); + apzc->ReceiveInputEvent(mti, nullptr); + mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + + // Spread fingers out to enter the pinch state + mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, mcc->Time(), 0); + mti.mTouches.AppendElement( + CreateSingleTouchData(firstFingerId, focusX - pinchLength, focusY)); + mti.mTouches.AppendElement( + CreateSingleTouchData(secondFingerId, focusX + pinchLength, focusY)); + apzc->ReceiveInputEvent(mti, nullptr); + mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + + // Do the actual pinch of 1.25x + mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, mcc->Time(), 0); + mti.mTouches.AppendElement( + CreateSingleTouchData(firstFingerId, focusX - pinchLengthScaled, focusY)); + mti.mTouches.AppendElement(CreateSingleTouchData( + secondFingerId, focusX + pinchLengthScaled, focusY)); + apzc->ReceiveInputEvent(mti, nullptr); + mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + + // Verify that the zoom changed, just to make sure our code above did what it + // was supposed to. + FrameMetrics zoomedMetrics = apzc->GetFrameMetrics(); + float newZoom = zoomedMetrics.GetZoom().ToScaleFactor().scale; + EXPECT_EQ(originalMetrics.GetZoom().ToScaleFactor().scale * zoomAmount, + newZoom); + + // Now we lift one finger... + mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, mcc->Time(), 0); + mti.mTouches.AppendElement(CreateSingleTouchData( + secondFingerId, focusX + pinchLengthScaled, focusY)); + apzc->ReceiveInputEvent(mti, nullptr); + mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + + // ... and pan with the remaining finger. This pan just breaks through the + // distance threshold. + focusY += 40; + mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, mcc->Time(), 0); + mti.mTouches.AppendElement( + CreateSingleTouchData(firstFingerId, focusX - pinchLengthScaled, focusY)); + apzc->ReceiveInputEvent(mti, nullptr); + mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + + // This one does an actual pan of 20 pixels + focusY += panDistance; + mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, mcc->Time(), 0); + mti.mTouches.AppendElement( + CreateSingleTouchData(firstFingerId, focusX - pinchLengthScaled, focusY)); + apzc->ReceiveInputEvent(mti, nullptr); + mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + + // Lift the remaining finger + mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, mcc->Time(), 0); + mti.mTouches.AppendElement( + CreateSingleTouchData(firstFingerId, focusX - pinchLengthScaled, focusY)); + apzc->ReceiveInputEvent(mti, nullptr); + mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + + // Verify that we scrolled + FrameMetrics finalMetrics = apzc->GetFrameMetrics(); + EXPECT_EQ(zoomedMetrics.GetVisualScrollOffset().y - (panDistance / newZoom), + finalMetrics.GetVisualScrollOffset().y); + + // Clear out any remaining fling animation and pending tasks + apzc->AdvanceAnimationsUntilEnd(); + while (mcc->RunThroughDelayedTasks()) + ; + apzc->AssertStateIsReset(); +} +#endif + +TEST_F(APZCGestureDetectorTester, Pan_With_Tap) { + SCOPED_GFX_PREF_BOOL("layout.css.touch_action.enabled", false); + + FrameMetrics originalMetrics = GetPinchableFrameMetrics(); + apzc->SetFrameMetrics(originalMetrics); + + // Making the APZC zoomable isn't really needed for the correct operation of + // this test, but it could help catch regressions where we accidentally enter + // a pinch state. + MakeApzcZoomable(); + + // Test parameters + int touchX = 250; + int touchY = 300; + int panDistance = 20; + + int firstFingerId = 0; + int secondFingerId = firstFingerId + 1; + + // Put finger down + MultiTouchInput mti = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); + mti.mTouches.AppendElement( + CreateSingleTouchData(firstFingerId, touchX, touchY)); + apzc->ReceiveInputEvent(mti, nullptr); + + // Start a pan, break through the threshold + touchY += 40; + mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, mcc->Time()); + mti.mTouches.AppendElement( + CreateSingleTouchData(firstFingerId, touchX, touchY)); + apzc->ReceiveInputEvent(mti, nullptr); + + // Do an actual pan for a bit + touchY += panDistance; + mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, mcc->Time()); + mti.mTouches.AppendElement( + CreateSingleTouchData(firstFingerId, touchX, touchY)); + apzc->ReceiveInputEvent(mti, nullptr); + + // Put a second finger down + mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); + mti.mTouches.AppendElement( + CreateSingleTouchData(firstFingerId, touchX, touchY)); + mti.mTouches.AppendElement( + CreateSingleTouchData(secondFingerId, touchX + 10, touchY)); + apzc->ReceiveInputEvent(mti, nullptr); + + // Lift the second finger + mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_END, mcc->Time()); + mti.mTouches.AppendElement( + CreateSingleTouchData(secondFingerId, touchX + 10, touchY)); + apzc->ReceiveInputEvent(mti, nullptr); + + // Bust through the threshold again + touchY += 40; + mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, mcc->Time()); + mti.mTouches.AppendElement( + CreateSingleTouchData(firstFingerId, touchX, touchY)); + apzc->ReceiveInputEvent(mti, nullptr); + + // Do some more actual panning + touchY += panDistance; + mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, mcc->Time()); + mti.mTouches.AppendElement( + CreateSingleTouchData(firstFingerId, touchX, touchY)); + apzc->ReceiveInputEvent(mti, nullptr); + + // Lift the first finger + mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_END, mcc->Time()); + mti.mTouches.AppendElement( + CreateSingleTouchData(firstFingerId, touchX, touchY)); + apzc->ReceiveInputEvent(mti, nullptr); + + // Verify that we scrolled + FrameMetrics finalMetrics = apzc->GetFrameMetrics(); + float zoom = finalMetrics.GetZoom().ToScaleFactor().scale; + EXPECT_EQ( + originalMetrics.GetVisualScrollOffset().y - (panDistance * 2 / zoom), + finalMetrics.GetVisualScrollOffset().y); + + // Clear out any remaining fling animation and pending tasks + apzc->AdvanceAnimationsUntilEnd(); + while (mcc->RunThroughDelayedTasks()) + ; + apzc->AssertStateIsReset(); +} + +TEST_F(APZCGestureDetectorTester, SecondTapIsFar_Bug1586496) { + SCOPED_GFX_PREF_BOOL("layout.css.touch_action.enabled", false); + + // Test that we receive two single-tap events when two tap gestures are + // close in time but far in distance. + EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, _, 0, apzc->GetGuid(), _)) + .Times(2); + + TimeDuration brief = + TimeDuration::FromMilliseconds(StaticPrefs::apz_max_tap_time() / 10); + + ScreenIntPoint point(10, 10); + Tap(apzc, point, brief); + + mcc->AdvanceBy(brief); + + point.x += apzc->GetSecondTapTolerance() * 2; + point.y += apzc->GetSecondTapTolerance() * 2; + + Tap(apzc, point, brief); +} + +class APZCFlingStopTester : public APZCGestureDetectorTester { + protected: + // Start a fling, and then tap while the fling is ongoing. When + // aSlow is false, the tap will happen while the fling is at a + // high velocity, and we check that the tap doesn't trigger sending a tap + // to content. If aSlow is true, the tap will happen while the fling + // is at a slow velocity, and we check that the tap does trigger sending + // a tap to content. See bug 1022956. + void DoFlingStopTest(bool aSlow) { + int touchStart = 50; + int touchEnd = 10; + + // Start the fling down. + Pan(apzc, touchStart, touchEnd); + // The touchstart from the pan will leave some cancelled tasks in the queue, + // clear them out + + // If we want to tap while the fling is fast, let the fling advance for 10ms + // only. If we want the fling to slow down more, advance to 2000ms. These + // numbers may need adjusting if our friction and threshold values change, + // but they should be deterministic at least. + int timeDelta = aSlow ? 2000 : 10; + int tapCallsExpected = aSlow ? 2 : 1; + + // Advance the fling animation by timeDelta milliseconds. + ParentLayerPoint pointOut; + AsyncTransform viewTransformOut; + apzc->SampleContentTransformForFrame( + &viewTransformOut, pointOut, TimeDuration::FromMilliseconds(timeDelta)); + + // Deliver a tap to abort the fling. Ensure that we get a SingleTap + // call out of it if and only if the fling is slow. + EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, _, 0, apzc->GetGuid(), _)) + .Times(tapCallsExpected); + Tap(apzc, ScreenIntPoint(10, 10), 0); + while (mcc->RunThroughDelayedTasks()) + ; + + // Deliver another tap, to make sure that taps are flowing properly once + // the fling is aborted. + Tap(apzc, ScreenIntPoint(100, 100), 0); + while (mcc->RunThroughDelayedTasks()) + ; + + // Verify that we didn't advance any further after the fling was aborted, in + // either case. + ParentLayerPoint finalPointOut; + apzc->SampleContentTransformForFrame(&viewTransformOut, finalPointOut); + EXPECT_EQ(pointOut.x, finalPointOut.x); + EXPECT_EQ(pointOut.y, finalPointOut.y); + + apzc->AssertStateIsReset(); + } + + void DoFlingStopWithSlowListener(bool aPreventDefault) { + MakeApzcWaitForMainThread(); + + int touchStart = 50; + int touchEnd = 10; + uint64_t blockId = 0; + + // Start the fling down. + Pan(apzc, touchStart, touchEnd, PanOptions::None, nullptr, nullptr, + &blockId); + apzc->ConfirmTarget(blockId); + apzc->ContentReceivedInputBlock(blockId, false); + + // Sample the fling a couple of times to ensure it's going. + ParentLayerPoint point, finalPoint; + AsyncTransform viewTransform; + apzc->SampleContentTransformForFrame(&viewTransform, point, + TimeDuration::FromMilliseconds(10)); + apzc->SampleContentTransformForFrame(&viewTransform, finalPoint, + TimeDuration::FromMilliseconds(10)); + EXPECT_GT(finalPoint.y, point.y); + + // Now we put our finger down to stop the fling + blockId = + TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time()).mInputBlockId; + + // Re-sample to make sure it hasn't moved + apzc->SampleContentTransformForFrame(&viewTransform, point, + TimeDuration::FromMilliseconds(10)); + EXPECT_EQ(finalPoint.x, point.x); + EXPECT_EQ(finalPoint.y, point.y); + + // respond to the touchdown that stopped the fling. + // even if we do a prevent-default on it, the animation should remain + // stopped. + apzc->ContentReceivedInputBlock(blockId, aPreventDefault); + + // Verify the page hasn't moved + apzc->SampleContentTransformForFrame(&viewTransform, point, + TimeDuration::FromMilliseconds(70)); + EXPECT_EQ(finalPoint.x, point.x); + EXPECT_EQ(finalPoint.y, point.y); + + // clean up + TouchUp(apzc, ScreenIntPoint(10, 10), mcc->Time()); + + apzc->AssertStateIsReset(); + } +}; + +TEST_F(APZCFlingStopTester, FlingStop) { + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); + DoFlingStopTest(false); +} + +TEST_F(APZCFlingStopTester, FlingStopTap) { + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); + DoFlingStopTest(true); +} + +TEST_F(APZCFlingStopTester, FlingStopSlowListener) { + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); + DoFlingStopWithSlowListener(false); +} + +TEST_F(APZCFlingStopTester, FlingStopPreventDefault) { + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); + DoFlingStopWithSlowListener(true); +} + +TEST_F(APZCGestureDetectorTester, ShortPress) { + MakeApzcUnzoomable(); + + MockFunction check; + { + InSequence s; + // This verifies that the single tap notification is sent after the + // touchup is fully processed. The ordering here is important. + EXPECT_CALL(check, Call("pre-tap")); + EXPECT_CALL(check, Call("post-tap")); + EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(10, 10), + 0, apzc->GetGuid(), _)) + .Times(1); + } + + check.Call("pre-tap"); + TapAndCheckStatus(apzc, ScreenIntPoint(10, 10), + TimeDuration::FromMilliseconds(100)); + check.Call("post-tap"); + + apzc->AssertStateIsReset(); +} + +TEST_F(APZCGestureDetectorTester, MediumPress) { + MakeApzcUnzoomable(); + + MockFunction check; + { + InSequence s; + // This verifies that the single tap notification is sent after the + // touchup is fully processed. The ordering here is important. + EXPECT_CALL(check, Call("pre-tap")); + EXPECT_CALL(check, Call("post-tap")); + EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(10, 10), + 0, apzc->GetGuid(), _)) + .Times(1); + } + + check.Call("pre-tap"); + TapAndCheckStatus(apzc, ScreenIntPoint(10, 10), + TimeDuration::FromMilliseconds(400)); + check.Call("post-tap"); + + apzc->AssertStateIsReset(); +} + +class APZCLongPressTester : public APZCGestureDetectorTester { + protected: + void DoLongPressTest(uint32_t aBehavior) { + MakeApzcUnzoomable(); + + APZEventResult result = + TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time()); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, result.mStatus); + uint64_t blockId = result.mInputBlockId; + + if (StaticPrefs::layout_css_touch_action_enabled() && + result.mStatus != nsEventStatus_eConsumeNoDefault) { + // SetAllowedTouchBehavior() must be called after sending touch-start. + nsTArray allowedTouchBehaviors; + allowedTouchBehaviors.AppendElement(aBehavior); + apzc->SetAllowedTouchBehavior(blockId, allowedTouchBehaviors); + } + // Have content "respond" to the touchstart + apzc->ContentReceivedInputBlock(blockId, false); + + MockFunction check; + + { + InSequence s; + + EXPECT_CALL(check, Call("preHandleLongTap")); + blockId++; + EXPECT_CALL(*mcc, HandleTap(TapType::eLongTap, LayoutDevicePoint(10, 10), + 0, apzc->GetGuid(), blockId)) + .Times(1); + EXPECT_CALL(check, Call("postHandleLongTap")); + + EXPECT_CALL(check, Call("preHandleLongTapUp")); + EXPECT_CALL(*mcc, + HandleTap(TapType::eLongTapUp, LayoutDevicePoint(10, 10), 0, + apzc->GetGuid(), _)) + .Times(1); + EXPECT_CALL(check, Call("postHandleLongTapUp")); + } + + // Manually invoke the longpress while the touch is currently down. + check.Call("preHandleLongTap"); + mcc->RunThroughDelayedTasks(); + check.Call("postHandleLongTap"); + + // Dispatching the longpress event starts a new touch block, which + // needs a new content response and also has a pending timeout task + // in the queue. Deal with those here. We do the content response first + // with preventDefault=false, and then we run the timeout task which + // "loses the race" and does nothing. + apzc->ContentReceivedInputBlock(blockId, false); + mcc->AdvanceByMillis(1000); + + // Finally, simulate lifting the finger. Since the long-press wasn't + // prevent-defaulted, we should get a long-tap-up event. + check.Call("preHandleLongTapUp"); + result.mStatus = TouchUp(apzc, ScreenIntPoint(10, 10), mcc->Time()); + mcc->RunThroughDelayedTasks(); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, result.mStatus); + check.Call("postHandleLongTapUp"); + + apzc->AssertStateIsReset(); + } + + void DoLongPressPreventDefaultTest(uint32_t aBehavior) { + MakeApzcUnzoomable(); + + EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0); + + int touchX = 10, touchStartY = 10, touchEndY = 50; + + APZEventResult result = + TouchDown(apzc, ScreenIntPoint(touchX, touchStartY), mcc->Time()); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, result.mStatus); + uint64_t blockId = result.mInputBlockId; + + if (StaticPrefs::layout_css_touch_action_enabled() && + result.mStatus != nsEventStatus_eConsumeNoDefault) { + // SetAllowedTouchBehavior() must be called after sending touch-start. + nsTArray allowedTouchBehaviors; + allowedTouchBehaviors.AppendElement(aBehavior); + apzc->SetAllowedTouchBehavior(blockId, allowedTouchBehaviors); + } + // Have content "respond" to the touchstart + apzc->ContentReceivedInputBlock(blockId, false); + + MockFunction check; + + { + InSequence s; + + EXPECT_CALL(check, Call("preHandleLongTap")); + blockId++; + EXPECT_CALL(*mcc, HandleTap(TapType::eLongTap, + LayoutDevicePoint(touchX, touchStartY), 0, + apzc->GetGuid(), blockId)) + .Times(1); + EXPECT_CALL(check, Call("postHandleLongTap")); + } + + // Manually invoke the longpress while the touch is currently down. + check.Call("preHandleLongTap"); + mcc->RunThroughDelayedTasks(); + check.Call("postHandleLongTap"); + + // There should be a TimeoutContentResponse task in the queue still, + // waiting for the response from the longtap event dispatched above. + // Send the signal that content has handled the long-tap, and then run + // the timeout task (it will be a no-op because the content "wins" the + // race. This takes the place of the "contextmenu" event. + apzc->ContentReceivedInputBlock(blockId, true); + mcc->AdvanceByMillis(1000); + + MultiTouchInput mti = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, mcc->Time()); + mti.mTouches.AppendElement(SingleTouchData( + 0, ParentLayerPoint(touchX, touchEndY), ScreenSize(0, 0), 0, 0)); + result.mStatus = apzc->ReceiveInputEvent(mti, nullptr); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, result.mStatus); + + EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, + LayoutDevicePoint(touchX, touchEndY), 0, + apzc->GetGuid(), _)) + .Times(0); + result.mStatus = + TouchUp(apzc, ScreenIntPoint(touchX, touchEndY), mcc->Time()); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, result.mStatus); + + ParentLayerPoint pointOut; + AsyncTransform viewTransformOut; + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + + EXPECT_EQ(ParentLayerPoint(), pointOut); + EXPECT_EQ(AsyncTransform(), viewTransformOut); + + apzc->AssertStateIsReset(); + } +}; + +TEST_F(APZCLongPressTester, LongPress) { + SCOPED_GFX_PREF_BOOL("layout.css.touch_action.enabled", false); + DoLongPressTest(mozilla::layers::AllowedTouchBehavior::NONE); +} + +TEST_F(APZCLongPressTester, LongPressWithTouchAction) { + SCOPED_GFX_PREF_BOOL("layout.css.touch_action.enabled", true); + DoLongPressTest(mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN | + mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN | + mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM); +} + +TEST_F(APZCLongPressTester, LongPressPreventDefault) { + SCOPED_GFX_PREF_BOOL("layout.css.touch_action.enabled", false); + DoLongPressPreventDefaultTest(mozilla::layers::AllowedTouchBehavior::NONE); +} + +TEST_F(APZCLongPressTester, LongPressPreventDefaultWithTouchAction) { + SCOPED_GFX_PREF_BOOL("layout.css.touch_action.enabled", true); + DoLongPressPreventDefaultTest( + mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN | + mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN | + mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM); +} + +TEST_F(APZCGestureDetectorTester, DoubleTap) { + MakeApzcWaitForMainThread(); + MakeApzcZoomable(); + + EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(10, 10), 0, + apzc->GetGuid(), _)) + .Times(0); + EXPECT_CALL(*mcc, HandleTap(TapType::eDoubleTap, LayoutDevicePoint(10, 10), 0, + apzc->GetGuid(), _)) + .Times(1); + + uint64_t blockIds[2]; + DoubleTapAndCheckStatus(apzc, ScreenIntPoint(10, 10), &blockIds); + + // responses to the two touchstarts + apzc->ContentReceivedInputBlock(blockIds[0], false); + apzc->ContentReceivedInputBlock(blockIds[1], false); + + apzc->AssertStateIsReset(); +} + +TEST_F(APZCGestureDetectorTester, DoubleTapNotZoomable) { + MakeApzcWaitForMainThread(); + MakeApzcUnzoomable(); + + EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(10, 10), 0, + apzc->GetGuid(), _)) + .Times(1); + EXPECT_CALL(*mcc, HandleTap(TapType::eSecondTap, LayoutDevicePoint(10, 10), 0, + apzc->GetGuid(), _)) + .Times(1); + EXPECT_CALL(*mcc, HandleTap(TapType::eDoubleTap, LayoutDevicePoint(10, 10), 0, + apzc->GetGuid(), _)) + .Times(0); + + uint64_t blockIds[2]; + DoubleTapAndCheckStatus(apzc, ScreenIntPoint(10, 10), &blockIds); + + // responses to the two touchstarts + apzc->ContentReceivedInputBlock(blockIds[0], false); + apzc->ContentReceivedInputBlock(blockIds[1], false); + + apzc->AssertStateIsReset(); +} + +TEST_F(APZCGestureDetectorTester, DoubleTapPreventDefaultFirstOnly) { + MakeApzcWaitForMainThread(); + MakeApzcZoomable(); + + EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(10, 10), 0, + apzc->GetGuid(), _)) + .Times(1); + EXPECT_CALL(*mcc, HandleTap(TapType::eDoubleTap, LayoutDevicePoint(10, 10), 0, + apzc->GetGuid(), _)) + .Times(0); + + uint64_t blockIds[2]; + DoubleTapAndCheckStatus(apzc, ScreenIntPoint(10, 10), &blockIds); + + // responses to the two touchstarts + apzc->ContentReceivedInputBlock(blockIds[0], true); + apzc->ContentReceivedInputBlock(blockIds[1], false); + + apzc->AssertStateIsReset(); +} + +TEST_F(APZCGestureDetectorTester, DoubleTapPreventDefaultBoth) { + MakeApzcWaitForMainThread(); + MakeApzcZoomable(); + + EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(10, 10), 0, + apzc->GetGuid(), _)) + .Times(0); + EXPECT_CALL(*mcc, HandleTap(TapType::eDoubleTap, LayoutDevicePoint(10, 10), 0, + apzc->GetGuid(), _)) + .Times(0); + + uint64_t blockIds[2]; + DoubleTapAndCheckStatus(apzc, ScreenIntPoint(10, 10), &blockIds); + + // responses to the two touchstarts + apzc->ContentReceivedInputBlock(blockIds[0], true); + apzc->ContentReceivedInputBlock(blockIds[1], true); + + apzc->AssertStateIsReset(); +} + +// Test for bug 947892 +// We test whether we dispatch tap event when the tap is followed by pinch. +TEST_F(APZCGestureDetectorTester, TapFollowedByPinch) { + MakeApzcZoomable(); + + EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(10, 10), 0, + apzc->GetGuid(), _)) + .Times(1); + + Tap(apzc, ScreenIntPoint(10, 10), TimeDuration::FromMilliseconds(100)); + + int inputId = 0; + MultiTouchInput mti; + mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); + mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), + ScreenSize(0, 0), 0, 0)); + mti.mTouches.AppendElement(SingleTouchData( + inputId + 1, ParentLayerPoint(10, 10), ScreenSize(0, 0), 0, 0)); + apzc->ReceiveInputEvent(mti, nullptr); + + mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_END, mcc->Time()); + mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), + ScreenSize(0, 0), 0, 0)); + mti.mTouches.AppendElement(SingleTouchData( + inputId + 1, ParentLayerPoint(10, 10), ScreenSize(0, 0), 0, 0)); + apzc->ReceiveInputEvent(mti, nullptr); + + apzc->AssertStateIsReset(); +} + +TEST_F(APZCGestureDetectorTester, TapFollowedByMultipleTouches) { + MakeApzcZoomable(); + + EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(10, 10), 0, + apzc->GetGuid(), _)) + .Times(1); + + Tap(apzc, ScreenIntPoint(10, 10), TimeDuration::FromMilliseconds(100)); + + int inputId = 0; + MultiTouchInput mti; + mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); + mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), + ScreenSize(0, 0), 0, 0)); + apzc->ReceiveInputEvent(mti, nullptr); + + mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); + mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), + ScreenSize(0, 0), 0, 0)); + mti.mTouches.AppendElement(SingleTouchData( + inputId + 1, ParentLayerPoint(10, 10), ScreenSize(0, 0), 0, 0)); + apzc->ReceiveInputEvent(mti, nullptr); + + mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_END, mcc->Time()); + mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), + ScreenSize(0, 0), 0, 0)); + mti.mTouches.AppendElement(SingleTouchData( + inputId + 1, ParentLayerPoint(10, 10), ScreenSize(0, 0), 0, 0)); + apzc->ReceiveInputEvent(mti, nullptr); + + apzc->AssertStateIsReset(); +} + +TEST_F(APZCGestureDetectorTester, LongPressInterruptedByWheel) { + // Since we try to allow concurrent input blocks of different types to + // co-exist, the wheel block shouldn't interrupt the long-press detection. + // But more importantly, this shouldn't crash, which is what it did at one + // point in time. + EXPECT_CALL(*mcc, HandleTap(TapType::eLongTap, _, _, _, _)).Times(1); + + APZEventResult result = TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time()); + uint64_t touchBlockId = result.mInputBlockId; + if (StaticPrefs::layout_css_touch_action_enabled() && + result.mStatus != nsEventStatus_eConsumeNoDefault) { + SetDefaultAllowedTouchBehavior(apzc, touchBlockId); + } + mcc->AdvanceByMillis(10); + uint64_t wheelBlockId = + Wheel(apzc, ScreenIntPoint(10, 10), ScreenPoint(0, -10), mcc->Time()) + .mInputBlockId; + EXPECT_NE(touchBlockId, wheelBlockId); + mcc->AdvanceByMillis(1000); +} + +TEST_F(APZCGestureDetectorTester, TapTimeoutInterruptedByWheel) { + // In this test, even though the wheel block comes right after the tap, the + // tap should still be dispatched because it completes fully before the wheel + // block arrived. + EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(10, 10), 0, + apzc->GetGuid(), _)) + .Times(1); + + // We make the APZC zoomable so the gesture detector needs to wait to + // distinguish between tap and double-tap. During that timeout is when we + // insert the wheel event. + MakeApzcZoomable(); + + uint64_t touchBlockId = 0; + Tap(apzc, ScreenIntPoint(10, 10), TimeDuration::FromMilliseconds(100), + nullptr, &touchBlockId); + mcc->AdvanceByMillis(10); + uint64_t wheelBlockId = + Wheel(apzc, ScreenIntPoint(10, 10), ScreenPoint(0, -10), mcc->Time()) + .mInputBlockId; + EXPECT_NE(touchBlockId, wheelBlockId); + while (mcc->RunThroughDelayedTasks()) + ; +} + +TEST_F(APZCGestureDetectorTester, LongPressWithInputQueueDelay) { + // In this test, we ensure that any time spent waiting in the input queue for + // the content response is subtracted from the long-press timeout in the + // GestureEventListener. In this test the content response timeout is longer + // than the long-press timeout. + SCOPED_GFX_PREF_INT("apz.content_response_timeout", 60); + SCOPED_GFX_PREF_INT("ui.click_hold_context_menus.delay", 30); + + // Turn off touch-action to avoid having to send allowed touch actions to the + // input block. + SCOPED_GFX_PREF_BOOL("layout.css.touch_action.enabled", false); + + MakeApzcWaitForMainThread(); + + MockFunction check; + + { + InSequence s; + EXPECT_CALL(check, Call("pre long-tap dispatch")); + EXPECT_CALL(*mcc, HandleTap(TapType::eLongTap, LayoutDevicePoint(10, 10), 0, + apzc->GetGuid(), _)) + .Times(1); + EXPECT_CALL(check, Call("post long-tap dispatch")); + } + + // Touch down + APZEventResult result = TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time()); + uint64_t touchBlockId = result.mInputBlockId; + // Simulate content response after 10ms + mcc->AdvanceByMillis(10); + apzc->ContentReceivedInputBlock(touchBlockId, false); + apzc->ConfirmTarget(touchBlockId); + // Ensure long-tap event happens within 20ms after that + check.Call("pre long-tap dispatch"); + mcc->AdvanceByMillis(20); + check.Call("post long-tap dispatch"); +} + +TEST_F(APZCGestureDetectorTester, LongPressWithInputQueueDelay2) { + // Similar to the previous test, except this time we don't simulate the + // content response at all, and still expect the long-press to happen on + // schedule. + SCOPED_GFX_PREF_INT("apz.content_response_timeout", 60); + SCOPED_GFX_PREF_INT("ui.click_hold_context_menus.delay", 30); + + // Turn off touch-action to avoid having to send allowed touch actions to the + // input block. + SCOPED_GFX_PREF_BOOL("layout.css.touch_action.enabled", false); + + MakeApzcWaitForMainThread(); + + MockFunction check; + + { + InSequence s; + EXPECT_CALL(check, Call("pre long-tap dispatch")); + EXPECT_CALL(*mcc, HandleTap(TapType::eLongTap, LayoutDevicePoint(10, 10), 0, + apzc->GetGuid(), _)) + .Times(1); + EXPECT_CALL(check, Call("post long-tap dispatch")); + } + + // Touch down + TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time()); + // Ensure the long-tap happens within 30ms even though there's no content + // response. + check.Call("pre long-tap dispatch"); + mcc->AdvanceByMillis(30); + check.Call("post long-tap dispatch"); +} + +TEST_F(APZCGestureDetectorTester, LongPressWithInputQueueDelay3) { + // Similar to the previous test, except now we have the long-press delay + // being longer than the content response timeout. + SCOPED_GFX_PREF_INT("apz.content_response_timeout", 30); + SCOPED_GFX_PREF_INT("ui.click_hold_context_menus.delay", 60); + + // Turn off touch-action to avoid having to send allowed touch actions to the + // input block. + SCOPED_GFX_PREF_BOOL("layout.css.touch_action.enabled", false); + + MakeApzcWaitForMainThread(); + + MockFunction check; + + { + InSequence s; + EXPECT_CALL(check, Call("pre long-tap dispatch")); + EXPECT_CALL(*mcc, HandleTap(TapType::eLongTap, LayoutDevicePoint(10, 10), 0, + apzc->GetGuid(), _)) + .Times(1); + EXPECT_CALL(check, Call("post long-tap dispatch")); + } + + // Touch down + TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time()); + // Ensure the long-tap happens at the 60ms mark even though the input event + // waits in the input queue for the full content response timeout of 30ms + mcc->AdvanceByMillis(59); + check.Call("pre long-tap dispatch"); + mcc->AdvanceByMillis(1); + check.Call("post long-tap dispatch"); +} diff --git a/gfx/layers/apz/test/gtest/TestHitTesting.cpp b/gfx/layers/apz/test/gtest/TestHitTesting.cpp new file mode 100644 index 0000000000..2b8089d7d9 --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestHitTesting.cpp @@ -0,0 +1,694 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "APZCTreeManagerTester.h" +#include "APZTestCommon.h" + +#include "InputUtils.h" + +class APZHitTestingTester : public APZCTreeManagerTester { + protected: + ScreenToParentLayerMatrix4x4 transformToApzc; + ParentLayerToScreenMatrix4x4 transformToGecko; + + already_AddRefed GetTargetAPZC( + const ScreenPoint& aPoint) { + RefPtr hit = + manager->GetTargetAPZC(aPoint).mTargetApzc; + if (hit) { + transformToApzc = manager->GetScreenToApzcTransform(hit.get()); + transformToGecko = manager->GetApzcToGeckoTransform(hit.get()); + } + return hit.forget(); + } + + protected: + void CreateHitTesting1LayerTree() { + const char* layerTreeSyntax = "c(tttt)"; + // LayerID 0 1234 + nsIntRegion layerVisibleRegion[] = { + nsIntRegion(IntRect(0, 0, 100, 100)), + nsIntRegion(IntRect(0, 0, 100, 100)), + nsIntRegion(IntRect(10, 10, 20, 20)), + nsIntRegion(IntRect(10, 10, 20, 20)), + nsIntRegion(IntRect(5, 5, 20, 20)), + }; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, + layers); + } + + void CreateHitTesting2LayerTree() { + const char* layerTreeSyntax = "c(tc(t))"; + // LayerID 0 12 3 + nsIntRegion layerVisibleRegion[] = { + nsIntRegion(IntRect(0, 0, 100, 100)), + nsIntRegion(IntRect(10, 10, 40, 40)), + nsIntRegion(IntRect(10, 60, 40, 40)), + nsIntRegion(IntRect(10, 60, 40, 40)), + }; + Matrix4x4 transforms[] = { + Matrix4x4(), + Matrix4x4(), + Matrix4x4::Scaling(2, 1, 1), + Matrix4x4(), + }; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, transforms, lm, + layers); + + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 200, 200)); + SetScrollableFrameMetrics(layers[1], + ScrollableLayerGuid::START_SCROLL_ID + 1, + CSSRect(0, 0, 80, 80)); + SetScrollableFrameMetrics(layers[3], + ScrollableLayerGuid::START_SCROLL_ID + 2, + CSSRect(0, 0, 80, 80)); + } + + void DisableApzOn(Layer* aLayer) { + ScrollMetadata m = aLayer->GetScrollMetadata(0); + m.SetForceDisableApz(true); + aLayer->SetScrollMetadata(m); + } + + void CreateComplexMultiLayerTree() { + const char* layerTreeSyntax = "c(tc(t)tc(c(t)tt))"; + // LayerID 0 12 3 45 6 7 89 + nsIntRegion layerVisibleRegion[] = { + nsIntRegion(IntRect(0, 0, 300, 400)), // root(0) + nsIntRegion(IntRect(0, 0, 100, 100)), // thebes(1) in top-left + nsIntRegion( + IntRect(50, 50, 200, 300)), // container(2) centered in root(0) + nsIntRegion( + IntRect(50, 50, 200, + 300)), // thebes(3) fully occupying parent container(2) + nsIntRegion(IntRect(0, 200, 100, 100)), // thebes(4) in bottom-left + nsIntRegion( + IntRect(200, 0, 100, + 400)), // container(5) along the right 100px of root(0) + nsIntRegion( + IntRect(200, 0, 100, 200)), // container(6) taking up the top half + // of parent container(5) + nsIntRegion( + IntRect(200, 0, 100, + 200)), // thebes(7) fully occupying parent container(6) + nsIntRegion(IntRect(200, 200, 100, + 100)), // thebes(8) in bottom-right (below (6)) + nsIntRegion(IntRect(200, 300, 100, + 100)), // thebes(9) in bottom-right (below (8)) + }; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, + layers); + SetScrollableFrameMetrics(layers[1], ScrollableLayerGuid::START_SCROLL_ID); + SetScrollableFrameMetrics(layers[2], ScrollableLayerGuid::START_SCROLL_ID); + SetScrollableFrameMetrics(layers[4], + ScrollableLayerGuid::START_SCROLL_ID + 1); + SetScrollableFrameMetrics(layers[6], + ScrollableLayerGuid::START_SCROLL_ID + 1); + SetScrollableFrameMetrics(layers[7], + ScrollableLayerGuid::START_SCROLL_ID + 2); + SetScrollableFrameMetrics(layers[8], + ScrollableLayerGuid::START_SCROLL_ID + 1); + SetScrollableFrameMetrics(layers[9], + ScrollableLayerGuid::START_SCROLL_ID + 3); + } + + void CreateBug1148350LayerTree() { + const char* layerTreeSyntax = "c(t)"; + // LayerID 0 1 + nsIntRegion layerVisibleRegion[] = { + nsIntRegion(IntRect(0, 0, 200, 200)), + nsIntRegion(IntRect(0, 0, 200, 200)), + }; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, + layers); + SetScrollableFrameMetrics(layers[1], ScrollableLayerGuid::START_SCROLL_ID); + } +}; + +// A simple hit testing test that doesn't involve any transforms on layers. +TEST_F(APZHitTestingTester, HitTesting1) { + SCOPED_GFX_VAR(UseWebRender, bool, false); + + CreateHitTesting1LayerTree(); + ScopedLayerTreeRegistration registration(manager, LayersId{0}, root, mcc); + + // No APZC attached so hit testing will return no APZC at (20,20) + RefPtr hit = GetTargetAPZC(ScreenPoint(20, 20)); + TestAsyncPanZoomController* nullAPZC = nullptr; + EXPECT_EQ(nullAPZC, hit.get()); + EXPECT_EQ(ScreenToParentLayerMatrix4x4(), transformToApzc); + EXPECT_EQ(ParentLayerToScreenMatrix4x4(), transformToGecko); + + uint32_t paintSequenceNumber = 0; + + // Now we have a root APZC that will match the page + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 100, 100)); + UpdateHitTestingTree(paintSequenceNumber++); + hit = GetTargetAPZC(ScreenPoint(15, 15)); + EXPECT_EQ(ApzcOf(root), hit.get()); + // expect hit point at LayerIntPoint(15, 15) + EXPECT_EQ(ParentLayerPoint(15, 15), + transformToApzc.TransformPoint(ScreenPoint(15, 15))); + EXPECT_EQ(ScreenPoint(15, 15), + transformToGecko.TransformPoint(ParentLayerPoint(15, 15))); + + // Now we have a sub APZC with a better fit + SetScrollableFrameMetrics(layers[3], ScrollableLayerGuid::START_SCROLL_ID + 1, + CSSRect(0, 0, 100, 100)); + UpdateHitTestingTree(paintSequenceNumber++); + EXPECT_NE(ApzcOf(root), ApzcOf(layers[3])); + hit = GetTargetAPZC(ScreenPoint(25, 25)); + EXPECT_EQ(ApzcOf(layers[3]), hit.get()); + // expect hit point at LayerIntPoint(25, 25) + EXPECT_EQ(ParentLayerPoint(25, 25), + transformToApzc.TransformPoint(ScreenPoint(25, 25))); + EXPECT_EQ(ScreenPoint(25, 25), + transformToGecko.TransformPoint(ParentLayerPoint(25, 25))); + + // At this point, layers[4] obscures layers[3] at the point (15, 15) so + // hitting there should hit the root APZC + hit = GetTargetAPZC(ScreenPoint(15, 15)); + EXPECT_EQ(ApzcOf(root), hit.get()); + + // Now test hit testing when we have two scrollable layers + SetScrollableFrameMetrics(layers[4], ScrollableLayerGuid::START_SCROLL_ID + 2, + CSSRect(0, 0, 100, 100)); + UpdateHitTestingTree(paintSequenceNumber++); + hit = GetTargetAPZC(ScreenPoint(15, 15)); + EXPECT_EQ(ApzcOf(layers[4]), hit.get()); + // expect hit point at LayerIntPoint(15, 15) + EXPECT_EQ(ParentLayerPoint(15, 15), + transformToApzc.TransformPoint(ScreenPoint(15, 15))); + EXPECT_EQ(ScreenPoint(15, 15), + transformToGecko.TransformPoint(ParentLayerPoint(15, 15))); + + // Hit test ouside the reach of layer[3,4] but inside root + hit = GetTargetAPZC(ScreenPoint(90, 90)); + EXPECT_EQ(ApzcOf(root), hit.get()); + // expect hit point at LayerIntPoint(90, 90) + EXPECT_EQ(ParentLayerPoint(90, 90), + transformToApzc.TransformPoint(ScreenPoint(90, 90))); + EXPECT_EQ(ScreenPoint(90, 90), + transformToGecko.TransformPoint(ParentLayerPoint(90, 90))); + + // Hit test ouside the reach of any layer + hit = GetTargetAPZC(ScreenPoint(1000, 10)); + EXPECT_EQ(nullAPZC, hit.get()); + EXPECT_EQ(ScreenToParentLayerMatrix4x4(), transformToApzc); + EXPECT_EQ(ParentLayerToScreenMatrix4x4(), transformToGecko); + hit = GetTargetAPZC(ScreenPoint(-1000, 10)); + EXPECT_EQ(nullAPZC, hit.get()); + EXPECT_EQ(ScreenToParentLayerMatrix4x4(), transformToApzc); + EXPECT_EQ(ParentLayerToScreenMatrix4x4(), transformToGecko); +} + +// A more involved hit testing test that involves css and async transforms. +TEST_F(APZHitTestingTester, HitTesting2) { + SCOPED_GFX_VAR(UseWebRender, bool, false); + // Velocity bias can cause extra repaint requests. + SCOPED_GFX_PREF_FLOAT("apz.velocity_bias", 0.0); + + CreateHitTesting2LayerTree(); + ScopedLayerTreeRegistration registration(manager, LayersId{0}, root, mcc); + + UpdateHitTestingTree(); + + // At this point, the following holds (all coordinates in screen pixels): + // layers[0] has content from (0,0)-(200,200), clipped by composition bounds + // (0,0)-(100,100) + // layers[1] has content from (10,10)-(90,90), clipped by composition bounds + // (10,10)-(50,50) + // layers[2] has content from (20,60)-(100,100). no clipping as it's not a + // scrollable layer + // layers[3] has content from (20,60)-(180,140), clipped by composition + // bounds (20,60)-(100,100) + + RefPtr apzcroot = ApzcOf(root); + TestAsyncPanZoomController* apzc1 = ApzcOf(layers[1]); + TestAsyncPanZoomController* apzc3 = ApzcOf(layers[3]); + + // Hit an area that's clearly on the root layer but not any of the child + // layers. + RefPtr hit = GetTargetAPZC(ScreenPoint(75, 25)); + EXPECT_EQ(apzcroot, hit.get()); + EXPECT_EQ(ParentLayerPoint(75, 25), + transformToApzc.TransformPoint(ScreenPoint(75, 25))); + EXPECT_EQ(ScreenPoint(75, 25), + transformToGecko.TransformPoint(ParentLayerPoint(75, 25))); + + // Hit an area on the root that would be on layers[3] if layers[2] + // weren't transformed. + // Note that if layers[2] were scrollable, then this would hit layers[2] + // because its composition bounds would be at (10,60)-(50,100) (and the + // scale-only transform that we set on layers[2] would be invalid because + // it would place the layer into overscroll, as its composition bounds + // start at x=10 but its content at x=20). + hit = GetTargetAPZC(ScreenPoint(15, 75)); + EXPECT_EQ(apzcroot, hit.get()); + EXPECT_EQ(ParentLayerPoint(15, 75), + transformToApzc.TransformPoint(ScreenPoint(15, 75))); + EXPECT_EQ(ScreenPoint(15, 75), + transformToGecko.TransformPoint(ParentLayerPoint(15, 75))); + + // Hit an area on layers[1]. + hit = GetTargetAPZC(ScreenPoint(25, 25)); + EXPECT_EQ(apzc1, hit.get()); + EXPECT_EQ(ParentLayerPoint(25, 25), + transformToApzc.TransformPoint(ScreenPoint(25, 25))); + EXPECT_EQ(ScreenPoint(25, 25), + transformToGecko.TransformPoint(ParentLayerPoint(25, 25))); + + // Hit an area on layers[3]. + hit = GetTargetAPZC(ScreenPoint(25, 75)); + EXPECT_EQ(apzc3, hit.get()); + // transformToApzc should unapply layers[2]'s transform + EXPECT_EQ(ParentLayerPoint(12.5, 75), + transformToApzc.TransformPoint(ScreenPoint(25, 75))); + // and transformToGecko should reapply it + EXPECT_EQ(ScreenPoint(25, 75), + transformToGecko.TransformPoint(ParentLayerPoint(12.5, 75))); + + // Hit an area on layers[3] that would be on the root if layers[2] + // weren't transformed. + hit = GetTargetAPZC(ScreenPoint(75, 75)); + EXPECT_EQ(apzc3, hit.get()); + // transformToApzc should unapply layers[2]'s transform + EXPECT_EQ(ParentLayerPoint(37.5, 75), + transformToApzc.TransformPoint(ScreenPoint(75, 75))); + // and transformToGecko should reapply it + EXPECT_EQ(ScreenPoint(75, 75), + transformToGecko.TransformPoint(ParentLayerPoint(37.5, 75))); + + // Pan the root layer upward by 50 pixels. + // This causes layers[1] to scroll out of view, and an async transform + // of -50 to be set on the root layer. + EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(3); + + // This first pan will move the APZC by 50 pixels, and dispatch a paint + // request. Since this paint request is in the queue to Gecko, + // transformToGecko will take it into account. + Pan(apzcroot, 100, 50, PanOptions::NoFling); + + // Hit where layers[3] used to be. It should now hit the root. + hit = GetTargetAPZC(ScreenPoint(75, 75)); + EXPECT_EQ(apzcroot, hit.get()); + // transformToApzc doesn't unapply the root's own async transform + EXPECT_EQ(ParentLayerPoint(75, 75), + transformToApzc.TransformPoint(ScreenPoint(75, 75))); + // and transformToGecko unapplies it and then reapplies it, because by the + // time the event being transformed reaches Gecko the new paint request will + // have been handled. + EXPECT_EQ(ScreenPoint(75, 75), + transformToGecko.TransformPoint(ParentLayerPoint(75, 75))); + + // Hit where layers[1] used to be and where layers[3] should now be. + hit = GetTargetAPZC(ScreenPoint(25, 25)); + EXPECT_EQ(apzc3, hit.get()); + // transformToApzc unapplies both layers[2]'s css transform and the root's + // async transform + EXPECT_EQ(ParentLayerPoint(12.5, 75), + transformToApzc.TransformPoint(ScreenPoint(25, 25))); + // transformToGecko reapplies both the css transform and the async transform + // because we have already issued a paint request with it. + EXPECT_EQ(ScreenPoint(25, 25), + transformToGecko.TransformPoint(ParentLayerPoint(12.5, 75))); + + // This second pan will move the APZC by another 50 pixels. + EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(3); + Pan(apzcroot, 100, 50, PanOptions::NoFling); + + // Hit where layers[3] used to be. It should now hit the root. + hit = GetTargetAPZC(ScreenPoint(75, 75)); + EXPECT_EQ(apzcroot, hit.get()); + // transformToApzc doesn't unapply the root's own async transform + EXPECT_EQ(ParentLayerPoint(75, 75), + transformToApzc.TransformPoint(ScreenPoint(75, 75))); + // transformToGecko unapplies the full async transform of -100 pixels + EXPECT_EQ(ScreenPoint(75, 75), + transformToGecko.TransformPoint(ParentLayerPoint(75, 75))); + + // Hit where layers[1] used to be. It should now hit the root. + hit = GetTargetAPZC(ScreenPoint(25, 25)); + EXPECT_EQ(apzcroot, hit.get()); + // transformToApzc doesn't unapply the root's own async transform + EXPECT_EQ(ParentLayerPoint(25, 25), + transformToApzc.TransformPoint(ScreenPoint(25, 25))); + // transformToGecko unapplies the full async transform of -100 pixels + EXPECT_EQ(ScreenPoint(25, 25), + transformToGecko.TransformPoint(ParentLayerPoint(25, 25))); +} + +TEST_F(APZHitTestingTester, HitTesting3) { + SCOPED_GFX_VAR(UseWebRender, bool, false); + + const char* layerTreeSyntax = "c(t)"; + // LayerID 0 1 + nsIntRegion layerVisibleRegions[] = {nsIntRegion(IntRect(0, 0, 200, 200)), + nsIntRegion(IntRect(0, 0, 50, 50))}; + Matrix4x4 transforms[] = {Matrix4x4(), Matrix4x4::Scaling(2, 2, 1)}; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, transforms, lm, + layers); + // No actual room to scroll + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 200, 200)); + SetScrollableFrameMetrics(layers[1], ScrollableLayerGuid::START_SCROLL_ID + 1, + CSSRect(0, 0, 50, 50)); + + ScopedLayerTreeRegistration registration(manager, LayersId{0}, root, mcc); + + UpdateHitTestingTree(); + + RefPtr hit = GetTargetAPZC(ScreenPoint(75, 75)); + EXPECT_EQ(ApzcOf(layers[1]), hit.get()); +} + +TEST_F(APZHitTestingTester, ComplexMultiLayerTree) { + SCOPED_GFX_VAR(UseWebRender, bool, false); + + CreateComplexMultiLayerTree(); + ScopedLayerTreeRegistration registration(manager, LayersId{0}, root, mcc); + UpdateHitTestingTree(); + + /* The layer tree looks like this: + + 0 + |----|--+--|----| + 1 2 4 5 + | /|\ + 3 6 8 9 + | + 7 + + Layers 1,2 have the same APZC + Layers 4,6,8 have the same APZC + Layer 7 has an APZC + Layer 9 has an APZC + */ + + TestAsyncPanZoomController* nullAPZC = nullptr; + // Ensure all the scrollable layers have an APZC + EXPECT_FALSE(layers[0]->HasScrollableFrameMetrics()); + EXPECT_NE(nullAPZC, ApzcOf(layers[1])); + EXPECT_NE(nullAPZC, ApzcOf(layers[2])); + EXPECT_FALSE(layers[3]->HasScrollableFrameMetrics()); + EXPECT_NE(nullAPZC, ApzcOf(layers[4])); + EXPECT_FALSE(layers[5]->HasScrollableFrameMetrics()); + EXPECT_NE(nullAPZC, ApzcOf(layers[6])); + EXPECT_NE(nullAPZC, ApzcOf(layers[7])); + EXPECT_NE(nullAPZC, ApzcOf(layers[8])); + EXPECT_NE(nullAPZC, ApzcOf(layers[9])); + // Ensure those that scroll together have the same APZCs + EXPECT_EQ(ApzcOf(layers[1]), ApzcOf(layers[2])); + EXPECT_EQ(ApzcOf(layers[4]), ApzcOf(layers[6])); + EXPECT_EQ(ApzcOf(layers[8]), ApzcOf(layers[6])); + // Ensure those that don't scroll together have different APZCs + EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[4])); + EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[7])); + EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[9])); + EXPECT_NE(ApzcOf(layers[4]), ApzcOf(layers[7])); + EXPECT_NE(ApzcOf(layers[4]), ApzcOf(layers[9])); + EXPECT_NE(ApzcOf(layers[7]), ApzcOf(layers[9])); + // Ensure the APZC parent chains are set up correctly + TestAsyncPanZoomController* layers1_2 = ApzcOf(layers[1]); + TestAsyncPanZoomController* layers4_6_8 = ApzcOf(layers[4]); + TestAsyncPanZoomController* layer7 = ApzcOf(layers[7]); + TestAsyncPanZoomController* layer9 = ApzcOf(layers[9]); + EXPECT_EQ(nullptr, layers1_2->GetParent()); + EXPECT_EQ(nullptr, layers4_6_8->GetParent()); + EXPECT_EQ(layers4_6_8, layer7->GetParent()); + EXPECT_EQ(nullptr, layer9->GetParent()); + // Ensure the hit-testing tree looks like the layer tree + RefPtr root = manager->GetRootNode(); + RefPtr node5 = root->GetLastChild(); + RefPtr node4 = node5->GetPrevSibling(); + RefPtr node2 = node4->GetPrevSibling(); + RefPtr node1 = node2->GetPrevSibling(); + RefPtr node3 = node2->GetLastChild(); + RefPtr node9 = node5->GetLastChild(); + RefPtr node8 = node9->GetPrevSibling(); + RefPtr node6 = node8->GetPrevSibling(); + RefPtr node7 = node6->GetLastChild(); + EXPECT_EQ(nullptr, node1->GetPrevSibling()); + EXPECT_EQ(nullptr, node3->GetPrevSibling()); + EXPECT_EQ(nullptr, node6->GetPrevSibling()); + EXPECT_EQ(nullptr, node7->GetPrevSibling()); + EXPECT_EQ(nullptr, node1->GetLastChild()); + EXPECT_EQ(nullptr, node3->GetLastChild()); + EXPECT_EQ(nullptr, node4->GetLastChild()); + EXPECT_EQ(nullptr, node7->GetLastChild()); + EXPECT_EQ(nullptr, node8->GetLastChild()); + EXPECT_EQ(nullptr, node9->GetLastChild()); + + RefPtr hit = GetTargetAPZC(ScreenPoint(25, 25)); + EXPECT_EQ(ApzcOf(layers[1]), hit.get()); + hit = GetTargetAPZC(ScreenPoint(275, 375)); + EXPECT_EQ(ApzcOf(layers[9]), hit.get()); + hit = GetTargetAPZC(ScreenPoint(250, 100)); + EXPECT_EQ(ApzcOf(layers[7]), hit.get()); +} + +TEST_F(APZHitTestingTester, TestRepaintFlushOnNewInputBlock) { + SCOPED_GFX_PREF_BOOL("layout.css.touch_action.enabled", false); + + // The main purpose of this test is to verify that touch-start events (or + // anything that starts a new input block) don't ever get untransformed. This + // should always hold because the APZ code should flush repaints when we start + // a new input block and the transform to gecko space should be empty. + + CreateSimpleScrollingLayer(); + ScopedLayerTreeRegistration registration(manager, LayersId{0}, root, mcc); + UpdateHitTestingTree(); + RefPtr apzcroot = ApzcOf(root); + + // At this point, the following holds (all coordinates in screen pixels): + // layers[0] has content from (0,0)-(500,500), clipped by composition bounds + // (0,0)-(200,200) + + MockFunction check; + + { + InSequence s; + + EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(1)); + EXPECT_CALL(check, Call("post-first-touch-start")); + EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(1)); + EXPECT_CALL(check, Call("post-second-fling")); + EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(1)); + EXPECT_CALL(check, Call("post-second-touch-start")); + } + + // This first pan will move the APZC by 50 pixels, and dispatch a paint + // request. + Pan(apzcroot, 100, 50, PanOptions::NoFling); + + // Verify that a touch start doesn't get untransformed + ScreenIntPoint touchPoint(50, 50); + MultiTouchInput mti = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); + mti.mTouches.AppendElement( + SingleTouchData(0, touchPoint, ScreenSize(0, 0), 0, 0)); + + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, + manager->ReceiveInputEvent(mti).mStatus); + EXPECT_EQ(touchPoint, mti.mTouches[0].mScreenPoint); + check.Call("post-first-touch-start"); + + // Send a touchend to clear state + mti.mType = MultiTouchInput::MULTITOUCH_END; + manager->ReceiveInputEvent(mti); + + mcc->AdvanceByMillis(1000); + + // Now do two pans. The first of these will dispatch a repaint request, as + // above. The second will get stuck in the paint throttler because the first + // one doesn't get marked as "completed", so this will result in a non-empty + // LD transform. (Note that any outstanding repaint requests from the first + // half of this test don't impact this half because we advance the time by 1 + // second, which will trigger the max-wait-exceeded codepath in the paint + // throttler). + Pan(apzcroot, 100, 50, PanOptions::NoFling); + check.Call("post-second-fling"); + Pan(apzcroot, 100, 50, PanOptions::NoFling); + + // Ensure that a touch start again doesn't get untransformed by flushing + // a repaint + mti.mType = MultiTouchInput::MULTITOUCH_START; + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, + manager->ReceiveInputEvent(mti).mStatus); + EXPECT_EQ(touchPoint, mti.mTouches[0].mScreenPoint); + check.Call("post-second-touch-start"); + + mti.mType = MultiTouchInput::MULTITOUCH_END; + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, + manager->ReceiveInputEvent(mti).mStatus); + EXPECT_EQ(touchPoint, mti.mTouches[0].mScreenPoint); +} + +TEST_F(APZHitTestingTester, TestRepaintFlushOnWheelEvents) { + // The purpose of this test is to ensure that wheel events trigger a repaint + // flush as per bug 1166871, and that the wheel event untransform is a no-op. + + CreateSimpleScrollingLayer(); + ScopedLayerTreeRegistration registration(manager, LayersId{0}, root, mcc); + UpdateHitTestingTree(); + TestAsyncPanZoomController* apzcroot = ApzcOf(root); + + EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(3)); + ScreenPoint origin(100, 50); + for (int i = 0; i < 3; i++) { + ScrollWheelInput swi(MillisecondsSinceStartup(mcc->Time()), mcc->Time(), 0, + ScrollWheelInput::SCROLLMODE_INSTANT, + ScrollWheelInput::SCROLLDELTA_PIXEL, origin, 0, 10, + false, WheelDeltaAdjustmentStrategy::eNone); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, + manager->ReceiveInputEvent(swi).mStatus); + EXPECT_EQ(origin, swi.mOrigin); + + AsyncTransform viewTransform; + ParentLayerPoint point; + apzcroot->SampleContentTransformForFrame(&viewTransform, point); + EXPECT_EQ(0, point.x); + EXPECT_EQ((i + 1) * 10, point.y); + EXPECT_EQ(0, viewTransform.mTranslation.x); + EXPECT_EQ((i + 1) * -10, viewTransform.mTranslation.y); + + mcc->AdvanceByMillis(5); + } +} + +TEST_F(APZHitTestingTester, TestForceDisableApz) { + CreateSimpleScrollingLayer(); + DisableApzOn(root); + ScopedLayerTreeRegistration registration(manager, LayersId{0}, root, mcc); + UpdateHitTestingTree(); + TestAsyncPanZoomController* apzcroot = ApzcOf(root); + + ScreenPoint origin(100, 50); + ScrollWheelInput swi(MillisecondsSinceStartup(mcc->Time()), mcc->Time(), 0, + ScrollWheelInput::SCROLLMODE_INSTANT, + ScrollWheelInput::SCROLLDELTA_PIXEL, origin, 0, 10, + false, WheelDeltaAdjustmentStrategy::eNone); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, + manager->ReceiveInputEvent(swi).mStatus); + EXPECT_EQ(origin, swi.mOrigin); + + AsyncTransform viewTransform; + ParentLayerPoint point; + apzcroot->SampleContentTransformForFrame(&viewTransform, point); + // Since APZ is force-disabled, we expect to see the async transform via + // the NORMAL AsyncMode, but not via the RESPECT_FORCE_DISABLE AsyncMode. + EXPECT_EQ(0, point.x); + EXPECT_EQ(10, point.y); + EXPECT_EQ(0, viewTransform.mTranslation.x); + EXPECT_EQ(-10, viewTransform.mTranslation.y); + viewTransform = apzcroot->GetCurrentAsyncTransform( + AsyncPanZoomController::eForCompositing); + point = apzcroot->GetCurrentAsyncScrollOffset( + AsyncPanZoomController::eForCompositing); + EXPECT_EQ(0, point.x); + EXPECT_EQ(0, point.y); + EXPECT_EQ(0, viewTransform.mTranslation.x); + EXPECT_EQ(0, viewTransform.mTranslation.y); + + mcc->AdvanceByMillis(10); + + // With untransforming events we should get normal behaviour (in this case, + // no noticeable untransform, because the repaint request already got + // flushed). + swi = ScrollWheelInput(MillisecondsSinceStartup(mcc->Time()), mcc->Time(), 0, + ScrollWheelInput::SCROLLMODE_INSTANT, + ScrollWheelInput::SCROLLDELTA_PIXEL, origin, 0, 0, + false, WheelDeltaAdjustmentStrategy::eNone); + EXPECT_EQ(nsEventStatus_eConsumeDoDefault, + manager->ReceiveInputEvent(swi).mStatus); + EXPECT_EQ(origin, swi.mOrigin); +} + +TEST_F(APZHitTestingTester, Bug1148350) { + CreateBug1148350LayerTree(); + ScopedLayerTreeRegistration registration(manager, LayersId{0}, root, mcc); + UpdateHitTestingTree(); + + MockFunction check; + { + InSequence s; + EXPECT_CALL(*mcc, + HandleTap(TapType::eSingleTap, LayoutDevicePoint(100, 100), 0, + ApzcOf(layers[1])->GetGuid(), _)) + .Times(1); + EXPECT_CALL(check, Call("Tapped without transform")); + EXPECT_CALL(*mcc, + HandleTap(TapType::eSingleTap, LayoutDevicePoint(100, 100), 0, + ApzcOf(layers[1])->GetGuid(), _)) + .Times(1); + EXPECT_CALL(check, Call("Tapped with interleaved transform")); + } + + Tap(manager, ScreenIntPoint(100, 100), TimeDuration::FromMilliseconds(100)); + mcc->RunThroughDelayedTasks(); + check.Call("Tapped without transform"); + + uint64_t blockId = + TouchDown(manager, ScreenIntPoint(100, 100), mcc->Time()).mInputBlockId; + if (StaticPrefs::layout_css_touch_action_enabled()) { + SetDefaultAllowedTouchBehavior(manager, blockId); + } + mcc->AdvanceByMillis(100); + + layers[0]->SetVisibleRegion(LayerIntRegion(LayerIntRect(0, 50, 200, 150))); + layers[0]->SetBaseTransform(Matrix4x4::Translation(0, 50, 0)); + UpdateHitTestingTree(); + + TouchUp(manager, ScreenIntPoint(100, 100), mcc->Time()); + mcc->RunThroughDelayedTasks(); + check.Call("Tapped with interleaved transform"); +} + +TEST_F(APZHitTestingTester, HitTestingRespectsScrollClip_Bug1257288) { + // Create the layer tree. + const char* layerTreeSyntax = "c(tt)"; + // LayerID 0 12 + nsIntRegion layerVisibleRegion[] = {nsIntRegion(IntRect(0, 0, 200, 200)), + nsIntRegion(IntRect(0, 0, 200, 200)), + nsIntRegion(IntRect(0, 0, 200, 100))}; + root = + CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers); + + // Add root scroll metadata to the first painted layer. + SetScrollableFrameMetrics(layers[1], ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 200, 200)); + + // Add root and subframe scroll metadata to the second painted layer. + // Give the subframe metadata a scroll clip corresponding to the subframe's + // composition bounds. + // Importantly, give the layer a layer clip which leaks outside of the + // subframe's composition bounds. + ScrollMetadata rootMetadata = BuildScrollMetadata( + ScrollableLayerGuid::START_SCROLL_ID, CSSRect(0, 0, 200, 200), + ParentLayerRect(0, 0, 200, 200)); + ScrollMetadata subframeMetadata = BuildScrollMetadata( + ScrollableLayerGuid::START_SCROLL_ID + 1, CSSRect(0, 0, 200, 200), + ParentLayerRect(0, 0, 200, 100)); + subframeMetadata.SetScrollClip( + Some(LayerClip(ParentLayerIntRect(0, 0, 200, 100)))); + layers[2]->SetScrollMetadata({subframeMetadata, rootMetadata}); + layers[2]->SetClipRect(Some(ParentLayerIntRect(0, 0, 200, 200))); + SetEventRegionsBasedOnBottommostMetrics(layers[2]); + + // Build the hit testing tree. + ScopedLayerTreeRegistration registration(manager, LayersId{0}, root, mcc); + UpdateHitTestingTree(); + + // Pan on a region that's inside layers[2]'s layer clip, but outside + // its subframe metadata's scroll clip. + Pan(manager, 120, 110); + + // Test that the subframe hasn't scrolled. + EXPECT_EQ(CSSPoint(0, 0), + ApzcOf(layers[2], 0)->GetFrameMetrics().GetVisualScrollOffset()); +} diff --git a/gfx/layers/apz/test/gtest/TestInputQueue.cpp b/gfx/layers/apz/test/gtest/TestInputQueue.cpp new file mode 100644 index 0000000000..665a276b03 --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestInputQueue.cpp @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "APZCTreeManagerTester.h" +#include "APZTestCommon.h" +#include "InputUtils.h" + +// Test of scenario described in bug 1269067 - that a continuing mouse drag +// doesn't interrupt a wheel scrolling animation +TEST_F(APZCTreeManagerTester, WheelInterruptedByMouseDrag) { + // Set up a scrollable layer + CreateSimpleScrollingLayer(); + ScopedLayerTreeRegistration registration(manager, LayersId{0}, root, mcc); + UpdateHitTestingTree(); + RefPtr apzc = ApzcOf(root); + + // First start the mouse drag + uint64_t dragBlockId = + MouseDown(apzc, ScreenIntPoint(5, 5), mcc->Time()).mInputBlockId; + uint64_t tmpBlockId = + MouseMove(apzc, ScreenIntPoint(6, 6), mcc->Time()).mInputBlockId; + EXPECT_EQ(dragBlockId, tmpBlockId); + + // Insert the wheel event, check that it has a new block id + uint64_t wheelBlockId = + SmoothWheel(apzc, ScreenIntPoint(6, 6), ScreenPoint(0, 1), mcc->Time()) + .mInputBlockId; + EXPECT_NE(dragBlockId, wheelBlockId); + + // Continue the drag, check that the block id is the same as before + tmpBlockId = MouseMove(apzc, ScreenIntPoint(7, 5), mcc->Time()).mInputBlockId; + EXPECT_EQ(dragBlockId, tmpBlockId); + + // Finish the wheel animation + apzc->AdvanceAnimationsUntilEnd(); + + // Check that it scrolled + ParentLayerPoint scroll = + apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting); + EXPECT_EQ(scroll.x, 0); + EXPECT_EQ(scroll.y, 10); // We scrolled 1 "line" or 10 pixels +} diff --git a/gfx/layers/apz/test/gtest/TestPanning.cpp b/gfx/layers/apz/test/gtest/TestPanning.cpp new file mode 100644 index 0000000000..4bfc7f89b6 --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestPanning.cpp @@ -0,0 +1,239 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "APZCBasicTester.h" +#include "APZTestCommon.h" +#include "InputUtils.h" +#include "gtest/gtest.h" + +class APZCPanningTester : public APZCBasicTester { + protected: + void DoPanTest(bool aShouldTriggerScroll, bool aShouldBeConsumed, + uint32_t aBehavior) { + if (aShouldTriggerScroll) { + // Three repaint request for each pan. + EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(6); + } else { + EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0); + } + + int touchStart = 50; + int touchEnd = 10; + ParentLayerPoint pointOut; + AsyncTransform viewTransformOut; + + nsTArray allowedTouchBehaviors; + allowedTouchBehaviors.AppendElement(aBehavior); + + // Pan down + PanAndCheckStatus(apzc, touchStart, touchEnd, aShouldBeConsumed, + &allowedTouchBehaviors); + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + + if (aShouldTriggerScroll) { + EXPECT_EQ(ParentLayerPoint(0, -(touchEnd - touchStart)), pointOut); + EXPECT_NE(AsyncTransform(), viewTransformOut); + } else { + EXPECT_EQ(ParentLayerPoint(), pointOut); + EXPECT_EQ(AsyncTransform(), viewTransformOut); + } + + // Clear the fling from the previous pan, or stopping it will + // consume the next touchstart + apzc->CancelAnimation(); + + // Pan back + PanAndCheckStatus(apzc, touchEnd, touchStart, aShouldBeConsumed, + &allowedTouchBehaviors); + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + + EXPECT_EQ(ParentLayerPoint(), pointOut); + EXPECT_EQ(AsyncTransform(), viewTransformOut); + } + + void DoPanWithPreventDefaultTest() { + MakeApzcWaitForMainThread(); + + int touchStart = 50; + int touchEnd = 10; + ParentLayerPoint pointOut; + AsyncTransform viewTransformOut; + uint64_t blockId = 0; + + // Pan down + nsTArray allowedTouchBehaviors; + allowedTouchBehaviors.AppendElement( + mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN); + PanAndCheckStatus(apzc, touchStart, touchEnd, true, &allowedTouchBehaviors, + &blockId); + + // Send the signal that content has handled and preventDefaulted the touch + // events. This flushes the event queue. + apzc->ContentReceivedInputBlock(blockId, true); + + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + EXPECT_EQ(ParentLayerPoint(), pointOut); + EXPECT_EQ(AsyncTransform(), viewTransformOut); + + apzc->AssertStateIsReset(); + } +}; + +TEST_F(APZCPanningTester, Pan) { + SCOPED_GFX_PREF_BOOL("layout.css.touch_action.enabled", false); + // Velocity bias can cause extra repaint requests. + SCOPED_GFX_PREF_FLOAT("apz.velocity_bias", 0.0); + DoPanTest(true, true, mozilla::layers::AllowedTouchBehavior::NONE); +} + +// In the each of the following 4 pan tests we are performing two pan gestures: +// vertical pan from top to bottom and back - from bottom to top. According to +// the pointer-events/touch-action spec AUTO and PAN_Y touch-action values allow +// vertical scrolling while NONE and PAN_X forbid it. The first parameter of +// DoPanTest method specifies this behavior. However, the events will be marked +// as consumed even if the behavior in PAN_X, because the user could move their +// finger horizontally too - APZ has no way of knowing beforehand and so must +// consume the events. +TEST_F(APZCPanningTester, PanWithTouchActionAuto) { + SCOPED_GFX_PREF_BOOL("layout.css.touch_action.enabled", true); + // Velocity bias can cause extra repaint requests. + SCOPED_GFX_PREF_FLOAT("apz.velocity_bias", 0.0); + DoPanTest(true, true, + mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN | + mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN); +} + +TEST_F(APZCPanningTester, PanWithTouchActionNone) { + SCOPED_GFX_PREF_BOOL("layout.css.touch_action.enabled", true); + // Velocity bias can cause extra repaint requests. + SCOPED_GFX_PREF_FLOAT("apz.velocity_bias", 0.0); + DoPanTest(false, false, 0); +} + +TEST_F(APZCPanningTester, PanWithTouchActionPanX) { + SCOPED_GFX_PREF_BOOL("layout.css.touch_action.enabled", true); + // Velocity bias can cause extra repaint requests. + SCOPED_GFX_PREF_FLOAT("apz.velocity_bias", 0.0); + DoPanTest(false, false, + mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN); +} + +TEST_F(APZCPanningTester, PanWithTouchActionPanY) { + SCOPED_GFX_PREF_BOOL("layout.css.touch_action.enabled", true); + // Velocity bias can cause extra repaint requests. + SCOPED_GFX_PREF_FLOAT("apz.velocity_bias", 0.0); + DoPanTest(true, true, mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN); +} + +TEST_F(APZCPanningTester, PanWithPreventDefaultAndTouchAction) { + SCOPED_GFX_PREF_BOOL("layout.css.touch_action.enabled", true); + DoPanWithPreventDefaultTest(); +} + +TEST_F(APZCPanningTester, PanWithPreventDefault) { + SCOPED_GFX_PREF_BOOL("layout.css.touch_action.enabled", false); + DoPanWithPreventDefaultTest(); +} + +TEST_F(APZCPanningTester, PanWithHistoricalTouchData) { + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0); + + // Simulate the same pan gesture, in three different ways. + // We start at y=50, with a 50ms resting period at the start of the pan. + // Then we accelerate the finger upwards towards y=10, reaching a 10px/10ms + // velocity towards the end of the panning motion. + // + // The first simulation fires touch move events with 10ms gaps. + // The second simulation skips two of the touch move events, simulating + // "jank". The third simulation also skips those two events, but reports the + // missed positions in the following event's historical coordinates. + // + // Consequently, the first and third simulation should estimate the same + // velocities, whereas the second simulation should estimate a different + // velocity because it is missing data. + + // First simulation: full data + + APZEventResult result = TouchDown(apzc, ScreenIntPoint(0, 50), mcc->Time()); + if (result.mStatus != nsEventStatus_eConsumeNoDefault && + StaticPrefs::layout_css_touch_action_enabled()) { + SetDefaultAllowedTouchBehavior(apzc, result.mInputBlockId); + } + + mcc->AdvanceByMillis(50); + result.mStatus = TouchMove(apzc, ScreenIntPoint(0, 45), mcc->Time()); + mcc->AdvanceByMillis(10); + result.mStatus = TouchMove(apzc, ScreenIntPoint(0, 40), mcc->Time()); + mcc->AdvanceByMillis(10); + result.mStatus = TouchMove(apzc, ScreenIntPoint(0, 30), mcc->Time()); + mcc->AdvanceByMillis(10); + result.mStatus = TouchMove(apzc, ScreenIntPoint(0, 20), mcc->Time()); + result.mStatus = TouchUp(apzc, ScreenIntPoint(0, 20), mcc->Time()); + auto velocityFromFullDataAsSeparateEvents = apzc->GetVelocityVector(); + apzc->CancelAnimation(); + + mcc->AdvanceByMillis(100); + + // Second simulation: partial data + + result = TouchDown(apzc, ScreenIntPoint(0, 50), mcc->Time()); + if (result.mStatus != nsEventStatus_eConsumeNoDefault && + StaticPrefs::layout_css_touch_action_enabled()) { + SetDefaultAllowedTouchBehavior(apzc, result.mInputBlockId); + } + + mcc->AdvanceByMillis(50); + result.mStatus = TouchMove(apzc, ScreenIntPoint(0, 45), mcc->Time()); + mcc->AdvanceByMillis(30); + result.mStatus = TouchMove(apzc, ScreenIntPoint(0, 20), mcc->Time()); + result.mStatus = TouchUp(apzc, ScreenIntPoint(0, 20), mcc->Time()); + auto velocityFromPartialData = apzc->GetVelocityVector(); + apzc->CancelAnimation(); + + mcc->AdvanceByMillis(100); + + // Third simulation: full data via historical data + + result = TouchDown(apzc, ScreenIntPoint(0, 50), mcc->Time()); + if (result.mStatus != nsEventStatus_eConsumeNoDefault && + StaticPrefs::layout_css_touch_action_enabled()) { + SetDefaultAllowedTouchBehavior(apzc, result.mInputBlockId); + } + + mcc->AdvanceByMillis(50); + result.mStatus = TouchMove(apzc, ScreenIntPoint(0, 45), mcc->Time()); + mcc->AdvanceByMillis(30); + + MultiTouchInput mti = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, mcc->Time()); + auto singleTouchData = CreateSingleTouchData(0, ScreenIntPoint(0, 20)); + singleTouchData.mHistoricalData.AppendElement( + SingleTouchData::HistoricalTouchData{ + mcc->Time() - TimeDuration::FromMilliseconds(20), + ScreenIntPoint(0, 40), + {}, + {}, + 0.0f, + 0.0f}); + singleTouchData.mHistoricalData.AppendElement( + SingleTouchData::HistoricalTouchData{ + mcc->Time() - TimeDuration::FromMilliseconds(10), + ScreenIntPoint(0, 30), + {}, + {}, + 0.0f, + 0.0f}); + mti.mTouches.AppendElement(singleTouchData); + result.mStatus = apzc->ReceiveInputEvent(mti).mStatus; + + result.mStatus = TouchUp(apzc, ScreenIntPoint(0, 20), mcc->Time()); + auto velocityFromFullDataViaHistory = apzc->GetVelocityVector(); + apzc->CancelAnimation(); + + EXPECT_EQ(velocityFromFullDataAsSeparateEvents, + velocityFromFullDataViaHistory); + EXPECT_NE(velocityFromPartialData, velocityFromFullDataViaHistory); +} diff --git a/gfx/layers/apz/test/gtest/TestPinching.cpp b/gfx/layers/apz/test/gtest/TestPinching.cpp new file mode 100644 index 0000000000..e64e6caf39 --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestPinching.cpp @@ -0,0 +1,625 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "APZCBasicTester.h" +#include "APZTestCommon.h" +#include "InputUtils.h" +#include "mozilla/StaticPrefs_apz.h" + +class APZCPinchTester : public APZCBasicTester { + public: + explicit APZCPinchTester( + AsyncPanZoomController::GestureBehavior aGestureBehavior = + AsyncPanZoomController::DEFAULT_GESTURES) + : APZCBasicTester(aGestureBehavior) {} + + protected: + FrameMetrics GetPinchableFrameMetrics() { + FrameMetrics fm; + fm.SetCompositionBounds(ParentLayerRect(0, 0, 100, 200)); + fm.SetScrollableRect(CSSRect(0, 0, 980, 1000)); + fm.SetVisualScrollOffset(CSSPoint(300, 300)); + fm.SetLayoutViewport(CSSRect(300, 300, 100, 200)); + fm.SetZoom(CSSToParentLayerScale2D(2.0, 2.0)); + // APZC only allows zooming on the root scrollable frame. + fm.SetIsRootContent(true); + // the visible area of the document in CSS pixels is x=300 y=300 w=50 h=100 + return fm; + } + + void DoPinchTest(bool aShouldTriggerPinch, + nsTArray* aAllowedTouchBehaviors = nullptr) { + apzc->SetFrameMetrics(GetPinchableFrameMetrics()); + MakeApzcZoomable(); + + if (aShouldTriggerPinch) { + // One repaint request for each gesture. + EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(2); + } else { + EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0); + } + + int touchInputId = 0; + if (mGestureBehavior == AsyncPanZoomController::USE_GESTURE_DETECTOR) { + PinchWithTouchInputAndCheckStatus(apzc, ScreenIntPoint(250, 300), 1.25, + touchInputId, aShouldTriggerPinch, + aAllowedTouchBehaviors); + } else { + PinchWithPinchInputAndCheckStatus(apzc, ScreenIntPoint(250, 300), 1.25, + aShouldTriggerPinch); + } + + apzc->AssertStateIsReset(); + + FrameMetrics fm = apzc->GetFrameMetrics(); + + if (aShouldTriggerPinch) { + // the visible area of the document in CSS pixels is now x=325 y=330 w=40 + // h=80 + EXPECT_EQ(2.5f, fm.GetZoom().ToScaleFactor().scale); + EXPECT_EQ(325, fm.GetVisualScrollOffset().x); + EXPECT_EQ(330, fm.GetVisualScrollOffset().y); + } else { + // The frame metrics should stay the same since touch-action:none makes + // apzc ignore pinch gestures. + EXPECT_EQ(2.0f, fm.GetZoom().ToScaleFactor().scale); + EXPECT_EQ(300, fm.GetVisualScrollOffset().x); + EXPECT_EQ(300, fm.GetVisualScrollOffset().y); + } + + // part 2 of the test, move to the top-right corner of the page and pinch + // and make sure we stay in the correct spot + fm.SetZoom(CSSToParentLayerScale2D(2.0, 2.0)); + fm.SetVisualScrollOffset(CSSPoint(930, 5)); + apzc->SetFrameMetrics(fm); + // the visible area of the document in CSS pixels is x=930 y=5 w=50 h=100 + + if (mGestureBehavior == AsyncPanZoomController::USE_GESTURE_DETECTOR) { + PinchWithTouchInputAndCheckStatus(apzc, ScreenIntPoint(250, 300), 0.5, + touchInputId, aShouldTriggerPinch, + aAllowedTouchBehaviors); + } else { + PinchWithPinchInputAndCheckStatus(apzc, ScreenIntPoint(250, 300), 0.5, + aShouldTriggerPinch); + } + + apzc->AssertStateIsReset(); + + fm = apzc->GetFrameMetrics(); + + if (aShouldTriggerPinch) { + // the visible area of the document in CSS pixels is now x=805 y=0 w=100 + // h=200 + EXPECT_EQ(1.0f, fm.GetZoom().ToScaleFactor().scale); + EXPECT_EQ(805, fm.GetVisualScrollOffset().x); + EXPECT_EQ(0, fm.GetVisualScrollOffset().y); + } else { + EXPECT_EQ(2.0f, fm.GetZoom().ToScaleFactor().scale); + EXPECT_EQ(930, fm.GetVisualScrollOffset().x); + EXPECT_EQ(5, fm.GetVisualScrollOffset().y); + } + } +}; + +class APZCPinchGestureDetectorTester : public APZCPinchTester { + public: + APZCPinchGestureDetectorTester() + : APZCPinchTester(AsyncPanZoomController::USE_GESTURE_DETECTOR) {} + + void DoPinchWithPreventDefaultTest() { + FrameMetrics originalMetrics = GetPinchableFrameMetrics(); + apzc->SetFrameMetrics(originalMetrics); + + MakeApzcWaitForMainThread(); + MakeApzcZoomable(); + + int touchInputId = 0; + uint64_t blockId = 0; + PinchWithTouchInput(apzc, ScreenIntPoint(250, 300), 1.25, touchInputId, + nullptr, nullptr, &blockId); + + // Send the prevent-default notification for the touch block + apzc->ContentReceivedInputBlock(blockId, true); + + // verify the metrics didn't change (i.e. the pinch was ignored) + FrameMetrics fm = apzc->GetFrameMetrics(); + EXPECT_EQ(originalMetrics.GetZoom(), fm.GetZoom()); + EXPECT_EQ(originalMetrics.GetVisualScrollOffset().x, + fm.GetVisualScrollOffset().x); + EXPECT_EQ(originalMetrics.GetVisualScrollOffset().y, + fm.GetVisualScrollOffset().y); + + apzc->AssertStateIsReset(); + } +}; + +class APZCPinchLockingTester : public APZCPinchTester { + private: + static const int mDPI = 160; + + ScreenIntPoint mFocus; + float mSpan; + + public: + APZCPinchLockingTester() + : APZCPinchTester(AsyncPanZoomController::USE_GESTURE_DETECTOR), + mFocus(ScreenIntPoint(200, 300)), + mSpan(10.0) {} + + virtual void SetUp() { + APZCPinchTester::SetUp(); + tm->SetDPI(mDPI); + apzc->SetFrameMetrics(GetPinchableFrameMetrics()); + MakeApzcZoomable(); + + apzc->ReceiveInputEvent( + CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_START, mFocus, + mSpan, mSpan, mcc->Time()), + nullptr); + mcc->AdvanceBy(TimeDuration::FromMilliseconds(51)); + } + + void twoFingerPan() { + ScreenCoord panDistance = + StaticPrefs::apz_pinch_lock_scroll_lock_threshold() * 1.2 * + tm->GetDPI(); + + mFocus = ScreenIntPoint((int)(mFocus.x + panDistance), (int)(mFocus.y)); + + apzc->ReceiveInputEvent( + CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_SCALE, mFocus, + mSpan, mSpan, mcc->Time()), + nullptr); + mcc->AdvanceBy(TimeDuration::FromMilliseconds(51)); + } + + void twoFingerZoom() { + float pinchDistance = + StaticPrefs::apz_pinch_lock_span_breakout_threshold() * 1.2 * + tm->GetDPI(); + + float newSpan = mSpan + pinchDistance; + + apzc->ReceiveInputEvent( + CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_SCALE, mFocus, + newSpan, mSpan, mcc->Time()), + nullptr); + mcc->AdvanceBy(TimeDuration::FromMilliseconds(51)); + mSpan = newSpan; + } + + bool isPinchLockActive() { + FrameMetrics originalMetrics = apzc->GetFrameMetrics(); + + // Send a small scale input to the APZC + float pinchDistance = + StaticPrefs::apz_pinch_lock_span_breakout_threshold() * 0.8 * + tm->GetDPI(); + apzc->ReceiveInputEvent( + CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_SCALE, mFocus, + mSpan + pinchDistance, mSpan, mcc->Time()), + nullptr); + + FrameMetrics result = apzc->GetFrameMetrics(); + bool lockActive = originalMetrics.GetZoom() == result.GetZoom() && + originalMetrics.GetVisualScrollOffset().x == + result.GetVisualScrollOffset().x && + originalMetrics.GetVisualScrollOffset().y == + result.GetVisualScrollOffset().y; + + // Avoid side effects, reset to original frame metrics + apzc->SetFrameMetrics(originalMetrics); + return lockActive; + } +}; + +TEST_F(APZCPinchTester, Pinch_DefaultGestures_NoTouchAction) { + SCOPED_GFX_PREF_BOOL("layout.css.touch_action.enabled", false); + DoPinchTest(true); +} + +TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_NoTouchAction) { + SCOPED_GFX_PREF_BOOL("layout.css.touch_action.enabled", false); + DoPinchTest(true); +} + +TEST_F(APZCPinchGestureDetectorTester, + Pinch_UseGestureDetector_TouchActionNone) { + SCOPED_GFX_PREF_BOOL("layout.css.touch_action.enabled", true); + nsTArray behaviors = {mozilla::layers::AllowedTouchBehavior::NONE, + mozilla::layers::AllowedTouchBehavior::NONE}; + DoPinchTest(false, &behaviors); +} + +TEST_F(APZCPinchGestureDetectorTester, + Pinch_UseGestureDetector_TouchActionZoom) { + SCOPED_GFX_PREF_BOOL("layout.css.touch_action.enabled", true); + nsTArray behaviors; + behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM); + behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM); + DoPinchTest(true, &behaviors); +} + +TEST_F(APZCPinchGestureDetectorTester, + Pinch_UseGestureDetector_TouchActionNotAllowZoom) { + SCOPED_GFX_PREF_BOOL("layout.css.touch_action.enabled", true); + nsTArray behaviors; + behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::NONE); + behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM); + DoPinchTest(false, &behaviors); +} + +TEST_F(APZCPinchGestureDetectorTester, + Pinch_UseGestureDetector_TouchActionNone_NoAPZZoom) { + SCOPED_GFX_PREF_BOOL("layout.css.touch_action.enabled", true); + SCOPED_GFX_PREF_BOOL("apz.allow_zooming", false); + + // Since we are preventing the pinch action via touch-action we should not be + // sending the pinch gesture notifications that would normally be sent when + // apz_allow_zooming is false. + EXPECT_CALL(*mcc, NotifyPinchGesture(_, _, _, _, _)).Times(0); + nsTArray behaviors = {mozilla::layers::AllowedTouchBehavior::NONE, + mozilla::layers::AllowedTouchBehavior::NONE}; + DoPinchTest(false, &behaviors); +} + +TEST_F(APZCPinchGestureDetectorTester, Pinch_PreventDefault) { + DoPinchWithPreventDefaultTest(); +} + +TEST_F(APZCPinchGestureDetectorTester, Pinch_PreventDefault_NoAPZZoom) { + SCOPED_GFX_PREF_BOOL("apz.allow_zooming", false); + + // Since we are preventing the pinch action we should not be sending the pinch + // gesture notifications that would normally be sent when apz_allow_zooming is + // false. + EXPECT_CALL(*mcc, NotifyPinchGesture(_, _, _, _, _)).Times(0); + + DoPinchWithPreventDefaultTest(); +} + +TEST_F(APZCPinchGestureDetectorTester, Panning_TwoFingerFling_ZoomDisabled) { + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); + + apzc->SetFrameMetrics(GetPinchableFrameMetrics()); + MakeApzcUnzoomable(); + + // Perform a two finger pan + int touchInputId = 0; + uint64_t blockId = 0; + PinchWithTouchInput(apzc, ScreenIntPoint(100, 200), ScreenIntPoint(100, 100), + 1, touchInputId, nullptr, nullptr, &blockId); + + // Expect to be in a flinging state + apzc->AssertStateIsFling(); +} + +TEST_F(APZCPinchGestureDetectorTester, Panning_TwoFingerFling_ZoomEnabled) { + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); + + apzc->SetFrameMetrics(GetPinchableFrameMetrics()); + MakeApzcZoomable(); + + // Perform a two finger pan + int touchInputId = 0; + uint64_t blockId = 0; + PinchWithTouchInput(apzc, ScreenIntPoint(100, 200), ScreenIntPoint(100, 100), + 1, touchInputId, nullptr, nullptr, &blockId); + + // Expect to NOT be in flinging state + apzc->AssertStateIsReset(); +} + +TEST_F(APZCPinchGestureDetectorTester, + Panning_TwoThenOneFingerFling_ZoomEnabled) { + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); + + apzc->SetFrameMetrics(GetPinchableFrameMetrics()); + MakeApzcZoomable(); + + // Perform a two finger pan lifting only the first finger + int touchInputId = 0; + uint64_t blockId = 0; + PinchWithTouchInput(apzc, ScreenIntPoint(100, 200), ScreenIntPoint(100, 100), + 1, touchInputId, nullptr, nullptr, &blockId, + PinchOptions::LiftFinger2); + + // Lift second finger after a pause + mcc->AdvanceBy(TimeDuration::FromMilliseconds(50)); + TouchUp(apzc, ScreenIntPoint(100, 100), mcc->Time()); + + // Expect to NOT be in flinging state + apzc->AssertStateIsReset(); +} + +TEST_F(APZCPinchTester, Panning_TwoFinger_ZoomDisabled) { + // set up APZ + apzc->SetFrameMetrics(GetPinchableFrameMetrics()); + MakeApzcUnzoomable(); + + nsEventStatus statuses[3]; // scalebegin, scale, scaleend + PinchWithPinchInput(apzc, ScreenIntPoint(250, 350), ScreenIntPoint(200, 300), + 10, &statuses); + + FrameMetrics fm = apzc->GetFrameMetrics(); + + // It starts from (300, 300), then moves the focus point from (250, 350) to + // (200, 300) pans by (50, 50) screen pixels, but there is a 2x zoom, which + // causes the scroll offset to change by half of that (25, 25) pixels. + EXPECT_EQ(325, fm.GetVisualScrollOffset().x); + EXPECT_EQ(325, fm.GetVisualScrollOffset().y); + EXPECT_EQ(2.0, fm.GetZoom().ToScaleFactor().scale); +} + +TEST_F(APZCPinchTester, Panning_Beyond_LayoutViewport) { + apzc->SetFrameMetrics(GetPinchableFrameMetrics()); + MakeApzcZoomable(); + + // Case 1 - visual viewport is still inside layout viewport. + Pan(apzc, 350, 300, PanOptions::NoFling); + FrameMetrics fm = apzc->GetFrameMetrics(); + // It starts from (300, 300) pans by (0, 50) screen pixels, but there is a + // 2x zoom, which causes the scroll offset to change by half of that (0, 25). + // But the visual viewport is still inside the layout viewport. + EXPECT_EQ(300, fm.GetVisualScrollOffset().x); + EXPECT_EQ(325, fm.GetVisualScrollOffset().y); + EXPECT_EQ(300, fm.GetLayoutViewport().X()); + EXPECT_EQ(300, fm.GetLayoutViewport().Y()); + + // Case 2 - visual viewport crosses the bottom boundary of the layout + // viewport. + Pan(apzc, 525, 325, PanOptions::NoFling); + fm = apzc->GetFrameMetrics(); + // It starts from (300, 325) pans by (0, 200) screen pixels, but there is a + // 2x zoom, which causes the scroll offset to change by half of that + // (0, 100). The visual viewport crossed the bottom boundary of the layout + // viewport by 25px. + EXPECT_EQ(300, fm.GetVisualScrollOffset().x); + EXPECT_EQ(425, fm.GetVisualScrollOffset().y); + EXPECT_EQ(300, fm.GetLayoutViewport().X()); + EXPECT_EQ(325, fm.GetLayoutViewport().Y()); + + // Case 3 - visual viewport crosses the top boundary of the layout viewport. + Pan(apzc, 425, 775, PanOptions::NoFling); + fm = apzc->GetFrameMetrics(); + // It starts from (300, 425) pans by (0, -350) screen pixels, but there is a + // 2x zoom, which causes the scroll offset to change by half of that + // (0, -175). The visual viewport crossed the top of the layout viewport by + // 75px. + EXPECT_EQ(300, fm.GetVisualScrollOffset().x); + EXPECT_EQ(250, fm.GetVisualScrollOffset().y); + EXPECT_EQ(300, fm.GetLayoutViewport().X()); + EXPECT_EQ(250, fm.GetLayoutViewport().Y()); + + // Case 4 - visual viewport crosses the left boundary of the layout viewport. + Pan(apzc, ScreenIntPoint(150, 10), ScreenIntPoint(350, 10), + PanOptions::NoFling); + fm = apzc->GetFrameMetrics(); + // It starts from (300, 250) pans by (-200, 0) screen pixels, but there is a + // 2x zoom, which causes the scroll offset to change by half of that + // (-100, 0). The visual viewport crossed the left boundary of the layout + // viewport by 100px. + EXPECT_EQ(200, fm.GetVisualScrollOffset().x); + EXPECT_EQ(250, fm.GetVisualScrollOffset().y); + EXPECT_EQ(200, fm.GetLayoutViewport().X()); + EXPECT_EQ(250, fm.GetLayoutViewport().Y()); + + // Case 5 - visual viewport crosses the right boundary of the layout viewport. + Pan(apzc, ScreenIntPoint(350, 10), ScreenIntPoint(150, 10), + PanOptions::NoFling); + fm = apzc->GetFrameMetrics(); + // It starts from (200, 250) pans by (200, 0) screen pixels, but there is a + // 2x zoom, which causes the scroll offset to change by half of that + // (100, 0). The visual viewport crossed the right boundary of the layout + // viewport by 50px. + EXPECT_EQ(300, fm.GetVisualScrollOffset().x); + EXPECT_EQ(250, fm.GetVisualScrollOffset().y); + EXPECT_EQ(250, fm.GetLayoutViewport().X()); + EXPECT_EQ(250, fm.GetLayoutViewport().Y()); + + // Case 6 - visual viewport crosses both the vertical and horizontal + // boundaries of the layout viewport by moving diagonally towards the + // top-right corner. + Pan(apzc, ScreenIntPoint(350, 200), ScreenIntPoint(150, 400), + PanOptions::NoFling); + fm = apzc->GetFrameMetrics(); + // It starts from (300, 250) pans by (200, -200) screen pixels, but there is + // a 2x zoom, which causes the scroll offset to change by half of that + // (100, -100). The visual viewport moved by (100, -100) outside the + // boundary of the layout viewport. + EXPECT_EQ(400, fm.GetVisualScrollOffset().x); + EXPECT_EQ(150, fm.GetVisualScrollOffset().y); + EXPECT_EQ(350, fm.GetLayoutViewport().X()); + EXPECT_EQ(150, fm.GetLayoutViewport().Y()); +} + +TEST_F(APZCPinchGestureDetectorTester, Pinch_APZZoom_Disabled) { + SCOPED_GFX_PREF_BOOL("apz.allow_zooming", false); + + FrameMetrics originalMetrics = GetPinchableFrameMetrics(); + apzc->SetFrameMetrics(originalMetrics); + + // When apz_allow_zooming is false, the ZoomConstraintsClient produces + // ZoomConstraints with mAllowZoom set to false. + MakeApzcUnzoomable(); + + // With apz_allow_zooming false, we expect the NotifyPinchGesture function to + // get called as the pinch progresses, but the metrics shouldn't change. + EXPECT_CALL(*mcc, + NotifyPinchGesture(PinchGestureInput::PINCHGESTURE_START, + apzc->GetGuid(), _, LayoutDeviceCoord(0), _)) + .Times(1); + EXPECT_CALL(*mcc, NotifyPinchGesture(PinchGestureInput::PINCHGESTURE_SCALE, + apzc->GetGuid(), _, _, _)) + .Times(AtLeast(1)); + EXPECT_CALL(*mcc, + NotifyPinchGesture(PinchGestureInput::PINCHGESTURE_END, + apzc->GetGuid(), _, LayoutDeviceCoord(0), _)) + .Times(1); + + int touchInputId = 0; + uint64_t blockId = 0; + PinchWithTouchInput(apzc, ScreenIntPoint(250, 300), 1.25, touchInputId, + nullptr, nullptr, &blockId); + + // verify the metrics didn't change (i.e. the pinch was ignored inside APZ) + FrameMetrics fm = apzc->GetFrameMetrics(); + EXPECT_EQ(originalMetrics.GetZoom(), fm.GetZoom()); + EXPECT_EQ(originalMetrics.GetVisualScrollOffset().x, + fm.GetVisualScrollOffset().x); + EXPECT_EQ(originalMetrics.GetVisualScrollOffset().y, + fm.GetVisualScrollOffset().y); + + apzc->AssertStateIsReset(); +} + +TEST_F(APZCPinchGestureDetectorTester, Pinch_NoSpan) { + SCOPED_GFX_PREF_BOOL("apz.allow_zooming", false); + SCOPED_GFX_PREF_BOOL("layout.css.touch_action.enabled", false); + + FrameMetrics originalMetrics = GetPinchableFrameMetrics(); + apzc->SetFrameMetrics(originalMetrics); + + // When apz_allow_zooming is false, the ZoomConstraintsClient produces + // ZoomConstraints with mAllowZoom set to false. + MakeApzcUnzoomable(); + + // With apz_allow_zooming false, we expect the NotifyPinchGesture function to + // get called as the pinch progresses, but the metrics shouldn't change. + EXPECT_CALL(*mcc, + NotifyPinchGesture(PinchGestureInput::PINCHGESTURE_START, + apzc->GetGuid(), _, LayoutDeviceCoord(0), _)) + .Times(1); + EXPECT_CALL(*mcc, NotifyPinchGesture(PinchGestureInput::PINCHGESTURE_SCALE, + apzc->GetGuid(), _, _, _)) + .Times(AtLeast(1)); + EXPECT_CALL(*mcc, + NotifyPinchGesture(PinchGestureInput::PINCHGESTURE_END, + apzc->GetGuid(), _, LayoutDeviceCoord(0), _)) + .Times(1); + + int inputId = 0; + ScreenIntPoint focus(250, 300); + + // Do a pinch holding a zero span and moving the focus by y=100 + + const TimeDuration TIME_BETWEEN_TOUCH_EVENT = + TimeDuration::FromMilliseconds(50); + + MultiTouchInput mtiStart = + MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, mcc->Time(), 0); + mtiStart.mTouches.AppendElement(CreateSingleTouchData(inputId, focus)); + mtiStart.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, focus)); + apzc->ReceiveInputEvent(mtiStart, nullptr); + mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + + focus.y -= 35 + 1; // this is to get over the PINCH_START_THRESHOLD in + // GestureEventListener.cpp + MultiTouchInput mtiMove1 = + MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, mcc->Time(), 0); + mtiMove1.mTouches.AppendElement(CreateSingleTouchData(inputId, focus)); + mtiMove1.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, focus)); + apzc->ReceiveInputEvent(mtiMove1, nullptr); + mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + + focus.y -= 100; // do a two-finger scroll of 100 screen pixels + MultiTouchInput mtiMove2 = + MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, mcc->Time(), 0); + mtiMove2.mTouches.AppendElement(CreateSingleTouchData(inputId, focus)); + mtiMove2.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, focus)); + apzc->ReceiveInputEvent(mtiMove2, nullptr); + mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); + + MultiTouchInput mtiEnd = + MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, mcc->Time(), 0); + mtiEnd.mTouches.AppendElement(CreateSingleTouchData(inputId, focus)); + mtiEnd.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, focus)); + apzc->ReceiveInputEvent(mtiEnd, nullptr); + + // Done, check the metrics to make sure we scrolled by 100 screen pixels, + // which is 50 CSS pixels for the pinchable frame metrics. + + FrameMetrics fm = apzc->GetFrameMetrics(); + EXPECT_EQ(originalMetrics.GetZoom(), fm.GetZoom()); + EXPECT_EQ(originalMetrics.GetVisualScrollOffset().x, + fm.GetVisualScrollOffset().x); + EXPECT_EQ(originalMetrics.GetVisualScrollOffset().y + 50, + fm.GetVisualScrollOffset().y); + + apzc->AssertStateIsReset(); +} + +TEST_F(APZCPinchTester, Pinch_TwoFinger_APZZoom_Disabled_Bug1354185) { + // Set up APZ such that mZoomConstraints.mAllowZoom is false. + SCOPED_GFX_PREF_BOOL("apz.allow_zooming", false); + apzc->SetFrameMetrics(GetPinchableFrameMetrics()); + MakeApzcUnzoomable(); + + // We expect a repaint request for scrolling. + EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1); + + // Send only the PINCHGESTURE_START and PINCHGESTURE_SCALE events, + // in order to trigger a call to AsyncPanZoomController::OnScale + // but not to AsyncPanZoomController::OnScaleEnd. + ScreenIntPoint aFocus(250, 350); + ScreenIntPoint aSecondFocus(200, 300); + float aScale = 10; + apzc->ReceiveInputEvent( + CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_START, aFocus, + 10.0, 10.0, mcc->Time()), + nullptr); + + apzc->ReceiveInputEvent( + CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_SCALE, + aSecondFocus, 10.0 * aScale, 10.0, mcc->Time()), + nullptr); +} + +TEST_F(APZCPinchLockingTester, Pinch_Locking_Free) { + SCOPED_GFX_PREF_INT("apz.pinch_lock.mode", 0); // PINCH_FREE + + twoFingerPan(); + EXPECT_FALSE(isPinchLockActive()); +} + +TEST_F(APZCPinchLockingTester, Pinch_Locking_Normal_Lock) { + SCOPED_GFX_PREF_INT("apz.pinch_lock.mode", 1); // PINCH_NORMAL + + twoFingerPan(); + EXPECT_TRUE(isPinchLockActive()); +} + +TEST_F(APZCPinchLockingTester, Pinch_Locking_Normal_Lock_Break) { + SCOPED_GFX_PREF_INT("apz.pinch_lock.mode", 1); // PINCH_NORMAL + + twoFingerPan(); + twoFingerZoom(); + EXPECT_TRUE(isPinchLockActive()); +} + +TEST_F(APZCPinchLockingTester, Pinch_Locking_Sticky_Lock) { + SCOPED_GFX_PREF_INT("apz.pinch_lock.mode", 2); // PINCH_STICKY + + twoFingerPan(); + EXPECT_TRUE(isPinchLockActive()); +} + +TEST_F(APZCPinchLockingTester, Pinch_Locking_Sticky_Lock_Break) { + SCOPED_GFX_PREF_INT("apz.pinch_lock.mode", 2); // PINCH_STICKY + + twoFingerPan(); + twoFingerZoom(); + EXPECT_FALSE(isPinchLockActive()); +} + +TEST_F(APZCPinchLockingTester, Pinch_Locking_Sticky_Lock_Break_Lock) { + SCOPED_GFX_PREF_INT("apz.pinch_lock.mode", 2); // PINCH_STICKY + + twoFingerPan(); + twoFingerZoom(); + twoFingerPan(); + EXPECT_TRUE(isPinchLockActive()); +} diff --git a/gfx/layers/apz/test/gtest/TestScrollHandoff.cpp b/gfx/layers/apz/test/gtest/TestScrollHandoff.cpp new file mode 100644 index 0000000000..03cfeafe81 --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestScrollHandoff.cpp @@ -0,0 +1,658 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "APZCTreeManagerTester.h" +#include "APZTestCommon.h" +#include "InputUtils.h" + +class APZScrollHandoffTester : public APZCTreeManagerTester { + protected: + UniquePtr registration; + TestAsyncPanZoomController* rootApzc; + + void CreateScrollHandoffLayerTree1() { + const char* layerTreeSyntax = "c(t)"; + nsIntRegion layerVisibleRegion[] = {nsIntRegion(IntRect(0, 0, 100, 100)), + nsIntRegion(IntRect(0, 50, 100, 50))}; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, + layers); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 200, 200)); + SetScrollableFrameMetrics(layers[1], + ScrollableLayerGuid::START_SCROLL_ID + 1, + CSSRect(0, 0, 100, 100)); + SetScrollHandoff(layers[1], root); + registration = MakeUnique(manager, LayersId{0}, + root, mcc); + UpdateHitTestingTree(); + rootApzc = ApzcOf(root); + rootApzc->GetFrameMetrics().SetIsRootContent( + true); // make root APZC zoomable + } + + void CreateScrollHandoffLayerTree2() { + const char* layerTreeSyntax = "c(c(t))"; + nsIntRegion layerVisibleRegion[] = {nsIntRegion(IntRect(0, 0, 100, 100)), + nsIntRegion(IntRect(0, 0, 100, 100)), + nsIntRegion(IntRect(0, 50, 100, 50))}; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, + layers); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 200, 200)); + SetScrollableFrameMetrics(layers[1], + ScrollableLayerGuid::START_SCROLL_ID + 2, + CSSRect(-100, -100, 200, 200)); + SetScrollableFrameMetrics(layers[2], + ScrollableLayerGuid::START_SCROLL_ID + 1, + CSSRect(0, 0, 100, 100)); + SetScrollHandoff(layers[1], root); + SetScrollHandoff(layers[2], layers[1]); + // No ScopedLayerTreeRegistration as that just needs to be done once per + // test and this is the second layer tree for a particular test. + MOZ_ASSERT(registration); + UpdateHitTestingTree(); + rootApzc = ApzcOf(root); + } + + void CreateScrollHandoffLayerTree3() { + const char* layerTreeSyntax = "c(c(t)c(t))"; + nsIntRegion layerVisibleRegion[] = { + nsIntRegion(IntRect(0, 0, 100, 100)), // root + nsIntRegion(IntRect(0, 0, 100, 50)), // scrolling parent 1 + nsIntRegion(IntRect(0, 0, 100, 50)), // scrolling child 1 + nsIntRegion(IntRect(0, 50, 100, 50)), // scrolling parent 2 + nsIntRegion(IntRect(0, 50, 100, 50)) // scrolling child 2 + }; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, + layers); + SetScrollableFrameMetrics(layers[0], ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 100, 100)); + SetScrollableFrameMetrics(layers[1], + ScrollableLayerGuid::START_SCROLL_ID + 1, + CSSRect(0, 0, 100, 100)); + SetScrollableFrameMetrics(layers[2], + ScrollableLayerGuid::START_SCROLL_ID + 2, + CSSRect(0, 0, 100, 100)); + SetScrollableFrameMetrics(layers[3], + ScrollableLayerGuid::START_SCROLL_ID + 3, + CSSRect(0, 50, 100, 100)); + SetScrollableFrameMetrics(layers[4], + ScrollableLayerGuid::START_SCROLL_ID + 4, + CSSRect(0, 50, 100, 100)); + SetScrollHandoff(layers[1], layers[0]); + SetScrollHandoff(layers[3], layers[0]); + SetScrollHandoff(layers[2], layers[1]); + SetScrollHandoff(layers[4], layers[3]); + registration = MakeUnique(manager, LayersId{0}, + root, mcc); + UpdateHitTestingTree(); + } + + // Creates a layer tree with a parent layer that is only scrollable + // horizontally, and a child layer that is only scrollable vertically. + void CreateScrollHandoffLayerTree4() { + const char* layerTreeSyntax = "c(t)"; + nsIntRegion layerVisibleRegion[] = {nsIntRegion(IntRect(0, 0, 100, 100)), + nsIntRegion(IntRect(0, 0, 100, 100))}; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, + layers); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 200, 100)); + SetScrollableFrameMetrics(layers[1], + ScrollableLayerGuid::START_SCROLL_ID + 1, + CSSRect(0, 0, 100, 200)); + SetScrollHandoff(layers[1], root); + registration = MakeUnique(manager, LayersId{0}, + root, mcc); + UpdateHitTestingTree(); + rootApzc = ApzcOf(root); + } + + void CreateScrollgrabLayerTree(bool makeParentScrollable = true) { + const char* layerTreeSyntax = "c(t)"; + nsIntRegion layerVisibleRegion[] = { + nsIntRegion(IntRect(0, 0, 100, 100)), // scroll-grabbing parent + nsIntRegion(IntRect(0, 20, 100, 80)) // child + }; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, + layers); + float parentHeight = makeParentScrollable ? 120 : 100; + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 100, parentHeight)); + SetScrollableFrameMetrics(layers[1], + ScrollableLayerGuid::START_SCROLL_ID + 1, + CSSRect(0, 0, 100, 800)); + SetScrollHandoff(layers[1], root); + registration = MakeUnique(manager, LayersId{0}, + root, mcc); + UpdateHitTestingTree(); + rootApzc = ApzcOf(root); + rootApzc->GetScrollMetadata().SetHasScrollgrab(true); + } + + void TestFlingAcceleration() { + // Jack up the fling acceleration multiplier so we can easily determine + // whether acceleration occured. + const float kAcceleration = 100.0f; + SCOPED_GFX_PREF_FLOAT("apz.fling_accel_base_mult", kAcceleration); + SCOPED_GFX_PREF_FLOAT("apz.fling_accel_min_fling_velocity", 0.0); + SCOPED_GFX_PREF_FLOAT("apz.fling_accel_min_pan_velocity", 0.0); + + RefPtr childApzc = ApzcOf(layers[1]); + + // Pan once, enough to fully scroll the scrollgrab parent and then scroll + // and fling the child. + Pan(manager, 70, 40); + + // Give the fling animation a chance to start. + SampleAnimationsOnce(); + + float childVelocityAfterFling1 = childApzc->GetVelocityVector().y; + + // Pan again. + Pan(manager, 70, 40); + + // Give the fling animation a chance to start. + // This time it should be accelerated. + SampleAnimationsOnce(); + + float childVelocityAfterFling2 = childApzc->GetVelocityVector().y; + + // We should have accelerated once. + // The division by 2 is to account for friction. + EXPECT_GT(childVelocityAfterFling2, + childVelocityAfterFling1 * kAcceleration / 2); + + // We should not have accelerated twice. + // The division by 4 is to account for friction. + EXPECT_LE(childVelocityAfterFling2, + childVelocityAfterFling1 * kAcceleration * kAcceleration / 4); + } + + void TestCrossApzcAxisLock() { + SCOPED_GFX_PREF_INT("apz.axis_lock.mode", 1); + + CreateScrollHandoffLayerTree1(); + + RefPtr childApzc = ApzcOf(layers[1]); + Pan(childApzc, ScreenIntPoint(10, 60), ScreenIntPoint(15, 90), + PanOptions::KeepFingerDown | PanOptions::ExactCoordinates); + + childApzc->AssertAxisLocked(ScrollDirection::eVertical); + } +}; + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +// Here we test that if the processing of a touch block is deferred while we +// wait for content to send a prevent-default message, overscroll is still +// handed off correctly when the block is processed. +TEST_F(APZScrollHandoffTester, DeferredInputEventProcessing) { + SCOPED_GFX_PREF_BOOL("apz.allow_immediate_handoff", true); + + // Set up the APZC tree. + CreateScrollHandoffLayerTree1(); + + RefPtr childApzc = ApzcOf(layers[1]); + + // Enable touch-listeners so that we can separate the queueing of input + // events from them being processed. + childApzc->SetWaitForMainThread(); + + // Queue input events for a pan. + uint64_t blockId = 0; + Pan(childApzc, 90, 30, PanOptions::NoFling, nullptr, nullptr, &blockId); + + // Allow the pan to be processed. + childApzc->ContentReceivedInputBlock(blockId, false); + childApzc->ConfirmTarget(blockId); + + // Make sure overscroll was handed off correctly. + EXPECT_EQ(50, childApzc->GetFrameMetrics().GetVisualScrollOffset().y); + EXPECT_EQ(10, rootApzc->GetFrameMetrics().GetVisualScrollOffset().y); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +// Here we test that if the layer structure changes in between two input +// blocks being queued, and the first block is only processed after the second +// one has been queued, overscroll handoff for the first block follows +// the original layer structure while overscroll handoff for the second block +// follows the new layer structure. +TEST_F(APZScrollHandoffTester, LayerStructureChangesWhileEventsArePending) { + SCOPED_GFX_PREF_BOOL("apz.allow_immediate_handoff", true); + + // Set up an initial APZC tree. + CreateScrollHandoffLayerTree1(); + + RefPtr childApzc = ApzcOf(layers[1]); + + // Enable touch-listeners so that we can separate the queueing of input + // events from them being processed. + childApzc->SetWaitForMainThread(); + + // Queue input events for a pan. + uint64_t blockId = 0; + Pan(childApzc, 90, 30, PanOptions::NoFling, nullptr, nullptr, &blockId); + + // Modify the APZC tree to insert a new APZC 'middle' into the handoff chain + // between the child and the root. + CreateScrollHandoffLayerTree2(); + RefPtr middle = layers[1]; + childApzc->SetWaitForMainThread(); + TestAsyncPanZoomController* middleApzc = ApzcOf(middle); + + // Queue input events for another pan. + uint64_t secondBlockId = 0; + Pan(childApzc, 30, 90, PanOptions::NoFling, nullptr, nullptr, &secondBlockId); + + // Allow the first pan to be processed. + childApzc->ContentReceivedInputBlock(blockId, false); + childApzc->ConfirmTarget(blockId); + + // Make sure things have scrolled according to the handoff chain in + // place at the time the touch-start of the first pan was queued. + EXPECT_EQ(50, childApzc->GetFrameMetrics().GetVisualScrollOffset().y); + EXPECT_EQ(10, rootApzc->GetFrameMetrics().GetVisualScrollOffset().y); + EXPECT_EQ(0, middleApzc->GetFrameMetrics().GetVisualScrollOffset().y); + + // Allow the second pan to be processed. + childApzc->ContentReceivedInputBlock(secondBlockId, false); + childApzc->ConfirmTarget(secondBlockId); + + // Make sure things have scrolled according to the handoff chain in + // place at the time the touch-start of the second pan was queued. + EXPECT_EQ(0, childApzc->GetFrameMetrics().GetVisualScrollOffset().y); + EXPECT_EQ(10, rootApzc->GetFrameMetrics().GetVisualScrollOffset().y); + EXPECT_EQ(-10, middleApzc->GetFrameMetrics().GetVisualScrollOffset().y); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +// Test that putting a second finger down on an APZC while a down-chain APZC +// is overscrolled doesn't result in being stuck in overscroll. +TEST_F(APZScrollHandoffTester, StuckInOverscroll_Bug1073250) { + // Enable overscrolling. + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); + SCOPED_GFX_VAR(UseWebRender, bool, false); + + CreateScrollHandoffLayerTree1(); + + TestAsyncPanZoomController* child = ApzcOf(layers[1]); + + // Pan, causing the parent APZC to overscroll. + Pan(manager, 10, 40, PanOptions::KeepFingerDown); + EXPECT_FALSE(child->IsOverscrolled()); + EXPECT_TRUE(rootApzc->IsOverscrolled()); + + // Put a second finger down. + MultiTouchInput secondFingerDown = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); + // Use the same touch identifier for the first touch (0) as Pan(). (A bit + // hacky.) + secondFingerDown.mTouches.AppendElement( + SingleTouchData(0, ScreenIntPoint(10, 40), ScreenSize(0, 0), 0, 0)); + secondFingerDown.mTouches.AppendElement( + SingleTouchData(1, ScreenIntPoint(30, 20), ScreenSize(0, 0), 0, 0)); + manager->ReceiveInputEvent(secondFingerDown); + + // Release the fingers. + MultiTouchInput fingersUp = secondFingerDown; + fingersUp.mType = MultiTouchInput::MULTITOUCH_END; + manager->ReceiveInputEvent(fingersUp); + + // Allow any animations to run their course. + child->AdvanceAnimationsUntilEnd(); + rootApzc->AdvanceAnimationsUntilEnd(); + + // Make sure nothing is overscrolled. + EXPECT_FALSE(child->IsOverscrolled()); + EXPECT_FALSE(rootApzc->IsOverscrolled()); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +// This is almost exactly like StuckInOverscroll_Bug1073250, except the +// APZC receiving the input events for the first touch block is the child +// (and thus not the same APZC that overscrolls, which is the parent). +TEST_F(APZScrollHandoffTester, StuckInOverscroll_Bug1231228) { + // Enable overscrolling. + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); + SCOPED_GFX_VAR(UseWebRender, bool, false); + + CreateScrollHandoffLayerTree1(); + + TestAsyncPanZoomController* child = ApzcOf(layers[1]); + + // Pan, causing the parent APZC to overscroll. + Pan(manager, 60, 90, PanOptions::KeepFingerDown); + EXPECT_FALSE(child->IsOverscrolled()); + EXPECT_TRUE(rootApzc->IsOverscrolled()); + + // Put a second finger down. + MultiTouchInput secondFingerDown = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); + // Use the same touch identifier for the first touch (0) as Pan(). (A bit + // hacky.) + secondFingerDown.mTouches.AppendElement( + SingleTouchData(0, ScreenIntPoint(10, 40), ScreenSize(0, 0), 0, 0)); + secondFingerDown.mTouches.AppendElement( + SingleTouchData(1, ScreenIntPoint(30, 20), ScreenSize(0, 0), 0, 0)); + manager->ReceiveInputEvent(secondFingerDown); + + // Release the fingers. + MultiTouchInput fingersUp = secondFingerDown; + fingersUp.mType = MultiTouchInput::MULTITOUCH_END; + manager->ReceiveInputEvent(fingersUp); + + // Allow any animations to run their course. + child->AdvanceAnimationsUntilEnd(); + rootApzc->AdvanceAnimationsUntilEnd(); + + // Make sure nothing is overscrolled. + EXPECT_FALSE(child->IsOverscrolled()); + EXPECT_FALSE(rootApzc->IsOverscrolled()); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +TEST_F(APZScrollHandoffTester, StuckInOverscroll_Bug1240202a) { + // Enable overscrolling. + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + CreateScrollHandoffLayerTree1(); + + TestAsyncPanZoomController* child = ApzcOf(layers[1]); + + // Pan, causing the parent APZC to overscroll. + Pan(manager, 60, 90, PanOptions::KeepFingerDown); + EXPECT_FALSE(child->IsOverscrolled()); + EXPECT_TRUE(rootApzc->IsOverscrolled()); + + // Lift the finger, triggering an overscroll animation + // (but don't allow it to run). + TouchUp(manager, ScreenIntPoint(10, 90), mcc->Time()); + + // Put the finger down again, interrupting the animation + // and entering the TOUCHING state. + TouchDown(manager, ScreenIntPoint(10, 90), mcc->Time()); + + // Lift the finger once again. + TouchUp(manager, ScreenIntPoint(10, 90), mcc->Time()); + + // Allow any animations to run their course. + child->AdvanceAnimationsUntilEnd(); + rootApzc->AdvanceAnimationsUntilEnd(); + + // Make sure nothing is overscrolled. + EXPECT_FALSE(child->IsOverscrolled()); + EXPECT_FALSE(rootApzc->IsOverscrolled()); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +TEST_F(APZScrollHandoffTester, StuckInOverscroll_Bug1240202b) { + // Enable overscrolling. + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + SCOPED_GFX_VAR(UseWebRender, bool, false); + + CreateScrollHandoffLayerTree1(); + + TestAsyncPanZoomController* child = ApzcOf(layers[1]); + + // Pan, causing the parent APZC to overscroll. + Pan(manager, 60, 90, PanOptions::KeepFingerDown); + EXPECT_FALSE(child->IsOverscrolled()); + EXPECT_TRUE(rootApzc->IsOverscrolled()); + + // Lift the finger, triggering an overscroll animation + // (but don't allow it to run). + TouchUp(manager, ScreenIntPoint(10, 90), mcc->Time()); + + // Put the finger down again, interrupting the animation + // and entering the TOUCHING state. + TouchDown(manager, ScreenIntPoint(10, 90), mcc->Time()); + + // Put a second finger down. Since we're in the TOUCHING state, + // the "are we panned into overscroll" check will fail and we + // will not ignore the second finger, instead entering the + // PINCHING state. + MultiTouchInput secondFingerDown(MultiTouchInput::MULTITOUCH_START, 0, + TimeStamp(), 0); + // Use the same touch identifier for the first touch (0) as TouchDown(). (A + // bit hacky.) + secondFingerDown.mTouches.AppendElement( + SingleTouchData(0, ScreenIntPoint(10, 90), ScreenSize(0, 0), 0, 0)); + secondFingerDown.mTouches.AppendElement( + SingleTouchData(1, ScreenIntPoint(10, 80), ScreenSize(0, 0), 0, 0)); + manager->ReceiveInputEvent(secondFingerDown); + + // Release the fingers. + MultiTouchInput fingersUp = secondFingerDown; + fingersUp.mType = MultiTouchInput::MULTITOUCH_END; + manager->ReceiveInputEvent(fingersUp); + + // Allow any animations to run their course. + child->AdvanceAnimationsUntilEnd(); + rootApzc->AdvanceAnimationsUntilEnd(); + + // Make sure nothing is overscrolled. + EXPECT_FALSE(child->IsOverscrolled()); + EXPECT_FALSE(rootApzc->IsOverscrolled()); +} +#endif + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +TEST_F(APZScrollHandoffTester, OpposingConstrainedAxes_Bug1201098) { + // Enable overscrolling. + SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true); + + CreateScrollHandoffLayerTree4(); + + RefPtr childApzc = ApzcOf(layers[1]); + + // Pan, causing the child APZC to overscroll. + Pan(childApzc, 50, 60); + + // Make sure only the child is overscrolled. + EXPECT_TRUE(childApzc->IsOverscrolled()); + EXPECT_FALSE(rootApzc->IsOverscrolled()); +} +#endif + +// Test that flinging in a direction where one component of the fling goes into +// overscroll but the other doesn't, results in just the one component being +// handed off to the parent, while the original APZC continues flinging in the +// other direction. +TEST_F(APZScrollHandoffTester, PartialFlingHandoff) { + SCOPED_GFX_VAR(UseWebRender, bool, false); + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); + + CreateScrollHandoffLayerTree1(); + + // Fling up and to the left. The child APZC has room to scroll up, but not + // to the left, so the horizontal component of the fling should be handed + // off to the parent APZC. + Pan(manager, ScreenIntPoint(90, 90), ScreenIntPoint(55, 55)); + + RefPtr parent = ApzcOf(root); + RefPtr child = ApzcOf(layers[1]); + + // Advance the child's fling animation once to give the partial handoff + // a chance to occur. + mcc->AdvanceByMillis(10); + child->AdvanceAnimations(mcc->GetSampleTime()); + + // Assert that partial handoff has occurred. + child->AssertStateIsFling(); + parent->AssertStateIsFling(); +} + +// Here we test that if two flings are happening simultaneously, overscroll +// is handed off correctly for each. +TEST_F(APZScrollHandoffTester, SimultaneousFlings) { + SCOPED_GFX_PREF_BOOL("apz.allow_immediate_handoff", true); + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); + + // Set up an initial APZC tree. + CreateScrollHandoffLayerTree3(); + + RefPtr parent1 = ApzcOf(layers[1]); + RefPtr child1 = ApzcOf(layers[2]); + RefPtr parent2 = ApzcOf(layers[3]); + RefPtr child2 = ApzcOf(layers[4]); + + // Pan on the lower child. + Pan(child2, 45, 5); + + // Pan on the upper child. + Pan(child1, 95, 55); + + // Check that child1 and child2 are in a FLING state. + child1->AssertStateIsFling(); + child2->AssertStateIsFling(); + + // Advance the animations on child1 and child2 until their end. + child1->AdvanceAnimationsUntilEnd(); + child2->AdvanceAnimationsUntilEnd(); + + // Check that the flings have been handed off to the parents. + child1->AssertStateIsReset(); + parent1->AssertStateIsFling(); + child2->AssertStateIsReset(); + parent2->AssertStateIsFling(); +} + +#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android +TEST_F(APZScrollHandoffTester, Scrollgrab) { + SCOPED_GFX_PREF_BOOL("apz.allow_immediate_handoff", true); + + // Set up the layer tree + CreateScrollgrabLayerTree(); + + RefPtr childApzc = ApzcOf(layers[1]); + + // Pan on the child, enough to fully scroll the scrollgrab parent (20 px) + // and leave some more (another 15 px) for the child. + Pan(childApzc, 80, 45); + + // Check that the parent and child have scrolled as much as we expect. + EXPECT_EQ(20, rootApzc->GetFrameMetrics().GetVisualScrollOffset().y); + EXPECT_EQ(15, childApzc->GetFrameMetrics().GetVisualScrollOffset().y); +} +#endif + +TEST_F(APZScrollHandoffTester, ScrollgrabFling) { + SCOPED_GFX_PREF_BOOL("apz.allow_immediate_handoff", true); + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); + + // Set up the layer tree + CreateScrollgrabLayerTree(); + + RefPtr childApzc = ApzcOf(layers[1]); + + // Pan on the child, not enough to fully scroll the scrollgrab parent. + Pan(childApzc, 80, 70); + + // Check that it is the scrollgrab parent that's in a fling, not the child. + rootApzc->AssertStateIsFling(); + childApzc->AssertStateIsReset(); +} + +TEST_F(APZScrollHandoffTester, ScrollgrabFlingAcceleration1) { + SCOPED_GFX_PREF_BOOL("apz.allow_immediate_handoff", true); + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); + SCOPED_GFX_VAR(UseWebRender, bool, false); + CreateScrollgrabLayerTree(true /* make parent scrollable */); + + // Note: Usually, fling acceleration does not work across handoff, because our + // fling acceleration code does not propagate the "fling cancel velocity" + // across handoff. However, this test sets apz.fling_min_velocity_threshold to + // zero, so the "fling cancel velocity" is allowed to be zero, and fling + // acceleration succeeds, almost by accident. + TestFlingAcceleration(); +} + +TEST_F(APZScrollHandoffTester, ScrollgrabFlingAcceleration2) { + SCOPED_GFX_PREF_BOOL("apz.allow_immediate_handoff", true); + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); + SCOPED_GFX_VAR(UseWebRender, bool, false); + CreateScrollgrabLayerTree(false /* do not make parent scrollable */); + TestFlingAcceleration(); +} + +TEST_F(APZScrollHandoffTester, ImmediateHandoffDisallowed_Pan) { + SCOPED_GFX_PREF_BOOL("apz.allow_immediate_handoff", false); + + CreateScrollHandoffLayerTree1(); + + RefPtr parentApzc = ApzcOf(root); + RefPtr childApzc = ApzcOf(layers[1]); + + // Pan on the child, enough to scroll it to its end and have scroll + // left to hand off. Since immediate handoff is disallowed, we expect + // the leftover scroll not to be handed off. + Pan(childApzc, 60, 5); + + // Verify that the parent has not scrolled. + EXPECT_EQ(50, childApzc->GetFrameMetrics().GetVisualScrollOffset().y); + EXPECT_EQ(0, parentApzc->GetFrameMetrics().GetVisualScrollOffset().y); + + // Pan again on the child. This time, since the child was scrolled to + // its end when the gesture began, we expect the scroll to be handed off. + Pan(childApzc, 60, 50); + + // Verify that the parent scrolled. + EXPECT_EQ(10, parentApzc->GetFrameMetrics().GetVisualScrollOffset().y); +} + +TEST_F(APZScrollHandoffTester, ImmediateHandoffDisallowed_Fling) { + SCOPED_GFX_PREF_BOOL("apz.allow_immediate_handoff", false); + SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); + + CreateScrollHandoffLayerTree1(); + + RefPtr parentApzc = ApzcOf(root); + RefPtr childApzc = ApzcOf(layers[1]); + + // Pan on the child, enough to get very close to the end, so that the + // subsequent fling reaches the end and has leftover velocity to hand off. + Pan(childApzc, 60, 12); + + // Allow the fling to run its course. + childApzc->AdvanceAnimationsUntilEnd(); + parentApzc->AdvanceAnimationsUntilEnd(); + + // Verify that the parent has not scrolled. + // The first comparison needs to be an ASSERT_NEAR because the fling + // computations are such that the final scroll position can be within + // COORDINATE_EPSILON of the end rather than right at the end. + ASSERT_NEAR(50, childApzc->GetFrameMetrics().GetVisualScrollOffset().y, + COORDINATE_EPSILON); + EXPECT_EQ(0, parentApzc->GetFrameMetrics().GetVisualScrollOffset().y); + + // Pan again on the child. This time, since the child was scrolled to + // its end when the gesture began, we expect the scroll to be handed off. + Pan(childApzc, 60, 50); + + // Allow the fling to run its course. The fling should also be handed off. + childApzc->AdvanceAnimationsUntilEnd(); + parentApzc->AdvanceAnimationsUntilEnd(); + + // Verify that the parent scrolled from the fling. + EXPECT_GT(parentApzc->GetFrameMetrics().GetVisualScrollOffset().y, 10); +} + +TEST_F(APZScrollHandoffTester, CrossApzcAxisLock_NoTouchAction) { + SCOPED_GFX_PREF_BOOL("layout.css.touch_action.enabled", false); + TestCrossApzcAxisLock(); +} + +TEST_F(APZScrollHandoffTester, CrossApzcAxisLock_TouchAction) { + SCOPED_GFX_PREF_BOOL("layout.css.touch_action.enabled", true); + TestCrossApzcAxisLock(); +} diff --git a/gfx/layers/apz/test/gtest/TestSnapping.cpp b/gfx/layers/apz/test/gtest/TestSnapping.cpp new file mode 100644 index 0000000000..e9cada645b --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestSnapping.cpp @@ -0,0 +1,129 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "APZCTreeManagerTester.h" +#include "APZTestCommon.h" + +#include "InputUtils.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/StaticPrefs_mousewheel.h" + +class APZCSnappingTester : public APZCTreeManagerTester {}; + +TEST_F(APZCSnappingTester, Bug1265510) { + SCOPED_GFX_VAR(UseWebRender, bool, false); + + const char* layerTreeSyntax = "c(t)"; + nsIntRegion layerVisibleRegion[] = {nsIntRegion(IntRect(0, 0, 100, 100)), + nsIntRegion(IntRect(0, 100, 100, 100))}; + root = + CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 100, 200)); + SetScrollableFrameMetrics(layers[1], ScrollableLayerGuid::START_SCROLL_ID + 1, + CSSRect(0, 0, 100, 200)); + SetScrollHandoff(layers[1], root); + + ScrollSnapInfo snap; + snap.mScrollSnapStrictnessY = StyleScrollSnapStrictness::Mandatory; + snap.mSnapPositionY.AppendElement(0 * AppUnitsPerCSSPixel()); + snap.mSnapPositionY.AppendElement(100 * AppUnitsPerCSSPixel()); + + ScrollMetadata metadata = root->GetScrollMetadata(0); + metadata.SetSnapInfo(ScrollSnapInfo(snap)); + root->SetScrollMetadata(metadata); + + UniquePtr registration = + MakeUnique(manager, LayersId{0}, root, mcc); + UpdateHitTestingTree(); + + TestAsyncPanZoomController* outer = ApzcOf(layers[0]); + TestAsyncPanZoomController* inner = ApzcOf(layers[1]); + + // Position the mouse near the bottom of the outer frame and scroll by 60px. + // (6 lines of 10px each). APZC will actually scroll to y=100 because of the + // mandatory snap coordinate there. + TimeStamp now = mcc->Time(); + SmoothWheel(manager, ScreenIntPoint(50, 80), ScreenPoint(0, 6), now); + // Advance in 5ms increments until we've scrolled by 70px. At this point, the + // closest snap point is y=100, and the inner frame should be under the mouse + // cursor. + while (outer + ->GetCurrentAsyncScrollOffset( + AsyncPanZoomController::AsyncTransformConsumer::eForHitTesting) + .y < 70) { + mcc->AdvanceByMillis(5); + outer->AdvanceAnimations(mcc->GetSampleTime()); + } + // Now do another wheel in a new transaction. This should start scrolling the + // inner frame; we verify that it does by checking the inner scroll position. + TimeStamp newTransactionTime = + now + TimeDuration::FromMilliseconds( + StaticPrefs::mousewheel_transaction_timeout() + 100); + SmoothWheel(manager, ScreenIntPoint(50, 80), ScreenPoint(0, 6), + newTransactionTime); + inner->AdvanceAnimationsUntilEnd(); + EXPECT_LT( + 0.0f, + inner + ->GetCurrentAsyncScrollOffset( + AsyncPanZoomController::AsyncTransformConsumer::eForHitTesting) + .y); + + // However, the outer frame should also continue to the snap point, otherwise + // it is demonstrating incorrect behaviour by violating the mandatory + // snapping. + outer->AdvanceAnimationsUntilEnd(); + EXPECT_EQ( + 100.0f, + outer + ->GetCurrentAsyncScrollOffset( + AsyncPanZoomController::AsyncTransformConsumer::eForHitTesting) + .y); +} + +TEST_F(APZCSnappingTester, Snap_After_Pinch) { + SCOPED_GFX_VAR(UseWebRender, bool, false); + + const char* layerTreeSyntax = "c"; + nsIntRegion layerVisibleRegion[] = { + nsIntRegion(IntRect(0, 0, 100, 100)), + }; + root = + CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 100, 200)); + + // Set up some basic scroll snapping + ScrollSnapInfo snap; + snap.mScrollSnapStrictnessY = StyleScrollSnapStrictness::Mandatory; + + snap.mSnapPositionY.AppendElement(0 * AppUnitsPerCSSPixel()); + snap.mSnapPositionY.AppendElement(100 * AppUnitsPerCSSPixel()); + + // Save the scroll snap info on the root APZC. + // Also mark the root APZC as "root content", since APZC only allows + // zooming on the root content APZC. + ScrollMetadata metadata = root->GetScrollMetadata(0); + metadata.SetSnapInfo(ScrollSnapInfo(snap)); + metadata.GetMetrics().SetIsRootContent(true); + root->SetScrollMetadata(metadata); + + UniquePtr registration = + MakeUnique(manager, LayersId{0}, root, mcc); + UpdateHitTestingTree(); + + RefPtr apzc = ApzcOf(root); + + // Allow zooming + apzc->UpdateZoomConstraints(ZoomConstraints( + true, true, CSSToParentLayerScale(0.25f), CSSToParentLayerScale(4.0f))); + + PinchWithPinchInput(apzc, ScreenIntPoint(50, 50), ScreenIntPoint(50, 50), + 1.2f); + + apzc->AssertStateIsSmoothMsdScroll(); +} diff --git a/gfx/layers/apz/test/gtest/TestSnappingOnMomentum.cpp b/gfx/layers/apz/test/gtest/TestSnappingOnMomentum.cpp new file mode 100644 index 0000000000..7ed1992037 --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestSnappingOnMomentum.cpp @@ -0,0 +1,88 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "APZCTreeManagerTester.h" +#include "APZTestCommon.h" + +#include "InputUtils.h" +#include "mozilla/StaticPrefs_layout.h" + +class APZCSnappingOnMomentumTester : public APZCTreeManagerTester {}; + +TEST_F(APZCSnappingOnMomentumTester, Snap_On_Momentum) { + SCOPED_GFX_VAR(UseWebRender, bool, false); + + const char* layerTreeSyntax = "c"; + nsIntRegion layerVisibleRegion[] = { + nsIntRegion(IntRect(0, 0, 100, 100)), + }; + root = + CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 100, 500)); + + // Set up some basic scroll snapping + ScrollSnapInfo snap; + snap.mScrollSnapStrictnessY = StyleScrollSnapStrictness::Mandatory; + + snap.mSnapPositionY.AppendElement(0 * AppUnitsPerCSSPixel()); + snap.mSnapPositionY.AppendElement(100 * AppUnitsPerCSSPixel()); + + ScrollMetadata metadata = root->GetScrollMetadata(0); + metadata.SetSnapInfo(ScrollSnapInfo(snap)); + root->SetScrollMetadata(metadata); + + UniquePtr registration = + MakeUnique(manager, LayersId{0}, root, mcc); + UpdateHitTestingTree(); + + RefPtr apzc = ApzcOf(root); + + TimeStamp now = mcc->Time(); + + PanGesture(PanGestureInput::PANGESTURE_START, manager, ScreenIntPoint(50, 80), + ScreenPoint(0, 2), now); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_PAN, manager, ScreenIntPoint(50, 80), + ScreenPoint(0, 25), mcc->Time()); + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_PAN, manager, ScreenIntPoint(50, 80), + ScreenPoint(0, 25), mcc->Time()); + + // The velocity should be positive when panning with positive displacement. + EXPECT_GT(apzc->GetVelocityVector().y, 3.0); + + mcc->AdvanceByMillis(5); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_END, manager, ScreenIntPoint(50, 80), + ScreenPoint(0, 0), mcc->Time()); + + // After lifting the fingers, the velocity should still be positive. + EXPECT_GT(apzc->GetVelocityVector().y, 3.0); + + mcc->AdvanceByMillis(5); + + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMSTART, manager, + ScreenIntPoint(50, 80), ScreenPoint(0, 200), mcc->Time()); + mcc->AdvanceByMillis(10); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, manager, + ScreenIntPoint(50, 80), ScreenPoint(0, 50), mcc->Time()); + mcc->AdvanceByMillis(10); + apzc->AdvanceAnimations(mcc->GetSampleTime()); + PanGesture(PanGestureInput::PANGESTURE_MOMENTUMEND, manager, + ScreenIntPoint(50, 80), ScreenPoint(0, 0), mcc->Time()); + + apzc->AdvanceAnimationsUntilEnd(); + EXPECT_EQ( + 100.0f, + apzc->GetCurrentAsyncScrollOffset( + AsyncPanZoomController::AsyncTransformConsumer::eForHitTesting) + .y); +} diff --git a/gfx/layers/apz/test/gtest/TestTreeManager.cpp b/gfx/layers/apz/test/gtest/TestTreeManager.cpp new file mode 100644 index 0000000000..5c4d0a60bc --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestTreeManager.cpp @@ -0,0 +1,306 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "APZCTreeManagerTester.h" +#include "APZTestCommon.h" +#include "InputUtils.h" + +class APZCTreeManagerGenericTester : public APZCTreeManagerTester { + protected: + void CreateSimpleDTCScrollingLayer() { + const char* layerTreeSyntax = "t"; + nsIntRegion layerVisibleRegion[] = { + nsIntRegion(IntRect(0, 0, 200, 200)), + }; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, + layers); + SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID, + CSSRect(0, 0, 500, 500)); + + EventRegions regions; + regions.mHitRegion = nsIntRegion(IntRect(0, 0, 200, 200)); + regions.mDispatchToContentHitRegion = regions.mHitRegion; + layers[0]->SetEventRegions(regions); + } + + void CreateSimpleMultiLayerTree() { + const char* layerTreeSyntax = "c(tt)"; + // LayerID 0 12 + nsIntRegion layerVisibleRegion[] = { + nsIntRegion(IntRect(0, 0, 100, 100)), + nsIntRegion(IntRect(0, 0, 100, 50)), + nsIntRegion(IntRect(0, 50, 100, 50)), + }; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, + layers); + } + + void CreatePotentiallyLeakingTree() { + const char* layerTreeSyntax = "c(c(c(t))c(c(t)))"; + // LayerID 0 1 2 3 4 5 6 + root = CreateLayerTree(layerTreeSyntax, nullptr, nullptr, lm, layers); + SetScrollableFrameMetrics(layers[0], ScrollableLayerGuid::START_SCROLL_ID); + SetScrollableFrameMetrics(layers[2], + ScrollableLayerGuid::START_SCROLL_ID + 1); + SetScrollableFrameMetrics(layers[5], + ScrollableLayerGuid::START_SCROLL_ID + 1); + SetScrollableFrameMetrics(layers[3], + ScrollableLayerGuid::START_SCROLL_ID + 2); + SetScrollableFrameMetrics(layers[6], + ScrollableLayerGuid::START_SCROLL_ID + 3); + } + + void CreateTwoLayerDTCTree(int32_t aRootContentLayerIndex) { + const char* layerTreeSyntax = "c(t)"; + // LayerID 0 1 + nsIntRegion layerVisibleRegion[] = { + nsIntRegion(IntRect(0, 0, 100, 100)), + nsIntRegion(IntRect(0, 0, 100, 100)), + }; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, + layers); + SetScrollableFrameMetrics(layers[0], ScrollableLayerGuid::START_SCROLL_ID); + SetScrollableFrameMetrics(layers[1], + ScrollableLayerGuid::START_SCROLL_ID + 1); + SetScrollHandoff(layers[1], layers[0]); + + // Make layers[aRootContentLayerIndex] the root content + ModifyFrameMetrics(layers[aRootContentLayerIndex], + [](ScrollMetadata& sm, FrameMetrics& fm) { + fm.SetIsRootContent(true); + }); + + // Both layers are fully dispatch-to-content + EventRegions regions; + regions.mHitRegion = nsIntRegion(IntRect(0, 0, 100, 100)); + regions.mDispatchToContentHitRegion = regions.mHitRegion; + layers[0]->SetEventRegions(regions); + layers[1]->SetEventRegions(regions); + } +}; + +TEST_F(APZCTreeManagerGenericTester, ScrollablePaintedLayers) { + CreateSimpleMultiLayerTree(); + ScopedLayerTreeRegistration registration(manager, LayersId{0}, root, mcc); + + // both layers have the same scrollId + SetScrollableFrameMetrics(layers[1], ScrollableLayerGuid::START_SCROLL_ID); + SetScrollableFrameMetrics(layers[2], ScrollableLayerGuid::START_SCROLL_ID); + UpdateHitTestingTree(); + + TestAsyncPanZoomController* nullAPZC = nullptr; + // so they should have the same APZC + EXPECT_FALSE(layers[0]->HasScrollableFrameMetrics()); + EXPECT_NE(nullAPZC, ApzcOf(layers[1])); + EXPECT_NE(nullAPZC, ApzcOf(layers[2])); + EXPECT_EQ(ApzcOf(layers[1]), ApzcOf(layers[2])); + + // Change the scrollId of layers[1], and verify the APZC changes + SetScrollableFrameMetrics(layers[1], + ScrollableLayerGuid::START_SCROLL_ID + 1); + UpdateHitTestingTree(); + EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[2])); + + // Change the scrollId of layers[2] to match that of layers[1], ensure we get + // the same APZC for both again + SetScrollableFrameMetrics(layers[2], + ScrollableLayerGuid::START_SCROLL_ID + 1); + UpdateHitTestingTree(); + EXPECT_EQ(ApzcOf(layers[1]), ApzcOf(layers[2])); +} + +TEST_F(APZCTreeManagerGenericTester, Bug1068268) { + CreatePotentiallyLeakingTree(); + ScopedLayerTreeRegistration registration(manager, LayersId{0}, root, mcc); + + UpdateHitTestingTree(); + RefPtr root = manager->GetRootNode(); + RefPtr node2 = root->GetFirstChild()->GetFirstChild(); + RefPtr node5 = root->GetLastChild()->GetLastChild(); + + EXPECT_EQ(ApzcOf(layers[2]), node5->GetApzc()); + EXPECT_EQ(ApzcOf(layers[2]), node2->GetApzc()); + EXPECT_EQ(ApzcOf(layers[0]), ApzcOf(layers[2])->GetParent()); + EXPECT_EQ(ApzcOf(layers[2]), ApzcOf(layers[5])); + + EXPECT_EQ(node2->GetFirstChild(), node2->GetLastChild()); + EXPECT_EQ(ApzcOf(layers[3]), node2->GetLastChild()->GetApzc()); + EXPECT_EQ(node5->GetFirstChild(), node5->GetLastChild()); + EXPECT_EQ(ApzcOf(layers[6]), node5->GetLastChild()->GetApzc()); + EXPECT_EQ(ApzcOf(layers[2]), ApzcOf(layers[3])->GetParent()); + EXPECT_EQ(ApzcOf(layers[5]), ApzcOf(layers[6])->GetParent()); +} + +TEST_F(APZCTreeManagerGenericTester, Bug1194876) { + // Create a layer tree with parent and child scrollable layers, with the + // child being the root content. + CreateTwoLayerDTCTree(1); + ScopedLayerTreeRegistration registration(manager, LayersId{0}, root, mcc); + UpdateHitTestingTree(); + + uint64_t blockId; + nsTArray targets; + + // First touch goes down, APZCTM will hit layers[1] because it is on top of + // layers[0], but we tell it the real target APZC is layers[0]. + MultiTouchInput mti; + mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); + mti.mTouches.AppendElement( + SingleTouchData(0, ParentLayerPoint(25, 50), ScreenSize(0, 0), 0, 0)); + blockId = manager->ReceiveInputEvent(mti).mInputBlockId; + manager->ContentReceivedInputBlock(blockId, false); + targets.AppendElement(ApzcOf(layers[0])->GetGuid()); + manager->SetTargetAPZC(blockId, targets); + + // Around here, the above touch will get processed by ApzcOf(layers[0]) + + // Second touch goes down (first touch remains down), APZCTM will again hit + // layers[1]. Again we tell it both touches landed on layers[0], but because + // layers[1] is the RCD layer, it will end up being the multitouch target. + mti.mTouches.AppendElement( + SingleTouchData(1, ParentLayerPoint(75, 50), ScreenSize(0, 0), 0, 0)); + blockId = manager->ReceiveInputEvent(mti).mInputBlockId; + manager->ContentReceivedInputBlock(blockId, false); + targets.AppendElement(ApzcOf(layers[0])->GetGuid()); + manager->SetTargetAPZC(blockId, targets); + + // Around here, the above multi-touch will get processed by ApzcOf(layers[1]). + // We want to ensure that ApzcOf(layers[0]) has had its state cleared, because + // otherwise it will do things like dispatch spurious long-tap events. + + EXPECT_CALL(*mcc, HandleTap(TapType::eLongTap, _, _, _, _)).Times(0); +} + +TEST_F(APZCTreeManagerGenericTester, TargetChangesMidGesture_Bug1570559) { + // Create a layer tree with parent and child scrollable layers, with the + // parent being the root content. + CreateTwoLayerDTCTree(0); + ScopedLayerTreeRegistration registration(manager, LayersId{0}, root, mcc); + UpdateHitTestingTree(); + + uint64_t blockId; + nsTArray targets; + + // First touch goes down. APZCTM hits the child layer because it is on top + // (and we confirm this target), but do not prevent-default the event, causing + // the child APZC's gesture detector to start a long-tap timeout task. + MultiTouchInput mti = + CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); + mti.mTouches.AppendElement( + SingleTouchData(0, ParentLayerPoint(25, 50), ScreenSize(0, 0), 0, 0)); + blockId = manager->ReceiveInputEvent(mti).mInputBlockId; + manager->ContentReceivedInputBlock(blockId, /* default prevented = */ false); + targets.AppendElement(ApzcOf(layers[1])->GetGuid()); + manager->SetTargetAPZC(blockId, targets); + + // Second touch goes down (first touch remains down). APZCTM again hits the + // child and we confirm this, but multi-touch events are routed to the root + // content APZC which is the parent. This event is prevent-defaulted, so we + // clear the parent's gesture state. The bug is that we fail to clear the + // child's gesture state. + mti.mTouches.AppendElement( + SingleTouchData(1, ParentLayerPoint(75, 50), ScreenSize(0, 0), 0, 0)); + blockId = manager->ReceiveInputEvent(mti).mInputBlockId; + manager->ContentReceivedInputBlock(blockId, /* default prevented = */ true); + targets.AppendElement(ApzcOf(layers[1])->GetGuid()); + manager->SetTargetAPZC(blockId, targets); + + // If we've failed to clear the child's gesture state, then the long tap + // timeout task will fire in TearDown() and a long-tap will be dispatched. + EXPECT_CALL(*mcc, HandleTap(TapType::eLongTap, _, _, _, _)).Times(0); +} + +TEST_F(APZCTreeManagerGenericTester, Bug1198900) { + // This is just a test that cancels a wheel event to make sure it doesn't + // crash. + CreateSimpleDTCScrollingLayer(); + ScopedLayerTreeRegistration registration(manager, LayersId{0}, root, mcc); + UpdateHitTestingTree(); + + ScreenPoint origin(100, 50); + ScrollWheelInput swi(MillisecondsSinceStartup(mcc->Time()), mcc->Time(), 0, + ScrollWheelInput::SCROLLMODE_INSTANT, + ScrollWheelInput::SCROLLDELTA_PIXEL, origin, 0, 10, + false, WheelDeltaAdjustmentStrategy::eNone); + uint64_t blockId; + blockId = manager->ReceiveInputEvent(swi).mInputBlockId; + manager->ContentReceivedInputBlock(blockId, /* preventDefault= */ true); +} + +// The next two tests check that APZ clamps the scroll offset it composites even +// if the main thread fails to do so. (The main thread will always clamp its +// scroll offset internally, but it may not send APZ the clamped version for +// scroll offset synchronization reasons.) +TEST_F(APZCTreeManagerTester, Bug1551582) { + // The simple layer tree has a scrollable rect of 500x500 and a composition + // bounds of 200x200, leading to a scroll range of (0,0,300,300). + CreateSimpleScrollingLayer(); + ScopedLayerTreeRegistration registration(manager, LayersId{0}, root, mcc); + UpdateHitTestingTree(); + + // Simulate the main thread scrolling to the end of the scroll range. + ModifyFrameMetrics(root, [](ScrollMetadata& aSm, FrameMetrics& aMetrics) { + aMetrics.SetLayoutScrollOffset(CSSPoint(300, 300)); + nsTArray scrollUpdates; + scrollUpdates.AppendElement(ScrollPositionUpdate::NewScroll( + ScrollOrigin::Other, CSSPoint::ToAppUnits(CSSPoint(300, 300)))); + aSm.SetScrollUpdates(scrollUpdates); + aMetrics.SetScrollGeneration(scrollUpdates.LastElement().GetGeneration()); + }); + UpdateHitTestingTree(); + + // Sanity check. + RefPtr apzc = ApzcOf(root); + CSSPoint compositedScrollOffset = apzc->GetCompositedScrollOffset(); + EXPECT_EQ(CSSPoint(300, 300), compositedScrollOffset); + + // Simulate the main thread shrinking the scrollable rect to 400x400 (and + // thereby the scroll range to (0,0,200,200) without sending a new scroll + // offset update for the clamped scroll position (200,200). + ModifyFrameMetrics(root, [](ScrollMetadata& aSm, FrameMetrics& aMetrics) { + aMetrics.SetScrollableRect(CSSRect(0, 0, 400, 400)); + }); + UpdateHitTestingTree(); + + // Check that APZ has clamped the scroll offset to (200,200) for us. + compositedScrollOffset = apzc->GetCompositedScrollOffset(); + EXPECT_EQ(CSSPoint(200, 200), compositedScrollOffset); +} +TEST_F(APZCTreeManagerTester, Bug1557424) { + // The simple layer tree has a scrollable rect of 500x500 and a composition + // bounds of 200x200, leading to a scroll range of (0,0,300,300). + CreateSimpleScrollingLayer(); + ScopedLayerTreeRegistration registration(manager, LayersId{0}, root, mcc); + UpdateHitTestingTree(); + + // Simulate the main thread scrolling to the end of the scroll range. + ModifyFrameMetrics(root, [](ScrollMetadata& aSm, FrameMetrics& aMetrics) { + aMetrics.SetLayoutScrollOffset(CSSPoint(300, 300)); + nsTArray scrollUpdates; + scrollUpdates.AppendElement(ScrollPositionUpdate::NewScroll( + ScrollOrigin::Other, CSSPoint::ToAppUnits(CSSPoint(300, 300)))); + aSm.SetScrollUpdates(scrollUpdates); + aMetrics.SetScrollGeneration(scrollUpdates.LastElement().GetGeneration()); + }); + UpdateHitTestingTree(); + + // Sanity check. + RefPtr apzc = ApzcOf(root); + CSSPoint compositedScrollOffset = apzc->GetCompositedScrollOffset(); + EXPECT_EQ(CSSPoint(300, 300), compositedScrollOffset); + + // Simulate the main thread expanding the composition bounds to 300x300 (and + // thereby shrinking the scroll range to (0,0,200,200) without sending a new + // scroll offset update for the clamped scroll position (200,200). + ModifyFrameMetrics(root, [](ScrollMetadata& aSm, FrameMetrics& aMetrics) { + aMetrics.SetCompositionBounds(ParentLayerRect(0, 0, 300, 300)); + }); + UpdateHitTestingTree(); + + // Check that APZ has clamped the scroll offset to (200,200) for us. + compositedScrollOffset = apzc->GetCompositedScrollOffset(); + EXPECT_EQ(CSSPoint(200, 200), compositedScrollOffset); +} diff --git a/gfx/layers/apz/test/gtest/moz.build b/gfx/layers/apz/test/gtest/moz.build new file mode 100644 index 0000000000..735a1a534c --- /dev/null +++ b/gfx/layers/apz/test/gtest/moz.build @@ -0,0 +1,35 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +UNIFIED_SOURCES += [ + "TestBasic.cpp", + "TestEventRegions.cpp", + "TestFlingAcceleration.cpp", + "TestGestureDetector.cpp", + "TestHitTesting.cpp", + "TestInputQueue.cpp", + "TestPanning.cpp", + "TestPinching.cpp", + "TestScrollHandoff.cpp", + "TestSnapping.cpp", + "TestSnappingOnMomentum.cpp", + "TestTreeManager.cpp", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +LOCAL_INCLUDES += [ + "/gfx/2d", + "/gfx/layers", + "/gfx/tests/gtest", # for TestLayers.h, which is shared with the gfx gtests +] + +FINAL_LIBRARY = "xul-gtest" + +CXXFLAGS += CONFIG["MOZ_CAIRO_CFLAGS"] + +if CONFIG["CC_TYPE"] in ("clang", "gcc"): + CXXFLAGS += ["-Wno-error=shadow"] diff --git a/gfx/layers/apz/test/gtest/mvm/TestMobileViewportManager.cpp b/gfx/layers/apz/test/gtest/mvm/TestMobileViewportManager.cpp new file mode 100644 index 0000000000..cd7e0a7d0d --- /dev/null +++ b/gfx/layers/apz/test/gtest/mvm/TestMobileViewportManager.cpp @@ -0,0 +1,220 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" +#include "gmock/gmock.h" + +#include + +#include "MobileViewportManager.h" +#include "mozilla/MVMContext.h" +#include "mozilla/dom/Event.h" + +using namespace mozilla; + +class MockMVMContext : public MVMContext { + using AutoSizeFlag = nsViewportInfo::AutoSizeFlag; + using AutoScaleFlag = nsViewportInfo::AutoScaleFlag; + using ZoomFlag = nsViewportInfo::ZoomFlag; + + // A "layout function" is a function that computes the content size + // as a function of the ICB size. + using LayoutFunction = std::function; + + public: + // MVMContext methods we don't care to implement. + MOCK_METHOD3(AddEventListener, + void(const nsAString& aType, nsIDOMEventListener* aListener, + bool aUseCapture)); + MOCK_METHOD3(RemoveEventListener, + void(const nsAString& aType, nsIDOMEventListener* aListener, + bool aUseCapture)); + MOCK_METHOD3(AddObserver, void(nsIObserver* aObserver, const char* aTopic, + bool aOwnsWeak)); + MOCK_METHOD2(RemoveObserver, + void(nsIObserver* aObserver, const char* aTopic)); + MOCK_METHOD0(Destroy, void()); + + MOCK_METHOD1(SetVisualViewportSize, void(const CSSSize& aSize)); + MOCK_METHOD0(PostVisualViewportResizeEventByDynamicToolbar, void()); + MOCK_METHOD0(UpdateDisplayPortMargins, void()); + + void SetMVM(MobileViewportManager* aMVM) { mMVM = aMVM; } + + // MVMContext method implementations. + nsViewportInfo GetViewportInfo(const ScreenIntSize& aDisplaySize) const { + // This is a very basic approximation of what Document::GetViewportInfo() + // does in the most common cases. + // Ideally, we would invoke the algorithm in Document::GetViewportInfo() + // itself, but that would require refactoring it a bit to remove + // dependencies on the actual Document which we don't have available in + // this test harness. + CSSSize viewportSize = mDisplaySize / mDeviceScale; + if (mAutoSizeFlag == AutoSizeFlag::FixedSize) { + viewportSize = CSSSize(mFixedViewportWidth, + mFixedViewportWidth * (float(mDisplaySize.height) / + mDisplaySize.width)); + } + return nsViewportInfo(mDefaultScale, mMinScale, mMaxScale, viewportSize, + mAutoSizeFlag, mAutoScaleFlag, mZoomFlag, + dom::ViewportFitType::Auto); + } + CSSToLayoutDeviceScale CSSToDevPixelScale() const { return mDeviceScale; } + float GetResolution() const { return mResolution; } + bool SubjectMatchesDocument(nsISupports* aSubject) const { return true; } + Maybe CalculateScrollableRectForRSF() const { + return Some(CSSRect(CSSPoint(), mContentSize)); + } + bool IsResolutionUpdatedByApz() const { return false; } + LayoutDeviceMargin ScrollbarAreaToExcludeFromCompositionBounds() const { + return LayoutDeviceMargin(); + } + Maybe GetContentViewerSize() const { + return Some(mDisplaySize); + } + bool AllowZoomingForDocument() const { return true; } + bool IsInReaderMode() const { return false; } + bool IsDocumentLoading() const { return false; } + + void SetResolutionAndScaleTo(float aResolution, + ResolutionChangeOrigin aOrigin) { + mResolution = aResolution; + mMVM->ResolutionUpdated(aOrigin); + } + void Reflow(const CSSSize& aNewSize) { + mICBSize = aNewSize; + mContentSize = mLayoutFunction(mICBSize); + } + + // Allow test code to modify the input metrics. + void SetMinScale(CSSToScreenScale aMinScale) { mMinScale = aMinScale; } + void SetMaxScale(CSSToScreenScale aMaxScale) { mMaxScale = aMaxScale; } + void SetInitialScale(CSSToScreenScale aInitialScale) { + mDefaultScale = aInitialScale; + mAutoScaleFlag = AutoScaleFlag::FixedScale; + } + void SetFixedViewportWidth(CSSCoord aWidth) { + mFixedViewportWidth = aWidth; + mAutoSizeFlag = AutoSizeFlag::FixedSize; + } + void SetDisplaySize(const LayoutDeviceIntSize& aNewDisplaySize) { + mDisplaySize = aNewDisplaySize; + } + void SetLayoutFunction(const LayoutFunction& aLayoutFunction) { + mLayoutFunction = aLayoutFunction; + } + + // Allow test code to query the output metrics. + CSSSize GetICBSize() const { return mICBSize; } + CSSSize GetContentSize() const { return mContentSize; } + + private: + // Input metrics, with some sensible defaults. + LayoutDeviceIntSize mDisplaySize{300, 600}; + CSSToScreenScale mDefaultScale{1.0f}; + CSSToScreenScale mMinScale{0.25f}; + CSSToScreenScale mMaxScale{10.0f}; + CSSToLayoutDeviceScale mDeviceScale{1.0f}; + CSSCoord mFixedViewportWidth; + AutoSizeFlag mAutoSizeFlag = AutoSizeFlag::AutoSize; + AutoScaleFlag mAutoScaleFlag = AutoScaleFlag::AutoScale; + ZoomFlag mZoomFlag = ZoomFlag::AllowZoom; + // As a default layout function, just set the content size to the ICB size. + LayoutFunction mLayoutFunction = [](CSSSize aICBSize) { return aICBSize; }; + + // Output metrics. + float mResolution = 1.0f; + CSSSize mICBSize; + CSSSize mContentSize; + + MobileViewportManager* mMVM = nullptr; +}; + +class MVMTester : public ::testing::Test { + public: + MVMTester() + : mMVMContext(new MockMVMContext()), + mMVM(new MobileViewportManager( + mMVMContext, + MobileViewportManager::ManagerType::VisualAndMetaViewport)) { + mMVMContext->SetMVM(mMVM.get()); + } + + void Resize(const LayoutDeviceIntSize& aNewDisplaySize) { + mMVMContext->SetDisplaySize(aNewDisplaySize); + mMVM->RequestReflow(false); + } + + protected: + RefPtr mMVMContext; + RefPtr mMVM; +}; + +TEST_F(MVMTester, ZoomBoundsRespectedAfterRotation_Bug1536755) { + // Set up initial conditions. + mMVMContext->SetDisplaySize(LayoutDeviceIntSize(600, 300)); + mMVMContext->SetInitialScale(CSSToScreenScale(1.0f)); + mMVMContext->SetMinScale(CSSToScreenScale(1.0f)); + mMVMContext->SetMaxScale(CSSToScreenScale(1.0f)); + // Set a layout function that simulates a page which is twice + // as tall as it is wide. + mMVMContext->SetLayoutFunction([](CSSSize aICBSize) { + return CSSSize(aICBSize.width, aICBSize.width * 2); + }); + + // Perform an initial viewport computation and reflow, and + // sanity-check the results. + mMVM->SetInitialViewport(); + EXPECT_EQ(CSSSize(600, 300), mMVMContext->GetICBSize()); + EXPECT_EQ(CSSSize(600, 1200), mMVMContext->GetContentSize()); + EXPECT_EQ(1.0f, mMVMContext->GetResolution()); + + // Now rotate the screen, and check that the minimum and maximum + // scales are still respected after the rotation. + Resize(LayoutDeviceIntSize(300, 600)); + EXPECT_EQ(CSSSize(300, 600), mMVMContext->GetICBSize()); + EXPECT_EQ(CSSSize(300, 600), mMVMContext->GetContentSize()); + EXPECT_EQ(1.0f, mMVMContext->GetResolution()); +} + +TEST_F(MVMTester, LandscapeToPortraitRotation_Bug1523844) { + // Set up initial conditions. + mMVMContext->SetDisplaySize(LayoutDeviceIntSize(300, 600)); + // Set a layout function that simulates a page with a fixed + // content size that's as wide as the screen in one orientation + // (and wider in the other). + mMVMContext->SetLayoutFunction( + [](CSSSize aICBSize) { return CSSSize(600, 1200); }); + + // Simulate a "DOMMetaAdded" event being fired before calling + // SetInitialViewport(). This matches what typically happens + // during real usage (the MVM receives the "DOMMetaAdded" + // before the "load", and it's the "load" that calls + // SetInitialViewport()), and is important to trigger this + // bug, because it causes the MVM to be stuck with an + // "mRestoreResolution" (prior to the fix). + mMVM->HandleDOMMetaAdded(); + + // Perform an initial viewport computation and reflow, and + // sanity-check the results. + mMVM->SetInitialViewport(); + EXPECT_EQ(CSSSize(300, 600), mMVMContext->GetICBSize()); + EXPECT_EQ(CSSSize(600, 1200), mMVMContext->GetContentSize()); + EXPECT_EQ(0.5f, mMVMContext->GetResolution()); + + // Rotate to landscape. + Resize(LayoutDeviceIntSize(600, 300)); + EXPECT_EQ(CSSSize(600, 300), mMVMContext->GetICBSize()); + EXPECT_EQ(CSSSize(600, 1200), mMVMContext->GetContentSize()); + EXPECT_EQ(1.0f, mMVMContext->GetResolution()); + + // Rotate back to portrait and check that we have returned + // to the portrait resolution. + Resize(LayoutDeviceIntSize(300, 600)); + EXPECT_EQ(CSSSize(300, 600), mMVMContext->GetICBSize()); + EXPECT_EQ(CSSSize(600, 1200), mMVMContext->GetContentSize()); + EXPECT_EQ(0.5f, mMVMContext->GetResolution()); +} diff --git a/gfx/layers/apz/test/gtest/mvm/moz.build b/gfx/layers/apz/test/gtest/mvm/moz.build new file mode 100644 index 0000000000..0fa985307b --- /dev/null +++ b/gfx/layers/apz/test/gtest/mvm/moz.build @@ -0,0 +1,13 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +UNIFIED_SOURCES += [ + "TestMobileViewportManager.cpp", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul-gtest" diff --git a/gfx/layers/apz/test/mochitest/.eslintrc.js b/gfx/layers/apz/test/mochitest/.eslintrc.js new file mode 100644 index 0000000000..721e0938af --- /dev/null +++ b/gfx/layers/apz/test/mochitest/.eslintrc.js @@ -0,0 +1,5 @@ +"use strict"; + +module.exports = { + extends: ["plugin:mozilla/mochitest-test", "plugin:mozilla/chrome-test"], +}; diff --git a/gfx/layers/apz/test/mochitest/FissionTestHelperChild.jsm b/gfx/layers/apz/test/mochitest/FissionTestHelperChild.jsm new file mode 100644 index 0000000000..d24546a211 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/FissionTestHelperChild.jsm @@ -0,0 +1,159 @@ +var EXPORTED_SYMBOLS = ["FissionTestHelperChild"]; + +// This code runs in the content process that holds the window to which +// this actor is attached. There is one instance of this class for each +// "inner window" (i.e. one per content document, including iframes/nested +// iframes). +// There is a 1:1 relationship between instances of this class and +// FissionTestHelperParent instances, and the pair are entangled such +// that they can communicate with each other regardless of which process +// they live in. + +class FissionTestHelperChild extends JSWindowActorChild { + constructor() { + super(); + this._msgCounter = 0; + this._oopifResponsePromiseResolvers = []; + } + + cw() { + return this.contentWindow.wrappedJSObject; + } + + initialize() { + // This exports a bunch of things into the content window so that + // the test can access them. Most things are scoped inside the + // FissionTestHelper object on the window to avoid polluting the global + // namespace. + + let cw = this.cw(); + Cu.exportFunction( + (cond, msg) => this.sendAsyncMessage("ok", { cond, msg }), + cw, + { defineAs: "ok" } + ); + Cu.exportFunction( + (a, b, msg) => this.sendAsyncMessage("is", { a, b, msg }), + cw, + { defineAs: "is" } + ); + + let FissionTestHelper = Cu.createObjectIn(cw, { + defineAs: "FissionTestHelper", + }); + FissionTestHelper.startTestPromise = new cw.Promise( + Cu.exportFunction(resolve => { + this._startTestPromiseResolver = resolve; + }, cw) + ); + + Cu.exportFunction(this.subtestDone.bind(this), FissionTestHelper, { + defineAs: "subtestDone", + }); + + Cu.exportFunction(this.subtestFailed.bind(this), FissionTestHelper, { + defineAs: "subtestFailed", + }); + + Cu.exportFunction(this.sendToOopif.bind(this), FissionTestHelper, { + defineAs: "sendToOopif", + }); + Cu.exportFunction(this.fireEventInEmbedder.bind(this), FissionTestHelper, { + defineAs: "fireEventInEmbedder", + }); + } + + // Called by the subtest to indicate completion to the top-level browser-chrome + // mochitest. + subtestDone() { + let cw = this.cw(); + if (cw.ApzCleanup) { + cw.ApzCleanup.execute(); + } + this.sendAsyncMessage("Test:Complete", {}); + } + + // Called by the subtest to indicate subtest failure. Only one of subtestDone + // or subtestFailed should be called. + subtestFailed(msg) { + this.sendAsyncMessage("ok", { cond: false, msg }); + this.subtestDone(); + } + + // Called by the subtest to eval some code in the OOP iframe. This returns + // a promise that resolves to the return value from the eval. + sendToOopif(iframeElement, stringToEval) { + let browsingContextId = iframeElement.browsingContext.id; + let msgId = ++this._msgCounter; + let cw = this.cw(); + let responsePromise = new cw.Promise( + Cu.exportFunction(resolve => { + this._oopifResponsePromiseResolvers[msgId] = resolve; + }, cw) + ); + this.sendAsyncMessage("EmbedderToOopif", { + browsingContextId, + msgId, + stringToEval, + }); + return responsePromise; + } + + // Called by OOP iframes to dispatch an event in the embedder window. This + // can be used by the OOP iframe to asynchronously notify the embedder of + // things that happen. The embedder can use promiseOneEvent from + // helper_fission_utils.js to listen for these events. + fireEventInEmbedder(eventType, data) { + this.sendAsyncMessage("OopifToEmbedder", { eventType, data }); + } + + handleEvent(evt) { + switch (evt.type) { + case "FissionTestHelper:Init": + this.initialize(); + break; + } + } + + receiveMessage(msg) { + switch (msg.name) { + case "Test:Start": + this._startTestPromiseResolver(); + delete this._startTestPromiseResolver; + break; + case "FromEmbedder": + let evalResult = this.contentWindow.eval(msg.data.stringToEval); + this.sendAsyncMessage("OopifToEmbedder", { + msgId: msg.data.msgId, + evalResult, + }); + break; + case "FromOopif": + if (typeof msg.data.msgId == "number") { + if (!(msg.data.msgId in this._oopifResponsePromiseResolvers)) { + dump( + "Error: FromOopif got a message with unknown numeric msgId in " + + this.contentWindow.location.href + + "\n" + ); + } + this._oopifResponsePromiseResolvers[msg.data.msgId]( + msg.data.evalResult + ); + delete this._oopifResponsePromiseResolvers[msg.data.msgId]; + } else if (typeof msg.data.eventType == "string") { + let cw = this.cw(); + let event = new cw.Event(msg.data.eventType); + event.data = Cu.cloneInto(msg.data.data, cw); + this.contentWindow.dispatchEvent(event); + } else { + dump( + "Warning: Unrecognized FromOopif message received in " + + this.contentWindow.location.href + + "\n" + ); + } + break; + } + } +} diff --git a/gfx/layers/apz/test/mochitest/FissionTestHelperParent.jsm b/gfx/layers/apz/test/mochitest/FissionTestHelperParent.jsm new file mode 100644 index 0000000000..334aa89bf1 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/FissionTestHelperParent.jsm @@ -0,0 +1,104 @@ +var EXPORTED_SYMBOLS = ["FissionTestHelperParent"]; + +// This code always runs in the parent process. There is one instance of +// this class for each "inner window" (should be one per content document, +// including iframes/nested iframes). +// There is a 1:1 relationship between instances of this class and +// FissionTestHelperChild instances, and the pair are entangled such +// that they can communicate with each other regardless of which process +// they live in. + +class FissionTestHelperParent extends JSWindowActorParent { + constructor() { + super(); + this._testCompletePromise = new Promise(resolve => { + this._testCompletePromiseResolver = resolve; + }); + } + + embedderWindow() { + let embedder = this.manager.browsingContext.embedderWindowGlobal; + // embedder is of type WindowGlobalParent, defined in WindowGlobalActors.webidl + if (!embedder) { + dump("ERROR: no embedder found in FissionTestHelperParent\n"); + } + return embedder; + } + + docURI() { + return this.manager.documentURI.spec; + } + + // Returns a promise that is resolved when this parent actor receives a + // "Test:Complete" message from the child. + getTestCompletePromise() { + return this._testCompletePromise; + } + + startTest() { + this.sendAsyncMessage("Test:Start", {}); + } + + receiveMessage(msg) { + switch (msg.name) { + case "ok": + FissionTestHelperParent.SimpleTest.ok( + msg.data.cond, + this.docURI() + " | " + msg.data.msg + ); + break; + + case "is": + FissionTestHelperParent.SimpleTest.is( + msg.data.a, + msg.data.b, + this.docURI() + " | " + msg.data.msg + ); + break; + + case "Test:Complete": + this._testCompletePromiseResolver(); + break; + + case "EmbedderToOopif": + // This relays messages from the embedder to an OOP-iframe. The browsing + // context id in the message data identifies the OOP-iframe. + let oopifBrowsingContext = BrowsingContext.get( + msg.data.browsingContextId + ); + if (oopifBrowsingContext == null) { + FissionTestHelperParent.SimpleTest.ok( + false, + "EmbedderToOopif couldn't find oopif" + ); + break; + } + let oopifActor = oopifBrowsingContext.currentWindowGlobal.getActor( + "FissionTestHelper" + ); + if (!oopifActor) { + FissionTestHelperParent.SimpleTest.ok( + false, + "EmbedderToOopif couldn't find oopif actor" + ); + break; + } + oopifActor.sendAsyncMessage("FromEmbedder", msg.data); + break; + + case "OopifToEmbedder": + // This relays messages from the OOP-iframe to the top-level content + // window which is embedding it. + let embedderActor = this.embedderWindow().getActor("FissionTestHelper"); + if (!embedderActor) { + FissionTestHelperParent.SimpleTest.ok( + false, + "OopifToEmbedder couldn't find embedder" + ); + break; + } + embedderActor.sendAsyncMessage("FromOopif", msg.data); + break; + } + } +} diff --git a/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js b/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js new file mode 100644 index 0000000000..86ffc7a81f --- /dev/null +++ b/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js @@ -0,0 +1,1076 @@ +// ownerGlobal isn't defined in content privileged windows. +/* eslint-disable mozilla/use-ownerGlobal */ + +// Utilities for synthesizing of native events. + +function getResolution() { + let resolution = -1; // bogus value in case DWU fails us + // Use window.top to get the root content window which is what has + // the resolution. + resolution = SpecialPowers.getDOMWindowUtils(window.top).getResolution(); + return resolution; +} + +function getPlatform() { + if (navigator.platform.indexOf("Win") == 0) { + return "windows"; + } + if (navigator.platform.indexOf("Mac") == 0) { + return "mac"; + } + // Check for Android before Linux + if (navigator.appVersion.includes("Android")) { + return "android"; + } + if (navigator.platform.indexOf("Linux") == 0) { + return "linux"; + } + return "unknown"; +} + +function nativeVerticalWheelEventMsg() { + switch (getPlatform()) { + case "windows": + return 0x020a; // WM_MOUSEWHEEL + case "mac": + var useWheelCodepath = SpecialPowers.getBoolPref( + "apz.test.mac.synth_wheel_input", + false + ); + // Default to 1 (kCGScrollPhaseBegan) to trigger PanGestureInput events + // from widget code. Allow setting a pref to override this behaviour and + // trigger ScrollWheelInput events instead. + return useWheelCodepath ? 0 : 1; + case "linux": + return 4; // value is unused, pass GDK_SCROLL_SMOOTH anyway + } + throw new Error( + "Native wheel events not supported on platform " + getPlatform() + ); +} + +function nativeHorizontalWheelEventMsg() { + switch (getPlatform()) { + case "windows": + return 0x020e; // WM_MOUSEHWHEEL + case "mac": + return 0; // value is unused, can be anything + case "linux": + return 4; // value is unused, pass GDK_SCROLL_SMOOTH anyway + } + throw new Error( + "Native wheel events not supported on platform " + getPlatform() + ); +} + +// Given an event target which may be a window or an element, get the associated window. +function windowForTarget(aTarget) { + if (aTarget instanceof Window) { + return aTarget; + } + return aTarget.ownerDocument.defaultView; +} + +// Given an event target which may be a window or an element, get the associated element. +function elementForTarget(aTarget) { + if (aTarget instanceof Window) { + return aTarget.document.documentElement; + } + return aTarget; +} + +// Given an event target which may be a window or an element, get the associatd nsIDOMWindowUtils. +function utilsForTarget(aTarget) { + return SpecialPowers.getDOMWindowUtils(windowForTarget(aTarget)); +} + +// Given a pixel scrolling delta, converts it to the platform's native units. +function nativeScrollUnits(aTarget, aDimen) { + switch (getPlatform()) { + case "linux": { + // GTK deltas are treated as line height divided by 3 by gecko. + var targetWindow = windowForTarget(aTarget); + var targetElement = elementForTarget(aTarget); + var lineHeight = targetWindow.getComputedStyle(targetElement)[ + "font-size" + ]; + return aDimen / (parseInt(lineHeight) * 3); + } + } + return aDimen; +} + +function nativeMouseDownEventMsg() { + switch (getPlatform()) { + case "windows": + return 2; // MOUSEEVENTF_LEFTDOWN + case "mac": + return 1; // NSEventTypeLeftMouseDown + case "linux": + return 4; // GDK_BUTTON_PRESS + case "android": + return 5; // ACTION_POINTER_DOWN + } + throw new Error( + "Native mouse-down events not supported on platform " + getPlatform() + ); +} + +function nativeMouseMoveEventMsg() { + switch (getPlatform()) { + case "windows": + return 1; // MOUSEEVENTF_MOVE + case "mac": + return 5; // NSEventTypeMouseMoved + case "linux": + return 3; // GDK_MOTION_NOTIFY + case "android": + return 7; // ACTION_HOVER_MOVE + } + throw new Error( + "Native mouse-move events not supported on platform " + getPlatform() + ); +} + +function nativeMouseUpEventMsg() { + switch (getPlatform()) { + case "windows": + return 4; // MOUSEEVENTF_LEFTUP + case "mac": + return 2; // NSEventTypeLeftMouseUp + case "linux": + return 7; // GDK_BUTTON_RELEASE + case "android": + return 6; // ACTION_POINTER_UP + } + throw new Error( + "Native mouse-up events not supported on platform " + getPlatform() + ); +} + +function getBoundingClientRectRelativeToVisualViewport(aElement) { + let utils = SpecialPowers.getDOMWindowUtils(window); + var rect = aElement.getBoundingClientRect(); + var offsetX = {}, + offsetY = {}; + // TODO: Audit whether these offset values are correct or not for + // position:fixed elements especially in the case where the visual viewport + // offset is not 0. + utils.getVisualViewportOffsetRelativeToLayoutViewport(offsetX, offsetY); + rect.x -= offsetX.value; + rect.y -= offsetY.value; + return rect; +} + +// Several event sythesization functions below (and their helpers) take a "target" +// parameter which may be either an element or a window. For such functions, +// the target's "bounding rect" refers to the bounding client rect for an element, +// and the window's origin for a window. +// Not all functions have been "upgraded" to allow a window argument yet; feel +// free to upgrade others as necessary. + +// Get the origin of |aTarget| relative to the root content document's +// visual viewport in CSS coordinates. +// |aTarget| may be an element (contained in the root content document or +// a subdocument) or, as a special case, the root content window. +// FIXME: Support iframe windows as targets. +function getTargetOrigin(aTarget) { + let origin = { left: 0, top: 0 }; + + // If the target is the root content window, its origin relative + // to the visual viewport is (0, 0). + if (aTarget instanceof Window) { + // FIXME: Assert that it's not an iframe window. + return origin; + } + + // Otherwise, we have an element. Start with the origin of + // its bounding client rect which is relative to the enclosing + // document's layout viewport. Note that for iframes, the + // layout viewport is also the visual viewport. + let rect = aTarget.getBoundingClientRect(); + origin.left += rect.left; + origin.top += rect.top; + + // Iterate up the window hierarchy until we reach the root + // content window, adding the offsets of any iframe windows + // relative to their parent window. + while (aTarget.ownerDocument.defaultView.frameElement) { + let iframe = aTarget.ownerDocument.defaultView.frameElement; + // The offset of the iframe window relative to the parent window + // includes the iframe's border, and the iframe's origin in its + // containing document. + let style = iframe.ownerDocument.defaultView.getComputedStyle(iframe); + let borderLeft = parseFloat(style.borderLeftWidth) || 0; + let borderTop = parseFloat(style.borderTopWidth) || 0; + let paddingLeft = parseFloat(style.paddingLeft) || 0; + let paddingTop = parseFloat(style.paddingTop) || 0; + rect = iframe.getBoundingClientRect(); + origin.left += rect.left + borderLeft + paddingLeft; + origin.top += rect.top + borderTop + paddingTop; + aTarget = iframe; + } + + // Now we have coordinates relative to the root content document's + // layout viewport. Subtract the offset of the visual viewport + // relative to the layout viewport, to get coordinates relative to + // the visual viewport. + var offsetX = {}, + offsetY = {}; + let rootUtils = SpecialPowers.getDOMWindowUtils(window.top); + rootUtils.getVisualViewportOffsetRelativeToLayoutViewport(offsetX, offsetY); + origin.left -= offsetX.value; + origin.top -= offsetY.value; + return origin; +} + +// Convert (aX, aY), in CSS pixels relative to aTarget's bounding rect +// to device pixels relative to the screen. +// TODO: this function currently does not incorporate some CSS transforms on +// elements enclosing aTarget, e.g. scale transforms. +function coordinatesRelativeToScreen(aX, aY, aTarget) { + // Note that |window| might not be the root content window, for two + // possible reasons: + // 1. The mochitest that's calling into this function is not using a mechanism + // like runSubtestsSeriallyInFreshWindows() to load the test page in + // a top-level context, so it's loaded into an iframe by the mochitest + // harness. + // 2. The mochitest itself creates an iframe and calls this function from + // script running in the context of the iframe. + // Since the resolution applies to the root content document, below we use + // the mozInnerScreen{X,Y} of the root content window (window.top) only, + // and factor any offsets between iframe windows and the root content window + // into |origin|. + var utils = SpecialPowers.getDOMWindowUtils(window); + var deviceScale = utils.screenPixelsPerCSSPixel; + var deviceScaleNoOverride = utils.screenPixelsPerCSSPixelNoOverride; + var resolution = getResolution(); + var origin = getTargetOrigin(aTarget); + // moxInnerScreen{X,Y} are in CSS coordinates of the browser chrome. + // The device scale applies to them, but the resolution only zooms the content. + // In addition, if we're inside RDM, RDM overrides the device scale; + // the overridden scale only applies to the content inside the RDM + // document, not to mozInnerScreen{X,Y}. + return { + x: + window.top.mozInnerScreenX * deviceScaleNoOverride + + (origin.left + aX) * resolution * deviceScale, + y: + window.top.mozInnerScreenY * deviceScaleNoOverride + + (origin.top + aY) * resolution * deviceScale, + }; +} + +// Get the bounding box of aElement, and return it in device pixels +// relative to the screen. +// TODO: This function should probably take into account the resolution +// and use getBoundingClientRectRelativeToVisualViewport() +// like coordinatesRelativeToScreen() does. +function rectRelativeToScreen(aElement) { + var targetWindow = aElement.ownerDocument.defaultView; + var scale = targetWindow.devicePixelRatio; + var rect = aElement.getBoundingClientRect(); + return { + x: (targetWindow.mozInnerScreenX + rect.left) * scale, + y: (targetWindow.mozInnerScreenY + rect.top) * scale, + w: rect.width * scale, + h: rect.height * scale, + }; +} + +// Synthesizes a native mousewheel event and returns immediately. This does not +// guarantee anything; you probably want to use one of the other functions below +// which actually wait for results. +// aX and aY are relative to the top-left of |aTarget|'s bounding rect. +// aDeltaX and aDeltaY are pixel deltas, and aObserver can be left undefined +// if not needed. +function synthesizeNativeWheel(aTarget, aX, aY, aDeltaX, aDeltaY, aObserver) { + var pt = coordinatesRelativeToScreen(aX, aY, aTarget); + if (aDeltaX && aDeltaY) { + throw new Error( + "Simultaneous wheeling of horizontal and vertical is not supported on all platforms." + ); + } + aDeltaX = nativeScrollUnits(aTarget, aDeltaX); + aDeltaY = nativeScrollUnits(aTarget, aDeltaY); + var msg = aDeltaX + ? nativeHorizontalWheelEventMsg() + : nativeVerticalWheelEventMsg(); + var utils = utilsForTarget(aTarget); + var element = elementForTarget(aTarget); + utils.sendNativeMouseScrollEvent( + pt.x, + pt.y, + msg, + aDeltaX, + aDeltaY, + 0, + 0, + 0, + element, + aObserver + ); + return true; +} + +// Synthesizes a native mousewheel event and invokes the callback once the +// request has been successfully made to the OS. This does not necessarily +// guarantee that the OS generates the event we requested. See +// synthesizeNativeWheel for details on the parameters. +function synthesizeNativeWheelAndWaitForObserver( + aElement, + aX, + aY, + aDeltaX, + aDeltaY, + aCallback +) { + var observer = { + observe(aSubject, aTopic, aData) { + if (aCallback && aTopic == "mousescrollevent") { + setTimeout(aCallback, 0); + } + }, + }; + return synthesizeNativeWheel(aElement, aX, aY, aDeltaX, aDeltaY, observer); +} + +// Synthesizes a native mousewheel event and invokes the callback once the +// wheel event is dispatched to |aTarget|'s containing window. If the event +// targets content in a subdocument, |aTarget| should be inside the +// subdocument (or the subdocument's window). See synthesizeNativeWheel for +// details on the other parameters. +function synthesizeNativeWheelAndWaitForWheelEvent( + aTarget, + aX, + aY, + aDeltaX, + aDeltaY, + aCallback +) { + let p = promiseNativeWheelAndWaitForWheelEvent( + aTarget, + aX, + aY, + aDeltaX, + aDeltaY + ); + if (aCallback) { + p.then(aCallback); + } + return true; +} + +// Same as synthesizeNativeWheelAndWaitForWheelEvent, except returns a promise +// instead of taking a callback +function promiseNativeWheelAndWaitForWheelEvent( + aTarget, + aX, + aY, + aDeltaX, + aDeltaY +) { + return new Promise((resolve, reject) => { + var targetWindow = windowForTarget(aTarget); + targetWindow.addEventListener( + "wheel", + function(e) { + setTimeout(resolve, 0); + }, + { once: true } + ); + try { + synthesizeNativeWheel(aTarget, aX, aY, aDeltaX, aDeltaY); + } catch (e) { + reject(); + } + }); +} + +// Synthesizes a native mousewheel event and invokes the callback once the +// first resulting scroll event is dispatched to |aTarget|'s containing window. +// If the event targets content in a subdocument, |aTarget| should be inside +// the subdocument (or the subdocument's window). See synthesizeNativeWheel +// for details on the other parameters. +function synthesizeNativeWheelAndWaitForScrollEvent( + aTarget, + aX, + aY, + aDeltaX, + aDeltaY, + aCallback +) { + promiseNativeWheelAndWaitForScrollEvent( + aTarget, + aX, + aY, + aDeltaX, + aDeltaY + ).then(aCallback); + return true; +} + +// Same as synthesizeNativeWheelAndWaitForScrollEvent, but returns a promise +// instead of taking a callback +function promiseNativeWheelAndWaitForScrollEvent( + aTarget, + aX, + aY, + aDeltaX, + aDeltaY +) { + return new Promise((resolve, reject) => { + var targetWindow = windowForTarget(aTarget); + targetWindow.addEventListener( + "scroll", + function() { + setTimeout(resolve, 0); + }, + { capture: true, once: true } + ); // scroll events don't always bubble + try { + synthesizeNativeWheel(aTarget, aX, aY, aDeltaX, aDeltaY); + } catch (e) { + reject(); + } + }); +} + +// Synthesizes a native mouse move event and returns immediately. +// aX and aY are relative to the top-left of |aTarget|'s bounding rect. +function synthesizeNativeMouseMove(aTarget, aX, aY) { + var pt = coordinatesRelativeToScreen(aX, aY, aTarget); + var utils = utilsForTarget(aTarget); + var element = elementForTarget(aTarget); + utils.sendNativeMouseEvent(pt.x, pt.y, nativeMouseMoveEventMsg(), 0, element); + return true; +} + +// Synthesizes a native mouse move event and invokes the callback once the +// mouse move event is dispatched to |aTarget|'s containing window. If the event +// targets content in a subdocument, |aTarget| should be inside the +// subdocument (or the subdocument window). See synthesizeNativeMouseMove for +// details on the other parameters. +function synthesizeNativeMouseMoveAndWaitForMoveEvent( + aTarget, + aX, + aY, + aCallback +) { + promiseNativeMouseMoveAndWaitForMoveEvent(aTarget, aX, aY).then(aCallback); + return true; +} + +// Same as synthesizeNativeMouseMoveAndWaitForMoveEvent but returns a promise +// instead of taking a callback. +function promiseNativeMouseMoveAndWaitForMoveEvent(aTarget, aX, aY) { + return new Promise((resolve, reject) => { + var targetWindow = windowForTarget(aTarget); + targetWindow.addEventListener( + "mousemove", + function(e) { + setTimeout(resolve, 0); + }, + { once: true } + ); + try { + synthesizeNativeMouseMove(aTarget, aX, aY); + } catch (e) { + reject(); + } + }); +} + +// Synthesizes a native touch event and dispatches it. aX and aY in CSS pixels +// relative to the top-left of |aTarget|'s bounding rect. +function synthesizeNativeTouch( + aTarget, + aX, + aY, + aType, + aObserver = null, + aTouchId = 0 +) { + var pt = coordinatesRelativeToScreen(aX, aY, aTarget); + var utils = utilsForTarget(aTarget); + utils.sendNativeTouchPoint(aTouchId, aType, pt.x, pt.y, 1, 90, aObserver); + return true; +} + +// Function to generate native touch events for a multi-touch sequence. +// aTarget is the element or window whose bounding rect the coordinates are relative to. +// aPositions is a 2D array of position data. It is indexed as [row][column], +// where advancing the row counter moves forward in time, and each column +// represents a single "finger" (or touch input). Each row must have exactly +// the same number of columns, and the number of columns must match the length +// of the aTouchIds parameter. +// For each row, each entry is either an object with x and y fields, +// or a null. A null value indicates that the "finger" should be "lifted" +// (i.e. send a touchend for that touch input). A non-null value therefore +// indicates the position of the touch input. +// This function takes care of the state tracking necessary to send +// touchstart/touchend inputs as necessary as the fingers go up and down. +// aObserver is the observer that will get registered on the very last +// synthesizeNativeTouch call this function makes. +// aTouchIds is an array holding the touch ID values of each "finger". +function synthesizeNativeTouchSequences( + aTarget, + aPositions, + aObserver = null, + aTouchIds = [0] +) { + // We use lastNonNullValue to figure out which synthesizeNativeTouch call + // will be the last one we make, so that we can register aObserver on it. + var lastNonNullValue = -1; + for (let i = 0; i < aPositions.length; i++) { + if (aPositions[i] == null) { + throw new Error(`aPositions[${i}] was unexpectedly null`); + } + if (aPositions[i].length != aTouchIds.length) { + throw new Error( + `aPositions[${i}] did not have the expected number of positions; ` + + `expected ${aTouchIds.length} touch points but found ${aPositions[i].length}` + ); + } + for (let j = 0; j < aTouchIds.length; j++) { + if (aPositions[i][j] != null) { + lastNonNullValue = i * aTouchIds.length + j; + // Do the conversion to screen space before actually synthesizing + // the events, otherwise the screen space may change as a result of + // the touch inputs and the conversion may not work as intended. + aPositions[i][j] = coordinatesRelativeToScreen( + aPositions[i][j].x, + aPositions[i][j].y, + aTarget + ); + } + } + } + if (lastNonNullValue < 0) { + throw new Error("All values in positions array were null!"); + } + + // Insert a row of nulls at the end of aPositions, to ensure that all + // touches get removed. If the touches have already been removed this will + // just add an extra no-op iteration in the aPositions loop below. + var allNullRow = new Array(aTouchIds.length); + allNullRow.fill(null); + aPositions.push(allNullRow); + + // The last sendNativeTouchPoint call will be the TOUCH_REMOVE which happens + // one iteration of aPosition after the last non-null value. + var lastSynthesizeCall = lastNonNullValue + aTouchIds.length; + + // track which touches are down and which are up. start with all up + var currentPositions = new Array(aTouchIds.length); + currentPositions.fill(null); + + var utils = utilsForTarget(aTarget); + // Iterate over the position data now, and generate the touches requested + for (let i = 0; i < aPositions.length; i++) { + for (let j = 0; j < aTouchIds.length; j++) { + if (aPositions[i][j] == null) { + // null means lift the finger + if (currentPositions[j] == null) { + // it's already lifted, do nothing + } else { + // synthesize the touch-up. If this is the last call we're going to + // make, pass the observer as well + var thisIndex = i * aTouchIds.length + j; + var observer = lastSynthesizeCall == thisIndex ? aObserver : null; + utils.sendNativeTouchPoint( + aTouchIds[j], + SpecialPowers.DOMWindowUtils.TOUCH_REMOVE, + currentPositions[j].x, + currentPositions[j].y, + 1, + 90, + observer + ); + currentPositions[j] = null; + } + } else { + utils.sendNativeTouchPoint( + aTouchIds[j], + SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, + aPositions[i][j].x, + aPositions[i][j].y, + 1, + 90, + null + ); + currentPositions[j] = aPositions[i][j]; + } + } + } + return true; +} + +// Note that when calling this function you'll want to make sure that the pref +// "apz.touch_start_tolerance" is set to 0, or some of the touchmove will get +// consumed to overcome the panning threshold. +function synthesizeNativeTouchDrag( + aTarget, + aX, + aY, + aDeltaX, + aDeltaY, + aObserver = null, + aTouchId = 0 +) { + var steps = Math.max(Math.abs(aDeltaX), Math.abs(aDeltaY)); + var positions = [[{ x: aX, y: aY }]]; + for (var i = 1; i < steps; i++) { + var dx = i * (aDeltaX / steps); + var dy = i * (aDeltaY / steps); + var pos = { x: aX + dx, y: aY + dy }; + positions.push([pos]); + } + positions.push([{ x: aX + aDeltaX, y: aY + aDeltaY }]); + return synthesizeNativeTouchSequences(aTarget, positions, aObserver, [ + aTouchId, + ]); +} + +// Promise-returning variant of synthesizeNativeTouchDrag +function promiseNativeTouchDrag( + aTarget, + aX, + aY, + aDeltaX, + aDeltaY, + aTouchId = 0 +) { + return new Promise(resolve => { + synthesizeNativeTouchDrag( + aTarget, + aX, + aY, + aDeltaX, + aDeltaY, + resolve, + aTouchId + ); + }); +} + +function synthesizeNativeTap(aElement, aX, aY, aObserver = null) { + var pt = coordinatesRelativeToScreen(aX, aY, aElement); + var utils = SpecialPowers.getDOMWindowUtils( + aElement.ownerDocument.defaultView + ); + utils.sendNativeTouchTap(pt.x, pt.y, false, aObserver); + return true; +} + +function synthesizeNativeMouseEvent(aTarget, aX, aY, aType, aObserver = null) { + var pt = coordinatesRelativeToScreen(aX, aY, aTarget); + var utils = utilsForTarget(aTarget); + var element = elementForTarget(aTarget); + utils.sendNativeMouseEvent(pt.x, pt.y, aType, 0, element, aObserver); + return true; +} + +// Promise-returning variant of synthesizeNativeMouseEvent +function promiseNativeMouseEvent(aTarget, aX, aY, aType) { + return new Promise(resolve => { + synthesizeNativeMouseEvent(aTarget, aX, aY, aType, resolve); + }); +} + +function synthesizeNativeClick(aElement, aX, aY, aObserver = null) { + var pt = coordinatesRelativeToScreen(aX, aY, aElement); + var utils = SpecialPowers.getDOMWindowUtils( + aElement.ownerDocument.defaultView + ); + utils.sendNativeMouseEvent( + pt.x, + pt.y, + nativeMouseDownEventMsg(), + 0, + aElement, + function() { + utils.sendNativeMouseEvent( + pt.x, + pt.y, + nativeMouseUpEventMsg(), + 0, + aElement, + aObserver + ); + } + ); + return true; +} + +// Promise-returning variant of synthesizeNativeClick. +function promiseNativeClick(aElement, aX, aY) { + return new Promise(resolve => { + synthesizeNativeClick(aElement, aX, aY, resolve); + }); +} + +function synthesizeNativeClickAndWaitForClickEvent( + aElement, + aX, + aY, + aCallback +) { + var targetWindow = windowForTarget(aElement); + targetWindow.addEventListener( + "click", + function(e) { + setTimeout(aCallback, 0); + }, + { capture: true, once: true } + ); + return synthesizeNativeClick(aElement, aX, aY); +} + +// Promise-returning variant of synthesizeNativeClickAndWaitForClickEvent +function promiseNativeClickAndClickEvent(aElement, aX, aY) { + return new Promise(resolve => { + synthesizeNativeClickAndWaitForClickEvent(aElement, aX, aY, resolve); + }); +} + +// Move the mouse to (dx, dy) relative to |target|, and scroll the wheel +// at that location. +// Moving the mouse is necessary to avoid wheel events from two consecutive +// moveMouseAndScrollWheelOver() calls on different elements being incorrectly +// considered as part of the same wheel transaction. +// We also wait for the mouse move event to be processed before sending the +// wheel event, otherwise there is a chance they might get reordered, and +// we have the transaction problem again. +function moveMouseAndScrollWheelOver( + target, + dx, + dy, + testDriver, + waitForScroll = true, + scrollDelta = 10 +) { + promiseMoveMouseAndScrollWheelOver( + target, + dx, + dy, + waitForScroll, + scrollDelta + ).then(testDriver); + return true; +} + +// Same as moveMouseAndScrollWheelOver, but returns a promise instead of taking +// a callback function. +function promiseMoveMouseAndScrollWheelOver( + target, + dx, + dy, + waitForScroll = true, + scrollDelta = 10 +) { + let p = promiseNativeMouseMoveAndWaitForMoveEvent(target, dx, dy); + if (waitForScroll) { + p = p.then(() => + promiseNativeWheelAndWaitForScrollEvent(target, dx, dy, 0, -scrollDelta) + ); + } else { + p = p.then(() => + promiseNativeWheelAndWaitForWheelEvent(target, dx, dy, 0, -scrollDelta) + ); + } + return p; +} + +// Synthesizes events to drag |target|'s vertical scrollbar by the distance +// specified, synthesizing a mousemove for each increment as specified. +// Returns null if the element doesn't have a vertical scrollbar. Otherwise, +// returns an async function that should be invoked after the mousemoves have been +// processed by the widget code, to end the scrollbar drag. Mousemoves being +// processed by the widget code can be detected by listening for the mousemove +// events in the caller, or for some other event that is triggered by the +// mousemove, such as the scroll event resulting from the scrollbar drag. +// The scaleFactor argument should be provided if the scrollframe has been +// scaled by an enclosing CSS transform. (TODO: this is a workaround for the +// fact that coordinatesRelativeToScreen is supposed to do this automatically +// but it currently does not). +// Note: helper_scrollbar_snap_bug1501062.html contains a copy of this code +// with modifications. Fixes here should be copied there if appropriate. +// |target| can be an element (for subframes) or a window (for root frames). +async function promiseVerticalScrollbarDrag( + target, + distance = 20, + increment = 5, + scaleFactor = 1 +) { + var targetElement = elementForTarget(target); + var w = {}, + h = {}; + utilsForTarget(target).getScrollbarSizes(targetElement, w, h); + var verticalScrollbarWidth = w.value; + if (verticalScrollbarWidth == 0) { + return null; + } + + var upArrowHeight = verticalScrollbarWidth; // assume square scrollbar buttons + var mouseX = targetElement.clientWidth + verticalScrollbarWidth / 2; + var mouseY = upArrowHeight + 5; // start dragging somewhere in the thumb + mouseX *= scaleFactor; + mouseY *= scaleFactor; + + dump( + "Starting drag at " + + mouseX + + ", " + + mouseY + + " from top-left of #" + + targetElement.id + + "\n" + ); + + // Move the mouse to the scrollbar thumb and drag it down + await promiseNativeMouseEvent( + target, + mouseX, + mouseY, + nativeMouseMoveEventMsg() + ); + // mouse down + await promiseNativeMouseEvent( + target, + mouseX, + mouseY, + nativeMouseDownEventMsg() + ); + // drag vertically by |increment| until we reach the specified distance + for (var y = increment; y < distance; y += increment) { + await promiseNativeMouseEvent( + target, + mouseX, + mouseY + y, + nativeMouseMoveEventMsg() + ); + } + await promiseNativeMouseEvent( + target, + mouseX, + mouseY + distance, + nativeMouseMoveEventMsg() + ); + + // and return an async function to call afterwards to finish up the drag + return async function() { + dump("Finishing drag of #" + targetElement.id + "\n"); + await promiseNativeMouseEvent( + target, + mouseX, + mouseY + distance, + nativeMouseUpEventMsg() + ); + }; +} + +// Synthesizes a native mouse drag, starting at offset (mouseX, mouseY) from +// the given target. The drag occurs in the given number of steps, to a final +// destination of (mouseX + distanceX, mouseY + distanceY) from the target. +// Returns a promise (wrapped in a function, so it doesn't execute immediately) +// that should be awaited after the mousemoves have been processed by the widget +// code, to end the drag. This is important otherwise the OS can sometimes +// reorder the events and the drag doesn't have the intended effect (see +// bug 1368603). +// Example usage: +// let dragFinisher = await promiseNativeMouseDrag(myElement, 0, 0); +// await myIndicationThatDragHadAnEffect; +// await dragFinisher(); +async function promiseNativeMouseDrag( + target, + mouseX, + mouseY, + distanceX = 20, + distanceY = 20, + steps = 20 +) { + var targetElement = elementForTarget(target); + dump( + "Starting drag at " + + mouseX + + ", " + + mouseY + + " from top-left of #" + + targetElement.id + + "\n" + ); + + // Move the mouse to the target position + await promiseNativeMouseEvent( + target, + mouseX, + mouseY, + nativeMouseMoveEventMsg() + ); + // mouse down + await promiseNativeMouseEvent( + target, + mouseX, + mouseY, + nativeMouseDownEventMsg() + ); + // drag vertically by |increment| until we reach the specified distance + for (var s = 1; s <= steps; s++) { + let dx = distanceX * (s / steps); + let dy = distanceY * (s / steps); + dump(`Dragging to ${mouseX + dx}, ${mouseY + dy} from target\n`); + await promiseNativeMouseEvent( + target, + mouseX + dx, + mouseY + dy, + nativeMouseMoveEventMsg() + ); + } + + // and return a function-wrapped promise to call afterwards to finish the drag + return function() { + return promiseNativeMouseEvent( + target, + mouseX + distanceX, + mouseY + distanceY, + nativeMouseUpEventMsg() + ); + }; +} + +// Synthesizes a native touch sequence of events corresponding to a pinch-zoom-in +// at the given focus point. The focus point must be specified in CSS coordinates +// relative to the document body. +function pinchZoomInTouchSequence(focusX, focusY) { + // prettier-ignore + var zoom_in = [ + [ { x: focusX - 25, y: focusY - 50 }, { x: focusX + 25, y: focusY + 50 } ], + [ { x: focusX - 30, y: focusY - 80 }, { x: focusX + 30, y: focusY + 80 } ], + [ { x: focusX - 35, y: focusY - 110 }, { x: focusX + 40, y: focusY + 110 } ], + [ { x: focusX - 40, y: focusY - 140 }, { x: focusX + 45, y: focusY + 140 } ], + [ { x: focusX - 45, y: focusY - 170 }, { x: focusX + 50, y: focusY + 170 } ], + [ { x: focusX - 50, y: focusY - 200 }, { x: focusX + 55, y: focusY + 200 } ], + ]; + + var touchIds = [0, 1]; + return synthesizeNativeTouchSequences(document.body, zoom_in, null, touchIds); +} + +// Returns a promise that is resolved when the observer service dispatches a +// message with the given topic. +function promiseTopic(aTopic) { + return new Promise((resolve, reject) => { + SpecialPowers.Services.obs.addObserver(function observer( + subject, + topic, + data + ) { + try { + SpecialPowers.Services.obs.removeObserver(observer, topic); + resolve([subject, data]); + } catch (ex) { + SpecialPowers.Services.obs.removeObserver(observer, topic); + reject(ex); + } + }, + aTopic); + }); +} + +// Returns a promise that is resolved when a APZ transform ends. +function promiseTransformEnd() { + return promiseTopic("APZ:TransformEnd"); +} + +// Returns a promise that resolves after the indicated number +// of touchend events have fired on the given target element. +function promiseTouchEnd(element, count = 1) { + return new Promise(resolve => { + var eventCount = 0; + var counterFunction = function(e) { + eventCount++; + if (eventCount == count) { + element.removeEventListener("touchend", counterFunction, { + passive: true, + }); + resolve(); + } + }; + element.addEventListener("touchend", counterFunction, { passive: true }); + }); +} + +// This generates a touch-based pinch zoom-in gesture that is expected +// to succeed. It returns after APZ has completed the zoom and reaches the end +// of the transform. The focus point is expected to be in CSS coordinates +// relative to the document body. +async function pinchZoomInWithTouch(focusX, focusY) { + // Register the listener for the TransformEnd observer topic + let transformEndPromise = promiseTopic("APZ:TransformEnd"); + + // Dispatch all the touch events + pinchZoomInTouchSequence(focusX, focusY); + + // Wait for TransformEnd to fire. + await transformEndPromise; +} + +// This generates a touch-based pinch gesture that is expected to succeed +// and trigger an APZ:TransformEnd observer notification. +// It returns after that notification has been dispatched. +// The coordinates of touch events in `touchSequence` are expected to be +// in CSS coordinates relative to the document body. +async function synthesizeNativeTouchAndWaitForTransformEnd( + touchSequence, + touchIds +) { + // Register the listener for the TransformEnd observer topic + let transformEndPromise = promiseTopic("APZ:TransformEnd"); + + // Dispatch all the touch events + synthesizeNativeTouchSequences(document.body, touchSequence, null, touchIds); + + // Wait for TransformEnd to fire. + await transformEndPromise; +} + +// Returns a touch sequence for a pinch-zoom-out operation in the center +// of the visual viewport. The touch sequence returned is in CSS coordinates +// relative to the document body. +function pinchZoomOutTouchSequenceAtCenter() { + // Divide the half of visual viewport size by 8, then cause touch events + // starting from the 7th furthest away from the center towards the center. + const deltaX = window.visualViewport.width / 16; + const deltaY = window.visualViewport.height / 16; + const centerX = + window.visualViewport.pageLeft + window.visualViewport.width / 2; + const centerY = + window.visualViewport.pageTop + window.visualViewport.height / 2; + // prettier-ignore + var zoom_out = [ + [ { x: centerX - (deltaX * 6), y: centerY - (deltaY * 6) }, + { x: centerX + (deltaX * 6), y: centerY + (deltaY * 6) } ], + [ { x: centerX - (deltaX * 5), y: centerY - (deltaY * 5) }, + { x: centerX + (deltaX * 5), y: centerY + (deltaY * 5) } ], + [ { x: centerX - (deltaX * 4), y: centerY - (deltaY * 4) }, + { x: centerX + (deltaX * 4), y: centerY + (deltaY * 4) } ], + [ { x: centerX - (deltaX * 3), y: centerY - (deltaY * 3) }, + { x: centerX + (deltaX * 3), y: centerY + (deltaY * 3) } ], + [ { x: centerX - (deltaX * 2), y: centerY - (deltaY * 2) }, + { x: centerX + (deltaX * 2), y: centerY + (deltaY * 2) } ], + [ { x: centerX - (deltaX * 1), y: centerY - (deltaY * 1) }, + { x: centerX + (deltaX * 1), y: centerY + (deltaY * 1) } ], + ]; + return zoom_out; +} + +// This generates a touch-based pinch zoom-out gesture that is expected +// to succeed. It returns after APZ has completed the zoom and reaches the end +// of the transform. The touch inputs are directed to the center of the +// current visual viewport. +async function pinchZoomOutWithTouchAtCenter() { + var zoom_out = pinchZoomOutTouchSequenceAtCenter(); + var touchIds = [0, 1]; + await synthesizeNativeTouchAndWaitForTransformEnd(zoom_out, touchIds); +} diff --git a/gfx/layers/apz/test/mochitest/apz_test_utils.js b/gfx/layers/apz/test/mochitest/apz_test_utils.js new file mode 100644 index 0000000000..4e1f7012e4 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/apz_test_utils.js @@ -0,0 +1,1177 @@ +// Utilities for writing APZ tests using the framework added in bug 961289 + +// ---------------------------------------------------------------------- +// Functions that convert the APZ test data into a more usable form. +// Every place we have a WebIDL sequence whose elements are dictionaries +// with two elements, a key, and a value, we convert this into a JS +// object with a property for each key/value pair. (This is the structure +// we really want, but we can't express in directly in WebIDL.) +// ---------------------------------------------------------------------- + +// getHitTestConfig() expects apz_test_native_event_utils.js to be loaded as well. +/* import-globals-from apz_test_native_event_utils.js */ + +function convertEntries(entries) { + var result = {}; + for (var i = 0; i < entries.length; ++i) { + result[entries[i].key] = entries[i].value; + } + return result; +} + +// TODO: Clean up these rect-handling functions so that e.g. a rect returned +// by Element.getBoundingClientRect() Just Works with them. +function parseRect(str) { + var pieces = str.replace(/[()\s]+/g, "").split(","); + SimpleTest.is(pieces.length, 4, "expected string of form (x,y,w,h)"); + for (var i = 0; i < 4; i++) { + var eq = pieces[i].indexOf("="); + if (eq >= 0) { + pieces[i] = pieces[i].substring(eq + 1); + } + } + return { + x: parseInt(pieces[0]), + y: parseInt(pieces[1]), + w: parseInt(pieces[2]), + h: parseInt(pieces[3]), + }; +} + +// These functions expect rects with fields named x/y/w/h, such as +// that returned by parseRect(). +function rectContains(haystack, needle) { + return ( + haystack.x <= needle.x && + haystack.y <= needle.y && + haystack.x + haystack.w >= needle.x + needle.w && + haystack.y + haystack.h >= needle.y + needle.h + ); +} +function rectToString(rect) { + return "(" + rect.x + "," + rect.y + "," + rect.w + "," + rect.h + ")"; +} +function assertRectContainment( + haystackRect, + haystackDesc, + needleRect, + needleDesc +) { + SimpleTest.ok( + rectContains(haystackRect, needleRect), + haystackDesc + + " " + + rectToString(haystackRect) + + " should contain " + + needleDesc + + " " + + rectToString(needleRect) + ); +} + +function getPropertyAsRect(scrollFrames, scrollId, prop) { + SimpleTest.ok( + scrollId in scrollFrames, + "expected scroll frame data for scroll id " + scrollId + ); + var scrollFrameData = scrollFrames[scrollId]; + SimpleTest.ok( + "displayport" in scrollFrameData, + "expected a " + prop + " for scroll id " + scrollId + ); + var value = scrollFrameData[prop]; + return parseRect(value); +} + +function convertScrollFrameData(scrollFrames) { + var result = {}; + for (var i = 0; i < scrollFrames.length; ++i) { + result[scrollFrames[i].scrollId] = convertEntries(scrollFrames[i].entries); + } + return result; +} + +function convertBuckets(buckets) { + var result = {}; + for (var i = 0; i < buckets.length; ++i) { + result[buckets[i].sequenceNumber] = convertScrollFrameData( + buckets[i].scrollFrames + ); + } + return result; +} + +function convertTestData(testData) { + var result = {}; + result.paints = convertBuckets(testData.paints); + result.repaintRequests = convertBuckets(testData.repaintRequests); + return result; +} + +// Returns the last bucket that has at least one scrollframe. This +// is useful for skipping over buckets that are from empty transactions, +// because those don't contain any useful data. +function getLastNonemptyBucket(buckets) { + for (var i = buckets.length - 1; i >= 0; --i) { + if (buckets[i].scrollFrames.length > 0) { + return buckets[i]; + } + } + return null; +} + +// Takes something like "matrix(1, 0, 0, 1, 234.024, 528.29023)"" and returns a number array +function parseTransform(transform) { + return /matrix\((.*),(.*),(.*),(.*),(.*),(.*)\)/ + .exec(transform) + .slice(1) + .map(parseFloat); +} + +function isTransformClose(a, b, name) { + is( + a.length, + b.length, + `expected transforms ${a} and ${b} to be the same length` + ); + for (let i = 0; i < a.length; i++) { + ok(Math.abs(a[i] - b[i]) < 0.01, name); + } +} + +// Given APZ test data for a single paint on the compositor side, +// reconstruct the APZC tree structure from the 'parentScrollId' +// entries that were logged. More specifically, the subset of the +// APZC tree structure corresponding to the layer subtree for the +// content process that triggered the paint, is reconstructed (as +// the APZ test data only contains information abot this subtree). +function buildApzcTree(paint) { + // The APZC tree can potentially have multiple root nodes, + // so we invent a node that is the parent of all roots. + // This 'root' does not correspond to an APZC. + var root = { scrollId: -1, children: [] }; + for (let scrollId in paint) { + paint[scrollId].children = []; + paint[scrollId].scrollId = scrollId; + } + for (let scrollId in paint) { + var parentNode = null; + if ("hasNoParentWithSameLayersId" in paint[scrollId]) { + parentNode = root; + } else if ("parentScrollId" in paint[scrollId]) { + parentNode = paint[paint[scrollId].parentScrollId]; + } + parentNode.children.push(paint[scrollId]); + } + return root; +} + +// Given an APZC tree produced by buildApzcTree, return the RCD node in +// the tree, or null if there was none. +function findRcdNode(apzcTree) { + // isRootContent will be undefined or "1" + if (apzcTree.isRootContent) { + return apzcTree; + } + for (var i = 0; i < apzcTree.children.length; i++) { + var rcd = findRcdNode(apzcTree.children[i]); + if (rcd != null) { + return rcd; + } + } + return null; +} + +// Return whether an element whose id includes |elementId| has been layerized. +// Assumes |elementId| will be present in the content description for the +// element, and not in the content descriptions of other elements. +function isLayerized(elementId) { + var contentTestData = SpecialPowers.getDOMWindowUtils( + window + ).getContentAPZTestData(); + var nonEmptyBucket = getLastNonemptyBucket(contentTestData.paints); + ok(nonEmptyBucket != null, "expected at least one nonempty paint"); + var seqno = nonEmptyBucket.sequenceNumber; + contentTestData = convertTestData(contentTestData); + var paint = contentTestData.paints[seqno]; + for (var scrollId in paint) { + if ("contentDescription" in paint[scrollId]) { + if (paint[scrollId].contentDescription.includes(elementId)) { + return true; + } + } + } + return false; +} + +// Return a rect (or null) that holds the last known content-side displayport +// for a given element. (The element selection works the same way, and with +// the same assumptions as the isLayerized function above). +function getLastContentDisplayportFor(elementId) { + var contentTestData = SpecialPowers.getDOMWindowUtils( + window + ).getContentAPZTestData(); + var nonEmptyBucket = getLastNonemptyBucket(contentTestData.paints); + ok(nonEmptyBucket != null, "expected at least one nonempty paint"); + var seqno = nonEmptyBucket.sequenceNumber; + contentTestData = convertTestData(contentTestData); + var paint = contentTestData.paints[seqno]; + for (var scrollId in paint) { + if ("contentDescription" in paint[scrollId]) { + if (paint[scrollId].contentDescription.includes(elementId)) { + if ("displayport" in paint[scrollId]) { + return parseRect(paint[scrollId].displayport); + } + } + } + } + return null; +} + +// Return a promise that is resolved on the next rAF callback +function promiseFrame() { + return new Promise(resolve => { + window.requestAnimationFrame(resolve); + }); +} + +// Return a promise that is resolved on the next MozAfterPaint event +function promiseAfterPaint() { + return new Promise(resolve => { + window.addEventListener("MozAfterPaint", resolve, { once: true }); + }); +} + +function promiseApzRepaintsFlushed(aWindow = window) { + return new Promise(function(resolve, reject) { + var repaintDone = function() { + dump("PromiseApzRepaintsFlushed: APZ flush done\n"); + SpecialPowers.Services.obs.removeObserver( + repaintDone, + "apz-repaints-flushed" + ); + setTimeout(resolve, 0); + }; + SpecialPowers.Services.obs.addObserver(repaintDone, "apz-repaints-flushed"); + if (SpecialPowers.getDOMWindowUtils(aWindow).flushApzRepaints()) { + dump( + "PromiseApzRepaintsFlushed: Flushed APZ repaints, waiting for callback...\n" + ); + } else { + dump( + "PromiseApzRepaintsFlushed: Flushing APZ repaints was a no-op, triggering callback directly...\n" + ); + repaintDone(); + } + }); +} + +function flushApzRepaints(aCallback, aWindow = window) { + if (!aCallback) { + throw new Error("A callback must be provided!"); + } + promiseApzRepaintsFlushed(aWindow).then(aCallback); +} + +// Flush repaints, APZ pending repaints, and any repaints resulting from that +// flush. This is particularly useful if the test needs to reach some sort of +// "idle" state in terms of repaints. Usually just waiting for all paints +// followed by flushApzRepaints is sufficient to flush all APZ state back to +// the main thread, but it can leave a paint scheduled which will get triggered +// at some later time. For tests that specifically test for painting at +// specific times, this method is the way to go. Even if in doubt, this is the +// preferred method as the extra step is "safe" and shouldn't interfere with +// most tests. +function waitForApzFlushedRepaints(aCallback) { + // First flush the main-thread paints and send transactions to the APZ + promiseAllPaintsDone() + // Then flush the APZ to make sure any repaint requests have been sent + // back to the main thread. Note that we need a wrapper function around + // promiseApzRepaintsFlushed otherwise the rect produced by + // promiseAllPaintsDone gets passed to it as the window parameter. + .then(() => promiseApzRepaintsFlushed()) + // Then flush the main-thread again to process the repaint requests. + // Once this is done, we should be in a stable state with nothing + // pending, so we can trigger the callback. + .then(promiseAllPaintsDone) + // Then allow the callback to be triggered. + .then(aCallback); +} + +// Same as waitForApzFlushedRepaints, but in async form. +async function promiseApzFlushedRepaints() { + await promiseAllPaintsDone(); + await promiseApzRepaintsFlushed(); + await promiseAllPaintsDone(); +} + +// This function takes a set of subtests to run one at a time in new top-level +// windows, and returns a Promise that is resolved once all the subtests are +// done running. +// +// The aSubtests array is an array of objects with the following keys: +// file: required, the filename of the subtest. +// prefs: optional, an array of arrays containing key-value prefs to set. +// dp_suppression: optional, a boolean on whether or not to respect displayport +// suppression during the test. +// onload: optional, a function that will be registered as a load event listener +// for the child window that will hold the subtest. the function will be +// passed exactly one argument, which will be the child window. +// An example of an array is: +// aSubtests = [ +// { 'file': 'test_file_name.html' }, +// { 'file': 'test_file_2.html', 'prefs': [['pref.name', true], ['other.pref', 1000]], 'dp_suppression': false } +// { 'file': 'file_3.html', 'onload': function(w) { w.subtestDone(); } } +// ]; +// +// Each subtest should call one of the subtestDone() or subtestFailed() +// functions when it is done, to indicate that the window should be torn +// down and the next test should run. +// These functions are injected into the subtest's window by this +// function prior to loading the subtest. For convenience, the |is| and |ok| +// functions provided by SimpleTest are also mapped into the subtest's window. +// For other things from the parent, the subtest can use window.opener. +// to access objects. +function runSubtestsSeriallyInFreshWindows(aSubtests) { + return new Promise(function(resolve, reject) { + var testIndex = -1; + var w = null; + + // If the "apz.subtest" pref has been set, only a single subtest whose name matches + // the pref's value (if any) will be run. + var onlyOneSubtest = SpecialPowers.getCharPref( + "apz.subtest", + /* default = */ "" + ); + + function advanceSubtestExecutionWithFailure(msg) { + SimpleTest.ok(false, msg); + advanceSubtestExecution(); + } + + function advanceSubtestExecution() { + var test = aSubtests[testIndex]; + if (w) { + // Run any cleanup functions registered in the subtest + // Guard against the subtest not loading apz_test_utils.js + if (w.ApzCleanup) { + w.ApzCleanup.execute(); + } + if (typeof test.dp_suppression != "undefined") { + // We modified the suppression when starting the test, so now undo that. + SpecialPowers.getDOMWindowUtils(window).respectDisplayPortSuppression( + !test.dp_suppression + ); + } + if (test.prefs) { + // We pushed some prefs for this test, pop them, and re-invoke + // advanceSubtestExecution() after that's been processed + SpecialPowers.popPrefEnv(function() { + w.close(); + w = null; + advanceSubtestExecution(); + }); + return; + } + + w.close(); + } + + testIndex++; + if (testIndex >= aSubtests.length) { + resolve(); + return; + } + + test = aSubtests[testIndex]; + + let recognizedProps = ["file", "prefs", "dp_suppression", "onload"]; + for (let prop in test) { + if (!recognizedProps.includes(prop)) { + SimpleTest.ok( + false, + "Subtest " + test.file + " has unrecognized property '" + prop + "'" + ); + setTimeout(function() { + advanceSubtestExecution(); + }, 0); + return; + } + } + + if (onlyOneSubtest && onlyOneSubtest != test.file) { + SimpleTest.ok( + true, + "Skipping " + + test.file + + " because only " + + onlyOneSubtest + + " is being run" + ); + setTimeout(function() { + advanceSubtestExecution(); + }, 0); + return; + } + + SimpleTest.ok(true, "Starting subtest " + test.file); + + if (typeof test.dp_suppression != "undefined") { + // Normally during a test, the displayport will get suppressed during page + // load, and unsuppressed at a non-deterministic time during the test. The + // unsuppression can trigger a repaint which interferes with the test, so + // to avoid that we can force the displayport to be unsuppressed for the + // entire test which is more deterministic. + SpecialPowers.getDOMWindowUtils(window).respectDisplayPortSuppression( + test.dp_suppression + ); + } + + function spawnTest(aFile) { + w = window.open("", "_blank"); + w.subtestDone = advanceSubtestExecution; + w.subtestFailed = advanceSubtestExecutionWithFailure; + w.isApzSubtest = true; + w.SimpleTest = SimpleTest; + w.dump = function(msg) { + return dump(aFile + " | " + msg); + }; + w.is = function(a, b, msg) { + return is(a, b, aFile + " | " + msg); + }; + w.isnot = function(a, b, msg) { + return isnot(a, b, aFile + " | " + msg); + }; + w.isfuzzy = function(a, b, eps, msg) { + return isfuzzy(a, b, eps, aFile + " | " + msg); + }; + w.ok = function(cond, msg) { + arguments[1] = aFile + " | " + msg; + // Forward all arguments to SimpleTest.ok where we will check that ok() was + // called with at most 2 arguments. + return SimpleTest.ok.apply(SimpleTest, arguments); + }; + w.todo_is = function(a, b, msg) { + return todo_is(a, b, aFile + " | " + msg); + }; + w.todo = function(cond, msg) { + return todo(cond, aFile + " | " + msg); + }; + if (test.onload) { + w.addEventListener( + "load", + function(e) { + test.onload(w); + }, + { once: true } + ); + } + var subtestUrl = + location.href.substring(0, location.href.lastIndexOf("/") + 1) + + aFile; + function urlResolves(url) { + var request = new XMLHttpRequest(); + request.open("GET", url, false); + request.send(); + return request.status !== 404; + } + if (!urlResolves(subtestUrl)) { + SimpleTest.ok( + false, + "Subtest URL " + + subtestUrl + + " does not resolve. " + + "Be sure it's present in the support-files section of mochitest.ini." + ); + reject(); + return undefined; + } + w.location = subtestUrl; + return w; + } + + if (test.prefs) { + // Got some prefs for this subtest, push them + SpecialPowers.pushPrefEnv({ set: test.prefs }, function() { + w = spawnTest(test.file); + }); + } else { + w = spawnTest(test.file); + } + } + + advanceSubtestExecution(); + }).catch(function(e) { + SimpleTest.ok(false, "Error occurred while running subtests: " + e); + }); +} + +function pushPrefs(prefs) { + return SpecialPowers.pushPrefEnv({ set: prefs }); +} + +async function waitUntilApzStable() { + if (!SpecialPowers.isMainProcess()) { + // We use this waitUntilApzStable function during test initialization + // and for those scenarios we want to flush the parent-process layer + // tree to the compositor and wait for that as well. That way we know + // that not only is the content-process layer tree ready in the compositor, + // the parent-process layer tree in the compositor has the appropriate + // RefLayer pointing to the content-process layer tree. + + // Sadly this helper function cannot reuse any code from other places because + // it must be totally self-contained to be shipped over to the parent process. + /* eslint-env mozilla/frame-script */ + function parentProcessFlush() { + function apzFlush() { + const { Services } = ChromeUtils.import( + "resource://gre/modules/Services.jsm" + ); + var topWin = Services.wm.getMostRecentWindow("navigator:browser"); + if (!topWin) { + topWin = Services.wm.getMostRecentWindow("navigator:geckoview"); + } + var topUtils = topWin.windowUtils; + + var repaintDone = function() { + dump("WaitUntilApzStable: APZ flush done in parent proc\n"); + Services.obs.removeObserver(repaintDone, "apz-repaints-flushed"); + // send message back to content process + sendAsyncMessage("apz-flush-done", null); + }; + var flushRepaint = function() { + if (topUtils.isMozAfterPaintPending) { + topWin.addEventListener("MozAfterPaint", flushRepaint, { + once: true, + }); + return; + } + + Services.obs.addObserver(repaintDone, "apz-repaints-flushed"); + if (topUtils.flushApzRepaints()) { + dump( + "WaitUntilApzStable: flushed APZ repaints in parent proc, waiting for callback...\n" + ); + } else { + dump( + "WaitUntilApzStable: flushing APZ repaints in parent proc was a no-op, triggering callback directly...\n" + ); + repaintDone(); + } + }; + + // Flush APZ repaints, but wait until all the pending paints have been + // sent. + flushRepaint(); + } + function cleanup() { + removeMessageListener("apz-flush", apzFlush); + removeMessageListener("cleanup", cleanup); + } + addMessageListener("apz-flush", apzFlush); + addMessageListener("cleanup", cleanup); + } + + // This is the first time waitUntilApzStable is being called, do initialization + if (typeof waitUntilApzStable.chromeHelper == "undefined") { + waitUntilApzStable.chromeHelper = SpecialPowers.loadChromeScript( + parentProcessFlush + ); + ApzCleanup.register(() => { + waitUntilApzStable.chromeHelper.sendAsyncMessage("cleanup", null); + waitUntilApzStable.chromeHelper.destroy(); + delete waitUntilApzStable.chromeHelper; + }); + } + + // Actually trigger the parent-process flush and wait for it to finish + waitUntilApzStable.chromeHelper.sendAsyncMessage("apz-flush", null); + await waitUntilApzStable.chromeHelper.promiseOneMessage("apz-flush-done"); + dump("WaitUntilApzStable: got apz-flush-done in child proc\n"); + } + + await SimpleTest.promiseFocus(window); + dump("WaitUntilApzStable: done promiseFocus\n"); + await promiseAllPaintsDone(); + dump("WaitUntilApzStable: done promiseAllPaintsDone\n"); + await promiseApzRepaintsFlushed(); + dump("WaitUntilApzStable: all done\n"); +} + +// This function returns a promise that is resolved after at least one paint +// has been sent and processed by the compositor. This function can force +// such a paint to happen if none are pending. This is useful to run after +// the waitUntilApzStable() but before reading the compositor-side APZ test +// data, because the test data for the content layers id only gets populated +// on content layer tree updates *after* the root layer tree has a RefLayer +// pointing to the contnet layer tree. waitUntilApzStable itself guarantees +// that the root layer tree is pointing to the content layer tree, but does +// not guarantee the subsequent paint; this function does that job. +async function forceLayerTreeToCompositor() { + // Modify a style property to force a layout flush + document.body.style.boxSizing = "border-box"; + var utils = SpecialPowers.getDOMWindowUtils(window); + if (!utils.isMozAfterPaintPending) { + dump("Forcing a paint since none was pending already...\n"); + var testMode = utils.isTestControllingRefreshes; + utils.advanceTimeAndRefresh(0); + if (!testMode) { + utils.restoreNormalRefresh(); + } + } + await promiseAllPaintsDone(null, true); + await promiseApzRepaintsFlushed(); +} + +function isApzEnabled() { + var enabled = SpecialPowers.getDOMWindowUtils(window).asyncPanZoomEnabled; + if (!enabled) { + // All tests are required to have at least one assertion. Since APZ is + // disabled, and the main test is presumably not going to run, we stick in + // a dummy assertion here to keep the test passing. + SimpleTest.ok(true, "APZ is not enabled; this test will be skipped"); + } + return enabled; +} + +function isKeyApzEnabled() { + return isApzEnabled() && SpecialPowers.getBoolPref("apz.keyboard.enabled"); +} + +// Despite what this function name says, this does not *directly* run the +// provided continuation testFunction. Instead, it returns a function that +// can be used to run the continuation. The extra level of indirection allows +// it to be more easily added to a promise chain, like so: +// waitUntilApzStable().then(runContinuation(myTest)); +// +// If you want to run the continuation directly, outside of a promise chain, +// you can invoke the return value of this function, like so: +// runContinuation(myTest)(); +function runContinuation(testFunction) { + // We need to wrap this in an extra function, so that the call site can + // be more readable without running the promise too early. In other words, + // if we didn't have this extra function, the promise would start running + // during construction of the promise chain, concurrently with the first + // promise in the chain. + return function() { + return new Promise(function(resolve, reject) { + var testContinuation = null; + + function driveTest() { + if (!testContinuation) { + testContinuation = testFunction(driveTest); + } + var ret = testContinuation.next(); + if (ret.done) { + resolve(); + } + } + + try { + driveTest(); + } catch (ex) { + SimpleTest.ok( + false, + "APZ test continuation failed with exception: " + ex + ); + } + }); + }; +} + +// Take a snapshot of the given rect, *including compositor transforms* (i.e. +// includes async scroll transforms applied by APZ). If you don't need the +// compositor transforms, you can probably get away with using +// SpecialPowers.snapshotWindowWithOptions or one of the friendlier wrappers. +// The rect provided is expected to be relative to the screen, for example as +// returned by rectRelativeToScreen in apz_test_native_event_utils.js. +// Example usage: +// var snapshot = getSnapshot(rectRelativeToScreen(myDiv)); +// which will take a snapshot of the 'myDiv' element. Note that if part of the +// element is obscured by other things on top, the snapshot will include those +// things. If it is clipped by a scroll container, the snapshot will include +// that area anyway, so you will probably get parts of the scroll container in +// the snapshot. If the rect extends outside the browser window then the +// results are undefined. +// The snapshot is returned in the form of a data URL. +function getSnapshot(rect) { + function parentProcessSnapshot() { + addMessageListener("snapshot", function(parentRect) { + const { Services } = ChromeUtils.import( + "resource://gre/modules/Services.jsm" + ); + var topWin = Services.wm.getMostRecentWindow("navigator:browser"); + if (!topWin) { + topWin = Services.wm.getMostRecentWindow("navigator:geckoview"); + } + + // reposition the rect relative to the top-level browser window + parentRect = JSON.parse(parentRect); + parentRect.x -= topWin.mozInnerScreenX; + parentRect.y -= topWin.mozInnerScreenY; + + // take the snapshot + var canvas = topWin.document.createElementNS( + "http://www.w3.org/1999/xhtml", + "canvas" + ); + canvas.width = parentRect.w; + canvas.height = parentRect.h; + var ctx = canvas.getContext("2d"); + ctx.drawWindow( + topWin, + parentRect.x, + parentRect.y, + parentRect.w, + parentRect.h, + "rgb(255,255,255)", + ctx.DRAWWINDOW_DRAW_VIEW | + ctx.DRAWWINDOW_USE_WIDGET_LAYERS | + ctx.DRAWWINDOW_DRAW_CARET + ); + return canvas.toDataURL(); + }); + } + + if (typeof getSnapshot.chromeHelper == "undefined") { + // This is the first time getSnapshot is being called; do initialization + getSnapshot.chromeHelper = SpecialPowers.loadChromeScript( + parentProcessSnapshot + ); + ApzCleanup.register(function() { + getSnapshot.chromeHelper.destroy(); + }); + } + + return getSnapshot.chromeHelper.sendQuery("snapshot", JSON.stringify(rect)); +} + +// Takes the document's query string and parses it, assuming the query string +// is composed of key-value pairs where the value is in JSON format. The object +// returned contains the various values indexed by their respective keys. In +// case of duplicate keys, the last value be used. +// Examples: +// ?key="value"&key2=false&key3=500 +// produces { "key": "value", "key2": false, "key3": 500 } +// ?key={"x":0,"y":50}&key2=[1,2,true] +// produces { "key": { "x": 0, "y": 0 }, "key2": [1, 2, true] } +function getQueryArgs() { + var args = {}; + if (location.search.length > 0) { + var params = location.search.substr(1).split("&"); + for (var p of params) { + var [k, v] = p.split("="); + args[k] = JSON.parse(v); + } + } + return args; +} + +// Return a function that returns a promise to create a script element with the +// given URI and append it to the head of the document in the given window. +// As with runContinuation(), the extra function wrapper is for convenience +// at the call site, so that this can be chained with other promises: +// waitUntilApzStable().then(injectScript('foo')) +// .then(injectScript('bar')); +// If you want to do the injection right away, run the function returned by +// this function: +// injectScript('foo')(); +function injectScript(aScript, aWindow = window) { + return function() { + return new Promise(function(resolve, reject) { + var e = aWindow.document.createElement("script"); + e.type = "text/javascript"; + e.onload = function() { + resolve(); + }; + e.onerror = function() { + dump("Script [" + aScript + "] errored out\n"); + reject(); + }; + e.src = aScript; + aWindow.document.getElementsByTagName("head")[0].appendChild(e); + }); + }; +} + +// Compute some configuration information used for hit testing. +// The computed information is cached to avoid recomputing it +// each time this function is called. +// The computed information is an object with three fields: +// utils: the nsIDOMWindowUtils instance for this window +// isWebRender: true if WebRender is enabled +// isWindow: true if the platform is Windows +function getHitTestConfig() { + if (!("hitTestConfig" in window)) { + var utils = SpecialPowers.getDOMWindowUtils(window); + var isWebRender = utils.layerManagerType == "WebRender"; + var isWindows = getPlatform() == "windows"; + window.hitTestConfig = { utils, isWebRender, isWindows }; + } + return window.hitTestConfig; +} + +// Compute the coordinates of the center of the given element. The argument +// can either be a string (the id of the element desired) or the element +// itself. +function centerOf(element) { + if (typeof element === "string") { + element = document.getElementById(element); + } + var bounds = element.getBoundingClientRect(); + return { x: bounds.x + bounds.width / 2, y: bounds.y + bounds.height / 2 }; +} + +// Peform a compositor hit test at the given point and return the result. +// The returned object has two fields: +// hitInfo: a combination of APZHitResultFlags +// scrollId: the view-id of the scroll frame that was hit +function hitTest(point) { + var utils = getHitTestConfig().utils; + dump("Hit-testing point (" + point.x + ", " + point.y + ")\n"); + utils.sendMouseEvent( + "MozMouseHittest", + point.x, + point.y, + 0, + 0, + 0, + true, + 0, + 0, + true, + true + ); + var data = utils.getCompositorAPZTestData(); + ok( + data.hitResults.length >= 1, + "Expected at least one hit result in the APZTestData" + ); + var result = data.hitResults[data.hitResults.length - 1]; + return { + hitInfo: result.hitResult, + scrollId: result.scrollId, + layersId: result.layersId, + }; +} + +// Returns a canonical stringification of the hitInfo bitfield. +function hitInfoToString(hitInfo) { + var strs = []; + for (var flag in APZHitResultFlags) { + if ((hitInfo & APZHitResultFlags[flag]) != 0) { + strs.push(flag); + } + } + if (strs.length == 0) { + return "INVISIBLE"; + } + strs.sort(function(a, b) { + return APZHitResultFlags[a] - APZHitResultFlags[b]; + }); + return strs.join(" | "); +} + +// Takes an object returned by hitTest, along with the expected values, and +// asserts that they match. Notably, it uses hitInfoToString to provide a +// more useful message for the case that the hit info doesn't match +function checkHitResult( + hitResult, + expectedHitInfo, + expectedScrollId, + expectedLayersId, + desc +) { + is( + hitInfoToString(hitResult.hitInfo), + hitInfoToString(expectedHitInfo), + desc + " hit info" + ); + is(hitResult.scrollId, expectedScrollId, desc + " scrollid"); + is(hitResult.layersId, expectedLayersId, desc + " layersid"); +} + +// Symbolic constants used by hitTestScrollbar(). +var ScrollbarTrackLocation = { + START: 1, + END: 2, +}; +var LayerState = { + ACTIVE: 1, + INACTIVE: 2, +}; + +// Perform a hit test on the scrollbar(s) of a scroll frame. +// This function takes a single argument which is expected to be +// an object with the following fields: +// element: The scroll frame to perform the hit test on. +// directions: The direction(s) of scrollbars to test. +// If directions.vertical is true, the vertical scrollbar will be tested. +// If directions.horizontal is true, the horizontal scrollbar will be tested. +// Both may be true in a single call (in which case two tests are performed). +// expectedScrollId: The scroll id that is expected to be hit. +// expectedLayersId: The layers id that is expected to be hit. +// trackLocation: One of ScrollbarTrackLocation.{START, END}. +// Determines which end of the scrollbar track is targeted. +// expectThumb: Whether the scrollbar thumb is expected to be present +// at the targeted end of the scrollbar track. +// layerState: Whether the scroll frame is active or inactive. +// The function performs the hit tests and asserts that the returned +// hit test information is consistent with the passed parameters. +// There is no return value. +// Tests that use this function must set the pref +// "layout.scrollbars.always-layerize-track". +function hitTestScrollbar(params) { + var config = getHitTestConfig(); + + var elem = params.element; + + var boundingClientRect = elem.getBoundingClientRect(); + + var verticalScrollbarWidth = boundingClientRect.width - elem.clientWidth; + var horizontalScrollbarHeight = boundingClientRect.height - elem.clientHeight; + + // On windows, the scrollbar tracks have buttons on the end. When computing + // coordinates for hit-testing we need to account for this. We assume the + // buttons are square, and so can use the scrollbar width/height to estimate + // the size of the buttons + var scrollbarArrowButtonHeight = config.isWindows + ? verticalScrollbarWidth + : 0; + var scrollbarArrowButtonWidth = config.isWindows + ? horizontalScrollbarHeight + : 0; + + // Compute the expected hit result flags. + // The direction flag (APZHitResultFlags.SCROLLBAR_VERTICAL) is added in + // later, for the vertical test only. + // The APZHitResultFlags.SCROLLBAR flag will be present regardless of whether + // the layer is active or inactive because we force layerization of scrollbar + // tracks. Unfortunately not forcing the layerization results in different + // behaviour on different platforms which makes testing harder. + var expectedHitInfo = APZHitResultFlags.VISIBLE | APZHitResultFlags.SCROLLBAR; + if (params.expectThumb) { + // The thumb has listeners which are APZ-aware. With WebRender we are able + // to losslessly propagate this flag to APZ, but with non-WebRender the area + // ends up in the mDispatchToContentRegion which we then convert back to + // a IRREGULAR_AREA flag. This still works correctly since IRREGULAR_AREA + // will fall back to the main thread for everything. + if (config.isWebRender) { + expectedHitInfo |= APZHitResultFlags.APZ_AWARE_LISTENERS; + if (params.layerState == LayerState.INACTIVE) { + expectedHitInfo |= APZHitResultFlags.INACTIVE_SCROLLFRAME; + } + } else { + expectedHitInfo |= APZHitResultFlags.IRREGULAR_AREA; + } + // We do not generate the layers for thumbs on inactive scrollframes. + if (params.layerState == LayerState.ACTIVE) { + expectedHitInfo |= APZHitResultFlags.SCROLLBAR_THUMB; + } + } + + var scrollframeMsg = + params.layerState == LayerState.ACTIVE + ? "active scrollframe" + : "inactive scrollframe"; + + // Hit-test the targeted areas, assuming we don't have overlay scrollbars + // with zero dimensions. + if (params.directions.vertical && verticalScrollbarWidth > 0) { + var verticalScrollbarPoint = { + x: boundingClientRect.right - verticalScrollbarWidth / 2, + y: + params.trackLocation == ScrollbarTrackLocation.START + ? boundingClientRect.y + scrollbarArrowButtonHeight + 5 + : boundingClientRect.bottom - + horizontalScrollbarHeight - + scrollbarArrowButtonHeight - + 5, + }; + checkHitResult( + hitTest(verticalScrollbarPoint), + expectedHitInfo | APZHitResultFlags.SCROLLBAR_VERTICAL, + params.expectedScrollId, + params.expectedLayersId, + scrollframeMsg + " - vertical scrollbar" + ); + } + + if (params.directions.horizontal && horizontalScrollbarHeight > 0) { + var horizontalScrollbarPoint = { + x: + params.trackLocation == ScrollbarTrackLocation.START + ? boundingClientRect.x + scrollbarArrowButtonWidth + 5 + : boundingClientRect.right - + verticalScrollbarWidth - + scrollbarArrowButtonWidth - + 5, + y: boundingClientRect.bottom - horizontalScrollbarHeight / 2, + }; + checkHitResult( + hitTest(horizontalScrollbarPoint), + expectedHitInfo, + params.expectedScrollId, + params.expectedLayersId, + scrollframeMsg + " - horizontal scrollbar" + ); + } +} + +// Return a list of prefs for the given test identifier. +function getPrefs(ident) { + switch (ident) { + case "TOUCH_EVENTS:PAN": + return [ + // Dropping the touch slop to 0 makes the tests easier to write because + // we can just do a one-pixel drag to get over the pan threshold rather + // than having to hard-code some larger value. + ["apz.touch_start_tolerance", "0.0"], + // The touchstart from the drag can turn into a long-tap if the touch-move + // events get held up. Try to prevent that by making long-taps require + // a 10 second hold. Note that we also cannot enable chaos mode on this + // test for this reason, since chaos mode can cause the long-press timer + // to fire sooner than the pref dictates. + ["ui.click_hold_context_menus.delay", 10000], + // The subtests in this test do touch-drags to pan the page, but we don't + // want those pans to turn into fling animations, so we increase the + // fling min velocity requirement absurdly high. + ["apz.fling_min_velocity_threshold", "10000"], + // The helper_div_pan's div gets a displayport on scroll, but if the + // test takes too long the displayport can expire before the new scroll + // position is synced back to the main thread. So we disable displayport + // expiry for these tests. + ["apz.displayport_expiry_ms", 0], + // We need to disable touch resampling during these tests because we + // rely on touch move events being processed without delay. Touch + // resampling only processes them once vsync fires. + ["android.touch_resampling.enabled", false], + ]; + case "TOUCH_ACTION": + return [ + ...getPrefs("TOUCH_EVENTS:PAN"), + ["layout.css.touch_action.enabled", true], + ["apz.test.fails_with_native_injection", getPlatform() == "windows"], + ]; + default: + return []; + } +} + +var ApzCleanup = { + _cleanups: [], + + register(func) { + if (this._cleanups.length == 0) { + if (!window.isApzSubtest) { + SimpleTest.registerCleanupFunction(this.execute.bind(this)); + } // else ApzCleanup.execute is called from runSubtestsSeriallyInFreshWindows + } + this._cleanups.push(func); + }, + + execute() { + while (this._cleanups.length > 0) { + var func = this._cleanups.pop(); + try { + func(); + } catch (ex) { + SimpleTest.ok( + false, + "Subtest cleanup function [" + + func.toString() + + "] threw exception [" + + ex + + "] on page [" + + location.href + + "]" + ); + } + } + }, +}; + +/** + * Returns a promise that will resolve if `eventTarget` receives an event of the + * given type that passes the given filter. Only the first matching message is + * used. The filter must be a function (or null); it is called with the event + * object and the call must return true to resolve the promise. + */ +function promiseOneEvent(eventTarget, eventType, filter) { + return new Promise((resolve, reject) => { + eventTarget.addEventListener(eventType, function listener(e) { + let success = false; + if (filter == null) { + success = true; + } else if (typeof filter == "function") { + try { + success = filter(e); + } catch (ex) { + dump( + `ERROR: Filter passed to promiseOneEvent threw exception: ${ex}\n` + ); + reject(); + return; + } + } else { + dump( + "ERROR: Filter passed to promiseOneEvent was neither null nor a function\n" + ); + reject(); + return; + } + if (success) { + eventTarget.removeEventListener(eventType, listener); + resolve(e); + } + }); + }); +} + +function visualViewportAsZoomedRect() { + let vv = window.visualViewport; + return { + x: vv.pageLeft, + y: vv.pageTop, + w: vv.width, + h: vv.height, + z: vv.scale, + }; +} + +// Pulls the latest compositor APZ test data and checks to see if the +// scroller with id `scrollerId` was checkerboarding. It also ensures that +// a scroller with id `scrollerId` was actually found in the test data. +// This function requires that "apz.test.logging_enabled" be set to true, +// in order for the test data to be logged. +function assertNotCheckerboarded(utils, scrollerId, msgPrefix) { + utils.advanceTimeAndRefresh(0); + var data = utils.getCompositorAPZTestData(); + //dump(JSON.stringify(data, null, 4)); + var found = false; + for (apzcData of data.additionalData) { + if (apzcData.key == scrollerId) { + var checkerboarding = apzcData.value + .split(",") + .includes("checkerboarding"); + ok(!checkerboarding, `${msgPrefix}: scroller is not checkerboarding`); + found = true; + } + } + ok(found, `${msgPrefix}: Found the scroller in the APZ data`); + utils.restoreNormalRefresh(); +} + +function waitToClearOutAnyPotentialScrolls(aWindow) { + return new Promise(resolve => { + aWindow.requestAnimationFrame(() => { + aWindow.requestAnimationFrame(() => { + flushApzRepaints(() => { + aWindow.requestAnimationFrame(() => { + aWindow.requestAnimationFrame(resolve); + }); + }, aWindow); + }); + }); + }); +} diff --git a/gfx/layers/apz/test/mochitest/browser.ini b/gfx/layers/apz/test/mochitest/browser.ini new file mode 100644 index 0000000000..52f6627e43 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/browser.ini @@ -0,0 +1,33 @@ +[DEFAULT] +prefs = + apz.allow_zooming=true +support-files = + apz_test_native_event_utils.js + apz_test_utils.js + +[browser_test_group_fission.js] +skip-if = (os == 'win' && bits == 32) # Some subtests fail intermittently on Win7. +support-files = + FissionTestHelperParent.jsm + FissionTestHelperChild.jsm + helper_fission_*.* + !/dom/animation/test/testcommon.js +[browser_test_select_zoom.js] +skip-if = (os == 'win') # bug 1495580 +support-files = + helper_test_select_zoom.html +[browser_test_background_tab_scroll.js] +skip-if = (toolkit == 'android') # wheel events not supported on mobile +support-files = + helper_background_tab_scroll.html +[browser_test_reset_scaling_zoom.js] +support-files = + helper_test_reset_scaling_zoom.html +[browser_test_scrollbar_in_extension_popup_window.js] +support-files = + !/browser/components/extensions/test/browser/head.js + !/browser/components/extensions/test/browser/head_browserAction.js +[browser_test_scrolling_in_extension_popup_window.js] +support-files = + !/browser/components/extensions/test/browser/head.js + !/browser/components/extensions/test/browser/head_browserAction.js diff --git a/gfx/layers/apz/test/mochitest/browser_test_background_tab_scroll.js b/gfx/layers/apz/test/mochitest/browser_test_background_tab_scroll.js new file mode 100644 index 0000000000..6b1f62d85e --- /dev/null +++ b/gfx/layers/apz/test/mochitest/browser_test_background_tab_scroll.js @@ -0,0 +1,70 @@ +add_task(async function test_main() { + // Load page in the background. This will cause the first-paint of the + // tab (which has ScrollPositionUpdate instances) to get sent to the + // compositor, but the parent process RefLayer won't be pointing to this + // tab so APZ never sees the ScrollPositionUpdate instances. + + let url = + "http://mochi.test:8888/browser/gfx/layers/apz/test/mochitest/helper_background_tab_scroll.html#scrolltarget"; + let backgroundTab = BrowserTestUtils.addTab(gBrowser, url); + let browser = backgroundTab.linkedBrowser; + await BrowserTestUtils.browserLoaded(browser, false, url); + dump("Done loading background tab\n"); + + // Switch to the foreground, to let the APZ tree get built. + await BrowserTestUtils.switchTab(gBrowser, backgroundTab); + dump("Switched background tab to foreground\n"); + + // Verify main-thread scroll position is where we expect + let scrollPos = await ContentTask.spawn(browser, null, function() { + return content.window.scrollY; + }); + is(scrollPos, 5000, "Expected background tab to be at scroll pos 5000"); + + // Trigger an APZ-side scroll via native wheel event, followed by some code + // to ensure APZ's repaint requests to arrive at the main-thread. If + // things are working properly, the main thread will accept the repaint + // requests and update the main-thread scroll position. If the APZ side + // is sending incorrect scroll generations in the repaint request, then + // the main thread will fail to clear the main-thread scroll origin (which + // was set by the scroll to the #scrolltarget anchor), and so will not + // accept APZ's scroll position updates. + let contentScrollFunction = async function() { + return new Promise(resolve => { + content.window.wrappedJSObject.synthesizeNativeWheelAndWaitForWheelEvent( + content.window, + 100, + 100, + 0, + 200, + () => { + // Advance some/all frames of the APZ wheel animation + let utils = content.window.SpecialPowers.getDOMWindowUtils( + content.window + ); + for (var i = 0; i < 10; i++) { + utils.advanceTimeAndRefresh(16); + } + utils.restoreNormalRefresh(); + // Flush pending APZ repaints, then read the main-thread scroll + // position + content.window.wrappedJSObject.flushApzRepaints(() => { + resolve(content.window.scrollY); + }, content.window); + } + ); + }); + }; + scrollPos = await ContentTask.spawn(browser, null, contentScrollFunction); + + // Verify main-thread scroll position has changed + ok( + scrollPos < 5000, + `Expected background tab to have scrolled up, is at ${scrollPos}` + ); + + // Cleanup + let tabClosed = BrowserTestUtils.waitForTabClosing(backgroundTab); + BrowserTestUtils.removeTab(backgroundTab); + await tabClosed; +}); diff --git a/gfx/layers/apz/test/mochitest/browser_test_group_fission.js b/gfx/layers/apz/test/mochitest/browser_test_group_fission.js new file mode 100644 index 0000000000..909420332c --- /dev/null +++ b/gfx/layers/apz/test/mochitest/browser_test_group_fission.js @@ -0,0 +1,154 @@ +add_task(async function setup_pref() { + await SpecialPowers.pushPrefEnv({ + set: [ + // To avoid throttling requestAnimationFrame callbacks in invisible + // iframes + ["layout.throttled_frame_rate", 60], + ["dom.animations-api.getAnimations.enabled", true], + ["dom.animations-api.timelines.enabled", true], + // Next two prefs are needed for hit-testing to work + ["test.events.async.enabled", true], + ["apz.test.logging_enabled", true], + ], + }); +}); + +add_task(async function test_main() { + function httpURL(filename) { + let chromeURL = getRootDirectory(gTestPath) + filename; + return chromeURL.replace( + "chrome://mochitests/content/", + "http://mochi.test:8888/" + ); + } + + var utils = SpecialPowers.getDOMWindowUtils(window); + var isWebRender = utils.layerManagerType == "WebRender"; + + // Each of these subtests is a dictionary that contains: + // file (required): filename of the subtest that will get opened in a new tab + // in the top-level fission-enabled browser window. + // setup (optional): function that takes the top-level fission window and is + // run once after the subtest is loaded but before it is started. + var subtests = [ + { file: "helper_fission_basic.html" }, + { file: "helper_fission_transforms.html" }, + { file: "helper_fission_scroll_oopif.html" }, + { + file: "helper_fission_event_region_override.html", + setup(win) { + win.document.addEventListener("wheel", e => e.preventDefault(), { + once: true, + passive: false, + }); + }, + }, + { file: "helper_fission_animation_styling_in_oopif.html" }, + { file: "helper_fission_force_empty_hit_region.html" }, + { file: "helper_fission_touch.html" }, + { + file: "helper_fission_tap.html", + prefs: [["apz.max_tap_time", 10000]], + }, + { file: "helper_fission_inactivescroller_under_oopif.html" }, + { + file: "helper_fission_tap_on_zoomed.html", + prefs: [["apz.max_tap_time", 10000]], + }, + { + file: "helper_fission_tap_in_nested_iframe_on_zoomed.html", + prefs: [["apz.max_tap_time", 10000]], + }, + // add additional tests here + ]; + if (isWebRender) { + subtests = subtests.concat([ + // add WebRender-specific tests here + ]); + } else { + subtests = subtests.concat([ + // Bug 1576514: On WebRender this test casues an assertion. + { + file: "helper_fission_animation_styling_in_transformed_oopif.html", + }, + ]); + } + + // ccov builds run slower and need longer, so let's scale up the timeout + // by the number of tests we're running. + requestLongerTimeout(subtests.length); + + let fissionWindow = await BrowserTestUtils.openNewBrowserWindow({ + fission: true, + }); + + // We import the JSM here so that we can install functions on the class + // below. + const { FissionTestHelperParent } = ChromeUtils.import( + getRootDirectory(gTestPath) + "FissionTestHelperParent.jsm" + ); + FissionTestHelperParent.SimpleTest = SimpleTest; + + ChromeUtils.registerWindowActor("FissionTestHelper", { + parent: { + moduleURI: getRootDirectory(gTestPath) + "FissionTestHelperParent.jsm", + }, + child: { + moduleURI: getRootDirectory(gTestPath) + "FissionTestHelperChild.jsm", + events: { + "FissionTestHelper:Init": { capture: true, wantUntrusted: true }, + }, + }, + allFrames: true, + }); + + try { + var onlyOneSubtest = SpecialPowers.getCharPref( + "apz.subtest", + /*default = */ "" + ); + + for (var subtest of subtests) { + if (onlyOneSubtest && onlyOneSubtest != subtest.file) { + SimpleTest.ok( + true, + "Skipping " + + subtest.file + + " because only " + + onlyOneSubtest + + " is being run" + ); + continue; + } + let url = httpURL(subtest.file); + dump(`Starting test ${url}\n`); + + // Load the test URL and tell it to get started, and wait until it reports + // completion. + await BrowserTestUtils.withNewTab( + { gBrowser: fissionWindow.gBrowser, url }, + async browser => { + let tabActor = browser.browsingContext.currentWindowGlobal.getActor( + "FissionTestHelper" + ); + let donePromise = tabActor.getTestCompletePromise(); + if (subtest.setup) { + subtest.setup(fissionWindow); + } + tabActor.startTest(); + await donePromise; + } + ); + + dump(`Finished test ${url}\n`); + } + } finally { + // Delete stuff we added to FissionTestHelperParent, beacuse the object will + // outlive this test, and leaving stuff on it may leak the things reachable + // from it. + delete FissionTestHelperParent.SimpleTest; + // Teardown + ChromeUtils.unregisterWindowActor("FissionTestHelper"); + await BrowserTestUtils.closeWindow(fissionWindow); + } +}); diff --git a/gfx/layers/apz/test/mochitest/browser_test_reset_scaling_zoom.js b/gfx/layers/apz/test/mochitest/browser_test_reset_scaling_zoom.js new file mode 100644 index 0000000000..ef2c8f8c52 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/browser_test_reset_scaling_zoom.js @@ -0,0 +1,44 @@ +add_task(async function setup_pref() { + let isWindows = navigator.platform.indexOf("Win") == 0; + await SpecialPowers.pushPrefEnv({ + set: [["apz.test.fails_with_native_injection", isWindows]], + }); +}); + +add_task(async function test_main() { + function httpURL(filename) { + let chromeURL = getRootDirectory(gTestPath) + filename; + return chromeURL.replace( + "chrome://mochitests/content/", + "http://mochi.test:8888/" + ); + } + + const pageUrl = httpURL("helper_test_reset_scaling_zoom.html"); + + await BrowserTestUtils.withNewTab(pageUrl, async function(browser) { + let getResolution = function() { + return content.window.SpecialPowers.getDOMWindowUtils( + content.window + ).getResolution(); + }; + + let doZoomIn = async function() { + await content.window.wrappedJSObject.doZoomIn(); + }; + + let resolution = await ContentTask.spawn(browser, null, getResolution); + is(resolution, 1.0, "Initial page resolution should be 1.0"); + + await ContentTask.spawn(browser, null, doZoomIn); + resolution = await ContentTask.spawn(browser, null, getResolution); + isnot(resolution, 1.0, "Expected resolution to be bigger than 1.0"); + + document.getElementById("cmd_fullZoomReset").doCommand(); + // Spin the event loop once just to make sure the message gets through + await new Promise(resolve => setTimeout(resolve, 0)); + + resolution = await ContentTask.spawn(browser, null, getResolution); + is(resolution, 1.0, "Expected resolution to be reset to 1.0"); + }); +}); diff --git a/gfx/layers/apz/test/mochitest/browser_test_scrollbar_in_extension_popup_window.js b/gfx/layers/apz/test/mochitest/browser_test_scrollbar_in_extension_popup_window.js new file mode 100644 index 0000000000..729174babc --- /dev/null +++ b/gfx/layers/apz/test/mochitest/browser_test_scrollbar_in_extension_popup_window.js @@ -0,0 +1,116 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/browser/components/extensions/test/browser/head.js", + this +); +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/browser/components/extensions/test/browser/head_browserAction.js", + this +); + +add_task(async () => { + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + browser_action: { + default_popup: "popup.html", + browser_style: true, + }, + }, + + files: { + "popup.html": ` + + + + + + +
      +
    • 1
    • +
    • 2
    • +
    • 3
    • +
    • 4
    • +
    • 5
    • +
    • 6
    • +
    • 7
    • +
    • 8
    • +
    • 9
    • +
    • 10
    • +
    + + `, + }, + }); + + await extension.startup(); + + registerCleanupFunction(() => { + SpecialPowers.clearUserPref("apz.popups.enabled"); + }); + + async function takeSnapshot(browserWin) { + let browser = await openBrowserActionPanel(extension, browserWin, true); + const snapshot = await SpecialPowers.spawn(browser, [], async () => { + await SpecialPowers.snapshotWindow( + content.window, + false /* withCaret */, + undefined /* use the default rect */, + undefined /* use the default bgcolor */, + { DRAWWINDOW_DRAW_VIEW: true } /* to capture scrollbars */ + ) + .toDataURL() + .toString(); + }); + + const popup = getBrowserActionPopup(extension, browserWin); + await closeBrowserAction(extension, browserWin); + is(popup.state, "closed", "browserAction popup has been closed"); + + return snapshot; + } + + // First, take a snapshot with disabling APZ in the popup window, we assume + // scrollbars are rendered properly there. + await SpecialPowers.setBoolPref("apz.popups.enabled", false); + const newWin = await BrowserTestUtils.openNewBrowserWindow(); + const reference = await takeSnapshot(newWin); + await BrowserTestUtils.closeWindow(newWin); + + // Then take a snapshot with enabling APZ. + await SpecialPowers.setBoolPref("apz.popups.enabled", true); + const anotherWin = await BrowserTestUtils.openNewBrowserWindow(); + const test = await takeSnapshot(anotherWin); + await BrowserTestUtils.closeWindow(anotherWin); + + is( + test, + reference, + "Contents in popup window opened by extension should be same regardless of the APZ state in the window" + ); + + await extension.unload(); +}); diff --git a/gfx/layers/apz/test/mochitest/browser_test_scrolling_in_extension_popup_window.js b/gfx/layers/apz/test/mochitest/browser_test_scrolling_in_extension_popup_window.js new file mode 100644 index 0000000000..c964aa8f14 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/browser_test_scrolling_in_extension_popup_window.js @@ -0,0 +1,182 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/browser/components/extensions/test/browser/head.js", + this +); +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/browser/components/extensions/test/browser/head_browserAction.js", + this +); + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js", + this +); + +// This is a simplified/combined version of promiseApzRepaintsFlushed and +// promiseAllPaintsDone. We need this function because, unfortunately, there is +// no easy way to use paint_listeners.js' functions and apz_test_utils.js' +// functions in popup contents opened by extensions either as scripts in the +// popup contents or scripts inside SpecialPowers.spawn because we can't use +// privileged functions in the popup contents' script, we can't use functions +// basically as it as in the sandboxed context either. +async function flushApzRepaintsInPopup(popup) { + // Flush APZ repaints and waits for MozAfterPaint. + await SpecialPowers.spawn(popup, [], async () => { + return new Promise(resolve => { + const utils = SpecialPowers.getDOMWindowUtils(content.window); + var repaintDone = function() { + dump("APZ flush done\n"); + SpecialPowers.Services.obs.removeObserver( + repaintDone, + "apz-repaints-flushed" + ); + if (utils.isMozAfterPaintPending) { + dump("Waits for a MozAfterPaint event\n"); + content.window.addEventListener( + "MozAfterPaint", + () => { + dump("Got a MozAfterPaint event\n"); + resolve(); + }, + { once: true } + ); + } else { + content.window.setTimeout(resolve, 0); + } + }; + SpecialPowers.Services.obs.addObserver( + repaintDone, + "apz-repaints-flushed" + ); + if (utils.flushApzRepaints()) { + dump("Flushed APZ repaints, waiting for callback...\n"); + } else { + dump( + "Flushing APZ repaints was a no-op, triggering callback directly...\n" + ); + repaintDone(); + } + }); + }); +} + +add_task(async () => { + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + browser_action: { + default_popup: "popup.html", + browser_style: true, + }, + }, + + files: { + "popup.html": ` + + + + + + +
      +
    • 1
    • +
    • 2
    • +
    • 3
    • +
    • 4
    • +
    • 5
    • +
    • 6
    • +
    • 7
    • +
    • 8
    • +
    • 9
    • +
    • 10
    • +
    + + `, + }, + }); + + await extension.startup(); + + await SpecialPowers.pushPrefEnv({ set: [["apz.popups.enabled", true]] }); + + // Open the popup window of the extension. + const browserForPopup = await openBrowserActionPanel( + extension, + undefined, + true + ); + + // Flush APZ repaints and waits for MozAfterPaint to make sure APZ state is + // stable. + await flushApzRepaintsInPopup(browserForPopup); + + const scrollEventPromise = SpecialPowers.spawn( + browserForPopup, + [], + async () => { + return new Promise(resolve => { + content.window.addEventListener( + "scroll", + event => { + dump("Got a scroll event in the popup content document\n"); + resolve(); + }, + { once: true } + ); + }); + } + ); + + // Send native mouse wheel to scroll the content in the popup. + await new Promise(resolve => { + synthesizeNativeWheelAndWaitForObserver( + browserForPopup, + 50, + 50, + 0, + -100, + () => { + resolve(); + } + ); + }); + + // Flush APZ repaints and waits for MozAfterPaint to make sure the scroll has + // been reflected on the main thread. + const apzPromise = flushApzRepaintsInPopup(browserForPopup); + + await Promise.all([apzPromise, scrollEventPromise]); + + const scrollY = await SpecialPowers.spawn(browserForPopup, [], () => { + return content.window.scrollY; + }); + ok(scrollY > 0, "Mouse wheel scrolling works in the popup window"); + + await closeBrowserAction(extension); + + await extension.unload(); +}); diff --git a/gfx/layers/apz/test/mochitest/browser_test_select_zoom.js b/gfx/layers/apz/test/mochitest/browser_test_select_zoom.js new file mode 100644 index 0000000000..4f4ba7b733 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/browser_test_select_zoom.js @@ -0,0 +1,220 @@ +/* This test is a a mash up of + https://searchfox.org/mozilla-central/rev/559b25eb41c1cbffcb90a34e008b8288312fcd25/gfx/layers/apz/test/mochitest/browser_test_group_fission.js + https://searchfox.org/mozilla-central/rev/559b25eb41c1cbffcb90a34e008b8288312fcd25/gfx/layers/apz/test/mochitest/helper_basic_zoom.html + https://searchfox.org/mozilla-central/rev/559b25eb41c1cbffcb90a34e008b8288312fcd25/browser/base/content/test/forms/browser_selectpopup.js +*/ + +function openSelectPopup(selectPopup, selector = "select", win = window) { + let popupShownPromise = BrowserTestUtils.waitForEvent( + selectPopup, + "popupshown" + ); + + EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true }, win); + return popupShownPromise; +} + +function hideSelectPopup(selectPopup, win = window) { + let browser = win.gBrowser.selectedBrowser; + let selectClosedPromise = SpecialPowers.spawn(browser, [], async function() { + let { SelectContentHelper } = ChromeUtils.import( + "resource://gre/actors/SelectChild.jsm", + null + ); + return ContentTaskUtils.waitForCondition(() => !SelectContentHelper.open); + }); + + EventUtils.synthesizeKey("KEY_Enter", {}, win); + + return selectClosedPromise; +} + +add_task(async function setup_pref() { + await SpecialPowers.pushPrefEnv({ + set: [ + // Dropping the touch slop to 0 makes the tests easier to write because + // we can just do a one-pixel drag to get over the pan threshold rather + // than having to hard-code some larger value. + ["apz.touch_start_tolerance", "0.0"], + // The subtests in this test do touch-drags to pan the page, but we don't + // want those pans to turn into fling animations, so we increase the + // fling-min threshold velocity to an arbitrarily large value. + ["apz.fling_min_velocity_threshold", "10000"], + // Explicitly enable pinch-zooming, so this test can run on desktop + // even though zooming isn't enabled by default on desktop yet. + ["apz.allow_zooming", true], + ], + }); +}); + +// This test opens a select popup after pinch (apz) zooming has happened. +add_task(async function() { + function httpURL(filename) { + let chromeURL = getRootDirectory(gTestPath) + filename; + //return chromeURL; + return chromeURL.replace( + "chrome://mochitests/content/", + "http://mochi.test:8888/" + ); + } + + const pageUrl = httpURL("helper_test_select_zoom.html"); + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl); + + await SpecialPowers.spawn(tab.linkedBrowser, [], async () => { + const input = content.document.getElementById("select"); + const focusPromise = new Promise(resolve => { + input.addEventListener("focus", resolve, { once: true }); + }); + input.focus(); + await focusPromise; + }); + + await SpecialPowers.spawn(tab.linkedBrowser, [], async () => { + await content.wrappedJSObject.waitUntilApzStable(); + }); + + const initial_resolution = await SpecialPowers.spawn( + tab.linkedBrowser, + [], + () => { + return content.window.windowUtils.getResolution(); + } + ); + + const initial_rect = await SpecialPowers.spawn(tab.linkedBrowser, [], () => { + return content.wrappedJSObject.getSelectRect(); + }); + + ok( + initial_resolution > 0, + "The initial_resolution is " + + initial_resolution + + ", which is some sane value" + ); + + let menulist = document.getElementById("ContentSelectDropdown"); + let selectPopup = menulist.menupopup; + + // First, get the position of the select popup when no translations have been applied. + await openSelectPopup(selectPopup); + + let popup_initial_rect = selectPopup.getBoundingClientRect(); + let popupInitialX = popup_initial_rect.left; + let popupInitialY = popup_initial_rect.top; + + await hideSelectPopup(selectPopup); + + ok(popupInitialX > 0, "select position before zooming (x) " + popupInitialX); + ok(popupInitialY > 0, "select position before zooming (y) " + popupInitialY); + + await SpecialPowers.spawn(tab.linkedBrowser, [], async () => { + await content.wrappedJSObject.pinchZoomInWithTouch(150, 300); + }); + + // Flush state and get the resolution we're at now + await SpecialPowers.spawn(tab.linkedBrowser, [], async () => { + await content.wrappedJSObject.promiseApzFlushedRepaints(); + }); + + const final_resolution = await SpecialPowers.spawn( + tab.linkedBrowser, + [], + () => { + return content.window.windowUtils.getResolution(); + } + ); + + ok( + final_resolution > initial_resolution, + "The final resolution (" + + final_resolution + + ") is greater after zooming in" + ); + + const final_rect = await SpecialPowers.spawn(tab.linkedBrowser, [], () => { + return content.wrappedJSObject.getSelectRect(); + }); + + await openSelectPopup(selectPopup); + + let popupRect = selectPopup.getBoundingClientRect(); + ok( + Math.abs(popupRect.left - popupInitialX) > 1, + "popup should have moved by more than one pixel (x) " + + popupRect.left + + " " + + popupInitialX + ); + ok( + Math.abs(popupRect.top - popupInitialY) > 1, + "popup should have moved by more than one pixel (y) " + + popupRect.top + + " " + + popupInitialY + ); + + ok( + Math.abs( + final_rect.left - initial_rect.left - (popupRect.left - popupInitialX) + ) < 1, + "popup should have moved approximately the same as the element (x)" + ); + let tolerance = navigator.platform.includes("Linux") ? final_rect.height : 1; + ok( + Math.abs( + final_rect.top - initial_rect.top - (popupRect.top - popupInitialY) + ) < tolerance, + "popup should have moved approximately the same as the element (y)" + ); + + ok( + true, + "initial " + + initial_rect.left + + " " + + initial_rect.top + + " " + + initial_rect.width + + " " + + initial_rect.height + ); + ok( + true, + "final " + + final_rect.left + + " " + + final_rect.top + + " " + + final_rect.width + + " " + + final_rect.height + ); + + ok( + true, + "initial popup " + + popup_initial_rect.left + + " " + + popup_initial_rect.top + + " " + + popup_initial_rect.width + + " " + + popup_initial_rect.height + ); + ok( + true, + "final popup " + + popupRect.left + + " " + + popupRect.top + + " " + + popupRect.width + + " " + + popupRect.height + ); + + await hideSelectPopup(selectPopup); + + BrowserTestUtils.removeTab(tab); +}); diff --git a/gfx/layers/apz/test/mochitest/helper_background_tab_scroll.html b/gfx/layers/apz/test/mochitest/helper_background_tab_scroll.html new file mode 100644 index 0000000000..f55a55f0fc --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_background_tab_scroll.html @@ -0,0 +1,9 @@ + + + + +
    #scrolltarget
    diff --git a/gfx/layers/apz/test/mochitest/helper_basic_doubletap_zoom.html b/gfx/layers/apz/test/mochitest/helper_basic_doubletap_zoom.html new file mode 100644 index 0000000000..296c3c54ab --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_basic_doubletap_zoom.html @@ -0,0 +1,61 @@ + + + + + + Sanity check for double-tap zooming + + + + + + + +
    Text before the div.
    +
    +
    Text after the div.
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_basic_onetouchpinch.html b/gfx/layers/apz/test/mochitest/helper_basic_onetouchpinch.html new file mode 100644 index 0000000000..1ec7788c9a --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_basic_onetouchpinch.html @@ -0,0 +1,90 @@ + + + + + + Sanity check for one-touch pinch zooming + + + + + + + + Here is some text to stare at as the test runs. It serves no functional + purpose, but gives you an idea of the zoom level. It's harder to tell what + the zoom level is when the page is just solid white. + + diff --git a/gfx/layers/apz/test/mochitest/helper_basic_pan.html b/gfx/layers/apz/test/mochitest/helper_basic_pan.html new file mode 100644 index 0000000000..894915793e --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_basic_pan.html @@ -0,0 +1,73 @@ + + + + + + Sanity panning test + + + + + + + +
    + This div makes the page scrollable. +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_basic_zoom.html b/gfx/layers/apz/test/mochitest/helper_basic_zoom.html new file mode 100644 index 0000000000..b67b0f43b0 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_basic_zoom.html @@ -0,0 +1,71 @@ + + + + + + Sanity check for zooming + + + + + + + + Here is some text to stare at as the test runs. It serves no functional + purpose, but gives you an idea of the zoom level. It's harder to tell what + the zoom level is when the page is just solid white. + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1162771.html b/gfx/layers/apz/test/mochitest/helper_bug1162771.html new file mode 100644 index 0000000000..cf097d7f0d --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1162771.html @@ -0,0 +1,131 @@ + + + + + + Test for touchend on media elements + + + + + + + +

    Tap on the colored boxes to hide them.

    + + +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1271432.html b/gfx/layers/apz/test/mochitest/helper_bug1271432.html new file mode 100644 index 0000000000..1e88ebc214 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1271432.html @@ -0,0 +1,573 @@ + + Ensure that the hit region doesn't get unexpectedly expanded + + + + + + + +Some text + +
    +Scrolling on the very left edge of this div will work. +Scrolling on the right side of this div (starting with the left edge of the orange box above) should work, but doesn't.
    +0
    +1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +49
    +50
    +51
    +52
    +53
    +54
    +55
    +56
    +57
    +58
    +59
    +60
    +61
    +62
    +63
    +64
    +65
    +66
    +67
    +68
    +69
    +70
    +71
    +72
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +95
    +96
    +97
    +98
    +99
    +100
    +101
    +102
    +103
    +104
    +105
    +106
    +107
    +108
    +109
    +110
    +111
    +112
    +113
    +114
    +115
    +116
    +117
    +118
    +119
    +120
    +121
    +122
    +123
    +124
    +125
    +126
    +127
    +128
    +129
    +130
    +131
    +132
    +133
    +134
    +135
    +136
    +137
    +138
    +139
    +140
    +141
    +142
    +143
    +144
    +145
    +146
    +147
    +148
    +149
    +150
    +151
    +152
    +153
    +154
    +155
    +156
    +157
    +158
    +159
    +160
    +161
    +162
    +163
    +164
    +165
    +166
    +167
    +168
    +169
    +170
    +171
    +172
    +173
    +174
    +175
    +176
    +177
    +178
    +179
    +180
    +181
    +182
    +183
    +184
    +185
    +186
    +187
    +188
    +189
    +190
    +191
    +192
    +193
    +194
    +195
    +196
    +197
    +198
    +199
    +200
    +201
    +202
    +203
    +204
    +205
    +206
    +207
    +208
    +209
    +210
    +211
    +212
    +213
    +214
    +215
    +216
    +217
    +218
    +219
    +220
    +221
    +222
    +223
    +224
    +225
    +226
    +227
    +228
    +229
    +230
    +231
    +232
    +233
    +234
    +235
    +236
    +237
    +238
    +239
    +240
    +241
    +242
    +243
    +244
    +245
    +246
    +247
    +248
    +249
    +250
    +251
    +252
    +253
    +254
    +255
    +256
    +257
    +258
    +259
    +260
    +261
    +262
    +263
    +264
    +265
    +266
    +267
    +268
    +269
    +270
    +271
    +272
    +273
    +274
    +275
    +276
    +277
    +278
    +279
    +280
    +281
    +282
    +283
    +284
    +285
    +286
    +287
    +288
    +289
    +290
    +291
    +292
    +293
    +294
    +295
    +296
    +297
    +298
    +299
    +300
    +301
    +302
    +303
    +304
    +305
    +306
    +307
    +308
    +309
    +310
    +311
    +312
    +313
    +314
    +315
    +316
    +317
    +318
    +319
    +320
    +321
    +322
    +323
    +324
    +325
    +326
    +327
    +328
    +329
    +330
    +331
    +332
    +333
    +334
    +335
    +336
    +337
    +338
    +339
    +340
    +341
    +342
    +343
    +344
    +345
    +346
    +347
    +348
    +349
    +350
    +351
    +352
    +353
    +354
    +355
    +356
    +357
    +358
    +359
    +360
    +361
    +362
    +363
    +364
    +365
    +366
    +367
    +368
    +369
    +370
    +371
    +372
    +373
    +374
    +375
    +376
    +377
    +378
    +379
    +380
    +381
    +382
    +383
    +384
    +385
    +386
    +387
    +388
    +389
    +390
    +391
    +392
    +393
    +394
    +395
    +396
    +397
    +398
    +399
    +400
    +401
    +402
    +403
    +404
    +405
    +406
    +407
    +408
    +409
    +410
    +411
    +412
    +413
    +414
    +415
    +416
    +417
    +418
    +419
    +420
    +421
    +422
    +423
    +424
    +425
    +426
    +427
    +428
    +429
    +430
    +431
    +432
    +433
    +434
    +435
    +436
    +437
    +438
    +439
    +440
    +441
    +442
    +443
    +444
    +445
    +446
    +447
    +448
    +449
    +450
    +451
    +452
    +453
    +454
    +455
    +456
    +457
    +458
    +459
    +460
    +461
    +462
    +463
    +464
    +465
    +466
    +467
    +468
    +469
    +470
    +471
    +472
    +473
    +474
    +475
    +476
    +477
    +478
    +479
    +480
    +481
    +482
    +483
    +484
    +485
    +486
    +487
    +488
    +489
    +490
    +491
    +492
    +493
    +494
    +495
    +496
    +497
    +498
    +499
    +
    +
    this div makes the page scrollable
    + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1280013.html b/gfx/layers/apz/test/mochitest/helper_bug1280013.html new file mode 100644 index 0000000000..3e2943475e --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1280013.html @@ -0,0 +1,73 @@ + + + + + + Test for bug 1280013 + + + + + + + The iframe below is at (0, 400). Scroll it into view, and then scroll the contents. The content should be fully rendered in high-resolution. + + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1285070.html b/gfx/layers/apz/test/mochitest/helper_bug1285070.html new file mode 100644 index 0000000000..3ecef53f4a --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1285070.html @@ -0,0 +1,44 @@ + + + + + + Test pointer events are dispatched once for touch tap + + + + + + +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1299195.html b/gfx/layers/apz/test/mochitest/helper_bug1299195.html new file mode 100644 index 0000000000..7c7eb8289c --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1299195.html @@ -0,0 +1,47 @@ + + + + + + Test pointer events are dispatched once for touch tap + + + + + + +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1326290.html b/gfx/layers/apz/test/mochitest/helper_bug1326290.html new file mode 100644 index 0000000000..991018e910 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1326290.html @@ -0,0 +1,63 @@ + + + + + + Dragging the mouse on a inactive scrollframe's scrollbar + + + + + + + +
    +
    Some content inside the inactive scrollframe
    +
    +
    Some content to ensure the root scrollframe is scrollable and the overflow:scroll div remains inactive
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1331693.html b/gfx/layers/apz/test/mochitest/helper_bug1331693.html new file mode 100644 index 0000000000..0558f675a6 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1331693.html @@ -0,0 +1,71 @@ + + + + + + Dragging the mouse on a scrollframe inside an SVGEffects + + + + + + + +
    A div that generate an svg effects display item +
    +
    Some content inside the scrollframe
    +
    +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1346632.html b/gfx/layers/apz/test/mochitest/helper_bug1346632.html new file mode 100644 index 0000000000..d08afc1481 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1346632.html @@ -0,0 +1,69 @@ + + + + + + Dragging the scrollbar on a page with a fixed-positioned element just past the right edge of the content + + + + + + + +
    +

    +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1414336.html b/gfx/layers/apz/test/mochitest/helper_bug1414336.html new file mode 100644 index 0000000000..50afd9a448 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1414336.html @@ -0,0 +1,98 @@ + + + + + + Test for Bug 1414336 + + + + + + + + + +Mozilla Bug 1414336 +

    +
    +

    Test bug1414336

    +

    Test bug1414336

    +

    Test bug1414336

    +

    Test bug1414336

    +

    Test bug1414336

    +

    Test bug1414336

    +

    Test bug1414336

    +

    Test bug1414336

    +

    Test bug1414336

    +

    Test bug1414336

    +

    Test bug1414336

    +

    Test bug1414336

    +

    Test bug1414336

    +

    Test bug1414336

    +

    Test bug1414336

    +

    Test bug1414336

    +

    Test bug1414336

    +

    Test bug1414336

    +

    Test bug1414336

    +

    Test bug1414336

    +

    Test bug1414336

    +

    Test bug1414336

    +

    Test bug1414336

    +

    Test bug1414336

    +

    Test bug1414336

    +

    Test bug1414336

    +

    Test bug1414336

    +

    Test bug1414336

    +

    Test bug1414336

    +

    Test bug1414336

    +

    Test bug1414336

    +

    Test bug1414336

    +
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1462961.html b/gfx/layers/apz/test/mochitest/helper_bug1462961.html new file mode 100644 index 0000000000..7d85f4b6b4 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1462961.html @@ -0,0 +1,74 @@ + + + + + + Dragging the mouse on a transformed scrollframe inside a fixed-pos element + + + + + + + +
    +
    +
    +
    +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1464568_opacity.html b/gfx/layers/apz/test/mochitest/helper_bug1464568_opacity.html new file mode 100644 index 0000000000..0a7715a995 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1464568_opacity.html @@ -0,0 +1,66 @@ + + + + Test that opacity animation is correctly placed during asynchronous scrolling + + + + + + + +
    +
    + +
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1464568_transform.html b/gfx/layers/apz/test/mochitest/helper_bug1464568_transform.html new file mode 100644 index 0000000000..1181571991 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1464568_transform.html @@ -0,0 +1,66 @@ + + + + Test that transform animation is correctly placed during asynchronous scrolling + + + + + + + +
    +
    + +
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1473108.html b/gfx/layers/apz/test/mochitest/helper_bug1473108.html new file mode 100644 index 0000000000..1d76560009 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1473108.html @@ -0,0 +1,50 @@ + + + + + + + Test for Bug 1473108 + + + + + + + + Mozilla Bug 1473108 + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1490393-2.html b/gfx/layers/apz/test/mochitest/helper_bug1490393-2.html new file mode 100644 index 0000000000..c706811564 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1490393-2.html @@ -0,0 +1,65 @@ + + + + + + Dragging the mouse on a scrollbar for a scrollframe inside nested transforms + + + + + + +
    +
    +
    +
    +
    Yay text
    +
    +
    +
    +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1490393.html b/gfx/layers/apz/test/mochitest/helper_bug1490393.html new file mode 100644 index 0000000000..7ae29c5d16 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1490393.html @@ -0,0 +1,64 @@ + + + + + + Dragging the mouse on a scrollbar for a scrollframe inside nested transforms + + + + + + +
    +
    +
    +
    +
    Yay text
    +
    +
    +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1502010_unconsumed_pan.html b/gfx/layers/apz/test/mochitest/helper_bug1502010_unconsumed_pan.html new file mode 100644 index 0000000000..a53cb3e4e2 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1502010_unconsumed_pan.html @@ -0,0 +1,76 @@ + + + + + + Test pointercancel doesn't get sent for horizontal panning on a pan-y element + + + + + + + +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1506497_touch_action_fixed_on_fixed.html b/gfx/layers/apz/test/mochitest/helper_bug1506497_touch_action_fixed_on_fixed.html new file mode 100644 index 0000000000..0bcdc57543 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1506497_touch_action_fixed_on_fixed.html @@ -0,0 +1,96 @@ + + + + + + Test for Bug 1506497 + + + + + + + + +
    +
    +
    Touch here and drag up
    +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1509575.html b/gfx/layers/apz/test/mochitest/helper_bug1509575.html new file mode 100644 index 0000000000..a7609b072c --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1509575.html @@ -0,0 +1,71 @@ + + + + + + + Test for Bug 1509575 + + + + + + +
    + Now you're scrolled, now you're not? +
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1544966_zoom_on_touch_action_none.html b/gfx/layers/apz/test/mochitest/helper_bug1544966_zoom_on_touch_action_none.html new file mode 100644 index 0000000000..e0a305e402 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1544966_zoom_on_touch_action_none.html @@ -0,0 +1,89 @@ + + + + + + Test for Bug 1544966 + + + + + + + + +
    + Put down two fingers at the same time and do a pinch action. +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1550510.html b/gfx/layers/apz/test/mochitest/helper_bug1550510.html new file mode 100644 index 0000000000..4dac5fb94c --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1550510.html @@ -0,0 +1,66 @@ + + + + + + Dragging the mouse on a scrollbar for a transformed, filtered scrollframe + + + + + + +
    +
    +
    +
    +
    + yay text +
    +
    +
    +
    +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1637113_main_thread_hit_test.html b/gfx/layers/apz/test/mochitest/helper_bug1637113_main_thread_hit_test.html new file mode 100644 index 0000000000..b3481c1d3c --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1637113_main_thread_hit_test.html @@ -0,0 +1,70 @@ + + + + + + + Test for Bug 1637113 + + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1637135_narrow_viewport.html b/gfx/layers/apz/test/mochitest/helper_bug1637135_narrow_viewport.html new file mode 100644 index 0000000000..5427713fdb --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1637135_narrow_viewport.html @@ -0,0 +1,60 @@ + + + + + + + Test for Bug 1637135 + + + + + + + +
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1638441_fixed_pos_hit_test.html b/gfx/layers/apz/test/mochitest/helper_bug1638441_fixed_pos_hit_test.html new file mode 100644 index 0000000000..6bcf5bdac2 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1638441_fixed_pos_hit_test.html @@ -0,0 +1,67 @@ + + + + + + + Test for Bug 1638441 + + + + + + + +
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1638458_contextmenu.html b/gfx/layers/apz/test/mochitest/helper_bug1638458_contextmenu.html new file mode 100644 index 0000000000..7141bdc83c --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1638458_contextmenu.html @@ -0,0 +1,82 @@ + + + + + + + Test for Bug 1638458 + + + + + + + +
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1648491_no_pointercancel_with_dtc.html b/gfx/layers/apz/test/mochitest/helper_bug1648491_no_pointercancel_with_dtc.html new file mode 100644 index 0000000000..15b96f69ce --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1648491_no_pointercancel_with_dtc.html @@ -0,0 +1,89 @@ + + + + + + Test for Bug 1648491 + + + + + + + + +
    + A two-finger pinch action here should send pointer events to content. +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1662800.html b/gfx/layers/apz/test/mochitest/helper_bug1662800.html new file mode 100644 index 0000000000..6e7231ebae --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1662800.html @@ -0,0 +1,61 @@ + + + + + + Dragging the mouse on a scrollbar for a scrollframe inside nested transforms with a scale component + + + + + + +
    +
    +
    +
    +
    +
    +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1663731_no_pointercancel_on_second_touchstart.html b/gfx/layers/apz/test/mochitest/helper_bug1663731_no_pointercancel_on_second_touchstart.html new file mode 100644 index 0000000000..f62d2db050 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1663731_no_pointercancel_on_second_touchstart.html @@ -0,0 +1,82 @@ + + + + + + Test for Bug 1663731 + + + + + + + + + A two-finger pinch action here should send pointer events to content and not do browser zooming. + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1669625.html b/gfx/layers/apz/test/mochitest/helper_bug1669625.html new file mode 100644 index 0000000000..f0136dae3b --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1669625.html @@ -0,0 +1,74 @@ + + + + + + Scrolling doesn't cause extra SchedulePaint calls + + + + + + + +
    spacer
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1674935.html b/gfx/layers/apz/test/mochitest/helper_bug1674935.html new file mode 100644 index 0000000000..f5efa16d5f --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1674935.html @@ -0,0 +1,76 @@ + + + + + Tests that keyboard arrow keys scroll a very specific page + + + + + + + + +
    +
    + + + + + +
    +
    +
    +

    Firefox

    +
    + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1682170_pointercancel_on_touchaction_pinchzoom.html b/gfx/layers/apz/test/mochitest/helper_bug1682170_pointercancel_on_touchaction_pinchzoom.html new file mode 100644 index 0000000000..7d0cccf18d --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1682170_pointercancel_on_touchaction_pinchzoom.html @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + A two-finger pinch action here should trigger browser zoom and trigger a pointercancel to content. + Note that the code does a zoom-out and the page is already at min zoom, so + the zoom doesn't produce any visual effect. But the DOM events should be the + same either way. + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug982141.html b/gfx/layers/apz/test/mochitest/helper_bug982141.html new file mode 100644 index 0000000000..be74044280 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug982141.html @@ -0,0 +1,137 @@ + + + + + + + Test for Bug 982141, helper page + + + + + + Mozilla Bug 982141 + +
    +
    + Wide content so that the vertical scrollbar for the parent div + doesn't eat into the 50px width and reduce the width of the + displayport. +
    + Line 1
    + Line 2
    + Line 3
    + Line 4
    + Line 5
    + Line 6
    + Line 7
    + Line 8
    + Line 9
    + Line 10
    + Line 11
    + Line 12
    + Line 13
    + Line 14
    + Line 15
    + Line 16
    + Line 17
    + Line 18
    + Line 19
    + Line 20
    + Line 21
    + Line 22
    + Line 23
    + Line 24
    + Line 25
    + Line 26
    + Line 27
    + Line 28
    + Line 29
    + Line 30
    + Line 31
    + Line 32
    + Line 33
    + Line 34
    + Line 35
    + Line 36
    + Line 37
    + Line 38
    + Line 39
    + Line 40
    + Line 41
    + Line 42
    + Line 43
    + Line 44
    + Line 45
    + Line 46
    + Line 40
    + Line 48
    + Line 49
    + Line 50
    +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_checkerboard_apzforcedisabled.html b/gfx/layers/apz/test/mochitest/helper_checkerboard_apzforcedisabled.html new file mode 100644 index 0000000000..f0d43f2b73 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_checkerboard_apzforcedisabled.html @@ -0,0 +1,88 @@ + + + + + + Checkerboarding while root scrollframe async-scrolls and a + subframe has APZ force disabled + + + + + + + +
    +
    +
    +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_checkerboard_no_multiplier.html b/gfx/layers/apz/test/mochitest/helper_checkerboard_no_multiplier.html new file mode 100644 index 0000000000..149ef9fbba --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_checkerboard_no_multiplier.html @@ -0,0 +1,57 @@ + + + +Testcase for checkerboarding with displayport multipliers dropped to zero + + + + + +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_checkerboard_scrollinfo.html b/gfx/layers/apz/test/mochitest/helper_checkerboard_scrollinfo.html new file mode 100644 index 0000000000..ba4ef039f5 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_checkerboard_scrollinfo.html @@ -0,0 +1,91 @@ + + + + Scrolling a scrollinfo layer and making sure it doesn't checkerboard + + + + + + + +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_checkerboard_zoom_during_load.html b/gfx/layers/apz/test/mochitest/helper_checkerboard_zoom_during_load.html new file mode 100644 index 0000000000..650b1c265d --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_checkerboard_zoom_during_load.html @@ -0,0 +1,55 @@ + + + +Testcase for checkerboarding after zooming during page load + + + + + +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_checkerboard_zoomoverflowhidden.html b/gfx/layers/apz/test/mochitest/helper_checkerboard_zoomoverflowhidden.html new file mode 100644 index 0000000000..220907e3be --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_checkerboard_zoomoverflowhidden.html @@ -0,0 +1,145 @@ + + + + + + Checkerboarding in while scrolling a subframe when root scrollframe has + overflow hidden and pinch zoomed in + + + + + + + +
    +
    +

    STR:

    +
      +
    1. set apz.allow_zoom to true
    2. +
    3. visit any bugzilla site (like this one)
    4. +
    5. zoom into the page and observe the left edge of the viewport
    6. +
    +

    ER: content should be shown
    + AR: foreground content seems to disappear, looks like it's being cut off +

    +

    I attached a video of the STR to show the problem a little bit better. So far, I could only reproduce this on bugzilla. Words words words words words words words words words words words words words words words words words words words words words words.

    + +
    +
    +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_click.html b/gfx/layers/apz/test/mochitest/helper_click.html new file mode 100644 index 0000000000..b70e7ef379 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_click.html @@ -0,0 +1,41 @@ + + + + + + Sanity mouse-clicking test + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_click_interrupt_animation.html b/gfx/layers/apz/test/mochitest/helper_click_interrupt_animation.html new file mode 100644 index 0000000000..8884bb2e0d --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_click_interrupt_animation.html @@ -0,0 +1,85 @@ + + + + + + Clicking on the content (not scrollbar) should interrupt animations + + + + + + + + +
    +
    + The above div is sized to 3x screen height so the linear gradient is more steep in terms of + color/pixel. We only scroll a few pages worth so we don't need the gradient all the way down. + And then we use a bottom-margin to make the page really big so the scrollthumb is + relatively small, giving us lots of space to click on the scrolltrack. + + diff --git a/gfx/layers/apz/test/mochitest/helper_div_pan.html b/gfx/layers/apz/test/mochitest/helper_div_pan.html new file mode 100644 index 0000000000..e71e06f23e --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_div_pan.html @@ -0,0 +1,44 @@ + + + + + + Sanity panning test for scrollable div + + + + + + +
    +
    + This div makes the |outer| div scrollable. +
    +
    +
    + This div makes the top-level page scrollable. +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_drag_click.html b/gfx/layers/apz/test/mochitest/helper_drag_click.html new file mode 100644 index 0000000000..f8ff85654d --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_drag_click.html @@ -0,0 +1,44 @@ + + + + + + Sanity mouse-drag click test + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_drag_root_scrollbar.html b/gfx/layers/apz/test/mochitest/helper_drag_root_scrollbar.html new file mode 100644 index 0000000000..5e81151bc5 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_drag_root_scrollbar.html @@ -0,0 +1,61 @@ + + + + + + Dragging the mouse on the viewport's scrollbar + + + + + + + +
    Some content to ensure the root scrollframe is scrollable
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_drag_scroll.html b/gfx/layers/apz/test/mochitest/helper_drag_scroll.html new file mode 100644 index 0000000000..b81a4fe7ae --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_drag_scroll.html @@ -0,0 +1,633 @@ + + + + + + Dragging the mouse on a content-implemented scrollbar + + + + + + + + +
    Drag up and down on this bar. The background/scrollbar shouldn't glitch
    +This is a tall page
    +1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +49
    +50
    +51
    +52
    +53
    +54
    +55
    +56
    +57
    +58
    +59
    +60
    +61
    +62
    +63
    +64
    +65
    +66
    +67
    +68
    +69
    +70
    +71
    +72
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +95
    +96
    +97
    +98
    +99
    +100
    +101
    +102
    +103
    +104
    +105
    +106
    +107
    +108
    +109
    +110
    +111
    +112
    +113
    +114
    +115
    +116
    +117
    +118
    +119
    +120
    +121
    +122
    +123
    +124
    +125
    +126
    +127
    +128
    +129
    +130
    +131
    +132
    +133
    +134
    +135
    +136
    +137
    +138
    +139
    +140
    +141
    +142
    +143
    +144
    +145
    +146
    +147
    +148
    +149
    +150
    +151
    +152
    +153
    +154
    +155
    +156
    +157
    +158
    +159
    +160
    +161
    +162
    +163
    +164
    +165
    +166
    +167
    +168
    +169
    +170
    +171
    +172
    +173
    +174
    +175
    +176
    +177
    +178
    +179
    +180
    +181
    +182
    +183
    +184
    +185
    +186
    +187
    +188
    +189
    +190
    +191
    +192
    +193
    +194
    +195
    +196
    +197
    +198
    +199
    +200
    +201
    +202
    +203
    +204
    +205
    +206
    +207
    +208
    +209
    +210
    +211
    +212
    +213
    +214
    +215
    +216
    +217
    +218
    +219
    +220
    +221
    +222
    +223
    +224
    +225
    +226
    +227
    +228
    +229
    +230
    +231
    +232
    +233
    +234
    +235
    +236
    +237
    +238
    +239
    +240
    +241
    +242
    +243
    +244
    +245
    +246
    +247
    +248
    +249
    +250
    +251
    +252
    +253
    +254
    +255
    +256
    +257
    +258
    +259
    +260
    +261
    +262
    +263
    +264
    +265
    +266
    +267
    +268
    +269
    +270
    +271
    +272
    +273
    +274
    +275
    +276
    +277
    +278
    +279
    +280
    +281
    +282
    +283
    +284
    +285
    +286
    +287
    +288
    +289
    +290
    +291
    +292
    +293
    +294
    +295
    +296
    +297
    +298
    +299
    +300
    +301
    +302
    +303
    +304
    +305
    +306
    +307
    +308
    +309
    +310
    +311
    +312
    +313
    +314
    +315
    +316
    +317
    +318
    +319
    +320
    +321
    +322
    +323
    +324
    +325
    +326
    +327
    +328
    +329
    +330
    +331
    +332
    +333
    +334
    +335
    +336
    +337
    +338
    +339
    +340
    +341
    +342
    +343
    +344
    +345
    +346
    +347
    +348
    +349
    +350
    +351
    +352
    +353
    +354
    +355
    +356
    +357
    +358
    +359
    +360
    +361
    +362
    +363
    +364
    +365
    +366
    +367
    +368
    +369
    +370
    +371
    +372
    +373
    +374
    +375
    +376
    +377
    +378
    +379
    +380
    +381
    +382
    +383
    +384
    +385
    +386
    +387
    +388
    +389
    +390
    +391
    +392
    +393
    +394
    +395
    +396
    +397
    +398
    +399
    +400
    +401
    +402
    +403
    +404
    +405
    +406
    +407
    +408
    +409
    +410
    +411
    +412
    +413
    +414
    +415
    +416
    +417
    +418
    +419
    +420
    +421
    +422
    +423
    +424
    +425
    +426
    +427
    +428
    +429
    +430
    +431
    +432
    +433
    +434
    +435
    +436
    +437
    +438
    +439
    +440
    +441
    +442
    +443
    +444
    +445
    +446
    +447
    +448
    +449
    +450
    +451
    +452
    +453
    +454
    +455
    +456
    +457
    +458
    +459
    +460
    +461
    +462
    +463
    +464
    +465
    +466
    +467
    +468
    +469
    +470
    +471
    +472
    +473
    +474
    +475
    +476
    +477
    +478
    +479
    +480
    +481
    +482
    +483
    +484
    +485
    +486
    +487
    +488
    +489
    +490
    +491
    +492
    +493
    +494
    +495
    +496
    +497
    +498
    +499
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_fission_animation_styling_in_oopif.html b/gfx/layers/apz/test/mochitest/helper_fission_animation_styling_in_oopif.html new file mode 100644 index 0000000000..317328e62a --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fission_animation_styling_in_oopif.html @@ -0,0 +1,166 @@ + + + + + Test for scrolled out of view animation optimization in an OOPIF + + + + + + +
    +
    + + + +
    + diff --git a/gfx/layers/apz/test/mochitest/helper_fission_animation_styling_in_transformed_oopif.html b/gfx/layers/apz/test/mochitest/helper_fission_animation_styling_in_transformed_oopif.html new file mode 100644 index 0000000000..d85d77f552 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fission_animation_styling_in_transformed_oopif.html @@ -0,0 +1,130 @@ + + + + + Test for scrolled out of view animation optimization in an OOPIF transformed by rotate(45deg) + + + + + + +
    +
    +
    + +
    +
    +
    + diff --git a/gfx/layers/apz/test/mochitest/helper_fission_basic.html b/gfx/layers/apz/test/mochitest/helper_fission_basic.html new file mode 100644 index 0000000000..dbc41477b9 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fission_basic.html @@ -0,0 +1,40 @@ + + + + + Basic sanity test that runs inside a fission-enabled window + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_fission_empty.html b/gfx/layers/apz/test/mochitest/helper_fission_empty.html new file mode 100644 index 0000000000..a782d614e6 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fission_empty.html @@ -0,0 +1,33 @@ + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_fission_event_region_override.html b/gfx/layers/apz/test/mochitest/helper_fission_event_region_override.html new file mode 100644 index 0000000000..41bd88faa9 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fission_event_region_override.html @@ -0,0 +1,84 @@ + + + + + Ensure the event region override flags work properly + + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_fission_force_empty_hit_region.html b/gfx/layers/apz/test/mochitest/helper_fission_force_empty_hit_region.html new file mode 100644 index 0000000000..7baa552c37 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fission_force_empty_hit_region.html @@ -0,0 +1,82 @@ + + + + + Ensure the ForceEmptyHitRegion flag works properly + + + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_fission_inactivescroller_under_oopif.html b/gfx/layers/apz/test/mochitest/helper_fission_inactivescroller_under_oopif.html new file mode 100644 index 0000000000..c3099f52ef --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fission_inactivescroller_under_oopif.html @@ -0,0 +1,88 @@ + + + + + Ensure inactive scollframes under OOPIFs hit-test properly + + + + + + + + + +
    +
    inside scroller
    +
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_fission_scroll_oopif.html b/gfx/layers/apz/test/mochitest/helper_fission_scroll_oopif.html new file mode 100644 index 0000000000..995d1769ad --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fission_scroll_oopif.html @@ -0,0 +1,157 @@ + + + + + Test for async-scrolling an OOPIF and ensuring hit-testing still works + + + + + + + + + +
    tall div to make the page scrollable
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_fission_tap.html b/gfx/layers/apz/test/mochitest/helper_fission_tap.html new file mode 100644 index 0000000000..702a6e1c96 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fission_tap.html @@ -0,0 +1,87 @@ + + + + + Test to ensure events get untransformed properly for OOP iframes + + + + + + + + + +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_fission_tap_in_nested_iframe_on_zoomed.html b/gfx/layers/apz/test/mochitest/helper_fission_tap_in_nested_iframe_on_zoomed.html new file mode 100644 index 0000000000..4d0663788b --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fission_tap_in_nested_iframe_on_zoomed.html @@ -0,0 +1,106 @@ + + + + + Test to ensure events get delivered properly for a nested OOP iframe + + + + + + + + + +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_fission_tap_on_zoomed.html b/gfx/layers/apz/test/mochitest/helper_fission_tap_on_zoomed.html new file mode 100644 index 0000000000..324d93607e --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fission_tap_on_zoomed.html @@ -0,0 +1,93 @@ + + + + + Test to ensure events get delivered properly for an OOP iframe + + + + + + + + + +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_fission_touch.html b/gfx/layers/apz/test/mochitest/helper_fission_touch.html new file mode 100644 index 0000000000..f15abfb3c9 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fission_touch.html @@ -0,0 +1,99 @@ + + + + + Test to ensure touch events for OOP iframes + + + + + + + + + +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_fission_transforms.html b/gfx/layers/apz/test/mochitest/helper_fission_transforms.html new file mode 100644 index 0000000000..9ef46da859 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fission_transforms.html @@ -0,0 +1,85 @@ + + + + + Test to ensure events get untransformed properly for OOP iframes + + + + + + + + + +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_fission_utils.js b/gfx/layers/apz/test/mochitest/helper_fission_utils.js new file mode 100644 index 0000000000..16ddb82181 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fission_utils.js @@ -0,0 +1,130 @@ +// loadOOPIFrame expects apz_test_utils.js to be loaded as well, for promiseOneEvent. +/* import-globals-from apz_test_utils.js */ + +function fission_subtest_init() { + // Silence SimpleTest warning about missing assertions by having it wait + // indefinitely. We don't need to give it an explicit finish because the + // entire window this test runs in will be closed after subtestDone is called. + SimpleTest.waitForExplicitFinish(); + + // This is the point at which we inject the ok, is, subtestDone, etc. functions + // into this window. In particular this function should run after SimpleTest.js + // is imported, otherwise SimpleTest.js will clobber the functions with its + // own versions. This is implicitly enforced because if we call this function + // before SimpleTest.js is imported, the above line will throw an exception. + window.dispatchEvent(new Event("FissionTestHelper:Init")); +} + +/** + * Starts loading the given `iframePage` in the iframe element with the given + * id, and waits for it to load. + * Note that calling this function doesn't do the load directly; instead it + * returns an async function which can be added to a thenable chain. + */ +function loadOOPIFrame(iframeElementId, iframePage) { + return async function() { + if (window.location.href.startsWith("https://example.com/")) { + dump( + `WARNING: Calling loadOOPIFrame from ${window.location.href} so the iframe may not be OOP\n` + ); + ok(false, "Current origin is not example.com:443"); + } + + let url = + "https://example.com/browser/gfx/layers/apz/test/mochitest/" + iframePage; + let loadPromise = promiseOneEvent(window, "OOPIF:Load", function(e) { + return typeof e.data.content == "string" && e.data.content == url; + }); + let elem = document.getElementById(iframeElementId); + elem.src = url; + await loadPromise; + }; +} + +/** + * This is similar to the hitTest function in apz_test_utils.js, in that it + * does a hit-test for a point and returns the result. The difference is that + * in the fission world, the hit-test may land on an OOPIF, which means the + * result information will be in the APZ test data for the OOPIF process. This + * function checks both the current process and OOPIF process to see which one + * got a hit result, and returns the result regardless of which process got it. + * The caller is expected to check the layers id which will allow distinguishing + * the two cases. + */ +async function fissionHitTest(point, iframeElement) { + let get_iframe_compositor_test_data = function() { + let utils = SpecialPowers.getDOMWindowUtils(window); + return JSON.stringify(utils.getCompositorAPZTestData()); + }; + + let utils = SpecialPowers.getDOMWindowUtils(window); + + // Get the test data before doing the actual hit-test, to get a baseline + // of what we can ignore. + let oldParentTestData = utils.getCompositorAPZTestData(); + let oldIframeTestData = JSON.parse( + await FissionTestHelper.sendToOopif( + iframeElement, + `(${get_iframe_compositor_test_data})()` + ) + ); + + // Now do the hit-test + dump(`Hit-testing point (${point.x}, ${point.y}) in fission context\n`); + utils.sendMouseEvent( + "MozMouseHittest", + point.x, + point.y, + 0, + 0, + 0, + true, + 0, + 0, + true, + true + ); + + // Collect the new test data + let newParentTestData = utils.getCompositorAPZTestData(); + let newIframeTestData = JSON.parse( + await FissionTestHelper.sendToOopif( + iframeElement, + `(${get_iframe_compositor_test_data})()` + ) + ); + + // See which test data has new hit results + let hitResultCount = function(testData) { + return Object.keys(testData.hitResults).length; + }; + + let hitIframe = + hitResultCount(newIframeTestData) > hitResultCount(oldIframeTestData); + let hitParent = + hitResultCount(newParentTestData) > hitResultCount(oldParentTestData); + + // Extract the results from the appropriate test data + let lastHitResult = function(testData) { + let lastHit = + testData.hitResults[Object.keys(testData.hitResults).length - 1]; + return { + hitInfo: lastHit.hitResult, + scrollId: lastHit.scrollId, + layersId: lastHit.layersId, + }; + }; + if (hitIframe && hitParent) { + throw new Error( + "Both iframe and parent got hit-results, that is unexpected!" + ); + } else if (hitIframe) { + return lastHitResult(newIframeTestData); + } else if (hitParent) { + return lastHitResult(newParentTestData); + } else { + throw new Error( + "Neither iframe nor parent got the hit-result, that is unexpected!" + ); + } +} diff --git a/gfx/layers/apz/test/mochitest/helper_fixed_pos_displayport.html b/gfx/layers/apz/test/mochitest/helper_fixed_pos_displayport.html new file mode 100644 index 0000000000..9f9cbca1c0 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fixed_pos_displayport.html @@ -0,0 +1,101 @@ + + + + + + position:fixed display port sizing + + + + + + +
    +
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_fixed_position_scroll_hittest.html b/gfx/layers/apz/test/mochitest/helper_fixed_position_scroll_hittest.html new file mode 100644 index 0000000000..f5250c0053 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fixed_position_scroll_hittest.html @@ -0,0 +1,51 @@ + + + + + + Hittest position:fixed zoomed scroll + + + + + + +
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_fullscreen.html b/gfx/layers/apz/test/mochitest/helper_fullscreen.html new file mode 100644 index 0000000000..32de4979f2 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_fullscreen.html @@ -0,0 +1,53 @@ + + + + + + Tests that layout viewport is not larger than visual viewport on fullscreen + + + + + +
    +
    overflowed element
    +
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_backface_hidden.html b/gfx/layers/apz/test/mochitest/helper_hittest_backface_hidden.html new file mode 100644 index 0000000000..0e84282e54 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_backface_hidden.html @@ -0,0 +1,67 @@ + + + + APZ hit-testing with backface-visibility:hidden + + + + + + + +
    +
    +
    +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_basic.html b/gfx/layers/apz/test/mochitest/helper_hittest_basic.html new file mode 100644 index 0000000000..8199be9d75 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_basic.html @@ -0,0 +1,141 @@ + + + + Various tests to exercise the APZ hit-testing codepaths + + + + + + +
    +
    +
    +
    +
    +
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_checkerboard.html b/gfx/layers/apz/test/mochitest/helper_hittest_checkerboard.html new file mode 100644 index 0000000000..05d3928dec --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_checkerboard.html @@ -0,0 +1,59 @@ + + + + APZ hit-testing over a checkerboarded area + + + + + + +
    + +
    +
    +
    +
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_clippath.html b/gfx/layers/apz/test/mochitest/helper_hittest_clippath.html new file mode 100644 index 0000000000..fbf95b77b8 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_clippath.html @@ -0,0 +1,115 @@ + + + + Hit-testing an iframe covered by an element with a clip-path + + + + + + + + + +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_clipped_fixed_modal.html b/gfx/layers/apz/test/mochitest/helper_hittest_clipped_fixed_modal.html new file mode 100644 index 0000000000..122863967a --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_clipped_fixed_modal.html @@ -0,0 +1,85 @@ + + + + Hit-testing on content covered by a fullscreen fixed-position item clipped away + + + + + + + +
    +
    + Filler to make the content div scrollable +
    +
    + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_deep_scene_stack.html b/gfx/layers/apz/test/mochitest/helper_hittest_deep_scene_stack.html new file mode 100644 index 0000000000..a04b1d3e83 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_deep_scene_stack.html @@ -0,0 +1,57 @@ + + + + Exercising the APZ/WR hit-test with a deep scene that produces many results + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_fixed_in_scrolled_transform.html b/gfx/layers/apz/test/mochitest/helper_hittest_fixed_in_scrolled_transform.html new file mode 100644 index 0000000000..be1fd5178d --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_fixed_in_scrolled_transform.html @@ -0,0 +1,88 @@ + + + + Hit-testing on the special setup from fixed-pos-scrolled-clip-3.html + + + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_float_bug1434846.html b/gfx/layers/apz/test/mochitest/helper_hittest_float_bug1434846.html new file mode 100644 index 0000000000..986c7b8477 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_float_bug1434846.html @@ -0,0 +1,56 @@ + + + + APZ hit-testing with floated subframe + + + + + + + +
    +
    +
    +
    +
    +
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_float_bug1443518.html b/gfx/layers/apz/test/mochitest/helper_hittest_float_bug1443518.html new file mode 100644 index 0000000000..0e2e754375 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_float_bug1443518.html @@ -0,0 +1,56 @@ + + + + APZ hit-testing with floated subframe + + + + + + + +
    +
    +
    +
    A line of text that overflows because it's sufficiently long
    +
    +
    +
    +
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_hidden_inactive_scrollframe.html b/gfx/layers/apz/test/mochitest/helper_hittest_hidden_inactive_scrollframe.html new file mode 100644 index 0000000000..bdc20be0f0 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_hidden_inactive_scrollframe.html @@ -0,0 +1,52 @@ + + + + APZ hit-testing with an inactive scrollframe that is visibility:hidden (bug 1673505) + + + + + + +
    +
    +
    + The body of this document is scrollable and is the main scrollable + element. On top of that we have a hidden fixed-pos item containing another + scrollframe, but this nested scrollframe is inactive. + Since the fixed-pos item is hidden, the nested scrollframe is hidden + too and shouldn't be the target of hit-testing. However, because it is + an inactive scrollframe, code to generate the "this is an inactive + scrollframe" area was marking it as hit-testable. This bug led to hit- + tests being mis-targeted to the nested scrollframe's layers id instead + of whatever was underneath. +
    +
    +
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_hoisted_scrollinfo.html b/gfx/layers/apz/test/mochitest/helper_hittest_hoisted_scrollinfo.html new file mode 100644 index 0000000000..b77b6106c4 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_hoisted_scrollinfo.html @@ -0,0 +1,84 @@ + + + + Hit-testing on a scrollframe forced to be inactive by being inside a filter + + + + + + + +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_nested_transforms_bug1459696.html b/gfx/layers/apz/test/mochitest/helper_hittest_nested_transforms_bug1459696.html new file mode 100644 index 0000000000..9d0a9fa50c --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_nested_transforms_bug1459696.html @@ -0,0 +1,80 @@ + + + + APZ hit-testing with nested inactive transforms (bug 1459696) + + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_pointerevents_svg.html b/gfx/layers/apz/test/mochitest/helper_hittest_pointerevents_svg.html new file mode 100644 index 0000000000..22b880736d --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_pointerevents_svg.html @@ -0,0 +1,177 @@ + + + + Hit-testing a scrollframe covered by nonrectangular and pointer-events:none things + + + + + + + + +
    +
    + + + +
    +
    +
    + + + +
    +
    + +
    +
    + + + +
    +
    +
    + + + +
    +
    + +
    +
    + + + +
    +
    +
    + + + +
    +
    + +
    +
    + + + +
    +
    +
    + + + +
    +
    + +
    + Each of the gradients should be scrollable, except where the black stuff on the right cover them. + The brown square should not prevent scrolling. Similarly, the content on the left (which goes + underneath the scroller) shouldn't matter. +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_spam.html b/gfx/layers/apz/test/mochitest/helper_hittest_spam.html new file mode 100644 index 0000000000..305d8677bc --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_spam.html @@ -0,0 +1,100 @@ + + + + Test doing lots of hit-testing on a rapidly changing page + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_sticky_bug1478304.html b/gfx/layers/apz/test/mochitest/helper_hittest_sticky_bug1478304.html new file mode 100644 index 0000000000..395c688a12 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_sticky_bug1478304.html @@ -0,0 +1,58 @@ + + + + APZ hit-testing with sticky element inside a transform (bug 1478304) + + + + + + + +
    +
    +
    sticky with transformed parent (click me or hover me and try a scroll)
    +
    +
    +
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_touchaction.html b/gfx/layers/apz/test/mochitest/helper_hittest_touchaction.html new file mode 100644 index 0000000000..da7577a0db --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_touchaction.html @@ -0,0 +1,364 @@ + + + + Testing APZ hit-test with touch-action boxes + + + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_horizontal_checkerboard.html b/gfx/layers/apz/test/mochitest/helper_horizontal_checkerboard.html new file mode 100644 index 0000000000..0355243738 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_horizontal_checkerboard.html @@ -0,0 +1,65 @@ + + + +Testcase for checkerboarding during horizontal scrolling + + + + + + +
    + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_iframe1.html b/gfx/layers/apz/test/mochitest/helper_iframe1.html new file mode 100644 index 0000000000..047da96bd4 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_iframe1.html @@ -0,0 +1,14 @@ + + + + + + + +
    +
    +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_iframe2.html b/gfx/layers/apz/test/mochitest/helper_iframe2.html new file mode 100644 index 0000000000..fee3883e95 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_iframe2.html @@ -0,0 +1,14 @@ + + + + + + + +
    +
    +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_iframe_pan.html b/gfx/layers/apz/test/mochitest/helper_iframe_pan.html new file mode 100644 index 0000000000..1d91b95c7b --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_iframe_pan.html @@ -0,0 +1,50 @@ + + + + + + Sanity panning test for scrollable div + + + + + + + +
    + This div makes the top-level page scrollable. +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_iframe_textarea.html b/gfx/layers/apz/test/mochitest/helper_iframe_textarea.html new file mode 100644 index 0000000000..f4a934db74 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_iframe_textarea.html @@ -0,0 +1,12 @@ + + + + + + +
    ABC
    + + +
    ABC
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_key_scroll.html b/gfx/layers/apz/test/mochitest/helper_key_scroll.html new file mode 100644 index 0000000000..a8e4eadce2 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_key_scroll.html @@ -0,0 +1,118 @@ + + + + + + Async key scrolling test, helper page + + + + + + + Async key scrolling test + +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_long_tap.html b/gfx/layers/apz/test/mochitest/helper_long_tap.html new file mode 100644 index 0000000000..9eb3f952a5 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_long_tap.html @@ -0,0 +1,108 @@ + + + + + + Ensure we get a touch-cancel after a contextmenu comes up + + + + + + + Link to nowhere + + diff --git a/gfx/layers/apz/test/mochitest/helper_minimum_scale_1_0.html b/gfx/layers/apz/test/mochitest/helper_minimum_scale_1_0.html new file mode 100644 index 0000000000..17ccb3a54d --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_minimum_scale_1_0.html @@ -0,0 +1,46 @@ + + + + + + Tests that the layout viewport is expanted to the minimum scale size (minimim-scale >= 1.0) + + + + + +
    +
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_no_scalable_with_initial_scale.html b/gfx/layers/apz/test/mochitest/helper_no_scalable_with_initial_scale.html new file mode 100644 index 0000000000..7280a26006 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_no_scalable_with_initial_scale.html @@ -0,0 +1,48 @@ + + + + + + Tests that the layout viewport is not expanted to the minimum scale size if user-scalable=no is specified + + + + + +
    +
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_onetouchpinch_nested.html b/gfx/layers/apz/test/mochitest/helper_onetouchpinch_nested.html new file mode 100644 index 0000000000..5ad454f7b1 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_onetouchpinch_nested.html @@ -0,0 +1,103 @@ + + + + + + One-touch pinch zooming while on a non-root scroller + + + + + + + + + Here is some text outside the scrollable div. +
    + Here is some text inside the scrollable div. +
    This div actually makes it overflow.
    +
    +
    This div makes the body scrollable.
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_overflowhidden_zoom.html b/gfx/layers/apz/test/mochitest/helper_overflowhidden_zoom.html new file mode 100644 index 0000000000..16236b850e --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_overflowhidden_zoom.html @@ -0,0 +1,83 @@ + + + + + + Tests that zooming in and out doesn't change the scroll position on an overflow hidden document + + + + + + +
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_override_root.html b/gfx/layers/apz/test/mochitest/helper_override_root.html new file mode 100644 index 0000000000..7726b7afa3 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_override_root.html @@ -0,0 +1,60 @@ + + + + + + Simple wheel scroll cancellation + + + + + + + This page should not be wheel-scrollable. + + diff --git a/gfx/layers/apz/test/mochitest/helper_override_subdoc.html b/gfx/layers/apz/test/mochitest/helper_override_subdoc.html new file mode 100644 index 0000000000..910f36ddc3 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_override_subdoc.html @@ -0,0 +1,15 @@ + + + + + + Wheel scroll cancellation inside iframe + + + + + This just loads helper_override_root in an iframe, so that we test event + regions overriding on in-process subdocuments. + + + diff --git a/gfx/layers/apz/test/mochitest/helper_overscroll_behavior_bug1425573.html b/gfx/layers/apz/test/mochitest/helper_overscroll_behavior_bug1425573.html new file mode 100644 index 0000000000..817522f9c3 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_overscroll_behavior_bug1425573.html @@ -0,0 +1,44 @@ + + + Wheel-scrolling over inactive subframe with overscroll-behavior + + + + + + + +
    +
    +
    +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_overscroll_behavior_bug1425603.html b/gfx/layers/apz/test/mochitest/helper_overscroll_behavior_bug1425603.html new file mode 100644 index 0000000000..7be903d543 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_overscroll_behavior_bug1425603.html @@ -0,0 +1,78 @@ + + + + Scrolling over checkerboarded area respects overscroll-behavior + + + + + + + +
    +
    +
    +
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_overscroll_behavior_bug1494440.html b/gfx/layers/apz/test/mochitest/helper_overscroll_behavior_bug1494440.html new file mode 100644 index 0000000000..b7b71e2f25 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_overscroll_behavior_bug1494440.html @@ -0,0 +1,51 @@ + + + Inactive iframe with overscroll-behavior + + + + + + +
    + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_scroll_anchoring_smooth_scroll.html b/gfx/layers/apz/test/mochitest/helper_scroll_anchoring_smooth_scroll.html new file mode 100644 index 0000000000..7bdfa83f24 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scroll_anchoring_smooth_scroll.html @@ -0,0 +1,54 @@ + + + +Tests scroll anchoring interaction with smooth visual scrolling. + + + + +
    +
    + diff --git a/gfx/layers/apz/test/mochitest/helper_scroll_inactive_perspective.html b/gfx/layers/apz/test/mochitest/helper_scroll_inactive_perspective.html new file mode 100644 index 0000000000..3b45c47c81 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scroll_inactive_perspective.html @@ -0,0 +1,46 @@ + + + Wheel-scrolling over inactive subframe with perspective + + + + + + + +
    +
    +
    +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_scroll_inactive_zindex.html b/gfx/layers/apz/test/mochitest/helper_scroll_inactive_zindex.html new file mode 100644 index 0000000000..8c3a0623fa --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scroll_inactive_zindex.html @@ -0,0 +1,47 @@ + + + Wheel-scrolling over inactive subframe with z-index + + + + + + + +
    +
    +
    +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_scroll_into_view_bug1516056.html b/gfx/layers/apz/test/mochitest/helper_scroll_into_view_bug1516056.html new file mode 100644 index 0000000000..d2a09f9835 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scroll_into_view_bug1516056.html @@ -0,0 +1,73 @@ + + + + + + Test for bug 1516056: "scroll into view" respects bounds on layout scroll position + + + + + +
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_scroll_into_view_bug1562757.html b/gfx/layers/apz/test/mochitest/helper_scroll_into_view_bug1562757.html new file mode 100644 index 0000000000..8da19c4341 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scroll_into_view_bug1562757.html @@ -0,0 +1,75 @@ + + + + + + Test for bug 1562757: "scroll into view" in iframe respects bounds on layout scroll position + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_scroll_on_position_fixed.html b/gfx/layers/apz/test/mochitest/helper_scroll_on_position_fixed.html new file mode 100644 index 0000000000..026e690de2 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scroll_on_position_fixed.html @@ -0,0 +1,61 @@ + + + Wheel-scrolling over position:fixed and position:sticky elements, in the top-level document as well as iframes + + + + + + +
    sticky
    +
    fixed
    + + +
    +
    scrollable content inside a fixed-pos item
    +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_scroll_over_scrollbar.html b/gfx/layers/apz/test/mochitest/helper_scroll_over_scrollbar.html new file mode 100644 index 0000000000..9ad85b40bd --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scroll_over_scrollbar.html @@ -0,0 +1,49 @@ + + + Wheel-scrolling over scrollbar + + + + + + + +
    +
    +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_scroll_snap_no_valid_snap_position.html b/gfx/layers/apz/test/mochitest/helper_scroll_snap_no_valid_snap_position.html new file mode 100644 index 0000000000..00f5e7d344 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scroll_snap_no_valid_snap_position.html @@ -0,0 +1,45 @@ + + + + + + No snapping occurs if there is no valid snap position + + + + + + +
    +
    +
    +
    +
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_scroll_tables_perspective.html b/gfx/layers/apz/test/mochitest/helper_scroll_tables_perspective.html new file mode 100644 index 0000000000..abd6ddc39a --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scroll_tables_perspective.html @@ -0,0 +1,67 @@ + + + + + + + + + +
    +
    +
    +
    +
    + A
    + B
    + C
    + D
    + E
    + f
    + g
    + h
    + i
    + j
    +
    +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_scrollbar_snap_bug1501062.html b/gfx/layers/apz/test/mochitest/helper_scrollbar_snap_bug1501062.html new file mode 100644 index 0000000000..d6fb4ced36 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scrollbar_snap_bug1501062.html @@ -0,0 +1,105 @@ + + + + + + Exercising the slider.snapMultiplier code + + + + + +
    +
    +
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_scrollby_bug1531796.html b/gfx/layers/apz/test/mochitest/helper_scrollby_bug1531796.html new file mode 100644 index 0000000000..5e6f0e1833 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scrollby_bug1531796.html @@ -0,0 +1,36 @@ + + + + + + Test that scrollBy() doesn't scroll more than it should + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_scrollframe_activation_on_load.html b/gfx/layers/apz/test/mochitest/helper_scrollframe_activation_on_load.html new file mode 100644 index 0000000000..469880823e --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scrollframe_activation_on_load.html @@ -0,0 +1,69 @@ + + + + + + Test for Bug 1151663, helper page + + + + + + Mozilla Bug 1151663 +
    + +
    +
    + +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_scrollto_tap.html b/gfx/layers/apz/test/mochitest/helper_scrollto_tap.html new file mode 100644 index 0000000000..5941047377 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scrollto_tap.html @@ -0,0 +1,61 @@ + + + + + + Sanity touch-tapping test + + + + + + +
    spacer
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_self_closer.html b/gfx/layers/apz/test/mochitest/helper_self_closer.html new file mode 100644 index 0000000000..09a9286c06 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_self_closer.html @@ -0,0 +1,12 @@ + + + + + + +See ya! (This window will close itself) diff --git a/gfx/layers/apz/test/mochitest/helper_smoothscroll_spam.html b/gfx/layers/apz/test/mochitest/helper_smoothscroll_spam.html new file mode 100644 index 0000000000..c56b0d6957 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_smoothscroll_spam.html @@ -0,0 +1,51 @@ + + + + + + Test for scenario in bug 1228407 + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_smoothscroll_spam_interleaved.html b/gfx/layers/apz/test/mochitest/helper_smoothscroll_spam_interleaved.html new file mode 100644 index 0000000000..0fad1d3672 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_smoothscroll_spam_interleaved.html @@ -0,0 +1,57 @@ + + + + + + Test for scenario in bug 1228407 with two scrollframes + + + + + + + +
    +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_subframe_style.css b/gfx/layers/apz/test/mochitest/helper_subframe_style.css new file mode 100644 index 0000000000..5af9640802 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_subframe_style.css @@ -0,0 +1,15 @@ +body { + height: 500px; +} + +.inner-frame { + margin-top: 50px; /* this should be at least 30px */ + height: 200%; + width: 75%; + overflow: scroll; +} +.inner-content { + height: 200%; + width: 200%; + background: repeating-linear-gradient(#EEE, #EEE 100px, #DDD 100px, #DDD 200px); +} diff --git a/gfx/layers/apz/test/mochitest/helper_tall.html b/gfx/layers/apz/test/mochitest/helper_tall.html new file mode 100644 index 0000000000..7fde795fdc --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_tall.html @@ -0,0 +1,504 @@ + + +This is a tall page
    +1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +49
    +50
    +51
    +52
    +53
    +54
    +55
    +56
    +57
    +58
    +59
    +60
    +61
    +62
    +63
    +64
    +65
    +66
    +67
    +68
    +69
    +70
    +71
    +72
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +95
    +96
    +97
    +98
    +99
    +100
    +101
    +102
    +103
    +104
    +105
    +106
    +107
    +108
    +109
    +110
    +111
    +112
    +113
    +114
    +115
    +116
    +117
    +118
    +119
    +120
    +121
    +122
    +123
    +124
    +125
    +126
    +127
    +128
    +129
    +130
    +131
    +132
    +133
    +134
    +135
    +136
    +137
    +138
    +139
    +140
    +141
    +142
    +143
    +144
    +145
    +146
    +147
    +148
    +149
    +150
    +151
    +152
    +153
    +154
    +155
    +156
    +157
    +158
    +159
    +160
    +161
    +162
    +163
    +164
    +165
    +166
    +167
    +168
    +169
    +170
    +171
    +172
    +173
    +174
    +175
    +176
    +177
    +178
    +179
    +180
    +181
    +182
    +183
    +184
    +185
    +186
    +187
    +188
    +189
    +190
    +191
    +192
    +193
    +194
    +195
    +196
    +197
    +198
    +199
    +200
    +201
    +202
    +203
    +204
    +205
    +206
    +207
    +208
    +209
    +210
    +211
    +212
    +213
    +214
    +215
    +216
    +217
    +218
    +219
    +220
    +221
    +222
    +223
    +224
    +225
    +226
    +227
    +228
    +229
    +230
    +231
    +232
    +233
    +234
    +235
    +236
    +237
    +238
    +239
    +240
    +241
    +242
    +243
    +244
    +245
    +246
    +247
    +248
    +249
    +250
    +251
    +252
    +253
    +254
    +255
    +256
    +257
    +258
    +259
    +260
    +261
    +262
    +263
    +264
    +265
    +266
    +267
    +268
    +269
    +270
    +271
    +272
    +273
    +274
    +275
    +276
    +277
    +278
    +279
    +280
    +281
    +282
    +283
    +284
    +285
    +286
    +287
    +288
    +289
    +290
    +291
    +292
    +293
    +294
    +295
    +296
    +297
    +298
    +299
    +300
    +301
    +302
    +303
    +304
    +305
    +306
    +307
    +308
    +309
    +310
    +311
    +312
    +313
    +314
    +315
    +316
    +317
    +318
    +319
    +320
    +321
    +322
    +323
    +324
    +325
    +326
    +327
    +328
    +329
    +330
    +331
    +332
    +333
    +334
    +335
    +336
    +337
    +338
    +339
    +340
    +341
    +342
    +343
    +344
    +345
    +346
    +347
    +348
    +349
    +350
    +351
    +352
    +353
    +354
    +355
    +356
    +357
    +358
    +359
    +360
    +361
    +362
    +363
    +364
    +365
    +366
    +367
    +368
    +369
    +370
    +371
    +372
    +373
    +374
    +375
    +376
    +377
    +378
    +379
    +380
    +381
    +382
    +383
    +384
    +385
    +386
    +387
    +388
    +389
    +390
    +391
    +392
    +393
    +394
    +395
    +396
    +397
    +398
    +399
    +400
    +401
    +402
    +403
    +404
    +405
    +406
    +407
    +408
    +409
    +410
    +411
    +412
    +413
    +414
    +415
    +416
    +417
    +418
    +419
    +420
    +421
    +422
    +423
    +424
    +425
    +426
    +427
    +428
    +429
    +430
    +431
    +432
    +433
    +434
    +435
    +436
    +437
    +438
    +439
    +440
    +441
    +442
    +443
    +444
    +445
    +446
    +447
    +448
    +449
    +450
    +451
    +452
    +453
    +454
    +455
    +456
    +457
    +458
    +459
    +460
    +461
    +462
    +463
    +464
    +465
    +466
    +467
    +468
    +469
    +470
    +471
    +472
    +473
    +474
    +475
    +476
    +477
    +478
    +479
    +480
    +481
    +482
    +483
    +484
    +485
    +486
    +487
    +488
    +489
    +490
    +491
    +492
    +493
    +494
    +495
    +496
    +497
    +498
    +499
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_tap.html b/gfx/layers/apz/test/mochitest/helper_tap.html new file mode 100644 index 0000000000..13e625692a --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_tap.html @@ -0,0 +1,32 @@ + + + + + + Sanity touch-tapping test + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_tap_default_passive.html b/gfx/layers/apz/test/mochitest/helper_tap_default_passive.html new file mode 100644 index 0000000000..b16b23758a --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_tap_default_passive.html @@ -0,0 +1,79 @@ + + + + + + Ensure APZ doesn't wait for passive listeners + + + + + + + Link to nowhere + + + diff --git a/gfx/layers/apz/test/mochitest/helper_tap_fullzoom.html b/gfx/layers/apz/test/mochitest/helper_tap_fullzoom.html new file mode 100644 index 0000000000..1feb2661e1 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_tap_fullzoom.html @@ -0,0 +1,33 @@ + + + + + + Sanity touch-tapping test with fullzoom + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_tap_passive.html b/gfx/layers/apz/test/mochitest/helper_tap_passive.html new file mode 100644 index 0000000000..481cfed9c2 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_tap_passive.html @@ -0,0 +1,64 @@ + + + + + + Ensure APZ doesn't wait for passive listeners + + + + + + + Link to nowhere + + diff --git a/gfx/layers/apz/test/mochitest/helper_test_reset_scaling_zoom.html b/gfx/layers/apz/test/mochitest/helper_test_reset_scaling_zoom.html new file mode 100644 index 0000000000..e925a8ba72 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_test_reset_scaling_zoom.html @@ -0,0 +1,23 @@ + + + + + + + + +Here is some text to stare at as the test runs. It serves no functional +purpose, but gives you an idea of the zoom level. It's harder to tell what +the zoom level is when the page is just solid white. + diff --git a/gfx/layers/apz/test/mochitest/helper_test_select_zoom.html b/gfx/layers/apz/test/mochitest/helper_test_select_zoom.html new file mode 100644 index 0000000000..b99cf9a005 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_test_select_zoom.html @@ -0,0 +1,45 @@ + + + + + + + + +Here is some text to stare at as the test runs. It serves no functional +purpose, but gives you an idea of the zoom level. It's harder to tell what +the zoom level is when the page is just solid white. + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_touch_action.html b/gfx/layers/apz/test/mochitest/helper_touch_action.html new file mode 100644 index 0000000000..9385bf025c --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_touch_action.html @@ -0,0 +1,123 @@ + + + + + + Sanity touch-action test + + + + + + +
    + This div makes the page scrollable on both axes.
    + This is the second line of text.
    + This is the third line of text.
    + This is the fourth line of text. +
    + +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_touch_action_complex.html b/gfx/layers/apz/test/mochitest/helper_touch_action_complex.html new file mode 100644 index 0000000000..cc6413027c --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_touch_action_complex.html @@ -0,0 +1,137 @@ + + + + + + Complex touch-action test + + + + + + +
    +
    +
    + + + + +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_touch_action_regions.html b/gfx/layers/apz/test/mochitest/helper_touch_action_regions.html new file mode 100644 index 0000000000..c7b302f72e --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_touch_action_regions.html @@ -0,0 +1,288 @@ + + + + + + Test to ensure APZ doesn't always wait for touch-action + + + + + + +
    +
    + This is a colored div that will move on the screen as the scroller scrolls. +
    +
    + This is a large div to make the scroller scrollable. +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_touch_action_zero_opacity_bug1500864.html b/gfx/layers/apz/test/mochitest/helper_touch_action_zero_opacity_bug1500864.html new file mode 100644 index 0000000000..99a79e697b --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_touch_action_zero_opacity_bug1500864.html @@ -0,0 +1,45 @@ + + + + + + Touch-action on a zero-opacity element + + + + + + +
    + Inside the black border is a zero-opacity touch-action none. +
    +
    +
    +
    this text shouldn't be visible
    +
    +
    +
    +
    + + diff --git a/gfx/layers/apz/test/mochitest/helper_visual_scrollbars_pagescroll.html b/gfx/layers/apz/test/mochitest/helper_visual_scrollbars_pagescroll.html new file mode 100644 index 0000000000..9146fc3ac5 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_visual_scrollbars_pagescroll.html @@ -0,0 +1,97 @@ + + + + + + Clicking on the scrollbar track in quick succession should scroll the right amount + + + + + + + + +
    +
    + The above div is sized to 3x screen height so the linear gradient is more steep in terms of + color/pixel. We only scroll a few pages worth so we don't need the gradient all the way down. + And then we use a bottom-margin to make the page really big so the scrollthumb is + relatively small, giving us lots of space to click on the scrolltrack. + + diff --git a/gfx/layers/apz/test/mochitest/helper_visual_smooth_scroll.html b/gfx/layers/apz/test/mochitest/helper_visual_smooth_scroll.html new file mode 100644 index 0000000000..9d278ee23c --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_visual_smooth_scroll.html @@ -0,0 +1,53 @@ + + + + + + Tests that the (internal) visual smooth scrolling API is not restricted to the layout scroll range + + + + + + +
    +
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_visualscroll_clamp_restore.html b/gfx/layers/apz/test/mochitest/helper_visualscroll_clamp_restore.html new file mode 100644 index 0000000000..88c2909a19 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_visualscroll_clamp_restore.html @@ -0,0 +1,68 @@ + + + +Tests scroll position is properly synchronized when visual position is temporarily clamped on the main thread + + + +
    This test runs automatically in automation. To run manually, follow the steps: 1. scroll all the way down
    +
    3. move the mouse. this div should have a hover effect exactly when the mouse is on top of it
    +
    + diff --git a/gfx/layers/apz/test/mochitest/helper_visualscroll_nonrcd_rsf.html b/gfx/layers/apz/test/mochitest/helper_visualscroll_nonrcd_rsf.html new file mode 100644 index 0000000000..9955e49e2a --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_visualscroll_nonrcd_rsf.html @@ -0,0 +1,88 @@ + + + +Tests that pending visual scroll positions on RSFs of non-RCDs get cleared properly + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_iframe.html b/gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_iframe.html new file mode 100644 index 0000000000..78494d4d04 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_iframe.html @@ -0,0 +1,54 @@ + + + + Checking zoomToFocusedInput scrolls that focused element is into iframe + + + + +
    ABC
    + +
    + +
    ABC
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_multiline.html b/gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_multiline.html new file mode 100644 index 0000000000..7b6c2af502 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_multiline.html @@ -0,0 +1,75 @@ + + + + Checking zoomToFocusedInput scrolls that focused non-input element is visible position + + + + +
    ABC
    +
    +
    + +
    ABC
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_scroll.html b/gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_scroll.html new file mode 100644 index 0000000000..a15b34b8ae --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_zoomToFocusedInput_scroll.html @@ -0,0 +1,53 @@ + + + + Checking zoomToFocusedInput scrolls that focused input element is visible position + + + + +
    ABC
    + + +
    ABC
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_zoom_keyboardscroll.html b/gfx/layers/apz/test/mochitest/helper_zoom_keyboardscroll.html new file mode 100644 index 0000000000..91f60add97 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_zoom_keyboardscroll.html @@ -0,0 +1,70 @@ + + + + + + Tests that keyboard arrow keys scroll after zooming in when there was no scrollable overflow before zooming + + + + + + +
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_zoom_out_clamped_scrollpos.html b/gfx/layers/apz/test/mochitest/helper_zoom_out_clamped_scrollpos.html new file mode 100644 index 0000000000..ee8fe68050 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_zoom_out_clamped_scrollpos.html @@ -0,0 +1,85 @@ + + + + + + Tests that zooming out with an unchanging scroll pos still works properly + + + + + + +
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_zoom_out_with_mainthread_clamping.html b/gfx/layers/apz/test/mochitest/helper_zoom_out_with_mainthread_clamping.html new file mode 100644 index 0000000000..c4d7048b68 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_zoom_out_with_mainthread_clamping.html @@ -0,0 +1,106 @@ + + + + + + Tests that zooming out in a way that triggers main-thread scroll re-clamping works properly + + + + + + +
    + + + diff --git a/gfx/layers/apz/test/mochitest/helper_zoom_prevented.html b/gfx/layers/apz/test/mochitest/helper_zoom_prevented.html new file mode 100644 index 0000000000..54eadc01df --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_zoom_prevented.html @@ -0,0 +1,75 @@ + + + + + + Checking prevent-default for zooming + + + + + + + Here is some text to stare at as the test runs. It serves no functional + purpose, but gives you an idea of the zoom level. It's harder to tell what + the zoom level is when the page is just solid white. + + diff --git a/gfx/layers/apz/test/mochitest/helper_zoom_restore_position_tabswitch.html b/gfx/layers/apz/test/mochitest/helper_zoom_restore_position_tabswitch.html new file mode 100644 index 0000000000..0373b321da --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_zoom_restore_position_tabswitch.html @@ -0,0 +1,74 @@ + + + + + + Switching tabs back to a zoomed page should restore visual offset + + + + + + + + Here is some text to stare at as the test runs. It serves no functional + purpose, but gives you an idea of the zoom level. It's harder to tell what + the zoom level is when the page is just solid white. + + diff --git a/gfx/layers/apz/test/mochitest/helper_zoom_with_dynamic_toolbar.html b/gfx/layers/apz/test/mochitest/helper_zoom_with_dynamic_toolbar.html new file mode 100644 index 0000000000..261bca1377 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_zoom_with_dynamic_toolbar.html @@ -0,0 +1,45 @@ + + + + + + Zooming out to the initial scale with the dynamic toolbar + + + + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_zoomed_pan.html b/gfx/layers/apz/test/mochitest/helper_zoomed_pan.html new file mode 100644 index 0000000000..98547fb73f --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_zoomed_pan.html @@ -0,0 +1,79 @@ + + + + + + Ensure layout viewport responds to panning while pinched + + + + + + +
    + + + diff --git a/gfx/layers/apz/test/mochitest/mochitest.ini b/gfx/layers/apz/test/mochitest/mochitest.ini new file mode 100644 index 0000000000..7cb2663efe --- /dev/null +++ b/gfx/layers/apz/test/mochitest/mochitest.ini @@ -0,0 +1,113 @@ +[DEFAULT] + prefs = + testing.paint_listener.debug=true + support-files = + apz_test_native_event_utils.js + apz_test_utils.js + helper_*.* + tags = apz +[test_bug982141.html] +[test_group_scrollframe_activation.html] +[test_bug1151667.html] + skip-if = + os == 'android' # wheel events not supported on mobile + xorigin # JavaScript error: http://mochi.test:8888/tests/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js, line 10: Error: Permission denied to access property "getResolution" +[test_bug1253683.html] + skip-if = + os == 'android' # wheel events not supported on mobile + verify && debug && os == 'linux' + xorigin # JavaScript error: http://mochi.test:8888/tests/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js, line 10: Error: Permission denied to access property "getResolution" +[test_bug1277814.html] + skip-if = + os == 'android' # wheel events not supported on mobile + xorigin # JavaScript error: http://mochi.test:8888/tests/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js, line 10: Error: Permission denied to access property "getResolution" +[test_bug1304689.html] +[test_bug1304689-2.html] +[test_group_bug1464568.html] + skip-if = xorigin # Hangs +[test_frame_reconstruction.html] +[test_group_fullscreen.html] + run-if = (os == 'android') +[test_group_mainthread.html] + skip-if = xorigin # Hangs +[test_group_minimum_scale_size.html] + run-if = (os == 'android') +[test_group_mouseevents.html] + skip-if = + toolkit == 'android' # mouse events not supported on mobile + xorigin # Fails and hangs - incorrect coordinates, scroll positions don't persist +[test_group_pointerevents.html] + skip-if = (os == 'win' && os_version == '10.0') # Bug 1404836 +[test_group_touchevents.html] + skip-if = + verify && debug && os == 'win' + xorigin # Hangs +[test_group_touchevents-2.html] + skip-if = (verify && debug && (os == 'win')) +[test_group_touchevents-3.html] + skip-if = (verify && debug && (os == 'win')) +[test_group_touchevents-4.html] + skip-if = (verify && debug && (os == 'win')) +[test_group_touchevents-5.html] + skip-if = (verify && debug && (os == 'win')) +[test_group_wheelevents.html] + skip-if = (toolkit == 'android') # wheel events not supported on mobile +[test_group_zoom.html] + skip-if = + os == 'win' # see bug 1495580 for Windows + xorigin # Incorrect coordinates, scroll positions don't persist +[test_group_zoom-2.html] + skip-if = (os == 'win') # see bug 1495580 for Windows +[test_group_double_tap_zoom.html] + run-if = (os == 'android') # FIXME: enable on desktop (see bug 1608506 comment 4) +[test_interrupted_reflow.html] + fail-if = xorigin # Incorrect coordinates, scroll positions don't persist +[test_group_keyboard.html] +[test_layerization.html] + skip-if = + os == 'android' # wheel events not supported on mobile + xorigin # JavaScript error: http://mochi.test:8888/tests/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js, line 10: Error: Permission denied to access property "getResolution" +[test_relative_update.html] + skip-if = + os == 'android' # wheel events not supported on mobile + xorigin # JavaScript error: http://mochi.test:8888/tests/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js, line 10: Error: Permission denied to access property "getResolution" +[test_scroll_inactive_bug1190112.html] + skip-if = (os == 'android') # wheel events not supported on mobile +[test_scroll_inactive_flattened_frame.html] + skip-if = (os == 'android') # wheel events not supported on mobile +[test_scroll_subframe_scrollbar.html] + skip-if = (os == 'android') # wheel events not supported on mobile +[test_touch_listeners_impacting_wheel.html] + skip-if = + toolkit == 'android' # wheel events not supported on mobile + toolkit == 'cocoa' # synthesized wheel smooth-scrolling not supported on OS X + xorigin # JavaScript error: http://mochi.test:8888/tests/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js, line 10: Error: Permission denied to access property "getResolution" +[test_wheel_scroll.html] + skip-if = + os == 'android' # wheel events not supported on mobile + xorigin # Hangs +[test_wheel_transactions.html] + skip-if = + toolkit == 'android' # wheel events not supported on mobile + xorigin # Hangs +[test_group_overrides.html] + skip-if = + toolkit == 'android' # wheel events not supported on mobile + xorigin # Hangs +[test_group_hittest.html] + skip-if = + toolkit == 'android' # mouse events not supported on mobile + xorigin # Hangs +[test_group_zoomToFocusedInput.html] + skip-if = + xorigin # Bug 1681211 +[test_group_scroll_snap.html] + skip-if = (os == 'android') # wheel events not supported on mobile +[test_group_checkerboarding.html] +[test_smoothness.html] + # hardware vsync only on win/mac + # e10s only since APZ is only enabled on e10s + # Frame Uniformity recording is not implemented for webrender + skip-if = + debug || (os != 'mac' && os != 'win') || !e10s || verify || webrender + true # Don't run in CI yet, see bug 1657477 diff --git a/gfx/layers/apz/test/mochitest/test_bug1151667.html b/gfx/layers/apz/test/mochitest/test_bug1151667.html new file mode 100644 index 0000000000..bb6fa4f497 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_bug1151667.html @@ -0,0 +1,65 @@ + + + + + Test for Bug 1151667 + + + + + + + + + +Mozilla Bug 1151667 +

    +
    + +
    +
    + +
    +
    +
    +
    + + diff --git a/gfx/layers/apz/test/mochitest/test_bug1253683.html b/gfx/layers/apz/test/mochitest/test_bug1253683.html new file mode 100644 index 0000000000..3f12ab1d1a --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_bug1253683.html @@ -0,0 +1,59 @@ + + + + + Test to ensure non-scrollable frames don't get layerized + + + + + + + + +

    +
    +
    sample code here
    +
    spacer to make the 'container' div the root scrollable element
    +
    +
    +
    +
    + + diff --git a/gfx/layers/apz/test/mochitest/test_bug1277814.html b/gfx/layers/apz/test/mochitest/test_bug1277814.html new file mode 100644 index 0000000000..34c00c7c3d --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_bug1277814.html @@ -0,0 +1,105 @@ + + + + + + Test for Bug 1277814 + + + + + + + + + + +
    + CoolCmd
    CoolCmd
    CoolCmd
    CoolCmd
    + CoolCmd
    CoolCmd
    CoolCmd
    CoolCmd
    + CoolCmd
    CoolCmd
    CoolCmd
    CoolCmd
    + CoolCmd
    CoolCmd
    CoolCmd
    CoolCmd
    + CoolCmd
    CoolCmd
    CoolCmd
    CoolCmd
    + + + diff --git a/gfx/layers/apz/test/mochitest/test_bug1304689-2.html b/gfx/layers/apz/test/mochitest/test_bug1304689-2.html new file mode 100644 index 0000000000..419905a10a --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_bug1304689-2.html @@ -0,0 +1,130 @@ + + + + + + Test for Bug 1285070 + + + + + + + + +
    +
    + this is some scrollable text.
    + this is a second line to make the scrolling more obvious.
    + and a third for good measure.
    + this is some scrollable text.
    + this is a second line to make the scrolling more obvious.
    + and a third for good measure.
    + this is some scrollable text.
    + this is a second line to make the scrolling more obvious.
    + and a third for good measure.
    + this is some scrollable text.
    + this is a second line to make the scrolling more obvious.
    + and a third for good measure.
    + this is some scrollable text.
    + this is a second line to make the scrolling more obvious.
    + and a third for good measure.
    + this is some scrollable text.
    + this is a second line to make the scrolling more obvious.
    + and a third for good measure.
    + this is some scrollable text.
    + this is a second line to make the scrolling more obvious.
    + and a third for good measure.
    + this is some scrollable text.
    + this is a second line to make the scrolling more obvious.
    + and a third for good measure.
    + this is some scrollable text.
    + this is a second line to make the scrolling more obvious.
    + and a third for good measure.
    + this is some scrollable text.
    + this is a second line to make the scrolling more obvious.
    + and a third for good measure.
    + this is some scrollable text.
    + this is a second line to make the scrolling more obvious.
    + and a third for good measure.
    + this is some scrollable text.
    + this is a second line to make the scrolling more obvious.
    + and a third for good measure.
    + this is some scrollable text.
    + this is a second line to make the scrolling more obvious.
    + and a third for good measure.
    + this is some scrollable text.
    + this is a second line to make the scrolling more obvious.
    + and a third for good measure.
    + this is some scrollable text.
    + this is a second line to make the scrolling more obvious.
    + and a third for good measure.
    +
    +
    + + diff --git a/gfx/layers/apz/test/mochitest/test_bug1304689.html b/gfx/layers/apz/test/mochitest/test_bug1304689.html new file mode 100644 index 0000000000..b36415068b --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_bug1304689.html @@ -0,0 +1,134 @@ + + + + + + Test for Bug 1285070 + + + + + + + + +
    +
    + this is some scrollable text.
    + this is a second line to make the scrolling more obvious.
    + and a third for good measure.
    + this is some scrollable text.
    + this is a second line to make the scrolling more obvious.
    + and a third for good measure.
    + this is some scrollable text.
    + this is a second line to make the scrolling more obvious.
    + and a third for good measure.
    + this is some scrollable text.
    + this is a second line to make the scrolling more obvious.
    + and a third for good measure.
    + this is some scrollable text.
    + this is a second line to make the scrolling more obvious.
    + and a third for good measure.
    + this is some scrollable text.
    + this is a second line to make the scrolling more obvious.
    + and a third for good measure.
    + this is some scrollable text.
    + this is a second line to make the scrolling more obvious.
    + and a third for good measure.
    + this is some scrollable text.
    + this is a second line to make the scrolling more obvious.
    + and a third for good measure.
    + this is some scrollable text.
    + this is a second line to make the scrolling more obvious.
    + and a third for good measure.
    + this is some scrollable text.
    + this is a second line to make the scrolling more obvious.
    + and a third for good measure.
    + this is some scrollable text.
    + this is a second line to make the scrolling more obvious.
    + and a third for good measure.
    + this is some scrollable text.
    + this is a second line to make the scrolling more obvious.
    + and a third for good measure.
    + this is some scrollable text.
    + this is a second line to make the scrolling more obvious.
    + and a third for good measure.
    + this is some scrollable text.
    + this is a second line to make the scrolling more obvious.
    + and a third for good measure.
    + this is some scrollable text.
    + this is a second line to make the scrolling more obvious.
    + and a third for good measure.
    +
    +
    + + diff --git a/gfx/layers/apz/test/mochitest/test_bug982141.html b/gfx/layers/apz/test/mochitest/test_bug982141.html new file mode 100644 index 0000000000..837d390e1d --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_bug982141.html @@ -0,0 +1,38 @@ + + + + + + Test for Bug 982141 + + + + + + + Mozilla Bug 982141 + + diff --git a/gfx/layers/apz/test/mochitest/test_frame_reconstruction.html b/gfx/layers/apz/test/mochitest/test_frame_reconstruction.html new file mode 100644 index 0000000000..427180357b --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_frame_reconstruction.html @@ -0,0 +1,218 @@ + + + + + Test for bug 1235899 + + + + + + + + + +Mozilla Bug 1235899 +

    +
    +

    You should be able to fling this list without it stopping abruptly

    +
    +
    +
      +
    1. Some text
    2. +
    3. Some text
    4. +
    5. Some text
    6. +
    7. Some text
    8. +
    9. Some text
    10. +
    11. Some text
    12. +
    13. Some text
    14. +
    15. Some text
    16. +
    17. Some text
    18. +
    19. Some text
    20. +
    21. Some text
    22. +
    23. Some text
    24. +
    25. Some text
    26. +
    27. Some text
    28. +
    29. Some text
    30. +
    31. Some text
    32. +
    33. Some text
    34. +
    35. Some text
    36. +
    37. Some text
    38. +
    39. Some text
    40. +
    41. Some text
    42. +
    43. Some text
    44. +
    45. Some text
    46. +
    47. Some text
    48. +
    49. Some text
    50. +
    51. Some text
    52. +
    53. Some text
    54. +
    55. Some text
    56. +
    57. Some text
    58. +
    59. Some text
    60. +
    61. Some text
    62. +
    63. Some text
    64. +
    65. Some text
    66. +
    67. Some text
    68. +
    69. Some text
    70. +
    71. Some text
    72. +
    73. Some text
    74. +
    75. Some text
    76. +
    77. Some text
    78. +
    79. Some text
    80. +
    81. Some text
    82. +
    83. Some text
    84. +
    85. Some text
    86. +
    87. Some text
    88. +
    89. Some text
    90. +
    91. Some text
    92. +
    93. Some text
    94. +
    95. Some text
    96. +
    97. Some text
    98. +
    99. Some text
    100. +
    101. Some text
    102. +
    103. Some text
    104. +
    105. Some text
    106. +
    107. Some text
    108. +
    109. Some text
    110. +
    111. Some text
    112. +
    113. Some text
    114. +
    115. Some text
    116. +
    117. Some text
    118. +
    119. Some text
    120. +
    121. Some text
    122. +
    123. Some text
    124. +
    125. Some text
    126. +
    127. Some text
    128. +
    129. Some text
    130. +
    131. Some text
    132. +
    133. Some text
    134. +
    135. Some text
    136. +
    137. Some text
    138. +
    139. Some text
    140. +
    141. Some text
    142. +
    143. Some text
    144. +
    145. Some text
    146. +
    147. Some text
    148. +
    149. Some text
    150. +
    151. Some text
    152. +
    153. Some text
    154. +
    155. Some text
    156. +
    157. Some text
    158. +
    159. Some text
    160. +
    161. Some text
    162. +
    163. Some text
    164. +
    165. Some text
    166. +
    167. Some text
    168. +
    169. Some text
    170. +
    171. Some text
    172. +
    173. Some text
    174. +
    175. Some text
    176. +
    177. Some text
    178. +
    179. Some text
    180. +
    181. Some text
    182. +
    183. Some text
    184. +
    185. Some text
    186. +
    187. Some text
    188. +
    189. Some text
    190. +
    191. Some text
    192. +
    193. Some text
    194. +
    195. Some text
    196. +
    197. Some text
    198. +
    199. Some text
    200. +
    201. Some text
    202. +
    203. Some text
    204. +
    +
    +
    +
    + +
    +
    +
    +
    diff --git a/gfx/layers/apz/test/mochitest/test_group_bug1464568.html b/gfx/layers/apz/test/mochitest/test_group_bug1464568.html
    new file mode 100644
    index 0000000000..ed30312758
    --- /dev/null
    +++ b/gfx/layers/apz/test/mochitest/test_group_bug1464568.html
    @@ -0,0 +1,30 @@
    +
    +
    +
    +  
    +  
    +  
    +  
    +  
    +
    +
    +
    +
    diff --git a/gfx/layers/apz/test/mochitest/test_group_checkerboarding.html b/gfx/layers/apz/test/mochitest/test_group_checkerboarding.html
    new file mode 100644
    index 0000000000..23a64e51ac
    --- /dev/null
    +++ b/gfx/layers/apz/test/mochitest/test_group_checkerboarding.html
    @@ -0,0 +1,68 @@
    +
    +
    +
    +  
    +  
    +  
    +  
    +  
    +
    +
    +
    +
    diff --git a/gfx/layers/apz/test/mochitest/test_group_double_tap_zoom.html b/gfx/layers/apz/test/mochitest/test_group_double_tap_zoom.html
    new file mode 100644
    index 0000000000..f08302f542
    --- /dev/null
    +++ b/gfx/layers/apz/test/mochitest/test_group_double_tap_zoom.html
    @@ -0,0 +1,36 @@
    +
    +
    +
    +  
    +  Various zoom-related tests that spawn in new windows
    +  
    +  
    +  
    +  
    +
    +
    +
    +
    diff --git a/gfx/layers/apz/test/mochitest/test_group_fullscreen.html b/gfx/layers/apz/test/mochitest/test_group_fullscreen.html
    new file mode 100644
    index 0000000000..43bcfbdef3
    --- /dev/null
    +++ b/gfx/layers/apz/test/mochitest/test_group_fullscreen.html
    @@ -0,0 +1,33 @@
    +
    +
    +
    +  
    +  
    +  
    +  
    +  
    +
    +
    +
    +
    diff --git a/gfx/layers/apz/test/mochitest/test_group_hittest.html b/gfx/layers/apz/test/mochitest/test_group_hittest.html
    new file mode 100644
    index 0000000000..0a4c506119
    --- /dev/null
    +++ b/gfx/layers/apz/test/mochitest/test_group_hittest.html
    @@ -0,0 +1,73 @@
    +
    +
    +
    +  
    +  Various hit-testing tests that spawn in new windows
    +  
    +  
    +  
    +  
    +
    +
    +
    +
    diff --git a/gfx/layers/apz/test/mochitest/test_group_keyboard.html b/gfx/layers/apz/test/mochitest/test_group_keyboard.html
    new file mode 100644
    index 0000000000..8fc6531381
    --- /dev/null
    +++ b/gfx/layers/apz/test/mochitest/test_group_keyboard.html
    @@ -0,0 +1,31 @@
    +
    +
    +
    +  
    +  Various keyboard scrolling tests
    +  
    +  
    +  
    +  
    +
    +
    +  Async key scrolling test
    +
    +
    diff --git a/gfx/layers/apz/test/mochitest/test_group_mainthread.html b/gfx/layers/apz/test/mochitest/test_group_mainthread.html
    new file mode 100644
    index 0000000000..6782dcbc02
    --- /dev/null
    +++ b/gfx/layers/apz/test/mochitest/test_group_mainthread.html
    @@ -0,0 +1,33 @@
    +
    +
    +
    +  
    +  Tests that perform main-thread scrolling
    +  
    +  
    +  
    +  
    +
    +
    +
    +
    diff --git a/gfx/layers/apz/test/mochitest/test_group_minimum_scale_size.html b/gfx/layers/apz/test/mochitest/test_group_minimum_scale_size.html
    new file mode 100644
    index 0000000000..630f2452be
    --- /dev/null
    +++ b/gfx/layers/apz/test/mochitest/test_group_minimum_scale_size.html
    @@ -0,0 +1,56 @@
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    diff --git a/gfx/layers/apz/test/mochitest/test_group_mouseevents.html b/gfx/layers/apz/test/mochitest/test_group_mouseevents.html
    new file mode 100644
    index 0000000000..f11abf9b7c
    --- /dev/null
    +++ b/gfx/layers/apz/test/mochitest/test_group_mouseevents.html
    @@ -0,0 +1,62 @@
    +
    +
    +
    +  
    +  Various mouse tests that spawn in new windows
    +  
    +  
    +  
    +  
    +
    +
    +
    +
    diff --git a/gfx/layers/apz/test/mochitest/test_group_overrides.html b/gfx/layers/apz/test/mochitest/test_group_overrides.html
    new file mode 100644
    index 0000000000..434c32d24e
    --- /dev/null
    +++ b/gfx/layers/apz/test/mochitest/test_group_overrides.html
    @@ -0,0 +1,37 @@
    +
    +
    +
    +  
    +  Various tests for event regions overrides
    +  
    +  
    +  
    +  
    +
    +
    +
    +
    diff --git a/gfx/layers/apz/test/mochitest/test_group_pointerevents.html b/gfx/layers/apz/test/mochitest/test_group_pointerevents.html
    new file mode 100644
    index 0000000000..1423d5e2c7
    --- /dev/null
    +++ b/gfx/layers/apz/test/mochitest/test_group_pointerevents.html
    @@ -0,0 +1,43 @@
    +
    +
    +
    +
    +  
    +  Test for Bug 1285070
    +  
    +  
    +  
    +  
    +  
    +
    +
    +
    +
    diff --git a/gfx/layers/apz/test/mochitest/test_group_scroll_snap.html b/gfx/layers/apz/test/mochitest/test_group_scroll_snap.html
    new file mode 100644
    index 0000000000..8bc85dda19
    --- /dev/null
    +++ b/gfx/layers/apz/test/mochitest/test_group_scroll_snap.html
    @@ -0,0 +1,36 @@
    +
    +
    +
    +  
    +  Various tests for scroll snap
    +  
    +  
    +  
    +  
    +
    +
    +
    +
    diff --git a/gfx/layers/apz/test/mochitest/test_group_scrollframe_activation.html b/gfx/layers/apz/test/mochitest/test_group_scrollframe_activation.html
    new file mode 100644
    index 0000000000..ba1c422dc7
    --- /dev/null
    +++ b/gfx/layers/apz/test/mochitest/test_group_scrollframe_activation.html
    @@ -0,0 +1,35 @@
    +
    +
    +
    +
    +
    +  
    +  Test for Bug 1151663
    +  
    +  
    +  
    +  
    +
    +
    +
    +  Mozilla Bug 1151663
    +
    +
    +
    diff --git a/gfx/layers/apz/test/mochitest/test_group_touchevents-2.html b/gfx/layers/apz/test/mochitest/test_group_touchevents-2.html
    new file mode 100644
    index 0000000000..333bea26fb
    --- /dev/null
    +++ b/gfx/layers/apz/test/mochitest/test_group_touchevents-2.html
    @@ -0,0 +1,67 @@
    +
    +
    +
    +  
    +  Various touch tests that spawn in new windows (2)
    +  
    +  
    +  
    +  
    +  
    +
    +
    +
    +
    diff --git a/gfx/layers/apz/test/mochitest/test_group_touchevents-3.html b/gfx/layers/apz/test/mochitest/test_group_touchevents-3.html
    new file mode 100644
    index 0000000000..a86c80a2ec
    --- /dev/null
    +++ b/gfx/layers/apz/test/mochitest/test_group_touchevents-3.html
    @@ -0,0 +1,47 @@
    +
    +
    +
    +  
    +  Various touch tests that spawn in new windows (3)
    +  
    +  
    +  
    +  
    +  
    +
    +
    +
    +
    diff --git a/gfx/layers/apz/test/mochitest/test_group_touchevents-4.html b/gfx/layers/apz/test/mochitest/test_group_touchevents-4.html
    new file mode 100644
    index 0000000000..ebb7dfa481
    --- /dev/null
    +++ b/gfx/layers/apz/test/mochitest/test_group_touchevents-4.html
    @@ -0,0 +1,55 @@
    +
    +
    +
    +  
    +  Various touch tests that spawn in new windows (4)
    +  
    +  
    +  
    +  
    +  
    +
    +
    +
    +
    diff --git a/gfx/layers/apz/test/mochitest/test_group_touchevents-5.html b/gfx/layers/apz/test/mochitest/test_group_touchevents-5.html
    new file mode 100644
    index 0000000000..8c8382d628
    --- /dev/null
    +++ b/gfx/layers/apz/test/mochitest/test_group_touchevents-5.html
    @@ -0,0 +1,45 @@
    +
    +
    +
    +  
    +  Various touch tests that spawn in new windows (5)
    +  
    +  
    +  
    +  
    +  
    +
    +
    +
    +
    diff --git a/gfx/layers/apz/test/mochitest/test_group_touchevents.html b/gfx/layers/apz/test/mochitest/test_group_touchevents.html
    new file mode 100644
    index 0000000000..df24e24f3d
    --- /dev/null
    +++ b/gfx/layers/apz/test/mochitest/test_group_touchevents.html
    @@ -0,0 +1,55 @@
    +
    +
    +
    +  
    +  Various touch tests that spawn in new windows
    +  
    +  
    +  
    +  
    +  
    +
    +
    +
    +
    diff --git a/gfx/layers/apz/test/mochitest/test_group_wheelevents.html b/gfx/layers/apz/test/mochitest/test_group_wheelevents.html
    new file mode 100644
    index 0000000000..55d04043d8
    --- /dev/null
    +++ b/gfx/layers/apz/test/mochitest/test_group_wheelevents.html
    @@ -0,0 +1,59 @@
    +
    +
    +
    +  
    +  Various wheel-scrolling tests that spawn in new windows
    +  
    +  
    +  
    +  
    +
    +
    +
    +
    diff --git a/gfx/layers/apz/test/mochitest/test_group_zoom-2.html b/gfx/layers/apz/test/mochitest/test_group_zoom-2.html
    new file mode 100644
    index 0000000000..99a2dae66c
    --- /dev/null
    +++ b/gfx/layers/apz/test/mochitest/test_group_zoom-2.html
    @@ -0,0 +1,77 @@
    +
    +
    +
    +  
    +  Various zoom-related tests that spawn in new windows
    +  
    +  
    +  
    +  
    +
    +
    +
    +
    diff --git a/gfx/layers/apz/test/mochitest/test_group_zoom.html b/gfx/layers/apz/test/mochitest/test_group_zoom.html
    new file mode 100644
    index 0000000000..7223ea3596
    --- /dev/null
    +++ b/gfx/layers/apz/test/mochitest/test_group_zoom.html
    @@ -0,0 +1,86 @@
    +
    +
    +
    +  
    +  Various zoom-related tests that spawn in new windows
    +  
    +  
    +  
    +  
    +
    +
    +
    +
    diff --git a/gfx/layers/apz/test/mochitest/test_group_zoomToFocusedInput.html b/gfx/layers/apz/test/mochitest/test_group_zoomToFocusedInput.html
    new file mode 100644
    index 0000000000..80b814254d
    --- /dev/null
    +++ b/gfx/layers/apz/test/mochitest/test_group_zoomToFocusedInput.html
    @@ -0,0 +1,29 @@
    +
    +
    +
    +  
    +  Various zoomToFocusedInput tests
    +  
    +  
    +  
    +  
    +
    +
    +
    +
    diff --git a/gfx/layers/apz/test/mochitest/test_interrupted_reflow.html b/gfx/layers/apz/test/mochitest/test_interrupted_reflow.html
    new file mode 100644
    index 0000000000..2340da3d80
    --- /dev/null
    +++ b/gfx/layers/apz/test/mochitest/test_interrupted_reflow.html
    @@ -0,0 +1,718 @@
    +
    +
    + 
    + 
    +  Test for bug 1292781
    +  
    +  
    +  
    +  
    +  
    +  
    +  
    + 
    + 
    +Mozilla Bug 1292781
    +

    +
    +

    The frame reconstruction should not leave this scrollframe in a bad state

    +
    +
    + this is the top of the scrollframe. +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    + this is near the top of the scrollframe. +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    + this is near the bottom of the scrollframe. +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    +
    this is a box
    + this is the bottom of the scrollframe. +
    +
    +
    + +
    +
    +
    +
    diff --git a/gfx/layers/apz/test/mochitest/test_layerization.html b/gfx/layers/apz/test/mochitest/test_layerization.html
    new file mode 100644
    index 0000000000..89be0bf022
    --- /dev/null
    +++ b/gfx/layers/apz/test/mochitest/test_layerization.html
    @@ -0,0 +1,203 @@
    +
    +
    +
    +
    +  Test for layerization
    +  
    +  
    +  
    +  
    +  
    +  
    +  
    +  
    +
    +
    +APZ layerization tests
    +

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + +
    +
    +
    +
    +
    + + diff --git a/gfx/layers/apz/test/mochitest/test_relative_update.html b/gfx/layers/apz/test/mochitest/test_relative_update.html new file mode 100644 index 0000000000..01c0ee1f9b --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_relative_update.html @@ -0,0 +1,92 @@ + + + + + Test for relative scroll offset updates (Bug 1453425) + + + + + + + + + +
    +
    +
    +
    + + + diff --git a/gfx/layers/apz/test/mochitest/test_scroll_inactive_bug1190112.html b/gfx/layers/apz/test/mochitest/test_scroll_inactive_bug1190112.html new file mode 100644 index 0000000000..953ef297cb --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_scroll_inactive_bug1190112.html @@ -0,0 +1,552 @@ + + + + Test scrolling flattened inactive frames + + + + + + + + +
    +
    +
    +
    +

    +1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    + +

    +1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    + +

    +1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    + +

    +1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    + +

    +1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    + +

    +1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    + +

    +1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    + +

    +1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    + +

    +1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    + +

    +1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    + +

    +1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    + +

    +
    + + + diff --git a/gfx/layers/apz/test/mochitest/test_scroll_inactive_flattened_frame.html b/gfx/layers/apz/test/mochitest/test_scroll_inactive_flattened_frame.html new file mode 100644 index 0000000000..4c64edf64a --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_scroll_inactive_flattened_frame.html @@ -0,0 +1,49 @@ + + + + Test scrolling flattened inactive frames + + + + + + + +
    +
    +
    +
    +
    +
    + + + diff --git a/gfx/layers/apz/test/mochitest/test_scroll_subframe_scrollbar.html b/gfx/layers/apz/test/mochitest/test_scroll_subframe_scrollbar.html new file mode 100644 index 0000000000..7148d8df9f --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_scroll_subframe_scrollbar.html @@ -0,0 +1,115 @@ + + + + Test scrolling subframe scrollbars + + + + + + + + +

    +1
    +2
    +3
    +4
    +5
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +

    + + + diff --git a/gfx/layers/apz/test/mochitest/test_smoothness.html b/gfx/layers/apz/test/mochitest/test_smoothness.html new file mode 100644 index 0000000000..b2d9ac20a9 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_smoothness.html @@ -0,0 +1,71 @@ + + + Test Frame Uniformity While Scrolling + + + + + + + + + + + +
    +
    + + diff --git a/gfx/layers/apz/test/mochitest/test_touch_listeners_impacting_wheel.html b/gfx/layers/apz/test/mochitest/test_touch_listeners_impacting_wheel.html new file mode 100644 index 0000000000..d629f05d1f --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_touch_listeners_impacting_wheel.html @@ -0,0 +1,114 @@ + + + + + Test for Bug 1203140 + + + + + + + + + +Mozilla Bug 1203140 +

    +
    +

    The box below has a touch listener and a passive wheel listener. With touch events disabled, APZ shouldn't wait for any listeners.

    +
    +
    Div to make 'content' scrollable
    +
    +
    +
    +
    + + + diff --git a/gfx/layers/apz/test/mochitest/test_wheel_scroll.html b/gfx/layers/apz/test/mochitest/test_wheel_scroll.html new file mode 100644 index 0000000000..15509deb94 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_wheel_scroll.html @@ -0,0 +1,104 @@ + + + + + Test for Bug 1013412 + + + + + + + + + +Mozilla Bug 1161206 +

    +
    +

    Scrolling the page should be async, but scrolling over the dark circle should not scroll the page and instead rotate the white ball.

    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + diff --git a/gfx/layers/apz/test/mochitest/test_wheel_transactions.html b/gfx/layers/apz/test/mochitest/test_wheel_transactions.html new file mode 100644 index 0000000000..76737dbb06 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_wheel_transactions.html @@ -0,0 +1,141 @@ + + + + + Test for Bug 1175585 + + + + + + + + + +APZ wheel transactions test +

    +
    +
    +
    +
    +
    +
    +
    +
    + + + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-h-ref.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-h-ref.html new file mode 100644 index 0000000000..62d99b6dfe --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-h-ref.html @@ -0,0 +1,8 @@ + + + + + +
    + + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-h-rtl-ref.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-h-rtl-ref.html new file mode 100644 index 0000000000..e40ac8debb --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-h-rtl-ref.html @@ -0,0 +1,9 @@ + + + + + + +
    + + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-h-rtl.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-h-rtl.html new file mode 100644 index 0000000000..81f7f77817 --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-h-rtl.html @@ -0,0 +1,13 @@ + + + + + + + +
    + + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-h.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-h.html new file mode 100644 index 0000000000..5d30584acd --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-h.html @@ -0,0 +1,12 @@ + + + + + + +
    + + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-v-ref.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-v-ref.html new file mode 100644 index 0000000000..aec5f89cbc --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-v-ref.html @@ -0,0 +1,8 @@ + + + + + +
    + + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-v-rtl-ref.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-v-rtl-ref.html new file mode 100644 index 0000000000..81be67146f --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-v-rtl-ref.html @@ -0,0 +1,9 @@ + + + + + + +
    + + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-v-rtl.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-v-rtl.html new file mode 100644 index 0000000000..24e7705723 --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-v-rtl.html @@ -0,0 +1,13 @@ + + + + + + + +
    + + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-v.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-v.html new file mode 100644 index 0000000000..268f3b92e3 --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-v.html @@ -0,0 +1,12 @@ + + + + + + +
    + + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-vh-ref.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-vh-ref.html new file mode 100644 index 0000000000..35922e3253 --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-vh-ref.html @@ -0,0 +1,8 @@ + + + + + +
    + + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-vh-rtl-ref.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-vh-rtl-ref.html new file mode 100644 index 0000000000..22bf3cf1c8 --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-vh-rtl-ref.html @@ -0,0 +1,9 @@ + + + + + + +
    + + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-vh-rtl.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-vh-rtl.html new file mode 100644 index 0000000000..09fce0bbe9 --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-vh-rtl.html @@ -0,0 +1,13 @@ + + + + + + + +
    + + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-vh.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-vh.html new file mode 100644 index 0000000000..a8d28ec414 --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-vh.html @@ -0,0 +1,12 @@ + + + + + + +
    + + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-zoom-1-ref.html b/gfx/layers/apz/test/reftest/async-scrollbar-zoom-1-ref.html new file mode 100644 index 0000000000..9568836459 --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-zoom-1-ref.html @@ -0,0 +1,8 @@ + + + + + +
    + + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-zoom-1.html b/gfx/layers/apz/test/reftest/async-scrollbar-zoom-1.html new file mode 100644 index 0000000000..31e1e99a3d --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-zoom-1.html @@ -0,0 +1,13 @@ + + + + + + +
    + + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-zoom-2-ref.html b/gfx/layers/apz/test/reftest/async-scrollbar-zoom-2-ref.html new file mode 100644 index 0000000000..9568836459 --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-zoom-2-ref.html @@ -0,0 +1,8 @@ + + + + + +
    + + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-zoom-2.html b/gfx/layers/apz/test/reftest/async-scrollbar-zoom-2.html new file mode 100644 index 0000000000..4032c3c638 --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-zoom-2.html @@ -0,0 +1,13 @@ + + + + + + +
    + + diff --git a/gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping-ref.html b/gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping-ref.html new file mode 100644 index 0000000000..3db9f2969e --- /dev/null +++ b/gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping-ref.html @@ -0,0 +1,27 @@ + + + + +
    + This is the top of the page. +
    + This is the bottom of the page. + diff --git a/gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping.html b/gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping.html new file mode 100644 index 0000000000..479363f3fb --- /dev/null +++ b/gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping.html @@ -0,0 +1,53 @@ + + + + + +
    + This is the top of the page. +
    + This is the bottom of the page. + diff --git a/gfx/layers/apz/test/reftest/iframe-zoomed-child.html b/gfx/layers/apz/test/reftest/iframe-zoomed-child.html new file mode 100644 index 0000000000..4d51f46399 --- /dev/null +++ b/gfx/layers/apz/test/reftest/iframe-zoomed-child.html @@ -0,0 +1,12 @@ + +
    +
    +
    +
    diff --git a/gfx/layers/apz/test/reftest/iframe-zoomed-ref.html b/gfx/layers/apz/test/reftest/iframe-zoomed-ref.html new file mode 100644 index 0000000000..2c98a7eb6a --- /dev/null +++ b/gfx/layers/apz/test/reftest/iframe-zoomed-ref.html @@ -0,0 +1,20 @@ + + + +
    + +
    + diff --git a/gfx/layers/apz/test/reftest/iframe-zoomed.html b/gfx/layers/apz/test/reftest/iframe-zoomed.html new file mode 100644 index 0000000000..d3d5d914ba --- /dev/null +++ b/gfx/layers/apz/test/reftest/iframe-zoomed.html @@ -0,0 +1,25 @@ + + + +
    + + +
    + diff --git a/gfx/layers/apz/test/reftest/initial-scale-1-ref.html b/gfx/layers/apz/test/reftest/initial-scale-1-ref.html new file mode 100644 index 0000000000..cb51966a28 --- /dev/null +++ b/gfx/layers/apz/test/reftest/initial-scale-1-ref.html @@ -0,0 +1,9 @@ + + + + + +This tests that an initial-scale of 0 (i.e. garbage) is overridden
    +with something a little more sane. + + diff --git a/gfx/layers/apz/test/reftest/initial-scale-1.html b/gfx/layers/apz/test/reftest/initial-scale-1.html new file mode 100644 index 0000000000..58babe9403 --- /dev/null +++ b/gfx/layers/apz/test/reftest/initial-scale-1.html @@ -0,0 +1,9 @@ + + + + + +This tests that an initial-scale of 0 (i.e. garbage) is overridden
    +with something a little more sane. + + diff --git a/gfx/layers/apz/test/reftest/pinch-zoom-position-fixed-ref.html b/gfx/layers/apz/test/reftest/pinch-zoom-position-fixed-ref.html new file mode 100644 index 0000000000..f7d485c509 --- /dev/null +++ b/gfx/layers/apz/test/reftest/pinch-zoom-position-fixed-ref.html @@ -0,0 +1,23 @@ + + + + + + + +
    + + diff --git a/gfx/layers/apz/test/reftest/pinch-zoom-position-fixed.html b/gfx/layers/apz/test/reftest/pinch-zoom-position-fixed.html new file mode 100644 index 0000000000..c4476f4872 --- /dev/null +++ b/gfx/layers/apz/test/reftest/pinch-zoom-position-fixed.html @@ -0,0 +1,37 @@ + + + + + + + + +
    + + diff --git a/gfx/layers/apz/test/reftest/pinch-zoom-position-sticky-ref.html b/gfx/layers/apz/test/reftest/pinch-zoom-position-sticky-ref.html new file mode 100644 index 0000000000..c430b532df --- /dev/null +++ b/gfx/layers/apz/test/reftest/pinch-zoom-position-sticky-ref.html @@ -0,0 +1,27 @@ + + + + + + + +
    +
    + + diff --git a/gfx/layers/apz/test/reftest/pinch-zoom-position-sticky.html b/gfx/layers/apz/test/reftest/pinch-zoom-position-sticky.html new file mode 100644 index 0000000000..245e0d775e --- /dev/null +++ b/gfx/layers/apz/test/reftest/pinch-zoom-position-sticky.html @@ -0,0 +1,30 @@ + + + + + + + + +
    +
    + + diff --git a/gfx/layers/apz/test/reftest/reftest.list b/gfx/layers/apz/test/reftest/reftest.list new file mode 100644 index 0000000000..c137040a9e --- /dev/null +++ b/gfx/layers/apz/test/reftest/reftest.list @@ -0,0 +1,41 @@ +# The following tests test the async positioning of the scrollbars. +# Basic root-frame scrollbar with async scrolling +# First make sure that we are actually drawing scrollbars +skip-if(!asyncPan) pref(apz.allow_zooming,true) != async-scrollbar-1-v.html about:blank +skip-if(!asyncPan) pref(apz.allow_zooming,true) != async-scrollbar-1-v-ref.html about:blank +fuzzy-if(Android,0-1,0-2) fuzzy-if(webrender&>kWidget&&!swgl,7-8,24-32) fuzzy-if(webrender&&cocoaWidget,22-22,44-44) skip-if(!asyncPan) pref(apz.allow_zooming,true) == async-scrollbar-1-v.html async-scrollbar-1-v-ref.html +fuzzy-if(Android,0-4,0-5) fuzzy-if(webrender&>kWidget,28-29,30-32) fuzzy-if(webrender&&cocoaWidget,22-22,44-44) skip-if(!asyncPan) pref(apz.allow_zooming,true) == async-scrollbar-1-h.html async-scrollbar-1-h-ref.html +fuzzy-if(Android,0-6,0-6) fuzzy-if(webrender&>kWidget&&!swgl,2-2,19-20) fuzzy-if(webrender&&cocoaWidget,17-17,88-88) skip-if(!asyncPan) pref(apz.allow_zooming,true) == async-scrollbar-1-vh.html async-scrollbar-1-vh-ref.html +fuzzy-if(Android,0-1,0-2) fuzzy-if(webrender&>kWidget&&!swgl,7-8,24-32) fuzzy-if(webrender&&cocoaWidget,22-22,44-44) skip-if(!asyncPan) pref(apz.allow_zooming,true) == async-scrollbar-1-v-rtl.html async-scrollbar-1-v-rtl-ref.html +fuzzy-if(Android,0-14,0-5) fuzzy-if(webrender&>kWidget,28-29,30-32) fuzzy-if(webrender&&cocoaWidget,22-22,44-44) skip-if(!asyncPan) pref(apz.allow_zooming,true) == async-scrollbar-1-h-rtl.html async-scrollbar-1-h-rtl-ref.html +fuzzy-if(Android,0-8,0-8) fuzzy-if(webrender&>kWidget,13-13,32-32) fuzzy-if(webrender&&cocoaWidget,17-17,50-54) skip-if(!asyncPan) pref(apz.allow_zooming,true) == async-scrollbar-1-vh-rtl.html async-scrollbar-1-vh-rtl-ref.html + +# Different async zoom levels. Since the scrollthumb gets async-scaled in the +# compositor, the border-radius ends of the scrollthumb are going to be a little +# off, hence the fuzzy-if clauses. +fuzzy-if(Android,0-54,0-20) skip-if(!Android) pref(apz.allow_zooming,true) fuzzy-if(geckoview&&webrender,0-45,0-214) == async-scrollbar-zoom-1.html async-scrollbar-zoom-1-ref.html +fuzzy-if(Android,0-45,0-27) skip-if(!Android) pref(apz.allow_zooming,true) == async-scrollbar-zoom-2.html async-scrollbar-zoom-2-ref.html + +# Test scrollbars working properly with pinch-zooming, i.e. different document resolutions. +# As above, the end of the scrollthumb won't match perfectly, but the bulk of the scrollbar should be present and identical. +# On desktop, even more fuzz is needed because thumb scaling is not exactly proportional: making the page twice as long +# won't make the thumb exactly twice as short, which is what the test expects. That's fine, as the purpose of the test is +# to catch more fundamental scrollbar rendering bugs such as the entire track being mispositioned or the thumb being +# clipped away. +fuzzy-if(Android,0-54,0-14) fuzzy-if(!Android,0-128,0-137) pref(apz.allow_zooming,true) == scrollbar-zoom-resolution-1.html scrollbar-zoom-resolution-1-ref.html +fuzzy-if(Android,0-51,0-22) fuzzy-if(!Android,0-128,0-137) pref(apz.allow_zooming,true) pref(apz.allow_zooming_out,true) == scrollbar-zoom-resolution-2.html scrollbar-zoom-resolution-2-ref.html + +# Meta-viewport tag support +skip-if(!Android) pref(apz.allow_zooming,true) == initial-scale-1.html initial-scale-1-ref.html + +skip-if(!asyncPan) == frame-reconstruction-scroll-clamping.html frame-reconstruction-scroll-clamping-ref.html + +# Test that position:fixed and position:sticky elements are attached to the +# layout viewport. +pref(apz.allow_zooming,true) == pinch-zoom-position-fixed.html pinch-zoom-position-fixed-ref.html +pref(apz.allow_zooming,true) == pinch-zoom-position-sticky.html pinch-zoom-position-sticky-ref.html + +pref(apz.allow_zooming,true) == iframe-zoomed.html iframe-zoomed-ref.html +pref(apz.allow_zooming,true) == scaled-iframe-zoomed.html scaled-iframe-zoomed-ref.html + +== root-scrollbars-1.html root-scrollbars-1-ref.html diff --git a/gfx/layers/apz/test/reftest/root-scrollbars-1-ref.html b/gfx/layers/apz/test/reftest/root-scrollbars-1-ref.html new file mode 100644 index 0000000000..435609f8a3 --- /dev/null +++ b/gfx/layers/apz/test/reftest/root-scrollbars-1-ref.html @@ -0,0 +1,14 @@ + + + + +In this file the scrollbars that appear are non-root scrollbars + + +
    + + diff --git a/gfx/layers/apz/test/reftest/root-scrollbars-1.html b/gfx/layers/apz/test/reftest/root-scrollbars-1.html new file mode 100644 index 0000000000..e2560d48a9 --- /dev/null +++ b/gfx/layers/apz/test/reftest/root-scrollbars-1.html @@ -0,0 +1,14 @@ + + + + +In this file the scrollbars that appear are the root scrollbars + + +
    + + diff --git a/gfx/layers/apz/test/reftest/scaled-iframe-zoomed-ref.html b/gfx/layers/apz/test/reftest/scaled-iframe-zoomed-ref.html new file mode 100644 index 0000000000..39847320e2 --- /dev/null +++ b/gfx/layers/apz/test/reftest/scaled-iframe-zoomed-ref.html @@ -0,0 +1,21 @@ + + + +
    + +
    + diff --git a/gfx/layers/apz/test/reftest/scaled-iframe-zoomed.html b/gfx/layers/apz/test/reftest/scaled-iframe-zoomed.html new file mode 100644 index 0000000000..89b09047f7 --- /dev/null +++ b/gfx/layers/apz/test/reftest/scaled-iframe-zoomed.html @@ -0,0 +1,26 @@ + + + +
    + + +
    + diff --git a/gfx/layers/apz/test/reftest/scrollbar-zoom-resolution-1-ref.html b/gfx/layers/apz/test/reftest/scrollbar-zoom-resolution-1-ref.html new file mode 100644 index 0000000000..9568836459 --- /dev/null +++ b/gfx/layers/apz/test/reftest/scrollbar-zoom-resolution-1-ref.html @@ -0,0 +1,8 @@ + + + + + +
    + + diff --git a/gfx/layers/apz/test/reftest/scrollbar-zoom-resolution-1.html b/gfx/layers/apz/test/reftest/scrollbar-zoom-resolution-1.html new file mode 100644 index 0000000000..c9cb6e80a7 --- /dev/null +++ b/gfx/layers/apz/test/reftest/scrollbar-zoom-resolution-1.html @@ -0,0 +1,8 @@ + + + + + +
    + + diff --git a/gfx/layers/apz/test/reftest/scrollbar-zoom-resolution-2-ref.html b/gfx/layers/apz/test/reftest/scrollbar-zoom-resolution-2-ref.html new file mode 100644 index 0000000000..9568836459 --- /dev/null +++ b/gfx/layers/apz/test/reftest/scrollbar-zoom-resolution-2-ref.html @@ -0,0 +1,8 @@ + + + + + +
    + + diff --git a/gfx/layers/apz/test/reftest/scrollbar-zoom-resolution-2.html b/gfx/layers/apz/test/reftest/scrollbar-zoom-resolution-2.html new file mode 100644 index 0000000000..0e3ec7173d --- /dev/null +++ b/gfx/layers/apz/test/reftest/scrollbar-zoom-resolution-2.html @@ -0,0 +1,8 @@ + + + + + +
    + + diff --git a/gfx/layers/apz/testutil/APZTestData.cpp b/gfx/layers/apz/testutil/APZTestData.cpp new file mode 100644 index 0000000000..08780cf20a --- /dev/null +++ b/gfx/layers/apz/testutil/APZTestData.cpp @@ -0,0 +1,113 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "APZTestData.h" +#include "mozilla/dom/APZTestDataBinding.h" +#include "mozilla/dom/ToJSValue.h" +#include "nsString.h" + +namespace mozilla { +namespace layers { + +struct APZTestDataToJSConverter { + template + static void ConvertMap(const std::map& aFrom, + dom::Sequence& aOutTo, + void (*aElementConverter)(const Key&, const Value&, + KeyValuePair&)) { + for (auto it = aFrom.begin(); it != aFrom.end(); ++it) { + if (!aOutTo.AppendElement(fallible)) { + // XXX(Bug 1632090) Instead of extending the array 1-by-1 (which might + // involve multiple reallocations) and potentially crashing here, + // SetCapacity could be called outside the loop once. + mozalloc_handle_oom(0); + } + aElementConverter(it->first, it->second, aOutTo.LastElement()); + } + } + + template + static void ConvertList(const nsTArray& aFrom, + dom::Sequence& aOutTo, + void (*aElementConverter)(const Src&, Target&)) { + for (auto it = aFrom.begin(); it != aFrom.end(); ++it) { + if (!aOutTo.AppendElement(fallible)) { + // XXX(Bug 1632090) Instead of extending the array 1-by-1 (which might + // involve multiple reallocations) and potentially crashing here, + // SetCapacity could be called outside the loop once. + mozalloc_handle_oom(0); + } + aElementConverter(*it, aOutTo.LastElement()); + } + } + + static void ConvertAPZTestData(const APZTestData& aFrom, + dom::APZTestData& aOutTo) { + ConvertMap(aFrom.mPaints, aOutTo.mPaints.Construct(), ConvertBucket); + ConvertMap(aFrom.mRepaintRequests, aOutTo.mRepaintRequests.Construct(), + ConvertBucket); + ConvertList(aFrom.mHitResults, aOutTo.mHitResults.Construct(), + ConvertHitResult); + ConvertMap(aFrom.mAdditionalData, aOutTo.mAdditionalData.Construct(), + ConvertAdditionalDataEntry); + } + + static void ConvertBucket(const SequenceNumber& aKey, + const APZTestData::Bucket& aValue, + dom::APZBucket& aOutKeyValuePair) { + aOutKeyValuePair.mSequenceNumber.Construct() = aKey; + ConvertMap(aValue, aOutKeyValuePair.mScrollFrames.Construct(), + ConvertScrollFrameData); + } + + static void ConvertScrollFrameData(const APZTestData::ViewID& aKey, + const APZTestData::ScrollFrameData& aValue, + dom::ScrollFrameData& aOutKeyValuePair) { + aOutKeyValuePair.mScrollId.Construct() = aKey; + ConvertMap(aValue, aOutKeyValuePair.mEntries.Construct(), ConvertEntry); + } + + static void ConvertEntry(const std::string& aKey, const std::string& aValue, + dom::ScrollFrameDataEntry& aOutKeyValuePair) { + ConvertString(aKey, aOutKeyValuePair.mKey.Construct()); + ConvertString(aValue, aOutKeyValuePair.mValue.Construct()); + } + + static void ConvertAdditionalDataEntry( + const std::string& aKey, const std::string& aValue, + dom::AdditionalDataEntry& aOutKeyValuePair) { + ConvertString(aKey, aOutKeyValuePair.mKey.Construct()); + ConvertString(aValue, aOutKeyValuePair.mValue.Construct()); + } + + static void ConvertString(const std::string& aFrom, nsString& aOutTo) { + CopyUTF8toUTF16(aFrom, aOutTo); + } + + static void ConvertHitResult(const APZTestData::HitResult& aResult, + dom::APZHitResult& aOutHitResult) { + aOutHitResult.mScreenX.Construct() = aResult.point.x; + aOutHitResult.mScreenY.Construct() = aResult.point.y; + static_assert(MaxEnumValue::value < + std::numeric_limits::digits, + "CompositorHitTestFlags MAX value have to be less than " + "number of bits in uint16_t"); + aOutHitResult.mHitResult.Construct() = + static_cast(aResult.result.serialize()); + aOutHitResult.mLayersId.Construct() = aResult.layersId.mId; + aOutHitResult.mScrollId.Construct() = aResult.scrollId; + } +}; + +bool APZTestData::ToJS(JS::MutableHandleValue aOutValue, + JSContext* aContext) const { + dom::APZTestData result; + APZTestDataToJSConverter::ConvertAPZTestData(*this, result); + return dom::ToJSValue(aContext, result, aOutValue); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/testutil/APZTestData.h b/gfx/layers/apz/testutil/APZTestData.h new file mode 100644 index 0000000000..6aff3bc203 --- /dev/null +++ b/gfx/layers/apz/testutil/APZTestData.h @@ -0,0 +1,219 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_APZTestData_h +#define mozilla_layers_APZTestData_h + +#include + +#include "nsDebug.h" // for NS_WARNING +#include "nsTArray.h" +#include "mozilla/Assertions.h" // for MOZ_ASSERT +#include "mozilla/DebugOnly.h" // for DebugOnly +#include "mozilla/GfxMessageUtils.h" // for ParamTraits specializations +#include "mozilla/StaticPrefs_apz.h" +#include "mozilla/ToString.h" // for ToString +#include "mozilla/gfx/CompositorHitTestInfo.h" +#include "mozilla/layers/LayersMessageUtils.h" // for ParamTraits specializations +#include "mozilla/layers/ScrollableLayerGuid.h" +#include "ipc/IPCMessageUtils.h" +#include "js/TypeDecls.h" + +namespace mozilla { +namespace layers { + +typedef uint32_t SequenceNumber; + +/** + * This structure is used to store information logged by various gecko + * components for later examination by test code. + * It contains a bucket for every paint (initiated on the client side), + * and every repaint request (initiated on the compositor side by + * AsyncPanZoomController::RequestContentRepait), which are identified by + * sequence numbers, and within that, a set of arbitrary string key/value + * pairs for every scrollable frame, identified by a scroll id. + * There are two instances of this data structure for every content thread: + * one on the client side and one of the compositor side. + * It also contains a list of hit-test results for MozMouseHittest events + * dispatched during testing. This list is only populated on the compositor + * instance of this class. + */ +// TODO(botond): +// - Improve warnings/asserts. +// - Add ability to associate a repaint request triggered during a layers +// update with the sequence number of the paint that caused the layers +// update. +class APZTestData { + typedef ScrollableLayerGuid::ViewID ViewID; + friend struct IPC::ParamTraits; + friend struct APZTestDataToJSConverter; + + public: + void StartNewPaint(SequenceNumber aSequenceNumber) { + // We should never get more than one paint with the same sequence number. + MOZ_ASSERT(mPaints.find(aSequenceNumber) == mPaints.end()); + mPaints.insert(DataStore::value_type(aSequenceNumber, Bucket())); + } + void LogTestDataForPaint(SequenceNumber aSequenceNumber, ViewID aScrollId, + const std::string& aKey, const std::string& aValue) { + LogTestDataImpl(mPaints, aSequenceNumber, aScrollId, aKey, aValue); + } + + void StartNewRepaintRequest(SequenceNumber aSequenceNumber) { + typedef std::pair InsertResultT; + DebugOnly insertResult = mRepaintRequests.insert( + DataStore::value_type(aSequenceNumber, Bucket())); + MOZ_ASSERT(((InsertResultT&)insertResult).second, + "Already have a repaint request with this sequence number"); + } + void LogTestDataForRepaintRequest(SequenceNumber aSequenceNumber, + ViewID aScrollId, const std::string& aKey, + const std::string& aValue) { + LogTestDataImpl(mRepaintRequests, aSequenceNumber, aScrollId, aKey, aValue); + } + void RecordHitResult(const ScreenPoint& aPoint, + const mozilla::gfx::CompositorHitTestInfo& aResult, + const LayersId& aLayersId, const ViewID& aScrollId) { + mHitResults.AppendElement(HitResult{aPoint, aResult, aLayersId, aScrollId}); + } + void RecordAdditionalData(const std::string& aKey, + const std::string& aValue) { + mAdditionalData[aKey] = aValue; + } + + // Convert this object to a JS representation. + bool ToJS(JS::MutableHandleValue aOutValue, JSContext* aContext) const; + + // Use dummy derived structures wrapping the typedefs to work around a type + // name length limit in MSVC. + typedef std::map ScrollFrameDataBase; + struct ScrollFrameData : ScrollFrameDataBase {}; + typedef std::map BucketBase; + struct Bucket : BucketBase {}; + typedef std::map DataStoreBase; + struct DataStore : DataStoreBase {}; + struct HitResult { + ScreenPoint point; + mozilla::gfx::CompositorHitTestInfo result; + LayersId layersId; + ViewID scrollId; + }; + + private: + DataStore mPaints; + DataStore mRepaintRequests; + CopyableTArray mHitResults; + // Additional free-form data that's not grouped paint or scroll frame. + std::map mAdditionalData; + + void LogTestDataImpl(DataStore& aDataStore, SequenceNumber aSequenceNumber, + ViewID aScrollId, const std::string& aKey, + const std::string& aValue) { + auto bucketIterator = aDataStore.find(aSequenceNumber); + if (bucketIterator == aDataStore.end()) { + MOZ_ASSERT(false, + "LogTestDataImpl called with nonexistent sequence number"); + return; + } + Bucket& bucket = bucketIterator->second; + ScrollFrameData& scrollFrameData = + bucket[aScrollId]; // create if doesn't exist + MOZ_ASSERT(scrollFrameData.find(aKey) == scrollFrameData.end() || + scrollFrameData[aKey] == aValue); + scrollFrameData.insert(ScrollFrameData::value_type(aKey, aValue)); + } +}; + +// A helper class for logging data for a paint. +class APZPaintLogHelper { + public: + APZPaintLogHelper(APZTestData* aTestData, SequenceNumber aPaintSequenceNumber) + : mTestData(aTestData), mPaintSequenceNumber(aPaintSequenceNumber) { + MOZ_ASSERT(!aTestData || StaticPrefs::apz_test_logging_enabled(), + "don't call me"); + } + + template + void LogTestData(ScrollableLayerGuid::ViewID aScrollId, + const std::string& aKey, const Value& aValue) const { + if (mTestData) { // avoid stringifying if mTestData == nullptr + LogTestData(aScrollId, aKey, ToString(aValue)); + } + } + + void LogTestData(ScrollableLayerGuid::ViewID aScrollId, + const std::string& aKey, const std::string& aValue) const { + if (mTestData) { + mTestData->LogTestDataForPaint(mPaintSequenceNumber, aScrollId, aKey, + aValue); + } + } + + private: + APZTestData* mTestData; + SequenceNumber mPaintSequenceNumber; +}; + +} // namespace layers +} // namespace mozilla + +namespace IPC { + +template <> +struct ParamTraits { + typedef mozilla::layers::APZTestData paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mPaints); + WriteParam(aMsg, aParam.mRepaintRequests); + WriteParam(aMsg, aParam.mHitResults); + WriteParam(aMsg, aParam.mAdditionalData); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return (ReadParam(aMsg, aIter, &aResult->mPaints) && + ReadParam(aMsg, aIter, &aResult->mRepaintRequests) && + ReadParam(aMsg, aIter, &aResult->mHitResults) && + ReadParam(aMsg, aIter, &aResult->mAdditionalData)); + } +}; + +template <> +struct ParamTraits + : ParamTraits {}; + +template <> +struct ParamTraits + : ParamTraits {}; + +template <> +struct ParamTraits + : ParamTraits {}; + +template <> +struct ParamTraits { + typedef mozilla::layers::APZTestData::HitResult paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.point); + WriteParam(aMsg, aParam.result); + WriteParam(aMsg, aParam.layersId); + WriteParam(aMsg, aParam.scrollId); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return (ReadParam(aMsg, aIter, &aResult->point) && + ReadParam(aMsg, aIter, &aResult->result) && + ReadParam(aMsg, aIter, &aResult->layersId) && + ReadParam(aMsg, aIter, &aResult->scrollId)); + } +}; + +} // namespace IPC + +#endif /* mozilla_layers_APZTestData_h */ diff --git a/gfx/layers/apz/util/APZCCallbackHelper.cpp b/gfx/layers/apz/util/APZCCallbackHelper.cpp new file mode 100644 index 0000000000..8d5d38f0cd --- /dev/null +++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp @@ -0,0 +1,925 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "APZCCallbackHelper.h" + +#include "TouchActionHelper.h" +#include "gfxPlatform.h" // For gfxPlatform::UseTiling + +#include "mozilla/dom/Element.h" +#include "mozilla/dom/MouseEventBinding.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/layers/LayerTransactionChild.h" +#include "mozilla/layers/RepaintRequest.h" +#include "mozilla/layers/ShadowLayers.h" +#include "mozilla/layers/WebRenderLayerManager.h" +#include "mozilla/layers/WebRenderBridgeChild.h" +#include "mozilla/DisplayPortUtils.h" +#include "mozilla/PresShell.h" +#include "mozilla/TouchEvents.h" +#include "mozilla/ToString.h" +#include "mozilla/ViewportUtils.h" +#include "nsContainerFrame.h" +#include "nsContentUtils.h" +#include "nsIContent.h" +#include "nsIDOMWindowUtils.h" +#include "mozilla/dom/Document.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIScrollableFrame.h" +#include "nsLayoutUtils.h" +#include "nsPrintfCString.h" +#include "nsRefreshDriver.h" +#include "nsString.h" +#include "nsView.h" +#include "Layers.h" + +static mozilla::LazyLogModule sApzHlpLog("apz.helper"); +#define APZCCH_LOG(...) MOZ_LOG(sApzHlpLog, LogLevel::Debug, (__VA_ARGS__)) +static mozilla::LazyLogModule sDisplayportLog("apz.displayport"); + +namespace mozilla { +namespace layers { + +using dom::BrowserParent; + +uint64_t APZCCallbackHelper::sLastTargetAPZCNotificationInputBlock = + uint64_t(-1); + +static ScreenMargin RecenterDisplayPort(const ScreenMargin& aDisplayPort) { + ScreenMargin margins = aDisplayPort; + margins.right = margins.left = margins.LeftRight() / 2; + margins.top = margins.bottom = margins.TopBottom() / 2; + return margins; +} + +static PresShell* GetPresShell(const nsIContent* aContent) { + if (dom::Document* doc = aContent->GetComposedDoc()) { + return doc->GetPresShell(); + } + return nullptr; +} + +static CSSPoint ScrollFrameTo(nsIScrollableFrame* aFrame, + const RepaintRequest& aRequest, + bool& aSuccessOut) { + aSuccessOut = false; + CSSPoint targetScrollPosition = aRequest.GetLayoutScrollOffset(); + + if (!aFrame) { + return targetScrollPosition; + } + + CSSPoint geckoScrollPosition = + CSSPoint::FromAppUnits(aFrame->GetScrollPosition()); + + // If the repaint request was triggered due to a previous main-thread scroll + // offset update sent to the APZ, then we don't need to do another scroll here + // and we can just return. + if (!aRequest.GetScrollOffsetUpdated()) { + return geckoScrollPosition; + } + + // If this frame is overflow:hidden, then the expectation is that it was + // sized in a way that respects its scrollable boundaries. For the root + // frame, this means that it cannot be scrolled in such a way that it moves + // the layout viewport. For a non-root frame, this means that it cannot be + // scrolled at all. + // + // In either case, |targetScrollPosition| should be the same as + // |geckoScrollPosition| here. + // + // However, this is slightly racy. We query the overflow property of the + // scroll frame at the time the repaint request arrives at the main thread + // (i.e., right now), but APZ made the decision of whether or not to allow + // scrolling based on the information it had at the time it processed the + // scroll event. The overflow property could have changed at some time + // between the two events and so APZ may have computed a scrollable region + // that is larger than what is actually allowed. + // + // Currently, we allow the scroll position to change even though the frame is + // overflow:hidden (that is, we take |targetScrollPosition|). If this turns + // out to be problematic, an alternative solution would be to ignore the + // scroll position change (that is, use |geckoScrollPosition|). + if (aFrame->GetScrollStyles().mVertical == StyleOverflow::Hidden && + targetScrollPosition.y != geckoScrollPosition.y) { + NS_WARNING( + nsPrintfCString( + "APZCCH: targetScrollPosition.y (%f) != geckoScrollPosition.y (%f)", + targetScrollPosition.y, geckoScrollPosition.y) + .get()); + } + if (aFrame->GetScrollStyles().mHorizontal == StyleOverflow::Hidden && + targetScrollPosition.x != geckoScrollPosition.x) { + NS_WARNING( + nsPrintfCString( + "APZCCH: targetScrollPosition.x (%f) != geckoScrollPosition.x (%f)", + targetScrollPosition.x, geckoScrollPosition.x) + .get()); + } + + // If the scrollable frame is currently in the middle of an async or smooth + // scroll then we don't want to interrupt it (see bug 961280). + // Also if the scrollable frame got a scroll request from a higher priority + // origin since the last layers update, then we don't want to push our scroll + // request because we'll clobber that one, which is bad. + bool scrollInProgress = APZCCallbackHelper::IsScrollInProgress(aFrame); + if (!scrollInProgress) { + aFrame->ScrollToCSSPixelsApproximate(targetScrollPosition, + ScrollOrigin::Apz); + geckoScrollPosition = CSSPoint::FromAppUnits(aFrame->GetScrollPosition()); + aSuccessOut = true; + } + // Return the final scroll position after setting it so that anything that + // relies on it can have an accurate value. Note that even if we set it above + // re-querying it is a good idea because it may have gotten clamped or + // rounded. + return geckoScrollPosition; +} + +/** + * Scroll the scroll frame associated with |aContent| to the scroll position + * requested in |aRequest|. + * + * Any difference between the requested and actual scroll positions is used to + * update the callback-transform stored on the content, and return a new + * display port. + */ +static DisplayPortMargins ScrollFrame(nsIContent* aContent, + const RepaintRequest& aRequest) { + // Scroll the window to the desired spot + nsIScrollableFrame* sf = + nsLayoutUtils::FindScrollableFrameFor(aRequest.GetScrollId()); + if (sf) { + sf->ResetScrollInfoIfNeeded(aRequest.GetScrollGeneration(), + aRequest.IsAnimationInProgress()); + sf->SetScrollableByAPZ(!aRequest.IsScrollInfoLayer()); + if (sf->IsRootScrollFrameOfDocument()) { + if (!APZCCallbackHelper::IsScrollInProgress(sf)) { + APZCCH_LOG("Setting VV offset to %s\n", + ToString(aRequest.GetVisualScrollOffset()).c_str()); + if (sf->SetVisualViewportOffset( + CSSPoint::ToAppUnits(aRequest.GetVisualScrollOffset()), + /* aRepaint = */ false)) { + // sf can't be destroyed if SetVisualViewportOffset returned true. + sf->MarkEverScrolled(); + } + } + } + } + // sf might have been destroyed by the call to SetVisualViewportOffset, so + // re-get it. + sf = nsLayoutUtils::FindScrollableFrameFor(aRequest.GetScrollId()); + bool scrollUpdated = false; + auto displayPortMargins = DisplayPortMargins::ForScrollFrame( + sf, aRequest.GetDisplayPortMargins(), + Some(aRequest.DisplayportPixelsPerCSSPixel())); + CSSPoint apzScrollOffset = aRequest.GetVisualScrollOffset(); + CSSPoint actualScrollOffset = ScrollFrameTo(sf, aRequest, scrollUpdated); + CSSPoint scrollDelta = apzScrollOffset - actualScrollOffset; + + if (scrollUpdated) { + if (aRequest.IsScrollInfoLayer()) { + // In cases where the APZ scroll offset is different from the content + // scroll offset, we want to interpret the margins as relative to the APZ + // scroll offset except when the frame is not scrollable by APZ. + // Therefore, if the layer is a scroll info layer, we leave the margins + // as-is and they will be interpreted as relative to the content scroll + // offset. + if (nsIFrame* frame = aContent->GetPrimaryFrame()) { + frame->SchedulePaint(); + } + } else { + // Correct the display port due to the difference between the requested + // and actual scroll offsets. + displayPortMargins = DisplayPortMargins::FromAPZ( + aRequest.GetDisplayPortMargins(), apzScrollOffset, actualScrollOffset, + aRequest.DisplayportPixelsPerCSSPixel()); + } + } else if (aRequest.IsRootContent() && + apzScrollOffset != aRequest.GetLayoutScrollOffset()) { + // APZ uses the visual viewport's offset to calculate where to place the + // display port, so the display port is misplaced when a pinch zoom occurs. + // + // We need to force a display port adjustment in the following paint to + // account for a difference between the requested and actual scroll + // offsets in repaints requested by + // AsyncPanZoomController::NotifyLayersUpdated. + displayPortMargins = DisplayPortMargins::FromAPZ( + aRequest.GetDisplayPortMargins(), apzScrollOffset, actualScrollOffset, + aRequest.DisplayportPixelsPerCSSPixel()); + } else { + // For whatever reason we couldn't update the scroll offset on the scroll + // frame, which means the data APZ used for its displayport calculation is + // stale. Fall back to a sane default behaviour. Note that we don't + // tile-align the recentered displayport because tile-alignment depends on + // the scroll position, and the scroll position here is out of our control. + // See bug 966507 comment 21 for a more detailed explanation. + displayPortMargins = DisplayPortMargins::ForScrollFrame( + sf, RecenterDisplayPort(aRequest.GetDisplayPortMargins()), + Some(aRequest.DisplayportPixelsPerCSSPixel())); + } + + // APZ transforms inputs assuming we applied the exact scroll offset it + // requested (|apzScrollOffset|). Since we may not have, record the difference + // between what APZ asked for and what we actually applied, and apply it to + // input events to compensate. + // Note that if the main-thread had a change in its scroll position, we don't + // want to record that difference here, because it can be large and throw off + // input events by a large amount. It is also going to be transient, because + // any main-thread scroll position change will be synced to APZ and we will + // get another repaint request when APZ confirms. In the interval while this + // is happening we can just leave the callback transform as it was. + bool mainThreadScrollChanged = + sf && sf->CurrentScrollGeneration() != aRequest.GetScrollGeneration() && + nsLayoutUtils::CanScrollOriginClobberApz(sf->LastScrollOrigin()); + if (aContent && !mainThreadScrollChanged) { + aContent->SetProperty(nsGkAtoms::apzCallbackTransform, + new CSSPoint(scrollDelta), + nsINode::DeleteProperty); + } + + return displayPortMargins; +} + +static void SetDisplayPortMargins(PresShell* aPresShell, nsIContent* aContent, + const DisplayPortMargins& aDisplayPortMargins, + CSSSize aDisplayPortBase) { + if (!aContent) { + return; + } + + bool hadDisplayPort = DisplayPortUtils::HasDisplayPort(aContent); + if (MOZ_LOG_TEST(sDisplayportLog, LogLevel::Debug)) { + if (!hadDisplayPort) { + mozilla::layers::ScrollableLayerGuid::ViewID viewID = + mozilla::layers::ScrollableLayerGuid::NULL_SCROLL_ID; + nsLayoutUtils::FindIDFor(aContent, &viewID); + MOZ_LOG( + sDisplayportLog, LogLevel::Debug, + ("APZCCH installing displayport margins %s on scrollId=%" PRIu64 "\n", + ToString(aDisplayPortMargins).c_str(), viewID)); + } + } + DisplayPortUtils::SetDisplayPortMargins(aContent, aPresShell, + aDisplayPortMargins, 0); + if (!hadDisplayPort) { + DisplayPortUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors( + aContent->GetPrimaryFrame()); + } + + nsRect base(0, 0, aDisplayPortBase.width * AppUnitsPerCSSPixel(), + aDisplayPortBase.height * AppUnitsPerCSSPixel()); + DisplayPortUtils::SetDisplayPortBaseIfNotSet(aContent, base); +} + +static void SetPaintRequestTime(nsIContent* aContent, + const TimeStamp& aPaintRequestTime) { + aContent->SetProperty(nsGkAtoms::paintRequestTime, + new TimeStamp(aPaintRequestTime), + nsINode::DeleteProperty); +} + +void APZCCallbackHelper::NotifyLayerTransforms( + const nsTArray& aTransforms) { + MOZ_ASSERT(NS_IsMainThread()); + for (const MatrixMessage& msg : aTransforms) { + BrowserParent* parent = + BrowserParent::GetBrowserParentFromLayersId(msg.GetLayersId()); + if (parent) { + parent->SetChildToParentConversionMatrix( + ViewAs( + msg.GetMatrix(), + PixelCastJustification::ContentProcessIsLayerInUiProcess), + msg.GetTopLevelViewportVisibleRectInBrowserCoords()); + } + } +} + +void APZCCallbackHelper::UpdateRootFrame(const RepaintRequest& aRequest) { + if (aRequest.GetScrollId() == ScrollableLayerGuid::NULL_SCROLL_ID) { + return; + } + RefPtr content = + nsLayoutUtils::FindContentFor(aRequest.GetScrollId()); + if (!content) { + return; + } + + RefPtr presShell = GetPresShell(content); + if (!presShell || aRequest.GetPresShellId() != presShell->GetPresShellId()) { + return; + } + + APZCCH_LOG("Handling request %s\n", ToString(aRequest).c_str()); + if (nsLayoutUtils::AllowZoomingForDocument(presShell->GetDocument()) && + aRequest.GetAsyncZoom().scale != 1.0) { + // If zooming is disabled then we don't really want to let APZ fiddle + // with these things. In theory setting the resolution here should be a + // no-op, but setting the visual viewport size is bad because it can cause a + // stale value to be returned by window.innerWidth/innerHeight (see bug + // 1187792). + + float presShellResolution = presShell->GetResolution(); + + // If the pres shell resolution has changed on the content side side + // the time this repaint request was fired, consider this request out of + // date and drop it; setting a zoom based on the out-of-date resolution can + // have the effect of getting us stuck with the stale resolution. + if (!FuzzyEqualsMultiplicative(presShellResolution, + aRequest.GetPresShellResolution())) { + return; + } + + // The pres shell resolution is updated by the the async zoom since the + // last paint. + presShellResolution = + aRequest.GetPresShellResolution() * aRequest.GetAsyncZoom().scale; + presShell->SetResolutionAndScaleTo(presShellResolution, + ResolutionChangeOrigin::Apz); + + // Changing the resolution will trigger a reflow which will cause the + // main-thread scroll position to be realigned in layer pixels. This + // (subpixel) scroll mutation can trigger a scroll update to APZ which + // is undesirable. Instead of having that happen as part of the post-reflow + // code, we force it to happen here with ScrollOrigin::Apz so that it + // doesn't trigger a scroll update to APZ. + nsIScrollableFrame* sf = + nsLayoutUtils::FindScrollableFrameFor(aRequest.GetScrollId()); + CSSPoint currentScrollPosition = + CSSPoint::FromAppUnits(sf->GetScrollPosition()); + sf->ScrollToCSSPixelsApproximate(currentScrollPosition, ScrollOrigin::Apz); + } + + // Do this as late as possible since scrolling can flush layout. It also + // adjusts the display port margins, so do it before we set those. + DisplayPortMargins displayPortMargins = ScrollFrame(content, aRequest); + + SetDisplayPortMargins(presShell, content, displayPortMargins, + aRequest.CalculateCompositedSizeInCssPixels()); + SetPaintRequestTime(content, aRequest.GetPaintRequestTime()); +} + +void APZCCallbackHelper::UpdateSubFrame(const RepaintRequest& aRequest) { + if (aRequest.GetScrollId() == ScrollableLayerGuid::NULL_SCROLL_ID) { + return; + } + RefPtr content = + nsLayoutUtils::FindContentFor(aRequest.GetScrollId()); + if (!content) { + return; + } + + // We don't currently support zooming for subframes, so nothing extra + // needs to be done beyond the tasks common to this and UpdateRootFrame. + DisplayPortMargins displayPortMargins = ScrollFrame(content, aRequest); + if (RefPtr presShell = GetPresShell(content)) { + SetDisplayPortMargins(presShell, content, displayPortMargins, + aRequest.CalculateCompositedSizeInCssPixels()); + } + SetPaintRequestTime(content, aRequest.GetPaintRequestTime()); +} + +bool APZCCallbackHelper::GetOrCreateScrollIdentifiers( + nsIContent* aContent, uint32_t* aPresShellIdOut, + ScrollableLayerGuid::ViewID* aViewIdOut) { + if (!aContent) { + return false; + } + *aViewIdOut = nsLayoutUtils::FindOrCreateIDFor(aContent); + if (PresShell* presShell = GetPresShell(aContent)) { + *aPresShellIdOut = presShell->GetPresShellId(); + return true; + } + return false; +} + +void APZCCallbackHelper::InitializeRootDisplayport(PresShell* aPresShell) { + // Create a view-id and set a zero-margin displayport for the root element + // of the root document in the chrome process. This ensures that the scroll + // frame for this element gets an APZC, which in turn ensures that all content + // in the chrome processes is covered by an APZC. + // The displayport is zero-margin because this element is generally not + // actually scrollable (if it is, APZC will set proper margins when it's + // scrolled). + if (!aPresShell) { + return; + } + + MOZ_ASSERT(aPresShell->GetDocument()); + nsIContent* content = aPresShell->GetDocument()->GetDocumentElement(); + if (!content) { + return; + } + + uint32_t presShellId; + ScrollableLayerGuid::ViewID viewId; + if (APZCCallbackHelper::GetOrCreateScrollIdentifiers(content, &presShellId, + &viewId)) { + nsPresContext* pc = aPresShell->GetPresContext(); + // This code is only correct for root content or toplevel documents. + MOZ_ASSERT(!pc || pc->IsRootContentDocumentCrossProcess() || + !pc->GetParentPresContext()); + nsIFrame* frame = aPresShell->GetRootScrollFrame(); + if (!frame) { + frame = aPresShell->GetRootFrame(); + } + nsRect baseRect; + if (frame) { + baseRect = nsRect(nsPoint(0, 0), + nsLayoutUtils::CalculateCompositionSizeForFrame(frame)); + } else if (pc) { + baseRect = nsRect(nsPoint(0, 0), pc->GetVisibleArea().Size()); + } + MOZ_LOG( + sDisplayportLog, LogLevel::Debug, + ("Initializing root displayport on scrollId=%" PRIu64 "\n", viewId)); + DisplayPortUtils::SetDisplayPortBaseIfNotSet(content, baseRect); + // Note that we also set the base rect that goes with these margins in + // nsRootBoxFrame::BuildDisplayList. + DisplayPortUtils::SetDisplayPortMargins( + content, aPresShell, DisplayPortMargins::Empty(content), 0); + DisplayPortUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors( + content->GetPrimaryFrame()); + } +} + +nsPresContext* APZCCallbackHelper::GetPresContextForContent( + nsIContent* aContent) { + dom::Document* doc = aContent->GetComposedDoc(); + if (!doc) { + return nullptr; + } + PresShell* presShell = doc->GetPresShell(); + if (!presShell) { + return nullptr; + } + return presShell->GetPresContext(); +} + +PresShell* APZCCallbackHelper::GetRootContentDocumentPresShellForContent( + nsIContent* aContent) { + nsPresContext* context = GetPresContextForContent(aContent); + if (!context) { + return nullptr; + } + context = context->GetInProcessRootContentDocumentPresContext(); + if (!context) { + return nullptr; + } + return context->PresShell(); +} + +nsEventStatus APZCCallbackHelper::DispatchWidgetEvent(WidgetGUIEvent& aEvent) { + nsEventStatus status = nsEventStatus_eConsumeNoDefault; + if (aEvent.mWidget) { + aEvent.mWidget->DispatchEvent(&aEvent, status); + } + return status; +} + +nsEventStatus APZCCallbackHelper::DispatchSynthesizedMouseEvent( + EventMessage aMsg, uint64_t aTime, const LayoutDevicePoint& aRefPoint, + Modifiers aModifiers, int32_t aClickCount, nsIWidget* aWidget) { + MOZ_ASSERT(aMsg == eMouseMove || aMsg == eMouseDown || aMsg == eMouseUp || + aMsg == eMouseLongTap); + + WidgetMouseEvent event(true, aMsg, aWidget, WidgetMouseEvent::eReal, + WidgetMouseEvent::eNormal); + event.mRefPoint = LayoutDeviceIntPoint::Truncate(aRefPoint.x, aRefPoint.y); + event.mTime = aTime; + event.mButton = MouseButton::ePrimary; + event.mInputSource = dom::MouseEvent_Binding::MOZ_SOURCE_TOUCH; + if (aMsg == eMouseLongTap) { + event.mFlags.mOnlyChromeDispatch = true; + } + if (aMsg != eMouseMove) { + event.mClickCount = aClickCount; + } + event.mModifiers = aModifiers; + // Real touch events will generate corresponding pointer events. We set + // convertToPointer to false to prevent the synthesized mouse events generate + // pointer events again. + event.convertToPointer = false; + return DispatchWidgetEvent(event); +} + +bool APZCCallbackHelper::DispatchMouseEvent( + PresShell* aPresShell, const nsString& aType, const CSSPoint& aPoint, + int32_t aButton, int32_t aClickCount, int32_t aModifiers, + unsigned short aInputSourceArg, uint32_t aPointerId) { + NS_ENSURE_TRUE(aPresShell, true); + + bool defaultPrevented = false; + nsContentUtils::SendMouseEvent( + aPresShell, aType, aPoint.x, aPoint.y, aButton, + nsIDOMWindowUtils::MOUSE_BUTTONS_NOT_SPECIFIED, aClickCount, aModifiers, + /* aIgnoreRootScrollFrame = */ false, 0, aInputSourceArg, aPointerId, + false, &defaultPrevented, false, /* aIsWidgetEventSynthesized = */ false); + return defaultPrevented; +} + +void APZCCallbackHelper::FireSingleTapEvent(const LayoutDevicePoint& aPoint, + Modifiers aModifiers, + int32_t aClickCount, + nsIWidget* aWidget) { + if (aWidget->Destroyed()) { + return; + } + APZCCH_LOG("Dispatching single-tap component events to %s\n", + ToString(aPoint).c_str()); + int time = 0; + DispatchSynthesizedMouseEvent(eMouseMove, time, aPoint, aModifiers, + aClickCount, aWidget); + DispatchSynthesizedMouseEvent(eMouseDown, time, aPoint, aModifiers, + aClickCount, aWidget); + DispatchSynthesizedMouseEvent(eMouseUp, time, aPoint, aModifiers, aClickCount, + aWidget); +} + +static dom::Element* GetDisplayportElementFor( + nsIScrollableFrame* aScrollableFrame) { + if (!aScrollableFrame) { + return nullptr; + } + nsIFrame* scrolledFrame = aScrollableFrame->GetScrolledFrame(); + if (!scrolledFrame) { + return nullptr; + } + // |scrolledFrame| should at this point be the root content frame of the + // nearest ancestor scrollable frame. The element corresponding to this + // frame should be the one with the displayport set on it, so find that + // element and return it. + nsIContent* content = scrolledFrame->GetContent(); + MOZ_ASSERT(content->IsElement()); // roc says this must be true + return content->AsElement(); +} + +static dom::Element* GetRootDocumentElementFor(nsIWidget* aWidget) { + // This returns the root element that ChromeProcessController sets the + // displayport on during initialization. + if (nsView* view = nsView::GetViewFor(aWidget)) { + if (PresShell* presShell = view->GetPresShell()) { + MOZ_ASSERT(presShell->GetDocument()); + return presShell->GetDocument()->GetDocumentElement(); + } + } + return nullptr; +} + +namespace { + +using FrameForPointOption = nsLayoutUtils::FrameForPointOption; + +// Determine the scrollable target frame for the given point and add it to +// the target list. If the frame doesn't have a displayport, set one. +// Return whether or not the frame had a displayport that has already been +// painted (in this case, the caller can send the SetTargetAPZC notification +// right away, rather than waiting for a transaction to propagate the +// displayport to APZ first). +static bool PrepareForSetTargetAPZCNotification( + nsIWidget* aWidget, const LayersId& aLayersId, nsIFrame* aRootFrame, + const LayoutDeviceIntPoint& aRefPoint, + nsTArray* aTargets) { + ScrollableLayerGuid guid(aLayersId, 0, ScrollableLayerGuid::NULL_SCROLL_ID); + RelativeTo relativeTo{aRootFrame, ViewportType::Visual}; + nsPoint point = nsLayoutUtils::GetEventCoordinatesRelativeTo( + aWidget, aRefPoint, relativeTo); + nsIFrame* target = nsLayoutUtils::GetFrameForPoint(relativeTo, point); + nsIScrollableFrame* scrollAncestor = + target ? nsLayoutUtils::GetAsyncScrollableAncestorFrame(target) + : aRootFrame->PresShell()->GetRootScrollFrameAsScrollable(); + + // Assuming that if there's no scrollAncestor, there's already a displayPort. + nsCOMPtr dpElement = + scrollAncestor ? GetDisplayportElementFor(scrollAncestor) + : GetRootDocumentElementFor(aWidget); + + if (MOZ_LOG_TEST(sApzHlpLog, LogLevel::Debug)) { + nsAutoString dpElementDesc; + if (dpElement) { + dpElement->Describe(dpElementDesc); + } + APZCCH_LOG("For event at %s found scrollable element %p (%s)\n", + ToString(aRefPoint).c_str(), dpElement.get(), + NS_LossyConvertUTF16toASCII(dpElementDesc).get()); + } + + bool guidIsValid = APZCCallbackHelper::GetOrCreateScrollIdentifiers( + dpElement, &(guid.mPresShellId), &(guid.mScrollId)); + aTargets->AppendElement(guid); + + if (!guidIsValid) { + return false; + } + if (DisplayPortUtils::HasDisplayPort(dpElement)) { + // If the element has a displayport but it hasn't been painted yet, + // we want the caller to wait for the paint to happen, but we don't + // need to set the displayport here since it's already been set. + return !DisplayPortUtils::HasPaintedDisplayPort(dpElement); + } + + if (!scrollAncestor) { + // This can happen if the document element gets swapped out after + // ChromeProcessController runs InitializeRootDisplayport. In this case + // let's try to set a displayport again and bail out on this operation. + APZCCH_LOG("Widget %p's document element %p didn't have a displayport\n", + aWidget, dpElement.get()); + APZCCallbackHelper::InitializeRootDisplayport(aRootFrame->PresShell()); + return false; + } + + APZCCH_LOG("%p didn't have a displayport, so setting one...\n", + dpElement.get()); + MOZ_LOG(sDisplayportLog, LogLevel::Debug, + ("Activating displayport on scrollId=%" PRIu64 " for SetTargetAPZC\n", + guid.mScrollId)); + bool activated = DisplayPortUtils::CalculateAndSetDisplayPortMargins( + scrollAncestor, DisplayPortUtils::RepaintMode::Repaint); + if (!activated) { + return false; + } + + nsIFrame* frame = do_QueryFrame(scrollAncestor); + DisplayPortUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(frame); + + return true; +} + +static void SendLayersDependentApzcTargetConfirmation( + PresShell* aPresShell, uint64_t aInputBlockId, + nsTArray&& aTargets) { + LayerManager* lm = aPresShell->GetLayerManager(); + if (!lm) { + return; + } + + if (WebRenderLayerManager* wrlm = lm->AsWebRenderLayerManager()) { + if (WebRenderBridgeChild* wrbc = wrlm->WrBridge()) { + wrbc->SendSetConfirmedTargetAPZC(aInputBlockId, aTargets); + } + return; + } + + ShadowLayerForwarder* lf = lm->AsShadowForwarder(); + if (!lf) { + return; + } + + LayerTransactionChild* shadow = lf->GetShadowManager(); + if (!shadow) { + return; + } + + shadow->SendSetConfirmedTargetAPZC(aInputBlockId, aTargets); +} + +} // namespace + +DisplayportSetListener::DisplayportSetListener( + nsIWidget* aWidget, PresShell* aPresShell, const uint64_t& aInputBlockId, + nsTArray&& aTargets) + : OneShotPostRefreshObserver(aPresShell), + mWidget(aWidget), + mInputBlockId(aInputBlockId), + mTargets(std::move(aTargets)) { + MOZ_ASSERT(!mAction, "Setting Action twice"); + mAction = [](PresShell* aPresShell, + OneShotPostRefreshObserver* aThisObserver) { + OnPostRefresh(static_cast(aThisObserver), + aPresShell); + }; +} + +DisplayportSetListener::~DisplayportSetListener() = default; + +bool DisplayportSetListener::Register() { + if (nsPresContext* presContext = mPresShell->GetPresContext()) { + presContext->RegisterOneShotPostRefreshObserver(this); + APZCCH_LOG("Successfully registered post-refresh observer\n"); + return true; + } + // In case of failure just send the notification right away + APZCCH_LOG("Sending target APZCs for input block %" PRIu64 "\n", + mInputBlockId); + mWidget->SetConfirmedTargetAPZC(mInputBlockId, mTargets); + return false; +} + +/* static */ +void DisplayportSetListener::OnPostRefresh(DisplayportSetListener* aListener, + PresShell* aPresShell) { + APZCCH_LOG("Got refresh, sending target APZCs for input block %" PRIu64 "\n", + aListener->mInputBlockId); + SendLayersDependentApzcTargetConfirmation( + aPresShell, aListener->mInputBlockId, std::move(aListener->mTargets)); +} + +UniquePtr +APZCCallbackHelper::SendSetTargetAPZCNotification(nsIWidget* aWidget, + dom::Document* aDocument, + const WidgetGUIEvent& aEvent, + const LayersId& aLayersId, + uint64_t aInputBlockId) { + if (!aWidget || !aDocument) { + return nullptr; + } + if (aInputBlockId == sLastTargetAPZCNotificationInputBlock) { + // We have already confirmed the target APZC for a previous event of this + // input block. If we activated a scroll frame for this input block, + // sending another target APZC confirmation would be harmful, as it might + // race the original confirmation (which needs to go through a layers + // transaction). + APZCCH_LOG("Not resending target APZC confirmation for input block %" PRIu64 + "\n", + aInputBlockId); + return nullptr; + } + sLastTargetAPZCNotificationInputBlock = aInputBlockId; + if (PresShell* presShell = aDocument->GetPresShell()) { + if (nsIFrame* rootFrame = presShell->GetRootFrame()) { + bool waitForRefresh = false; + nsTArray targets; + + if (const WidgetTouchEvent* touchEvent = aEvent.AsTouchEvent()) { + for (size_t i = 0; i < touchEvent->mTouches.Length(); i++) { + waitForRefresh |= PrepareForSetTargetAPZCNotification( + aWidget, aLayersId, rootFrame, touchEvent->mTouches[i]->mRefPoint, + &targets); + } + } else if (const WidgetWheelEvent* wheelEvent = aEvent.AsWheelEvent()) { + waitForRefresh = PrepareForSetTargetAPZCNotification( + aWidget, aLayersId, rootFrame, wheelEvent->mRefPoint, &targets); + } else if (const WidgetMouseEvent* mouseEvent = aEvent.AsMouseEvent()) { + waitForRefresh = PrepareForSetTargetAPZCNotification( + aWidget, aLayersId, rootFrame, mouseEvent->mRefPoint, &targets); + } + // TODO: Do other types of events need to be handled? + + if (!targets.IsEmpty()) { + if (waitForRefresh) { + APZCCH_LOG( + "At least one target got a new displayport, need to wait for " + "refresh\n"); + return MakeUnique( + aWidget, presShell, aInputBlockId, std::move(targets)); + } + APZCCH_LOG("Sending target APZCs for input block %" PRIu64 "\n", + aInputBlockId); + aWidget->SetConfirmedTargetAPZC(aInputBlockId, targets); + } + } + } + return nullptr; +} + +nsTArray +APZCCallbackHelper::SendSetAllowedTouchBehaviorNotification( + nsIWidget* aWidget, dom::Document* aDocument, + const WidgetTouchEvent& aEvent, uint64_t aInputBlockId, + const SetAllowedTouchBehaviorCallback& aCallback) { + nsTArray flags; + if (!aWidget || !aDocument) { + return flags; + } + if (PresShell* presShell = aDocument->GetPresShell()) { + if (nsIFrame* rootFrame = presShell->GetRootFrame()) { + for (uint32_t i = 0; i < aEvent.mTouches.Length(); i++) { + flags.AppendElement(TouchActionHelper::GetAllowedTouchBehavior( + aWidget, RelativeTo{rootFrame, ViewportType::Visual}, + aEvent.mTouches[i]->mRefPoint)); + } + aCallback(aInputBlockId, flags); + } + } + return flags; +} + +void APZCCallbackHelper::NotifyMozMouseScrollEvent( + const ScrollableLayerGuid::ViewID& aScrollId, const nsString& aEvent) { + nsCOMPtr targetContent = nsLayoutUtils::FindContentFor(aScrollId); + if (!targetContent) { + return; + } + RefPtr ownerDoc = targetContent->OwnerDoc(); + if (!ownerDoc) { + return; + } + + nsContentUtils::DispatchEventOnlyToChrome(ownerDoc, targetContent, aEvent, + CanBubble::eYes, Cancelable::eYes); +} + +void APZCCallbackHelper::NotifyFlushComplete(PresShell* aPresShell) { + MOZ_ASSERT(NS_IsMainThread()); + // In some cases, flushing the APZ state to the main thread doesn't actually + // trigger a flush and repaint (this is an intentional optimization - the + // stuff visible to the user is still correct). However, reftests update their + // snapshot based on invalidation events that are emitted during paints, + // so we ensure that we kick off a paint when an APZ flush is done. Note that + // only chrome/testing code can trigger this behaviour. + if (aPresShell && aPresShell->GetRootFrame()) { + aPresShell->GetRootFrame()->SchedulePaint(nsIFrame::PAINT_DEFAULT, false); + } + + nsCOMPtr observerService = + mozilla::services::GetObserverService(); + MOZ_ASSERT(observerService); + observerService->NotifyObservers(nullptr, "apz-repaints-flushed", nullptr); +} + +/* static */ +bool APZCCallbackHelper::IsScrollInProgress(nsIScrollableFrame* aFrame) { + using IncludeApzAnimation = nsIScrollableFrame::IncludeApzAnimation; + + return aFrame->IsScrollAnimating(IncludeApzAnimation::No) || + nsLayoutUtils::CanScrollOriginClobberApz(aFrame->LastScrollOrigin()); +} + +/* static */ +void APZCCallbackHelper::NotifyAsyncScrollbarDragInitiated( + uint64_t aDragBlockId, const ScrollableLayerGuid::ViewID& aScrollId, + ScrollDirection aDirection) { + MOZ_ASSERT(NS_IsMainThread()); + if (nsIScrollableFrame* scrollFrame = + nsLayoutUtils::FindScrollableFrameFor(aScrollId)) { + scrollFrame->AsyncScrollbarDragInitiated(aDragBlockId, aDirection); + } +} + +/* static */ +void APZCCallbackHelper::NotifyAsyncScrollbarDragRejected( + const ScrollableLayerGuid::ViewID& aScrollId) { + MOZ_ASSERT(NS_IsMainThread()); + if (nsIScrollableFrame* scrollFrame = + nsLayoutUtils::FindScrollableFrameFor(aScrollId)) { + scrollFrame->AsyncScrollbarDragRejected(); + } +} + +/* static */ +void APZCCallbackHelper::NotifyAsyncAutoscrollRejected( + const ScrollableLayerGuid::ViewID& aScrollId) { + MOZ_ASSERT(NS_IsMainThread()); + nsCOMPtr observerService = + mozilla::services::GetObserverService(); + MOZ_ASSERT(observerService); + + nsAutoString data; + data.AppendInt(aScrollId); + observerService->NotifyObservers(nullptr, "autoscroll-rejected-by-apz", + data.get()); +} + +/* static */ +void APZCCallbackHelper::CancelAutoscroll( + const ScrollableLayerGuid::ViewID& aScrollId) { + MOZ_ASSERT(NS_IsMainThread()); + nsCOMPtr observerService = + mozilla::services::GetObserverService(); + MOZ_ASSERT(observerService); + + nsAutoString data; + data.AppendInt(aScrollId); + observerService->NotifyObservers(nullptr, "apz:cancel-autoscroll", + data.get()); +} + +/* static */ +void APZCCallbackHelper::NotifyPinchGesture( + PinchGestureInput::PinchGestureType aType, + const LayoutDevicePoint& aFocusPoint, LayoutDeviceCoord aSpanChange, + Modifiers aModifiers, const nsCOMPtr& aWidget) { + APZCCH_LOG("APZCCallbackHelper dispatching pinch gesture\n"); + EventMessage msg; + switch (aType) { + case PinchGestureInput::PINCHGESTURE_START: + msg = eMagnifyGestureStart; + break; + case PinchGestureInput::PINCHGESTURE_SCALE: + msg = eMagnifyGestureUpdate; + break; + case PinchGestureInput::PINCHGESTURE_FINGERLIFTED: + case PinchGestureInput::PINCHGESTURE_END: + msg = eMagnifyGesture; + break; + } + + WidgetSimpleGestureEvent event(true, msg, aWidget.get()); + // XXX mDelta for the eMagnifyGesture event is supposed to be the + // cumulative magnification over the entire gesture (per docs in + // SimpleGestureEvent.webidl) but currently APZ just sends us a zero + // aSpanChange for that event, so the mDelta is wrong. Nothing relies + // on that currently, but we might want to fix it at some point. + event.mDelta = aSpanChange; + event.mModifiers = aModifiers; + event.mRefPoint = RoundedToInt(aFocusPoint); + + DispatchWidgetEvent(event); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/util/APZCCallbackHelper.h b/gfx/layers/apz/util/APZCCallbackHelper.h new file mode 100644 index 0000000000..4bf6806c76 --- /dev/null +++ b/gfx/layers/apz/util/APZCCallbackHelper.h @@ -0,0 +1,204 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_APZCCallbackHelper_h +#define mozilla_layers_APZCCallbackHelper_h + +#include "InputData.h" +#include "LayersTypes.h" +#include "Units.h" +#include "mozilla/EventForwards.h" +#include "mozilla/layers/MatrixMessage.h" +#include "nsRefreshObservers.h" + +#include + +class nsIContent; +class nsIScrollableFrame; +class nsIWidget; +template +struct already_AddRefed; +template +class nsCOMPtr; + +namespace mozilla { + +class PresShell; + +namespace layers { + +struct RepaintRequest; + +typedef std::function&)> + SetAllowedTouchBehaviorCallback; + +/* Refer to documentation on SendSetTargetAPZCNotification for this class */ +class DisplayportSetListener : public OneShotPostRefreshObserver { + public: + DisplayportSetListener(nsIWidget* aWidget, PresShell* aPresShell, + const uint64_t& aInputBlockId, + nsTArray&& aTargets); + virtual ~DisplayportSetListener(); + bool Register(); + + private: + RefPtr mWidget; + uint64_t mInputBlockId; + nsTArray mTargets; + + static void OnPostRefresh(DisplayportSetListener* aListener, + PresShell* aPresShell); +}; + +/* This class contains some helper methods that facilitate implementing the + GeckoContentController callback interface required by the + AsyncPanZoomController. Since different platforms need to implement this + interface in similar-but- not-quite-the-same ways, this utility class + provides some helpful methods to hold code that can be shared across the + different platform implementations. + */ +class APZCCallbackHelper { + typedef mozilla::layers::ScrollableLayerGuid ScrollableLayerGuid; + + public: + static void NotifyLayerTransforms(const nsTArray& aTransforms); + + /* Applies the scroll and zoom parameters from the given RepaintRequest object + to the root frame for the given metrics' scrollId. If tiled thebes layers + are enabled, this will align the displayport to tile boundaries. Setting + the scroll position can cause some small adjustments to be made to the + actual scroll position. */ + static void UpdateRootFrame(const RepaintRequest& aRequest); + + /* Applies the scroll parameters from the given RepaintRequest object to the + subframe corresponding to given metrics' scrollId. If tiled thebes + layers are enabled, this will align the displayport to tile boundaries. + Setting the scroll position can cause some small adjustments to be made + to the actual scroll position. */ + static void UpdateSubFrame(const RepaintRequest& aRequest); + + /* Get the presShellId and view ID for the given content element. + * If the view ID does not exist, one is created. + * The pres shell ID should generally already exist; if it doesn't for some + * reason, false is returned. */ + static bool GetOrCreateScrollIdentifiers( + nsIContent* aContent, uint32_t* aPresShellIdOut, + ScrollableLayerGuid::ViewID* aViewIdOut); + + /* Initialize a zero-margin displayport on the root document element of the + given presShell. */ + static void InitializeRootDisplayport(PresShell* aPresShell); + + /* Get the pres context associated with the document enclosing |aContent|. */ + static nsPresContext* GetPresContextForContent(nsIContent* aContent); + + /* Get the pres shell associated with the root content document enclosing + * |aContent|. */ + static PresShell* GetRootContentDocumentPresShellForContent( + nsIContent* aContent); + + /* Dispatch a widget event via the widget stored in the event, if any. + * In a child process, allows the BrowserParent event-capture mechanism to + * intercept the event. */ + static nsEventStatus DispatchWidgetEvent(WidgetGUIEvent& aEvent); + + /* Synthesize a mouse event with the given parameters, and dispatch it + * via the given widget. */ + static nsEventStatus DispatchSynthesizedMouseEvent( + EventMessage aMsg, uint64_t aTime, const LayoutDevicePoint& aRefPoint, + Modifiers aModifiers, int32_t aClickCount, nsIWidget* aWidget); + + /* Dispatch a mouse event with the given parameters. + * Return whether or not any listeners have called preventDefault on the + * event. + * This is a lightweight wrapper around nsContentUtils::SendMouseEvent() + * and as such expects |aPoint| to be in layout coordinates. */ + MOZ_CAN_RUN_SCRIPT + static bool DispatchMouseEvent(PresShell* aPresShell, const nsString& aType, + const CSSPoint& aPoint, int32_t aButton, + int32_t aClickCount, int32_t aModifiers, + unsigned short aInputSourceArg, + uint32_t aPointerId); + + /* Fire a single-tap event at the given point. The event is dispatched + * via the given widget. */ + static void FireSingleTapEvent(const LayoutDevicePoint& aPoint, + Modifiers aModifiers, int32_t aClickCount, + nsIWidget* aWidget); + + /* Perform hit-testing on the touch points of |aEvent| to determine + * which scrollable frames they target. If any of these frames don't have + * a displayport, set one. + * + * If any displayports need to be set, this function returns a heap-allocated + * object. The caller is responsible for calling Register() on that object, + * and release()'ing the UniquePtr if that Register() call returns true. + * The object registers itself as a post-refresh observer on the presShell + * and ensures that notifications get sent to APZ correctly after the + * refresh. + * + * Having the caller manage this object is desirable in case they want to + * (a) know about the fact that a displayport needs to be set, and + * (b) register a post-refresh observer of their own that will run in + * a defined ordering relative to the APZ messages. + */ + static UniquePtr SendSetTargetAPZCNotification( + nsIWidget* aWidget, mozilla::dom::Document* aDocument, + const WidgetGUIEvent& aEvent, const LayersId& aLayersId, + uint64_t aInputBlockId); + + /* Figure out the allowed touch behaviors of each touch point in |aEvent| + * and send that information to the provided callback. Also returns the + * allowed touch behaviors. */ + static nsTArray SendSetAllowedTouchBehaviorNotification( + nsIWidget* aWidget, mozilla::dom::Document* aDocument, + const WidgetTouchEvent& aEvent, uint64_t aInputBlockId, + const SetAllowedTouchBehaviorCallback& aCallback); + + /* Notify content of a mouse scroll testing event. */ + static void NotifyMozMouseScrollEvent( + const ScrollableLayerGuid::ViewID& aScrollId, const nsString& aEvent); + + /* Notify content that the repaint flush is complete. */ + static void NotifyFlushComplete(PresShell* aPresShell); + + static void NotifyAsyncScrollbarDragInitiated( + uint64_t aDragBlockId, const ScrollableLayerGuid::ViewID& aScrollId, + ScrollDirection aDirection); + static void NotifyAsyncScrollbarDragRejected( + const ScrollableLayerGuid::ViewID& aScrollId); + static void NotifyAsyncAutoscrollRejected( + const ScrollableLayerGuid::ViewID& aScrollId); + + static void CancelAutoscroll(const ScrollableLayerGuid::ViewID& aScrollId); + + /* + * Check if the scrollable frame is currently in the middle of a main thread + * async or smooth scroll, or has already requested some other apz scroll that + * hasn't been acknowledged by apz. + * + * We want to discard apz updates to the main-thread scroll offset if this is + * true to prevent clobbering higher priority origins. + */ + static bool IsScrollInProgress(nsIScrollableFrame* aFrame); + + /* Notify content of the progress of a pinch gesture that APZ won't do + * zooming for (because the apz.allow_zooming pref is false). This function + * will dispatch appropriate WidgetSimpleGestureEvent events to gecko. + */ + static void NotifyPinchGesture(PinchGestureInput::PinchGestureType aType, + const LayoutDevicePoint& aFocusPoint, + LayoutDeviceCoord aSpanChange, + Modifiers aModifiers, + const nsCOMPtr& aWidget); + + private: + static uint64_t sLastTargetAPZCNotificationInputBlock; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* mozilla_layers_APZCCallbackHelper_h */ diff --git a/gfx/layers/apz/util/APZEventState.cpp b/gfx/layers/apz/util/APZEventState.cpp new file mode 100644 index 0000000000..159952f772 --- /dev/null +++ b/gfx/layers/apz/util/APZEventState.cpp @@ -0,0 +1,553 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "APZEventState.h" + +#include + +#include "APZCCallbackHelper.h" +#include "ActiveElementManager.h" +#include "TouchManager.h" +#include "mozilla/BasicEvents.h" +#include "mozilla/dom/Document.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/PositionedEventTargeting.h" +#include "mozilla/Preferences.h" +#include "mozilla/PresShell.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/StaticPrefs_ui.h" +#include "mozilla/ToString.h" +#include "mozilla/TouchEvents.h" +#include "mozilla/ViewportUtils.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/dom/MouseEventBinding.h" +#include "mozilla/layers/APZCCallbackHelper.h" +#include "mozilla/layers/IAPZCTreeManager.h" +#include "mozilla/widget/nsAutoRollup.h" +#include "nsCOMPtr.h" +#include "nsDocShell.h" +#include "nsIDOMWindowUtils.h" +#include "nsINamed.h" +#include "nsIScrollableFrame.h" +#include "nsIScrollbarMediator.h" +#include "nsITimer.h" +#include "nsIWeakReferenceUtils.h" +#include "nsIWidget.h" +#include "nsLayoutUtils.h" +#include "nsQueryFrame.h" + +static mozilla::LazyLogModule sApzEvtLog("apz.eventstate"); +#define APZES_LOG(...) MOZ_LOG(sApzEvtLog, LogLevel::Debug, (__VA_ARGS__)) + +// Static helper functions +namespace { + +int32_t WidgetModifiersToDOMModifiers(mozilla::Modifiers aModifiers) { + int32_t result = 0; + if (aModifiers & mozilla::MODIFIER_SHIFT) { + result |= nsIDOMWindowUtils::MODIFIER_SHIFT; + } + if (aModifiers & mozilla::MODIFIER_CONTROL) { + result |= nsIDOMWindowUtils::MODIFIER_CONTROL; + } + if (aModifiers & mozilla::MODIFIER_ALT) { + result |= nsIDOMWindowUtils::MODIFIER_ALT; + } + if (aModifiers & mozilla::MODIFIER_META) { + result |= nsIDOMWindowUtils::MODIFIER_META; + } + if (aModifiers & mozilla::MODIFIER_ALTGRAPH) { + result |= nsIDOMWindowUtils::MODIFIER_ALTGRAPH; + } + if (aModifiers & mozilla::MODIFIER_CAPSLOCK) { + result |= nsIDOMWindowUtils::MODIFIER_CAPSLOCK; + } + if (aModifiers & mozilla::MODIFIER_FN) { + result |= nsIDOMWindowUtils::MODIFIER_FN; + } + if (aModifiers & mozilla::MODIFIER_FNLOCK) { + result |= nsIDOMWindowUtils::MODIFIER_FNLOCK; + } + if (aModifiers & mozilla::MODIFIER_NUMLOCK) { + result |= nsIDOMWindowUtils::MODIFIER_NUMLOCK; + } + if (aModifiers & mozilla::MODIFIER_SCROLLLOCK) { + result |= nsIDOMWindowUtils::MODIFIER_SCROLLLOCK; + } + if (aModifiers & mozilla::MODIFIER_SYMBOL) { + result |= nsIDOMWindowUtils::MODIFIER_SYMBOL; + } + if (aModifiers & mozilla::MODIFIER_SYMBOLLOCK) { + result |= nsIDOMWindowUtils::MODIFIER_SYMBOLLOCK; + } + if (aModifiers & mozilla::MODIFIER_OS) { + result |= nsIDOMWindowUtils::MODIFIER_OS; + } + return result; +} + +} // namespace + +namespace mozilla { +namespace layers { + +APZEventState::APZEventState(nsIWidget* aWidget, + ContentReceivedInputBlockCallback&& aCallback) + : mWidget(nullptr) // initialized in constructor body + , + mActiveElementManager(new ActiveElementManager()), + mContentReceivedInputBlockCallback(std::move(aCallback)), + mPendingTouchPreventedResponse(false), + mPendingTouchPreventedBlockId(0), + mEndTouchIsClick(false), + mFirstTouchCancelled(false), + mTouchEndCancelled(false), + mLastTouchIdentifier(0) { + nsresult rv; + mWidget = do_GetWeakReference(aWidget, &rv); + MOZ_ASSERT(NS_SUCCEEDED(rv), + "APZEventState constructed with a widget that" + " does not support weak references. APZ will NOT work!"); +} + +APZEventState::~APZEventState() = default; + +class DelayedFireSingleTapEvent final : public nsITimerCallback, + public nsINamed { + public: + NS_DECL_ISUPPORTS + + DelayedFireSingleTapEvent(nsWeakPtr aWidget, LayoutDevicePoint& aPoint, + Modifiers aModifiers, int32_t aClickCount, + nsITimer* aTimer, RefPtr& aTouchRollup) + : mWidget(aWidget), + mPoint(aPoint), + mModifiers(aModifiers), + mClickCount(aClickCount) + // Hold the reference count until we are called back. + , + mTimer(aTimer), + mTouchRollup(aTouchRollup) {} + + NS_IMETHOD Notify(nsITimer*) override { + if (nsCOMPtr widget = do_QueryReferent(mWidget)) { + widget::nsAutoRollup rollup(mTouchRollup.get()); + APZCCallbackHelper::FireSingleTapEvent(mPoint, mModifiers, mClickCount, + widget); + } + mTimer = nullptr; + return NS_OK; + } + + NS_IMETHOD + GetName(nsACString& aName) override { + aName.AssignLiteral("DelayedFireSingleTapEvent"); + return NS_OK; + } + + void ClearTimer() { mTimer = nullptr; } + + private: + ~DelayedFireSingleTapEvent() = default; + + nsWeakPtr mWidget; + LayoutDevicePoint mPoint; + Modifiers mModifiers; + int32_t mClickCount; + nsCOMPtr mTimer; + RefPtr mTouchRollup; +}; + +NS_IMPL_ISUPPORTS(DelayedFireSingleTapEvent, nsITimerCallback, nsINamed) + +void APZEventState::ProcessSingleTap(const CSSPoint& aPoint, + const CSSToLayoutDeviceScale& aScale, + Modifiers aModifiers, + int32_t aClickCount) { + APZES_LOG("Handling single tap at %s with %d\n", ToString(aPoint).c_str(), + mTouchEndCancelled); + + RefPtr touchRollup = GetTouchRollup(); + mTouchRollup = nullptr; + + nsCOMPtr widget = GetWidget(); + if (!widget) { + return; + } + + if (mTouchEndCancelled) { + return; + } + + LayoutDevicePoint ldPoint = aPoint * aScale; + + APZES_LOG("Scheduling timer for click event\n"); + nsCOMPtr timer = NS_NewTimer(); + RefPtr callback = new DelayedFireSingleTapEvent( + mWidget, ldPoint, aModifiers, aClickCount, timer, touchRollup); + nsresult rv = timer->InitWithCallback( + callback, StaticPrefs::ui_touch_activation_duration_ms(), + nsITimer::TYPE_ONE_SHOT); + if (NS_FAILED(rv)) { + // Make |callback| not hold the timer, so they will both be destructed when + // we leave the scope of this function. + callback->ClearTimer(); + } +} + +bool APZEventState::FireContextmenuEvents(PresShell* aPresShell, + const CSSPoint& aPoint, + const CSSToLayoutDeviceScale& aScale, + Modifiers aModifiers, + const nsCOMPtr& aWidget) { + // Suppress retargeting for mouse events generated by a long-press + EventRetargetSuppression suppression; + + // Synthesize mousemove event for allowing users to emulate to move mouse + // cursor over the element. As a result, users can open submenu UI which + // is opened when mouse cursor is moved over a link (i.e., it's a case that + // users cannot stay in the page after tapping it). So, this improves + // accessibility in websites which are designed for desktop. + // Note that we don't need to check whether mousemove event is consumed or + // not because Chrome also ignores the result. + APZCCallbackHelper::DispatchSynthesizedMouseEvent( + eMouseMove, 0 /* time */, aPoint * aScale, aModifiers, 0 /* clickCount */, + aWidget); + + // Converting the modifiers to DOM format for the DispatchMouseEvent call + // is the most useless thing ever because nsDOMWindowUtils::SendMouseEvent + // just converts them back to widget format, but that API has many callers, + // including in JS code, so it's not trivial to change. + CSSPoint point = CSSPoint::FromAppUnits( + ViewportUtils::VisualToLayout(CSSPoint::ToAppUnits(aPoint), aPresShell)); + bool eventHandled = APZCCallbackHelper::DispatchMouseEvent( + aPresShell, u"contextmenu"_ns, point, 2, 1, + WidgetModifiersToDOMModifiers(aModifiers), + dom::MouseEvent_Binding::MOZ_SOURCE_TOUCH, + 0 /* Use the default value here. */); + + APZES_LOG("Contextmenu event handled: %d\n", eventHandled); + if (eventHandled) { + // If the contextmenu event was handled then we're showing a contextmenu, + // and so we should remove any activation + mActiveElementManager->ClearActivation(); +#ifndef XP_WIN + } else { + // If the contextmenu wasn't consumed, fire the eMouseLongTap event. + nsEventStatus status = APZCCallbackHelper::DispatchSynthesizedMouseEvent( + eMouseLongTap, /*time*/ 0, aPoint * aScale, aModifiers, + /*clickCount*/ 1, aWidget); + eventHandled = (status == nsEventStatus_eConsumeNoDefault); + APZES_LOG("eMouseLongTap event handled: %d\n", eventHandled); +#endif + } + + return eventHandled; +} + +void APZEventState::ProcessLongTap(PresShell* aPresShell, + const CSSPoint& aPoint, + const CSSToLayoutDeviceScale& aScale, + Modifiers aModifiers, + uint64_t aInputBlockId) { + APZES_LOG("Handling long tap at %s\n", ToString(aPoint).c_str()); + + nsCOMPtr widget = GetWidget(); + if (!widget) { + return; + } + + SendPendingTouchPreventedResponse(false); + +#ifdef XP_WIN + // On Windows, we fire the contextmenu events when the user lifts their + // finger, in keeping with the platform convention. This happens in the + // ProcessLongTapUp function. However, we still fire the eMouseLongTap event + // at this time, because things like text selection or dragging may want + // to know about it. + nsEventStatus status = APZCCallbackHelper::DispatchSynthesizedMouseEvent( + eMouseLongTap, /*time*/ 0, aPoint * aScale, aModifiers, /*clickCount*/ 1, + widget); + + bool eventHandled = (status == nsEventStatus_eConsumeNoDefault); +#else + bool eventHandled = + FireContextmenuEvents(aPresShell, aPoint, aScale, aModifiers, widget); +#endif + mContentReceivedInputBlockCallback(aInputBlockId, eventHandled); + + if (eventHandled) { + // Also send a touchcancel to content, so that listeners that might be + // waiting for a touchend don't trigger. + WidgetTouchEvent cancelTouchEvent(true, eTouchCancel, widget.get()); + cancelTouchEvent.mModifiers = aModifiers; + auto ldPoint = LayoutDeviceIntPoint::Round(aPoint * aScale); + cancelTouchEvent.mTouches.AppendElement(new mozilla::dom::Touch( + mLastTouchIdentifier, ldPoint, LayoutDeviceIntPoint(), 0, 0)); + APZCCallbackHelper::DispatchWidgetEvent(cancelTouchEvent); + } +} + +void APZEventState::ProcessLongTapUp(PresShell* aPresShell, + const CSSPoint& aPoint, + const CSSToLayoutDeviceScale& aScale, + Modifiers aModifiers) { +#ifdef XP_WIN + nsCOMPtr widget = GetWidget(); + if (widget) { + FireContextmenuEvents(aPresShell, aPoint, aScale, aModifiers, widget); + } +#endif +} + +void APZEventState::ProcessTouchEvent( + const WidgetTouchEvent& aEvent, const ScrollableLayerGuid& aGuid, + uint64_t aInputBlockId, nsEventStatus aApzResponse, + nsEventStatus aContentResponse, + nsTArray&& aAllowedTouchBehaviors) { + if (aEvent.mMessage == eTouchStart && aEvent.mTouches.Length() > 0) { + mActiveElementManager->SetTargetElement(aEvent.mTouches[0]->GetTarget()); + mLastTouchIdentifier = aEvent.mTouches[0]->Identifier(); + } + if (aEvent.mMessage == eTouchStart) { + // We get the allowed touch behaviors on a touchstart, but may not actually + // use them until the first touchmove, so we stash them in a member + // variable. + mTouchBlockAllowedBehaviors = std::move(aAllowedTouchBehaviors); + } + + bool isTouchPrevented = aContentResponse == nsEventStatus_eConsumeNoDefault; + bool sentContentResponse = false; + APZES_LOG("Handling event type %d isPrevented=%d\n", aEvent.mMessage, + isTouchPrevented); + switch (aEvent.mMessage) { + case eTouchStart: { + mTouchEndCancelled = false; + mTouchRollup = do_GetWeakReference(widget::nsAutoRollup::GetLastRollup()); + + SendPendingTouchPreventedResponse(false); + // The above call may have sent a message to APZ if we get two + // TOUCH_STARTs in a row and just responded to the first one. + + // We're about to send a response back to APZ, but we should only do it + // for events that went through APZ (which should be all of them). + MOZ_ASSERT(aEvent.mFlags.mHandledByAPZ); + + // If the first touchstart event was preventDefaulted, ensure that any + // subsequent additional touchstart events also get preventDefaulted. This + // ensures that e.g. pinch zooming is prevented even if just the first + // touchstart was prevented by content. + if (mTouchCounter.GetActiveTouchCount() == 0) { + mFirstTouchCancelled = isTouchPrevented; + } else { + if (mFirstTouchCancelled && !isTouchPrevented) { + APZES_LOG( + "Propagating prevent-default from first-touch for block %" PRIu64 + "\n", + aInputBlockId); + } + isTouchPrevented |= mFirstTouchCancelled; + } + + if (isTouchPrevented) { + mContentReceivedInputBlockCallback(aInputBlockId, isTouchPrevented); + sentContentResponse = true; + } else { + APZES_LOG("Event not prevented; pending response for %" PRIu64 " %s\n", + aInputBlockId, ToString(aGuid).c_str()); + mPendingTouchPreventedResponse = true; + mPendingTouchPreventedGuid = aGuid; + mPendingTouchPreventedBlockId = aInputBlockId; + } + break; + } + + case eTouchEnd: + if (isTouchPrevented) { + mTouchEndCancelled = true; + mEndTouchIsClick = false; + } + [[fallthrough]]; + case eTouchCancel: + mActiveElementManager->HandleTouchEndEvent(mEndTouchIsClick); + [[fallthrough]]; + case eTouchMove: { + if (mPendingTouchPreventedResponse) { + MOZ_ASSERT(aGuid == mPendingTouchPreventedGuid); + } + sentContentResponse = SendPendingTouchPreventedResponse(isTouchPrevented); + break; + } + + default: + MOZ_ASSERT_UNREACHABLE("Unknown touch event type"); + break; + } + + mTouchCounter.Update(aEvent); + if (mTouchCounter.GetActiveTouchCount() == 0) { + mFirstTouchCancelled = false; + } + + APZES_LOG("Pointercancel if %d %d %d %d %d\n", sentContentResponse, + !isTouchPrevented, aApzResponse == nsEventStatus_eConsumeDoDefault, + StaticPrefs::dom_w3c_pointer_events_enabled(), + MainThreadAgreesEventsAreConsumableByAPZ()); + if (sentContentResponse && !isTouchPrevented && + aApzResponse == nsEventStatus_eConsumeDoDefault && + StaticPrefs::dom_w3c_pointer_events_enabled() && + MainThreadAgreesEventsAreConsumableByAPZ()) { + WidgetTouchEvent cancelEvent(aEvent); + cancelEvent.mMessage = eTouchPointerCancel; + cancelEvent.mFlags.mCancelable = false; // mMessage != eTouchCancel; + for (uint32_t i = 0; i < cancelEvent.mTouches.Length(); ++i) { + if (mozilla::dom::Touch* touch = cancelEvent.mTouches[i]) { + touch->convertToPointer = true; + } + } + nsEventStatus status; + cancelEvent.mWidget->DispatchEvent(&cancelEvent, status); + } +} + +bool APZEventState::MainThreadAgreesEventsAreConsumableByAPZ() const { + // APZ errs on the side of saying it can consume touch events to perform + // default user-agent behaviours. In particular it may say this if it hasn't + // received accurate touch-action information. Here we double-check using + // accurate touch-action information. This code is kinda-sorta the main + // thread equivalent of AsyncPanZoomController::ArePointerEventsConsumable(). + + switch (mTouchBlockAllowedBehaviors.Length()) { + case 0: + // If we don't have any touch-action (e.g. because it is disabled) then + // APZ has no restrictions. + return true; + + case 1: { + // If there's one touch point in this touch block, then check the pan-x + // and pan-y flags. If neither is allowed, then we disagree with APZ and + // say that it can't do anything with this touch block. Note that it would + // be even better if we could check the allowed scroll directions of the + // scrollframe at this point and refine this further. + TouchBehaviorFlags flags = mTouchBlockAllowedBehaviors[0]; + return (flags & AllowedTouchBehavior::HORIZONTAL_PAN) || + (flags & AllowedTouchBehavior::VERTICAL_PAN); + } + + case 2: { + // If there's two touch points in this touch block, check that they both + // allow zooming. + for (const auto& allowed : mTouchBlockAllowedBehaviors) { + if (!(allowed & AllowedTouchBehavior::PINCH_ZOOM)) { + return false; + } + } + return true; + } + + default: + // More than two touch points? APZ shouldn't be doing anything with this, + // so APZ shouldn't be consuming them. + return false; + } +} + +void APZEventState::ProcessWheelEvent(const WidgetWheelEvent& aEvent, + uint64_t aInputBlockId) { + // If this event starts a swipe, indicate that it shouldn't result in a + // scroll by setting defaultPrevented to true. + bool defaultPrevented = aEvent.DefaultPrevented() || aEvent.TriggersSwipe(); + mContentReceivedInputBlockCallback(aInputBlockId, defaultPrevented); +} + +void APZEventState::ProcessMouseEvent(const WidgetMouseEvent& aEvent, + uint64_t aInputBlockId) { + bool defaultPrevented = false; + mContentReceivedInputBlockCallback(aInputBlockId, defaultPrevented); +} + +void APZEventState::ProcessAPZStateChange(ViewID aViewId, + APZStateChange aChange, int aArg) { + switch (aChange) { + case APZStateChange::eTransformBegin: { + nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aViewId); + if (sf) { + sf->SetTransformingByAPZ(true); + } + nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(sf); + if (scrollbarMediator) { + scrollbarMediator->ScrollbarActivityStarted(); + } + + nsIContent* content = nsLayoutUtils::FindContentFor(aViewId); + dom::Document* doc = content ? content->GetComposedDoc() : nullptr; + nsCOMPtr docshell(doc ? doc->GetDocShell() : nullptr); + if (docshell && sf) { + nsDocShell* nsdocshell = static_cast(docshell.get()); + nsdocshell->NotifyAsyncPanZoomStarted(); + } + break; + } + case APZStateChange::eTransformEnd: { + nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aViewId); + if (sf) { + sf->SetTransformingByAPZ(false); + } + nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(sf); + if (scrollbarMediator) { + scrollbarMediator->ScrollbarActivityStopped(); + } + + nsIContent* content = nsLayoutUtils::FindContentFor(aViewId); + dom::Document* doc = content ? content->GetComposedDoc() : nullptr; + nsCOMPtr docshell(doc ? doc->GetDocShell() : nullptr); + if (docshell && sf) { + nsDocShell* nsdocshell = static_cast(docshell.get()); + nsdocshell->NotifyAsyncPanZoomStopped(); + } + break; + } + case APZStateChange::eStartTouch: { + mActiveElementManager->HandleTouchStart(aArg); + break; + } + case APZStateChange::eStartPanning: { + // The user started to pan, so we don't want anything to be :active. + mActiveElementManager->ClearActivation(); + break; + } + case APZStateChange::eEndTouch: { + mEndTouchIsClick = aArg; + mActiveElementManager->HandleTouchEnd(); + break; + } + } +} + +bool APZEventState::SendPendingTouchPreventedResponse(bool aPreventDefault) { + if (mPendingTouchPreventedResponse) { + APZES_LOG("Sending response %d for pending guid: %s\n", aPreventDefault, + ToString(mPendingTouchPreventedGuid).c_str()); + mContentReceivedInputBlockCallback(mPendingTouchPreventedBlockId, + aPreventDefault); + mPendingTouchPreventedResponse = false; + return true; + } + return false; +} + +already_AddRefed APZEventState::GetWidget() const { + nsCOMPtr result = do_QueryReferent(mWidget); + return result.forget(); +} + +already_AddRefed APZEventState::GetTouchRollup() const { + nsCOMPtr result = do_QueryReferent(mTouchRollup); + return result.forget(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/util/APZEventState.h b/gfx/layers/apz/util/APZEventState.h new file mode 100644 index 0000000000..60020f0f9a --- /dev/null +++ b/gfx/layers/apz/util/APZEventState.h @@ -0,0 +1,124 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_APZEventState_h +#define mozilla_layers_APZEventState_h + +#include + +#include "Units.h" +#include "mozilla/EventForwards.h" +#include "mozilla/layers/GeckoContentControllerTypes.h" // for APZStateChange +#include "mozilla/layers/ScrollableLayerGuid.h" // for ScrollableLayerGuid +#include "mozilla/layers/TouchCounter.h" // for TouchCounter +#include "mozilla/RefPtr.h" +#include "nsCOMPtr.h" +#include "nsISupportsImpl.h" // for NS_INLINE_DECL_REFCOUNTING +#include "nsIWeakReferenceUtils.h" // for nsWeakPtr + +#include + +template +class nsCOMPtr; +class nsIContent; +class nsIWidget; + +namespace mozilla { + +class PresShell; + +namespace layers { + +class ActiveElementManager; + +typedef std::function + ContentReceivedInputBlockCallback; + +/** + * A content-side component that keeps track of state for handling APZ + * gestures and sending APZ notifications. + */ +class APZEventState final { + typedef GeckoContentController_APZStateChange APZStateChange; + typedef ScrollableLayerGuid::ViewID ViewID; + + public: + APZEventState(nsIWidget* aWidget, + ContentReceivedInputBlockCallback&& aCallback); + + NS_INLINE_DECL_REFCOUNTING(APZEventState); + + void ProcessSingleTap(const CSSPoint& aPoint, + const CSSToLayoutDeviceScale& aScale, + Modifiers aModifiers, int32_t aClickCount); + MOZ_CAN_RUN_SCRIPT + void ProcessLongTap(PresShell* aPresShell, const CSSPoint& aPoint, + const CSSToLayoutDeviceScale& aScale, + Modifiers aModifiers, uint64_t aInputBlockId); + MOZ_CAN_RUN_SCRIPT + void ProcessLongTapUp(PresShell* aPresShell, const CSSPoint& aPoint, + const CSSToLayoutDeviceScale& aScale, + Modifiers aModifiers); + void ProcessTouchEvent(const WidgetTouchEvent& aEvent, + const ScrollableLayerGuid& aGuid, + uint64_t aInputBlockId, nsEventStatus aApzResponse, + nsEventStatus aContentResponse, + nsTArray&& aAllowedTouchBehaviors); + void ProcessWheelEvent(const WidgetWheelEvent& aEvent, + uint64_t aInputBlockId); + void ProcessMouseEvent(const WidgetMouseEvent& aEvent, + uint64_t aInputBlockId); + void ProcessAPZStateChange(ViewID aViewId, APZStateChange aChange, int aArg); + void ProcessClusterHit(); + + private: + ~APZEventState(); + bool SendPendingTouchPreventedResponse(bool aPreventDefault); + MOZ_CAN_RUN_SCRIPT + bool FireContextmenuEvents(PresShell* aPresShell, const CSSPoint& aPoint, + const CSSToLayoutDeviceScale& aScale, + Modifiers aModifiers, + const nsCOMPtr& aWidget); + already_AddRefed GetWidget() const; + already_AddRefed GetTouchRollup() const; + bool MainThreadAgreesEventsAreConsumableByAPZ() const; + + private: + nsWeakPtr mWidget; + RefPtr mActiveElementManager; + ContentReceivedInputBlockCallback mContentReceivedInputBlockCallback; + TouchCounter mTouchCounter; + bool mPendingTouchPreventedResponse; + ScrollableLayerGuid mPendingTouchPreventedGuid; + uint64_t mPendingTouchPreventedBlockId; + bool mEndTouchIsClick; + bool mFirstTouchCancelled; + bool mTouchEndCancelled; + int32_t mLastTouchIdentifier; + nsTArray mTouchBlockAllowedBehaviors; + + // Because touch-triggered mouse events (e.g. mouse events from a tap + // gesture) happen asynchronously from the touch events themselves, we + // need to stash and replicate some of the state from the touch events + // to the mouse events. One piece of state is the rollup content, which + // is the content for which a popup window was recently closed. If we + // don't replicate this state properly during the mouse events, the + // synthetic click might reopen a popup window that was just closed by + // the touch event, which is undesirable. See also documentation in + // nsAutoRollup.h + // Note that in cases where we get multiple touch blocks interleaved with + // their single-tap event notifications, mTouchRollup may hold an incorrect + // value. This is kind of an edge case, and falls in the same category of + // problems as bug 1227241. I intend that fixing that bug will also take + // care of this potential problem. + nsWeakPtr mTouchRollup; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* mozilla_layers_APZEventState_h */ diff --git a/gfx/layers/apz/util/APZThreadUtils.cpp b/gfx/layers/apz/util/APZThreadUtils.cpp new file mode 100644 index 0000000000..63db976834 --- /dev/null +++ b/gfx/layers/apz/util/APZThreadUtils.cpp @@ -0,0 +1,118 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "APZThreadUtils.h" + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/StaticMutex.h" + +#include "nsISerialEventTarget.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" + +namespace mozilla { +namespace layers { + +static bool sThreadAssertionsEnabled = true; +static StaticRefPtr sControllerThread; +static StaticMutex sControllerThreadMutex; + +/*static*/ +void APZThreadUtils::SetThreadAssertionsEnabled(bool aEnabled) { + StaticMutexAutoLock lock(sControllerThreadMutex); + sThreadAssertionsEnabled = aEnabled; +} + +/*static*/ +bool APZThreadUtils::GetThreadAssertionsEnabled() { + StaticMutexAutoLock lock(sControllerThreadMutex); + return sThreadAssertionsEnabled; +} + +/*static*/ +void APZThreadUtils::SetControllerThread(nsISerialEventTarget* aThread) { + // We must either be setting the initial controller thread, or removing it, + // or re-using an existing controller thread. + StaticMutexAutoLock lock(sControllerThreadMutex); + MOZ_ASSERT(!sControllerThread || !aThread || sControllerThread == aThread); + if (aThread != sControllerThread) { + // This can only happen once, on startup. + sControllerThread = aThread; + ClearOnShutdown(&sControllerThread); + } +} + +/*static*/ +void APZThreadUtils::AssertOnControllerThread() { +#if DEBUG + if (!GetThreadAssertionsEnabled()) { + return; + } + StaticMutexAutoLock lock(sControllerThreadMutex); + MOZ_ASSERT(sControllerThread && sControllerThread->IsOnCurrentThread()); +#endif +} + +/*static*/ +void APZThreadUtils::RunOnControllerThread(RefPtr&& aTask) { + RefPtr thread; + { + StaticMutexAutoLock lock(sControllerThreadMutex); + thread = sControllerThread; + } + RefPtr task = std::move(aTask); + + if (!thread) { + // Could happen on startup or if Shutdown() got called. + NS_WARNING("Dropping task posted to controller thread"); + return; + } + + if (thread->IsOnCurrentThread()) { + task->Run(); + } else { + thread->Dispatch(task.forget()); + } +} + +/*static*/ +bool APZThreadUtils::IsControllerThread() { + StaticMutexAutoLock lock(sControllerThreadMutex); + return sControllerThread && sControllerThread->IsOnCurrentThread(); +} + +/*static*/ +bool APZThreadUtils::IsControllerThreadAlive() { + StaticMutexAutoLock lock(sControllerThreadMutex); + return !!sControllerThread; +} + +/*static*/ +void APZThreadUtils::DelayedDispatch(already_AddRefed aRunnable, + int aDelayMs) { + MOZ_ASSERT(!XRE_IsContentProcess(), + "ContentProcessController should only be used remotely."); + RefPtr thread; + { + StaticMutexAutoLock lock(sControllerThreadMutex); + thread = sControllerThread; + } + if (!thread) { + // Could happen on startup + NS_WARNING("Dropping task posted to controller thread"); + return; + } + if (aDelayMs) { + thread->DelayedDispatch(std::move(aRunnable), aDelayMs); + } else { + thread->Dispatch(std::move(aRunnable)); + } +} + +NS_IMPL_ISUPPORTS(GenericNamedTimerCallbackBase, nsITimerCallback, nsINamed) + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/util/APZThreadUtils.h b/gfx/layers/apz/util/APZThreadUtils.h new file mode 100644 index 0000000000..fb03ac10da --- /dev/null +++ b/gfx/layers/apz/util/APZThreadUtils.h @@ -0,0 +1,117 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_APZThreadUtils_h +#define mozilla_layers_APZThreadUtils_h + +#include "nsINamed.h" +#include "nsITimer.h" +#include "nsString.h" + +class nsISerialEventTarget; + +namespace mozilla { + +class Runnable; + +namespace layers { + +class APZThreadUtils { + public: + /** + * In the gtest environment everything runs on one thread, so we + * shouldn't assert that we're on a particular thread. This enables + * that behaviour. + */ + static void SetThreadAssertionsEnabled(bool aEnabled); + static bool GetThreadAssertionsEnabled(); + + /** + * Set the controller thread. + */ + static void SetControllerThread(nsISerialEventTarget* aThread); + + /** + * This can be used to assert that the current thread is the + * controller/UI thread (on which input events are received). + * This does nothing if thread assertions are disabled. + */ + static void AssertOnControllerThread(); + + /** + * Run the given task on the APZ "controller thread" for this platform. If + * this function is called from the controller thread itself then the task is + * run immediately without getting queued. + */ + static void RunOnControllerThread(RefPtr&& aTask); + + /** + * Returns true if currently on APZ "controller thread". + */ + static bool IsControllerThread(); + + /** + * Returns true if the controller thread is still alive. + */ + static bool IsControllerThreadAlive(); + + /** + * Schedules a runnable to run on the controller thread at some time + * in the future. + */ + static void DelayedDispatch(already_AddRefed aRunnable, + int aDelayMs); +}; + +// A base class for GenericNamedTimerCallback. +// This is necessary because NS_IMPL_ISUPPORTS doesn't work for a class +// template. +class GenericNamedTimerCallbackBase : public nsITimerCallback, public nsINamed { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + protected: + virtual ~GenericNamedTimerCallbackBase() = default; +}; + +// An nsITimerCallback implementation with nsINamed that can be used with any +// function object that's callable with no arguments. +template +class GenericNamedTimerCallback final : public GenericNamedTimerCallbackBase { + public: + GenericNamedTimerCallback(const Function& aFunction, const char* aName) + : mFunction(aFunction), mName(aName) {} + + NS_IMETHOD Notify(nsITimer*) override { + mFunction(); + return NS_OK; + } + + NS_IMETHOD GetName(nsACString& aName) override { + aName = mName; + return NS_OK; + } + + private: + Function mFunction; + nsCString mName; +}; + +// Convenience function for constructing a GenericNamedTimerCallback. +// Returns a raw pointer, suitable for passing directly as an argument to +// nsITimer::InitWithCallback(). The intention is to enable the following +// terse inline usage: +// timer->InitWithCallback(NewNamedTimerCallback([](){ ... }, name), delay); +template +GenericNamedTimerCallback* NewNamedTimerCallback( + const Function& aFunction, const char* aName) { + return new GenericNamedTimerCallback(aFunction, aName); +} + +} // namespace layers +} // namespace mozilla + +#endif /* mozilla_layers_APZThreadUtils_h */ diff --git a/gfx/layers/apz/util/ActiveElementManager.cpp b/gfx/layers/apz/util/ActiveElementManager.cpp new file mode 100644 index 0000000000..ed485c3a1d --- /dev/null +++ b/gfx/layers/apz/util/ActiveElementManager.cpp @@ -0,0 +1,178 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ActiveElementManager.h" +#include "mozilla/EventStateManager.h" +#include "mozilla/EventStates.h" +#include "mozilla/PresShell.h" +#include "mozilla/StaticPrefs_ui.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/Document.h" + +static mozilla::LazyLogModule sApzAemLog("apz.activeelement"); +#define AEM_LOG(...) MOZ_LOG(sApzAemLog, LogLevel::Debug, (__VA_ARGS__)) + +namespace mozilla { +namespace layers { + +ActiveElementManager::ActiveElementManager() + : mCanBePan(false), mCanBePanSet(false), mSetActiveTask(nullptr) {} + +ActiveElementManager::~ActiveElementManager() = default; + +void ActiveElementManager::SetTargetElement(dom::EventTarget* aTarget) { + if (mTarget) { + // Multiple fingers on screen (since HandleTouchEnd clears mTarget). + AEM_LOG("Multiple fingers on-screen, clearing target element\n"); + CancelTask(); + ResetActive(); + ResetTouchBlockState(); + return; + } + + mTarget = do_QueryInterface(aTarget); + AEM_LOG("Setting target element to %p\n", mTarget.get()); + TriggerElementActivation(); +} + +void ActiveElementManager::HandleTouchStart(bool aCanBePan) { + AEM_LOG("Touch start, aCanBePan: %d\n", aCanBePan); + if (mCanBePanSet) { + // Multiple fingers on screen (since HandleTouchEnd clears mCanBePanSet). + AEM_LOG("Multiple fingers on-screen, clearing touch block state\n"); + CancelTask(); + ResetActive(); + ResetTouchBlockState(); + return; + } + + mCanBePan = aCanBePan; + mCanBePanSet = true; + TriggerElementActivation(); +} + +void ActiveElementManager::TriggerElementActivation() { + // Both HandleTouchStart() and SetTargetElement() call this. They can be + // called in either order. One will set mCanBePanSet, and the other, mTarget. + // We want to actually trigger the activation once both are set. + if (!(mTarget && mCanBePanSet)) { + return; + } + + // If the touch cannot be a pan, make mTarget :active right away. + // Otherwise, wait a bit to see if the user will pan or not. + if (!mCanBePan) { + SetActive(mTarget); + } else { + CancelTask(); // this is only needed because of bug 1169802. Fixing that + // bug properly should make this unnecessary. + MOZ_ASSERT(mSetActiveTask == nullptr); + + RefPtr task = + NewCancelableRunnableMethod>( + "layers::ActiveElementManager::SetActiveTask", this, + &ActiveElementManager::SetActiveTask, mTarget); + mSetActiveTask = task; + NS_GetCurrentThread()->DelayedDispatch( + task.forget(), StaticPrefs::ui_touch_activation_delay_ms()); + AEM_LOG("Scheduling mSetActiveTask %p\n", mSetActiveTask.get()); + } +} + +void ActiveElementManager::ClearActivation() { + AEM_LOG("Clearing element activation\n"); + CancelTask(); + ResetActive(); +} + +void ActiveElementManager::HandleTouchEndEvent(bool aWasClick) { + AEM_LOG("Touch end event, aWasClick: %d\n", aWasClick); + + // If the touch was a click, make mTarget :active right away. + // nsEventStateManager will reset the active element when processing + // the mouse-down event generated by the click. + CancelTask(); + if (aWasClick) { + // Scrollbar thumbs use a different mechanism for their active + // highlight (the "active" attribute), so don't set the active state + // on them because nothing will clear it. + if (!(mTarget && mTarget->IsXULElement(nsGkAtoms::thumb))) { + SetActive(mTarget); + } + } else { + // We might reach here if mCanBePan was false on touch-start and + // so we set the element active right away. Now it turns out the + // action was not a click so we need to reset the active element. + ResetActive(); + } + + ResetTouchBlockState(); +} + +void ActiveElementManager::HandleTouchEnd() { + AEM_LOG("Touch end, clearing pan state\n"); + mCanBePanSet = false; +} + +static nsPresContext* GetPresContextFor(nsIContent* aContent) { + if (!aContent) { + return nullptr; + } + PresShell* presShell = aContent->OwnerDoc()->GetPresShell(); + if (!presShell) { + return nullptr; + } + return presShell->GetPresContext(); +} + +void ActiveElementManager::SetActive(dom::Element* aTarget) { + AEM_LOG("Setting active %p\n", aTarget); + + if (nsPresContext* pc = GetPresContextFor(aTarget)) { + pc->EventStateManager()->SetContentState(aTarget, NS_EVENT_STATE_ACTIVE); + } +} + +void ActiveElementManager::ResetActive() { + AEM_LOG("Resetting active from %p\n", mTarget.get()); + + // Clear the :active flag from mTarget by setting it on the document root. + if (mTarget) { + dom::Element* root = mTarget->OwnerDoc()->GetDocumentElement(); + if (root) { + AEM_LOG("Found root %p, making active\n", root); + SetActive(root); + } + } +} + +void ActiveElementManager::ResetTouchBlockState() { + mTarget = nullptr; + mCanBePanSet = false; +} + +void ActiveElementManager::SetActiveTask( + const nsCOMPtr& aTarget) { + AEM_LOG("mSetActiveTask %p running\n", mSetActiveTask.get()); + + // This gets called from mSetActiveTask's Run() method. The message loop + // deletes the task right after running it, so we need to null out + // mSetActiveTask to make sure we're not left with a dangling pointer. + mSetActiveTask = nullptr; + SetActive(aTarget); +} + +void ActiveElementManager::CancelTask() { + AEM_LOG("Cancelling task %p\n", mSetActiveTask.get()); + + if (mSetActiveTask) { + mSetActiveTask->Cancel(); + mSetActiveTask = nullptr; + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/util/ActiveElementManager.h b/gfx/layers/apz/util/ActiveElementManager.h new file mode 100644 index 0000000000..b783659962 --- /dev/null +++ b/gfx/layers/apz/util/ActiveElementManager.h @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_ActiveElementManager_h +#define mozilla_layers_ActiveElementManager_h + +#include "nsCOMPtr.h" +#include "nsISupportsImpl.h" + +namespace mozilla { + +class CancelableRunnable; + +namespace dom { +class Element; +class EventTarget; +} // namespace dom + +namespace layers { + +/** + * Manages setting and clearing the ':active' CSS pseudostate in the presence + * of touch input. + */ +class ActiveElementManager final { + ~ActiveElementManager(); + + public: + NS_INLINE_DECL_REFCOUNTING(ActiveElementManager) + + ActiveElementManager(); + + /** + * Specify the target of a touch. Typically this should be called right + * after HandleTouchStart(), but in cases where the APZ needs to wait for + * a content response the HandleTouchStart() may be delayed, in which case + * this function can be called first. + * |aTarget| may be nullptr. + */ + void SetTargetElement(dom::EventTarget* aTarget); + /** + * Handle a touch-start state notification from APZ. This notification + * may be delayed until after touch listeners have responded to the APZ. + * @param aCanBePan whether the touch can be a pan + */ + void HandleTouchStart(bool aCanBePan); + /** + * Clear the active element. + */ + void ClearActivation(); + /** + * Handle a touch-end or touch-cancel event. + * @param aWasClick whether the touch was a click + */ + void HandleTouchEndEvent(bool aWasClick); + /** + * Handle a touch-end state notification from APZ. This notification may be + * delayed until after touch listeners have responded to the APZ. + */ + void HandleTouchEnd(); + + private: + /** + * The target of the first touch point in the current touch block. + */ + nsCOMPtr mTarget; + /** + * Whether the current touch block can be a pan. Set in HandleTouchStart(). + */ + bool mCanBePan; + /** + * Whether mCanBePan has been set for the current touch block. + * We need to keep track of this to allow HandleTouchStart() and + * SetTargetElement() to be called in either order. + */ + bool mCanBePanSet; + /** + * A task for calling SetActive() after a timeout. + */ + RefPtr mSetActiveTask; + + // Helpers + void TriggerElementActivation(); + void SetActive(dom::Element* aTarget); + void ResetActive(); + void ResetTouchBlockState(); + void SetActiveTask(const nsCOMPtr& aTarget); + void CancelTask(); +}; + +} // namespace layers +} // namespace mozilla + +#endif /* mozilla_layers_ActiveElementManager_h */ diff --git a/gfx/layers/apz/util/CheckerboardReportService.cpp b/gfx/layers/apz/util/CheckerboardReportService.cpp new file mode 100644 index 0000000000..24668deecf --- /dev/null +++ b/gfx/layers/apz/util/CheckerboardReportService.cpp @@ -0,0 +1,219 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "CheckerboardReportService.h" + +#include "jsapi.h" // for JS_Now +#include "MainThreadUtils.h" // for NS_IsMainThread +#include "mozilla/Assertions.h" // for MOZ_ASSERT +#include "mozilla/ClearOnShutdown.h" // for ClearOnShutdown +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPrefs_apz.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/CheckerboardReportServiceBinding.h" // for dom::CheckerboardReports +#include "mozilla/gfx/GPUParent.h" +#include "mozilla/gfx/GPUProcessManager.h" +#include "nsContentUtils.h" // for nsContentUtils +#include "nsIObserverService.h" +#include "nsXULAppAPI.h" + +namespace mozilla { +namespace layers { + +/*static*/ +StaticRefPtr CheckerboardEventStorage::sInstance; + +/*static*/ +already_AddRefed +CheckerboardEventStorage::GetInstance() { + // The instance in the parent process does all the work, so if this is getting + // called in the child process something is likely wrong. + MOZ_ASSERT(XRE_IsParentProcess()); + + MOZ_ASSERT(NS_IsMainThread()); + if (!sInstance) { + sInstance = new CheckerboardEventStorage(); + ClearOnShutdown(&sInstance); + } + RefPtr instance = sInstance.get(); + return instance.forget(); +} + +void CheckerboardEventStorage::Report(uint32_t aSeverity, + const std::string& aLog) { + if (!NS_IsMainThread()) { + RefPtr task = NS_NewRunnableFunction( + "layers::CheckerboardEventStorage::Report", + [aSeverity, aLog]() -> void { + CheckerboardEventStorage::Report(aSeverity, aLog); + }); + NS_DispatchToMainThread(task.forget()); + return; + } + + if (XRE_IsGPUProcess()) { + if (gfx::GPUParent* gpu = gfx::GPUParent::GetSingleton()) { + nsCString log(aLog.c_str()); + Unused << gpu->SendReportCheckerboard(aSeverity, log); + } + return; + } + + RefPtr storage = GetInstance(); + storage->ReportCheckerboard(aSeverity, aLog); +} + +void CheckerboardEventStorage::ReportCheckerboard(uint32_t aSeverity, + const std::string& aLog) { + MOZ_ASSERT(NS_IsMainThread()); + + if (aSeverity == 0) { + // This code assumes all checkerboard reports have a nonzero severity. + return; + } + + CheckerboardReport severe(aSeverity, JS_Now(), aLog); + CheckerboardReport recent; + + // First look in the "severe" reports to see if the new one belongs in that + // list. + for (int i = 0; i < SEVERITY_MAX_INDEX; i++) { + if (mCheckerboardReports[i].mSeverity >= severe.mSeverity) { + continue; + } + // The new one deserves to be in the "severe" list. Take the one getting + // bumped off the list, and put it in |recent| for possible insertion into + // the recents list. + recent = mCheckerboardReports[SEVERITY_MAX_INDEX - 1]; + + // Shuffle the severe list down, insert the new one. + for (int j = SEVERITY_MAX_INDEX - 1; j > i; j--) { + mCheckerboardReports[j] = mCheckerboardReports[j - 1]; + } + mCheckerboardReports[i] = severe; + severe.mSeverity = 0; // mark |severe| as inserted + break; + } + + // If |severe.mSeverity| is nonzero, the incoming report didn't get inserted + // into the severe list; put it into |recent| for insertion into the recent + // list. + if (severe.mSeverity) { + MOZ_ASSERT(recent.mSeverity == 0, "recent should be empty here"); + recent = severe; + } // else |recent| may hold a report that got knocked out of the severe list. + + if (recent.mSeverity == 0) { + // Nothing to be inserted into the recent list. + return; + } + + // If it wasn't in the "severe" list, add it to the "recent" list. + for (int i = SEVERITY_MAX_INDEX; i < RECENT_MAX_INDEX; i++) { + if (mCheckerboardReports[i].mTimestamp >= recent.mTimestamp) { + continue; + } + // |recent| needs to be inserted at |i|. Shuffle the remaining ones down + // and insert it. + for (int j = RECENT_MAX_INDEX - 1; j > i; j--) { + mCheckerboardReports[j] = mCheckerboardReports[j - 1]; + } + mCheckerboardReports[i] = recent; + break; + } +} + +void CheckerboardEventStorage::GetReports( + nsTArray& aOutReports) { + MOZ_ASSERT(NS_IsMainThread()); + + for (int i = 0; i < RECENT_MAX_INDEX; i++) { + CheckerboardReport& r = mCheckerboardReports[i]; + if (r.mSeverity == 0) { + continue; + } + dom::CheckerboardReport report; + report.mSeverity.Construct() = r.mSeverity; + report.mTimestamp.Construct() = r.mTimestamp / 1000; // micros to millis + report.mLog.Construct() = + NS_ConvertUTF8toUTF16(r.mLog.c_str(), r.mLog.size()); + report.mReason.Construct() = (i < SEVERITY_MAX_INDEX) + ? dom::CheckerboardReason::Severe + : dom::CheckerboardReason::Recent; + aOutReports.AppendElement(report); + } +} + +} // namespace layers + +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CheckerboardReportService, mParent) +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CheckerboardReportService, AddRef) +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CheckerboardReportService, Release) + +/*static*/ +bool CheckerboardReportService::IsEnabled(JSContext* aCtx, JSObject* aGlobal) { + // Only allow this in the parent process + if (!XRE_IsParentProcess()) { + return false; + } + // Allow privileged code or about:checkerboard (unprivileged) to access this. + return nsContentUtils::IsSystemCaller(aCtx) || + nsContentUtils::IsSpecificAboutPage(aGlobal, "about:checkerboard"); +} + +/*static*/ +already_AddRefed +CheckerboardReportService::Constructor(const dom::GlobalObject& aGlobal) { + RefPtr ces = + new CheckerboardReportService(aGlobal.GetAsSupports()); + return ces.forget(); +} + +CheckerboardReportService::CheckerboardReportService(nsISupports* aParent) + : mParent(aParent) {} + +JSObject* CheckerboardReportService::WrapObject( + JSContext* aCtx, JS::Handle aGivenProto) { + return CheckerboardReportService_Binding::Wrap(aCtx, this, aGivenProto); +} + +nsISupports* CheckerboardReportService::GetParentObject() { return mParent; } + +void CheckerboardReportService::GetReports( + nsTArray& aOutReports) { + RefPtr instance = + mozilla::layers::CheckerboardEventStorage::GetInstance(); + MOZ_ASSERT(instance); + instance->GetReports(aOutReports); +} + +bool CheckerboardReportService::IsRecordingEnabled() const { + return StaticPrefs::apz_record_checkerboarding(); +} + +void CheckerboardReportService::SetRecordingEnabled(bool aEnabled) { + Preferences::SetBool("apz.record_checkerboarding", aEnabled); +} + +void CheckerboardReportService::FlushActiveReports() { + MOZ_ASSERT(XRE_IsParentProcess()); + gfx::GPUProcessManager* gpu = gfx::GPUProcessManager::Get(); + if (gpu && gpu->NotifyGpuObservers("APZ:FlushActiveCheckerboard")) { + return; + } + + nsCOMPtr obsSvc = mozilla::services::GetObserverService(); + MOZ_ASSERT(obsSvc); + if (obsSvc) { + obsSvc->NotifyObservers(nullptr, "APZ:FlushActiveCheckerboard", nullptr); + } +} + +} // namespace dom +} // namespace mozilla diff --git a/gfx/layers/apz/util/CheckerboardReportService.h b/gfx/layers/apz/util/CheckerboardReportService.h new file mode 100644 index 0000000000..3062255f95 --- /dev/null +++ b/gfx/layers/apz/util/CheckerboardReportService.h @@ -0,0 +1,138 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_CheckerboardReportService_h +#define mozilla_dom_CheckerboardReportService_h + +#include + +#include "js/TypeDecls.h" // for JSContext, JSObject +#include "mozilla/StaticPtr.h" // for StaticRefPtr +#include "nsCOMPtr.h" // for nsCOMPtr +#include "nsISupports.h" // for NS_INLINE_DECL_REFCOUNTING +#include "nsTArrayForwardDeclare.h" // for nsTArray +#include "nsWrapperCache.h" // for nsWrapperCache + +namespace mozilla { + +namespace dom { +struct CheckerboardReport; +} + +namespace layers { + +// CheckerboardEventStorage is a singleton that stores info on checkerboard +// events, so that they can be accessed from about:checkerboard and visualized. +// Note that this class is NOT threadsafe, and all methods must be called on +// the main thread. +class CheckerboardEventStorage { + NS_INLINE_DECL_REFCOUNTING(CheckerboardEventStorage) + + public: + /** + * Get the singleton instance. + */ + static already_AddRefed GetInstance(); + + /** + * Get the stored checkerboard reports. + */ + void GetReports(nsTArray& aOutReports); + + /** + * Save a checkerboard event log, optionally dropping older ones that were + * less severe or less recent. Zero-severity reports may be ignored entirely. + */ + static void Report(uint32_t aSeverity, const std::string& aLog); + + private: + /* Stuff for refcounted singleton */ + CheckerboardEventStorage() = default; + virtual ~CheckerboardEventStorage() = default; + + static StaticRefPtr sInstance; + + void ReportCheckerboard(uint32_t aSeverity, const std::string& aLog); + + private: + /** + * Struct that this class uses internally to store a checkerboard report. + */ + struct CheckerboardReport { + uint32_t mSeverity; // if 0, this report is empty + int64_t mTimestamp; // microseconds since epoch, as from JS_Now() + std::string mLog; + + CheckerboardReport() : mSeverity(0), mTimestamp(0) {} + + CheckerboardReport(uint32_t aSeverity, int64_t aTimestamp, + const std::string& aLog) + : mSeverity(aSeverity), mTimestamp(aTimestamp), mLog(aLog) {} + }; + + // The first 5 (indices 0-4) are the most severe ones in decreasing order + // of severity; the next 5 (indices 5-9) are the most recent ones that are + // not already in the "severe" list. + static const int SEVERITY_MAX_INDEX = 5; + static const int RECENT_MAX_INDEX = 10; + CheckerboardReport mCheckerboardReports[RECENT_MAX_INDEX]; +}; + +} // namespace layers + +namespace dom { + +class GlobalObject; + +/** + * CheckerboardReportService is a wrapper object that allows access to the + * stuff in CheckerboardEventStorage (above). We need this wrapper for proper + * garbage/cycle collection, since this can be accessed from JS. + */ +class CheckerboardReportService : public nsWrapperCache { + public: + /** + * Check if the given page is allowed to access this object via the WebIDL + * bindings. It only returns true if the page is about:checkerboard. + */ + static bool IsEnabled(JSContext* aCtx, JSObject* aGlobal); + + /* + * Other standard WebIDL binding glue. + */ + + static already_AddRefed Constructor( + const dom::GlobalObject& aGlobal); + + explicit CheckerboardReportService(nsISupports* aSupports); + + JSObject* WrapObject(JSContext* aCtx, + JS::Handle aGivenProto) override; + + nsISupports* GetParentObject(); + + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(CheckerboardReportService) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(CheckerboardReportService) + + public: + /* + * The methods exposed via the webidl. + */ + void GetReports(nsTArray& aOutReports); + bool IsRecordingEnabled() const; + void SetRecordingEnabled(bool aEnabled); + void FlushActiveReports(); + + private: + virtual ~CheckerboardReportService() = default; + + nsCOMPtr mParent; +}; + +} // namespace dom +} // namespace mozilla + +#endif /* mozilla_layers_CheckerboardReportService_h */ diff --git a/gfx/layers/apz/util/ChromeProcessController.cpp b/gfx/layers/apz/util/ChromeProcessController.cpp new file mode 100644 index 0000000000..0f907f8049 --- /dev/null +++ b/gfx/layers/apz/util/ChromeProcessController.cpp @@ -0,0 +1,335 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ChromeProcessController.h" + +#include "MainThreadUtils.h" // for NS_IsMainThread() +#include "base/task.h" +#include "mozilla/PresShell.h" +#include "mozilla/dom/Element.h" +#include "mozilla/layers/CompositorBridgeParent.h" +#include "mozilla/layers/APZCCallbackHelper.h" +#include "mozilla/layers/APZEventState.h" +#include "mozilla/layers/APZThreadUtils.h" +#include "mozilla/layers/IAPZCTreeManager.h" +#include "mozilla/layers/InputAPZContext.h" +#include "mozilla/layers/DoubleTapToZoom.h" +#include "mozilla/layers/RepaintRequest.h" +#include "mozilla/dom/Document.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsLayoutUtils.h" +#include "nsView.h" + +static mozilla::LazyLogModule sApzChromeLog("apz.cc.chrome"); + +using namespace mozilla; +using namespace mozilla::layers; +using namespace mozilla::widget; + +ChromeProcessController::ChromeProcessController( + nsIWidget* aWidget, APZEventState* aAPZEventState, + IAPZCTreeManager* aAPZCTreeManager) + : mWidget(aWidget), + mAPZEventState(aAPZEventState), + mAPZCTreeManager(aAPZCTreeManager), + mUIThread(NS_GetCurrentThread()) { + // Otherwise we're initializing mUIThread incorrectly. + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aAPZEventState); + MOZ_ASSERT(aAPZCTreeManager); + + mUIThread->Dispatch( + NewRunnableMethod("layers::ChromeProcessController::InitializeRoot", this, + &ChromeProcessController::InitializeRoot)); +} + +ChromeProcessController::~ChromeProcessController() = default; + +void ChromeProcessController::InitializeRoot() { + APZCCallbackHelper::InitializeRootDisplayport(GetPresShell()); +} + +void ChromeProcessController::NotifyLayerTransforms( + nsTArray&& aTransforms) { + if (!mUIThread->IsOnCurrentThread()) { + mUIThread->Dispatch( + NewRunnableMethod>>( + "layers::ChromeProcessController::NotifyLayerTransforms", this, + &ChromeProcessController::NotifyLayerTransforms, + std::move(aTransforms))); + return; + } + + APZCCallbackHelper::NotifyLayerTransforms(aTransforms); +} + +void ChromeProcessController::RequestContentRepaint( + const RepaintRequest& aRequest) { + MOZ_ASSERT(IsRepaintThread()); + + if (aRequest.IsRootContent()) { + APZCCallbackHelper::UpdateRootFrame(aRequest); + } else { + APZCCallbackHelper::UpdateSubFrame(aRequest); + } +} + +bool ChromeProcessController::IsRepaintThread() { return NS_IsMainThread(); } + +void ChromeProcessController::DispatchToRepaintThread( + already_AddRefed aTask) { + NS_DispatchToMainThread(std::move(aTask)); +} + +void ChromeProcessController::Destroy() { + if (!mUIThread->IsOnCurrentThread()) { + mUIThread->Dispatch( + NewRunnableMethod("layers::ChromeProcessController::Destroy", this, + &ChromeProcessController::Destroy)); + return; + } + + MOZ_ASSERT(mUIThread->IsOnCurrentThread()); + mWidget = nullptr; + mAPZEventState = nullptr; +} + +PresShell* ChromeProcessController::GetPresShell() const { + if (!mWidget) { + return nullptr; + } + if (nsView* view = nsView::GetViewFor(mWidget)) { + return view->GetPresShell(); + } + return nullptr; +} + +dom::Document* ChromeProcessController::GetRootDocument() const { + if (PresShell* presShell = GetPresShell()) { + return presShell->GetDocument(); + } + return nullptr; +} + +dom::Document* ChromeProcessController::GetRootContentDocument( + const ScrollableLayerGuid::ViewID& aScrollId) const { + nsIContent* content = nsLayoutUtils::FindContentFor(aScrollId); + if (!content) { + return nullptr; + } + if (PresShell* presShell = + APZCCallbackHelper::GetRootContentDocumentPresShellForContent( + content)) { + return presShell->GetDocument(); + } + return nullptr; +} + +void ChromeProcessController::HandleDoubleTap( + const mozilla::CSSPoint& aPoint, Modifiers aModifiers, + const ScrollableLayerGuid& aGuid) { + MOZ_ASSERT(mUIThread->IsOnCurrentThread()); + + RefPtr document = GetRootContentDocument(aGuid.mScrollId); + if (!document.get()) { + return; + } + + CSSRect zoomToRect = CalculateRectToZoomTo(document, aPoint); + + uint32_t presShellId; + ScrollableLayerGuid::ViewID viewId; + if (APZCCallbackHelper::GetOrCreateScrollIdentifiers( + document->GetDocumentElement(), &presShellId, &viewId)) { + APZThreadUtils::RunOnControllerThread( + NewRunnableMethod( + "IAPZCTreeManager::ZoomToRect", mAPZCTreeManager, + &IAPZCTreeManager::ZoomToRect, + ScrollableLayerGuid(aGuid.mLayersId, presShellId, viewId), + zoomToRect, ZoomToRectBehavior::DEFAULT_BEHAVIOR)); + } +} + +void ChromeProcessController::HandleTap( + TapType aType, const mozilla::LayoutDevicePoint& aPoint, + Modifiers aModifiers, const ScrollableLayerGuid& aGuid, + uint64_t aInputBlockId) { + MOZ_LOG(sApzChromeLog, LogLevel::Debug, + ("HandleTap called with %d\n", (int)aType)); + if (!mUIThread->IsOnCurrentThread()) { + MOZ_LOG(sApzChromeLog, LogLevel::Debug, ("HandleTap redispatching\n")); + mUIThread->Dispatch( + NewRunnableMethod( + "layers::ChromeProcessController::HandleTap", this, + &ChromeProcessController::HandleTap, aType, aPoint, aModifiers, + aGuid, aInputBlockId)); + return; + } + + if (!mAPZEventState) { + return; + } + + RefPtr presShell = GetPresShell(); + if (!presShell) { + return; + } + if (!presShell->GetPresContext()) { + return; + } + CSSToLayoutDeviceScale scale( + presShell->GetPresContext()->CSSToDevPixelScale()); + + CSSPoint point = aPoint / scale; + + // Stash the guid in InputAPZContext so that when the visual-to-layout + // transform is applied to the event's coordinates, we use the right transform + // based on the scroll frame being targeted. + // The other values don't really matter. + InputAPZContext context(aGuid, aInputBlockId, nsEventStatus_eSentinel); + + switch (aType) { + case TapType::eSingleTap: + mAPZEventState->ProcessSingleTap(point, scale, aModifiers, 1); + break; + case TapType::eDoubleTap: + HandleDoubleTap(point, aModifiers, aGuid); + break; + case TapType::eSecondTap: + mAPZEventState->ProcessSingleTap(point, scale, aModifiers, 2); + break; + case TapType::eLongTap: { + RefPtr eventState(mAPZEventState); + eventState->ProcessLongTap(presShell, point, scale, aModifiers, + aInputBlockId); + break; + } + case TapType::eLongTapUp: { + RefPtr eventState(mAPZEventState); + eventState->ProcessLongTapUp(presShell, point, scale, aModifiers); + break; + } + } +} + +void ChromeProcessController::NotifyPinchGesture( + PinchGestureInput::PinchGestureType aType, const ScrollableLayerGuid& aGuid, + const LayoutDevicePoint& aFocusPoint, LayoutDeviceCoord aSpanChange, + Modifiers aModifiers) { + if (!mUIThread->IsOnCurrentThread()) { + mUIThread->Dispatch( + NewRunnableMethod( + "layers::ChromeProcessController::NotifyPinchGesture", this, + &ChromeProcessController::NotifyPinchGesture, aType, aGuid, + aFocusPoint, aSpanChange, aModifiers)); + return; + } + + if (mWidget) { + // Dispatch the call to APZCCallbackHelper::NotifyPinchGesture to the main + // thread so that it runs asynchronously from the current call. This is + // because the call can run arbitrary JS code, which can also spin the event + // loop and cause undesirable re-entrancy in APZ. + mUIThread->Dispatch(NewRunnableFunction( + "layers::ChromeProcessController::NotifyPinchGestureAsync", + &APZCCallbackHelper::NotifyPinchGesture, aType, aFocusPoint, + aSpanChange, aModifiers, mWidget)); + } +} + +void ChromeProcessController::NotifyAPZStateChange( + const ScrollableLayerGuid& aGuid, APZStateChange aChange, int aArg) { + if (!mUIThread->IsOnCurrentThread()) { + mUIThread->Dispatch( + NewRunnableMethod( + "layers::ChromeProcessController::NotifyAPZStateChange", this, + &ChromeProcessController::NotifyAPZStateChange, aGuid, aChange, + aArg)); + return; + } + + if (!mAPZEventState) { + return; + } + + mAPZEventState->ProcessAPZStateChange(aGuid.mScrollId, aChange, aArg); +} + +void ChromeProcessController::NotifyMozMouseScrollEvent( + const ScrollableLayerGuid::ViewID& aScrollId, const nsString& aEvent) { + if (!mUIThread->IsOnCurrentThread()) { + mUIThread->Dispatch( + NewRunnableMethod( + "layers::ChromeProcessController::NotifyMozMouseScrollEvent", this, + &ChromeProcessController::NotifyMozMouseScrollEvent, aScrollId, + aEvent)); + return; + } + + APZCCallbackHelper::NotifyMozMouseScrollEvent(aScrollId, aEvent); +} + +void ChromeProcessController::NotifyFlushComplete() { + MOZ_ASSERT(IsRepaintThread()); + + APZCCallbackHelper::NotifyFlushComplete(GetPresShell()); +} + +void ChromeProcessController::NotifyAsyncScrollbarDragInitiated( + uint64_t aDragBlockId, const ScrollableLayerGuid::ViewID& aScrollId, + ScrollDirection aDirection) { + if (!mUIThread->IsOnCurrentThread()) { + mUIThread->Dispatch(NewRunnableMethod( + "layers::ChromeProcessController::NotifyAsyncScrollbarDragInitiated", + this, &ChromeProcessController::NotifyAsyncScrollbarDragInitiated, + aDragBlockId, aScrollId, aDirection)); + return; + } + + APZCCallbackHelper::NotifyAsyncScrollbarDragInitiated(aDragBlockId, aScrollId, + aDirection); +} + +void ChromeProcessController::NotifyAsyncScrollbarDragRejected( + const ScrollableLayerGuid::ViewID& aScrollId) { + if (!mUIThread->IsOnCurrentThread()) { + mUIThread->Dispatch(NewRunnableMethod( + "layers::ChromeProcessController::NotifyAsyncScrollbarDragRejected", + this, &ChromeProcessController::NotifyAsyncScrollbarDragRejected, + aScrollId)); + return; + } + + APZCCallbackHelper::NotifyAsyncScrollbarDragRejected(aScrollId); +} + +void ChromeProcessController::NotifyAsyncAutoscrollRejected( + const ScrollableLayerGuid::ViewID& aScrollId) { + if (!mUIThread->IsOnCurrentThread()) { + mUIThread->Dispatch(NewRunnableMethod( + "layers::ChromeProcessController::NotifyAsyncAutoscrollRejected", this, + &ChromeProcessController::NotifyAsyncAutoscrollRejected, aScrollId)); + return; + } + + APZCCallbackHelper::NotifyAsyncAutoscrollRejected(aScrollId); +} + +void ChromeProcessController::CancelAutoscroll( + const ScrollableLayerGuid& aGuid) { + if (!mUIThread->IsOnCurrentThread()) { + mUIThread->Dispatch(NewRunnableMethod( + "layers::ChromeProcessController::CancelAutoscroll", this, + &ChromeProcessController::CancelAutoscroll, aGuid)); + return; + } + + APZCCallbackHelper::CancelAutoscroll(aGuid.mScrollId); +} diff --git a/gfx/layers/apz/util/ChromeProcessController.h b/gfx/layers/apz/util/ChromeProcessController.h new file mode 100644 index 0000000000..158a0d6eba --- /dev/null +++ b/gfx/layers/apz/util/ChromeProcessController.h @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_ChromeProcessController_h +#define mozilla_layers_ChromeProcessController_h + +#include "mozilla/layers/GeckoContentController.h" +#include "nsCOMPtr.h" +#include "mozilla/RefPtr.h" +#include "mozilla/layers/MatrixMessage.h" + +class nsIDOMWindowUtils; +class nsISerialEventTarget; +class nsIWidget; + +namespace mozilla { +class PresShell; +namespace dom { +class Document; +} + +namespace layers { + +class IAPZCTreeManager; +class APZEventState; + +/** + * ChromeProcessController is a GeckoContentController attached to the root of + * a compositor's layer tree. It's used directly by APZ by default, and remoted + * using PAPZ if there is a gpu process. + * + * If ChromeProcessController needs to implement a new method on + * GeckoContentController PAPZ, APZChild, and RemoteContentController must be + * updated to handle it. + */ +class ChromeProcessController : public mozilla::layers::GeckoContentController { + protected: + typedef mozilla::layers::FrameMetrics FrameMetrics; + typedef mozilla::layers::ScrollableLayerGuid ScrollableLayerGuid; + + public: + explicit ChromeProcessController(nsIWidget* aWidget, + APZEventState* aAPZEventState, + IAPZCTreeManager* aAPZCTreeManager); + virtual ~ChromeProcessController(); + void Destroy() override; + + // GeckoContentController interface + void NotifyLayerTransforms(nsTArray&& aTransforms) override; + void RequestContentRepaint(const RepaintRequest& aRequest) override; + bool IsRepaintThread() override; + void DispatchToRepaintThread(already_AddRefed aTask) override; + MOZ_CAN_RUN_SCRIPT + void HandleTap(TapType aType, const mozilla::LayoutDevicePoint& aPoint, + Modifiers aModifiers, const ScrollableLayerGuid& aGuid, + uint64_t aInputBlockId) override; + void NotifyPinchGesture(PinchGestureInput::PinchGestureType aType, + const ScrollableLayerGuid& aGuid, + const LayoutDevicePoint& aFocusPoint, + LayoutDeviceCoord aSpanChange, + Modifiers aModifiers) override; + void NotifyAPZStateChange(const ScrollableLayerGuid& aGuid, + APZStateChange aChange, int aArg) override; + void NotifyMozMouseScrollEvent(const ScrollableLayerGuid::ViewID& aScrollId, + const nsString& aEvent) override; + void NotifyFlushComplete() override; + void NotifyAsyncScrollbarDragInitiated( + uint64_t aDragBlockId, const ScrollableLayerGuid::ViewID& aScrollId, + ScrollDirection aDirection) override; + void NotifyAsyncScrollbarDragRejected( + const ScrollableLayerGuid::ViewID& aScrollId) override; + void NotifyAsyncAutoscrollRejected( + const ScrollableLayerGuid::ViewID& aScrollId) override; + void CancelAutoscroll(const ScrollableLayerGuid& aGuid) override; + + private: + nsCOMPtr mWidget; + RefPtr mAPZEventState; + RefPtr mAPZCTreeManager; + nsCOMPtr mUIThread; + + void InitializeRoot(); + PresShell* GetPresShell() const; + dom::Document* GetRootDocument() const; + dom::Document* GetRootContentDocument( + const ScrollableLayerGuid::ViewID& aScrollId) const; + void HandleDoubleTap(const mozilla::CSSPoint& aPoint, Modifiers aModifiers, + const ScrollableLayerGuid& aGuid); +}; + +} // namespace layers +} // namespace mozilla + +#endif /* mozilla_layers_ChromeProcessController_h */ diff --git a/gfx/layers/apz/util/ContentProcessController.cpp b/gfx/layers/apz/util/ContentProcessController.cpp new file mode 100644 index 0000000000..0c52890631 --- /dev/null +++ b/gfx/layers/apz/util/ContentProcessController.cpp @@ -0,0 +1,108 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ContentProcessController.h" + +#include "mozilla/PresShell.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/layers/APZCCallbackHelper.h" +#include "mozilla/layers/APZChild.h" +#include "nsIContentInlines.h" + +#include "InputData.h" // for InputData + +namespace mozilla { +namespace layers { + +ContentProcessController::ContentProcessController( + const RefPtr& aBrowser) + : mBrowser(aBrowser) { + MOZ_ASSERT(mBrowser); +} + +void ContentProcessController::NotifyLayerTransforms( + nsTArray&& aTransforms) { + // This should never get called + MOZ_ASSERT(false); +} + +void ContentProcessController::RequestContentRepaint( + const RepaintRequest& aRequest) { + if (mBrowser) { + mBrowser->UpdateFrame(aRequest); + } +} + +void ContentProcessController::HandleTap(TapType aType, + const LayoutDevicePoint& aPoint, + Modifiers aModifiers, + const ScrollableLayerGuid& aGuid, + uint64_t aInputBlockId) { + // This should never get called + MOZ_ASSERT(false); +} + +void ContentProcessController::NotifyPinchGesture( + PinchGestureInput::PinchGestureType aType, const ScrollableLayerGuid& aGuid, + const LayoutDevicePoint& aFocusPoint, LayoutDeviceCoord aSpanChange, + Modifiers aModifiers) { + // This should never get called + MOZ_ASSERT_UNREACHABLE("Unexpected message to content process"); +} + +void ContentProcessController::NotifyAPZStateChange( + const ScrollableLayerGuid& aGuid, APZStateChange aChange, int aArg) { + if (mBrowser) { + mBrowser->NotifyAPZStateChange(aGuid.mScrollId, aChange, aArg); + } +} + +void ContentProcessController::NotifyMozMouseScrollEvent( + const ScrollableLayerGuid::ViewID& aScrollId, const nsString& aEvent) { + if (mBrowser) { + APZCCallbackHelper::NotifyMozMouseScrollEvent(aScrollId, aEvent); + } +} + +void ContentProcessController::NotifyFlushComplete() { + if (mBrowser) { + RefPtr presShell = mBrowser->GetTopLevelPresShell(); + APZCCallbackHelper::NotifyFlushComplete(presShell); + } +} + +void ContentProcessController::NotifyAsyncScrollbarDragInitiated( + uint64_t aDragBlockId, const ScrollableLayerGuid::ViewID& aScrollId, + ScrollDirection aDirection) { + APZCCallbackHelper::NotifyAsyncScrollbarDragInitiated(aDragBlockId, aScrollId, + aDirection); +} + +void ContentProcessController::NotifyAsyncScrollbarDragRejected( + const ScrollableLayerGuid::ViewID& aScrollId) { + APZCCallbackHelper::NotifyAsyncScrollbarDragRejected(aScrollId); +} + +void ContentProcessController::NotifyAsyncAutoscrollRejected( + const ScrollableLayerGuid::ViewID& aScrollId) { + APZCCallbackHelper::NotifyAsyncAutoscrollRejected(aScrollId); +} + +void ContentProcessController::CancelAutoscroll( + const ScrollableLayerGuid& aGuid) { + // This should never get called + MOZ_ASSERT_UNREACHABLE("Unexpected message to content process"); +} + +bool ContentProcessController::IsRepaintThread() { return NS_IsMainThread(); } + +void ContentProcessController::DispatchToRepaintThread( + already_AddRefed aTask) { + NS_DispatchToMainThread(std::move(aTask)); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/util/ContentProcessController.h b/gfx/layers/apz/util/ContentProcessController.h new file mode 100644 index 0000000000..ab5522cd44 --- /dev/null +++ b/gfx/layers/apz/util/ContentProcessController.h @@ -0,0 +1,87 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_ContentProcessController_h +#define mozilla_layers_ContentProcessController_h + +#include "mozilla/layers/GeckoContentController.h" + +class nsIObserver; + +namespace mozilla { + +namespace dom { +class BrowserChild; +} // namespace dom + +namespace layers { + +class APZChild; + +/** + * ContentProcessController is a GeckoContentController for a BrowserChild, and + * is always remoted using PAPZ/APZChild. + * + * ContentProcessController is created in ContentChild when a layer tree id has + * been allocated for a PBrowser that lives in that content process, and is + * destroyed when the Destroy message is received, or when the tab dies. + * + * If ContentProcessController needs to implement a new method on + * GeckoContentController PAPZ, APZChild, and RemoteContentController must be + * updated to handle it. + */ +class ContentProcessController final : public GeckoContentController { + public: + explicit ContentProcessController(const RefPtr& aBrowser); + + // GeckoContentController + + void NotifyLayerTransforms(nsTArray&& aTransforms) override; + + void RequestContentRepaint(const RepaintRequest& aRequest) override; + + void HandleTap(TapType aType, const LayoutDevicePoint& aPoint, + Modifiers aModifiers, const ScrollableLayerGuid& aGuid, + uint64_t aInputBlockId) override; + + void NotifyPinchGesture(PinchGestureInput::PinchGestureType aType, + const ScrollableLayerGuid& aGuid, + const LayoutDevicePoint& aFocusPoint, + LayoutDeviceCoord aSpanChange, + Modifiers aModifiers) override; + + void NotifyAPZStateChange(const ScrollableLayerGuid& aGuid, + APZStateChange aChange, int aArg) override; + + void NotifyMozMouseScrollEvent(const ScrollableLayerGuid::ViewID& aScrollId, + const nsString& aEvent) override; + + void NotifyFlushComplete() override; + + void NotifyAsyncScrollbarDragInitiated( + uint64_t aDragBlockId, const ScrollableLayerGuid::ViewID& aScrollId, + ScrollDirection aDirection) override; + void NotifyAsyncScrollbarDragRejected( + const ScrollableLayerGuid::ViewID& aScrollId) override; + + void NotifyAsyncAutoscrollRejected( + const ScrollableLayerGuid::ViewID& aScrollId) override; + + void CancelAutoscroll(const ScrollableLayerGuid& aGuid) override; + + bool IsRepaintThread() override; + + void DispatchToRepaintThread(already_AddRefed aTask) override; + + private: + RefPtr mBrowser; +}; + +} // namespace layers + +} // namespace mozilla + +#endif // mozilla_layers_ContentProcessController_h diff --git a/gfx/layers/apz/util/DoubleTapToZoom.cpp b/gfx/layers/apz/util/DoubleTapToZoom.cpp new file mode 100644 index 0000000000..6d4d3d7f0c --- /dev/null +++ b/gfx/layers/apz/util/DoubleTapToZoom.cpp @@ -0,0 +1,188 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DoubleTapToZoom.h" + +#include // for std::min, std::max + +#include "mozilla/PresShell.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/dom/Element.h" +#include "nsCOMPtr.h" +#include "nsIContent.h" +#include "mozilla/dom/Document.h" +#include "nsIFrame.h" +#include "nsIFrameInlines.h" +#include "nsIScrollableFrame.h" +#include "nsLayoutUtils.h" +#include "nsStyleConsts.h" + +namespace mozilla { +namespace layers { + +namespace { + +using FrameForPointOption = nsLayoutUtils::FrameForPointOption; + +// Returns the DOM element found at |aPoint|, interpreted as being relative to +// the root frame of |aPresShell| in visual coordinates. If the point is inside +// a subdocument, returns an element inside the subdocument, rather than the +// subdocument element (and does so recursively). The implementation was adapted +// from DocumentOrShadowRoot::ElementFromPoint(), with the notable exception +// that we don't pass nsLayoutUtils::IGNORE_CROSS_DOC to GetFrameForPoint(), so +// as to get the behaviour described above in the presence of subdocuments. +static already_AddRefed ElementFromPoint( + const RefPtr& aPresShell, const CSSPoint& aPoint) { + nsIFrame* rootFrame = aPresShell->GetRootFrame(); + if (!rootFrame) { + return nullptr; + } + nsIFrame* frame = nsLayoutUtils::GetFrameForPoint( + RelativeTo{rootFrame, ViewportType::Visual}, CSSPoint::ToAppUnits(aPoint), + {{FrameForPointOption::IgnorePaintSuppression}}); + while (frame && (!frame->GetContent() || + frame->GetContent()->IsInNativeAnonymousSubtree())) { + frame = nsLayoutUtils::GetParentOrPlaceholderFor(frame); + } + if (!frame) { + return nullptr; + } + // FIXME(emilio): This should probably use the flattened tree, GetParent() is + // not guaranteed to be an element in presence of shadow DOM. + nsIContent* content = frame->GetContent(); + if (!content) { + return nullptr; + } + if (dom::Element* element = content->GetAsElementOrParentElement()) { + return do_AddRef(element); + } + return nullptr; +} + +static bool ShouldZoomToElement(const nsCOMPtr& aElement) { + if (nsIFrame* frame = aElement->GetPrimaryFrame()) { + if (frame->StyleDisplay()->IsInlineFlow()) { + return false; + } + } + if (aElement->IsAnyOfHTMLElements(nsGkAtoms::li, nsGkAtoms::q)) { + return false; + } + return true; +} + +static bool IsRectZoomedIn(const CSSRect& aRect, + const CSSRect& aCompositedArea) { + // This functions checks to see if the area of the rect visible in the + // composition bounds (i.e. the overlapArea variable below) is approximately + // the max area of the rect we can show. + CSSRect overlap = aCompositedArea.Intersect(aRect); + float overlapArea = overlap.Width() * overlap.Height(); + float availHeight = std::min( + aRect.Width() * aCompositedArea.Height() / aCompositedArea.Width(), + aRect.Height()); + float showing = overlapArea / (aRect.Width() * availHeight); + float ratioW = aRect.Width() / aCompositedArea.Width(); + float ratioH = aRect.Height() / aCompositedArea.Height(); + + return showing > 0.9 && (ratioW > 0.9 || ratioH > 0.9); +} + +} // namespace + +CSSRect CalculateRectToZoomTo(const RefPtr& aRootContentDocument, + const CSSPoint& aPoint) { + // Ensure the layout information we get is up-to-date. + aRootContentDocument->FlushPendingNotifications(FlushType::Layout); + + // An empty rect as return value is interpreted as "zoom out". + const CSSRect zoomOut; + + RefPtr presShell = aRootContentDocument->GetPresShell(); + if (!presShell) { + return zoomOut; + } + + nsIScrollableFrame* rootScrollFrame = + presShell->GetRootScrollFrameAsScrollable(); + if (!rootScrollFrame) { + return zoomOut; + } + + nsCOMPtr element = ElementFromPoint(presShell, aPoint); + if (!element) { + return zoomOut; + } + + while (element && !ShouldZoomToElement(element)) { + element = element->GetParentElement(); + } + + if (!element) { + return zoomOut; + } + + FrameMetrics metrics = + nsLayoutUtils::CalculateBasicFrameMetrics(rootScrollFrame); + CSSPoint visualScrollOffset = metrics.GetVisualScrollOffset(); + CSSRect compositedArea(visualScrollOffset, + metrics.CalculateCompositedSizeInCssPixels()); + const CSSCoord margin = 15; + CSSRect rect = + nsLayoutUtils::GetBoundingContentRect(element, rootScrollFrame); + + // If the element is taller than the visible area of the page scale + // the height of the |rect| so that it has the same aspect ratio as + // the root frame. The clipped |rect| is centered on the y value of + // the touch point. This allows tall narrow elements to be zoomed. + if (!rect.IsEmpty() && compositedArea.Width() > 0.0f) { + const float widthRatio = rect.Width() / compositedArea.Width(); + float targetHeight = compositedArea.Height() * widthRatio; + if (widthRatio < 0.9 && targetHeight < rect.Height()) { + const CSSPoint scrollPoint = + CSSPoint::FromAppUnits(rootScrollFrame->GetScrollPosition()); + float newY = aPoint.y + scrollPoint.y - (targetHeight * 0.5f); + if ((newY + targetHeight) > rect.YMost()) { + rect.MoveByY(rect.Height() - targetHeight); + } else if (newY > rect.Y()) { + rect.MoveToY(newY); + } + rect.SetHeight(targetHeight); + } + } + + rect = CSSRect(std::max(metrics.GetScrollableRect().X(), rect.X() - margin), + rect.Y(), rect.Width() + 2 * margin, rect.Height()); + // Constrict the rect to the screen's right edge + rect.SetWidth( + std::min(rect.Width(), metrics.GetScrollableRect().XMost() - rect.X())); + + // If the rect is already taking up most of the visible area and is + // stretching the width of the page, then we want to zoom out instead. + if (IsRectZoomedIn(rect, compositedArea)) { + return zoomOut; + } + + CSSRect rounded(rect); + rounded.Round(); + + // If the block we're zooming to is really tall, and the user double-tapped + // more than a screenful of height from the top of it, then adjust the + // y-coordinate so that we center the actual point the user double-tapped + // upon. This prevents flying to the top of the page when double-tapping + // to zoom in (bug 761721). The 1.2 multiplier is just a little fuzz to + // compensate for 'rect' including horizontal margins but not vertical ones. + CSSCoord cssTapY = visualScrollOffset.y + aPoint.y; + if ((rect.Height() > rounded.Height()) && + (cssTapY > rounded.Y() + (rounded.Height() * 1.2))) { + rounded.MoveToY(cssTapY - (rounded.Height() / 2)); + } + + return rounded; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/util/DoubleTapToZoom.h b/gfx/layers/apz/util/DoubleTapToZoom.h new file mode 100644 index 0000000000..163481fe03 --- /dev/null +++ b/gfx/layers/apz/util/DoubleTapToZoom.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_DoubleTapToZoom_h +#define mozilla_layers_DoubleTapToZoom_h + +#include "Units.h" + +template +class RefPtr; + +namespace mozilla { +namespace dom { +class Document; +} + +namespace layers { + +/** + * For a double tap at |aPoint|, return the rect to which the browser + * should zoom in response, or an empty rect if the browser should zoom out. + * |aDocument| should be the root content document for the content that was + * tapped. + */ +CSSRect CalculateRectToZoomTo( + const RefPtr& aRootContentDocument, + const CSSPoint& aPoint); + +} // namespace layers +} // namespace mozilla + +#endif /* mozilla_layers_DoubleTapToZoom_h */ diff --git a/gfx/layers/apz/util/InputAPZContext.cpp b/gfx/layers/apz/util/InputAPZContext.cpp new file mode 100644 index 0000000000..77573221ff --- /dev/null +++ b/gfx/layers/apz/util/InputAPZContext.cpp @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "InputAPZContext.h" + +namespace mozilla { +namespace layers { + +ScrollableLayerGuid InputAPZContext::sGuid; +uint64_t InputAPZContext::sBlockId = 0; +nsEventStatus InputAPZContext::sApzResponse = nsEventStatus_eSentinel; +bool InputAPZContext::sPendingLayerization = false; +bool InputAPZContext::sRoutedToChildProcess = false; + +/*static*/ +ScrollableLayerGuid InputAPZContext::GetTargetLayerGuid() { return sGuid; } + +/*static*/ +uint64_t InputAPZContext::GetInputBlockId() { return sBlockId; } + +/*static*/ +nsEventStatus InputAPZContext::GetApzResponse() { return sApzResponse; } + +/*static*/ +bool InputAPZContext::HavePendingLayerization() { return sPendingLayerization; } + +/*static*/ +bool InputAPZContext::WasRoutedToChildProcess() { + return sRoutedToChildProcess; +} + +InputAPZContext::InputAPZContext(const ScrollableLayerGuid& aGuid, + const uint64_t& aBlockId, + const nsEventStatus& aApzResponse, + bool aPendingLayerization) + : mOldGuid(sGuid), + mOldBlockId(sBlockId), + mOldApzResponse(sApzResponse), + mOldPendingLayerization(sPendingLayerization), + mOldRoutedToChildProcess(sRoutedToChildProcess) { + sGuid = aGuid; + sBlockId = aBlockId; + sApzResponse = aApzResponse; + sPendingLayerization = aPendingLayerization; + sRoutedToChildProcess = false; +} + +InputAPZContext::~InputAPZContext() { + sGuid = mOldGuid; + sBlockId = mOldBlockId; + sApzResponse = mOldApzResponse; + sPendingLayerization = mOldPendingLayerization; + sRoutedToChildProcess = mOldRoutedToChildProcess; +} + +/*static*/ +void InputAPZContext::SetRoutedToChildProcess() { + sRoutedToChildProcess = true; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/util/InputAPZContext.h b/gfx/layers/apz/util/InputAPZContext.h new file mode 100644 index 0000000000..928359ab1d --- /dev/null +++ b/gfx/layers/apz/util/InputAPZContext.h @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_InputAPZContext_h +#define mozilla_layers_InputAPZContext_h + +#include "mozilla/EventForwards.h" +#include "mozilla/layers/ScrollableLayerGuid.h" + +namespace mozilla { +namespace layers { + +// InputAPZContext is used to communicate various pieces of information +// around the codebase without having to plumb it through lots of functions +// and codepaths. Conceptually it is attached to a WidgetInputEvent that is +// relevant to APZ. +// +// There are two types of information bits propagated using this class. One +// type is propagated "downwards" (from a process entry point like nsBaseWidget +// or BrowserChild) into deeper code that is run during complicated operations +// like event dispatch. The other type is information that is propagated +// "upwards", from the deeper code back to the entry point. +class MOZ_STACK_CLASS InputAPZContext { + private: + // State that is propagated downwards from InputAPZContext creation into + // "deeper" code. + static ScrollableLayerGuid sGuid; + static uint64_t sBlockId; + static nsEventStatus sApzResponse; + static bool sPendingLayerization; + + // State that is set in deeper code and propagated upwards. + static bool sRoutedToChildProcess; + + public: + // Functions to access downwards-propagated data + static ScrollableLayerGuid GetTargetLayerGuid(); + static uint64_t GetInputBlockId(); + static nsEventStatus GetApzResponse(); + static bool HavePendingLayerization(); + + // Functions to access upwards-propagated data + static bool WasRoutedToChildProcess(); + + // Constructor sets the data to be propagated downwards + InputAPZContext(const ScrollableLayerGuid& aGuid, const uint64_t& aBlockId, + const nsEventStatus& aApzResponse, + bool aPendingLayerization = false); + ~InputAPZContext(); + + // Functions to set data to be propagated upwards + static void SetRoutedToChildProcess(); + + private: + ScrollableLayerGuid mOldGuid; + uint64_t mOldBlockId; + nsEventStatus mOldApzResponse; + bool mOldPendingLayerization; + + bool mOldRoutedToChildProcess; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* mozilla_layers_InputAPZContext_h */ diff --git a/gfx/layers/apz/util/ScrollInputMethods.h b/gfx/layers/apz/util/ScrollInputMethods.h new file mode 100644 index 0000000000..999f2c6abf --- /dev/null +++ b/gfx/layers/apz/util/ScrollInputMethods.h @@ -0,0 +1,78 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_ScrollInputMethods_h +#define mozilla_layers_ScrollInputMethods_h + +namespace mozilla { +namespace layers { + +/** + * An enumeration that lists various input methods used to trigger scrolling. + * Used as the values for the SCROLL_INPUT_METHODS telemetry histogram. + */ +enum class ScrollInputMethod { + + // === Driven by APZ === + + ApzTouch, // touch events + ApzWheelPixel, // wheel events, pixel scrolling mode + ApzWheelLine, // wheel events, line scrolling mode + ApzWheelPage, // wheel events, page scrolling mode + ApzPanGesture, // pan gesture events (generally triggered by trackpad) + ApzScrollbarDrag, // dragging the scrollbar + + // === Driven by the main thread === + + // Keyboard + MainThreadScrollLine, // line scrolling + // (generally triggered by up/down arrow keys) + MainThreadScrollCharacter, // character scrolling + // (generally triggered by left/right arrow keys) + MainThreadScrollPage, // page scrolling + // (generally triggered by PageUp/PageDown keys) + MainThreadCompleteScroll, // scrolling to the end of the scroll range + // (generally triggered by Home/End keys) + MainThreadScrollCaretIntoView, // scrolling to bring the caret into view + // after moving the caret via the keyboard + + // Touch + MainThreadTouch, // touch events + + // Scrollbar + MainThreadScrollbarDrag, // dragging the scrollbar + MainThreadScrollbarButtonClick, // clicking the buttons at the ends of the + // scrollback track + MainThreadScrollbarTrackClick, // clicking the scrollbar track above or + // below the thumb + + // Autoscrolling + MainThreadAutoscrolling, // autoscrolling + + // === Additional input methods implemented in APZ === + + // Async Keyboard + ApzScrollLine, // line scrolling + // (generally triggered by up/down arrow keys) + ApzScrollCharacter, // character scrolling + // (generally triggered by left/right arrow keys) + ApzScrollPage, // page scrolling + // (generally triggered by PageUp/PageDown keys) + ApzCompleteScroll, // scrolling to the end of the scroll range + // (generally triggered by Home/End keys) + + // Autoscrolling + ApzAutoscrolling, + + // New input methods can be added at the end, up to a maximum of 64. + // They should only be added at the end, to preserve the numerical values + // of the existing enumerators. +}; + +} // namespace layers +} // namespace mozilla + +#endif /* mozilla_layers_ScrollInputMethods_h */ diff --git a/gfx/layers/apz/util/ScrollLinkedEffectDetector.cpp b/gfx/layers/apz/util/ScrollLinkedEffectDetector.cpp new file mode 100644 index 0000000000..6ef8749a9c --- /dev/null +++ b/gfx/layers/apz/util/ScrollLinkedEffectDetector.cpp @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ScrollLinkedEffectDetector.h" + +#include "mozilla/dom/Document.h" +#include "nsThreadUtils.h" + +namespace mozilla { +namespace layers { + +uint32_t ScrollLinkedEffectDetector::sDepth = 0; +bool ScrollLinkedEffectDetector::sFoundScrollLinkedEffect = false; + +/* static */ +void ScrollLinkedEffectDetector::PositioningPropertyMutated() { + MOZ_ASSERT(NS_IsMainThread()); + + if (sDepth > 0) { + // We are inside a scroll event dispatch + sFoundScrollLinkedEffect = true; + } +} + +ScrollLinkedEffectDetector::ScrollLinkedEffectDetector(dom::Document* aDoc) + : mDocument(aDoc) { + MOZ_ASSERT(NS_IsMainThread()); + sDepth++; +} + +ScrollLinkedEffectDetector::~ScrollLinkedEffectDetector() { + sDepth--; + if (sDepth == 0) { + // We have exited all (possibly-nested) scroll event dispatches, + // record whether or not we found an effect, and reset state + if (sFoundScrollLinkedEffect) { + mDocument->ReportHasScrollLinkedEffect(); + sFoundScrollLinkedEffect = false; + } + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/util/ScrollLinkedEffectDetector.h b/gfx/layers/apz/util/ScrollLinkedEffectDetector.h new file mode 100644 index 0000000000..81044ed614 --- /dev/null +++ b/gfx/layers/apz/util/ScrollLinkedEffectDetector.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_ScrollLinkedEffectDetector_h +#define mozilla_layers_ScrollLinkedEffectDetector_h + +#include "mozilla/RefPtr.h" + +namespace mozilla { + +namespace dom { +class Document; +} + +namespace layers { + +// ScrollLinkedEffectDetector is used to detect the existence of a scroll-linked +// effect on a webpage. Generally speaking, a scroll-linked effect is something +// on the page that animates or changes with respect to the scroll position. +// Content authors usually rely on running some JS in response to the scroll +// event in order to implement such effects, and therefore it tends to be laggy +// or work improperly with APZ enabled. This class helps us detect such an +// effect so that we can warn the author and/or take other preventative +// measures. +class MOZ_STACK_CLASS ScrollLinkedEffectDetector final { + private: + static uint32_t sDepth; + static bool sFoundScrollLinkedEffect; + + public: + static void PositioningPropertyMutated(); + + explicit ScrollLinkedEffectDetector(dom::Document*); + ~ScrollLinkedEffectDetector(); + + private: + RefPtr mDocument; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* mozilla_layers_ScrollLinkedEffectDetector_h */ diff --git a/gfx/layers/apz/util/TouchActionHelper.cpp b/gfx/layers/apz/util/TouchActionHelper.cpp new file mode 100644 index 0000000000..1cac71467f --- /dev/null +++ b/gfx/layers/apz/util/TouchActionHelper.cpp @@ -0,0 +1,106 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TouchActionHelper.h" + +#include "mozilla/layers/IAPZCTreeManager.h" +#include "nsContainerFrame.h" +#include "nsIFrameInlines.h" +#include "nsIScrollableFrame.h" +#include "nsLayoutUtils.h" + +namespace mozilla { +namespace layers { + +static void UpdateAllowedBehavior(StyleTouchAction aTouchActionValue, + bool aConsiderPanning, + TouchBehaviorFlags& aOutBehavior) { + if (aTouchActionValue != StyleTouchAction::AUTO) { + // Double-tap-zooming need property value AUTO + aOutBehavior &= ~AllowedTouchBehavior::DOUBLE_TAP_ZOOM; + if (aTouchActionValue != StyleTouchAction::MANIPULATION && + !(aTouchActionValue & StyleTouchAction::PINCH_ZOOM)) { + // Pinch-zooming needs value AUTO or MANIPULATION, or the PINCH_ZOOM bit + // set + aOutBehavior &= ~AllowedTouchBehavior::PINCH_ZOOM; + } + } + + if (aConsiderPanning) { + if (aTouchActionValue == StyleTouchAction::NONE) { + aOutBehavior &= ~AllowedTouchBehavior::VERTICAL_PAN; + aOutBehavior &= ~AllowedTouchBehavior::HORIZONTAL_PAN; + } + + // Values pan-x and pan-y set at the same time to the same element do not + // affect panning constraints. Therefore we need to check whether pan-x is + // set without pan-y and the same for pan-y. + if ((aTouchActionValue & StyleTouchAction::PAN_X) && + !(aTouchActionValue & StyleTouchAction::PAN_Y)) { + aOutBehavior &= ~AllowedTouchBehavior::VERTICAL_PAN; + } else if ((aTouchActionValue & StyleTouchAction::PAN_Y) && + !(aTouchActionValue & StyleTouchAction::PAN_X)) { + aOutBehavior &= ~AllowedTouchBehavior::HORIZONTAL_PAN; + } + } +} + +TouchBehaviorFlags TouchActionHelper::GetAllowedTouchBehavior( + nsIWidget* aWidget, RelativeTo aRootFrame, + const LayoutDeviceIntPoint& aPoint) { + TouchBehaviorFlags behavior = AllowedTouchBehavior::VERTICAL_PAN | + AllowedTouchBehavior::HORIZONTAL_PAN | + AllowedTouchBehavior::PINCH_ZOOM | + AllowedTouchBehavior::DOUBLE_TAP_ZOOM; + + nsPoint relativePoint = + nsLayoutUtils::GetEventCoordinatesRelativeTo(aWidget, aPoint, aRootFrame); + + nsIFrame* target = nsLayoutUtils::GetFrameForPoint(aRootFrame, relativePoint); + if (!target) { + return behavior; + } + nsIScrollableFrame* nearestScrollableParent = + nsLayoutUtils::GetNearestScrollableFrame(target, 0); + nsIFrame* nearestScrollableFrame = do_QueryFrame(nearestScrollableParent); + + // We're walking up the DOM tree until we meet the element with touch behavior + // and accumulating touch-action restrictions of all elements in this chain. + // The exact quote from the spec, that clarifies more: + // To determine the effect of a touch, find the nearest ancestor (starting + // from the element itself) that has a default touch behavior. Then examine + // the touch-action property of each element between the hit tested element + // and the element with the default touch behavior (including both the hit + // tested element and the element with the default touch behavior). If the + // touch-action property of any of those elements disallows the default touch + // behavior, do nothing. Otherwise allow the element to start considering the + // touch for the purposes of executing a default touch behavior. + + // Currently we support only two touch behaviors: panning and zooming. + // For panning we walk up until we meet the first scrollable element (the + // element that supports panning) or root element. For zooming we walk up + // until the root element since Firefox currently supports only zooming of the + // root frame but not the subframes. + + bool considerPanning = true; + + for (nsIFrame* frame = target; frame && frame->GetContent() && behavior; + frame = frame->GetInFlowParent()) { + UpdateAllowedBehavior(nsLayoutUtils::GetTouchActionFromFrame(frame), + considerPanning, behavior); + + if (frame == nearestScrollableFrame) { + // We met the scrollable element, after it we shouldn't consider + // touch-action values for the purpose of panning but only for zooming. + considerPanning = false; + } + } + + return behavior; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/util/TouchActionHelper.h b/gfx/layers/apz/util/TouchActionHelper.h new file mode 100644 index 0000000000..723b276997 --- /dev/null +++ b/gfx/layers/apz/util/TouchActionHelper.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __mozilla_layers_TouchActionHelper_h__ +#define __mozilla_layers_TouchActionHelper_h__ + +#include "mozilla/layers/LayersTypes.h" // for TouchBehaviorFlags +#include "RelativeTo.h" // for RelativeTo + +class nsIFrame; +class nsIWidget; + +namespace mozilla { +namespace layers { + +/* + * Helper class to figure out the allowed touch behavior for frames, as per + * the touch-action spec. + */ +class TouchActionHelper { + public: + /* + * Performs hit testing on content, finds frame that corresponds to the aPoint + * and retrieves touch-action css property value from it according the rules + * specified in the spec: + * http://www.w3.org/TR/pointerevents/#the-touch-action-css-property. + */ + static TouchBehaviorFlags GetAllowedTouchBehavior( + nsIWidget* aWidget, RelativeTo aRootFrame, + const LayoutDeviceIntPoint& aPoint); +}; + +} // namespace layers +} // namespace mozilla + +#endif /*__mozilla_layers_TouchActionHelper_h__ */ diff --git a/gfx/layers/apz/util/TouchCounter.cpp b/gfx/layers/apz/util/TouchCounter.cpp new file mode 100644 index 0000000000..9e4d6f1ba6 --- /dev/null +++ b/gfx/layers/apz/util/TouchCounter.cpp @@ -0,0 +1,74 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TouchCounter.h" + +#include "InputData.h" +#include "mozilla/TouchEvents.h" + +namespace mozilla { +namespace layers { + +TouchCounter::TouchCounter() : mActiveTouchCount(0) {} + +void TouchCounter::Update(const MultiTouchInput& aInput) { + switch (aInput.mType) { + case MultiTouchInput::MULTITOUCH_START: + // touch-start event contains all active touches of the current session + mActiveTouchCount = aInput.mTouches.Length(); + break; + case MultiTouchInput::MULTITOUCH_END: + if (mActiveTouchCount >= aInput.mTouches.Length()) { + // touch-end event contains only released touches + mActiveTouchCount -= aInput.mTouches.Length(); + } else { + NS_WARNING("Got an unexpected touchend"); + mActiveTouchCount = 0; + } + break; + case MultiTouchInput::MULTITOUCH_CANCEL: + mActiveTouchCount = 0; + break; + case MultiTouchInput::MULTITOUCH_MOVE: + break; + } +} + +void TouchCounter::Update(const WidgetTouchEvent& aEvent) { + switch (aEvent.mMessage) { + case eTouchStart: + // touch-start event contains all active touches of the current session + mActiveTouchCount = aEvent.mTouches.Length(); + break; + case eTouchEnd: { + // touch-end contains all touches, but ones being lifted are marked as + // changed + uint32_t liftedTouches = 0; + for (const auto& touch : aEvent.mTouches) { + if (touch->mChanged) { + liftedTouches++; + } + } + if (mActiveTouchCount >= liftedTouches) { + mActiveTouchCount -= liftedTouches; + } else { + NS_WARNING("Got an unexpected touchend"); + mActiveTouchCount = 0; + } + break; + } + case eTouchCancel: + mActiveTouchCount = 0; + break; + default: + break; + } +} + +uint32_t TouchCounter::GetActiveTouchCount() const { return mActiveTouchCount; } + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/apz/util/TouchCounter.h b/gfx/layers/apz/util/TouchCounter.h new file mode 100644 index 0000000000..c13f475355 --- /dev/null +++ b/gfx/layers/apz/util/TouchCounter.h @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_TouchCounter_h +#define mozilla_layers_TouchCounter_h + +#include "mozilla/EventForwards.h" + +namespace mozilla { + +class MultiTouchInput; + +namespace layers { + +// TouchCounter simply tracks the number of active touch points. Feed it +// your input events to update the internal state. Generally you should +// only be calling one of the Update functions, depending on which type +// of touch inputs you have access to. +class TouchCounter { + public: + TouchCounter(); + void Update(const MultiTouchInput& aInput); + void Update(const WidgetTouchEvent& aEvent); + uint32_t GetActiveTouchCount() const; + + private: + uint32_t mActiveTouchCount; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* mozilla_layers_TouchCounter_h */ diff --git a/gfx/layers/basic/AutoMaskData.h b/gfx/layers/basic/AutoMaskData.h new file mode 100644 index 0000000000..8775c8c33d --- /dev/null +++ b/gfx/layers/basic/AutoMaskData.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_AUTOMASKDATA_H_ +#define GFX_AUTOMASKDATA_H_ + +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor + +namespace mozilla { +namespace layers { + +/** + * Drawing with a mask requires a mask surface and a transform. + * + * This helper class manages the SourceSurface logic. + */ +class MOZ_STACK_CLASS AutoMoz2DMaskData { + public: + AutoMoz2DMaskData() = default; + ~AutoMoz2DMaskData() = default; + + void Construct(const gfx::Matrix& aTransform, gfx::SourceSurface* aSurface) { + MOZ_ASSERT(!IsConstructed()); + mTransform = aTransform; + mSurface = aSurface; + } + + gfx::SourceSurface* GetSurface() { + MOZ_ASSERT(IsConstructed()); + return mSurface.get(); + } + + const gfx::Matrix& GetTransform() { + MOZ_ASSERT(IsConstructed()); + return mTransform; + } + + private: + bool IsConstructed() { return !!mSurface; } + + gfx::Matrix mTransform; + RefPtr mSurface; + + AutoMoz2DMaskData(const AutoMoz2DMaskData&) = delete; + AutoMoz2DMaskData& operator=(const AutoMoz2DMaskData&) = delete; +}; + +} // namespace layers +} // namespace mozilla + +#endif // GFX_AUTOMASKDATA_H_ diff --git a/gfx/layers/basic/BasicCanvasLayer.cpp b/gfx/layers/basic/BasicCanvasLayer.cpp new file mode 100644 index 0000000000..5f7399c020 --- /dev/null +++ b/gfx/layers/basic/BasicCanvasLayer.cpp @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "BasicCanvasLayer.h" +#include "basic/BasicLayers.h" // for BasicLayerManager +#include "basic/BasicLayersImpl.h" // for GetEffectiveOperator +#include "CanvasRenderer.h" +#include "mozilla/mozalloc.h" // for operator new +#include "mozilla/Maybe.h" +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsISupportsImpl.h" // for Layer::AddRef, etc +#include "gfx2DGlue.h" +#include "GLScreenBuffer.h" +#include "GLContext.h" +#include "gfxUtils.h" +#include "mozilla/layers/PersistentBufferProvider.h" +#include "client/TextureClientSharedSurface.h" + +class gfxContext; + +using namespace mozilla::gfx; +using namespace mozilla::gl; + +namespace mozilla { +namespace layers { + +void BasicCanvasLayer::Paint(DrawTarget* aDT, const Point& aDeviceOffset, + Layer* aMaskLayer) { + if (IsHidden()) return; + // Ignore IsDirty(). + + MOZ_ASSERT(mCanvasRenderer); + mCanvasRenderer->FirePreTransactionCallback(); + + const auto snapshot = mCanvasRenderer->BorrowSnapshot(); + if (!snapshot) return; + const auto& surface = snapshot->mSurf; + + Maybe oldTM; + if (!mCanvasRenderer->YIsDown()) { + // y-flip + oldTM = Some(aDT->GetTransform()); + aDT->SetTransform(Matrix(*oldTM) + .PreTranslate(0.0f, mBounds.Height()) + .PreScale(1.0f, -1.0f)); + } + + FillRectWithMask( + aDT, aDeviceOffset, Rect(0, 0, mBounds.Width(), mBounds.Height()), + surface, mSamplingFilter, + DrawOptions(GetEffectiveOpacity(), GetEffectiveOperator(this)), + aMaskLayer); + + if (oldTM) { + aDT->SetTransform(*oldTM); + } + + mCanvasRenderer->FireDidTransactionCallback(); + + Painted(); +} + +RefPtr BasicCanvasLayer::CreateCanvasRendererInternal() { + return new CanvasRenderer(); +} + +already_AddRefed BasicLayerManager::CreateCanvasLayer() { + NS_ASSERTION(InConstruction(), "Only allowed in construction phase"); + RefPtr layer = new BasicCanvasLayer(this); + return layer.forget(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/basic/BasicCanvasLayer.h b/gfx/layers/basic/BasicCanvasLayer.h new file mode 100644 index 0000000000..4753acdb58 --- /dev/null +++ b/gfx/layers/basic/BasicCanvasLayer.h @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_BASICCANVASLAYER_H +#define GFX_BASICCANVASLAYER_H + +#include "BasicImplData.h" // for BasicImplData +#include "BasicLayers.h" // for BasicLayerManager +#include "Layers.h" // for CanvasLayer, etc +#include "nsDebug.h" // for NS_ASSERTION +#include "nsRegion.h" // for nsIntRegion + +namespace mozilla { +namespace layers { + +class BasicCanvasLayer : public CanvasLayer, public BasicImplData { + public: + explicit BasicCanvasLayer(BasicLayerManager* aLayerManager) + : CanvasLayer(aLayerManager, static_cast(this)) {} + + void SetVisibleRegion(const LayerIntRegion& aRegion) override { + NS_ASSERTION(BasicManager()->InConstruction(), + "Can only set properties in construction phase"); + CanvasLayer::SetVisibleRegion(aRegion); + } + + void Paint(gfx::DrawTarget* aDT, const gfx::Point& aDeviceOffset, + Layer* aMaskLayer) override; + + protected: + BasicLayerManager* BasicManager() { + return static_cast(mManager); + } + + RefPtr CreateCanvasRendererInternal() override; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/basic/BasicColorLayer.cpp b/gfx/layers/basic/BasicColorLayer.cpp new file mode 100644 index 0000000000..b8a37f7420 --- /dev/null +++ b/gfx/layers/basic/BasicColorLayer.cpp @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "BasicLayersImpl.h" // for FillRectWithMask, etc +#include "Layers.h" // for ColorLayer, etc +#include "BasicImplData.h" // for BasicImplData +#include "BasicLayers.h" // for BasicLayerManager +#include "gfxContext.h" // for gfxContext, etc +#include "gfxRect.h" // for gfxRect +#include "gfx2DGlue.h" +#include "mozilla/mozalloc.h" // for operator new +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsDebug.h" // for NS_ASSERTION +#include "nsISupportsImpl.h" // for Layer::AddRef, etc +#include "nsRect.h" // for mozilla::gfx::IntRect +#include "nsRegion.h" // for nsIntRegion +#include "mozilla/gfx/PathHelpers.h" + +using namespace mozilla::gfx; + +namespace mozilla { +namespace layers { + +class BasicColorLayer : public ColorLayer, public BasicImplData { + public: + explicit BasicColorLayer(BasicLayerManager* aLayerManager) + : ColorLayer(aLayerManager, static_cast(this)) { + MOZ_COUNT_CTOR(BasicColorLayer); + } + + protected: + MOZ_COUNTED_DTOR_OVERRIDE(BasicColorLayer) + + public: + void SetVisibleRegion(const LayerIntRegion& aRegion) override { + NS_ASSERTION(BasicManager()->InConstruction(), + "Can only set properties in construction phase"); + ColorLayer::SetVisibleRegion(aRegion); + } + + void Paint(DrawTarget* aDT, const gfx::Point& aDeviceOffset, + Layer* aMaskLayer) override { + if (IsHidden()) { + return; + } + + Rect snapped(mBounds.X(), mBounds.Y(), mBounds.Width(), mBounds.Height()); + MaybeSnapToDevicePixels(snapped, *aDT, true); + + // Clip drawing in case we're using (unbounded) operator source. + aDT->PushClipRect(snapped); + FillRectWithMask( + aDT, aDeviceOffset, snapped, mColor, + DrawOptions(GetEffectiveOpacity(), GetEffectiveOperator(this)), + aMaskLayer); + aDT->PopClip(); + } + + protected: + BasicLayerManager* BasicManager() { + return static_cast(mManager); + } +}; + +already_AddRefed BasicLayerManager::CreateColorLayer() { + NS_ASSERTION(InConstruction(), "Only allowed in construction phase"); + RefPtr layer = new BasicColorLayer(this); + return layer.forget(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/basic/BasicCompositor.cpp b/gfx/layers/basic/BasicCompositor.cpp new file mode 100644 index 0000000000..a058baf21d --- /dev/null +++ b/gfx/layers/basic/BasicCompositor.cpp @@ -0,0 +1,1252 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "BasicCompositor.h" +#include "BasicLayersImpl.h" // for FillRectWithMask +#include "GeckoProfiler.h" +#include "TextureHostBasic.h" +#include "mozilla/layers/Effects.h" +#include "nsIWidget.h" +#include "gfx2DGlue.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/gfx/Helpers.h" +#include "mozilla/gfx/Swizzle.h" +#include "mozilla/gfx/Tools.h" +#include "mozilla/gfx/ssse3-scaler.h" +#include "mozilla/layers/ImageDataSerializer.h" +#include "mozilla/layers/NativeLayer.h" +#include "mozilla/SSE.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/StaticPrefs_nglayout.h" +#include "gfxPlatform.h" +#include "gfxUtils.h" +#include "YCbCrUtils.h" +#include +#include "ImageContainer.h" +#ifdef MOZ_WIDGET_GTK +# include "mozilla/WidgetUtilsGtk.h" +#endif + +namespace mozilla { +using namespace mozilla::gfx; + +namespace layers { + +class DataTextureSourceBasic : public DataTextureSource, + public TextureSourceBasic { + public: + const char* Name() const override { return "DataTextureSourceBasic"; } + + explicit DataTextureSourceBasic(DataSourceSurface* aSurface) + : mSurface(aSurface), mWrappingExistingData(!!aSurface) {} + + DataTextureSource* AsDataTextureSource() override { + // If the texture wraps someone else's memory we'd rather not use it as + // a DataTextureSource per say (that is call Update on it). + return mWrappingExistingData ? nullptr : this; + } + + TextureSourceBasic* AsSourceBasic() override { return this; } + + gfx::SourceSurface* GetSurface(DrawTarget* aTarget) override { + return mSurface; + } + + SurfaceFormat GetFormat() const override { + return mSurface ? mSurface->GetFormat() : gfx::SurfaceFormat::UNKNOWN; + } + + IntSize GetSize() const override { + return mSurface ? mSurface->GetSize() : gfx::IntSize(0, 0); + } + + bool Update(gfx::DataSourceSurface* aSurface, + nsIntRegion* aDestRegion = nullptr, + gfx::IntPoint* aSrcOffset = nullptr) override { + MOZ_ASSERT(!mWrappingExistingData); + if (mWrappingExistingData) { + return false; + } + mSurface = aSurface; + return true; + } + + void DeallocateDeviceData() override { + mSurface = nullptr; + SetUpdateSerial(0); + } + + public: + RefPtr mSurface; + bool mWrappingExistingData; +}; + +/** + * WrappingTextureSourceYCbCrBasic wraps YUV format BufferTextureHost to defer + * yuv->rgb conversion. The conversion happens when GetSurface is called. + */ +class WrappingTextureSourceYCbCrBasic : public DataTextureSource, + public TextureSourceBasic { + public: + const char* Name() const override { + return "WrappingTextureSourceYCbCrBasic"; + } + + explicit WrappingTextureSourceYCbCrBasic(BufferTextureHost* aTexture) + : mTexture(aTexture), mSize(aTexture->GetSize()), mNeedsUpdate(true) { + mFromYCBCR = true; + } + + DataTextureSource* AsDataTextureSource() override { return this; } + + TextureSourceBasic* AsSourceBasic() override { return this; } + + WrappingTextureSourceYCbCrBasic* AsWrappingTextureSourceYCbCrBasic() + override { + return this; + } + + gfx::SourceSurface* GetSurface(DrawTarget* aTarget) override { + if (mSurface && !mNeedsUpdate) { + return mSurface; + } + MOZ_ASSERT(mTexture); + if (!mTexture) { + return nullptr; + } + + if (!mSurface) { + mSurface = + Factory::CreateDataSourceSurface(mSize, gfx::SurfaceFormat::B8G8R8X8); + } + if (!mSurface) { + return nullptr; + } + MOZ_ASSERT(mTexture->GetBufferDescriptor().type() == + BufferDescriptor::TYCbCrDescriptor); + MOZ_ASSERT(mTexture->GetSize() == mSize); + + mSurface = ImageDataSerializer::DataSourceSurfaceFromYCbCrDescriptor( + mTexture->GetBuffer(), + mTexture->GetBufferDescriptor().get_YCbCrDescriptor(), mSurface); + mNeedsUpdate = false; + return mSurface; + } + + SurfaceFormat GetFormat() const override { + return gfx::SurfaceFormat::B8G8R8X8; + } + + IntSize GetSize() const override { return mSize; } + + virtual bool Update(gfx::DataSourceSurface* aSurface, + nsIntRegion* aDestRegion = nullptr, + gfx::IntPoint* aSrcOffset = nullptr) override { + return false; + } + + void DeallocateDeviceData() override { + mTexture = nullptr; + mSurface = nullptr; + SetUpdateSerial(0); + } + + void Unbind() override { mNeedsUpdate = true; } + + void SetBufferTextureHost(BufferTextureHost* aTexture) override { + mTexture = aTexture; + mNeedsUpdate = true; + } + + void ConvertAndScale(const SurfaceFormat& aDestFormat, + const IntSize& aDestSize, unsigned char* aDestBuffer, + int32_t aStride) { + MOZ_ASSERT(mTexture); + if (!mTexture) { + return; + } + MOZ_ASSERT(mTexture->GetBufferDescriptor().type() == + BufferDescriptor::TYCbCrDescriptor); + MOZ_ASSERT(mTexture->GetSize() == mSize); + ImageDataSerializer::ConvertAndScaleFromYCbCrDescriptor( + mTexture->GetBuffer(), + mTexture->GetBufferDescriptor().get_YCbCrDescriptor(), aDestFormat, + aDestSize, aDestBuffer, aStride); + } + + public: + BufferTextureHost* mTexture; + const gfx::IntSize mSize; + RefPtr mSurface; + bool mNeedsUpdate; +}; + +class BasicAsyncReadbackBuffer final : public AsyncReadbackBuffer { + public: + explicit BasicAsyncReadbackBuffer(const IntSize& aSize) + : AsyncReadbackBuffer(aSize) {} + + bool MapAndCopyInto(DataSourceSurface* aSurface, + const IntSize& aReadSize) const override; + + void TakeSurface(SourceSurface* aSurface) { mSurface = aSurface; } + + private: + RefPtr mSurface; +}; + +bool BasicAsyncReadbackBuffer::MapAndCopyInto(DataSourceSurface* aSurface, + const IntSize& aReadSize) const { + if (!mSurface) { + return false; + } + + MOZ_RELEASE_ASSERT(aReadSize <= aSurface->GetSize()); + RefPtr source = mSurface->GetDataSurface(); + + DataSourceSurface::ScopedMap sourceMap(source, DataSourceSurface::READ); + DataSourceSurface::ScopedMap destMap(aSurface, DataSourceSurface::WRITE); + + return SwizzleData(sourceMap.GetData(), sourceMap.GetStride(), + mSurface->GetFormat(), destMap.GetData(), + destMap.GetStride(), aSurface->GetFormat(), aReadSize); +} + +BasicCompositor::BasicCompositor(CompositorBridgeParent* aParent, + widget::CompositorWidget* aWidget) + : Compositor(aWidget, aParent), + mIsPendingEndRemoteDrawing(false), + mFullWindowRenderTarget(nullptr) { + MOZ_COUNT_CTOR(BasicCompositor); + + // The widget backends may create intermediate Cairo surfaces to deal with + // various window buffers, regardless of actual content backend type, when + // using the basic compositor. Ensure that the buffers will be able to fit + // in or blit with a Cairo surface. + mMaxTextureSize = + std::min(Factory::GetMaxSurfaceSize(gfxVars::ContentBackend()), + Factory::GetMaxSurfaceSize(BackendType::CAIRO)); +} + +BasicCompositor::~BasicCompositor() { MOZ_COUNT_DTOR(BasicCompositor); } + +bool BasicCompositor::Initialize(nsCString* const out_failureReason) { + return mWidget ? mWidget->InitCompositor(this) : false; +}; + +int32_t BasicCompositor::GetMaxTextureSize() const { return mMaxTextureSize; } + +void BasicCompositingRenderTarget::BindRenderTarget() { + if (mClearOnBind) { + mDrawTarget->ClearRect(Rect(GetRect())); + mClearOnBind = false; + } +} + +void BasicCompositor::Destroy() { + if (mIsPendingEndRemoteDrawing) { + // Force to end previous remote drawing. + EndRemoteDrawing(); + MOZ_ASSERT(!mIsPendingEndRemoteDrawing); + } + mWidget->CleanupRemoteDrawing(); + + Compositor::Destroy(); +} + +TextureFactoryIdentifier BasicCompositor::GetTextureFactoryIdentifier() { + TextureFactoryIdentifier ident(LayersBackend::LAYERS_BASIC, + XRE_GetProcessType(), GetMaxTextureSize()); + return ident; +} + +already_AddRefed BasicCompositor::CreateRenderTarget( + const IntRect& aRect, SurfaceInitMode aInit) { + MOZ_ASSERT(!aRect.IsZeroArea(), + "Trying to create a render target of invalid size"); + + if (aRect.IsZeroArea()) { + return nullptr; + } + + RefPtr target = + mRenderTarget->mDrawTarget->CreateSimilarDrawTarget( + aRect.Size(), SurfaceFormat::B8G8R8A8); + + if (!target) { + return nullptr; + } + + RefPtr rt = + new BasicCompositingRenderTarget(target, aRect, aRect.TopLeft()); + + rt->mDrawTarget->SetTransform(Matrix::Translation(-rt->GetOrigin())); + + return rt.forget(); +} + +already_AddRefed +BasicCompositor::CreateRenderTargetFromSource( + const IntRect& aRect, const CompositingRenderTarget* aSource, + const IntPoint& aSourcePoint) { + MOZ_CRASH("GFX: Shouldn't be called!"); + return nullptr; +} + +already_AddRefed +BasicCompositor::CreateRootRenderTarget(DrawTarget* aDrawTarget, + const IntRect& aDrawTargetRect, + const IntRegion& aClearRegion) { + RefPtr rt = new BasicCompositingRenderTarget( + aDrawTarget, aDrawTargetRect, IntPoint()); + + rt->mDrawTarget->SetTransform(Matrix::Translation(-rt->GetOrigin())); + + if (!aClearRegion.IsEmpty()) { + gfx::IntRect clearRect = aClearRegion.GetBounds(); + gfxUtils::ClipToRegion(rt->mDrawTarget, aClearRegion); + rt->mDrawTarget->ClearRect(gfx::Rect(clearRect)); + rt->mDrawTarget->PopClip(); + } + + return rt.forget(); +} + +already_AddRefed BasicCompositor::CreateDataTextureSource( + TextureFlags aFlags) { + RefPtr result = new DataTextureSourceBasic(nullptr); + if (aFlags & TextureFlags::RGB_FROM_YCBCR) { + result->mFromYCBCR = true; + } + return result.forget(); +} + +already_AddRefed +BasicCompositor::CreateDataTextureSourceAround(DataSourceSurface* aSurface) { + RefPtr result = new DataTextureSourceBasic(aSurface); + return result.forget(); +} + +already_AddRefed +BasicCompositor::CreateDataTextureSourceAroundYCbCr(TextureHost* aTexture) { + BufferTextureHost* bufferTexture = aTexture->AsBufferTextureHost(); + MOZ_ASSERT(bufferTexture); + + if (!bufferTexture) { + return nullptr; + } + RefPtr result = + new WrappingTextureSourceYCbCrBasic(bufferTexture); + return result.forget(); +} + +bool BasicCompositor::SupportsEffect(EffectTypes aEffect) { + return aEffect != EffectTypes::YCBCR && + aEffect != EffectTypes::COMPONENT_ALPHA; +} + +bool BasicCompositor::SupportsLayerGeometry() const { + return StaticPrefs::layers_geometry_basic_enabled(); +} + +static RefPtr BuildPathFromPolygon(const RefPtr& aDT, + const gfx::Polygon& aPolygon) { + MOZ_ASSERT(!aPolygon.IsEmpty()); + + RefPtr pathBuilder = aDT->CreatePathBuilder(); + const nsTArray& points = aPolygon.GetPoints(); + + pathBuilder->MoveTo(points[0].As2DPoint()); + + for (size_t i = 1; i < points.Length(); ++i) { + pathBuilder->LineTo(points[i].As2DPoint()); + } + + pathBuilder->Close(); + return pathBuilder->Finish(); +} + +static void DrawSurface(gfx::DrawTarget* aDest, const gfx::Rect& aDestRect, + const gfx::Rect& /* aClipRect */, + const gfx::DeviceColor& aColor, + const gfx::DrawOptions& aOptions, + gfx::SourceSurface* aMask, + const gfx::Matrix* aMaskTransform) { + FillRectWithMask(aDest, aDestRect, aColor, aOptions, aMask, aMaskTransform); +} + +static void DrawSurface(gfx::DrawTarget* aDest, const gfx::Polygon& aPolygon, + const gfx::Rect& aClipRect, + const gfx::DeviceColor& aColor, + const gfx::DrawOptions& aOptions, + gfx::SourceSurface* aMask, + const gfx::Matrix* aMaskTransform) { + RefPtr path = BuildPathFromPolygon(aDest, aPolygon); + FillPathWithMask(aDest, path, aClipRect, aColor, aOptions, aMask, + aMaskTransform); +} + +static void DrawTextureSurface( + gfx::DrawTarget* aDest, const gfx::Rect& aDestRect, + const gfx::Rect& /* aClipRect */, gfx::SourceSurface* aSource, + gfx::SamplingFilter aSamplingFilter, const gfx::DrawOptions& aOptions, + ExtendMode aExtendMode, gfx::SourceSurface* aMask, + const gfx::Matrix* aMaskTransform, const Matrix* aSurfaceTransform) { + FillRectWithMask(aDest, aDestRect, aSource, aSamplingFilter, aOptions, + aExtendMode, aMask, aMaskTransform, aSurfaceTransform); +} + +static void DrawTextureSurface( + gfx::DrawTarget* aDest, const gfx::Polygon& aPolygon, + const gfx::Rect& aClipRect, gfx::SourceSurface* aSource, + gfx::SamplingFilter aSamplingFilter, const gfx::DrawOptions& aOptions, + ExtendMode aExtendMode, gfx::SourceSurface* aMask, + const gfx::Matrix* aMaskTransform, const Matrix* aSurfaceTransform) { + RefPtr path = BuildPathFromPolygon(aDest, aPolygon); + FillPathWithMask(aDest, path, aClipRect, aSource, aSamplingFilter, aOptions, + aExtendMode, aMask, aMaskTransform, aSurfaceTransform); +} + +template +static void DrawSurfaceWithTextureCoords( + gfx::DrawTarget* aDest, const Geometry& aGeometry, + const gfx::Rect& aDestRect, gfx::SourceSurface* aSource, + const gfx::Rect& aTextureCoords, gfx::SamplingFilter aSamplingFilter, + const gfx::DrawOptions& aOptions, gfx::SourceSurface* aMask, + const gfx::Matrix* aMaskTransform) { + if (!aSource) { + gfxWarning() << "DrawSurfaceWithTextureCoords problem " + << gfx::hexa(aSource) << " and " << gfx::hexa(aMask); + return; + } + + // Convert aTextureCoords into aSource's coordinate space + gfxRect sourceRect(aTextureCoords.X() * aSource->GetSize().width, + aTextureCoords.Y() * aSource->GetSize().height, + aTextureCoords.Width() * aSource->GetSize().width, + aTextureCoords.Height() * aSource->GetSize().height); + + // Floating point error can accumulate above and we know our visible region + // is integer-aligned, so round it out. + sourceRect.Round(); + + // Compute a transform that maps sourceRect to aDestRect. + Matrix matrix = gfxUtils::TransformRectToRect( + sourceRect, gfx::IntPoint::Truncate(aDestRect.X(), aDestRect.Y()), + gfx::IntPoint::Truncate(aDestRect.XMost(), aDestRect.Y()), + gfx::IntPoint::Truncate(aDestRect.XMost(), aDestRect.YMost())); + + // Only use REPEAT if aTextureCoords is outside (0, 0, 1, 1). + gfx::Rect unitRect(0, 0, 1, 1); + ExtendMode mode = unitRect.Contains(aTextureCoords) ? ExtendMode::CLAMP + : ExtendMode::REPEAT; + + DrawTextureSurface(aDest, aGeometry, aDestRect, aSource, aSamplingFilter, + aOptions, mode, aMask, aMaskTransform, &matrix); +} + +static void SetupMask(const EffectChain& aEffectChain, DrawTarget* aDest, + const IntPoint& aOffset, + RefPtr& aMaskSurface, + Matrix& aMaskTransform) { + if (aEffectChain.mSecondaryEffects[EffectTypes::MASK]) { + EffectMask* effectMask = static_cast( + aEffectChain.mSecondaryEffects[EffectTypes::MASK].get()); + aMaskSurface = effectMask->mMaskTexture->AsSourceBasic()->GetSurface(aDest); + if (!aMaskSurface) { + gfxWarning() << "Invalid sourceMask effect"; + } + MOZ_ASSERT(effectMask->mMaskTransform.Is2D(), + "How did we end up with a 3D transform here?!"); + aMaskTransform = effectMask->mMaskTransform.As2D(); + aMaskTransform.PostTranslate(-aOffset.x, -aOffset.y); + } +} + +static bool AttemptVideoScale(TextureSourceBasic* aSource, + const SourceSurface* aSourceMask, + gfx::Float aOpacity, CompositionOp aBlendMode, + const TexturedEffect* aTexturedEffect, + const Matrix& aNewTransform, + const gfx::Rect& aRect, + const gfx::Rect& aClipRect, DrawTarget* aDest, + const DrawTarget* aBuffer) { +#ifdef MOZILLA_SSE_HAVE_CPUID_DETECTION + if (!mozilla::supports_ssse3()) return false; + if (aNewTransform + .IsTranslation()) // unscaled painting should take the regular path + return false; + if (aNewTransform.HasNonAxisAlignedTransform() || + aNewTransform.HasNegativeScaling()) + return false; + if (aSourceMask || aOpacity != 1.0f) return false; + if (aBlendMode != CompositionOp::OP_OVER && + aBlendMode != CompositionOp::OP_SOURCE) + return false; + + IntRect dstRect; + // the compiler should know a lot about aNewTransform at this point + // maybe it can do some sophisticated optimization of the following + if (!aNewTransform.TransformBounds(aRect).ToIntRect(&dstRect)) return false; + + IntRect clipRect; + if (!aClipRect.ToIntRect(&clipRect)) return false; + + if (!(aTexturedEffect->mTextureCoords == Rect(0.0f, 0.0f, 1.0f, 1.0f))) + return false; + if (aDest->GetFormat() == SurfaceFormat::R5G6B5_UINT16) return false; + + uint8_t* dstData; + IntSize dstSize; + int32_t dstStride; + SurfaceFormat dstFormat; + if (aDest->LockBits(&dstData, &dstSize, &dstStride, &dstFormat)) { + // If we're not painting to aBuffer the clip will + // be applied later + IntRect fillRect = dstRect; + if (aDest == aBuffer) { + // we need to clip fillRect because LockBits ignores the clip on the aDest + fillRect = fillRect.Intersect(clipRect); + } + + fillRect = fillRect.Intersect(IntRect(IntPoint(0, 0), aDest->GetSize())); + IntPoint offset = fillRect.TopLeft() - dstRect.TopLeft(); + + RefPtr srcSource = + aSource->GetSurface(aDest)->GetDataSurface(); + DataSourceSurface::ScopedMap mapSrc(srcSource, DataSourceSurface::READ); + + bool success = ssse3_scale_data( + (uint32_t*)mapSrc.GetData(), srcSource->GetSize().width, + srcSource->GetSize().height, mapSrc.GetStride() / 4, + ((uint32_t*)dstData) + fillRect.X() + (dstStride / 4) * fillRect.Y(), + dstRect.Width(), dstRect.Height(), dstStride / 4, offset.x, offset.y, + fillRect.Width(), fillRect.Height()); + + aDest->ReleaseBits(dstData); + return success; + } else +#endif // MOZILLA_SSE_HAVE_CPUID_DETECTION + return false; +} + +static bool AttemptVideoConvertAndScale( + TextureSource* aSource, const SourceSurface* aSourceMask, + gfx::Float aOpacity, CompositionOp aBlendMode, + const TexturedEffect* aTexturedEffect, const Matrix& aNewTransform, + const gfx::Rect& aRect, const gfx::Rect& aClipRect, DrawTarget* aDest, + const DrawTarget* aBuffer) { +#if defined(XP_WIN) && defined(_M_X64) + // libyuv does not support SIMD scaling on win 64bit. See Bug 1295927. + return false; +#endif + + WrappingTextureSourceYCbCrBasic* wrappingSource = + aSource->AsWrappingTextureSourceYCbCrBasic(); + if (!wrappingSource) return false; +#ifdef MOZILLA_SSE_HAVE_CPUID_DETECTION + if (!mozilla::supports_ssse3()) // libyuv requests SSSE3 for fast YUV + // conversion. + return false; + if (aNewTransform.HasNonAxisAlignedTransform() || + aNewTransform.HasNegativeScaling()) + return false; + if (aSourceMask || aOpacity != 1.0f) return false; + if (aBlendMode != CompositionOp::OP_OVER && + aBlendMode != CompositionOp::OP_SOURCE) + return false; + + IntRect dstRect; + // the compiler should know a lot about aNewTransform at this point + // maybe it can do some sophisticated optimization of the following + if (!aNewTransform.TransformBounds(aRect).ToIntRect(&dstRect)) return false; + + IntRect clipRect; + if (!aClipRect.ToIntRect(&clipRect)) return false; + + if (!(aTexturedEffect->mTextureCoords == Rect(0.0f, 0.0f, 1.0f, 1.0f))) + return false; + if (aDest->GetFormat() == SurfaceFormat::R5G6B5_UINT16) return false; + + if (aDest == aBuffer && !clipRect.Contains(dstRect)) return false; + if (!IntRect(IntPoint(0, 0), aDest->GetSize()).Contains(dstRect)) + return false; + + uint8_t* dstData; + IntSize dstSize; + int32_t dstStride; + SurfaceFormat dstFormat; + if (aDest->LockBits(&dstData, &dstSize, &dstStride, &dstFormat)) { + wrappingSource->ConvertAndScale( + dstFormat, dstRect.Size(), + dstData + ptrdiff_t(dstRect.X()) * BytesPerPixel(dstFormat) + + ptrdiff_t(dstRect.Y()) * dstStride, + dstStride); + aDest->ReleaseBits(dstData); +# ifdef MOZ_WIDGET_GTK + if (mozilla::widget::IsMainWindowTransparent()) { + gfx::Rect rect(dstRect.X(), dstRect.Y(), dstRect.Width(), + dstRect.Height()); + aDest->FillRect(rect, ColorPattern(DeviceColor(0, 0, 0, 1)), + DrawOptions(1.f, CompositionOp::OP_ADD)); + aDest->Flush(); + } +# endif + return true; + } else +#endif // MOZILLA_SSE_HAVE_CPUID_DETECTION + return false; +} + +void BasicCompositor::DrawQuad(const gfx::Rect& aRect, + const gfx::IntRect& aClipRect, + const EffectChain& aEffectChain, + gfx::Float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::Rect& aVisibleRect) { + DrawGeometry(aRect, aRect, aClipRect, aEffectChain, aOpacity, aTransform, + aVisibleRect, true); +} + +void BasicCompositor::DrawPolygon(const gfx::Polygon& aPolygon, + const gfx::Rect& aRect, + const gfx::IntRect& aClipRect, + const EffectChain& aEffectChain, + gfx::Float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::Rect& aVisibleRect) { + DrawGeometry(aPolygon, aRect, aClipRect, aEffectChain, aOpacity, aTransform, + aVisibleRect, false); +} + +template +void BasicCompositor::DrawGeometry( + const Geometry& aGeometry, const gfx::Rect& aRect, + const gfx::IntRect& aClipRect, const EffectChain& aEffectChain, + gfx::Float aOpacity, const gfx::Matrix4x4& aTransform, + const gfx::Rect& aVisibleRect, const bool aEnableAA) { + RefPtr buffer = mRenderTarget->mDrawTarget; + AutoRestoreTransform autoRestoreTransform(buffer); + + Matrix newTransform; + Rect transformBounds; + Matrix4x4 new3DTransform; + IntPoint offset = mRenderTarget->GetOrigin(); + + // For 2D drawing, |dest| and |buffer| are the same surface. For 3D drawing, + // |dest| is a temporary surface. + RefPtr dest; + + if (aTransform.Is2D()) { + dest = buffer; + newTransform = aTransform.As2D(); + } else { + // Create a temporary surface for the transform. + dest = Factory::CreateDrawTarget(gfxVars::ContentBackend(), + RoundedOut(aRect).Size(), + SurfaceFormat::B8G8R8A8); + if (!dest) { + return; + } + + dest->SetTransform(Matrix::Translation(-aRect.X(), -aRect.Y())); + + // Get the bounds post-transform. + transformBounds = aTransform.TransformAndClipBounds( + aRect, Rect(mRenderTarget->GetRect())); + transformBounds.RoundOut(); + + if (transformBounds.IsEmpty()) { + return; + } + + newTransform = Matrix(); + + // When we apply the 3D transformation, we do it against a temporary + // surface, so undo the coordinate offset. + new3DTransform = aTransform; + new3DTransform.PreTranslate(aRect.X(), aRect.Y(), 0); + } + + // The current transform on buffer is always only a translation by `-offset`. + // aClipRect is relative to mRenderTarget->GetClipSpaceOrigin(). + // For non-root render targets, the clip space origin is equal to `offset`. + // For the root render target, the clip space origin is at (0, 0) and the + // offset can be anywhere. + IntRect clipRectInRenderTargetSpace = + aClipRect + mRenderTarget->GetClipSpaceOrigin(); + if (Maybe rtClip = mRenderTarget->GetClipRect()) { + clipRectInRenderTargetSpace = + clipRectInRenderTargetSpace.Intersect(*rtClip); + } + buffer->PushClipRect(Rect(clipRectInRenderTargetSpace)); + Rect deviceSpaceClipRect(clipRectInRenderTargetSpace - offset); + + newTransform.PostTranslate(-offset.x, -offset.y); + buffer->SetTransform(newTransform); + + RefPtr sourceMask; + Matrix maskTransform; + if (aTransform.Is2D()) { + SetupMask(aEffectChain, dest, offset, sourceMask, maskTransform); + } + + CompositionOp blendMode = CompositionOp::OP_OVER; + if (Effect* effect = + aEffectChain.mSecondaryEffects[EffectTypes::BLEND_MODE].get()) { + blendMode = static_cast(effect)->mBlendMode; + } + + const AntialiasMode aaMode = + aEnableAA ? AntialiasMode::DEFAULT : AntialiasMode::NONE; + + DrawOptions drawOptions(aOpacity, blendMode, aaMode); + + switch (aEffectChain.mPrimaryEffect->mType) { + case EffectTypes::SOLID_COLOR: { + EffectSolidColor* effectSolidColor = + static_cast(aEffectChain.mPrimaryEffect.get()); + + bool unboundedOp = !IsOperatorBoundByMask(blendMode); + if (unboundedOp) { + dest->PushClipRect(aRect); + } + + DrawSurface(dest, aGeometry, aRect, effectSolidColor->mColor, drawOptions, + sourceMask, &maskTransform); + + if (unboundedOp) { + dest->PopClip(); + } + break; + } + case EffectTypes::RGB: { + TexturedEffect* texturedEffect = + static_cast(aEffectChain.mPrimaryEffect.get()); + TextureSourceBasic* source = texturedEffect->mTexture->AsSourceBasic(); + + if (source && texturedEffect->mPremultiplied) { + // we have a fast path for video here + if (source->mFromYCBCR && + AttemptVideoConvertAndScale(texturedEffect->mTexture, sourceMask, + aOpacity, blendMode, texturedEffect, + newTransform, aRect, + deviceSpaceClipRect, dest, buffer)) { + // we succeeded in convert and scaling + } else if (source->mFromYCBCR && !source->GetSurface(dest)) { + gfxWarning() << "Failed to get YCbCr to rgb surface."; + } else if (source->mFromYCBCR && + AttemptVideoScale(source, sourceMask, aOpacity, blendMode, + texturedEffect, newTransform, aRect, + deviceSpaceClipRect, dest, buffer)) { + // we succeeded in scaling + } else { + DrawSurfaceWithTextureCoords( + dest, aGeometry, aRect, source->GetSurface(dest), + texturedEffect->mTextureCoords, texturedEffect->mSamplingFilter, + drawOptions, sourceMask, &maskTransform); + } + } else if (source) { + SourceSurface* srcSurf = source->GetSurface(dest); + if (srcSurf) { + RefPtr srcData = srcSurf->GetDataSurface(); + + // Yes, we re-create the premultiplied data every time. + // This might be better with a cache, eventually. + RefPtr premultData = + gfxUtils::CreatePremultipliedDataSurface(srcData); + + DrawSurfaceWithTextureCoords(dest, aGeometry, aRect, premultData, + texturedEffect->mTextureCoords, + texturedEffect->mSamplingFilter, + drawOptions, sourceMask, &maskTransform); + } + } else { + gfxDevCrash(LogReason::IncompatibleBasicTexturedEffect) + << "Bad for basic with " << texturedEffect->mTexture->Name() + << " and " << gfx::hexa(sourceMask); + } + + break; + } + case EffectTypes::YCBCR: { + MOZ_CRASH("Can't (easily) support component alpha with BasicCompositor!"); + break; + } + case EffectTypes::RENDER_TARGET: { + EffectRenderTarget* effectRenderTarget = + static_cast(aEffectChain.mPrimaryEffect.get()); + RefPtr surface = + static_cast( + effectRenderTarget->mRenderTarget.get()); + RefPtr sourceSurf = surface->mDrawTarget->Snapshot(); + + DrawSurfaceWithTextureCoords(dest, aGeometry, aRect, sourceSurf, + effectRenderTarget->mTextureCoords, + effectRenderTarget->mSamplingFilter, + drawOptions, sourceMask, &maskTransform); + break; + } + case EffectTypes::COMPONENT_ALPHA: { + MOZ_CRASH("Can't (easily) support component alpha with BasicCompositor!"); + break; + } + default: { + MOZ_CRASH("Invalid effect type!"); + break; + } + } + + if (!aTransform.Is2D()) { + dest->Flush(); + + RefPtr destSnapshot = dest->Snapshot(); + + SetupMask(aEffectChain, buffer, offset, sourceMask, maskTransform); + + if (sourceMask) { + RefPtr transformDT = dest->CreateSimilarDrawTarget( + IntSize::Truncate(transformBounds.Width(), transformBounds.Height()), + SurfaceFormat::B8G8R8A8); + new3DTransform.PostTranslate(-transformBounds.X(), -transformBounds.Y(), + 0); + if (transformDT && + transformDT->Draw3DTransformedSurface(destSnapshot, new3DTransform)) { + RefPtr transformSnapshot = transformDT->Snapshot(); + + // Transform the source by it's normal transform, and then the inverse + // of the mask transform so that it's in the mask's untransformed + // coordinate space. + Matrix sourceTransform = newTransform; + sourceTransform.PostTranslate(transformBounds.TopLeft()); + + Matrix inverseMask = maskTransform; + inverseMask.Invert(); + + sourceTransform *= inverseMask; + + SurfacePattern source(transformSnapshot, ExtendMode::CLAMP, + sourceTransform); + + buffer->PushClipRect(transformBounds); + + // Mask in the untransformed coordinate space, and then transform + // by the mask transform to put the result back into destination + // coords. + buffer->SetTransform(maskTransform); + buffer->MaskSurface(source, sourceMask, Point(0, 0)); + + buffer->PopClip(); + } + } else { + buffer->Draw3DTransformedSurface(destSnapshot, new3DTransform); + } + } + + buffer->PopClip(); +} + +void BasicCompositor::ClearRect(const gfx::Rect& aRect) { + mRenderTarget->mDrawTarget->ClearRect(aRect); + + if (mFullWindowRenderTarget) { + mFullWindowRenderTarget->mDrawTarget->ClearRect(aRect); + } +} + +bool BasicCompositor::ReadbackRenderTarget(CompositingRenderTarget* aSource, + AsyncReadbackBuffer* aDest) { + RefPtr snapshot = + static_cast(aSource) + ->mDrawTarget->Snapshot(); + static_cast(aDest)->TakeSurface(snapshot); + return true; +} + +already_AddRefed +BasicCompositor::CreateAsyncReadbackBuffer(const gfx::IntSize& aSize) { + return MakeAndAddRef(aSize); +} + +bool BasicCompositor::BlitRenderTarget(CompositingRenderTarget* aSource, + const gfx::IntSize& aSourceSize, + const gfx::IntSize& aDestSize) { + RefPtr surface = + static_cast(aSource) + ->mDrawTarget->Snapshot(); + mRenderTarget->mDrawTarget->DrawSurface( + surface, Rect(Point(), Size(aDestSize)), Rect(Point(), Size(aSourceSize)), + DrawSurfaceOptions(), DrawOptions(1.0f, CompositionOp::OP_SOURCE)); + return true; +} + +Maybe BasicCompositor::BeginFrameForWindow( + const nsIntRegion& aInvalidRegion, const Maybe& aClipRect, + const IntRect& aRenderBounds, const nsIntRegion& aOpaqueRegion) { + if (mIsPendingEndRemoteDrawing) { + // Force to end previous remote drawing. + EndRemoteDrawing(); + MOZ_ASSERT(!mIsPendingEndRemoteDrawing); + } + + MOZ_RELEASE_ASSERT(mCurrentFrameDest == FrameDestination::NO_CURRENT_FRAME, + "mCurrentFrameDest not restored properly"); + + IntRect rect(IntPoint(), mWidget->GetClientSize().ToUnknownSize()); + + mShouldInvalidateWindow = NeedToRecreateFullWindowRenderTarget(); + + if (mShouldInvalidateWindow) { + mInvalidRegion = rect; + } else { + IntRegion invalidRegionSafe; + // Sometimes the invalid region is larger than we want to draw. + invalidRegionSafe.And(aInvalidRegion, rect); + + mInvalidRegion = invalidRegionSafe; + } + + LayoutDeviceIntRegion invalidRegion = + LayoutDeviceIntRegion::FromUnknownRegion(mInvalidRegion); + BufferMode bufferMode = BufferMode::BUFFERED; + // StartRemoteDrawingInRegion can mutate invalidRegion. + RefPtr dt = + mWidget->StartRemoteDrawingInRegion(invalidRegion, &bufferMode); + if (!dt) { + return Nothing(); + } + if (invalidRegion.IsEmpty()) { + mWidget->EndRemoteDrawingInRegion(dt, invalidRegion); + return Nothing(); + } + + mInvalidRegion = invalidRegion.ToUnknownRegion(); + IntRegion clearRegion; + clearRegion.Sub(mInvalidRegion, aOpaqueRegion); + + RefPtr target; + if (bufferMode == BufferMode::BUFFERED) { + // Buffer drawing via a back buffer. + IntRect backBufferRect = mInvalidRegion.GetBounds(); + bool isCleared = false; + RefPtr backBuffer = + mWidget->GetBackBufferDrawTarget(dt, backBufferRect, &isCleared); + if (!backBuffer) { + mWidget->EndRemoteDrawingInRegion(dt, invalidRegion); + return Nothing(); + } + // Set up a render target for drawirg to the back buffer. + target = CreateRootRenderTarget(backBuffer, backBufferRect, + isCleared ? IntRegion() : clearRegion); + mFrontBuffer = dt; + // We will copy the drawing from the back buffer into mFrontBuffer (the + // widget) in EndRemoteDrawing(). + } else { + // In BufferMode::BUFFER_NONE, the DrawTarget returned by + // StartRemoteDrawingInRegion can cover different rectangles in window + // space. It can either cover the entire window, or it can cover just the + // invalid region. We discern between the two cases by comparing the + // DrawTarget's size with the invalild region's size. + IntRect invalidRect = mInvalidRegion.GetBounds(); + IntPoint dtLocation = dt->GetSize() == invalidRect.Size() + ? invalidRect.TopLeft() + : IntPoint(0, 0); + IntRect dtBounds(dtLocation, dt->GetSize()); + + // Set up a render target for drawing directly to dt. + target = CreateRootRenderTarget(dt, dtBounds, clearRegion); + } + + mCurrentFrameDest = FrameDestination::WINDOW; + + MOZ_RELEASE_ASSERT(target); + SetRenderTarget(target); + + gfxUtils::ClipToRegion(mRenderTarget->mDrawTarget, mInvalidRegion); + + mRenderTarget->mDrawTarget->PushClipRect(Rect(aClipRect.valueOr(rect))); + + return Some(rect); +} + +Maybe BasicCompositor::BeginFrameForTarget( + const nsIntRegion& aInvalidRegion, const Maybe& aClipRect, + const IntRect& aRenderBounds, const nsIntRegion& aOpaqueRegion, + DrawTarget* aTarget, const IntRect& aTargetBounds) { + if (mIsPendingEndRemoteDrawing) { + // Force to end previous remote drawing. + EndRemoteDrawing(); + MOZ_ASSERT(!mIsPendingEndRemoteDrawing); + } + + MOZ_RELEASE_ASSERT(mCurrentFrameDest == FrameDestination::NO_CURRENT_FRAME, + "mCurrentFrameDest not restored properly"); + + mInvalidRegion.And(aInvalidRegion, aTargetBounds); + MOZ_RELEASE_ASSERT(!mInvalidRegion.IsEmpty()); + + IntRegion clearRegion; + clearRegion.Sub(mInvalidRegion, aOpaqueRegion); + + // Set up a render target for drawing directly to aTarget. + RefPtr target = + CreateRootRenderTarget(aTarget, aTargetBounds, clearRegion); + MOZ_RELEASE_ASSERT(target); + SetRenderTarget(target); + + mCurrentFrameDest = FrameDestination::TARGET; + + gfxUtils::ClipToRegion(mRenderTarget->mDrawTarget, mInvalidRegion); + + mRenderTarget->mDrawTarget->PushClipRect( + Rect(aClipRect.valueOr(aTargetBounds))); + + return Some(aTargetBounds); +} + +void BasicCompositor::BeginFrameForNativeLayers() { + if (mIsPendingEndRemoteDrawing) { + // Force to end previous remote drawing. + EndRemoteDrawing(); + MOZ_ASSERT(!mIsPendingEndRemoteDrawing); + } + + MOZ_RELEASE_ASSERT(mCurrentFrameDest == FrameDestination::NO_CURRENT_FRAME, + "mCurrentFrameDest not restored properly"); + + mShouldInvalidateWindow = NeedToRecreateFullWindowRenderTarget(); + + // Make a 1x1 dummy render target so that GetCurrentRenderTarget() returns + // something non-null even outside of calls to + // Begin/EndRenderingToNativeLayer. + if (!mNativeLayersReferenceRT) { + RefPtr dt = Factory::CreateDrawTarget( + gfxVars::ContentBackend(), IntSize(1, 1), SurfaceFormat::B8G8R8A8); + mNativeLayersReferenceRT = + new BasicCompositingRenderTarget(dt, IntRect(0, 0, 1, 1), IntPoint()); + } + SetRenderTarget(mNativeLayersReferenceRT); + + mCurrentFrameDest = FrameDestination::NATIVE_LAYERS; +} + +Maybe BasicCompositor::BeginRenderingToNativeLayer( + const nsIntRegion& aInvalidRegion, const Maybe& aClipRect, + const nsIntRegion& aOpaqueRegion, NativeLayer* aNativeLayer) { + IntRect rect = aNativeLayer->GetRect(); + + // We only support a single invalid rect per native layer. This is a + // limitation that's imposed by the AttemptVideo[ConvertAnd]Scale functions, + // which require knowing the combined clip in DrawGeometry and can only handle + // a single clip rect. + IntRect invalidRect; + if (mShouldInvalidateWindow) { + invalidRect = rect; + } else { + IntRegion invalidRegion; + invalidRegion.And(aInvalidRegion, rect); + if (invalidRegion.IsEmpty()) { + return Nothing(); + } + invalidRect = invalidRegion.GetBounds(); + } + mInvalidRegion = invalidRect; + + RefPtr target; + aNativeLayer->SetSurfaceIsFlipped(false); + IntRegion invalidRelativeToLayer = invalidRect - rect.TopLeft(); + RefPtr dt = aNativeLayer->NextSurfaceAsDrawTarget( + gfx::IntRect({}, aNativeLayer->GetSize()), invalidRelativeToLayer, + BackendType::SKIA); + if (!dt) { + return Nothing(); + } + mCurrentNativeLayer = aNativeLayer; + IntRegion clearRegion; + clearRegion.Sub(mInvalidRegion, aOpaqueRegion); + // Set up a render target for drawing directly to dt. + target = CreateRootRenderTarget(dt, rect, clearRegion); + + MOZ_RELEASE_ASSERT(target); + SetRenderTarget(target); + + IntRect clipRect = invalidRect; + if (aClipRect) { + clipRect = clipRect.Intersect(*aClipRect); + } + mRenderTarget->SetClipRect(Some(clipRect)); + + return Some(rect); +} + +void BasicCompositor::EndRenderingToNativeLayer() { + mRenderTarget->SetClipRect(Nothing()); + SetRenderTarget(mNativeLayersReferenceRT); + + MOZ_RELEASE_ASSERT(mCurrentNativeLayer); + mCurrentNativeLayer->NotifySurfaceReady(); + mCurrentNativeLayer = nullptr; +} + +void BasicCompositor::EndFrame() { + Compositor::EndFrame(); + + if (mCurrentFrameDest != FrameDestination::NATIVE_LAYERS) { + // Pop aClipRect/bounds rect + mRenderTarget->mDrawTarget->PopClip(); + + if (StaticPrefs::nglayout_debug_widget_update_flashing()) { + float r = float(rand()) / float(RAND_MAX); + float g = float(rand()) / float(RAND_MAX); + float b = float(rand()) / float(RAND_MAX); + // We're still clipped to mInvalidRegion, so just fill the bounds. + mRenderTarget->mDrawTarget->FillRect( + Rect(mInvalidRegion.GetBounds()), + ColorPattern(DeviceColor(r, g, b, 0.2f))); + } + + // Pop aInvalidRegion + mRenderTarget->mDrawTarget->PopClip(); + } + + // Reset the translation that was applied in CreateRootRenderTarget. + mRenderTarget->mDrawTarget->SetTransform(gfx::Matrix()); + + switch (mCurrentFrameDest) { + case FrameDestination::NO_CURRENT_FRAME: + MOZ_CRASH("EndFrame being called without BeginFrameForXYZ?"); + break; + case FrameDestination::WINDOW: + TryToEndRemoteDrawing(); + break; + case FrameDestination::TARGET: + case FrameDestination::NATIVE_LAYERS: + mRenderTarget = nullptr; + break; + } + mCurrentFrameDest = FrameDestination::NO_CURRENT_FRAME; + mShouldInvalidateWindow = false; +} + +RefPtr BasicCompositor::GetSurfacePoolHandle() { +#ifdef XP_MACOSX + if (!mSurfacePoolHandle) { + mSurfacePoolHandle = SurfacePool::Create(0)->GetHandleForGL(nullptr); + } +#endif + return mSurfacePoolHandle; +} + +void BasicCompositor::TryToEndRemoteDrawing() { + if (mIsDestroyed || !mRenderTarget) { + return; + } + + // If it is not a good time to call EndRemoteDrawing, defer it. + if (NeedsToDeferEndRemoteDrawing()) { + mIsPendingEndRemoteDrawing = true; + + const uint32_t retryMs = 2; + RefPtr self = this; + RefPtr runnable = + NS_NewRunnableFunction("layers::BasicCompositor::TryToEndRemoteDrawing", + [self]() { self->TryToEndRemoteDrawing(); }); + GetCurrentSerialEventTarget()->DelayedDispatch(runnable.forget(), retryMs); + } else { + EndRemoteDrawing(); + } +} + +void BasicCompositor::EndRemoteDrawing() { + if (mIsDestroyed || !mRenderTarget) { + return; + } + + if (mFrontBuffer) { + // This is the case where we have a back buffer for BufferMode::BUFFERED + // drawing. + // mRenderTarget->mDrawTarget is the back buffer. + // mFrontBuffer is always located at (0, 0) in window space. + RefPtr source = mWidget->EndBackBufferDrawing(); + IntPoint srcOffset = mRenderTarget->GetOrigin(); + + // The source DrawTarget is clipped to the invalidation region, so we have + // to copy the individual rectangles in the region or else we'll draw + // garbage pixels. + // CopySurface ignores both the transform and the clip. + for (auto iter = mInvalidRegion.RectIter(); !iter.Done(); iter.Next()) { + const IntRect& r = iter.Get(); + mFrontBuffer->CopySurface(source, r - srcOffset, r.TopLeft()); + } + + mWidget->EndRemoteDrawingInRegion( + mFrontBuffer, LayoutDeviceIntRegion::FromUnknownRegion(mInvalidRegion)); + + mFrontBuffer = nullptr; + } else { + mWidget->EndRemoteDrawingInRegion( + mRenderTarget->mDrawTarget, + LayoutDeviceIntRegion::FromUnknownRegion(mInvalidRegion)); + } + + mRenderTarget = nullptr; + mIsPendingEndRemoteDrawing = false; +} + +void BasicCompositor::NormalDrawingDone() { + // Now is a good time to update mFullWindowRenderTarget. + + if (!ShouldRecordFrames()) { + // If we are no longer recording a profile, we can drop the render target if + // it exists. + mFullWindowRenderTarget = nullptr; + return; + } + + if (NeedToRecreateFullWindowRenderTarget()) { + // We have either (1) just started recording and not yet allocated a + // buffer or (2) are already recording and have resized the window. In + // either case, we need a new render target. + IntRect windowRect(IntPoint(0, 0), + mWidget->GetClientSize().ToUnknownSize()); + RefPtr drawTarget = + mRenderTarget->mDrawTarget->CreateSimilarDrawTarget( + windowRect.Size(), mRenderTarget->mDrawTarget->GetFormat()); + + mFullWindowRenderTarget = + new BasicCompositingRenderTarget(drawTarget, windowRect, IntPoint()); + } + + RefPtr source = mRenderTarget->mDrawTarget->Snapshot(); + IntPoint srcOffset = mRenderTarget->GetOrigin(); + for (auto iter = mInvalidRegion.RectIter(); !iter.Done(); iter.Next()) { + const IntRect& r = iter.Get(); + mFullWindowRenderTarget->mDrawTarget->CopySurface(source, r - srcOffset, + r.TopLeft()); + } + mFullWindowRenderTarget->mDrawTarget->Flush(); +} + +bool BasicCompositor::NeedsToDeferEndRemoteDrawing() { + return mFrontBuffer && mWidget->NeedsToDeferEndRemoteDrawing(); +} + +void BasicCompositor::FinishPendingComposite() { EndRemoteDrawing(); } + +bool BasicCompositor::NeedToRecreateFullWindowRenderTarget() const { + if (!ShouldRecordFrames()) { + return false; + } + if (!mFullWindowRenderTarget) { + return true; + } + IntSize windowSize = mWidget->GetClientSize().ToUnknownSize(); + return mFullWindowRenderTarget->mDrawTarget->GetSize() != windowSize; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/basic/BasicCompositor.h b/gfx/layers/basic/BasicCompositor.h new file mode 100644 index 0000000000..7044088e0e --- /dev/null +++ b/gfx/layers/basic/BasicCompositor.h @@ -0,0 +1,240 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_BASICCOMPOSITOR_H +#define MOZILLA_GFX_BASICCOMPOSITOR_H + +#include "mozilla/layers/Compositor.h" +#include "mozilla/layers/TextureHost.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Triangle.h" +#include "mozilla/gfx/Polygon.h" + +namespace mozilla { +namespace layers { + +class BasicCompositingRenderTarget : public CompositingRenderTarget { + public: + BasicCompositingRenderTarget(gfx::DrawTarget* aDrawTarget, + const gfx::IntRect& aRect, + const gfx::IntPoint& aClipSpaceOrigin) + : CompositingRenderTarget(aRect.TopLeft()), + mDrawTarget(aDrawTarget), + mSize(aRect.Size()), + mClipSpaceOrigin(aClipSpaceOrigin) {} + + const char* Name() const override { return "BasicCompositingRenderTarget"; } + + gfx::IntSize GetSize() const override { return mSize; } + + // The point that DrawGeometry's aClipRect is relative to. Will be (0, 0) for + // root render targets and equal to GetOrigin() for non-root render targets. + gfx::IntPoint GetClipSpaceOrigin() const { return mClipSpaceOrigin; } + + // In render target coordinates, i.e. the same space as GetOrigin(). + // NOT relative to mClipSpaceOrigin! + void SetClipRect(const Maybe& aRect) { mClipRect = aRect; } + const Maybe& GetClipRect() const { return mClipRect; } + + void BindRenderTarget(); + + gfx::SurfaceFormat GetFormat() const override { + return mDrawTarget ? mDrawTarget->GetFormat() + : gfx::SurfaceFormat(gfx::SurfaceFormat::UNKNOWN); + } + + RefPtr mDrawTarget; + gfx::IntSize mSize; + gfx::IntPoint mClipSpaceOrigin; + Maybe mClipRect; +}; + +class BasicCompositor : public Compositor { + public: + BasicCompositor(CompositorBridgeParent* aParent, + widget::CompositorWidget* aWidget); + + protected: + virtual ~BasicCompositor(); + + public: + BasicCompositor* AsBasicCompositor() override { return this; } + + bool Initialize(nsCString* const out_failureReason) override; + + void Destroy() override; + + TextureFactoryIdentifier GetTextureFactoryIdentifier() override; + + already_AddRefed CreateRenderTarget( + const gfx::IntRect& aRect, SurfaceInitMode aInit) override; + + already_AddRefed CreateRenderTargetFromSource( + const gfx::IntRect& aRect, const CompositingRenderTarget* aSource, + const gfx::IntPoint& aSourcePoint) override; + + virtual already_AddRefed CreateRootRenderTarget( + gfx::DrawTarget* aDrawTarget, const gfx::IntRect& aDrawTargetRect, + const gfx::IntRegion& aClearRegion); + + already_AddRefed CreateDataTextureSource( + TextureFlags aFlags = TextureFlags::NO_FLAGS) override; + + already_AddRefed CreateDataTextureSourceAround( + gfx::DataSourceSurface* aSurface) override; + + already_AddRefed CreateDataTextureSourceAroundYCbCr( + TextureHost* aTexture) override; + + bool SupportsEffect(EffectTypes aEffect) override; + + bool SupportsLayerGeometry() const override; + + bool ReadbackRenderTarget(CompositingRenderTarget* aSource, + AsyncReadbackBuffer* aDest) override; + + already_AddRefed CreateAsyncReadbackBuffer( + const gfx::IntSize& aSize) override; + + bool BlitRenderTarget(CompositingRenderTarget* aSource, + const gfx::IntSize& aSourceSize, + const gfx::IntSize& aDestSize) override; + + void SetRenderTarget(CompositingRenderTarget* aSource) override { + mRenderTarget = static_cast(aSource); + mRenderTarget->BindRenderTarget(); + } + + already_AddRefed GetWindowRenderTarget() + const override { + return do_AddRef(mFullWindowRenderTarget); + } + + already_AddRefed GetCurrentRenderTarget() + const override { + return do_AddRef(mRenderTarget); + } + + void DrawQuad(const gfx::Rect& aRect, const gfx::IntRect& aClipRect, + const EffectChain& aEffectChain, gfx::Float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::Rect& aVisibleRect) override; + + void ClearRect(const gfx::Rect& aRect) override; + + Maybe BeginFrameForWindow( + const nsIntRegion& aInvalidRegion, const Maybe& aClipRect, + const gfx::IntRect& aRenderBounds, + const nsIntRegion& aOpaqueRegion) override; + + Maybe BeginFrameForTarget( + const nsIntRegion& aInvalidRegion, const Maybe& aClipRect, + const gfx::IntRect& aRenderBounds, const nsIntRegion& aOpaqueRegion, + gfx::DrawTarget* aTarget, const gfx::IntRect& aTargetBounds) override; + + void BeginFrameForNativeLayers() override; + + Maybe BeginRenderingToNativeLayer( + const nsIntRegion& aInvalidRegion, const Maybe& aClipRect, + const nsIntRegion& aOpaqueRegion, NativeLayer* aNativeLayer) override; + + void EndRenderingToNativeLayer() override; + + void NormalDrawingDone() override; + void EndFrame() override; + + RefPtr GetSurfacePoolHandle() override; + + bool SupportsPartialTextureUpdate() override { return true; } + bool CanUseCanvasLayerForSize(const gfx::IntSize& aSize) override { + return true; + } + int32_t GetMaxTextureSize() const override; + void SetDestinationSurfaceSize(const gfx::IntSize& aSize) override {} + + void MakeCurrent(MakeCurrentFlags aFlags = 0) override {} + +#ifdef MOZ_DUMP_PAINTING + const char* Name() const override { return "Basic"; } +#endif // MOZ_DUMP_PAINTING + + LayersBackend GetBackendType() const override { + return LayersBackend::LAYERS_BASIC; + } + + bool IsPendingComposite() override { return mIsPendingEndRemoteDrawing; } + + void FinishPendingComposite() override; + + private: + template + void DrawGeometry(const Geometry& aGeometry, const gfx::Rect& aRect, + const gfx::IntRect& aClipRect, + const EffectChain& aEffectChain, gfx::Float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::Rect& aVisibleRect, const bool aEnableAA); + + void DrawPolygon(const gfx::Polygon& aPolygon, const gfx::Rect& aRect, + const gfx::IntRect& aClipRect, + const EffectChain& aEffectChain, gfx::Float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::Rect& aVisibleRect) override; + + void TryToEndRemoteDrawing(); + void EndRemoteDrawing(); + + bool NeedsToDeferEndRemoteDrawing(); + + bool NeedToRecreateFullWindowRenderTarget() const; + + // When rendering to a back buffer, this is the front buffer that the contents + // of the back buffer need to be copied to. Only non-null between + // BeginFrameForWindow and EndRemoteDrawing, and only when using a back + // buffer. + RefPtr mFrontBuffer; + + // The current render target for drawing + RefPtr mRenderTarget; + + // The native layer that we're currently rendering to, if any. + // Non-null only between BeginFrameForWindow and EndFrame if + // BeginFrameForWindow has been called with a non-null aNativeLayer. + RefPtr mCurrentNativeLayer; + + RefPtr mSurfacePoolHandle; + + gfx::IntRegion mInvalidRegion; + + uint32_t mMaxTextureSize; + bool mIsPendingEndRemoteDrawing; + bool mShouldInvalidateWindow = false; + + // Where the current frame is being rendered to. + enum class FrameDestination : uint8_t { + NO_CURRENT_FRAME, // before BeginFrameForXYZ or after EndFrame + WINDOW, // between BeginFrameForWindow and EndFrame + TARGET, // between BeginFrameForTarget and EndFrame + NATIVE_LAYERS // between BeginFrameForNativeLayers and EndFrame + }; + FrameDestination mCurrentFrameDest = FrameDestination::NO_CURRENT_FRAME; + + // mDrawTarget will not be the full window on all platforms. We therefore need + // to keep a full window render target around when we are capturing + // screenshots on those platforms. + RefPtr mFullWindowRenderTarget; + + // The 1x1 dummy render target that's the "current" render target between + // BeginFrameForNativeLayers and EndFrame but outside pairs of + // Begin/EndRenderingToNativeLayer. Created on demand. + RefPtr mNativeLayersReferenceRT; +}; + +BasicCompositor* AssertBasicCompositor(Compositor* aCompositor); + +} // namespace layers +} // namespace mozilla + +#endif /* MOZILLA_GFX_BASICCOMPOSITOR_H */ diff --git a/gfx/layers/basic/BasicContainerLayer.cpp b/gfx/layers/basic/BasicContainerLayer.cpp new file mode 100644 index 0000000000..7cb4697d9e --- /dev/null +++ b/gfx/layers/basic/BasicContainerLayer.cpp @@ -0,0 +1,162 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "BasicContainerLayer.h" +#include // for int32_t +#include "BasicLayersImpl.h" // for ToData +#include "basic/BasicImplData.h" // for BasicImplData +#include "basic/BasicLayers.h" // for BasicLayerManager +#include "mozilla/gfx/BaseRect.h" // for BaseRect +#include "mozilla/mozalloc.h" // for operator new +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsISupportsImpl.h" // for Layer::AddRef, etc +#include "nsPoint.h" // for nsIntPoint +#include "nsRegion.h" // for nsIntRegion +#include "ReadbackProcessor.h" + +using namespace mozilla::gfx; + +namespace mozilla { +namespace layers { + +BasicContainerLayer::~BasicContainerLayer() { + ContainerLayer::RemoveAllChildren(); + MOZ_COUNT_DTOR(BasicContainerLayer); +} + +void BasicContainerLayer::ComputeEffectiveTransforms( + const Matrix4x4& aTransformToSurface) { + // We push groups for container layers if we need to, which always + // are aligned in device space, so it doesn't really matter how we snap + // containers. + Matrix residual; + Matrix4x4 transformToSurface = aTransformToSurface; + bool participate3DCtx = Extend3DContext() || Is3DContextLeaf(); + if (!participate3DCtx && GetContentFlags() & CONTENT_BACKFACE_HIDDEN) { + // For backface-hidden layers + transformToSurface.ProjectTo2D(); + } + Matrix4x4 idealTransform = GetLocalTransform() * transformToSurface; + if (!participate3DCtx && !(GetContentFlags() & CONTENT_BACKFACE_HIDDEN)) { + // For non-backface-hidden layers, + // 3D components are required to handle CONTENT_BACKFACE_HIDDEN. + idealTransform.ProjectTo2D(); + } + + if (!idealTransform.CanDraw2D()) { + if (!Extend3DContext()) { + mEffectiveTransform = idealTransform; + ComputeEffectiveTransformsForChildren(Matrix4x4()); + ComputeEffectiveTransformForMaskLayers(Matrix4x4()); + mUseIntermediateSurface = true; + return; + } + + mEffectiveTransform = idealTransform; + ComputeEffectiveTransformsForChildren(idealTransform); + ComputeEffectiveTransformForMaskLayers(idealTransform); + mUseIntermediateSurface = false; + return; + } + + // With 2D transform or extended 3D context. + + Layer* child = GetFirstChild(); + bool hasSingleBlendingChild = false; + if (!HasMultipleChildren() && child) { + hasSingleBlendingChild = child->GetMixBlendMode() != CompositionOp::OP_OVER; + } + + /* If we have a single childand it is not blending,, it can just inherit our + * opacity, otherwise we need a PushGroup and we need to mark ourselves as + * using an intermediate surface so our children don't inherit our opacity via + * GetEffectiveOpacity. Having a mask layer always forces our own push group + * Having a blend mode also always forces our own push group + */ + mUseIntermediateSurface = + GetMaskLayer() || GetForceIsolatedGroup() || + (GetMixBlendMode() != CompositionOp::OP_OVER && HasMultipleChildren()) || + (GetEffectiveOpacity() != 1.0 && + ((HasMultipleChildren() && !Extend3DContext()) || + hasSingleBlendingChild)); + + mEffectiveTransform = + !mUseIntermediateSurface + ? idealTransform + : (!(GetContentFlags() & CONTENT_BACKFACE_HIDDEN) + ? SnapTransformTranslation(idealTransform, &residual) + : SnapTransformTranslation3D(idealTransform, &residual)); + Matrix4x4 childTransformToSurface = + (!mUseIntermediateSurface || + (mUseIntermediateSurface && !Extend3DContext() /* 2D */)) + ? idealTransform + : Matrix4x4::From2D(residual); + ComputeEffectiveTransformsForChildren(childTransformToSurface); + + ComputeEffectiveTransformForMaskLayers(aTransformToSurface); +} + +bool BasicContainerLayer::ChildrenPartitionVisibleRegion( + const gfx::IntRect& aInRect) { + Matrix transform; + if (!GetEffectiveTransform().CanDraw2D(&transform) || + ThebesMatrix(transform).HasNonIntegerTranslation()) + return false; + + nsIntPoint offset(int32_t(transform._31), int32_t(transform._32)); + gfx::IntRect rect = aInRect.Intersect( + GetLocalVisibleRegion().GetBounds().ToUnknownRect() + offset); + nsIntRegion covered; + + for (Layer* l = mFirstChild; l; l = l->GetNextSibling()) { + if (ToData(l)->IsHidden()) continue; + + Matrix childTransform; + if (!l->GetEffectiveTransform().CanDraw2D(&childTransform) || + ThebesMatrix(childTransform).HasNonIntegerTranslation() || + l->GetEffectiveOpacity() != 1.0) + return false; + nsIntRegion childRegion = l->GetLocalVisibleRegion().ToUnknownRegion(); + childRegion.MoveBy(int32_t(childTransform._31), + int32_t(childTransform._32)); + childRegion.And(childRegion, rect); + if (l->GetClipRect()) { + childRegion.And(childRegion, l->GetClipRect()->ToUnknownRect() + offset); + } + nsIntRegion intersection; + intersection.And(covered, childRegion); + if (!intersection.IsEmpty()) return false; + covered.Or(covered, childRegion); + } + + return covered.Contains(rect); +} + +void BasicContainerLayer::Validate( + LayerManager::DrawPaintedLayerCallback aCallback, void* aCallbackData, + ReadbackProcessor* aReadback) { + ReadbackProcessor readback; + if (BasicManager()->IsRetained()) { + readback.BuildUpdates(this); + } + for (Layer* l = mFirstChild; l; l = l->GetNextSibling()) { + BasicImplData* data = ToData(l); + data->Validate(aCallback, aCallbackData, &readback); + if (l->GetMaskLayer()) { + data = ToData(l->GetMaskLayer()); + data->Validate(aCallback, aCallbackData, nullptr); + } + } +} + +already_AddRefed BasicLayerManager::CreateContainerLayer() { + NS_ASSERTION(InConstruction(), "Only allowed in construction phase"); + RefPtr layer = new BasicContainerLayer(this); + return layer.forget(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/basic/BasicContainerLayer.h b/gfx/layers/basic/BasicContainerLayer.h new file mode 100644 index 0000000000..07a3183a54 --- /dev/null +++ b/gfx/layers/basic/BasicContainerLayer.h @@ -0,0 +1,103 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_BASICCONTAINERLAYER_H +#define GFX_BASICCONTAINERLAYER_H + +#include "BasicImplData.h" // for BasicImplData +#include "BasicLayers.h" // for BasicLayerManager +#include "Layers.h" // for Layer, ContainerLayer +#include "nsDebug.h" // for NS_ASSERTION +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR +#include "nsISupportsUtils.h" // for NS_ADDREF, NS_RELEASE +#include "mozilla/gfx/Rect.h" + +namespace mozilla { +namespace layers { + +class BasicContainerLayer : public ContainerLayer, public BasicImplData { + public: + explicit BasicContainerLayer(BasicLayerManager* aManager) + : ContainerLayer(aManager, static_cast(this)) { + MOZ_COUNT_CTOR(BasicContainerLayer); + mSupportsComponentAlphaChildren = true; + } + + protected: + virtual ~BasicContainerLayer(); + + public: + void SetVisibleRegion(const LayerIntRegion& aRegion) override { + NS_ASSERTION(BasicManager()->InConstruction(), + "Can only set properties in construction phase"); + ContainerLayer::SetVisibleRegion(aRegion); + } + bool InsertAfter(Layer* aChild, Layer* aAfter) override { + if (!BasicManager()->InConstruction()) { + NS_ERROR("Can only set properties in construction phase"); + return false; + } + return ContainerLayer::InsertAfter(aChild, aAfter); + } + + bool RemoveChild(Layer* aChild) override { + if (!BasicManager()->InConstruction()) { + NS_ERROR("Can only set properties in construction phase"); + return false; + } + return ContainerLayer::RemoveChild(aChild); + } + + bool RepositionChild(Layer* aChild, Layer* aAfter) override { + if (!BasicManager()->InConstruction()) { + NS_ERROR("Can only set properties in construction phase"); + return false; + } + return ContainerLayer::RepositionChild(aChild, aAfter); + } + + void ComputeEffectiveTransforms( + const gfx::Matrix4x4& aTransformToSurface) override; + + /** + * Returns true when: + * a) no (non-hidden) childrens' visible areas overlap in + * (aInRect intersected with this layer's visible region). + * b) the (non-hidden) childrens' visible areas cover + * (aInRect intersected with this layer's visible region). + * c) this layer and all (non-hidden) children have transforms that are + * translations by integers. aInRect is in the root coordinate system. Child + * layers with opacity do not contribute to the covered area in check b). This + * method can be conservative; it's OK to return false under any + * circumstances. + */ + bool ChildrenPartitionVisibleRegion(const gfx::IntRect& aInRect); + + void ForceIntermediateSurface() { mUseIntermediateSurface = true; } + + void SetSupportsComponentAlphaChildren(bool aSupports) { + mSupportsComponentAlphaChildren = aSupports; + } + + void Validate(LayerManager::DrawPaintedLayerCallback aCallback, + void* aCallbackData, ReadbackProcessor* aReadback) override; + + /** + * We don't really have a hard restriction for max layer size, but we pick + * 4096 to avoid excessive memory usage. + */ + int32_t GetMaxLayerSize() override { return 4096; } + + protected: + BasicLayerManager* BasicManager() { + return static_cast(mManager); + } +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/basic/BasicImageLayer.cpp b/gfx/layers/basic/BasicImageLayer.cpp new file mode 100644 index 0000000000..d1b392154f --- /dev/null +++ b/gfx/layers/basic/BasicImageLayer.cpp @@ -0,0 +1,108 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "BasicLayersImpl.h" // for FillRectWithMask, etc +#include "ImageContainer.h" // for AutoLockImage, etc +#include "ImageLayers.h" // for ImageLayer +#include "Layers.h" // for Layer (ptr only), etc +#include "basic/BasicImplData.h" // for BasicImplData +#include "basic/BasicLayers.h" // for BasicLayerManager +#include "mozilla/mozalloc.h" // for operator new +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsDebug.h" // for NS_ASSERTION +#include "nsISupportsImpl.h" // for gfxPattern::Release, etc +#include "nsRect.h" // for mozilla::gfx::IntRect +#include "nsRegion.h" // for nsIntRegion +#include "mozilla/gfx/Point.h" // for IntSize + +using namespace mozilla::gfx; + +namespace mozilla::layers { + +class BasicImageLayer : public ImageLayer, public BasicImplData { + public: + explicit BasicImageLayer(BasicLayerManager* aLayerManager) + : ImageLayer(aLayerManager, static_cast(this)), + mSize(-1, -1) { + MOZ_COUNT_CTOR(BasicImageLayer); + } + + protected: + MOZ_COUNTED_DTOR_OVERRIDE(BasicImageLayer) + + public: + void SetVisibleRegion(const LayerIntRegion& aRegion) override { + NS_ASSERTION(BasicManager()->InConstruction(), + "Can only set properties in construction phase"); + ImageLayer::SetVisibleRegion(aRegion); + } + + void Paint(DrawTarget* aDT, const gfx::Point& aDeviceOffset, + Layer* aMaskLayer) override; + + already_AddRefed GetAsSourceSurface() override; + + protected: + BasicLayerManager* BasicManager() { + return static_cast(mManager); + } + + gfx::IntSize mSize; +}; + +void BasicImageLayer::Paint(DrawTarget* aDT, const gfx::Point& aDeviceOffset, + Layer* aMaskLayer) { + if (IsHidden() || !mContainer) { + return; + } + + RefPtr originalIF = mContainer->GetImageFactory(); + mContainer->SetImageFactory(mManager->IsCompositingCheap() + ? nullptr + : BasicManager()->GetImageFactory()); + + AutoLockImage autoLock(mContainer); + Image* image = autoLock.GetImage(BasicManager()->GetCompositionTime()); + if (!image) { + mContainer->SetImageFactory(originalIF); + return; + } + RefPtr surface = image->GetAsSourceSurface(); + if (!surface || !surface->IsValid()) { + mContainer->SetImageFactory(originalIF); + return; + } + + gfx::IntSize size = mSize = surface->GetSize(); + FillRectWithMask( + aDT, aDeviceOffset, Rect(0, 0, size.width, size.height), surface, + mSamplingFilter, + DrawOptions(GetEffectiveOpacity(), GetEffectiveOperator(this)), + aMaskLayer); + + mContainer->SetImageFactory(originalIF); +} + +already_AddRefed BasicImageLayer::GetAsSourceSurface() { + if (!mContainer) { + return nullptr; + } + + AutoLockImage lockImage(mContainer); + Image* image = lockImage.GetImage(); + if (!image) { + return nullptr; + } + return image->GetAsSourceSurface(); +} + +already_AddRefed BasicLayerManager::CreateImageLayer() { + NS_ASSERTION(InConstruction(), "Only allowed in construction phase"); + RefPtr layer = new BasicImageLayer(this); + return layer.forget(); +} + +} // namespace mozilla::layers diff --git a/gfx/layers/basic/BasicImages.cpp b/gfx/layers/basic/BasicImages.cpp new file mode 100644 index 0000000000..7774e39f54 --- /dev/null +++ b/gfx/layers/basic/BasicImages.cpp @@ -0,0 +1,180 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include // for uint8_t, uint32_t +#include "BasicLayers.h" // for BasicLayerManager +#include "ImageContainer.h" // for PlanarYCbCrImage, etc +#include "ImageTypes.h" // for ImageFormat, etc +#include "cairo.h" // for cairo_user_data_key_t +#include "gfxASurface.h" // for gfxASurface, etc +#include "gfxPlatform.h" // for gfxPlatform, gfxImageFormat +#include "gfxUtils.h" // for gfxUtils +#include "mozilla/CheckedInt.h" +#include "mozilla/mozalloc.h" // for operator delete[], etc +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsDebug.h" // for NS_ERROR, NS_ASSERTION +#include "nsISupportsImpl.h" // for Image::Release, etc +#include "nsThreadUtils.h" // for NS_IsMainThread +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/gfx/DataSurfaceHelpers.h" +#include "gfx2DGlue.h" +#include "YCbCrUtils.h" // for YCbCr conversions + +namespace mozilla { +namespace layers { + +class BasicPlanarYCbCrImage : public RecyclingPlanarYCbCrImage { + public: + BasicPlanarYCbCrImage(const gfx::IntSize& aScaleHint, + gfxImageFormat aOffscreenFormat, + BufferRecycleBin* aRecycleBin) + : RecyclingPlanarYCbCrImage(aRecycleBin), + mScaleHint(aScaleHint), + mStride(0), + mDelayedConversion(false) { + SetOffscreenFormat(aOffscreenFormat); + } + + ~BasicPlanarYCbCrImage() { + if (mDecodedBuffer) { + // Right now this only happens if the Image was never drawn, otherwise + // this will have been tossed away at surface destruction. + mRecycleBin->RecycleBuffer(std::move(mDecodedBuffer), + mSize.height * mStride); + } + } + + virtual bool CopyData(const Data& aData) override; + virtual void SetDelayedConversion(bool aDelayed) override { + mDelayedConversion = aDelayed; + } + + already_AddRefed GetAsSourceSurface() override; + + virtual size_t SizeOfIncludingThis( + MallocSizeOf aMallocSizeOf) const override { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); + } + + virtual size_t SizeOfExcludingThis( + MallocSizeOf aMallocSizeOf) const override { + size_t size = RecyclingPlanarYCbCrImage::SizeOfExcludingThis(aMallocSizeOf); + size += aMallocSizeOf(mDecodedBuffer.get()); + return size; + } + + private: + UniquePtr mDecodedBuffer; + gfx::IntSize mScaleHint; + int mStride; + bool mDelayedConversion; +}; + +class BasicImageFactory : public ImageFactory { + public: + BasicImageFactory() = default; + + virtual RefPtr CreatePlanarYCbCrImage( + const gfx::IntSize& aScaleHint, BufferRecycleBin* aRecycleBin) override { + return new BasicPlanarYCbCrImage( + aScaleHint, gfxPlatform::GetPlatform()->GetOffscreenFormat(), + aRecycleBin); + } +}; + +bool BasicPlanarYCbCrImage::CopyData(const Data& aData) { + RecyclingPlanarYCbCrImage::CopyData(aData); + + if (mDelayedConversion) { + return false; + } + + // Do some sanity checks to prevent integer overflow + if (aData.mYSize.width > PlanarYCbCrImage::MAX_DIMENSION || + aData.mYSize.height > PlanarYCbCrImage::MAX_DIMENSION) { + NS_ERROR("Illegal image source width or height"); + return false; + } + + gfx::SurfaceFormat format = + gfx::ImageFormatToSurfaceFormat(GetOffscreenFormat()); + + gfx::IntSize size(mScaleHint); + gfx::GetYCbCrToRGBDestFormatAndSize(aData, format, size); + if (size.width > PlanarYCbCrImage::MAX_DIMENSION || + size.height > PlanarYCbCrImage::MAX_DIMENSION) { + NS_ERROR("Illegal image dest width or height"); + return false; + } + + mStride = gfx::StrideForFormatAndWidth(format, size.width); + mozilla::CheckedInt32 requiredBytes = + mozilla::CheckedInt32(size.height) * mozilla::CheckedInt32(mStride); + if (!requiredBytes.isValid()) { + // invalid size + return false; + } + mDecodedBuffer = AllocateBuffer(requiredBytes.value()); + if (!mDecodedBuffer) { + // out of memory + return false; + } + + gfx::ConvertYCbCrToRGB(aData, format, size, mDecodedBuffer.get(), mStride); + SetOffscreenFormat(gfx::SurfaceFormatToImageFormat(format)); + mSize = size; + + return true; +} + +already_AddRefed +BasicPlanarYCbCrImage::GetAsSourceSurface() { + NS_ASSERTION(NS_IsMainThread(), "Must be main thread"); + + if (mSourceSurface) { + RefPtr surface(mSourceSurface); + return surface.forget(); + } + + if (!mDecodedBuffer) { + return PlanarYCbCrImage::GetAsSourceSurface(); + } + + gfxImageFormat format = GetOffscreenFormat(); + + RefPtr surface; + { + // Create a DrawTarget so that we can own the data inside mDecodeBuffer. + // We create the target out of mDecodedBuffer, and get a snapshot from it. + // The draw target is destroyed on scope exit and the surface owns the data. + RefPtr drawTarget = gfxPlatform::CreateDrawTargetForData( + mDecodedBuffer.get(), mSize, mStride, + gfx::ImageFormatToSurfaceFormat(format)); + if (!drawTarget) { + return nullptr; + } + + surface = drawTarget->Snapshot(); + } + + mRecycleBin->RecycleBuffer(std::move(mDecodedBuffer), mSize.height * mStride); + + mSourceSurface = surface; + return surface.forget(); +} + +ImageFactory* BasicLayerManager::GetImageFactory() { + if (!mFactory) { + mFactory = new BasicImageFactory(); + } + + return mFactory.get(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/basic/BasicImplData.h b/gfx/layers/basic/BasicImplData.h new file mode 100644 index 0000000000..6d0a7980a9 --- /dev/null +++ b/gfx/layers/basic/BasicImplData.h @@ -0,0 +1,140 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_BASICIMPLDATA_H +#define GFX_BASICIMPLDATA_H + +#include "mozilla/AlreadyAddRefed.h" // for already_AddRefed +#include "mozilla/gfx/Point.h" // for Point +#include "mozilla/gfx/Types.h" // for CompositionOp, CompositionOp::OP_OVER, CompositionOp::OP_SOURCE +#include "mozilla/layers/LayerManager.h" // for LayerManager, LayerManager::DrawPaintedLayerCallback +#include "nsDebug.h" // for NS_ASSERTION +#include "nsISupports.h" // for MOZ_COUNTED_DTOR_VIRTUAL, MOZ_COUNT_CTOR + +class gfxContext; + +namespace mozilla { +namespace gfx { +class DrawTarget; +class SourceSurface; +} // namespace gfx + +namespace layers { + +class Layer; +class ReadbackProcessor; + +/** + * This is the ImplData for all Basic layers. It also exposes methods + * private to the Basic implementation that are common to all Basic layer types. + * In particular, there is an internal Paint() method that we can use + * to paint the contents of non-PaintedLayers. + * + * The class hierarchy for Basic layers is like this: + * BasicImplData + * Layer | | | + * | | | | + * +-> ContainerLayer | | | + * | | | | | + * | +-> BasicContainerLayer <--+ | | + * | | | + * +-> PaintedLayer | | + * | | | | + * | +-> BasicPaintedLayer <---------+ | + * | | + * +-> ImageLayer | + * | | + * +-> BasicImageLayer <--------------+ + */ +class BasicImplData { + public: + BasicImplData() + : mHidden(false), + mClipToVisibleRegion(false), + mDrawAtomically(false), + mOperator(gfx::CompositionOp::OP_OVER) { + MOZ_COUNT_CTOR(BasicImplData); + } + MOZ_COUNTED_DTOR_VIRTUAL(BasicImplData) + + /** + * Layers that paint themselves, such as ImageLayers, should paint + * in response to this method call. aContext will already have been + * set up to account for all the properties of the layer (transform, + * opacity, etc). + */ + virtual void Paint(gfx::DrawTarget* aDT, const gfx::Point& aDeviceOffset, + Layer* aMaskLayer) {} + + /** + * Like Paint() but called for PaintedLayers with the additional parameters + * they need. + * If mClipToVisibleRegion is set, then the layer must clip to its + * effective visible region (snapped or unsnapped, it doesn't matter). + */ + virtual void PaintThebes(gfxContext* aContext, Layer* aMasklayer, + LayerManager::DrawPaintedLayerCallback aCallback, + void* aCallbackData) {} + + virtual void Validate(LayerManager::DrawPaintedLayerCallback aCallback, + void* aCallbackData, ReadbackProcessor* aReadback) {} + + /** + * Layers will get this call when their layer manager is destroyed, this + * indicates they should clear resources they don't really need after their + * LayerManager ceases to exist. + */ + virtual void ClearCachedResources() {} + + /** + * This variable is set by MarkLayersHidden() before painting. It indicates + * that the layer should not be composited during this transaction. + */ + void SetHidden(bool aCovered) { mHidden = aCovered; } + bool IsHidden() const { return false; } + /** + * This variable is set by MarkLayersHidden() before painting. This is + * the operator to be used when compositing the layer in this transaction. It + * must be OVER or SOURCE. + */ + void SetOperator(gfx::CompositionOp aOperator) { + NS_ASSERTION(aOperator == gfx::CompositionOp::OP_OVER || + aOperator == gfx::CompositionOp::OP_SOURCE, + "Bad composition operator"); + mOperator = aOperator; + } + + gfx::CompositionOp GetOperator() const { return mOperator; } + + /** + * Return a surface for this layer. Will use an existing surface, if + * possible, or may create a temporary surface. Implement this + * method for any layers that might be used as a mask. Should only + * return false if a surface cannot be created. If true is + * returned, only one of |aSurface| or |aDescriptor| is valid. + */ + virtual already_AddRefed GetAsSourceSurface() { + return nullptr; + } + + bool GetClipToVisibleRegion() { return mClipToVisibleRegion; } + void SetClipToVisibleRegion(bool aClip) { mClipToVisibleRegion = aClip; } + + void SetDrawAtomically(bool aDrawAtomically) { + mDrawAtomically = aDrawAtomically; + } + + protected: + bool mHidden; + bool mClipToVisibleRegion; + bool mDrawAtomically; + gfx::CompositionOp mOperator; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/basic/BasicLayerManager.cpp b/gfx/layers/basic/BasicLayerManager.cpp new file mode 100644 index 0000000000..83bc8cdffc --- /dev/null +++ b/gfx/layers/basic/BasicLayerManager.cpp @@ -0,0 +1,969 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include // for uint32_t +#include // for rand, RAND_MAX +#include // for int32_t +#include // for stack +#include "BasicContainerLayer.h" // for BasicContainerLayer +#include "BasicLayersImpl.h" // for ToData, BasicReadbackLayer, etc +#include "GeckoProfiler.h" // for AUTO_PROFILER_LABEL +#include "ImageContainer.h" // for ImageFactory +#include "Layers.h" // for Layer, ContainerLayer, etc +#include "ReadbackLayer.h" // for ReadbackLayer +#include "ReadbackProcessor.h" // for ReadbackProcessor +#include "RenderTrace.h" // for RenderTraceLayers, etc +#include "basic/BasicImplData.h" // for BasicImplData +#include "basic/BasicLayers.h" // for BasicLayerManager, etc +#include "gfxASurface.h" // for gfxASurface, etc +#include "gfxContext.h" // for gfxContext, etc +#include "gfxImageSurface.h" // for gfxImageSurface +#include "gfxMatrix.h" // for gfxMatrix +#include "gfxPlatform.h" // for gfxPlatform + +#include "gfxPoint.h" // for IntSize, gfxPoint +#include "gfxRect.h" // for gfxRect +#include "gfxUtils.h" // for gfxUtils +#include "gfx2DGlue.h" // for thebes --> moz2d transition +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/StaticPrefs_nglayout.h" +#include "mozilla/WidgetUtils.h" // for ScreenRotation +#include "mozilla/gfx/2D.h" // for DrawTarget +#include "mozilla/gfx/BasePoint.h" // for BasePoint +#include "mozilla/gfx/BaseRect.h" // for BaseRect +#include "mozilla/gfx/Matrix.h" // for Matrix +#include "mozilla/gfx/PathHelpers.h" +#include "mozilla/gfx/Rect.h" // for IntRect, Rect +#include "mozilla/layers/BSPTree.h" +#include "mozilla/layers/LayersTypes.h" // for BufferMode::BUFFER_NONE, etc +#include "mozilla/mozalloc.h" // for operator new +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsDebug.h" // for NS_ASSERTION, etc +#include "nsISupportsImpl.h" // for gfxContext::Release, etc +#include "nsLayoutUtils.h" // for nsLayoutUtils +#include "nsPoint.h" // for nsIntPoint +#include "nsRect.h" // for mozilla::gfx::IntRect +#include "nsRegion.h" // for nsIntRegion, etc +#include "nsTArray.h" // for AutoTArray +#include "TreeTraversal.h" // for ForEachNode + +class nsIWidget; + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +/** + * Clips to the smallest device-pixel-aligned rectangle containing aRect + * in user space. + * Returns true if the clip is "perfect", i.e. we actually clipped exactly to + * aRect. + */ +static bool ClipToContain(gfxContext* aContext, const IntRect& aRect) { + gfxRect userRect(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()); + gfxRect deviceRect = aContext->UserToDevice(userRect); + deviceRect.RoundOut(); + + Matrix currentMatrix = aContext->CurrentMatrix(); + aContext->SetMatrix(Matrix()); + aContext->NewPath(); + aContext->Rectangle(deviceRect); + aContext->Clip(); + aContext->SetMatrix(currentMatrix); + + return aContext->DeviceToUser(deviceRect).IsEqualInterior(userRect); +} + +bool BasicLayerManager::PushGroupForLayer(gfxContext* aContext, Layer* aLayer, + const nsIntRegion& aRegion, + PushedGroup& aGroupResult) { + aGroupResult.mVisibleRegion = aRegion; + aGroupResult.mFinalTarget = aContext; + aGroupResult.mOperator = GetEffectiveOperator(aLayer); + aGroupResult.mOpacity = aLayer->GetEffectiveOpacity(); + + // If we need to call PushGroup, we should clip to the smallest possible + // area first to minimize the size of the temporary surface. + bool didCompleteClip = ClipToContain(aContext, aRegion.GetBounds()); + + bool canPushGroup = + aGroupResult.mOperator == CompositionOp::OP_OVER || + (aGroupResult.mOperator == CompositionOp::OP_SOURCE && + (aLayer->CanUseOpaqueSurface() || + aLayer->GetContentFlags() & Layer::CONTENT_COMPONENT_ALPHA)); + + if (!canPushGroup) { + aContext->Save(); + gfxUtils::ClipToRegion(aGroupResult.mFinalTarget, + aGroupResult.mVisibleRegion); + + // PushGroup/PopGroup do not support non operator over. + gfxRect rect = aContext->GetClipExtents(gfxContext::eDeviceSpace); + rect.RoundOut(); + IntRect surfRect; + ToRect(rect).ToIntRect(&surfRect); + + if (!surfRect.IsEmpty()) { + RefPtr dt; + if (aContext->GetDrawTarget()->CanCreateSimilarDrawTarget( + surfRect.Size(), SurfaceFormat::B8G8R8A8)) { + dt = aContext->GetDrawTarget()->CreateSimilarDrawTarget( + surfRect.Size(), SurfaceFormat::B8G8R8A8); + } + + RefPtr ctx = + gfxContext::CreateOrNull(dt, ToRect(rect).TopLeft()); + if (!ctx) { + gfxCriticalNote + << "BasicLayerManager context problem in PushGroupForLayer " + << gfx::hexa(dt); + return false; + } + ctx->SetMatrix(aContext->CurrentMatrix()); + + aGroupResult.mGroupOffset = surfRect.TopLeft(); + aGroupResult.mGroupTarget = ctx; + + aGroupResult.mMaskSurface = + GetMaskForLayer(aLayer, &aGroupResult.mMaskTransform); + return true; + } + aContext->Restore(); + } + + Matrix maskTransform; + RefPtr maskSurf = GetMaskForLayer(aLayer, &maskTransform); + + if (maskSurf) { + // The returned transform will transform the mask to device space on the + // destination. Since the User->Device space transform will be applied + // to the mask by PopGroupAndBlend we need to adjust the transform to + // transform the mask to user space. + Matrix currentTransform = aGroupResult.mFinalTarget->CurrentMatrix(); + currentTransform.Invert(); + maskTransform = maskTransform * currentTransform; + } + + if (aLayer->CanUseOpaqueSurface() && + ((didCompleteClip && aRegion.GetNumRects() == 1) || + !aContext->CurrentMatrix().HasNonIntegerTranslation())) { + // If the layer is opaque in its visible region we can push a + // gfxContentType::COLOR group. We need to make sure that only pixels inside + // the layer's visible region are copied back to the destination. Remember + // if we've already clipped precisely to the visible region. + aGroupResult.mNeedsClipToVisibleRegion = + !didCompleteClip || aRegion.GetNumRects() > 1; + if (aGroupResult.mNeedsClipToVisibleRegion) { + aGroupResult.mFinalTarget->Save(); + gfxUtils::ClipToRegion(aGroupResult.mFinalTarget, + aGroupResult.mVisibleRegion); + } + + aContext->PushGroupForBlendBack( + gfxContentType::COLOR, aGroupResult.mOpacity, maskSurf, maskTransform); + } else { + if (aLayer->GetContentFlags() & Layer::CONTENT_COMPONENT_ALPHA) { + aContext->PushGroupAndCopyBackground(gfxContentType::COLOR_ALPHA, + aGroupResult.mOpacity, maskSurf, + maskTransform); + } else { + aContext->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, + aGroupResult.mOpacity, maskSurf, + maskTransform); + } + } + + aGroupResult.mGroupTarget = aGroupResult.mFinalTarget; + + return true; +} + +void BasicLayerManager::PopGroupForLayer(PushedGroup& group) { + if (group.mFinalTarget == group.mGroupTarget) { + group.mFinalTarget->PopGroupAndBlend(); + if (group.mNeedsClipToVisibleRegion) { + group.mFinalTarget->Restore(); + } + return; + } + + DrawTarget* dt = group.mFinalTarget->GetDrawTarget(); + RefPtr sourceDT = group.mGroupTarget->GetDrawTarget(); + group.mGroupTarget = nullptr; + + RefPtr src = sourceDT->Snapshot(); + + if (group.mMaskSurface) { + Point finalOffset = group.mFinalTarget->GetDeviceOffset(); + dt->SetTransform(group.mMaskTransform * Matrix::Translation(-finalOffset)); + Matrix surfTransform = group.mMaskTransform; + surfTransform.Invert(); + dt->MaskSurface(SurfacePattern(src, ExtendMode::CLAMP, + surfTransform * Matrix::Translation( + group.mGroupOffset.x, + group.mGroupOffset.y)), + group.mMaskSurface, Point(0, 0), + DrawOptions(group.mOpacity, group.mOperator)); + } else { + // For now this is required since our group offset is in device space of the + // final target, context but that may still have its own device offset. Once + // PushGroup/PopGroup logic is migrated to DrawTargets this can go as + // gfxContext::GetDeviceOffset will essentially always become null. + dt->SetTransform( + Matrix::Translation(-group.mFinalTarget->GetDeviceOffset())); + dt->DrawSurface(src, + Rect(group.mGroupOffset.x, group.mGroupOffset.y, + src->GetSize().width, src->GetSize().height), + Rect(0, 0, src->GetSize().width, src->GetSize().height), + DrawSurfaceOptions(SamplingFilter::POINT), + DrawOptions(group.mOpacity, group.mOperator)); + } + + if (group.mNeedsClipToVisibleRegion) { + dt->PopClip(); + } + + group.mFinalTarget->Restore(); +} + +static IntRect ToInsideIntRect(const gfxRect& aRect) { + return IntRect::RoundIn(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()); +} + +// A context helper for BasicLayerManager::PaintLayer() that holds all the +// painting context together in a data structure so it can be easily passed +// around. It also uses ensures that the Transform and Opaque rect are restored +// to their former state on destruction. + +class PaintLayerContext { + public: + PaintLayerContext(gfxContext* aTarget, Layer* aLayer, + LayerManager::DrawPaintedLayerCallback aCallback, + void* aCallbackData) + : mTarget(aTarget), + mTargetMatrixSR(aTarget), + mLayer(aLayer), + mCallback(aCallback), + mCallbackData(aCallbackData), + mPushedOpaqueRect(false) {} + + ~PaintLayerContext() { + // Matrix is restored by mTargetMatrixSR + if (mPushedOpaqueRect) { + ClearOpaqueRect(); + } + } + + // Gets the effective transform and returns true if it is a 2D + // transform. + bool Setup2DTransform() { + // Will return an identity matrix for 3d transforms. + return mLayer->GetEffectiveTransformForBuffer().CanDraw2D(&mTransform); + } + + // Applies the effective transform if it's 2D. If it's a 3D transform then + // it applies an identity. + void Apply2DTransform() { mTarget->SetMatrix(mTransform); } + + // Set the opaque rect to match the bounds of the visible region. + void AnnotateOpaqueRect() { + const nsIntRegion visibleRegion = + mLayer->GetLocalVisibleRegion().ToUnknownRegion(); + const IntRect& bounds = visibleRegion.GetBounds(); + + DrawTarget* dt = mTarget->GetDrawTarget(); + const IntRect& targetOpaqueRect = dt->GetOpaqueRect(); + + // Try to annotate currentSurface with a region of pixels that have been + // (or will be) painted opaque, if no such region is currently set. + if (targetOpaqueRect.IsEmpty() && visibleRegion.GetNumRects() == 1 && + (mLayer->GetContentFlags() & Layer::CONTENT_OPAQUE) && + !mTransform.HasNonAxisAlignedTransform()) { + gfx::Rect opaqueRect = dt->GetTransform().TransformBounds( + gfx::Rect(bounds.X(), bounds.Y(), bounds.Width(), bounds.Height())); + opaqueRect.RoundIn(); + IntRect intOpaqueRect; + if (opaqueRect.ToIntRect(&intOpaqueRect)) { + mTarget->GetDrawTarget()->SetOpaqueRect(intOpaqueRect); + mPushedOpaqueRect = true; + } + } + } + + // Clear the Opaque rect. Although this doesn't really restore it to it's + // previous state it will happen on the exit path of the PaintLayer() so when + // painting is complete the opaque rect qill be clear. + void ClearOpaqueRect() { mTarget->GetDrawTarget()->SetOpaqueRect(IntRect()); } + + gfxContext* mTarget; + gfxContextMatrixAutoSaveRestore mTargetMatrixSR; + Layer* mLayer; + LayerManager::DrawPaintedLayerCallback mCallback; + void* mCallbackData; + Matrix mTransform; + bool mPushedOpaqueRect; +}; + +BasicLayerManager::BasicLayerManager(nsIWidget* aWidget) + : mPhase(PHASE_NONE), + mWidget(aWidget), + mDoubleBuffering(BufferMode::BUFFER_NONE), + mType(BLM_WIDGET), + mUsingDefaultTarget(false), + mTransactionIncomplete(false), + mCompositorMightResample(false) { + MOZ_COUNT_CTOR(BasicLayerManager); + NS_ASSERTION(aWidget, "Must provide a widget"); +} + +BasicLayerManager::BasicLayerManager(BasicLayerManagerType aType) + : mPhase(PHASE_NONE), + mWidget(nullptr), + mDoubleBuffering(BufferMode::BUFFER_NONE), + mType(aType), + mUsingDefaultTarget(false), + mTransactionIncomplete(false), + mCompositorMightResample(false) { + MOZ_COUNT_CTOR(BasicLayerManager); + MOZ_ASSERT(mType != BLM_WIDGET); +} + +BasicLayerManager::~BasicLayerManager() { + NS_ASSERTION(!InTransaction(), "Died during transaction?"); + + ClearCachedResources(); + + mRoot = nullptr; + + MOZ_COUNT_DTOR(BasicLayerManager); +} + +void BasicLayerManager::SetDefaultTarget(gfxContext* aContext) { + NS_ASSERTION(!InTransaction(), "Must set default target outside transaction"); + mDefaultTarget = aContext; +} + +void BasicLayerManager::SetDefaultTargetConfiguration( + BufferMode aDoubleBuffering, ScreenRotation aRotation) { + mDoubleBuffering = aDoubleBuffering; +} + +bool BasicLayerManager::BeginTransaction(const nsCString& aURL) { + mInTransaction = true; + mUsingDefaultTarget = true; + return BeginTransactionWithTarget(mDefaultTarget, aURL); +} + +bool BasicLayerManager::BeginTransactionWithTarget(gfxContext* aTarget, + const nsCString& aURL) { + mInTransaction = true; + +#ifdef MOZ_LAYERS_HAVE_LOG + MOZ_LAYERS_LOG(("[----- BeginTransaction")); + Log(); +#endif + + NS_ASSERTION(!InTransaction(), "Nested transactions not allowed"); + mPhase = PHASE_CONSTRUCTION; + mTarget = aTarget; + return true; +} + +static void TransformIntRect(IntRect& aRect, const Matrix& aMatrix, + IntRect (*aRoundMethod)(const gfxRect&)) { + Rect gr = Rect(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()); + gr = aMatrix.TransformBounds(gr); + aRect = (*aRoundMethod)(ThebesRect(gr)); +} + +/** + * This function assumes that GetEffectiveTransform transforms + * all layers to the same coordinate system (the "root coordinate system"). + * It can't be used as is by accelerated layers because of intermediate + * surfaces. This must set the hidden flag to true or false on *all* layers in + * the subtree. It also sets the operator for all layers to "OVER", and call + * SetDrawAtomically(false). + * It clears mClipToVisibleRegion on all layers. + * @param aClipRect the cliprect, in the root coordinate system. We assume + * that any layer drawing is clipped to this rect. It is therefore not + * allowed to add to the opaque region outside that rect. + * @param aDirtyRect the dirty rect that will be painted, in the root + * coordinate system. Layers outside this rect should be hidden. + * @param aOpaqueRegion the opaque region covering aLayer, in the + * root coordinate system. + */ +enum { + ALLOW_OPAQUE = 0x01, +}; +static void MarkLayersHidden(Layer* aLayer, const IntRect& aClipRect, + const IntRect& aDirtyRect, + nsIntRegion& aOpaqueRegion, uint32_t aFlags) { + IntRect newClipRect(aClipRect); + uint32_t newFlags = aFlags; + + // Allow aLayer or aLayer's descendants to cover underlying layers + // only if it's opaque. + if (aLayer->GetOpacity() != 1.0f) { + newFlags &= ~ALLOW_OPAQUE; + } + + { + const Maybe& clipRect = aLayer->GetLocalClipRect(); + if (clipRect) { + IntRect cr = clipRect->ToUnknownRect(); + // clipRect is in the container's coordinate system. Get it into the + // global coordinate system. + if (aLayer->GetParent()) { + Matrix tr; + if (aLayer->GetParent()->GetEffectiveTransform().CanDraw2D(&tr)) { + // Clip rect is applied after aLayer's transform, i.e., in the + // coordinate system of aLayer's parent. + TransformIntRect(cr, tr, ToInsideIntRect); + } else { + cr.SetRect(0, 0, 0, 0); + } + } + newClipRect.IntersectRect(newClipRect, cr); + } + } + + BasicImplData* data = ToData(aLayer); + data->SetOperator(CompositionOp::OP_OVER); + data->SetClipToVisibleRegion(false); + data->SetDrawAtomically(false); + + if (!aLayer->AsContainerLayer()) { + Matrix transform; + if (!aLayer->GetEffectiveTransform().CanDraw2D(&transform)) { + data->SetHidden(false); + return; + } + + nsIntRegion region = aLayer->GetLocalVisibleRegion().ToUnknownRegion(); + IntRect r = region.GetBounds(); + TransformIntRect(r, transform, ToOutsideIntRect); + r.IntersectRect(r, aDirtyRect); + data->SetHidden(aOpaqueRegion.Contains(r)); + + // Allow aLayer to cover underlying layers only if aLayer's + // content is opaque + if ((aLayer->GetContentFlags() & Layer::CONTENT_OPAQUE) && + (newFlags & ALLOW_OPAQUE)) { + for (auto iter = region.RectIter(); !iter.Done(); iter.Next()) { + r = iter.Get(); + TransformIntRect(r, transform, ToInsideIntRect); + + r.IntersectRect(r, newClipRect); + aOpaqueRegion.Or(aOpaqueRegion, r); + } + } + } else { + Layer* child = aLayer->GetLastChild(); + bool allHidden = true; + for (; child; child = child->GetPrevSibling()) { + MarkLayersHidden(child, newClipRect, aDirtyRect, aOpaqueRegion, newFlags); + if (!ToData(child)->IsHidden()) { + allHidden = false; + } + } + data->SetHidden(allHidden); + } +} + +/** + * This function assumes that GetEffectiveTransform transforms + * all layers to the same coordinate system (the "root coordinate system"). + * MarkLayersHidden must be called before calling this. + * @param aVisibleRect the rectangle of aLayer that is visible (i.e. not + * clipped and in the dirty rect), in the root coordinate system. + */ +static void ApplyDoubleBuffering(Layer* aLayer, const IntRect& aVisibleRect) { + BasicImplData* data = ToData(aLayer); + if (data->IsHidden()) return; + + IntRect newVisibleRect(aVisibleRect); + + { + const Maybe& clipRect = aLayer->GetLocalClipRect(); + if (clipRect) { + IntRect cr = clipRect->ToUnknownRect(); + // clipRect is in the container's coordinate system. Get it into the + // global coordinate system. + if (aLayer->GetParent()) { + Matrix tr; + if (aLayer->GetParent()->GetEffectiveTransform().CanDraw2D(&tr)) { + NS_ASSERTION(!ThebesMatrix(tr).HasNonIntegerTranslation(), + "Parent can only have an integer translation"); + cr += nsIntPoint(int32_t(tr._31), int32_t(tr._32)); + } else { + NS_ERROR("Parent can only have an integer translation"); + } + } + newVisibleRect.IntersectRect(newVisibleRect, cr); + } + } + + BasicContainerLayer* container = + static_cast(aLayer->AsContainerLayer()); + // Layers that act as their own backbuffers should be drawn to the destination + // using OP_SOURCE to ensure that alpha values in a transparent window are + // cleared. This can also be faster than OP_OVER. + if (!container) { + data->SetOperator(CompositionOp::OP_SOURCE); + data->SetDrawAtomically(true); + } else { + if (container->UseIntermediateSurface() || + !container->ChildrenPartitionVisibleRegion(newVisibleRect)) { + // We need to double-buffer this container. + data->SetOperator(CompositionOp::OP_SOURCE); + container->ForceIntermediateSurface(); + } else { + // Tell the children to clip to their visible regions so our assumption + // that they don't paint outside their visible regions is valid! + for (Layer* child = aLayer->GetFirstChild(); child; + child = child->GetNextSibling()) { + ToData(child)->SetClipToVisibleRegion(true); + ApplyDoubleBuffering(child, newVisibleRect); + } + } + } +} + +void BasicLayerManager::EndTransaction(DrawPaintedLayerCallback aCallback, + void* aCallbackData, + EndTransactionFlags aFlags) { + mInTransaction = false; + + EndTransactionInternal(aCallback, aCallbackData, aFlags); +} + +void BasicLayerManager::AbortTransaction() { + NS_ASSERTION(InConstruction(), "Should be in construction phase"); + mPhase = PHASE_NONE; + mUsingDefaultTarget = false; + mInTransaction = false; +} + +bool BasicLayerManager::EndTransactionInternal( + DrawPaintedLayerCallback aCallback, void* aCallbackData, + EndTransactionFlags aFlags) { + AUTO_PROFILER_LABEL("BasicLayerManager::EndTransactionInternal", GRAPHICS); + +#ifdef MOZ_LAYERS_HAVE_LOG + MOZ_LAYERS_LOG((" ----- (beginning paint)")); + Log(); +#endif + + NS_ASSERTION(InConstruction(), "Should be in construction phase"); + mPhase = PHASE_DRAWING; + + SetCompositionTime(TimeStamp::Now()); + + RenderTraceLayers(mRoot, "FF00"); + + mTransactionIncomplete = false; + + std::unordered_set scrollIdsUpdated; + + if (mRoot) { + if (aFlags & END_NO_COMPOSITE) { + // Apply pending tree updates before recomputing effective + // properties. + scrollIdsUpdated = mRoot->ApplyPendingUpdatesToSubtree(); + } + + // Need to do this before we call ApplyDoubleBuffering, + // which depends on correct effective transforms + if (mTarget) { + mSnapEffectiveTransforms = + !mTarget->GetDrawTarget()->GetUserData(&sDisablePixelSnapping); + } else { + mSnapEffectiveTransforms = true; + } + mRoot->ComputeEffectiveTransforms( + mTarget ? Matrix4x4::From2D(mTarget->CurrentMatrix()) : Matrix4x4()); + + ToData(mRoot)->Validate(aCallback, aCallbackData, nullptr); + if (mRoot->GetMaskLayer()) { + ToData(mRoot->GetMaskLayer()) + ->Validate(aCallback, aCallbackData, nullptr); + } + } + + if (mTarget && mRoot && !(aFlags & END_NO_IMMEDIATE_REDRAW) && + !(aFlags & END_NO_COMPOSITE)) { + IntRect clipRect = + ToOutsideIntRect(mTarget->GetClipExtents(gfxContext::eDeviceSpace)); + + if (IsRetained()) { + nsIntRegion region; + MarkLayersHidden(mRoot, clipRect, clipRect, region, ALLOW_OPAQUE); + if (mUsingDefaultTarget && mDoubleBuffering != BufferMode::BUFFER_NONE) { + ApplyDoubleBuffering(mRoot, clipRect); + } + } + + PaintLayer(mTarget, mRoot, aCallback, aCallbackData); + if (!mRegionToClear.IsEmpty()) { + for (auto iter = mRegionToClear.RectIter(); !iter.Done(); iter.Next()) { + const IntRect& r = iter.Get(); + mTarget->GetDrawTarget()->ClearRect( + Rect(r.X(), r.Y(), r.Width(), r.Height())); + } + } + if (mWidget) { + FlashWidgetUpdateArea(mTarget); + } + RecordFrame(); + + if (!mTransactionIncomplete) { + // Clear out target if we have a complete transaction. + mTarget = nullptr; + } + } + + if (mRoot) { + mAnimationReadyTime = TimeStamp::Now(); + mRoot->StartPendingAnimations(mAnimationReadyTime); + + // Once we're sure we're not going to fall back to a full paint, + // notify the scroll frames which had pending updates. + if (!mTransactionIncomplete) { + for (ScrollableLayerGuid::ViewID scrollId : scrollIdsUpdated) { + nsLayoutUtils::NotifyPaintSkipTransaction(scrollId); + } + } + } + +#ifdef MOZ_LAYERS_HAVE_LOG + Log(); + MOZ_LAYERS_LOG(("]----- EndTransaction")); +#endif + + // Go back to the construction phase if the transaction isn't complete. + // Layout will update the layer tree and call EndTransaction(). + mPhase = mTransactionIncomplete ? PHASE_CONSTRUCTION : PHASE_NONE; + + if (!mTransactionIncomplete) { + // This is still valid if the transaction was incomplete. + mUsingDefaultTarget = false; + } + + NS_ASSERTION(!aCallback || !mTransactionIncomplete, + "If callback is not null, transaction must be complete"); + + // XXX - We should probably assert here that for an incomplete transaction + // out target is the default target. + + return !mTransactionIncomplete; +} + +void BasicLayerManager::FlashWidgetUpdateArea(gfxContext* aContext) { + if (StaticPrefs::nglayout_debug_widget_update_flashing()) { + float r = float(rand()) / float(RAND_MAX); + float g = float(rand()) / float(RAND_MAX); + float b = float(rand()) / float(RAND_MAX); + aContext->SetDeviceColor(DeviceColor(r, g, b, 0.2f)); + aContext->Paint(); + } +} + +bool BasicLayerManager::EndEmptyTransaction(EndTransactionFlags aFlags) { + mInTransaction = false; + + if (!mRoot) { + return false; + } + + return EndTransactionInternal(nullptr, nullptr, aFlags); +} + +void BasicLayerManager::SetRoot(Layer* aLayer) { + NS_ASSERTION(aLayer, "Root can't be null"); + NS_ASSERTION(aLayer->Manager() == this, "Wrong manager"); + NS_ASSERTION(InConstruction(), "Only allowed in construction phase"); + mRoot = aLayer; +} + +void BasicLayerManager::PaintSelfOrChildren(PaintLayerContext& aPaintContext, + gfxContext* aGroupTarget) { + MOZ_ASSERT(aGroupTarget); + BasicImplData* data = ToData(aPaintContext.mLayer); + + /* Only paint ourself, or our children - This optimization relies on this! */ + Layer* child = aPaintContext.mLayer->GetFirstChild(); + if (!child) { + if (aPaintContext.mLayer->AsPaintedLayer()) { + data->PaintThebes(aGroupTarget, aPaintContext.mLayer->GetMaskLayer(), + aPaintContext.mCallback, aPaintContext.mCallbackData); + } else { + data->Paint(aGroupTarget->GetDrawTarget(), + aGroupTarget->GetDeviceOffset(), + aPaintContext.mLayer->GetMaskLayer()); + } + } else { + ContainerLayer* container = + static_cast(aPaintContext.mLayer); + + nsTArray children = container->SortChildrenBy3DZOrder( + ContainerLayer::SortMode::WITHOUT_GEOMETRY); + + for (uint32_t i = 0; i < children.Length(); i++) { + Layer* layer = children.ElementAt(i).layer; + if (layer->IsBackfaceHidden()) { + continue; + } + if (!layer->AsContainerLayer() && !layer->IsVisible()) { + continue; + } + + PaintLayer(aGroupTarget, layer, aPaintContext.mCallback, + aPaintContext.mCallbackData); + if (mTransactionIncomplete) break; + } + } +} + +void BasicLayerManager::FlushGroup(PaintLayerContext& aPaintContext, + bool aNeedsClipToVisibleRegion) { + // If we're doing our own double-buffering, we need to avoid drawing + // the results of an incomplete transaction to the destination surface --- + // that could cause flicker. Double-buffering is implemented using a + // temporary surface for one or more container layers, so we need to stop + // those temporary surfaces from being composited to aTarget. + // ApplyDoubleBuffering guarantees that this container layer can't + // intersect any other leaf layers, so if the transaction is not yet marked + // incomplete, the contents of this container layer are the final contents + // for the window. + if (!mTransactionIncomplete) { + if (aNeedsClipToVisibleRegion) { + gfxUtils::ClipToRegion( + aPaintContext.mTarget, + aPaintContext.mLayer->GetLocalVisibleRegion().ToUnknownRegion()); + } + + CompositionOp op = GetEffectiveOperator(aPaintContext.mLayer); + AutoSetOperator setOperator(aPaintContext.mTarget, op); + + PaintWithMask(aPaintContext.mTarget, + aPaintContext.mLayer->GetEffectiveOpacity(), + aPaintContext.mLayer->GetMaskLayer()); + } +} + +/** + * Install the clip applied to the layer on the given gfxContext. The + * given gfxContext is the buffer that the layer will be painted to. + */ +static void InstallLayerClipPreserves3D(gfxContext* aTarget, Layer* aLayer) { + const Maybe& clipRect = aLayer->GetLocalClipRect(); + + if (!clipRect) { + return; + } + MOZ_ASSERT( + !aLayer->Extend3DContext() || !aLayer->Combines3DTransformWithAncestors(), + "Layers in a preserve 3D context have no clip" + " except leaves and the estabisher!"); + + Layer* parent = aLayer->GetParent(); + Matrix4x4 transform3d = parent && parent->Extend3DContext() + ? parent->GetEffectiveTransform() + : Matrix4x4(); + Matrix transform; + if (!transform3d.CanDraw2D(&transform)) { + gfxDevCrash(LogReason::CannotDraw3D) + << "GFX: We should not have a 3D transform that CanDraw2D() is false!"; + } + Matrix oldTransform = aTarget->CurrentMatrix(); + transform *= oldTransform; + aTarget->SetMatrix(transform); + + aTarget->SnappedClip(gfxRect(clipRect->X(), clipRect->Y(), clipRect->Width(), + clipRect->Height())); + + aTarget->SetMatrix(oldTransform); +} + +void BasicLayerManager::PaintLayer(gfxContext* aTarget, Layer* aLayer, + DrawPaintedLayerCallback aCallback, + void* aCallbackData) { + MOZ_ASSERT(aTarget); + + AUTO_PROFILER_LABEL("BasicLayerManager::PaintLayer", GRAPHICS); + + PaintLayerContext paintLayerContext(aTarget, aLayer, aCallback, + aCallbackData); + + // Don't attempt to paint layers with a singular transform, cairo will + // just throw an error. + if (aLayer->GetEffectiveTransform().IsSingular()) { + return; + } + + RenderTraceScope trace("BasicLayerManager::PaintLayer", "707070"); + + const Maybe& clipRect = aLayer->GetLocalClipRect(); + BasicContainerLayer* container = + static_cast(aLayer->AsContainerLayer()); + bool needsGroup = container && container->UseIntermediateSurface(); + BasicImplData* data = ToData(aLayer); + bool needsClipToVisibleRegion = + data->GetClipToVisibleRegion() && !aLayer->AsPaintedLayer(); + NS_ASSERTION(needsGroup || !container || + container->GetOperator() == CompositionOp::OP_OVER, + "non-OVER operator should have forced UseIntermediateSurface"); + NS_ASSERTION( + !container || !aLayer->GetMaskLayer() || + container->UseIntermediateSurface(), + "ContainerLayer with mask layer should force UseIntermediateSurface"); + + gfxContextAutoSaveRestore contextSR; + gfxMatrix transform; + // Will return an identity matrix for 3d transforms, and is handled separately + // below. + bool is2D = paintLayerContext.Setup2DTransform(); + MOZ_ASSERT(is2D || needsGroup || !container || container->Extend3DContext() || + container->Is3DContextLeaf(), + "Must PushGroup for 3d transforms!"); + + Layer* parent = aLayer->GetParent(); + bool inPreserves3DChain = parent && parent->Extend3DContext(); + bool needsSaveRestore = needsGroup || clipRect || needsClipToVisibleRegion || + !is2D || inPreserves3DChain; + if (needsSaveRestore) { + contextSR.SetContext(aTarget); + + // The clips on ancestors on the preserved3d chain should be + // installed on the aTarget before painting the layer. + InstallLayerClipPreserves3D(aTarget, aLayer); + for (Layer* l = parent; l && l->Extend3DContext(); l = l->GetParent()) { + InstallLayerClipPreserves3D(aTarget, l); + } + } + + paintLayerContext.Apply2DTransform(); + + nsIntRegion visibleRegion = aLayer->GetLocalVisibleRegion().ToUnknownRegion(); + // If needsGroup is true, we'll clip to the visible region after we've popped + // the group + if (needsClipToVisibleRegion && !needsGroup) { + gfxUtils::ClipToRegion(aTarget, visibleRegion); + // Don't need to clip to visible region again + needsClipToVisibleRegion = false; + } + + if (is2D) { + paintLayerContext.AnnotateOpaqueRect(); + } + + bool clipIsEmpty = aTarget->GetClipExtents().IsEmpty(); + if (clipIsEmpty) { + PaintSelfOrChildren(paintLayerContext, aTarget); + return; + } + + if (is2D) { + if (needsGroup) { + PushedGroup pushedGroup; + if (PushGroupForLayer(aTarget, aLayer, + aLayer->GetLocalVisibleRegion().ToUnknownRegion(), + pushedGroup)) { + PaintSelfOrChildren(paintLayerContext, pushedGroup.mGroupTarget); + PopGroupForLayer(pushedGroup); + } + } else { + PaintSelfOrChildren(paintLayerContext, aTarget); + } + } else { + if (!needsGroup && container) { + PaintSelfOrChildren(paintLayerContext, aTarget); + return; + } + + IntRect bounds = visibleRegion.GetBounds(); + // DrawTarget without the 3D transform applied: + RefPtr untransformedDT = + gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget( + IntSize(bounds.Width(), bounds.Height()), SurfaceFormat::B8G8R8A8); + if (!untransformedDT || !untransformedDT->IsValid()) { + return; + } + untransformedDT->SetTransform( + Matrix::Translation(-Point(bounds.X(), bounds.Y()))); + + RefPtr groupTarget = + gfxContext::CreatePreservingTransformOrNull(untransformedDT); + MOZ_ASSERT(groupTarget); // already checked the target above + + PaintSelfOrChildren(paintLayerContext, groupTarget); + + // Temporary fast fix for bug 725886 + // Revert these changes when 725886 is ready +#ifdef DEBUG + if (aLayer->GetDebugColorIndex() != 0) { + DeviceColor color((aLayer->GetDebugColorIndex() & 1) ? 1.f : 0.f, + (aLayer->GetDebugColorIndex() & 2) ? 1.f : 0.f, + (aLayer->GetDebugColorIndex() & 4) ? 1.f : 0.f); + untransformedDT->FillRect(Rect(bounds), ColorPattern(color)); + } +#endif + Matrix4x4 effectiveTransform = aLayer->GetEffectiveTransform(); + Rect xformBounds = effectiveTransform.TransformAndClipBounds( + Rect(bounds), ToRect(aTarget->GetClipExtents())); + xformBounds.RoundOut(); + effectiveTransform.PostTranslate(-xformBounds.X(), -xformBounds.Y(), 0); + effectiveTransform.PreTranslate(bounds.X(), bounds.Y(), 0); + + RefPtr untransformedSurf = untransformedDT->Snapshot(); + RefPtr xformDT = untransformedDT->CreateSimilarDrawTarget( + IntSize::Truncate(xformBounds.Width(), xformBounds.Height()), + SurfaceFormat::B8G8R8A8); + RefPtr xformSurf; + if (xformDT && untransformedSurf && + xformDT->Draw3DTransformedSurface(untransformedSurf, + effectiveTransform)) { + xformSurf = xformDT->Snapshot(); + } + + if (xformSurf) { + aTarget->SetPattern(new gfxPattern( + xformSurf, Matrix::Translation(xformBounds.TopLeft()))); + + // Azure doesn't support EXTEND_NONE, so to avoid extending the edges + // of the source surface out to the current clip region, clip to + // the rectangle of the result surface now. + aTarget->SnappedClip(ThebesRect(xformBounds)); + FlushGroup(paintLayerContext, needsClipToVisibleRegion); + } + } +} + +void BasicLayerManager::ClearCachedResources(Layer* aSubtree) { + MOZ_ASSERT(!aSubtree || aSubtree->Manager() == this); + if (aSubtree) { + ClearLayer(aSubtree); + } else if (mRoot) { + ClearLayer(mRoot); + } +} +void BasicLayerManager::ClearLayer(Layer* aLayer) { + ToData(aLayer)->ClearCachedResources(); + for (Layer* child = aLayer->GetFirstChild(); child; + child = child->GetNextSibling()) { + ClearLayer(child); + } +} + +already_AddRefed BasicLayerManager::CreateReadbackLayer() { + NS_ASSERTION(InConstruction(), "Only allowed in construction phase"); + RefPtr layer = new BasicReadbackLayer(this); + return layer.forget(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/basic/BasicLayers.h b/gfx/layers/basic/BasicLayers.h new file mode 100644 index 0000000000..d313cea13c --- /dev/null +++ b/gfx/layers/basic/BasicLayers.h @@ -0,0 +1,243 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_BASICLAYERS_H +#define GFX_BASICLAYERS_H + +#include // for INT32_MAX, int32_t +#include "gfxTypes.h" +#include "gfxContext.h" // for gfxContext +#include "mozilla/Attributes.h" // for override +#include "mozilla/WidgetUtils.h" // for ScreenRotation +#include "mozilla/layers/LayerManager.h" // for LayerManager::DrawPaintedLayerCallback, LayerManager::END_DEFAULT, LayerManager::EndTra... +#include "mozilla/layers/LayersTypes.h" // for BufferMode, LayersBackend, etc +#include "mozilla/TimeStamp.h" +#include "nsAString.h" +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsISupportsImpl.h" // for gfxContext::AddRef, etc +#include "nsRegion.h" // for nsIntRegion +#include "nscore.h" // for nsAString, etc + +class nsIWidget; + +namespace mozilla { +namespace layers { + +class CanvasLayer; +class ColorLayer; +class ContainerLayer; +class ImageFactory; +class ImageLayer; +class Layer; +class PaintLayerContext; +class PaintedLayer; +class ReadbackLayer; + +/** + * This is a cairo/Thebes-only, main-thread-only implementation of layers. + * + * In each transaction, the client sets up the layer tree and then during + * the drawing phase, each PaintedLayer is painted directly into the target + * context (with appropriate clipping and Push/PopGroups performed + * between layers). + */ +class BasicLayerManager final : public LayerManager { + public: + enum BasicLayerManagerType { BLM_WIDGET, BLM_OFFSCREEN, BLM_INACTIVE }; + /** + * Construct a BasicLayerManager which will have no default + * target context. SetDefaultTarget or BeginTransactionWithTarget + * must be called for any rendering to happen. PaintedLayers will not + * be retained. + */ + explicit BasicLayerManager(BasicLayerManagerType aType); + /** + * Construct a BasicLayerManager which will have no default + * target context. SetDefaultTarget or BeginTransactionWithTarget + * must be called for any rendering to happen. PaintedLayers will be + * retained; that is, we will try to retain the visible contents of + * PaintedLayers as cairo surfaces. We create PaintedLayer buffers by + * creating similar surfaces to the default target context, or to + * aWidget's GetThebesSurface if there is no default target context, or + * to the passed-in context if there is no widget and no default + * target context. + * + * This does not keep a strong reference to the widget, so the caller + * must ensure that the widget outlives the layer manager or call + * ClearWidget before the widget dies. + */ + explicit BasicLayerManager(nsIWidget* aWidget); + + protected: + virtual ~BasicLayerManager(); + + public: + BasicLayerManager* AsBasicLayerManager() override { return this; } + + /** + * Set the default target context that will be used when BeginTransaction + * is called. This can only be called outside a transaction. + * + * aDoubleBuffering can request double-buffering for drawing to the + * default target. When BUFFERED, the layer manager avoids blitting + * temporary results to aContext and then overpainting them with final + * results, by using a temporary buffer when necessary. In BUFFERED + * mode we always completely overwrite the contents of aContext's + * destination surface (within the clip region) using OP_SOURCE. + */ + void SetDefaultTarget(gfxContext* aContext); + virtual void SetDefaultTargetConfiguration(BufferMode aDoubleBuffering, + ScreenRotation aRotation); + gfxContext* GetDefaultTarget() { return mDefaultTarget; } + + nsIWidget* GetRetainerWidget() { return mWidget; } + void ClearRetainerWidget() { mWidget = nullptr; } + + virtual bool IsWidgetLayerManager() override { return mWidget != nullptr; } + virtual bool IsInactiveLayerManager() override { + return mType == BLM_INACTIVE; + } + + virtual bool BeginTransaction(const nsCString& aURL = nsCString()) override; + virtual bool BeginTransactionWithTarget( + gfxContext* aTarget, const nsCString& aURL = nsCString()) override; + virtual bool EndEmptyTransaction( + EndTransactionFlags aFlags = END_DEFAULT) override; + virtual void EndTransaction( + DrawPaintedLayerCallback aCallback, void* aCallbackData, + EndTransactionFlags aFlags = END_DEFAULT) override; + void AbortTransaction(); + + virtual void SetRoot(Layer* aLayer) override; + + virtual already_AddRefed CreatePaintedLayer() override; + virtual already_AddRefed CreateContainerLayer() override; + virtual already_AddRefed CreateImageLayer() override; + virtual already_AddRefed CreateCanvasLayer() override; + virtual already_AddRefed CreateColorLayer() override; + virtual already_AddRefed CreateReadbackLayer() override; + virtual ImageFactory* GetImageFactory(); + + virtual LayersBackend GetBackendType() override { + return LayersBackend::LAYERS_BASIC; + } + virtual void GetBackendName(nsAString& name) override { + name.AssignLiteral("Basic"); + } + + bool InConstruction() { return mPhase == PHASE_CONSTRUCTION; } +#ifdef DEBUG + bool InDrawing() { return mPhase == PHASE_DRAWING; } + bool InForward() { return mPhase == PHASE_FORWARD; } +#endif + bool InTransaction() { return mPhase != PHASE_NONE; } + + gfxContext* GetTarget() { return mTarget; } + void SetTarget(gfxContext* aTarget) { + mUsingDefaultTarget = false; + mTarget = aTarget; + } + bool IsRetained() { return mWidget != nullptr; } + + virtual const char* Name() const override { return "Basic"; } + + // Clear the cached contents of this layer tree. + virtual void ClearCachedResources(Layer* aSubtree = nullptr) override; + + void SetTransactionIncomplete() { mTransactionIncomplete = true; } + bool IsTransactionIncomplete() { return mTransactionIncomplete; } + + struct PushedGroup { + PushedGroup() + : mFinalTarget(nullptr), + mNeedsClipToVisibleRegion(false), + mOperator(gfx::CompositionOp::OP_COUNT), + mOpacity(0.0f) {} + gfxContext* mFinalTarget; + RefPtr mGroupTarget; + nsIntRegion mVisibleRegion; + bool mNeedsClipToVisibleRegion; + gfx::IntPoint mGroupOffset; + gfx::CompositionOp mOperator; + gfx::Float mOpacity; + RefPtr mMaskSurface; + gfx::Matrix mMaskTransform; + }; + + // Construct a PushedGroup for a specific layer. + // Return false if it has some errors in PushGroupForLayer(). Then, the + // "aGroupResult" is unavailable for future using. + bool PushGroupForLayer(gfxContext* aContext, Layer* aLayerContext, + const nsIntRegion& aRegion, PushedGroup& aGroupResult); + + void PopGroupForLayer(PushedGroup& aGroup); + + virtual bool IsCompositingCheap() override { return false; } + virtual int32_t GetMaxTextureSize() const override { return INT32_MAX; } + bool CompositorMightResample() { return mCompositorMightResample; } + + TimeStamp GetCompositionTime() const { return mCompositionTime; } + + protected: + enum TransactionPhase { + PHASE_NONE, + PHASE_CONSTRUCTION, + PHASE_DRAWING, + PHASE_FORWARD + }; + TransactionPhase mPhase; + + // This is the main body of the PaintLayer routine which will if it has + // children, recurse into PaintLayer() otherwise it will paint using the + // underlying Paint() method of the Layer. It will not do both. + void PaintSelfOrChildren(PaintLayerContext& aPaintContext, + gfxContext* aGroupTarget); + + // Paint the group onto the underlying target. This is used by PaintLayer to + // flush the group to the underlying target. + void FlushGroup(PaintLayerContext& aPaintContext, + bool aNeedsClipToVisibleRegion); + + // Paints aLayer to mTarget. + void PaintLayer(gfxContext* aTarget, Layer* aLayer, + DrawPaintedLayerCallback aCallback, void* aCallbackData); + + // Clear the contents of a layer + void ClearLayer(Layer* aLayer); + + bool EndTransactionInternal(DrawPaintedLayerCallback aCallback, + void* aCallbackData, + EndTransactionFlags aFlags = END_DEFAULT); + + void FlashWidgetUpdateArea(gfxContext* aContext); + + void SetCompositionTime(TimeStamp aTimeStamp) { + mCompositionTime = aTimeStamp; + } + + // Widget whose surface should be used as the basis for PaintedLayer + // buffers. + nsIWidget* mWidget; + // The default context for BeginTransaction. + RefPtr mDefaultTarget; + // The context to draw into. + RefPtr mTarget; + // Image factory we use. + RefPtr mFactory; + + BufferMode mDoubleBuffering; + BasicLayerManagerType mType; + bool mUsingDefaultTarget; + bool mTransactionIncomplete; + bool mCompositorMightResample; + + TimeStamp mCompositionTime; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_BASICLAYERS_H */ diff --git a/gfx/layers/basic/BasicLayersImpl.cpp b/gfx/layers/basic/BasicLayersImpl.cpp new file mode 100644 index 0000000000..80c5df5f51 --- /dev/null +++ b/gfx/layers/basic/BasicLayersImpl.cpp @@ -0,0 +1,219 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "BasicLayersImpl.h" +#include // for operator new +#include "Layers.h" // for Layer, etc +#include "basic/BasicImplData.h" // for BasicImplData +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/DebugOnly.h" // for DebugOnly +#include "mozilla/layers/CompositorTypes.h" +#include "mozilla/layers/ISurfaceAllocator.h" +#include "AutoMaskData.h" + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +bool GetMaskData(Layer* aMaskLayer, const Point& aDeviceOffset, + AutoMoz2DMaskData* aMaskData) { + if (aMaskLayer) { + RefPtr surface = + static_cast(aMaskLayer->ImplData()) + ->GetAsSourceSurface(); + if (surface) { + Matrix transform; + Matrix4x4 effectiveTransform = aMaskLayer->GetEffectiveTransform(); + DebugOnly maskIs2D = effectiveTransform.CanDraw2D(&transform); + NS_ASSERTION(maskIs2D, "How did we end up with a 3D transform here?!"); + transform.PostTranslate(-aDeviceOffset.x, -aDeviceOffset.y); + aMaskData->Construct(transform, surface); + return true; + } + } + return false; +} + +already_AddRefed GetMaskForLayer(Layer* aLayer, + Matrix* aMaskTransform) { + if (!aLayer->GetMaskLayer()) { + return nullptr; + } + + MOZ_ASSERT(aMaskTransform); + + AutoMoz2DMaskData mask; + if (GetMaskData(aLayer->GetMaskLayer(), Point(), &mask)) { + *aMaskTransform = mask.GetTransform(); + RefPtr surf = mask.GetSurface(); + return surf.forget(); + } + + return nullptr; +} + +void PaintWithMask(gfxContext* aContext, float aOpacity, Layer* aMaskLayer) { + AutoMoz2DMaskData mask; + if (GetMaskData(aMaskLayer, Point(), &mask)) { + aContext->SetMatrix(mask.GetTransform()); + aContext->Mask(mask.GetSurface(), aOpacity); + return; + } + + // if there is no mask, just paint normally + aContext->Paint(aOpacity); +} + +void FillRectWithMask(DrawTarget* aDT, const Rect& aRect, + const DeviceColor& aColor, const DrawOptions& aOptions, + SourceSurface* aMaskSource, + const Matrix* aMaskTransform) { + if (aMaskSource && aMaskTransform) { + aDT->PushClipRect(aRect); + Matrix oldTransform = aDT->GetTransform(); + + aDT->SetTransform(*aMaskTransform); + aDT->MaskSurface(ColorPattern(aColor), aMaskSource, Point(), aOptions); + aDT->SetTransform(oldTransform); + aDT->PopClip(); + return; + } + + aDT->FillRect(aRect, ColorPattern(aColor), aOptions); +} +void FillRectWithMask(DrawTarget* aDT, const gfx::Point& aDeviceOffset, + const Rect& aRect, const DeviceColor& aColor, + const DrawOptions& aOptions, Layer* aMaskLayer) { + AutoMoz2DMaskData mask; + if (GetMaskData(aMaskLayer, aDeviceOffset, &mask)) { + const Matrix& maskTransform = mask.GetTransform(); + FillRectWithMask(aDT, aRect, aColor, aOptions, mask.GetSurface(), + &maskTransform); + return; + } + + FillRectWithMask(aDT, aRect, aColor, aOptions); +} + +void FillRectWithMask(DrawTarget* aDT, const Rect& aRect, + SourceSurface* aSurface, SamplingFilter aSamplingFilter, + const DrawOptions& aOptions, ExtendMode aExtendMode, + SourceSurface* aMaskSource, const Matrix* aMaskTransform, + const Matrix* aSurfaceTransform) { + if (aMaskSource && aMaskTransform) { + aDT->PushClipRect(aRect); + Matrix oldTransform = aDT->GetTransform(); + + Matrix inverseMask = *aMaskTransform; + inverseMask.Invert(); + + Matrix transform = oldTransform * inverseMask; + if (aSurfaceTransform) { + transform = (*aSurfaceTransform) * transform; + } + + SurfacePattern source(aSurface, aExtendMode, transform, aSamplingFilter); + + aDT->SetTransform(*aMaskTransform); + aDT->MaskSurface(source, aMaskSource, Point(0, 0), aOptions); + + aDT->SetTransform(oldTransform); + aDT->PopClip(); + return; + } + + aDT->FillRect( + aRect, + SurfacePattern(aSurface, aExtendMode, + aSurfaceTransform ? (*aSurfaceTransform) : Matrix(), + aSamplingFilter), + aOptions); +} + +void FillRectWithMask(DrawTarget* aDT, const gfx::Point& aDeviceOffset, + const Rect& aRect, SourceSurface* aSurface, + SamplingFilter aSamplingFilter, + const DrawOptions& aOptions, Layer* aMaskLayer) { + AutoMoz2DMaskData mask; + if (GetMaskData(aMaskLayer, aDeviceOffset, &mask)) { + const Matrix& maskTransform = mask.GetTransform(); + FillRectWithMask(aDT, aRect, aSurface, aSamplingFilter, aOptions, + ExtendMode::CLAMP, mask.GetSurface(), &maskTransform); + return; + } + + FillRectWithMask(aDT, aRect, aSurface, aSamplingFilter, aOptions, + ExtendMode::CLAMP); +} + +void FillPathWithMask(DrawTarget* aDT, const Path* aPath, const Rect& aClipRect, + const DeviceColor& aColor, const DrawOptions& aOptions, + SourceSurface* aMaskSource, + const Matrix* aMaskTransform) { + if (aMaskSource && aMaskTransform) { + aDT->PushClipRect(aClipRect); + Matrix oldTransform = aDT->GetTransform(); + + aDT->SetTransform(*aMaskTransform); + aDT->MaskSurface(ColorPattern(aColor), aMaskSource, Point(), aOptions); + aDT->SetTransform(oldTransform); + aDT->PopClip(); + return; + } + + aDT->Fill(aPath, ColorPattern(aColor), aOptions); +} + +void FillPathWithMask(DrawTarget* aDT, const Path* aPath, const Rect& aClipRect, + SourceSurface* aSurface, SamplingFilter aSamplingFilter, + const DrawOptions& aOptions, ExtendMode aExtendMode, + SourceSurface* aMaskSource, const Matrix* aMaskTransform, + const Matrix* aSurfaceTransform) { + if (aMaskSource && aMaskTransform) { + aDT->PushClipRect(aClipRect); + Matrix oldTransform = aDT->GetTransform(); + + Matrix inverseMask = *aMaskTransform; + inverseMask.Invert(); + + Matrix transform = oldTransform * inverseMask; + if (aSurfaceTransform) { + transform = (*aSurfaceTransform) * transform; + } + + SurfacePattern source(aSurface, aExtendMode, transform, aSamplingFilter); + + aDT->SetTransform(*aMaskTransform); + aDT->MaskSurface(source, aMaskSource, Point(0, 0), aOptions); + aDT->SetTransform(oldTransform); + aDT->PopClip(); + return; + } + + aDT->Fill(aPath, + SurfacePattern(aSurface, aExtendMode, + aSurfaceTransform ? (*aSurfaceTransform) : Matrix(), + aSamplingFilter), + aOptions); +} + +BasicImplData* ToData(Layer* aLayer) { + return static_cast(aLayer->ImplData()); +} + +gfx::CompositionOp GetEffectiveOperator(Layer* aLayer) { + CompositionOp op = aLayer->GetEffectiveMixBlendMode(); + + if (op != CompositionOp::OP_OVER) { + return op; + } + + return ToData(aLayer)->GetOperator(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/basic/BasicLayersImpl.h b/gfx/layers/basic/BasicLayersImpl.h new file mode 100644 index 0000000000..37d968ca80 --- /dev/null +++ b/gfx/layers/basic/BasicLayersImpl.h @@ -0,0 +1,145 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_BASICLAYERSIMPL_H +#define GFX_BASICLAYERSIMPL_H + +#include "BasicImplData.h" // for BasicImplData +#include "BasicLayers.h" // for BasicLayerManager +#include "ReadbackLayer.h" // for ReadbackLayer +#include "gfxContext.h" // for gfxContext, etc +#include "mozilla/Attributes.h" // for MOZ_STACK_CLASS +#include "mozilla/Maybe.h" // for Maybe +#include "nsDebug.h" // for NS_ASSERTION +#include "nsISupportsImpl.h" // for gfxContext::Release, etc +#include "nsRegion.h" // for nsIntRegion + +namespace mozilla { +namespace gfx { +class DrawTarget; +} // namespace gfx + +namespace layers { + +class AutoMoz2DMaskData; +class Layer; + +class AutoSetOperator { + typedef mozilla::gfx::CompositionOp CompositionOp; + + public: + AutoSetOperator(gfxContext* aContext, CompositionOp aOperator) { + if (aOperator != CompositionOp::OP_OVER) { + aContext->SetOp(aOperator); + mContext = aContext; + } + } + ~AutoSetOperator() { + if (mContext) { + mContext->SetOp(CompositionOp::OP_OVER); + } + } + + private: + RefPtr mContext; +}; + +class BasicReadbackLayer : public ReadbackLayer, public BasicImplData { + public: + explicit BasicReadbackLayer(BasicLayerManager* aLayerManager) + : ReadbackLayer(aLayerManager, static_cast(this)) { + MOZ_COUNT_CTOR(BasicReadbackLayer); + } + + protected: + MOZ_COUNTED_DTOR_OVERRIDE(BasicReadbackLayer) + + public: + void SetVisibleRegion(const LayerIntRegion& aRegion) override { + NS_ASSERTION(BasicManager()->InConstruction(), + "Can only set properties in construction phase"); + ReadbackLayer::SetVisibleRegion(aRegion); + } + + protected: + BasicLayerManager* BasicManager() { + return static_cast(mManager); + } +}; + +/* + * Extract a mask surface for a mask layer + * Returns true and through outparams a surface for the mask layer if + * a mask layer is present and has a valid surface and transform; + * false otherwise. + * The transform for the layer will be put in aMaskData + */ +bool GetMaskData(Layer* aMaskLayer, const gfx::Point& aDeviceOffset, + AutoMoz2DMaskData* aMaskData); + +already_AddRefed GetMaskForLayer( + Layer* aLayer, gfx::Matrix* aMaskTransform); + +// Paint the current source to a context using a mask, if present +void PaintWithMask(gfxContext* aContext, float aOpacity, Layer* aMaskLayer); + +// Fill the rect with the source, using a mask and opacity, if present +void FillRectWithMask(gfx::DrawTarget* aDT, const gfx::Rect& aRect, + const gfx::DeviceColor& aColor, + const gfx::DrawOptions& aOptions, + gfx::SourceSurface* aMaskSource = nullptr, + const gfx::Matrix* aMaskTransform = nullptr); +void FillRectWithMask(gfx::DrawTarget* aDT, const gfx::Rect& aRect, + gfx::SourceSurface* aSurface, + gfx::SamplingFilter aSamplingFilter, + const gfx::DrawOptions& aOptions, + gfx::ExtendMode aExtendMode, + gfx::SourceSurface* aMaskSource = nullptr, + const gfx::Matrix* aMaskTransform = nullptr, + const gfx::Matrix* aSurfaceTransform = nullptr); +void FillRectWithMask(gfx::DrawTarget* aDT, const gfx::Point& aDeviceOffset, + const gfx::Rect& aRect, gfx::SourceSurface* aSurface, + gfx::SamplingFilter aSamplingFilter, + const gfx::DrawOptions& aOptions, Layer* aMaskLayer); +void FillRectWithMask(gfx::DrawTarget* aDT, const gfx::Point& aDeviceOffset, + const gfx::Rect& aRect, const gfx::DeviceColor& aColor, + const gfx::DrawOptions& aOptions, Layer* aMaskLayer); + +void FillPathWithMask(gfx::DrawTarget* aDT, const gfx::Path* aPath, + const gfx::Rect& aClipRect, + const gfx::DeviceColor& aColor, + const gfx::DrawOptions& aOptions, + gfx::SourceSurface* aMaskSource = nullptr, + const gfx::Matrix* aMaskTransform = nullptr); +void FillPathWithMask(gfx::DrawTarget* aDT, const gfx::Path* aPath, + const gfx::Rect& aClipRect, gfx::SourceSurface* aSurface, + gfx::SamplingFilter aSamplingFilter, + const gfx::DrawOptions& aOptions, + gfx::ExtendMode aExtendMode, + gfx::SourceSurface* aMaskSource, + const gfx::Matrix* aMaskTransform, + const gfx::Matrix* aSurfaceTransform); + +BasicImplData* ToData(Layer* aLayer); + +/** + * Returns the operator to be used when blending and compositing this layer. + * Currently there is no way to specify both a blending and a compositing + * operator other than normal and source over respectively. + * + * If the layer has + * an effective blend mode operator other than normal, as returned by + * GetEffectiveMixBlendMode, this operator is used for blending, and source + * over is used for compositing. + * If the blend mode for this layer is normal, the compositing operator + * returned by GetOperator is used. + */ +gfx::CompositionOp GetEffectiveOperator(Layer* aLayer); + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/basic/BasicPaintedLayer.cpp b/gfx/layers/basic/BasicPaintedLayer.cpp new file mode 100644 index 0000000000..c8c9750831 --- /dev/null +++ b/gfx/layers/basic/BasicPaintedLayer.cpp @@ -0,0 +1,239 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "BasicPaintedLayer.h" +#include // for uint32_t +#include "GeckoProfiler.h" // for AUTO_PROFILER_LABEL +#include "ReadbackLayer.h" // for ReadbackLayer, ReadbackSink +#include "ReadbackProcessor.h" // for ReadbackProcessor::Update, etc +#include "RenderTrace.h" // for RenderTraceInvalidateEnd, etc +#include "BasicLayersImpl.h" // for AutoMaskData, etc +#include "gfxContext.h" // for gfxContext, etc +#include "gfxRect.h" // for gfxRect +#include "gfxUtils.h" // for gfxUtils +#include "mozilla/gfx/2D.h" // for DrawTarget +#include "mozilla/gfx/BaseRect.h" // for BaseRect +#include "mozilla/gfx/Matrix.h" // for Matrix +#include "mozilla/gfx/Rect.h" // for Rect, IntRect +#include "mozilla/gfx/Types.h" // for Float, etc +#include "mozilla/layers/LayersTypes.h" +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsISupportsImpl.h" // for gfxContext::Release, etc +#include "nsPoint.h" // for nsIntPoint +#include "nsRect.h" // for mozilla::gfx::IntRect +#include "nsTArray.h" // for nsTArray, nsTArray_Impl +#include "AutoMaskData.h" +#include "gfx2DGlue.h" + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +static nsIntRegion IntersectWithClip(const nsIntRegion& aRegion, + gfxContext* aContext) { + gfxRect clip = aContext->GetClipExtents(); + nsIntRegion result; + result.And(aRegion, IntRect::RoundOut(clip.X(), clip.Y(), clip.Width(), + clip.Height())); + return result; +} + +void BasicPaintedLayer::PaintThebes( + gfxContext* aContext, Layer* aMaskLayer, + LayerManager::DrawPaintedLayerCallback aCallback, void* aCallbackData) { + AUTO_PROFILER_LABEL("BasicPaintedLayer::PaintThebes", GRAPHICS); + + NS_ASSERTION(BasicManager()->InDrawing(), "Can only draw in drawing phase"); + + float opacity = GetEffectiveOpacity(); + CompositionOp effectiveOperator = GetEffectiveOperator(this); + + if (!BasicManager()->IsRetained()) { + ClearValidRegion(); + mContentClient->Clear(); + + nsIntRegion toDraw = + IntersectWithClip(GetLocalVisibleRegion().ToUnknownRegion(), aContext); + + RenderTraceInvalidateStart(this, "FFFF00", toDraw.GetBounds()); + + if (!toDraw.IsEmpty() && !IsHidden()) { + if (!aCallback) { + BasicManager()->SetTransactionIncomplete(); + return; + } + + aContext->Save(); + + bool needsGroup = opacity != 1.0 || + effectiveOperator != CompositionOp::OP_OVER || + aMaskLayer; + RefPtr context = nullptr; + BasicLayerManager::PushedGroup group; + bool availableGroup = false; + + if (needsGroup) { + availableGroup = + BasicManager()->PushGroupForLayer(aContext, this, toDraw, group); + if (availableGroup) { + context = group.mGroupTarget; + } + } else { + context = aContext; + } + if (context) { + DrawTarget* target = context->GetDrawTarget(); + bool oldAA = target->GetPermitSubpixelAA(); + SetAntialiasingFlags(this, target); + aCallback(this, context, toDraw, toDraw, DrawRegionClip::NONE, + nsIntRegion(), aCallbackData); + target->SetPermitSubpixelAA(oldAA); + } + if (needsGroup && availableGroup) { + BasicManager()->PopGroupForLayer(group); + } + + aContext->Restore(); + } + + RenderTraceInvalidateEnd(this, "FFFF00"); + return; + } + + if (BasicManager()->IsTransactionIncomplete()) return; + + gfxRect clipExtents; + clipExtents = aContext->GetClipExtents(); + + // Pull out the mask surface and transform here, because the mask + // is internal to basic layers + AutoMoz2DMaskData mask; + SourceSurface* maskSurface = nullptr; + Matrix maskTransform; + if (GetMaskData(aMaskLayer, aContext->GetDeviceOffset(), &mask)) { + maskSurface = mask.GetSurface(); + maskTransform = mask.GetTransform(); + } + + if (!IsHidden() && !clipExtents.IsEmpty()) { + mContentClient->DrawTo(this, aContext->GetDrawTarget(), opacity, + effectiveOperator, maskSurface, &maskTransform); + } +} + +void BasicPaintedLayer::Validate( + LayerManager::DrawPaintedLayerCallback aCallback, void* aCallbackData, + ReadbackProcessor* aReadback) { + if (!mContentClient) { + // This client will have a null Forwarder, which means it will not have + // a ContentHost on the other side. + mContentClient = new ContentClientBasic(mBackend); + } + + if (!BasicManager()->IsRetained()) { + return; + } + + nsTArray readbackUpdates; + if (aReadback && UsedForReadback()) { + aReadback->GetPaintedLayerUpdates(this, &readbackUpdates); + } + + uint32_t flags = 0; +#ifndef MOZ_WIDGET_ANDROID + if (BasicManager()->CompositorMightResample()) { + flags |= ContentClient::PAINT_WILL_RESAMPLE; + } + if (!(flags & ContentClient::PAINT_WILL_RESAMPLE)) { + if (MayResample()) { + flags |= ContentClient::PAINT_WILL_RESAMPLE; + } + } +#endif + if (mDrawAtomically) { + flags |= ContentClient::PAINT_NO_ROTATION; + } + PaintState state = mContentClient->BeginPaint(this, flags); + SubtractFromValidRegion(state.mRegionToInvalidate); + + DrawTarget* target = mContentClient->BorrowDrawTargetForPainting(state); + if (target && target->IsValid()) { + // The area that became invalid and is visible needs to be repainted + // (this could be the whole visible area if our buffer switched + // from RGB to RGBA, because we might need to repaint with + // subpixel AA) + state.mRegionToInvalidate.And(state.mRegionToInvalidate, + GetLocalVisibleRegion().ToUnknownRegion()); + SetAntialiasingFlags(this, target); + + RenderTraceInvalidateStart(this, "FFFF00", state.mRegionToDraw.GetBounds()); + + RefPtr ctx = + gfxContext::CreatePreservingTransformOrNull(target); + MOZ_ASSERT(ctx); // already checked the target above + + PaintBuffer(ctx, state.mRegionToDraw, state.mRegionToDraw, + state.mRegionToInvalidate, state.mClip, aCallback, + aCallbackData); + MOZ_LAYERS_LOG_IF_SHADOWABLE(this, + ("Layer::Mutated(%p) PaintThebes", this)); + Mutated(); + ctx = nullptr; + mContentClient->ReturnDrawTarget(target); + target = nullptr; + + RenderTraceInvalidateEnd(this, "FFFF00"); + } else { + if (target) { + mContentClient->ReturnDrawTarget(target); + target = nullptr; + } + + // It's possible that state.mRegionToInvalidate is nonempty here, + // if we are shrinking the valid region to nothing. So use mRegionToDraw + // instead. + NS_WARNING_ASSERTION( + state.mRegionToDraw.IsEmpty(), + "No context when we have something to draw, resource exhaustion?"); + } + + for (uint32_t i = 0; i < readbackUpdates.Length(); ++i) { + ReadbackProcessor::Update& update = readbackUpdates[i]; + nsIntPoint offset = update.mLayer->GetBackgroundLayerOffset(); + RefPtr dt = update.mLayer->GetSink()->BeginUpdate( + update.mUpdateRect + offset, update.mSequenceCounter); + if (dt) { + NS_ASSERTION(GetEffectiveOpacity() == 1.0, + "Should only read back opaque layers"); + NS_ASSERTION(!GetMaskLayer(), + "Should only read back layers without masks"); + dt->SetTransform(dt->GetTransform().PreTranslate(offset.x, offset.y)); + mContentClient->DrawTo(this, dt, 1.0, CompositionOp::OP_OVER, nullptr, + nullptr); + update.mLayer->GetSink()->EndUpdate(update.mUpdateRect + offset); + } + } +} + +already_AddRefed BasicLayerManager::CreatePaintedLayer() { + NS_ASSERTION(InConstruction(), "Only allowed in construction phase"); + + BackendType backend = gfxPlatform::GetPlatform()->GetDefaultContentBackend(); + + if (mDefaultTarget) { + backend = mDefaultTarget->GetDrawTarget()->GetBackendType(); + } else if (mType == BLM_WIDGET) { + backend = gfxPlatform::GetPlatform()->GetContentBackendFor( + LayersBackend::LAYERS_BASIC); + } + + RefPtr layer = new BasicPaintedLayer(this, backend); + return layer.forget(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/basic/BasicPaintedLayer.h b/gfx/layers/basic/BasicPaintedLayer.h new file mode 100644 index 0000000000..de6184f34c --- /dev/null +++ b/gfx/layers/basic/BasicPaintedLayer.h @@ -0,0 +1,121 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_BASICPAINTEDLAYER_H +#define GFX_BASICPAINTEDLAYER_H + +#include "Layers.h" // for PaintedLayer, LayerManager, etc +#include "RotatedBuffer.h" // for RotatedBuffer, etc +#include "BasicImplData.h" // for BasicImplData +#include "BasicLayers.h" // for BasicLayerManager +#include "gfxPoint.h" // for gfxPoint +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/gfx/BasePoint.h" // for BasePoint +#include "mozilla/layers/ContentClient.h" // for ContentClientBasic +#include "mozilla/mozalloc.h" // for operator delete +#include "nsDebug.h" // for NS_ASSERTION +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "nsRegion.h" // for nsIntRegion +class gfxContext; + +namespace mozilla { +namespace layers { + +class ReadbackProcessor; + +class BasicPaintedLayer : public PaintedLayer, public BasicImplData { + public: + typedef ContentClient::PaintState PaintState; + typedef ContentClient::ContentType ContentType; + + BasicPaintedLayer(BasicLayerManager* aLayerManager, gfx::BackendType aBackend) + : PaintedLayer(aLayerManager, static_cast(this)), + mContentClient(nullptr), + mBackend(aBackend) { + MOZ_COUNT_CTOR(BasicPaintedLayer); + } + + protected: + MOZ_COUNTED_DTOR_OVERRIDE(BasicPaintedLayer) + + public: + void SetVisibleRegion(const LayerIntRegion& aRegion) override { + NS_ASSERTION(BasicManager()->InConstruction(), + "Can only set properties in construction phase"); + PaintedLayer::SetVisibleRegion(aRegion); + } + void InvalidateRegion(const nsIntRegion& aRegion) override { + NS_ASSERTION(BasicManager()->InConstruction(), + "Can only set properties in construction phase"); + mInvalidRegion.Add(aRegion); + UpdateValidRegionAfterInvalidRegionChanged(); + } + + void PaintThebes(gfxContext* aContext, Layer* aMaskLayer, + LayerManager::DrawPaintedLayerCallback aCallback, + void* aCallbackData) override; + + void Validate(LayerManager::DrawPaintedLayerCallback aCallback, + void* aCallbackData, ReadbackProcessor* aReadback) override; + + void ClearCachedResources() override { + if (mContentClient) { + mContentClient->Clear(); + } + ClearValidRegion(); + } + + void ComputeEffectiveTransforms( + const gfx::Matrix4x4& aTransformToSurface) override { + if (!BasicManager()->IsRetained()) { + // Don't do any snapping of our transform, since we're just going to + // draw straight through without intermediate buffers. + mEffectiveTransform = GetLocalTransform() * aTransformToSurface; + if (gfxPoint(0, 0) != mResidualTranslation) { + mResidualTranslation = gfxPoint(0, 0); + ClearValidRegion(); + } + ComputeEffectiveTransformForMaskLayers(aTransformToSurface); + return; + } + PaintedLayer::ComputeEffectiveTransforms(aTransformToSurface); + } + + BasicLayerManager* BasicManager() { + return static_cast(mManager); + } + + protected: + virtual void PaintBuffer(gfxContext* aContext, + const nsIntRegion& aRegionToDraw, + const nsIntRegion& aExtendedRegionToDraw, + const nsIntRegion& aRegionToInvalidate, + DrawRegionClip aClip, + LayerManager::DrawPaintedLayerCallback aCallback, + void* aCallbackData) { + if (!aCallback) { + BasicManager()->SetTransactionIncomplete(); + return; + } + aCallback(this, aContext, aExtendedRegionToDraw, aExtendedRegionToDraw, + aClip, aRegionToInvalidate, aCallbackData); + // Everything that's visible has been validated. Do this instead of just + // OR-ing with aRegionToDraw, since that can lead to a very complex region + // here (OR doesn't automatically simplify to the simplest possible + // representation of a region.) + nsIntRegion tmp; + tmp.Or(mVisibleRegion.ToUnknownRegion(), aExtendedRegionToDraw); + AddToValidRegion(tmp); + } + + RefPtr mContentClient; + gfx::BackendType mBackend; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/basic/MacIOSurfaceTextureHostBasic.cpp b/gfx/layers/basic/MacIOSurfaceTextureHostBasic.cpp new file mode 100644 index 0000000000..1de9ea8246 --- /dev/null +++ b/gfx/layers/basic/MacIOSurfaceTextureHostBasic.cpp @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MacIOSurfaceTextureHostBasic.h" +#include "mozilla/gfx/MacIOSurface.h" +#include "MacIOSurfaceHelpers.h" + +namespace mozilla { +namespace layers { + +MacIOSurfaceTextureSourceBasic::MacIOSurfaceTextureSourceBasic( + MacIOSurface* aSurface) + : mSurface(aSurface) { + MOZ_COUNT_CTOR(MacIOSurfaceTextureSourceBasic); +} + +MacIOSurfaceTextureSourceBasic::~MacIOSurfaceTextureSourceBasic() { + MOZ_COUNT_DTOR(MacIOSurfaceTextureSourceBasic); +} + +gfx::IntSize MacIOSurfaceTextureSourceBasic::GetSize() const { + return gfx::IntSize(mSurface->GetDevicePixelWidth(), + mSurface->GetDevicePixelHeight()); +} + +gfx::SurfaceFormat MacIOSurfaceTextureSourceBasic::GetFormat() const { + // Set the format the same way as CreateSourceSurfaceFromMacIOSurface. + return mSurface->GetFormat() == gfx::SurfaceFormat::NV12 + ? gfx::SurfaceFormat::B8G8R8X8 + : gfx::SurfaceFormat::B8G8R8A8; +} + +MacIOSurfaceTextureHostBasic::MacIOSurfaceTextureHostBasic( + TextureFlags aFlags, const SurfaceDescriptorMacIOSurface& aDescriptor) + : TextureHost(aFlags) { + mSurface = MacIOSurface::LookupSurface( + aDescriptor.surfaceId(), aDescriptor.scaleFactor(), + !aDescriptor.isOpaque(), aDescriptor.yUVColorSpace()); +} + +gfx::SourceSurface* MacIOSurfaceTextureSourceBasic::GetSurface( + gfx::DrawTarget* aTarget) { + if (!mSourceSurface) { + mSourceSurface = CreateSourceSurfaceFromMacIOSurface(mSurface); + } + return mSourceSurface; +} + +bool MacIOSurfaceTextureHostBasic::Lock() { + if (!mProvider) { + return false; + } + + if (!mTextureSource) { + mTextureSource = new MacIOSurfaceTextureSourceBasic(mSurface); + } + return true; +} + +void MacIOSurfaceTextureHostBasic::SetTextureSourceProvider( + TextureSourceProvider* aProvider) { + if (!aProvider->AsCompositor() || + !aProvider->AsCompositor()->AsBasicCompositor()) { + mTextureSource = nullptr; + return; + } + + mProvider = aProvider; + + if (mTextureSource) { + mTextureSource->SetTextureSourceProvider(aProvider); + } +} + +gfx::SurfaceFormat MacIOSurfaceTextureHostBasic::GetFormat() const { + return mSurface->HasAlpha() ? gfx::SurfaceFormat::R8G8B8A8 + : gfx::SurfaceFormat::B8G8R8X8; +} + +gfx::IntSize MacIOSurfaceTextureHostBasic::GetSize() const { + return gfx::IntSize(mSurface->GetDevicePixelWidth(), + mSurface->GetDevicePixelHeight()); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/basic/MacIOSurfaceTextureHostBasic.h b/gfx/layers/basic/MacIOSurfaceTextureHostBasic.h new file mode 100644 index 0000000000..9dd4c38a26 --- /dev/null +++ b/gfx/layers/basic/MacIOSurfaceTextureHostBasic.h @@ -0,0 +1,87 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_MACIOSURFACETEXTUREHOST_BASIC_H +#define MOZILLA_GFX_MACIOSURFACETEXTUREHOST_BASIC_H + +#include "mozilla/layers/BasicCompositor.h" +#include "mozilla/layers/TextureHostBasic.h" + +class MacIOSurface; + +namespace mozilla { +namespace layers { + +class BasicCompositor; + +/** + * A texture source meant for use with BasicCompositor. + * + * It does not own any GL texture, and attaches its shared handle to one of + * the compositor's temporary textures when binding. + */ +class MacIOSurfaceTextureSourceBasic : public TextureSourceBasic, + public TextureSource { + public: + explicit MacIOSurfaceTextureSourceBasic(MacIOSurface* aSurface); + virtual ~MacIOSurfaceTextureSourceBasic(); + + const char* Name() const override { return "MacIOSurfaceTextureSourceBasic"; } + + TextureSourceBasic* AsSourceBasic() override { return this; } + + gfx::IntSize GetSize() const override; + gfx::SurfaceFormat GetFormat() const override; + gfx::SourceSurface* GetSurface(gfx::DrawTarget* aTarget) override; + + void DeallocateDeviceData() override {} + + protected: + RefPtr mSurface; + RefPtr mSourceSurface; +}; + +/** + * A TextureHost for shared MacIOSurface + * + * Most of the logic actually happens in MacIOSurfaceTextureSourceBasic. + */ +class MacIOSurfaceTextureHostBasic : public TextureHost { + public: + MacIOSurfaceTextureHostBasic( + TextureFlags aFlags, const SurfaceDescriptorMacIOSurface& aDescriptor); + + void SetTextureSourceProvider(TextureSourceProvider* aProvider) override; + + bool Lock() override; + + gfx::SurfaceFormat GetFormat() const override; + + bool BindTextureSource(CompositableTextureSourceRef& aTexture) override { + aTexture = mTextureSource; + return !!aTexture; + } + + already_AddRefed GetAsSurface() override { + return nullptr; // XXX - implement this (for MOZ_DUMP_PAINTING) + } + + gfx::IntSize GetSize() const override; + MacIOSurface* GetMacIOSurface() override { return mSurface; } + +#ifdef MOZ_LAYERS_HAVE_LOG + const char* Name() override { return "MacIOSurfaceTextureHostBasic"; } +#endif + + protected: + RefPtr mTextureSource; + RefPtr mSurface; +}; + +} // namespace layers +} // namespace mozilla + +#endif // MOZILLA_GFX_MACIOSURFACETEXTUREHOSTOGL_BASIC_H diff --git a/gfx/layers/basic/TextureClientX11.cpp b/gfx/layers/basic/TextureClientX11.cpp new file mode 100644 index 0000000000..06f41e8d0b --- /dev/null +++ b/gfx/layers/basic/TextureClientX11.cpp @@ -0,0 +1,140 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/TextureClientX11.h" + +#include "mozilla/layers/CompositableClient.h" +#include "mozilla/layers/CompositableForwarder.h" +#include "mozilla/layers/ISurfaceAllocator.h" +#include "mozilla/layers/ShadowLayerUtilsX11.h" +#include "mozilla/layers/TextureForwarder.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Logging.h" +#include "gfx2DGlue.h" +#include "gfxPlatform.h" +#include "gfxXlibSurface.h" + +#include "mozilla/X11Util.h" +#include + +using namespace mozilla; +using namespace mozilla::gfx; + +namespace mozilla::layers { + +X11TextureData::X11TextureData(gfx::IntSize aSize, gfx::SurfaceFormat aFormat, + bool aClientDeallocation, bool aIsCrossProcess, + gfxXlibSurface* aSurface) + : mSize(aSize), + mFormat(aFormat), + mSurface(aSurface), + mClientDeallocation(aClientDeallocation), + mIsCrossProcess(aIsCrossProcess) { + MOZ_ASSERT(mSurface); +} + +bool X11TextureData::Lock(OpenMode aMode) { return true; } + +void X11TextureData::Unlock() { + if (mSurface && mIsCrossProcess) { + FinishX(DefaultXDisplay()); + } +} + +void X11TextureData::FillInfo(TextureData::Info& aInfo) const { + aInfo.size = mSize; + aInfo.format = mFormat; + aInfo.supportsMoz2D = true; + aInfo.hasIntermediateBuffer = false; + aInfo.hasSynchronization = false; + aInfo.canExposeMappedData = false; +} + +bool X11TextureData::Serialize(SurfaceDescriptor& aOutDescriptor) { + MOZ_ASSERT(mSurface); + if (!mSurface) { + return false; + } + + if (!mClientDeallocation) { + // Pass to the host the responsibility of freeing the pixmap. ReleasePixmap + // means the underlying pixmap will not be deallocated in mSurface's + // destructor. ToSurfaceDescriptor is at most called once per TextureClient. + mSurface->ReleasePixmap(); + } + + aOutDescriptor = SurfaceDescriptorX11(mSurface); + return true; +} + +already_AddRefed X11TextureData::BorrowDrawTarget() { + MOZ_ASSERT(mSurface); + if (!mSurface) { + return nullptr; + } + + IntSize size = mSurface->GetSize(); + RefPtr dt = + Factory::CreateDrawTargetForCairoSurface(mSurface->CairoSurface(), size); + + return dt.forget(); +} + +bool X11TextureData::UpdateFromSurface(gfx::SourceSurface* aSurface) { + RefPtr dt = BorrowDrawTarget(); + + if (!dt) { + return false; + } + + dt->CopySurface(aSurface, IntRect(IntPoint(), aSurface->GetSize()), + IntPoint()); + + return true; +} + +void X11TextureData::Deallocate(LayersIPCChannel*) { mSurface = nullptr; } + +TextureData* X11TextureData::CreateSimilar( + LayersIPCChannel* aAllocator, LayersBackend aLayersBackend, + TextureFlags aFlags, TextureAllocationFlags aAllocFlags) const { + return X11TextureData::Create(mSize, mFormat, aFlags, aAllocator); +} + +X11TextureData* X11TextureData::Create(gfx::IntSize aSize, + gfx::SurfaceFormat aFormat, + TextureFlags aFlags, + LayersIPCChannel* aAllocator) { + MOZ_ASSERT(aSize.width >= 0 && aSize.height >= 0); + if (aSize.width <= 0 || aSize.height <= 0 || + aSize.width > XLIB_IMAGE_SIDE_SIZE_LIMIT || + aSize.height > XLIB_IMAGE_SIDE_SIZE_LIMIT) { + gfxDebug() << "Asking for X11 surface of invalid size " << aSize.width + << "x" << aSize.height; + return nullptr; + } + gfxImageFormat imageFormat = SurfaceFormatToImageFormat(aFormat); + RefPtr surface = + gfxPlatform::GetPlatform()->CreateOffscreenSurface(aSize, imageFormat); + if (!surface || surface->GetType() != gfxSurfaceType::Xlib) { + NS_ERROR("creating Xlib surface failed!"); + return nullptr; + } + + gfxXlibSurface* xlibSurface = static_cast(surface.get()); + + bool crossProcess = !aAllocator->IsSameProcess(); + X11TextureData* texture = new X11TextureData( + aSize, aFormat, !!(aFlags & TextureFlags::DEALLOCATE_CLIENT), + crossProcess, xlibSurface); + if (crossProcess) { + FinishX(DefaultXDisplay()); + } + + return texture; +} + +} // namespace mozilla::layers diff --git a/gfx/layers/basic/TextureClientX11.h b/gfx/layers/basic/TextureClientX11.h new file mode 100644 index 0000000000..2089157c42 --- /dev/null +++ b/gfx/layers/basic/TextureClientX11.h @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_TEXTURECLIENT_X11_H +#define MOZILLA_GFX_TEXTURECLIENT_X11_H + +#include "mozilla/layers/TextureClient.h" +#include "ISurfaceAllocator.h" // For IsSurfaceDescriptorValid + +namespace mozilla { +namespace layers { + +class X11TextureData : public TextureData { + public: + static X11TextureData* Create(gfx::IntSize aSize, gfx::SurfaceFormat aFormat, + TextureFlags aFlags, + LayersIPCChannel* aAllocator); + + virtual bool Serialize(SurfaceDescriptor& aOutDescriptor) override; + + virtual bool Lock(OpenMode aMode) override; + + virtual void Unlock() override; + + virtual void FillInfo(TextureData::Info& aInfo) const override; + + virtual already_AddRefed BorrowDrawTarget() override; + + virtual void Deallocate(LayersIPCChannel*) override; + + virtual TextureData* CreateSimilar( + LayersIPCChannel* aAllocator, LayersBackend aLayersBackend, + TextureFlags aFlags = TextureFlags::DEFAULT, + TextureAllocationFlags aAllocFlags = ALLOC_DEFAULT) const override; + + virtual bool UpdateFromSurface(gfx::SourceSurface* aSurface) override; + + protected: + X11TextureData(gfx::IntSize aSize, gfx::SurfaceFormat aFormat, + bool aClientDeallocation, bool aIsCrossProcess, + gfxXlibSurface* aSurface); + + gfx::IntSize mSize; + gfx::SurfaceFormat mFormat; + RefPtr mSurface; + bool mClientDeallocation; + bool mIsCrossProcess; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/basic/TextureHostBasic.cpp b/gfx/layers/basic/TextureHostBasic.cpp new file mode 100644 index 0000000000..c42fc2874c --- /dev/null +++ b/gfx/layers/basic/TextureHostBasic.cpp @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TextureHostBasic.h" +#ifdef XP_MACOSX +# include "MacIOSurfaceTextureHostBasic.h" +#endif + +using namespace mozilla::gl; +using namespace mozilla::gfx; + +namespace mozilla { +namespace layers { + +already_AddRefed CreateTextureHostBasic( + const SurfaceDescriptor& aDesc, ISurfaceAllocator* aDeallocator, + LayersBackend aBackend, TextureFlags aFlags) { +#ifdef XP_MACOSX + if (aDesc.type() == SurfaceDescriptor::TSurfaceDescriptorMacIOSurface) { + const SurfaceDescriptorMacIOSurface& desc = + aDesc.get_SurfaceDescriptorMacIOSurface(); + return MakeAndAddRef(aFlags, desc); + } +#endif + return CreateBackendIndependentTextureHost(aDesc, aDeallocator, aBackend, + aFlags); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/basic/TextureHostBasic.h b/gfx/layers/basic/TextureHostBasic.h new file mode 100644 index 0000000000..f48bc89c97 --- /dev/null +++ b/gfx/layers/basic/TextureHostBasic.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_TEXTUREHOSTBASIC_H_ +#define MOZILLA_GFX_TEXTUREHOSTBASIC_H_ + +#include "CompositableHost.h" +#include "mozilla/layers/LayersSurfaces.h" +#include "mozilla/layers/TextureHost.h" +#include "mozilla/gfx/2D.h" + +namespace mozilla { +namespace layers { + +already_AddRefed CreateTextureHostBasic( + const SurfaceDescriptor& aDesc, ISurfaceAllocator* aDeallocator, + LayersBackend aBackend, TextureFlags aFlags); + +/** + * A texture source interface that can be used by the software Compositor. + */ +class TextureSourceBasic { + public: + TextureSourceBasic() : mFromYCBCR(false) {} + virtual ~TextureSourceBasic() = default; + virtual gfx::SourceSurface* GetSurface(gfx::DrawTarget* aTarget) = 0; + virtual void SetBufferTextureHost(BufferTextureHost* aTexture) {} + bool mFromYCBCR; // we to track sources from YCBCR so we can use a less + // accurate fast path for video +}; + +} // namespace layers +} // namespace mozilla + +#endif // MOZILLA_GFX_TEXTUREHOSTBASIC_H_ diff --git a/gfx/layers/basic/X11BasicCompositor.cpp b/gfx/layers/basic/X11BasicCompositor.cpp new file mode 100644 index 0000000000..d9cf164571 --- /dev/null +++ b/gfx/layers/basic/X11BasicCompositor.cpp @@ -0,0 +1,116 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "X11BasicCompositor.h" +#include "gfxPlatform.h" +#include "gfx2DGlue.h" +#include "gfxXlibSurface.h" +#include "gfxImageSurface.h" +#include "mozilla/X11Util.h" + +namespace mozilla { +using namespace mozilla::gfx; + +namespace layers { + +bool X11DataTextureSourceBasic::Update(gfx::DataSourceSurface* aSurface, + nsIntRegion* aDestRegion, + gfx::IntPoint* aSrcOffset) { + // Reallocate our internal X11 surface if we don't have a DrawTarget yet, + // or if we changed surface size or format since last update. + if (!mBufferDrawTarget || + (aSurface->GetSize() != mBufferDrawTarget->GetSize()) || + (aSurface->GetFormat() != mBufferDrawTarget->GetFormat())) { + RefPtr surf; + gfxImageFormat imageFormat = + SurfaceFormatToImageFormat(aSurface->GetFormat()); + Display* display = DefaultXDisplay(); + Screen* screen = DefaultScreenOfDisplay(display); + XRenderPictFormat* xrenderFormat = + gfxXlibSurface::FindRenderFormat(display, imageFormat); + + if (xrenderFormat) { + surf = gfxXlibSurface::Create(screen, xrenderFormat, aSurface->GetSize()); + } + + if (!surf) { + NS_WARNING("Couldn't create native surface, fallback to image surface"); + surf = new gfxImageSurface(aSurface->GetSize(), imageFormat); + } + + mBufferDrawTarget = + gfxPlatform::CreateDrawTargetForSurface(surf, aSurface->GetSize()); + } + + // Image contents have changed, upload to our DrawTarget + // If aDestRegion is null, means we're updating the whole surface + // Note : Incremental update with a source offset is only used on Mac. + NS_ASSERTION(!aSrcOffset, + "SrcOffset should not be used with linux OMTC basic"); + + if (aDestRegion) { + for (auto iter = aDestRegion->RectIter(); !iter.Done(); iter.Next()) { + const IntRect& rect = iter.Get(); + IntRect srcRect(rect.X(), rect.Y(), rect.Width(), rect.Height()); + IntPoint dstPoint(rect.X(), rect.Y()); + + // We're uploading regions to our buffer, so let's just copy contents over + mBufferDrawTarget->CopySurface(aSurface, srcRect, dstPoint); + } + } else { + // We're uploading the whole buffer, so let's just copy the full surface + IntSize size = aSurface->GetSize(); + mBufferDrawTarget->CopySurface( + aSurface, IntRect(0, 0, size.width, size.height), IntPoint(0, 0)); + } + + return true; +} + +TextureSourceBasic* X11DataTextureSourceBasic::AsSourceBasic() { return this; } + +IntSize X11DataTextureSourceBasic::GetSize() const { + if (!mBufferDrawTarget) { + NS_WARNING("Trying to query the size of an uninitialized TextureSource"); + return IntSize(0, 0); + } + return mBufferDrawTarget->GetSize(); +} + +gfx::SurfaceFormat X11DataTextureSourceBasic::GetFormat() const { + if (!mBufferDrawTarget) { + NS_WARNING("Trying to query the format of an uninitialized TextureSource"); + return gfx::SurfaceFormat::UNKNOWN; + } + return mBufferDrawTarget->GetFormat(); +} + +SourceSurface* X11DataTextureSourceBasic::GetSurface(DrawTarget* aTarget) { + RefPtr surface; + if (mBufferDrawTarget) { + surface = mBufferDrawTarget->Snapshot(); + return surface.get(); + } + return nullptr; +} + +void X11DataTextureSourceBasic::DeallocateDeviceData() { + mBufferDrawTarget = nullptr; +} + +already_AddRefed X11BasicCompositor::CreateDataTextureSource( + TextureFlags aFlags) { + RefPtr result = new X11DataTextureSourceBasic(); + return result.forget(); +} + +void X11BasicCompositor::EndFrame() { + BasicCompositor::EndFrame(); + XFlush(DefaultXDisplay()); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/basic/X11BasicCompositor.h b/gfx/layers/basic/X11BasicCompositor.h new file mode 100644 index 0000000000..44b6acbd93 --- /dev/null +++ b/gfx/layers/basic/X11BasicCompositor.h @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_X11BASICCOMPOSITOR_H +#define MOZILLA_GFX_X11BASICCOMPOSITOR_H + +#include "mozilla/layers/BasicCompositor.h" +#include "mozilla/layers/X11TextureSourceBasic.h" +#include "mozilla/layers/TextureHostBasic.h" +#include "gfxXlibSurface.h" +#include "mozilla/gfx/2D.h" + +namespace mozilla { +namespace layers { + +// TextureSource for Image-backed surfaces. +class X11DataTextureSourceBasic : public DataTextureSource, + public TextureSourceBasic { + public: + X11DataTextureSourceBasic() = default; + + const char* Name() const override { return "X11DataTextureSourceBasic"; } + + bool Update(gfx::DataSourceSurface* aSurface, + nsIntRegion* aDestRegion = nullptr, + gfx::IntPoint* aSrcOffset = nullptr) override; + + TextureSourceBasic* AsSourceBasic() override; + + gfx::SourceSurface* GetSurface(gfx::DrawTarget* aTarget) override; + + void DeallocateDeviceData() override; + + gfx::IntSize GetSize() const override; + + gfx::SurfaceFormat GetFormat() const override; + + private: + // We are going to buffer layer content on this xlib draw target + RefPtr mBufferDrawTarget; +}; + +class X11BasicCompositor : public BasicCompositor { + public: + explicit X11BasicCompositor(CompositorBridgeParent* aParent, + widget::CompositorWidget* aWidget) + : BasicCompositor(aParent, aWidget) {} + + already_AddRefed CreateDataTextureSource( + TextureFlags aFlags = TextureFlags::NO_FLAGS) override; + + already_AddRefed CreateDataTextureSourceAround( + gfx::DataSourceSurface* aSurface) override { + return nullptr; + } + + void EndFrame() override; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* MOZILLA_GFX_X11BASICCOMPOSITOR_H */ diff --git a/gfx/layers/basic/X11TextureSourceBasic.cpp b/gfx/layers/basic/X11TextureSourceBasic.cpp new file mode 100644 index 0000000000..b987f1ba1b --- /dev/null +++ b/gfx/layers/basic/X11TextureSourceBasic.cpp @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "X11TextureSourceBasic.h" +#include "gfxXlibSurface.h" +#include "gfx2DGlue.h" + +namespace mozilla::layers { + +using namespace mozilla::gfx; + +X11TextureSourceBasic::X11TextureSourceBasic(BasicCompositor* aCompositor, + gfxXlibSurface* aSurface) + : mSurface(aSurface) {} + +IntSize X11TextureSourceBasic::GetSize() const { return mSurface->GetSize(); } + +SurfaceFormat X11TextureSourceBasic::GetFormat() const { + gfxContentType type = mSurface->GetContentType(); + return X11TextureSourceBasic::ContentTypeToSurfaceFormat(type); +} + +SourceSurface* X11TextureSourceBasic::GetSurface(DrawTarget* aTarget) { + if (!mSourceSurface) { + mSourceSurface = Factory::CreateSourceSurfaceForCairoSurface( + mSurface->CairoSurface(), GetSize(), GetFormat()); + } + return mSourceSurface; +} + +SurfaceFormat X11TextureSourceBasic::ContentTypeToSurfaceFormat( + gfxContentType aType) { + switch (aType) { + case gfxContentType::COLOR: + return SurfaceFormat::B8G8R8X8; + case gfxContentType::ALPHA: + return SurfaceFormat::A8; + case gfxContentType::COLOR_ALPHA: + return SurfaceFormat::B8G8R8A8; + default: + return SurfaceFormat::UNKNOWN; + } +} + +} // namespace mozilla::layers diff --git a/gfx/layers/basic/X11TextureSourceBasic.h b/gfx/layers/basic/X11TextureSourceBasic.h new file mode 100644 index 0000000000..7d89c935eb --- /dev/null +++ b/gfx/layers/basic/X11TextureSourceBasic.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_X11TEXTURESOURCEBASIC__H +#define MOZILLA_GFX_X11TEXTURESOURCEBASIC__H + +#include "mozilla/layers/BasicCompositor.h" +#include "mozilla/layers/TextureHostBasic.h" +#include "mozilla/layers/X11TextureHost.h" +#include "mozilla/gfx/2D.h" + +namespace mozilla { +namespace layers { + +class BasicCompositor; + +// TextureSource for Xlib-backed surfaces. +class X11TextureSourceBasic : public TextureSourceBasic, + public X11TextureSource { + public: + X11TextureSourceBasic(BasicCompositor* aCompositor, gfxXlibSurface* aSurface); + + virtual const char* Name() const override { return "X11TextureSourceBasic"; } + + virtual X11TextureSourceBasic* AsSourceBasic() override { return this; } + + virtual gfx::IntSize GetSize() const override; + + virtual gfx::SurfaceFormat GetFormat() const override; + + virtual gfx::SourceSurface* GetSurface(gfx::DrawTarget* aTarget) override; + + virtual void DeallocateDeviceData() override {} + + virtual void Updated() override {} + + static gfx::SurfaceFormat ContentTypeToSurfaceFormat(gfxContentType aType); + + protected: + RefPtr mSurface; + RefPtr mSourceSurface; +}; + +} // namespace layers +} // namespace mozilla + +#endif // MOZILLA_GFX_X11TEXTURESOURCEBASIC__H diff --git a/gfx/layers/client/CanvasClient.cpp b/gfx/layers/client/CanvasClient.cpp new file mode 100644 index 0000000000..2040dae6bf --- /dev/null +++ b/gfx/layers/client/CanvasClient.cpp @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "CanvasClient.h" + +#include "ClientCanvasLayer.h" // for ClientCanvasLayer +#include "gfx2DGlue.h" // for ImageFormatToSurfaceFormat +#include "gfxPlatform.h" // for gfxPlatform +#include "mozilla/gfx/BaseSize.h" // for BaseSize +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/layers/BufferTexture.h" +#include "mozilla/layers/CompositableForwarder.h" +#include "mozilla/layers/CompositorBridgeChild.h" // for CompositorBridgeChild +#include "mozilla/layers/LayersTypes.h" +#include "mozilla/layers/OOPCanvasRenderer.h" +#include "mozilla/layers/TextureClient.h" // for TextureClient, etc +#include "mozilla/layers/TextureClientOGL.h" +#include "mozilla/layers/TextureClientRecycleAllocator.h" +#include "nsDebug.h" // for printf_stderr, NS_ASSERTION +#include "nsXULAppAPI.h" // for XRE_GetProcessType, etc + +using namespace mozilla::gfx; +using namespace mozilla::gl; + +namespace mozilla { +namespace layers { + +void CanvasClient::UseTexture(TextureClient* const aTexture) { + MOZ_ASSERT(aTexture); + + const auto isClientNonPremult = + bool(mTextureFlags & TextureFlags::NON_PREMULTIPLIED); + const auto isTextureNonPremult = + bool(aTexture->GetFlags() & TextureFlags::NON_PREMULTIPLIED); + MOZ_ALWAYS_TRUE(isTextureNonPremult == isClientNonPremult); + + bool changed = false; + + if (aTexture != mFrontBuffer) { + if (!aTexture->IsSharedWithCompositor()) { + if (!AddTextureClient(aTexture)) { + return; + } + } + changed = true; + mFrontBuffer = aTexture; + } + + AutoTArray textures; + CompositableForwarder::TimedTextureClient* t = textures.AppendElement(); + t->mTextureClient = aTexture; + t->mPictureRect = nsIntRect(nsIntPoint(0, 0), aTexture->GetSize()); + t->mFrameID = mFrameID; + + GetForwarder()->UseTextures(this, textures); + if (changed) { + aTexture->SyncWithObject(GetForwarder()->GetSyncObject()); + } +} + +static constexpr bool kIsWindows = +#ifdef XP_WIN + true; +#else + false; +#endif + +RefPtr CanvasClient::CreateTextureClientForCanvas( + const gfx::SurfaceFormat aFormat, const gfx::IntSize aSize, + const TextureFlags aFlags) { + if (kIsWindows) { + // With WebRender, host side uses data of TextureClient longer. + // Then back buffer reuse in CanvasClient2D::Update() does not work. It + // causes a lot of TextureClient allocations. For reducing the allocations, + // TextureClientRecycler is used. + if (GetForwarder() && GetForwarder()->GetCompositorBackendType() == + LayersBackend::LAYERS_WR) { + return GetTextureClientRecycler()->CreateOrRecycle( + aFormat, aSize, BackendSelector::Canvas, mTextureFlags | aFlags); + } + return CreateTextureClientForDrawing( + aFormat, aSize, BackendSelector::Canvas, mTextureFlags | aFlags); + } + + // XXX - We should use CreateTextureClientForDrawing, but we first need + // to use double buffering. + gfx::BackendType backend = + gfxPlatform::GetPlatform()->GetPreferredCanvasBackend(); + return TextureClient::CreateForRawBufferAccess( + GetForwarder(), aFormat, aSize, backend, mTextureFlags | aFlags); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/CanvasClient.h b/gfx/layers/client/CanvasClient.h new file mode 100644 index 0000000000..2136e6f652 --- /dev/null +++ b/gfx/layers/client/CanvasClient.h @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_CANVASCLIENT_H +#define MOZILLA_GFX_CANVASCLIENT_H + +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/Attributes.h" // for override +#include "mozilla/RefPtr.h" // for RefPtr, already_AddRefed +#include "mozilla/layers/CompositableClient.h" // for CompositableClient +#include "mozilla/layers/CompositorTypes.h" // for TextureInfo, etc +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor +#include "mozilla/layers/TextureClient.h" // for TextureClient, etc +#include "mozilla/layers/PersistentBufferProvider.h" + +#include "mozilla/MaybeOneOf.h" + +#include "mozilla/mozalloc.h" // for operator delete + +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/gfx/Types.h" // for SurfaceFormat + +namespace mozilla { +namespace layers { + +class CompositableForwarder; + +/** + * Compositable client for 2d and webgl canvas. + */ +class CanvasClient final : public CompositableClient { + int32_t mFrameID = 0; + RefPtr mFrontBuffer; + + public: + /** + * Creates, configures, and returns a new canvas client. If necessary, a + * message will be sent to the compositor to create a corresponding image + * host. + */ + CanvasClient(CompositableForwarder* aFwd, const TextureFlags flags) + : CompositableClient(aFwd, flags) {} + + virtual ~CanvasClient() = default; + + void Clear() { mFrontBuffer = nullptr; } + + bool AddTextureClient(TextureClient* aTexture) override { + ++mFrameID; + return CompositableClient::AddTextureClient(aTexture); + } + + TextureInfo GetTextureInfo() const override { + return TextureInfo(CompositableType::IMAGE, mTextureFlags); + } + + void OnDetach() override { Clear(); } + + RefPtr CreateTextureClientForCanvas(gfx::SurfaceFormat, + gfx::IntSize, + TextureFlags); + void UseTexture(TextureClient*); +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/client/ClientCanvasLayer.cpp b/gfx/layers/client/ClientCanvasLayer.cpp new file mode 100644 index 0000000000..ca717b0a89 --- /dev/null +++ b/gfx/layers/client/ClientCanvasLayer.cpp @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ClientCanvasLayer.h" +#include "GeckoProfiler.h" // for AUTO_PROFILER_LABEL +#include "ClientLayerManager.h" // for ClientLayerManager, etc +#include "nsCOMPtr.h" // for already_AddRefed + +namespace mozilla { +namespace layers { + +ClientCanvasLayer::~ClientCanvasLayer() { MOZ_COUNT_DTOR(ClientCanvasLayer); } + +void ClientCanvasLayer::RenderLayer() { + AUTO_PROFILER_LABEL("ClientCanvasLayer::RenderLayer", GRAPHICS); + + RenderMaskLayers(this); + + ClientCanvasRenderer* canvasRenderer = + mCanvasRenderer->AsClientCanvasRenderer(); + MOZ_ASSERT(canvasRenderer); + canvasRenderer->UpdateCompositableClient(); + ClientManager()->Hold(this); +} + +RefPtr ClientCanvasLayer::CreateCanvasRendererInternal() { + return new ClientCanvasRenderer(this); +} + +already_AddRefed ClientLayerManager::CreateCanvasLayer() { + NS_ASSERTION(InConstruction(), "Only allowed in construction phase"); + RefPtr layer = new ClientCanvasLayer(this); + CREATE_SHADOW(Canvas); + return layer.forget(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/ClientCanvasLayer.h b/gfx/layers/client/ClientCanvasLayer.h new file mode 100644 index 0000000000..64576f9e79 --- /dev/null +++ b/gfx/layers/client/ClientCanvasLayer.h @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_CLIENTCANVASLAYER_H +#define GFX_CLIENTCANVASLAYER_H + +#include "ClientCanvasRenderer.h" +#include "ClientLayerManager.h" // for ClientLayerManager, etc +#include "Layers.h" // for CanvasLayer, etc +#include "mozilla/Attributes.h" // for override +#include "mozilla/layers/CanvasClient.h" // for CanvasClient, etc +#include "mozilla/layers/LayersMessages.h" // for CanvasLayerAttributes, etc +#include "mozilla/mozalloc.h" // for operator delete +#include "nsDebug.h" // for NS_ASSERTION +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc + +namespace mozilla { +namespace layers { + +class CompositableClient; +class ShadowableLayer; + +class ClientCanvasLayer : public CanvasLayer, public ClientLayer { + public: + explicit ClientCanvasLayer(ClientLayerManager* aLayerManager) + : CanvasLayer(aLayerManager, static_cast(this)) { + MOZ_COUNT_CTOR(ClientCanvasLayer); + } + + RefPtr CreateCanvasRendererInternal() override; + + protected: + virtual ~ClientCanvasLayer(); + + public: + void SetVisibleRegion(const LayerIntRegion& aRegion) override { + NS_ASSERTION(ClientManager()->InConstruction(), + "Can only set properties in construction phase"); + CanvasLayer::SetVisibleRegion(aRegion); + } + + void RenderLayer() override; + + void ClearCachedResources() override { + mCanvasRenderer->ClearCachedResources(); + } + + void HandleMemoryPressure() override { + mCanvasRenderer->ClearCachedResources(); + } + + void FillSpecificAttributes(SpecificLayerAttributes& aAttrs) override { + aAttrs = CanvasLayerAttributes(mSamplingFilter, mBounds); + } + + Layer* AsLayer() override { return this; } + ShadowableLayer* AsShadowableLayer() override { return this; } + + void Disconnect() override { mCanvasRenderer->DisconnectClient(); } + + CompositableClient* GetCompositableClient() override { + ClientCanvasRenderer* canvasRenderer = + mCanvasRenderer->AsClientCanvasRenderer(); + MOZ_ASSERT(canvasRenderer); + return canvasRenderer->GetCanvasClient(); + } + + protected: + ClientLayerManager* ClientManager() { + return static_cast(mManager); + } +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/client/ClientCanvasRenderer.cpp b/gfx/layers/client/ClientCanvasRenderer.cpp new file mode 100644 index 0000000000..f9b99fb4f8 --- /dev/null +++ b/gfx/layers/client/ClientCanvasRenderer.cpp @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ClientCanvasRenderer.h" + +#include "ClientCanvasLayer.h" + +namespace mozilla { +namespace layers { + +CompositableForwarder* ClientCanvasRenderer::GetForwarder() { + return mLayer->Manager()->AsShadowForwarder(); +} + +bool ClientCanvasRenderer::CreateCompositable() { + if (!mCanvasClient) { + auto compositableFlags = TextureFlags::NO_FLAGS; + if (!mData.mIsAlphaPremult) { + // WR needs this flag marked on the compositable, not just the texture. + compositableFlags |= TextureFlags::NON_PREMULTIPLIED; + } + mCanvasClient = new CanvasClient(GetForwarder(), compositableFlags); + + if (mLayer->HasShadow()) { + mCanvasClient->Connect(); + GetForwarder()->AsLayerForwarder()->Attach(mCanvasClient, mLayer); + } + } + + return true; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/ClientCanvasRenderer.h b/gfx/layers/client/ClientCanvasRenderer.h new file mode 100644 index 0000000000..5ea857b10f --- /dev/null +++ b/gfx/layers/client/ClientCanvasRenderer.h @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_CLIENTCANVASRENDERER_H +#define GFX_CLIENTCANVASRENDERER_H + +#include "ShareableCanvasRenderer.h" + +namespace mozilla { +namespace layers { + +class ClientCanvasLayer; + +class ClientCanvasRenderer final : public ShareableCanvasRenderer { + public: + explicit ClientCanvasRenderer(ClientCanvasLayer* aLayer) : mLayer(aLayer) {} + + ClientCanvasRenderer* AsClientCanvasRenderer() override { return this; } + + CompositableForwarder* GetForwarder() override; + + bool CreateCompositable() override; + + protected: + ClientCanvasLayer* mLayer; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/client/ClientColorLayer.cpp b/gfx/layers/client/ClientColorLayer.cpp new file mode 100644 index 0000000000..4ecaf5829e --- /dev/null +++ b/gfx/layers/client/ClientColorLayer.cpp @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ClientLayerManager.h" // for ClientLayerManager, etc +#include "Layers.h" // for ColorLayer, etc +#include "mozilla/layers/LayersMessages.h" // for ColorLayerAttributes, etc +#include "mozilla/mozalloc.h" // for operator new +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsDebug.h" // for NS_ASSERTION +#include "nsISupportsImpl.h" // for Layer::AddRef, etc +#include "nsRegion.h" // for nsIntRegion + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +class ClientColorLayer : public ColorLayer, public ClientLayer { + public: + explicit ClientColorLayer(ClientLayerManager* aLayerManager) + : ColorLayer(aLayerManager, static_cast(this)) { + MOZ_COUNT_CTOR(ClientColorLayer); + } + + protected: + MOZ_COUNTED_DTOR_OVERRIDE(ClientColorLayer) + + public: + void SetVisibleRegion(const LayerIntRegion& aRegion) override { + NS_ASSERTION(ClientManager()->InConstruction(), + "Can only set properties in construction phase"); + ColorLayer::SetVisibleRegion(aRegion); + } + + void RenderLayer() override { RenderMaskLayers(this); } + + void FillSpecificAttributes(SpecificLayerAttributes& aAttrs) override { + aAttrs = ColorLayerAttributes(GetColor(), GetBounds()); + } + + Layer* AsLayer() override { return this; } + ShadowableLayer* AsShadowableLayer() override { return this; } + + protected: + ClientLayerManager* ClientManager() { + return static_cast(mManager); + } +}; + +already_AddRefed ClientLayerManager::CreateColorLayer() { + NS_ASSERTION(InConstruction(), "Only allowed in construction phase"); + RefPtr layer = new ClientColorLayer(this); + CREATE_SHADOW(Color); + return layer.forget(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/ClientContainerLayer.cpp b/gfx/layers/client/ClientContainerLayer.cpp new file mode 100644 index 0000000000..3bcb754197 --- /dev/null +++ b/gfx/layers/client/ClientContainerLayer.cpp @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ClientContainerLayer.h" +#include "ClientLayerManager.h" // for ClientLayerManager, etc +#include "mozilla/mozalloc.h" // for operator new +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsISupportsImpl.h" // for Layer::AddRef, etc + +namespace mozilla { +namespace layers { + +already_AddRefed ClientLayerManager::CreateContainerLayer() { + NS_ASSERTION(InConstruction(), "Only allowed in construction phase"); + RefPtr layer = new ClientContainerLayer(this); + CREATE_SHADOW(Container); + return layer.forget(); +} + +already_AddRefed ClientLayerManager::CreateRefLayer() { + NS_ASSERTION(InConstruction(), "Only allowed in construction phase"); + RefPtr layer = new ClientRefLayer(this); + CREATE_SHADOW(Ref); + return layer.forget(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/ClientContainerLayer.h b/gfx/layers/client/ClientContainerLayer.h new file mode 100644 index 0000000000..f0331e6d16 --- /dev/null +++ b/gfx/layers/client/ClientContainerLayer.h @@ -0,0 +1,162 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_CLIENTCONTAINERLAYER_H +#define GFX_CLIENTCONTAINERLAYER_H + +#include // for uint32_t +#include "ClientLayerManager.h" // for ClientLayerManager, etc +#include "Layers.h" // for Layer, ContainerLayer, etc +#include "nsDebug.h" // for NS_ASSERTION +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "nsISupportsUtils.h" // for NS_ADDREF, NS_RELEASE +#include "nsRegion.h" // for nsIntRegion +#include "nsTArray.h" // for AutoTArray +#include "ReadbackProcessor.h" +#include "ClientPaintedLayer.h" + +namespace mozilla { +namespace layers { + +class ShadowableLayer; + +class ClientContainerLayer : public ContainerLayer, public ClientLayer { + public: + explicit ClientContainerLayer(ClientLayerManager* aManager) + : ContainerLayer(aManager, static_cast(this)) { + MOZ_COUNT_CTOR(ClientContainerLayer); + mSupportsComponentAlphaChildren = true; + } + + protected: + virtual ~ClientContainerLayer() { + ContainerLayer::RemoveAllChildren(); + MOZ_COUNT_DTOR(ClientContainerLayer); + } + + public: + void RenderLayer() override { + RenderMaskLayers(this); + + DefaultComputeSupportsComponentAlphaChildren(); + + ReadbackProcessor readback; + readback.BuildUpdates(this); + + nsTArray children = CollectChildren(); + for (uint32_t i = 0; i < children.Length(); i++) { + Layer* child = children.ElementAt(i); + + ToClientLayer(child)->RenderLayerWithReadback(&readback); + + if (!ClientManager()->GetRepeatTransaction() && + !child->GetInvalidRegion().IsEmpty()) { + child->Mutated(); + } + } + } + + void SetVisibleRegion(const LayerIntRegion& aRegion) override { + NS_ASSERTION(ClientManager()->InConstruction(), + "Can only set properties in construction phase"); + ContainerLayer::SetVisibleRegion(aRegion); + } + bool InsertAfter(Layer* aChild, Layer* aAfter) override { + if (!ClientManager()->InConstruction()) { + NS_ERROR("Can only set properties in construction phase"); + return false; + } + + if (!ContainerLayer::InsertAfter(aChild, aAfter)) { + return false; + } + + ClientManager()->AsShadowForwarder()->InsertAfter( + ClientManager()->Hold(this), ClientManager()->Hold(aChild), + aAfter ? ClientManager()->Hold(aAfter) : nullptr); + return true; + } + + bool RemoveChild(Layer* aChild) override { + if (!ClientManager()->InConstruction()) { + NS_ERROR("Can only set properties in construction phase"); + return false; + } + // hold on to aChild before we remove it! + ShadowableLayer* heldChild = ClientManager()->Hold(aChild); + if (!ContainerLayer::RemoveChild(aChild)) { + return false; + } + ClientManager()->AsShadowForwarder()->RemoveChild( + ClientManager()->Hold(this), heldChild); + return true; + } + + bool RepositionChild(Layer* aChild, Layer* aAfter) override { + if (!ClientManager()->InConstruction()) { + NS_ERROR("Can only set properties in construction phase"); + return false; + } + if (!ContainerLayer::RepositionChild(aChild, aAfter)) { + return false; + } + ClientManager()->AsShadowForwarder()->RepositionChild( + ClientManager()->Hold(this), ClientManager()->Hold(aChild), + aAfter ? ClientManager()->Hold(aAfter) : nullptr); + return true; + } + + Layer* AsLayer() override { return this; } + ShadowableLayer* AsShadowableLayer() override { return this; } + + void ComputeEffectiveTransforms( + const gfx::Matrix4x4& aTransformToSurface) override { + DefaultComputeEffectiveTransforms(aTransformToSurface); + } + + void ForceIntermediateSurface() { mUseIntermediateSurface = true; } + + void SetSupportsComponentAlphaChildren(bool aSupports) { + mSupportsComponentAlphaChildren = aSupports; + } + + protected: + ClientLayerManager* ClientManager() { + return static_cast(mManager); + } +}; + +class ClientRefLayer : public RefLayer, public ClientLayer { + public: + explicit ClientRefLayer(ClientLayerManager* aManager) + : RefLayer(aManager, static_cast(this)) { + MOZ_COUNT_CTOR(ClientRefLayer); + } + + protected: + MOZ_COUNTED_DTOR_OVERRIDE(ClientRefLayer) + + public: + Layer* AsLayer() override { return this; } + ShadowableLayer* AsShadowableLayer() override { return this; } + + void RenderLayer() override { RenderMaskLayers(this); } + + void ComputeEffectiveTransforms( + const gfx::Matrix4x4& aTransformToSurface) override { + DefaultComputeEffectiveTransforms(aTransformToSurface); + } + + private: + ClientLayerManager* ClientManager() { + return static_cast(mManager); + } +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/client/ClientImageLayer.cpp b/gfx/layers/client/ClientImageLayer.cpp new file mode 100644 index 0000000000..a82801fbfe --- /dev/null +++ b/gfx/layers/client/ClientImageLayer.cpp @@ -0,0 +1,152 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ClientLayerManager.h" // for ClientLayerManager, etc +#include "ImageContainer.h" // for AutoLockImage, etc +#include "ImageLayers.h" // for ImageLayer +#include "mozilla/Attributes.h" // for override +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/layers/CompositorTypes.h" +#include "mozilla/layers/ImageClient.h" // for ImageClient, etc +#include "mozilla/layers/LayersMessages.h" // for ImageLayerAttributes, etc +#include "mozilla/mozalloc.h" // for operator delete, etc +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsDebug.h" // for NS_ASSERTION +#include "nsISupportsImpl.h" // for Layer::AddRef, etc +#include "nsRegion.h" // for nsIntRegion + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +class ClientImageLayer : public ImageLayer, public ClientLayer { + public: + explicit ClientImageLayer(ClientLayerManager* aLayerManager) + : ImageLayer(aLayerManager, static_cast(this)), + mImageClientTypeContainer(CompositableType::UNKNOWN) { + MOZ_COUNT_CTOR(ClientImageLayer); + } + + protected: + virtual ~ClientImageLayer() { + DestroyBackBuffer(); + MOZ_COUNT_DTOR(ClientImageLayer); + } + + void SetContainer(ImageContainer* aContainer) override { + ImageLayer::SetContainer(aContainer); + mImageClientTypeContainer = CompositableType::UNKNOWN; + } + + void SetVisibleRegion(const LayerIntRegion& aRegion) override { + NS_ASSERTION(ClientManager()->InConstruction(), + "Can only set properties in construction phase"); + ImageLayer::SetVisibleRegion(aRegion); + } + + void RenderLayer() override; + + void ClearCachedResources() override { DestroyBackBuffer(); } + + bool SupportsAsyncUpdate() override { + if (GetImageClientType() == CompositableType::IMAGE_BRIDGE) { + return true; + } + return false; + } + + void HandleMemoryPressure() override { + if (mImageClient) { + mImageClient->HandleMemoryPressure(); + } + } + + void FillSpecificAttributes(SpecificLayerAttributes& aAttrs) override { + aAttrs = ImageLayerAttributes(mSamplingFilter, mScaleToSize, mScaleMode); + } + + Layer* AsLayer() override { return this; } + ShadowableLayer* AsShadowableLayer() override { return this; } + + void Disconnect() override { DestroyBackBuffer(); } + + void DestroyBackBuffer() { + if (mImageClient) { + mImageClient->SetLayer(nullptr); + mImageClient->OnDetach(); + mImageClient = nullptr; + } + } + + CompositableClient* GetCompositableClient() override { return mImageClient; } + + protected: + ClientLayerManager* ClientManager() { + return static_cast(mManager); + } + + CompositableType GetImageClientType() { + if (mImageClientTypeContainer != CompositableType::UNKNOWN) { + return mImageClientTypeContainer; + } + + if (mContainer->IsAsync()) { + mImageClientTypeContainer = CompositableType::IMAGE_BRIDGE; + return mImageClientTypeContainer; + } + + AutoLockImage autoLock(mContainer); + + mImageClientTypeContainer = autoLock.HasImage() ? CompositableType::IMAGE + : CompositableType::UNKNOWN; + return mImageClientTypeContainer; + } + + RefPtr mImageClient; + CompositableType mImageClientTypeContainer; +}; + +void ClientImageLayer::RenderLayer() { + RenderMaskLayers(this); + + if (!mContainer) { + return; + } + + if (!mImageClient || + !mImageClient->UpdateImage(mContainer, GetContentFlags())) { + CompositableType type = GetImageClientType(); + if (type == CompositableType::UNKNOWN) { + return; + } + TextureFlags flags = TextureFlags::DEFAULT; + mImageClient = ImageClient::CreateImageClient( + type, ClientManager()->AsShadowForwarder(), flags); + if (!mImageClient) { + return; + } + mImageClient->SetLayer(this); + if (HasShadow() && !mContainer->IsAsync()) { + mImageClient->Connect(); + ClientManager()->AsShadowForwarder()->Attach(mImageClient, this); + } + if (!mImageClient->UpdateImage(mContainer, GetContentFlags())) { + return; + } + } + ClientManager()->Hold(this); +} + +already_AddRefed ClientLayerManager::CreateImageLayer() { + NS_ASSERTION(InConstruction(), "Only allowed in construction phase"); + RefPtr layer = new ClientImageLayer(this); + CREATE_SHADOW(Image); + return layer.forget(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/ClientLayerManager.cpp b/gfx/layers/client/ClientLayerManager.cpp new file mode 100644 index 0000000000..34a9eb566c --- /dev/null +++ b/gfx/layers/client/ClientLayerManager.cpp @@ -0,0 +1,903 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ClientLayerManager.h" +#include "GeckoProfiler.h" // for AUTO_PROFILER_LABEL +#include "gfxEnv.h" // for gfxEnv +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/Hal.h" +#include "mozilla/StaticPrefs_apz.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/dom/BrowserChild.h" // for BrowserChild +#include "mozilla/hal_sandbox/PHal.h" // for ScreenConfiguration +#include "mozilla/layers/CompositableClient.h" +#include "mozilla/layers/CompositorBridgeChild.h" // for CompositorBridgeChild +#include "mozilla/layers/FrameUniformityData.h" +#include "mozilla/layers/ISurfaceAllocator.h" +#include "mozilla/layers/LayersMessages.h" // for EditReply, etc +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor +#include "mozilla/layers/LayerTransactionChild.h" +#include "mozilla/layers/PersistentBufferProvider.h" +#include "mozilla/layers/SyncObject.h" +#include "mozilla/layers/TransactionIdAllocator.h" +#include "mozilla/PerfStats.h" +#include "ClientReadbackLayer.h" // for ClientReadbackLayer +#include "nsAString.h" +#include "nsDisplayList.h" +#include "nsIWidgetListener.h" +#include "nsLayoutUtils.h" +#include "nsTArray.h" // for AutoTArray +#include "nsXULAppAPI.h" // for XRE_GetProcessType, etc +#include "TiledLayerBuffer.h" +#include "FrameLayerBuilder.h" // for FrameLayerbuilder +#ifdef MOZ_WIDGET_ANDROID +# include "AndroidBridge.h" +# include "LayerMetricsWrapper.h" +#endif +#ifdef XP_WIN +# include "mozilla/gfx/DeviceManagerDx.h" +# include "gfxDWriteFonts.h" +#endif + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +ClientLayerManager::ClientLayerManager(nsIWidget* aWidget) + : mPhase(PHASE_NONE), + mWidget(aWidget), + mPaintedLayerCallback(nullptr), + mPaintedLayerCallbackData(nullptr), + mLatestTransactionId{0}, + mLastPaintTime(TimeDuration::Forever()), + mTargetRotation(ROTATION_0), + mRepeatTransaction(false), + mIsRepeatTransaction(false), + mTransactionIncomplete(false), + mCompositorMightResample(false), + mNeedsComposite(false), + mQueuedAsyncPaints(false), + mNotifyingWidgetListener(false), + mPaintSequenceNumber(0), + mForwarder(new ShadowLayerForwarder(this)) { + MOZ_COUNT_CTOR(ClientLayerManager); + mMemoryPressureObserver = MemoryPressureObserver::Create(this); +} + +ClientLayerManager::~ClientLayerManager() { + mMemoryPressureObserver->Unregister(); + ClearCachedResources(); + // Stop receiveing AsyncParentMessage at Forwarder. + // After the call, the message is directly handled by LayerTransactionChild. + // Basically this function should be called in ShadowLayerForwarder's + // destructor. But when the destructor is triggered by + // CompositorBridgeChild::Destroy(), the destructor can not handle it + // correctly. See Bug 1000525. + mForwarder->StopReceiveAsyncParentMessge(); + mRoot = nullptr; + + MOZ_COUNT_DTOR(ClientLayerManager); +} + +bool ClientLayerManager::Initialize( + PCompositorBridgeChild* aCBChild, bool aShouldAccelerate, + TextureFactoryIdentifier* aTextureFactoryIdentifier) { + MOZ_ASSERT(mForwarder); + MOZ_ASSERT(aTextureFactoryIdentifier); + + nsTArray backendHints; + gfxPlatform::GetPlatform()->GetCompositorBackends(aShouldAccelerate, + backendHints); + if (backendHints.IsEmpty()) { + gfxCriticalNote << "Failed to get backend hints."; + return false; + } + + PLayerTransactionChild* shadowManager = + aCBChild->SendPLayerTransactionConstructor(backendHints, LayersId{0}); + + TextureFactoryIdentifier textureFactoryIdentifier; + shadowManager->SendGetTextureFactoryIdentifier(&textureFactoryIdentifier); + if (textureFactoryIdentifier.mParentBackend == LayersBackend::LAYERS_NONE) { + gfxCriticalNote << "Failed to create an OMT compositor."; + return false; + } + + mForwarder->SetShadowManager(shadowManager); + UpdateTextureFactoryIdentifier(textureFactoryIdentifier); + *aTextureFactoryIdentifier = textureFactoryIdentifier; + return true; +} + +void ClientLayerManager::Destroy() { + MOZ_DIAGNOSTIC_ASSERT(!mNotifyingWidgetListener, + "Try to avoid destroying widgets and layer managers " + "during DidCompositeWindow, if you can"); + + // It's important to call ClearCachedResource before Destroy because the + // former will early-return if the later has already run. + ClearCachedResources(); + LayerManager::Destroy(); + + if (mTransactionIdAllocator) { + // Make sure to notify the refresh driver just in case it's waiting on a + // pending transaction. Do this at the top of the event loop so we don't + // cause a paint to occur during compositor shutdown. + RefPtr allocator = mTransactionIdAllocator; + TransactionId id = mLatestTransactionId; + + RefPtr task = NS_NewRunnableFunction( + "TransactionIdAllocator::NotifyTransactionCompleted", + [allocator, id]() -> void { + allocator->NotifyTransactionCompleted(id); + }); + NS_DispatchToMainThread(task.forget()); + } + + // Forget the widget pointer in case we outlive our owning widget. + mWidget = nullptr; +} + +int32_t ClientLayerManager::GetMaxTextureSize() const { + return mForwarder->GetMaxTextureSize(); +} + +void ClientLayerManager::SetDefaultTargetConfiguration( + BufferMode aDoubleBuffering, ScreenRotation aRotation) { + mTargetRotation = aRotation; +} + +void ClientLayerManager::SetRoot(Layer* aLayer) { + if (mRoot != aLayer) { + // Have to hold the old root and its children in order to + // maintain the same view of the layer tree in this process as + // the parent sees. Otherwise layers can be destroyed + // mid-transaction and bad things can happen (v. bug 612573) + if (mRoot) { + Hold(mRoot); + } + mForwarder->SetRoot(Hold(aLayer)); + NS_ASSERTION(aLayer, "Root can't be null"); + NS_ASSERTION(aLayer->Manager() == this, "Wrong manager"); + NS_ASSERTION(InConstruction(), "Only allowed in construction phase"); + mRoot = aLayer; + } +} + +void ClientLayerManager::Mutated(Layer* aLayer) { + LayerManager::Mutated(aLayer); + + NS_ASSERTION(InConstruction() || InDrawing(), "wrong phase"); + mForwarder->Mutated(Hold(aLayer)); +} + +void ClientLayerManager::MutatedSimple(Layer* aLayer) { + LayerManager::MutatedSimple(aLayer); + + NS_ASSERTION(InConstruction() || InDrawing(), "wrong phase"); + mForwarder->MutatedSimple(Hold(aLayer)); +} + +already_AddRefed ClientLayerManager::CreateReadbackLayer() { + RefPtr layer = new ClientReadbackLayer(this); + return layer.forget(); +} + +bool ClientLayerManager::BeginTransactionWithTarget(gfxContext* aTarget, + const nsCString& aURL) { +#ifdef MOZ_DUMP_PAINTING + // When we are dump painting, we expect to be able to read the contents of + // compositable clients from previous paints inside this layer transaction + // before we flush async paints in EndTransactionInternal. + // So to work around this flush async paints now. + if (gfxEnv::DumpPaint()) { + FlushAsyncPaints(); + } +#endif + + MOZ_ASSERT(mForwarder, + "ClientLayerManager::BeginTransaction without forwarder"); + if (!mForwarder->IPCOpen()) { + gfxCriticalNote << "ClientLayerManager::BeginTransaction with IPC channel " + "down. GPU process may have died."; + return false; + } + + mInTransaction = true; + mTransactionStart = TimeStamp::Now(); + mURL = aURL; + +#ifdef MOZ_LAYERS_HAVE_LOG + MOZ_LAYERS_LOG(("[----- BeginTransaction")); + Log(); +#endif + + NS_ASSERTION(!InTransaction(), "Nested transactions not allowed"); + mPhase = PHASE_CONSTRUCTION; + + MOZ_ASSERT(mKeepAlive.IsEmpty(), "uncommitted txn?"); + + // If the last transaction was incomplete (a failed DoEmptyTransaction), + // don't signal a new transaction to ShadowLayerForwarder. Carry on adding + // to the previous transaction. + hal::ScreenOrientation orientation; + if (dom::BrowserChild* window = mWidget->GetOwningBrowserChild()) { + orientation = window->GetOrientation(); + } else { + hal::ScreenConfiguration currentConfig; + hal::GetCurrentScreenConfiguration(¤tConfig); + orientation = currentConfig.orientation(); + } + LayoutDeviceIntRect targetBounds = mWidget->GetNaturalBounds(); + targetBounds.MoveTo(0, 0); + mForwarder->BeginTransaction(targetBounds.ToUnknownRect(), mTargetRotation, + orientation); + + // If we're drawing on behalf of a context with async pan/zoom + // enabled, then the entire buffer of painted layers might be + // composited (including resampling) asynchronously before we get + // a chance to repaint, so we have to ensure that it's all valid + // and not rotated. + // + // Desktop does not support async zoom yet, so we ignore this for those + // platforms. +#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_UIKIT) + if (mWidget && mWidget->GetOwningBrowserChild()) { + mCompositorMightResample = AsyncPanZoomEnabled(); + } +#endif + + // If we have a non-default target, we need to let our shadow manager draw + // to it. This will happen at the end of the transaction. + if (aTarget && XRE_IsParentProcess()) { + mShadowTarget = aTarget; + } else { + NS_ASSERTION( + !aTarget, + "Content-process ClientLayerManager::BeginTransactionWithTarget not " + "supported"); + } + + // If this is a new paint, increment the paint sequence number. + if (!mIsRepeatTransaction) { + // Increment the paint sequence number even if test logging isn't + // enabled in this process; it may be enabled in the parent process, + // and the parent process expects unique sequence numbers. + ++mPaintSequenceNumber; + if (StaticPrefs::apz_test_logging_enabled()) { + mApzTestData.StartNewPaint(mPaintSequenceNumber); + } + } + return true; +} + +bool ClientLayerManager::BeginTransaction(const nsCString& aURL) { + return BeginTransactionWithTarget(nullptr, aURL); +} + +bool ClientLayerManager::EndTransactionInternal( + DrawPaintedLayerCallback aCallback, void* aCallbackData, + EndTransactionFlags) { + // This just causes the compositor to check whether the GPU is done with its + // textures or not and unlock them if it is. This helps us avoid the case + // where we take a long time painting asynchronously, turn IPC back on at + // the end of that, and then have to wait for the compositor to to get into + // TiledLayerBufferComposite::UseTiles before getting a response. + if (mForwarder) { + mForwarder->UpdateTextureLocks(); + } + + // Wait for any previous async paints to complete before starting to paint + // again. Do this outside the profiler and telemetry block so this doesn't + // count as time spent rasterizing. + { + PaintTelemetry::AutoRecord record( + PaintTelemetry::Metric::FlushRasterization); + FlushAsyncPaints(); + } + + PaintTelemetry::AutoRecord record(PaintTelemetry::Metric::Rasterization); + AUTO_PROFILER_TRACING_MARKER("Paint", "Rasterize", GRAPHICS); + PerfStats::AutoMetricRecording autoRecording; + + Maybe startTime; + if (StaticPrefs::layers_acceleration_draw_fps()) { + startTime = Some(TimeStamp::Now()); + } + + AUTO_PROFILER_LABEL("ClientLayerManager::EndTransactionInternal", GRAPHICS); + +#ifdef MOZ_LAYERS_HAVE_LOG + MOZ_LAYERS_LOG((" ----- (beginning paint)")); + Log(); +#endif + + NS_ASSERTION(InConstruction(), "Should be in construction phase"); + mPhase = PHASE_DRAWING; + + ClientLayer* root = ClientLayer::ToClientLayer(GetRoot()); + + mTransactionIncomplete = false; + mQueuedAsyncPaints = false; + + // Apply pending tree updates before recomputing effective + // properties. + auto scrollIdsUpdated = GetRoot()->ApplyPendingUpdatesToSubtree(); + + mPaintedLayerCallback = aCallback; + mPaintedLayerCallbackData = aCallbackData; + + GetRoot()->ComputeEffectiveTransforms(Matrix4x4()); + + // Skip the painting if the device is in device-reset status. + if (!gfxPlatform::GetPlatform()->DidRenderingDeviceReset()) { + if (StaticPrefs::gfx_content_always_paint() && XRE_IsContentProcess()) { + TimeStamp start = TimeStamp::Now(); + root->RenderLayer(); + mLastPaintTime = TimeStamp::Now() - start; + } else { + root->RenderLayer(); + } + } else { + gfxCriticalNote << "LayerManager::EndTransaction skip RenderLayer()."; + } + + // Once we're sure we're not going to fall back to a full paint, + // notify the scroll frames which had pending updates. + if (!mTransactionIncomplete) { + for (ScrollableLayerGuid::ViewID scrollId : scrollIdsUpdated) { + nsLayoutUtils::NotifyPaintSkipTransaction(scrollId); + } + } + + if (!mRepeatTransaction && !GetRoot()->GetInvalidRegion().IsEmpty()) { + GetRoot()->Mutated(); + } + + if (!mIsRepeatTransaction) { + mAnimationReadyTime = TimeStamp::Now(); + GetRoot()->StartPendingAnimations(mAnimationReadyTime); + } + + mPaintedLayerCallback = nullptr; + mPaintedLayerCallbackData = nullptr; + + // Go back to the construction phase if the transaction isn't complete. + // Layout will update the layer tree and call EndTransaction(). + mPhase = mTransactionIncomplete ? PHASE_CONSTRUCTION : PHASE_NONE; + + NS_ASSERTION(!aCallback || !mTransactionIncomplete, + "If callback is not null, transaction must be complete"); + + if (gfxPlatform::GetPlatform()->DidRenderingDeviceReset()) { + FrameLayerBuilder::InvalidateAllLayers(this); + } + + if (startTime) { + PaintTiming& pt = mForwarder->GetPaintTiming(); + pt.rasterMs() = (TimeStamp::Now() - startTime.value()).ToMilliseconds(); + } + + return !mTransactionIncomplete; +} + +void ClientLayerManager::StorePluginWidgetConfigurations( + const nsTArray& aConfigurations) { + if (mForwarder) { + mForwarder->StorePluginWidgetConfigurations(aConfigurations); + } +} + +void ClientLayerManager::EndTransaction(DrawPaintedLayerCallback aCallback, + void* aCallbackData, + EndTransactionFlags aFlags) { + if (!mForwarder->IPCOpen()) { + mInTransaction = false; + return; + } + + if (mWidget) { + mWidget->PrepareWindowEffects(); + } + EndTransactionInternal(aCallback, aCallbackData, aFlags); + if (XRE_IsContentProcess()) { + RegisterPayload({CompositionPayloadType::eContentPaint, TimeStamp::Now()}); + } + ForwardTransaction(!(aFlags & END_NO_REMOTE_COMPOSITE)); + + if (mRepeatTransaction) { + mRepeatTransaction = false; + mIsRepeatTransaction = true; + + // BeginTransaction will reset the transaction start time, but we + // would like to keep the original time for telemetry purposes. + TimeStamp transactionStart = mTransactionStart; + if (BeginTransaction(mURL)) { + mTransactionStart = transactionStart; + ClientLayerManager::EndTransaction(aCallback, aCallbackData, aFlags); + } + + mIsRepeatTransaction = false; + } else { + MakeSnapshotIfRequired(); + } + + mInTransaction = false; + mTransactionStart = TimeStamp(); +} + +bool ClientLayerManager::EndEmptyTransaction(EndTransactionFlags aFlags) { + mInTransaction = false; + + if (!mRoot || !mForwarder->IPCOpen()) { + return false; + } + + if (!EndTransactionInternal(nullptr, nullptr, aFlags)) { + // Return without calling ForwardTransaction. This leaves the + // ShadowLayerForwarder transaction open; the following + // EndTransaction will complete it. + if (PaintThread::Get() && mQueuedAsyncPaints) { + PaintThread::Get()->QueueEndLayerTransaction(nullptr); + } + return false; + } + if (mWidget) { + mWidget->PrepareWindowEffects(); + } + ForwardTransaction(!(aFlags & END_NO_REMOTE_COMPOSITE)); + MakeSnapshotIfRequired(); + return true; +} + +CompositorBridgeChild* ClientLayerManager::GetRemoteRenderer() { + if (!mWidget) { + return nullptr; + } + + return mWidget->GetRemoteRenderer(); +} + +CompositorBridgeChild* ClientLayerManager::GetCompositorBridgeChild() { + if (!XRE_IsParentProcess()) { + return CompositorBridgeChild::Get(); + } + return GetRemoteRenderer(); +} + +void ClientLayerManager::FlushAsyncPaints() { + AUTO_PROFILER_LABEL_CATEGORY_PAIR(GRAPHICS_FlushingAsyncPaints); + + CompositorBridgeChild* cbc = GetCompositorBridgeChild(); + if (cbc) { + cbc->FlushAsyncPaints(); + } +} + +void ClientLayerManager::ScheduleComposite() { + mForwarder->ScheduleComposite(); +} + +void ClientLayerManager::DidComposite(TransactionId aTransactionId, + const TimeStamp& aCompositeStart, + const TimeStamp& aCompositeEnd) { + if (!mWidget) { + return; + } + + // Notifying the observers may tick the refresh driver which can cause + // a lot of different things to happen that may affect the lifetime of + // this layer manager. So let's make sure this object stays alive until + // the end of the method invocation. + RefPtr selfRef = this; + + // |aTransactionId| will be > 0 if the compositor is acknowledging a shadow + // layers transaction. + if (aTransactionId.IsValid()) { + nsIWidgetListener* listener = mWidget->GetWidgetListener(); + if (listener) { + mNotifyingWidgetListener = true; + listener->DidCompositeWindow(aTransactionId, aCompositeStart, + aCompositeEnd); + mNotifyingWidgetListener = false; + } + // DidCompositeWindow might have called Destroy on us and nulled out + // mWidget, see bug 1510058. Re-check it here. + if (mWidget) { + listener = mWidget->GetAttachedWidgetListener(); + if (listener) { + listener->DidCompositeWindow(aTransactionId, aCompositeStart, + aCompositeEnd); + } + } + if (mTransactionIdAllocator) { + mTransactionIdAllocator->NotifyTransactionCompleted(aTransactionId); + } + } + + // These observers fire whether or not we were in a transaction. + for (size_t i = 0; i < mDidCompositeObservers.Length(); i++) { + mDidCompositeObservers[i]->DidComposite(); + } +} + +void ClientLayerManager::GetCompositorSideAPZTestData( + APZTestData* aData) const { + if (mForwarder->HasShadowManager()) { + if (!mForwarder->GetShadowManager()->SendGetAPZTestData(aData)) { + NS_WARNING("Call to PLayerTransactionChild::SendGetAPZTestData() failed"); + } + } +} + +void ClientLayerManager::SetTransactionIdAllocator( + TransactionIdAllocator* aAllocator) { + // When changing the refresh driver, the previous refresh driver may never + // receive updates of pending transactions it's waiting for. So clear the + // waiting state before assigning another refresh driver. + if (mTransactionIdAllocator && (aAllocator != mTransactionIdAllocator)) { + mTransactionIdAllocator->ClearPendingTransactions(); + + // We should also reset the transaction id of the new allocator to previous + // allocator's last transaction id, so that completed transactions for + // previous allocator will be ignored and won't confuse the new allocator. + if (aAllocator) { + aAllocator->ResetInitialTransactionId( + mTransactionIdAllocator->LastTransactionId()); + } + } + + mTransactionIdAllocator = aAllocator; +} + +float ClientLayerManager::RequestProperty(const nsAString& aProperty) { + if (mForwarder->HasShadowManager()) { + float value; + if (!mForwarder->GetShadowManager()->SendRequestProperty( + nsString(aProperty), &value)) { + NS_WARNING("Call to PLayerTransactionChild::SendGetAPZTestData() failed"); + } + return value; + } + return -1; +} + +void ClientLayerManager::StartNewRepaintRequest( + SequenceNumber aSequenceNumber) { + if (StaticPrefs::apz_test_logging_enabled()) { + mApzTestData.StartNewRepaintRequest(aSequenceNumber); + } +} + +void ClientLayerManager::GetFrameUniformity(FrameUniformityData* aOutData) { + if (HasShadowManager()) { + mForwarder->GetShadowManager()->SendGetFrameUniformity(aOutData); + } +} + +void ClientLayerManager::MakeSnapshotIfRequired() { + if (!mShadowTarget) { + return; + } + if (mWidget) { + if (CompositorBridgeChild* remoteRenderer = GetRemoteRenderer()) { + // The compositor doesn't draw to a different sized surface + // when there's a rotation. Instead we rotate the result + // when drawing into dt + LayoutDeviceIntRect outerBounds = mWidget->GetBounds(); + + IntRect bounds = ToOutsideIntRect(mShadowTarget->GetClipExtents()); + if (mTargetRotation) { + bounds = + RotateRect(bounds, outerBounds.ToUnknownRect(), mTargetRotation); + } + + SurfaceDescriptor inSnapshot; + if (!bounds.IsEmpty() && + mForwarder->AllocSurfaceDescriptor( + bounds.Size(), gfxContentType::COLOR_ALPHA, &inSnapshot)) { + // Make a copy of |inSnapshot| because the call to send it over IPC + // will call forget() on the Shmem inside, and zero it out. + SurfaceDescriptor outSnapshot = inSnapshot; + + if (remoteRenderer->SendMakeSnapshot(inSnapshot, bounds)) { + RefPtr surf = GetSurfaceForDescriptor(outSnapshot); + DrawTarget* dt = mShadowTarget->GetDrawTarget(); + + Rect dstRect(bounds.X(), bounds.Y(), bounds.Width(), bounds.Height()); + Rect srcRect(0, 0, bounds.Width(), bounds.Height()); + + gfx::Matrix rotate = ComputeTransformForUnRotation( + outerBounds.ToUnknownRect(), mTargetRotation); + + gfx::Matrix oldMatrix = dt->GetTransform(); + dt->SetTransform(rotate * oldMatrix); + dt->DrawSurface(surf, dstRect, srcRect, DrawSurfaceOptions(), + DrawOptions(1.0f, CompositionOp::OP_OVER)); + dt->SetTransform(oldMatrix); + } + mForwarder->DestroySurfaceDescriptor(&outSnapshot); + } + } + } + mShadowTarget = nullptr; +} + +void ClientLayerManager::FlushRendering() { + if (mWidget) { + if (CompositorBridgeChild* remoteRenderer = mWidget->GetRemoteRenderer()) { + if (mWidget->SynchronouslyRepaintOnResize() || + StaticPrefs::layers_force_synchronous_resize()) { + remoteRenderer->SendFlushRendering(); + } else { + remoteRenderer->SendFlushRenderingAsync(); + } + } + } +} + +void ClientLayerManager::WaitOnTransactionProcessed() { + CompositorBridgeChild* remoteRenderer = GetCompositorBridgeChild(); + if (remoteRenderer) { + remoteRenderer->SendWaitOnTransactionProcessed(); + } +} +void ClientLayerManager::UpdateTextureFactoryIdentifier( + const TextureFactoryIdentifier& aNewIdentifier) { + mForwarder->IdentifyTextureHost(aNewIdentifier); +} + +void ClientLayerManager::SendInvalidRegion(const nsIntRegion& aRegion) { + if (mWidget) { + if (CompositorBridgeChild* remoteRenderer = mWidget->GetRemoteRenderer()) { + remoteRenderer->SendNotifyRegionInvalidated(aRegion); + } + } +} + +uint32_t ClientLayerManager::StartFrameTimeRecording(int32_t aBufferSize) { + CompositorBridgeChild* renderer = GetRemoteRenderer(); + if (renderer) { + uint32_t startIndex; + renderer->SendStartFrameTimeRecording(aBufferSize, &startIndex); + return startIndex; + } + return -1; +} + +void ClientLayerManager::StopFrameTimeRecording( + uint32_t aStartIndex, nsTArray& aFrameIntervals) { + CompositorBridgeChild* renderer = GetRemoteRenderer(); + if (renderer) { + renderer->SendStopFrameTimeRecording(aStartIndex, &aFrameIntervals); + } +} + +void ClientLayerManager::ForwardTransaction(bool aScheduleComposite) { + AUTO_PROFILER_TRACING_MARKER("Paint", "ForwardTransaction", GRAPHICS); + TimeStamp start = TimeStamp::Now(); + + GetCompositorBridgeChild()->EndCanvasTransaction(); + + // Skip the synchronization for buffer since we also skip the painting during + // device-reset status. With OMTP, we have to wait for async paints + // before we synchronize and it's done on the paint thread. + RefPtr syncObject = nullptr; + if (!gfxPlatform::GetPlatform()->DidRenderingDeviceReset()) { + if (mForwarder->GetSyncObject() && + mForwarder->GetSyncObject()->IsSyncObjectValid()) { + syncObject = mForwarder->GetSyncObject(); + } + } + + // If there were async paints queued, then we need to notify the paint thread + // that we finished queuing async paints so it can schedule a runnable after + // all async painting is finished to do a texture sync and unblock the main + // thread if it is waiting before doing a new layer transaction. + if (mQueuedAsyncPaints) { + MOZ_ASSERT(PaintThread::Get()); + PaintThread::Get()->QueueEndLayerTransaction(syncObject); + } else if (syncObject) { + syncObject->Synchronize(); + } + + mPhase = PHASE_FORWARD; + + mLatestTransactionId = + mTransactionIdAllocator->GetTransactionId(!mIsRepeatTransaction); + TimeStamp refreshStart = mTransactionIdAllocator->GetTransactionStart(); + if (!refreshStart) { + refreshStart = mTransactionStart; + } + + if (StaticPrefs::gfx_content_always_paint() && XRE_IsContentProcess()) { + mForwarder->SendPaintTime(mLatestTransactionId, mLastPaintTime); + } + + // forward this transaction's changeset to our LayerManagerComposite + bool sent = false; + bool ok = mForwarder->EndTransaction( + mRegionToClear, mLatestTransactionId, aScheduleComposite, + mPaintSequenceNumber, mIsRepeatTransaction, + mTransactionIdAllocator->GetVsyncId(), + mTransactionIdAllocator->GetVsyncStart(), refreshStart, mTransactionStart, + mContainsSVG, mURL, &sent, mPayload); + + if (ok) { + if (sent) { + // Our payload has now been dispatched. + mPayload.Clear(); + mNeedsComposite = false; + } + } else if (HasShadowManager()) { + NS_WARNING("failed to forward Layers transaction"); + } + + if (!sent) { + // Clear the transaction id so that it doesn't get returned + // unless we forwarded to somewhere that doesn't actually + // have a compositor. + mTransactionIdAllocator->RevokeTransactionId(mLatestTransactionId); + mLatestTransactionId = mLatestTransactionId.Prev(); + } + + mPhase = PHASE_NONE; + + // this may result in Layers being deleted, which results in + // PLayer::Send__delete__() and DeallocShmem() + mKeepAlive.Clear(); + + BrowserChild* window = mWidget ? mWidget->GetOwningBrowserChild() : nullptr; + if (window) { + TimeStamp end = TimeStamp::Now(); + window->DidRequestComposite(start, end); + } +} + +ShadowableLayer* ClientLayerManager::Hold(Layer* aLayer) { + MOZ_ASSERT(HasShadowManager(), "top-level tree, no shadow tree to remote to"); + + ShadowableLayer* shadowable = ClientLayer::ToClientLayer(aLayer); + MOZ_ASSERT(shadowable, "trying to remote an unshadowable layer"); + + mKeepAlive.AppendElement(aLayer); + return shadowable; +} + +bool ClientLayerManager::IsCompositingCheap() { + // Whether compositing is cheap depends on the parent backend. + return mForwarder->mShadowManager && + LayerManager::IsCompositingCheap( + mForwarder->GetCompositorBackendType()); +} + +bool ClientLayerManager::AreComponentAlphaLayersEnabled() { + return GetCompositorBackendType() != LayersBackend::LAYERS_BASIC && + AsShadowForwarder()->SupportsComponentAlpha() && + LayerManager::AreComponentAlphaLayersEnabled(); +} + +void ClientLayerManager::SetIsFirstPaint() { mForwarder->SetIsFirstPaint(); } + +void ClientLayerManager::SetFocusTarget(const FocusTarget& aFocusTarget) { + mForwarder->SetFocusTarget(aFocusTarget); +} + +void ClientLayerManager::ClearCachedResources(Layer* aSubtree) { + if (mDestroyed) { + // ClearCachedResource was already called by ClientLayerManager::Destroy + return; + } + MOZ_ASSERT(!HasShadowManager() || !aSubtree); + mForwarder->ClearCachedResources(); + if (aSubtree) { + ClearLayer(aSubtree); + } else if (mRoot) { + ClearLayer(mRoot); + } +} + +void ClientLayerManager::OnMemoryPressure(MemoryPressureReason aWhy) { + if (mRoot) { + HandleMemoryPressureLayer(mRoot); + } + + if (GetCompositorBridgeChild()) { + GetCompositorBridgeChild()->HandleMemoryPressure(); + } +} + +void ClientLayerManager::ClearLayer(Layer* aLayer) { + aLayer->ClearCachedResources(); + for (Layer* child = aLayer->GetFirstChild(); child; + child = child->GetNextSibling()) { + ClearLayer(child); + } +} + +void ClientLayerManager::HandleMemoryPressureLayer(Layer* aLayer) { + ClientLayer::ToClientLayer(aLayer)->HandleMemoryPressure(); + for (Layer* child = aLayer->GetFirstChild(); child; + child = child->GetNextSibling()) { + HandleMemoryPressureLayer(child); + } +} + +void ClientLayerManager::GetBackendName(nsAString& aName) { + switch (mForwarder->GetCompositorBackendType()) { + case LayersBackend::LAYERS_NONE: + aName.AssignLiteral("None"); + return; + case LayersBackend::LAYERS_BASIC: + aName.AssignLiteral("Basic"); + return; + case LayersBackend::LAYERS_OPENGL: + aName.AssignLiteral("OpenGL"); + return; + case LayersBackend::LAYERS_D3D11: { +#ifdef XP_WIN + if (DeviceManagerDx::Get()->IsWARP()) { + aName.AssignLiteral("Direct3D 11 WARP"); + } else { + aName.AssignLiteral("Direct3D 11"); + } +#endif + return; + } + default: + MOZ_CRASH("Invalid backend"); + } +} + +bool ClientLayerManager::AsyncPanZoomEnabled() const { + return mWidget && mWidget->AsyncPanZoomEnabled(); +} + +void ClientLayerManager::SetLayersObserverEpoch(LayersObserverEpoch aEpoch) { + mForwarder->SetLayersObserverEpoch(aEpoch); +} + +void ClientLayerManager::AddDidCompositeObserver( + DidCompositeObserver* aObserver) { + if (!mDidCompositeObservers.Contains(aObserver)) { + mDidCompositeObservers.AppendElement(aObserver); + } +} + +void ClientLayerManager::RemoveDidCompositeObserver( + DidCompositeObserver* aObserver) { + mDidCompositeObservers.RemoveElement(aObserver); +} + +already_AddRefed +ClientLayerManager::CreatePersistentBufferProvider(const gfx::IntSize& aSize, + gfx::SurfaceFormat aFormat) { + // Don't use a shared buffer provider if compositing is considered "not cheap" + // because the canvas will most likely be flattened into a thebes layer + // instead of being sent to the compositor, in which case rendering into + // shared memory is wasteful. + if (IsCompositingCheap()) { + RefPtr provider = + PersistentBufferProviderShared::Create(aSize, aFormat, + AsShadowForwarder()); + if (provider) { + return provider.forget(); + } + } + + return LayerManager::CreatePersistentBufferProvider(aSize, aFormat); +} + +ClientLayer::~ClientLayer() { MOZ_COUNT_DTOR(ClientLayer); } + +ClientLayer* ClientLayer::ToClientLayer(Layer* aLayer) { + return static_cast(aLayer->ImplData()); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/ClientLayerManager.h b/gfx/layers/client/ClientLayerManager.h new file mode 100644 index 0000000000..587f9ec3f2 --- /dev/null +++ b/gfx/layers/client/ClientLayerManager.h @@ -0,0 +1,413 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_CLIENTLAYERMANAGER_H +#define GFX_CLIENTLAYERMANAGER_H + +#include // for size_t +#include // for uint32_t, int32_t +#include // for operator new +#include // for string +#include // for forward +#include "gfxContext.h" // for gfxContext +#include "mozilla/AlreadyAddRefed.h" // for already_AddRefed +#include "mozilla/Assertions.h" // for AssertionConditionType, MOZ_ASSERT, MOZ_ASSERT_HELPER2 +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/TimeStamp.h" // for TimeStamp, BaseTimeDuration +#include "mozilla/WidgetUtils.h" // for ScreenRotation +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/gfx/Types.h" // for SurfaceFormat +#include "mozilla/layers/APZTestData.h" // for APZTestData, SequenceNumber +#include "mozilla/layers/CompositorTypes.h" // for TextureFactoryIdentifier +#include "mozilla/layers/LayerManager.h" // for LayerManager::DrawPaintedLayerCallback, DidCompositeObserver (ptr only), Laye... +#include "mozilla/layers/LayersTypes.h" // for LayerHandle, LayersBackend, TransactionId, BufferMode, LayersBackend::LAYERS_... +#include "mozilla/layers/MemoryPressureObserver.h" // for MemoryPressureListener, MemoryPressureObserver (ptr only), MemoryPressureReason +#include "mozilla/layers/ScrollableLayerGuid.h" // for ScrollableLayerGuid, ScrollableLayerGuid::ViewID +#include "mozilla/layers/ShadowLayers.h" // for ShadowLayerForwarder, ShadowableLayer +#include "nsISupports.h" // for MOZ_COUNTED_DEFAULT_CTOR +#include "nsIThread.h" // for TimeDuration +#include "nsIWidget.h" // for nsIWidget +#include "nsRegion.h" // for nsIntRegion +#include "nsStringFwd.h" // for nsCString, nsAString +#include "nsTArray.h" // for nsTArray + +// XXX Includes that could be avoided by moving function implementation to the +// cpp file. +#include "mozilla/StaticPrefs_apz.h" // for apz_test_logging_enabled + +namespace mozilla { +namespace layers { + +class CanvasLayer; +class ColorLayer; +class ContainerLayer; +class ClientPaintedLayer; +class CompositorBridgeChild; +class FocusTarget; +class FrameUniformityData; +class ImageLayer; +class Layer; +class PCompositorBridgeChild; +class PaintTiming; +class PaintedLayer; +class PersistentBufferProvider; +class ReadbackLayer; +class ReadbackProcessor; +class RefLayer; +class TransactionIdAllocator; + +class ClientLayerManager final : public LayerManager, + public MemoryPressureListener { + typedef nsTArray > LayerRefArray; + + public: + explicit ClientLayerManager(nsIWidget* aWidget); + bool Initialize(PCompositorBridgeChild* aCBChild, bool aShouldAccelerate, + TextureFactoryIdentifier* aTextureFactoryIdentifier); + void Destroy() override; + + protected: + virtual ~ClientLayerManager(); + + public: + ShadowLayerForwarder* AsShadowForwarder() override { return mForwarder; } + + KnowsCompositor* AsKnowsCompositor() override { return mForwarder; } + + ClientLayerManager* AsClientLayerManager() override { return this; } + + int32_t GetMaxTextureSize() const override; + + void SetDefaultTargetConfiguration(BufferMode aDoubleBuffering, + ScreenRotation aRotation); + bool BeginTransactionWithTarget(gfxContext* aTarget, + const nsCString& aURL) override; + bool BeginTransaction(const nsCString& aURL) override; + bool EndEmptyTransaction(EndTransactionFlags aFlags = END_DEFAULT) override; + void EndTransaction(DrawPaintedLayerCallback aCallback, void* aCallbackData, + EndTransactionFlags aFlags = END_DEFAULT) override; + + LayersBackend GetBackendType() override { + return LayersBackend::LAYERS_CLIENT; + } + LayersBackend GetCompositorBackendType() override { + return AsShadowForwarder()->GetCompositorBackendType(); + } + void GetBackendName(nsAString& name) override; + const char* Name() const override { return "Client"; } + + void SetRoot(Layer* aLayer) override; + + void Mutated(Layer* aLayer) override; + void MutatedSimple(Layer* aLayer) override; + + already_AddRefed CreatePaintedLayer() override; + already_AddRefed CreatePaintedLayerWithHint( + PaintedLayerCreationHint aHint) override; + already_AddRefed CreateContainerLayer() override; + already_AddRefed CreateImageLayer() override; + already_AddRefed CreateCanvasLayer() override; + already_AddRefed CreateReadbackLayer() override; + already_AddRefed CreateColorLayer() override; + already_AddRefed CreateRefLayer() override; + + void UpdateTextureFactoryIdentifier( + const TextureFactoryIdentifier& aNewIdentifier) override; + TextureFactoryIdentifier GetTextureFactoryIdentifier() override { + return AsShadowForwarder()->GetTextureFactoryIdentifier(); + } + + void FlushRendering() override; + void WaitOnTransactionProcessed() override; + void SendInvalidRegion(const nsIntRegion& aRegion) override; + + uint32_t StartFrameTimeRecording(int32_t aBufferSize) override; + + void StopFrameTimeRecording(uint32_t aStartIndex, + nsTArray& aFrameIntervals) override; + + bool NeedsWidgetInvalidation() override { return false; } + + ShadowableLayer* Hold(Layer* aLayer); + + bool HasShadowManager() const { return mForwarder->HasShadowManager(); } + + bool IsCompositingCheap() override; + bool HasShadowManagerInternal() const override { return HasShadowManager(); } + + void SetIsFirstPaint() override; + bool GetIsFirstPaint() const override { + return mForwarder->GetIsFirstPaint(); + } + + void SetFocusTarget(const FocusTarget& aFocusTarget) override; + + /** + * Pass through call to the forwarder for nsPresContext's + * CollectPluginGeometryUpdates. Passes widget configuration information + * to the compositor for transmission to the chrome process. This + * configuration gets set when the window paints. + */ + void StorePluginWidgetConfigurations( + const nsTArray& aConfigurations) override; + + // Drop cached resources and ask our shadow manager to do the same, + // if we have one. + void ClearCachedResources(Layer* aSubtree = nullptr) override; + + void OnMemoryPressure(MemoryPressureReason aWhy) override; + + void SetRepeatTransaction() { mRepeatTransaction = true; } + bool GetRepeatTransaction() { return mRepeatTransaction; } + + bool IsRepeatTransaction() { return mIsRepeatTransaction; } + + void SetTransactionIncomplete() { mTransactionIncomplete = true; } + void SetQueuedAsyncPaints() { mQueuedAsyncPaints = true; } + + bool HasShadowTarget() { return !!mShadowTarget; } + + void SetShadowTarget(gfxContext* aTarget) { mShadowTarget = aTarget; } + + bool CompositorMightResample() { return mCompositorMightResample; } + + DrawPaintedLayerCallback GetPaintedLayerCallback() const { + return mPaintedLayerCallback; + } + + void* GetPaintedLayerCallbackData() const { + return mPaintedLayerCallbackData; + } + + CompositorBridgeChild* GetRemoteRenderer(); + + CompositorBridgeChild* GetCompositorBridgeChild() override; + + bool InConstruction() { return mPhase == PHASE_CONSTRUCTION; } +#ifdef DEBUG + bool InDrawing() { return mPhase == PHASE_DRAWING; } + bool InForward() { return mPhase == PHASE_FORWARD; } +#endif + bool InTransaction() { return mPhase != PHASE_NONE; } + + void SetNeedsComposite(bool aNeedsComposite) override { + mNeedsComposite = aNeedsComposite; + } + bool NeedsComposite() const override { return mNeedsComposite; } + + void ScheduleComposite() override; + void GetFrameUniformity(FrameUniformityData* aFrameUniformityData) override; + + void DidComposite(TransactionId aTransactionId, + const mozilla::TimeStamp& aCompositeStart, + const mozilla::TimeStamp& aCompositeEnd) override; + + bool AreComponentAlphaLayersEnabled() override; + + // Log APZ test data for the current paint. We supply the paint sequence + // number ourselves, and take care of calling APZTestData::StartNewPaint() + // when a new paint is started. + void LogTestDataForCurrentPaint(ScrollableLayerGuid::ViewID aScrollId, + const std::string& aKey, + const std::string& aValue) { + MOZ_ASSERT(StaticPrefs::apz_test_logging_enabled(), "don't call me"); + mApzTestData.LogTestDataForPaint(mPaintSequenceNumber, aScrollId, aKey, + aValue); + } + + // Log APZ test data for a repaint request. The sequence number must be + // passed in from outside, and APZTestData::StartNewRepaintRequest() needs + // to be called from the outside as well when a new repaint request is + // started. + void StartNewRepaintRequest(SequenceNumber aSequenceNumber); + + // TODO(botond): When we start using this and write a wrapper similar to + // nsLayoutUtils::LogTestDataForPaint(), make sure that wrapper checks + // StaticPrefs::apz_test_logging_enabled(). + void LogTestDataForRepaintRequest(SequenceNumber aSequenceNumber, + ScrollableLayerGuid::ViewID aScrollId, + const std::string& aKey, + const std::string& aValue) { + MOZ_ASSERT(StaticPrefs::apz_test_logging_enabled(), "don't call me"); + mApzTestData.LogTestDataForRepaintRequest(aSequenceNumber, aScrollId, aKey, + aValue); + } + void LogAdditionalTestData(const std::string& aKey, + const std::string& aValue) { + MOZ_ASSERT(StaticPrefs::apz_test_logging_enabled(), "don't call me"); + mApzTestData.RecordAdditionalData(aKey, aValue); + } + + // Get the content-side APZ test data for reading. For writing, use the + // LogTestData...() functions. + const APZTestData& GetAPZTestData() const { return mApzTestData; } + + // Get a copy of the compositor-side APZ test data for our layers ID. + void GetCompositorSideAPZTestData(APZTestData* aData) const; + + void SetTransactionIdAllocator(TransactionIdAllocator* aAllocator) override; + + TransactionId GetLastTransactionId() override { return mLatestTransactionId; } + + float RequestProperty(const nsAString& aProperty) override; + + bool AsyncPanZoomEnabled() const override; + + void SetLayersObserverEpoch(LayersObserverEpoch aEpoch) override; + + void AddDidCompositeObserver(DidCompositeObserver* aObserver) override; + void RemoveDidCompositeObserver(DidCompositeObserver* aObserver) override; + + already_AddRefed CreatePersistentBufferProvider( + const gfx::IntSize& aSize, gfx::SurfaceFormat aFormat) override; + + static PaintTiming* MaybeGetPaintTiming(LayerManager* aManager) { + if (!aManager) { + return nullptr; + } + if (ClientLayerManager* lm = aManager->AsClientLayerManager()) { + return &lm->AsShadowForwarder()->GetPaintTiming(); + } + return nullptr; + } + + protected: + enum TransactionPhase { + PHASE_NONE, + PHASE_CONSTRUCTION, + PHASE_DRAWING, + PHASE_FORWARD + }; + TransactionPhase mPhase; + + private: + /** + * Forward transaction results to the parent context. + */ + void ForwardTransaction(bool aScheduleComposite); + + /** + * Take a snapshot of the parent context, and copy + * it into mShadowTarget. + */ + void MakeSnapshotIfRequired(); + + void ClearLayer(Layer* aLayer); + + void HandleMemoryPressureLayer(Layer* aLayer); + + bool EndTransactionInternal(DrawPaintedLayerCallback aCallback, + void* aCallbackData, EndTransactionFlags); + + void FlushAsyncPaints(); + + LayerRefArray mKeepAlive; + + nsIWidget* mWidget; + + /* PaintedLayer callbacks; valid at the end of a transaciton, + * while rendering */ + DrawPaintedLayerCallback mPaintedLayerCallback; + void* mPaintedLayerCallbackData; + + // When we're doing a transaction in order to draw to a non-default + // target, the layers transaction is only performed in order to send + // a PLayers:Update. We save the original non-default target to + // mShadowTarget, and then perform the transaction using + // mDummyTarget as the render target. After the transaction ends, + // we send a message to our remote side to capture the actual pixels + // being drawn to the default target, and then copy those pixels + // back to mShadowTarget. + RefPtr mShadowTarget; + + RefPtr mTransactionIdAllocator; + TransactionId mLatestTransactionId; + TimeDuration mLastPaintTime; + + // Sometimes we draw to targets that don't natively support + // landscape/portrait orientation. When we need to implement that + // ourselves, |mTargetRotation| describes the induced transform we + // need to apply when compositing content to our target. + ScreenRotation mTargetRotation; + + // Used to repeat the transaction right away (to avoid rebuilding + // a display list) to support progressive drawing. + bool mRepeatTransaction; + bool mIsRepeatTransaction; + bool mTransactionIncomplete; + bool mCompositorMightResample; + bool mNeedsComposite; + bool mQueuedAsyncPaints; + bool mNotifyingWidgetListener; + + // An incrementing sequence number for paints. + // Incremented in BeginTransaction(), but not for repeat transactions. + uint32_t mPaintSequenceNumber; + + APZTestData mApzTestData; + + RefPtr mForwarder; + mozilla::TimeStamp mTransactionStart; + nsCString mURL; + + nsTArray mDidCompositeObservers; + + RefPtr mMemoryPressureObserver; +}; + +class ClientLayer : public ShadowableLayer { + public: + MOZ_COUNTED_DEFAULT_CTOR(ClientLayer) + + ~ClientLayer(); + + // Shrink memory usage. + // Called when "memory-pressure" is observed. + virtual void HandleMemoryPressure() {} + + virtual void RenderLayer() = 0; + virtual void RenderLayerWithReadback(ReadbackProcessor* aReadback) { + RenderLayer(); + } + + virtual ClientPaintedLayer* AsThebes() { return nullptr; } + + static ClientLayer* ToClientLayer(Layer* aLayer); + + template + static inline void RenderMaskLayers(LayerType* aLayer) { + if (aLayer->GetMaskLayer()) { + ToClientLayer(aLayer->GetMaskLayer())->RenderLayer(); + } + for (size_t i = 0; i < aLayer->GetAncestorMaskLayerCount(); i++) { + ToClientLayer(aLayer->GetAncestorMaskLayerAt(i))->RenderLayer(); + } + } +}; + +// Create a LayerHandle for aLayer, if we're forwarding our layer tree +// to a parent process. Record the new layer creation in the current +// open transaction as a side effect. +template +void CreateShadowFor(ClientLayer* aLayer, ClientLayerManager* aMgr, + CreatedMethod aMethod) { + LayerHandle shadow = aMgr->AsShadowForwarder()->ConstructShadowFor(aLayer); + if (!shadow) { + return; + } + + aLayer->SetShadow(aMgr->AsShadowForwarder(), shadow); + (aMgr->AsShadowForwarder()->*aMethod)(aLayer); + aMgr->Hold(aLayer->AsLayer()); +} + +#define CREATE_SHADOW(_type) \ + CreateShadowFor(layer, this, &ShadowLayerForwarder::Created##_type##Layer) + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_CLIENTLAYERMANAGER_H */ diff --git a/gfx/layers/client/ClientPaintedLayer.cpp b/gfx/layers/client/ClientPaintedLayer.cpp new file mode 100644 index 0000000000..1a3f101cc3 --- /dev/null +++ b/gfx/layers/client/ClientPaintedLayer.cpp @@ -0,0 +1,210 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ClientPaintedLayer.h" +#include "ClientTiledPaintedLayer.h" // for ClientTiledPaintedLayer +#include // for uint32_t +#include "GeckoProfiler.h" // for AUTO_PROFILER_LABEL +#include "client/ClientLayerManager.h" // for ClientLayerManager, etc +#include "gfxContext.h" // for gfxContext +#include "gfx2DGlue.h" +#include "gfxEnv.h" // for gfxEnv +#include "gfxRect.h" // for gfxRect + +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/gfx/2D.h" // for DrawTarget +#include "mozilla/gfx/DrawEventRecorder.h" +#include "mozilla/gfx/Matrix.h" // for Matrix +#include "mozilla/gfx/Rect.h" // for Rect, IntRect +#include "mozilla/gfx/Types.h" // for Float, etc +#include "mozilla/layers/LayersTypes.h" +#include "mozilla/Preferences.h" +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsISupportsImpl.h" // for Layer::AddRef, etc +#include "nsRect.h" // for mozilla::gfx::IntRect +#include "PaintThread.h" +#include "ReadbackProcessor.h" +#include "RotatedBuffer.h" + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +bool ClientPaintedLayer::EnsureContentClient() { + if (!mContentClient) { + mContentClient = ContentClient::CreateContentClient( + ClientManager()->AsShadowForwarder()); + + if (!mContentClient) { + return false; + } + + mContentClient->Connect(); + ClientManager()->AsShadowForwarder()->Attach(mContentClient, this); + MOZ_ASSERT(mContentClient->GetForwarder()); + } + + return true; +} + +void ClientPaintedLayer::UpdateContentClient(PaintState& aState) { + Mutated(); + + AddToValidRegion(aState.mRegionToDraw); + + ContentClientRemoteBuffer* contentClientRemote = + static_cast(mContentClient.get()); + MOZ_ASSERT(contentClientRemote->GetIPCHandle()); + + // Hold(this) ensures this layer is kept alive through the current transaction + // The ContentClient assumes this layer is kept alive (e.g., in CreateBuffer), + // so deleting this Hold for whatever reason will break things. + ClientManager()->Hold(this); + contentClientRemote->Updated(aState.mRegionToDraw, + mVisibleRegion.ToUnknownRegion()); +} + +bool ClientPaintedLayer::UpdatePaintRegion(PaintState& aState) { + SubtractFromValidRegion(aState.mRegionToInvalidate); + + if (!aState.mRegionToDraw.IsEmpty() && + !ClientManager()->GetPaintedLayerCallback()) { + ClientManager()->SetTransactionIncomplete(); + return false; + } + + // The area that became invalid and is visible needs to be repainted + // (this could be the whole visible area if our buffer switched + // from RGB to RGBA, because we might need to repaint with + // subpixel AA) + aState.mRegionToInvalidate.And(aState.mRegionToInvalidate, + GetLocalVisibleRegion().ToUnknownRegion()); + return true; +} + +void ClientPaintedLayer::FinishPaintState(PaintState& aState) { + if (aState.mAsyncTask && !aState.mAsyncTask->mCapture->IsEmpty()) { + ClientManager()->SetQueuedAsyncPaints(); + PaintThread::Get()->QueuePaintTask(std::move(aState.mAsyncTask)); + } +} + +uint32_t ClientPaintedLayer::GetPaintFlags(ReadbackProcessor* aReadback) { + uint32_t flags = ContentClient::PAINT_CAN_DRAW_ROTATED; +#ifndef MOZ_IGNORE_PAINT_WILL_RESAMPLE + if (ClientManager()->CompositorMightResample()) { + flags |= ContentClient::PAINT_WILL_RESAMPLE; + } + if (!(flags & ContentClient::PAINT_WILL_RESAMPLE)) { + if (MayResample()) { + flags |= ContentClient::PAINT_WILL_RESAMPLE; + } + } +#endif + if ((!aReadback || !UsedForReadback()) && PaintThread::Get()) { + flags |= ContentClient::PAINT_ASYNC; + } + return flags; +} + +void ClientPaintedLayer::RenderLayerWithReadback(ReadbackProcessor* aReadback) { + AUTO_PROFILER_LABEL("ClientPaintedLayer::RenderLayerWithReadback", GRAPHICS); + NS_ASSERTION(ClientManager()->InDrawing(), "Can only draw in drawing phase"); + + RenderMaskLayers(this); + + if (!EnsureContentClient()) { + return; + } + + nsTArray readbackUpdates; + nsIntRegion readbackRegion; + if (aReadback && UsedForReadback()) { + aReadback->GetPaintedLayerUpdates(this, &readbackUpdates); + } + + uint32_t flags = GetPaintFlags(aReadback); + + PaintState state = mContentClient->BeginPaint(this, flags); + if (!UpdatePaintRegion(state)) { + mContentClient->EndPaint(state, nullptr); + FinishPaintState(state); + return; + } + + bool didUpdate = false; + RotatedBuffer::DrawIterator iter; + while (DrawTarget* target = + mContentClient->BorrowDrawTargetForPainting(state, &iter)) { + SetAntialiasingFlags(this, target); + + RefPtr ctx = + gfxContext::CreatePreservingTransformOrNull(target); + MOZ_ASSERT(ctx); // already checked the target above + + if (!gfxEnv::SkipRasterization()) { + if (!target->IsCaptureDT()) { + target->ClearRect(Rect()); + if (target->IsValid()) { + ClientManager()->GetPaintedLayerCallback()( + this, ctx, iter.mDrawRegion, iter.mDrawRegion, state.mClip, + state.mRegionToInvalidate, + ClientManager()->GetPaintedLayerCallbackData()); + } + } else { + ClientManager()->GetPaintedLayerCallback()( + this, ctx, iter.mDrawRegion, iter.mDrawRegion, state.mClip, + state.mRegionToInvalidate, + ClientManager()->GetPaintedLayerCallbackData()); + } + } + + ctx = nullptr; + mContentClient->ReturnDrawTarget(target); + didUpdate = true; + } + + mContentClient->EndPaint(state, &readbackUpdates); + FinishPaintState(state); + + if (didUpdate) { + UpdateContentClient(state); + } +} + +already_AddRefed ClientLayerManager::CreatePaintedLayer() { + return CreatePaintedLayerWithHint(NONE); +} + +already_AddRefed ClientLayerManager::CreatePaintedLayerWithHint( + PaintedLayerCreationHint aHint) { + NS_ASSERTION(InConstruction(), "Only allowed in construction phase"); + if (gfxPlatform::GetPlatform()->UsesTiling()) { + RefPtr layer = + new ClientTiledPaintedLayer(this, aHint); + CREATE_SHADOW(Painted); + return layer.forget(); + } else { + RefPtr layer = new ClientPaintedLayer(this, aHint); + CREATE_SHADOW(Painted); + return layer.forget(); + } +} + +void ClientPaintedLayer::PrintInfo(std::stringstream& aStream, + const char* aPrefix) { + PaintedLayer::PrintInfo(aStream, aPrefix); + if (mContentClient) { + aStream << "\n"; + nsAutoCString pfx(aPrefix); + pfx += " "; + mContentClient->PrintInfo(aStream, pfx.get()); + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/ClientPaintedLayer.h b/gfx/layers/client/ClientPaintedLayer.h new file mode 100644 index 0000000000..96398262af --- /dev/null +++ b/gfx/layers/client/ClientPaintedLayer.h @@ -0,0 +1,123 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_CLIENTPAINTEDLAYER_H +#define GFX_CLIENTPAINTEDLAYER_H + +#include "ClientLayerManager.h" // for ClientLayerManager, etc +#include "Layers.h" // for PaintedLayer, etc +#include "RotatedBuffer.h" // for RotatedBuffer, etc +#include "mozilla/Attributes.h" // for override +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/layers/ContentClient.h" // for ContentClient +#include "mozilla/mozalloc.h" // for operator delete +#include "nsDebug.h" // for NS_ASSERTION +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "nsRegion.h" // for nsIntRegion +#include "mozilla/layers/PLayerTransaction.h" // for PaintedLayerAttributes + +namespace mozilla { +namespace gfx { +class DrawEventRecorderMemory; +class DrawTargetCapture; +}; // namespace gfx + +namespace layers { +class CompositableClient; +class ShadowableLayer; +class SpecificLayerAttributes; + +class ClientPaintedLayer : public PaintedLayer, public ClientLayer { + public: + typedef ContentClient::PaintState PaintState; + typedef ContentClient::ContentType ContentType; + + explicit ClientPaintedLayer( + ClientLayerManager* aLayerManager, + LayerManager::PaintedLayerCreationHint aCreationHint = LayerManager::NONE) + : PaintedLayer(aLayerManager, static_cast(this), + aCreationHint), + mContentClient(nullptr) { + MOZ_COUNT_CTOR(ClientPaintedLayer); + } + + protected: + virtual ~ClientPaintedLayer() { + if (mContentClient) { + mContentClient->OnDetach(); + mContentClient = nullptr; + } + MOZ_COUNT_DTOR(ClientPaintedLayer); + } + + public: + void SetVisibleRegion(const LayerIntRegion& aRegion) override { + NS_ASSERTION(ClientManager()->InConstruction(), + "Can only set properties in construction phase"); + PaintedLayer::SetVisibleRegion(aRegion); + } + void InvalidateRegion(const nsIntRegion& aRegion) override { + NS_ASSERTION(ClientManager()->InConstruction(), + "Can only set properties in construction phase"); + mInvalidRegion.Add(aRegion); + UpdateValidRegionAfterInvalidRegionChanged(); + } + + void RenderLayer() override { RenderLayerWithReadback(nullptr); } + + void RenderLayerWithReadback(ReadbackProcessor* aReadback) override; + + void ClearCachedResources() override { + if (mContentClient) { + mContentClient->Clear(); + } + ClearValidRegion(); + DestroyBackBuffer(); + } + + void HandleMemoryPressure() override { + if (mContentClient) { + mContentClient->HandleMemoryPressure(); + } + } + + void FillSpecificAttributes(SpecificLayerAttributes& aAttrs) override { + aAttrs = PaintedLayerAttributes(GetValidRegion()); + } + + ClientLayerManager* ClientManager() { + return static_cast(mManager); + } + + Layer* AsLayer() override { return this; } + ShadowableLayer* AsShadowableLayer() override { return this; } + + CompositableClient* GetCompositableClient() override { + return mContentClient; + } + + void Disconnect() override { mContentClient = nullptr; } + + protected: + void RecordThebes(); + bool HasMaskLayers(); + bool EnsureContentClient(); + uint32_t GetPaintFlags(ReadbackProcessor* aReadback); + void UpdateContentClient(PaintState& aState); + bool UpdatePaintRegion(PaintState& aState); + void FinishPaintState(PaintState& aState); + + void PrintInfo(std::stringstream& aStream, const char* aPrefix) override; + + void DestroyBackBuffer() { mContentClient = nullptr; } + + RefPtr mContentClient; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/client/ClientReadbackLayer.h b/gfx/layers/client/ClientReadbackLayer.h new file mode 100644 index 0000000000..1e6535bb16 --- /dev/null +++ b/gfx/layers/client/ClientReadbackLayer.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_CLIENTREADBACKLAYER_H +#define GFX_CLIENTREADBACKLAYER_H + +#include "ClientLayerManager.h" +#include "ReadbackLayer.h" + +namespace mozilla { +namespace layers { + +class ClientReadbackLayer : public ReadbackLayer, public ClientLayer { + public: + explicit ClientReadbackLayer(ClientLayerManager* aManager) + : ReadbackLayer(aManager, nullptr) { + mImplData = static_cast(this); + } + + ShadowableLayer* AsShadowableLayer() override { return this; } + Layer* AsLayer() override { return this; } + void RenderLayer() override {} +}; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_CLIENTREADBACKLAYER_H */ diff --git a/gfx/layers/client/ClientTiledPaintedLayer.cpp b/gfx/layers/client/ClientTiledPaintedLayer.cpp new file mode 100644 index 0000000000..1e93c51f72 --- /dev/null +++ b/gfx/layers/client/ClientTiledPaintedLayer.cpp @@ -0,0 +1,653 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ClientTiledPaintedLayer.h" +#include "FrameMetrics.h" // for FrameMetrics +#include "Units.h" // for ScreenIntRect, CSSPoint, etc +#include "UnitTransforms.h" // for TransformTo +#include "ClientLayerManager.h" // for ClientLayerManager, etc +#include "gfxPlatform.h" // for gfxPlatform +#include "gfxRect.h" // for gfxRect +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/gfx/BaseSize.h" // for BaseSize +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/gfx/Rect.h" // for Rect, RectTyped +#include "mozilla/layers/CompositorBridgeChild.h" +#include "mozilla/layers/LayerMetricsWrapper.h" // for LayerMetricsWrapper +#include "mozilla/layers/LayersMessages.h" +#include "mozilla/layers/PaintThread.h" +#include "mozilla/mozalloc.h" // for operator delete, etc +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "mozilla/layers/MultiTiledContentClient.h" +#include "mozilla/layers/SingleTiledContentClient.h" + +namespace mozilla { +namespace layers { + +using gfx::IntRect; +using gfx::IntSize; +using gfx::Rect; + +ClientTiledPaintedLayer::ClientTiledPaintedLayer( + ClientLayerManager* const aManager, + ClientLayerManager::PaintedLayerCreationHint aCreationHint) + : PaintedLayer(aManager, static_cast(this), aCreationHint), + mContentClient(), + mHaveSingleTiledContentClient(false) { + MOZ_COUNT_CTOR(ClientTiledPaintedLayer); + mPaintData.mLastScrollOffset = ParentLayerPoint(0, 0); + mPaintData.mFirstPaint = true; +} + +ClientTiledPaintedLayer::~ClientTiledPaintedLayer() { + MOZ_COUNT_DTOR(ClientTiledPaintedLayer); +} + +void ClientTiledPaintedLayer::ClearCachedResources() { + if (mContentClient) { + mContentClient->ClearCachedResources(); + } + ClearValidRegion(); + mContentClient = nullptr; +} + +void ClientTiledPaintedLayer::FillSpecificAttributes( + SpecificLayerAttributes& aAttrs) { + aAttrs = PaintedLayerAttributes(GetValidRegion()); +} + +static Maybe ApplyParentLayerToLayerTransform( + const ParentLayerToLayerMatrix4x4& aTransform, + const ParentLayerRect& aParentLayerRect, const LayerRect& aClip) { + return UntransformBy(aTransform, aParentLayerRect, aClip); +} + +static LayerToParentLayerMatrix4x4 GetTransformToAncestorsParentLayer( + Layer* aStart, const LayerMetricsWrapper& aAncestor) { + // If the ancestor layer Combines3DTransformWithAncestors, then the + // scroll offset is contained in the transform of the layer at the + // root of the 3D context. So we must first find that layer, then + // calcuate the transform to its parent. + LayerMetricsWrapper root3dAncestor = aAncestor; + while (root3dAncestor.Combines3DTransformWithAncestors()) { + root3dAncestor = root3dAncestor.GetParent(); + } + + gfx::Matrix4x4 transform; + const LayerMetricsWrapper& ancestorParent = root3dAncestor.GetParent(); + for (LayerMetricsWrapper iter(aStart, LayerMetricsWrapper::StartAt::BOTTOM); + ancestorParent ? iter != ancestorParent : iter.IsValid(); + iter = iter.GetParent()) { + transform = transform * iter.GetTransform(); + } + return ViewAs(transform); +} + +void ClientTiledPaintedLayer::GetAncestorLayers( + LayerMetricsWrapper* aOutScrollAncestor, + LayerMetricsWrapper* aOutDisplayPortAncestor, + bool* aOutHasTransformAnimation) { + LayerMetricsWrapper scrollAncestor; + LayerMetricsWrapper displayPortAncestor; + bool hasTransformAnimation = false; + for (LayerMetricsWrapper ancestor(this, LayerMetricsWrapper::StartAt::BOTTOM); + ancestor; ancestor = ancestor.GetParent()) { + hasTransformAnimation |= ancestor.HasTransformAnimation(); + const FrameMetrics& metrics = ancestor.Metrics(); + if (!scrollAncestor && + metrics.GetScrollId() != ScrollableLayerGuid::NULL_SCROLL_ID) { + scrollAncestor = ancestor; + } + if (!metrics.GetDisplayPort().IsEmpty()) { + displayPortAncestor = ancestor; + // Any layer that has a displayport must be scrollable, so we can break + // here. + break; + } + } + if (aOutScrollAncestor) { + *aOutScrollAncestor = scrollAncestor; + } + if (aOutDisplayPortAncestor) { + *aOutDisplayPortAncestor = displayPortAncestor; + } + if (aOutHasTransformAnimation) { + *aOutHasTransformAnimation = hasTransformAnimation; + } +} + +void ClientTiledPaintedLayer::BeginPaint() { + mPaintData.ResetPaintData(); + + if (!GetBaseTransform().Is2D()) { + // Give up if there is a complex CSS transform on the layer. We might + // eventually support these but for now it's too complicated to handle + // given that it's a pretty rare scenario. + return; + } + + // Get the metrics of the nearest scrollable layer and the nearest layer + // with a displayport. + LayerMetricsWrapper scrollAncestor; + LayerMetricsWrapper displayPortAncestor; + bool hasTransformAnimation; + GetAncestorLayers(&scrollAncestor, &displayPortAncestor, + &hasTransformAnimation); + + if (!displayPortAncestor || !scrollAncestor) { + // No displayport or scroll ancestor, so we can't do progressive rendering. +#if defined(MOZ_WIDGET_ANDROID) + // Android are guaranteed to have a displayport set, so this + // should never happen. + NS_WARNING("Tiled PaintedLayer with no scrollable container ancestor"); +#endif + return; + } + + TILING_LOG( + "TILING %p: Found scrollAncestor %p, displayPortAncestor %p, transform " + "%d\n", + this, scrollAncestor.GetLayer(), displayPortAncestor.GetLayer(), + hasTransformAnimation); + + const FrameMetrics& scrollMetrics = scrollAncestor.Metrics(); + const FrameMetrics& displayportMetrics = displayPortAncestor.Metrics(); + + // Calculate the transform required to convert ParentLayer space of our + // display port ancestor to the Layer space of this layer. + ParentLayerToLayerMatrix4x4 transformDisplayPortToLayer = + GetTransformToAncestorsParentLayer(this, displayPortAncestor).Inverse(); + + LayerRect layerBounds(GetVisibleRegion().GetBounds()); + + // Compute the critical display port that applies to this layer in the + // LayoutDevice space of this layer, but only if there is no OMT animation + // on this layer. If there is an OMT animation then we need to draw the whole + // visible region of this layer as determined by layout, because we don't know + // what parts of it might move into view in the compositor. + mPaintData.mHasTransformAnimation = hasTransformAnimation; + if (!mPaintData.mHasTransformAnimation && + mContentClient->GetLowPrecisionTiledBuffer()) { + ParentLayerRect criticalDisplayPort = + (displayportMetrics.GetCriticalDisplayPort() * + displayportMetrics.GetZoom()) + + displayportMetrics.GetCompositionBounds().TopLeft(); + Maybe criticalDisplayPortTransformed = + ApplyParentLayerToLayerTransform(transformDisplayPortToLayer, + criticalDisplayPort, layerBounds); + if (criticalDisplayPortTransformed) { + mPaintData.mCriticalDisplayPort = + Some(RoundedToInt(*criticalDisplayPortTransformed)); + } else { + mPaintData.mCriticalDisplayPort = Some(LayerIntRect(0, 0, 0, 0)); + } + } + TILING_LOG("TILING %p: Critical displayport %s\n", this, + mPaintData.mCriticalDisplayPort + ? Stringify(*mPaintData.mCriticalDisplayPort).c_str() + : "not set"); + + // Store the resolution from the displayport ancestor layer. Because this is + // Gecko-side, before any async transforms have occurred, we can use the zoom + // for this. + mPaintData.mResolution = displayportMetrics.GetZoom(); + TILING_LOG("TILING %p: Resolution %s\n", this, + Stringify(mPaintData.mResolution).c_str()); + + // Store the applicable composition bounds in this layer's Layer units. + mPaintData.mTransformToCompBounds = + GetTransformToAncestorsParentLayer(this, scrollAncestor); + ParentLayerToLayerMatrix4x4 transformToBounds = + mPaintData.mTransformToCompBounds.Inverse(); + Maybe compositionBoundsTransformed = + ApplyParentLayerToLayerTransform( + transformToBounds, scrollMetrics.GetCompositionBounds(), layerBounds); + if (compositionBoundsTransformed) { + mPaintData.mCompositionBounds = *compositionBoundsTransformed; + } else { + mPaintData.mCompositionBounds.SetEmpty(); + } + TILING_LOG("TILING %p: Composition bounds %s\n", this, + Stringify(mPaintData.mCompositionBounds).c_str()); + + // Calculate the scroll offset since the last transaction + mPaintData.mScrollOffset = + displayportMetrics.GetLayoutScrollOffset() * displayportMetrics.GetZoom(); + TILING_LOG("TILING %p: Scroll offset %s\n", this, + Stringify(mPaintData.mScrollOffset).c_str()); +} + +bool ClientTiledPaintedLayer::IsScrollingOnCompositor( + const FrameMetrics& aParentMetrics) { + CompositorBridgeChild* compositor = nullptr; + if (Manager() && Manager()->AsClientLayerManager()) { + compositor = Manager()->AsClientLayerManager()->GetCompositorBridgeChild(); + } + + if (!compositor) { + return false; + } + + FrameMetrics compositorMetrics; + if (!compositor->LookupCompositorFrameMetrics(aParentMetrics.GetScrollId(), + compositorMetrics)) { + return false; + } + + // 1 is a tad high for a fuzzy equals epsilon however if our scroll delta + // is so small then we have nothing to gain from using paint heuristics. + float COORDINATE_EPSILON = 1.f; + + return !FuzzyEqualsAdditive(compositorMetrics.GetVisualScrollOffset().x, + aParentMetrics.GetVisualScrollOffset().x, + COORDINATE_EPSILON) || + !FuzzyEqualsAdditive(compositorMetrics.GetVisualScrollOffset().y, + aParentMetrics.GetVisualScrollOffset().y, + COORDINATE_EPSILON); +} + +bool ClientTiledPaintedLayer::UseProgressiveDraw() { + if (!StaticPrefs::layers_progressive_paint()) { + // pref is disabled, so never do progressive + return false; + } + + if (!mContentClient->GetTiledBuffer()->SupportsProgressiveUpdate()) { + return false; + } + + if (ClientManager()->HasShadowTarget()) { + // This condition is true when we are in a reftest scenario. We don't want + // to draw progressively here because it can cause intermittent reftest + // failures because the harness won't wait for all the tiles to be drawn. + return false; + } + + if (GetIsFixedPosition() || GetParent()->GetIsFixedPosition()) { + // This layer is fixed-position and so even if it does have a scrolling + // ancestor it will likely be entirely on-screen all the time, so we + // should draw it all at once + return false; + } + + if (mPaintData.mHasTransformAnimation) { + // The compositor is going to animate this somehow, so we want it all + // on the screen at once. + return false; + } + + if (ClientManager()->AsyncPanZoomEnabled()) { + LayerMetricsWrapper scrollAncestor; + GetAncestorLayers(&scrollAncestor, nullptr, nullptr); + MOZ_ASSERT( + scrollAncestor); // because mPaintData.mCriticalDisplayPort is set + if (!scrollAncestor) { + return false; + } + const FrameMetrics& parentMetrics = scrollAncestor.Metrics(); + if (!IsScrollingOnCompositor(parentMetrics)) { + return false; + } + } + + return true; +} + +bool ClientTiledPaintedLayer::RenderHighPrecision( + const nsIntRegion& aInvalidRegion, const nsIntRegion& aVisibleRegion, + LayerManager::DrawPaintedLayerCallback aCallback, void* aCallbackData) { + // If we have started drawing low-precision already, then we + // shouldn't do anything there. + if (mPaintData.mLowPrecisionPaintCount != 0) { + return false; + } + + // Only draw progressively when there is something to paint and the + // resolution is unchanged + if (!aInvalidRegion.IsEmpty() && UseProgressiveDraw() && + mContentClient->GetTiledBuffer()->GetFrameResolution() == + mPaintData.mResolution) { + // Store the old valid region, then clear it before painting. + // We clip the old valid region to the visible region, as it only gets + // used to decide stale content (currently valid and previously visible) + nsIntRegion oldValidRegion = + mContentClient->GetTiledBuffer()->GetValidRegion(); + oldValidRegion.And(oldValidRegion, aVisibleRegion); + if (mPaintData.mCriticalDisplayPort) { + oldValidRegion.And(oldValidRegion, + mPaintData.mCriticalDisplayPort->ToUnknownRect()); + } + + TILING_LOG("TILING %p: Progressive update with old valid region %s\n", this, + Stringify(oldValidRegion).c_str()); + + nsIntRegion drawnRegion; + bool updatedBuffer = mContentClient->GetTiledBuffer()->ProgressiveUpdate( + GetValidRegion(), aInvalidRegion, oldValidRegion, drawnRegion, + &mPaintData, aCallback, aCallbackData); + AddToValidRegion(drawnRegion); + return updatedBuffer; + } + + // Otherwise do a non-progressive paint. We must do this even when + // the region to paint is empty as the valid region may have shrunk. + + nsIntRegion validRegion = aVisibleRegion; + if (mPaintData.mCriticalDisplayPort) { + validRegion.AndWith(mPaintData.mCriticalDisplayPort->ToUnknownRect()); + } + SetValidRegion(validRegion); + + TILING_LOG("TILING %p: Non-progressive paint invalid region %s\n", this, + Stringify(aInvalidRegion).c_str()); + TILING_LOG("TILING %p: Non-progressive paint new valid region %s\n", this, + Stringify(GetValidRegion()).c_str()); + + TilePaintFlags flags = + PaintThread::Get() ? TilePaintFlags::Async : TilePaintFlags::None; + + mContentClient->GetTiledBuffer()->SetFrameResolution(mPaintData.mResolution); + mContentClient->GetTiledBuffer()->PaintThebes( + GetValidRegion(), aInvalidRegion, aInvalidRegion, aCallback, + aCallbackData, flags); + mPaintData.mPaintFinished = true; + return true; +} + +bool ClientTiledPaintedLayer::RenderLowPrecision( + const nsIntRegion& aInvalidRegion, const nsIntRegion& aVisibleRegion, + LayerManager::DrawPaintedLayerCallback aCallback, void* aCallbackData) { + nsIntRegion invalidRegion = aInvalidRegion; + + // Render the low precision buffer, if the visible region is larger than the + // critical display port. + if (!mPaintData.mCriticalDisplayPort || + !nsIntRegion(mPaintData.mCriticalDisplayPort->ToUnknownRect()) + .Contains(aVisibleRegion)) { + nsIntRegion oldValidRegion = + mContentClient->GetLowPrecisionTiledBuffer()->GetValidRegion(); + oldValidRegion.And(oldValidRegion, aVisibleRegion); + + bool updatedBuffer = false; + + // If the frame resolution or format have changed, invalidate the buffer + if (mContentClient->GetLowPrecisionTiledBuffer()->GetFrameResolution() != + mPaintData.mResolution || + mContentClient->GetLowPrecisionTiledBuffer()->HasFormatChanged()) { + if (!mLowPrecisionValidRegion.IsEmpty()) { + updatedBuffer = true; + } + oldValidRegion.SetEmpty(); + mLowPrecisionValidRegion.SetEmpty(); + mContentClient->GetLowPrecisionTiledBuffer()->ResetPaintedAndValidState(); + mContentClient->GetLowPrecisionTiledBuffer()->SetFrameResolution( + mPaintData.mResolution); + invalidRegion = aVisibleRegion; + } + + // Invalidate previously valid content that is no longer visible + if (mPaintData.mLowPrecisionPaintCount == 1) { + mLowPrecisionValidRegion.And(mLowPrecisionValidRegion, aVisibleRegion); + } + mPaintData.mLowPrecisionPaintCount++; + + // Remove the valid high-precision region from the invalid low-precision + // region. We don't want to spend time drawing things twice. + invalidRegion.SubOut(GetValidRegion()); + + TILING_LOG( + "TILING %p: Progressive paint: low-precision invalid region is %s\n", + this, Stringify(invalidRegion).c_str()); + TILING_LOG( + "TILING %p: Progressive paint: low-precision old valid region is %s\n", + this, Stringify(oldValidRegion).c_str()); + + if (!invalidRegion.IsEmpty()) { + nsIntRegion drawnRegion; + updatedBuffer = + mContentClient->GetLowPrecisionTiledBuffer()->ProgressiveUpdate( + mLowPrecisionValidRegion, invalidRegion, oldValidRegion, + drawnRegion, &mPaintData, aCallback, aCallbackData); + mLowPrecisionValidRegion.OrWith(drawnRegion); + } + + TILING_LOG( + "TILING %p: Progressive paint: low-precision new valid region is %s\n", + this, Stringify(mLowPrecisionValidRegion).c_str()); + return updatedBuffer; + } + if (!mLowPrecisionValidRegion.IsEmpty()) { + TILING_LOG("TILING %p: Clearing low-precision buffer\n", this); + // Clear the low precision tiled buffer. + mLowPrecisionValidRegion.SetEmpty(); + mContentClient->GetLowPrecisionTiledBuffer()->ResetPaintedAndValidState(); + // Return true here so we send a Painted callback after clearing the valid + // region of the low precision buffer. This allows the shadow buffer's valid + // region to be updated and the associated resources to be freed. + return true; + } + return false; +} + +void ClientTiledPaintedLayer::EndPaint() { + mPaintData.mLastScrollOffset = mPaintData.mScrollOffset; + mPaintData.mPaintFinished = true; + mPaintData.mFirstPaint = false; + TILING_LOG("TILING %p: Paint finished\n", this); +} + +void ClientTiledPaintedLayer::RenderLayer() { + if (!ClientManager()->IsRepeatTransaction()) { + // Only paint the mask layers on the first transaction. + RenderMaskLayers(this); + } + + LayerManager::DrawPaintedLayerCallback callback = + ClientManager()->GetPaintedLayerCallback(); + void* data = ClientManager()->GetPaintedLayerCallbackData(); + + IntSize layerSize = mVisibleRegion.GetBounds().ToUnknownRect().Size(); + IntSize tileSize = gfx::gfxVars::TileSize(); + bool isHalfTileWidthOrHeight = layerSize.width <= tileSize.width / 2 || + layerSize.height <= tileSize.height / 2; + + // Use single tile when layer is not scrollable, is smaller than one + // tile, or when more than half of the tiles' pixels in either + // dimension would be wasted. + bool wantSingleTiledContentClient = + (mCreationHint == LayerManager::NONE || layerSize <= tileSize || + isHalfTileWidthOrHeight) && + SingleTiledContentClient::ClientSupportsLayerSize(layerSize, + ClientManager()) && + StaticPrefs::layers_single_tile_enabled(); + + if (mContentClient && mHaveSingleTiledContentClient && + !wantSingleTiledContentClient) { + mContentClient = nullptr; + ClearValidRegion(); + } + + if (!mContentClient) { + if (wantSingleTiledContentClient) { + mContentClient = new SingleTiledContentClient(*this, ClientManager()); + mHaveSingleTiledContentClient = true; + } else { + mContentClient = new MultiTiledContentClient(*this, ClientManager()); + mHaveSingleTiledContentClient = false; + } + + mContentClient->Connect(); + ClientManager()->AsShadowForwarder()->Attach(mContentClient, this); + MOZ_ASSERT(mContentClient->GetForwarder()); + } + + if (mContentClient->GetTiledBuffer()->HasFormatChanged()) { + ClearValidRegion(); + mContentClient->GetTiledBuffer()->ResetPaintedAndValidState(); + } + + TILING_LOG("TILING %p: Initial visible region %s\n", this, + Stringify(mVisibleRegion).c_str()); + TILING_LOG("TILING %p: Initial valid region %s\n", this, + Stringify(GetValidRegion()).c_str()); + TILING_LOG("TILING %p: Initial low-precision valid region %s\n", this, + Stringify(mLowPrecisionValidRegion).c_str()); + + nsIntRegion neededRegion = mVisibleRegion.ToUnknownRegion(); +#ifndef MOZ_IGNORE_PAINT_WILL_RESAMPLE + // This is handled by PadDrawTargetOutFromRegion in TiledContentClient for + // mobile + if (MayResample()) { + // If we're resampling then bilinear filtering can read up to 1 pixel + // outside of our texture coords. Make the visible region a single rect, + // and pad it out by 1 pixel (restricted to tile boundaries) so that + // we always have valid content or transparent pixels to sample from. + IntRect bounds = neededRegion.GetBounds(); + IntRect wholeTiles = bounds; + wholeTiles.InflateToMultiple(gfx::gfxVars::TileSize()); + IntRect padded = bounds; + padded.Inflate(1); + padded.IntersectRect(padded, wholeTiles); + neededRegion = padded; + } +#endif + + nsIntRegion invalidRegion; + invalidRegion.Sub(neededRegion, GetValidRegion()); + if (invalidRegion.IsEmpty()) { + EndPaint(); + return; + } + + if (!callback) { + ClientManager()->SetTransactionIncomplete(); + return; + } + + if (!ClientManager()->IsRepeatTransaction()) { + // For more complex cases we need to calculate a bunch of metrics before we + // can do the paint. + BeginPaint(); + if (mPaintData.mPaintFinished) { + return; + } + + // Make sure that tiles that fall outside of the visible region or outside + // of the critical displayport are discarded on the first update. Also make + // sure that we only draw stuff inside the critical displayport on the first + // update. + nsIntRegion validRegion; + validRegion.And(GetValidRegion(), neededRegion); + if (mPaintData.mCriticalDisplayPort) { + validRegion.AndWith(mPaintData.mCriticalDisplayPort->ToUnknownRect()); + invalidRegion.And(invalidRegion, + mPaintData.mCriticalDisplayPort->ToUnknownRect()); + } + SetValidRegion(validRegion); + + TILING_LOG("TILING %p: First-transaction valid region %s\n", this, + Stringify(validRegion).c_str()); + TILING_LOG("TILING %p: First-transaction invalid region %s\n", this, + Stringify(invalidRegion).c_str()); + } else { + if (mPaintData.mCriticalDisplayPort) { + invalidRegion.And(invalidRegion, + mPaintData.mCriticalDisplayPort->ToUnknownRect()); + } + TILING_LOG("TILING %p: Repeat-transaction invalid region %s\n", this, + Stringify(invalidRegion).c_str()); + } + + nsIntRegion lowPrecisionInvalidRegion; + if (mContentClient->GetLowPrecisionTiledBuffer()) { + // Calculate the invalid region for the low precision buffer. Make sure + // to remove the valid high-precision area so we don't double-paint it. + lowPrecisionInvalidRegion.Sub(neededRegion, mLowPrecisionValidRegion); + lowPrecisionInvalidRegion.Sub(lowPrecisionInvalidRegion, GetValidRegion()); + } + TILING_LOG("TILING %p: Low-precision invalid region %s\n", this, + Stringify(lowPrecisionInvalidRegion).c_str()); + + bool updatedHighPrecision = + RenderHighPrecision(invalidRegion, neededRegion, callback, data); + if (updatedHighPrecision) { + ClientManager()->Hold(this); + mContentClient->UpdatedBuffer(TiledContentClient::TILED_BUFFER); + + if (!mPaintData.mPaintFinished) { + // There is still more high-res stuff to paint, so we're not + // done yet. A subsequent transaction will take care of this. + ClientManager()->SetRepeatTransaction(); + return; + } + } + + // If there is nothing to draw in low-precision, then we're done. + if (lowPrecisionInvalidRegion.IsEmpty()) { + EndPaint(); + return; + } + + if (updatedHighPrecision) { + // If there are low precision updates, but we just did some high-precision + // updates, then mark the paint as unfinished and request a repeat + // transaction. This is so that we don't perform low-precision updates in + // the same transaction as high-precision updates. + TILING_LOG( + "TILING %p: Scheduling repeat transaction for low-precision painting\n", + this); + ClientManager()->SetRepeatTransaction(); + mPaintData.mLowPrecisionPaintCount = 1; + mPaintData.mPaintFinished = false; + return; + } + + bool updatedLowPrecision = RenderLowPrecision(lowPrecisionInvalidRegion, + neededRegion, callback, data); + if (updatedLowPrecision) { + ClientManager()->Hold(this); + mContentClient->UpdatedBuffer( + TiledContentClient::LOW_PRECISION_TILED_BUFFER); + + if (!mPaintData.mPaintFinished) { + // There is still more low-res stuff to paint, so we're not + // done yet. A subsequent transaction will take care of this. + ClientManager()->SetRepeatTransaction(); + return; + } + } + + // If we get here, we've done all the high- and low-precision + // paints we wanted to do, so we can finish the paint and chill. + EndPaint(); +} + +bool ClientTiledPaintedLayer::IsOptimizedFor( + LayerManager::PaintedLayerCreationHint aHint) { + // The only creation hint is whether the layer is scrollable or not, and this + // is only respected on OSX, where it's used to determine whether to + // use a tiled content client or not. + // There are pretty nasty performance consequences for not using tiles on + // large, scrollable layers, so we want the layer to be recreated in this + // situation. + return aHint == GetCreationHint(); +} + +void ClientTiledPaintedLayer::PrintInfo(std::stringstream& aStream, + const char* aPrefix) { + PaintedLayer::PrintInfo(aStream, aPrefix); + if (mContentClient) { + aStream << "\n"; + nsAutoCString pfx(aPrefix); + pfx += " "; + mContentClient->PrintInfo(aStream, pfx.get()); + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/ClientTiledPaintedLayer.h b/gfx/layers/client/ClientTiledPaintedLayer.h new file mode 100644 index 0000000000..6043da0b94 --- /dev/null +++ b/gfx/layers/client/ClientTiledPaintedLayer.h @@ -0,0 +1,150 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_CLIENTTILEDPAINTEDLAYER_H +#define GFX_CLIENTTILEDPAINTEDLAYER_H + +#include "ClientLayerManager.h" // for ClientLayer, etc +#include "Layers.h" // for PaintedLayer, etc +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/layers/TiledContentClient.h" +#include "nsRegion.h" // for nsIntRegion + +namespace mozilla { +namespace layers { + +class ShadowableLayer; +class SpecificLayerAttributes; + +/** + * An implementation of PaintedLayer that ONLY supports remote + * composition that is backed by tiles. This painted layer implementation + * is better suited to mobile hardware to work around slow implementation + * of glTexImage2D (for OGL compositors), and restrait memory bandwidth. + * + * Tiled PaintedLayers use a different protocol compared with other + * layers. A copy of the tiled buffer is made and sent to the compositing + * thread via the layers protocol. Tiles are uploaded by the buffers + * asynchonously without using IPC, that means they are not safe for cross- + * process use (bug 747811). Each tile has a TextureHost/Client pair but + * they communicate directly rather than using the Texture protocol. + * + * There is no ContentClient for tiled layers. There is a ContentHost, however. + */ +class ClientTiledPaintedLayer : public PaintedLayer, public ClientLayer { + typedef PaintedLayer Base; + + public: + explicit ClientTiledPaintedLayer(ClientLayerManager* const aManager, + ClientLayerManager::PaintedLayerCreationHint + aCreationHint = LayerManager::NONE); + + protected: + virtual ~ClientTiledPaintedLayer(); + + void PrintInfo(std::stringstream& aStream, const char* aPrefix) override; + + public: + // Override name to distinguish it from ClientPaintedLayer in layer dumps + const char* Name() const override { return "TiledPaintedLayer"; } + + // PaintedLayer + Layer* AsLayer() override { return this; } + void InvalidateRegion(const nsIntRegion& aRegion) override { + mInvalidRegion.Add(aRegion); + UpdateValidRegionAfterInvalidRegionChanged(); + if (!mLowPrecisionValidRegion.IsEmpty()) { + // Also update mLowPrecisionValidRegion. Unfortunately we call + // mInvalidRegion.GetRegion() here, which is expensive. + mLowPrecisionValidRegion.SubOut(mInvalidRegion.GetRegion()); + } + } + + // Shadow methods + void FillSpecificAttributes(SpecificLayerAttributes& aAttrs) override; + ShadowableLayer* AsShadowableLayer() override { return this; } + + void RenderLayer() override; + + void ClearCachedResources() override; + + void HandleMemoryPressure() override { + if (mContentClient) { + mContentClient->HandleMemoryPressure(); + } + } + + /** + * Helper method to find the nearest ancestor layers which + * scroll and have a displayport. The parameters are out-params + * which hold the return values; the values passed in may be null. + */ + void GetAncestorLayers(LayerMetricsWrapper* aOutScrollAncestor, + LayerMetricsWrapper* aOutDisplayPortAncestor, + bool* aOutHasTransformAnimation); + + bool IsOptimizedFor( + LayerManager::PaintedLayerCreationHint aCreationHint) override; + + private: + ClientLayerManager* ClientManager() { + return static_cast(mManager); + } + + /** + * For the initial PaintThebes of a transaction, calculates all the data + * needed for that paint and any repeated transactions. + */ + void BeginPaint(); + + /** + * Check if the layer is being scrolled by APZ on the compositor. + */ + bool IsScrollingOnCompositor(const FrameMetrics& aParentMetrics); + + /** + * Check if we should use progressive draw on this layer. We will + * disable progressive draw based on a preference or if the layer + * is not being scrolled. + */ + bool UseProgressiveDraw(); + + /** + * Helper function to do the high-precision paint. + * This function returns true if it updated the paint buffer. + */ + bool RenderHighPrecision(const nsIntRegion& aInvalidRegion, + const nsIntRegion& aVisibleRegion, + LayerManager::DrawPaintedLayerCallback aCallback, + void* aCallbackData); + + /** + * Helper function to do the low-precision paint. + * This function returns true if it updated the paint buffer. + */ + bool RenderLowPrecision(const nsIntRegion& aInvalidRegion, + const nsIntRegion& aVisibleRegion, + LayerManager::DrawPaintedLayerCallback aCallback, + void* aCallbackData); + + /** + * This causes the paint to be marked as finished, and updates any data + * necessary to persist until the next paint. + */ + void EndPaint(); + + RefPtr mContentClient; + // Flag to indicate if mContentClient is a SingleTiledContentClient. This is + // only valid when mContentClient is non-null. + bool mHaveSingleTiledContentClient; + nsIntRegion mLowPrecisionValidRegion; + BasicTiledLayerPaintData mPaintData; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/client/CompositableClient.cpp b/gfx/layers/client/CompositableClient.cpp new file mode 100644 index 0000000000..9178f8f5c1 --- /dev/null +++ b/gfx/layers/client/CompositableClient.cpp @@ -0,0 +1,182 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/CompositableClient.h" + +#include // for uint64_t, uint32_t + +#include "gfxPlatform.h" // for gfxPlatform +#include "mozilla/layers/CompositableForwarder.h" +#include "mozilla/layers/ImageBridgeChild.h" +#include "mozilla/layers/TextureClient.h" // for TextureClient, etc +#include "mozilla/layers/TextureClientOGL.h" +#include "mozilla/layers/TextureClientRecycleAllocator.h" +#include "mozilla/mozalloc.h" // for operator delete, etc +#ifdef XP_WIN +# include "gfxWindowsPlatform.h" // for gfxWindowsPlatform +# include "mozilla/layers/TextureD3D11.h" +#endif +#include "IPDLActor.h" +#include "gfxUtils.h" + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +void CompositableClient::InitIPDL(const CompositableHandle& aHandle) { + MOZ_ASSERT(aHandle); + + mForwarder->AssertInForwarderThread(); + + mHandle = aHandle; + mIsAsync |= !NS_IsMainThread(); + // By simply taking the lock on mTextureClientRecycler we force memory barrier + // on mHandle and mIsAsync which makes them behave as Atomic as they are only + // ever set in this method. + auto l = mTextureClientRecycler.Lock(); +} + +CompositableClient::CompositableClient(CompositableForwarder* aForwarder, + TextureFlags aTextureFlags) + : mForwarder(aForwarder), + mTextureFlags(aTextureFlags), + mTextureClientRecycler("CompositableClient::mTextureClientRecycler"), + mIsAsync(false) {} + +CompositableClient::~CompositableClient() { Destroy(); } + +LayersBackend CompositableClient::GetCompositorBackendType() const { + return mForwarder->GetCompositorBackendType(); +} + +bool CompositableClient::Connect(ImageContainer* aImageContainer) { + MOZ_ASSERT(!mHandle); + if (!GetForwarder() || mHandle) { + return false; + } + + GetForwarder()->AssertInForwarderThread(); + GetForwarder()->Connect(this, aImageContainer); + return true; +} + +bool CompositableClient::IsConnected() const { + // CanSend() is only reliable in the same thread as the IPDL channel. + mForwarder->AssertInForwarderThread(); + return !!mHandle; +} + +void CompositableClient::Destroy() { + auto l = mTextureClientRecycler.Lock(); + if (*l) { + (*l)->Destroy(); + } + + if (mHandle) { + mForwarder->ReleaseCompositable(mHandle); + mHandle = CompositableHandle(); + } +} + +CompositableHandle CompositableClient::GetAsyncHandle() const { + if (mIsAsync) { + return mHandle; + } + return CompositableHandle(); +} + +already_AddRefed CompositableClient::CreateBufferTextureClient( + gfx::SurfaceFormat aFormat, gfx::IntSize aSize, + gfx::BackendType aMoz2DBackend, TextureFlags aTextureFlags) { + return TextureClient::CreateForRawBufferAccess(GetForwarder(), aFormat, aSize, + aMoz2DBackend, + aTextureFlags | mTextureFlags); +} + +already_AddRefed +CompositableClient::CreateTextureClientForDrawing( + gfx::SurfaceFormat aFormat, gfx::IntSize aSize, BackendSelector aSelector, + TextureFlags aTextureFlags, TextureAllocationFlags aAllocFlags) { + return TextureClient::CreateForDrawing( + GetForwarder(), aFormat, aSize, aSelector, aTextureFlags | mTextureFlags, + aAllocFlags); +} + +already_AddRefed +CompositableClient::CreateTextureClientFromSurface( + gfx::SourceSurface* aSurface, BackendSelector aSelector, + TextureFlags aTextureFlags, TextureAllocationFlags aAllocFlags) { + return TextureClient::CreateFromSurface(GetForwarder(), aSurface, aSelector, + aTextureFlags | mTextureFlags, + aAllocFlags); +} + +bool CompositableClient::AddTextureClient(TextureClient* aClient) { + if (!aClient) { + return false; + } + aClient->SetAddedToCompositableClient(); + return aClient->InitIPDLActor(mForwarder); +} + +void CompositableClient::ClearCachedResources() { + auto l = mTextureClientRecycler.Lock(); + if (*l) { + (*l)->ShrinkToMinimumSize(); + } +} + +void CompositableClient::HandleMemoryPressure() { + auto l = mTextureClientRecycler.Lock(); + if (*l) { + (*l)->ShrinkToMinimumSize(); + } +} + +void CompositableClient::RemoveTexture(TextureClient* aTexture) { + mForwarder->RemoveTextureFromCompositable(this, aTexture); +} + +TextureClientRecycleAllocator* CompositableClient::GetTextureClientRecycler() { + auto l = mTextureClientRecycler.Lock(); + if (*l) { + return *l; + } + + if (!mForwarder || !mForwarder->GetTextureForwarder()) { + return nullptr; + } + + *l = new layers::TextureClientRecycleAllocator(mForwarder); + return *l; +} + +void CompositableClient::DumpTextureClient(std::stringstream& aStream, + TextureClient* aTexture, + TextureDumpMode aCompress) { + if (!aTexture) { + return; + } + RefPtr dSurf = aTexture->GetAsSurface(); + if (!dSurf) { + return; + } + if (aCompress == TextureDumpMode::Compress) { + aStream << gfxUtils::GetAsLZ4Base64Str(dSurf).get(); + } else { + aStream << gfxUtils::GetAsDataURI(dSurf).get(); + } +} + +AutoRemoveTexture::~AutoRemoveTexture() { + if (mCompositable && mTexture && mCompositable->IsConnected()) { + mCompositable->RemoveTexture(mTexture); + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/CompositableClient.h b/gfx/layers/client/CompositableClient.h new file mode 100644 index 0000000000..90b4235f8e --- /dev/null +++ b/gfx/layers/client/CompositableClient.h @@ -0,0 +1,213 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_BUFFERCLIENT_H +#define MOZILLA_GFX_BUFFERCLIENT_H + +#include // for uint64_t + +#include // for map +#include // for vector + +#include "mozilla/Assertions.h" // for MOZ_CRASH +#include "mozilla/DataMutex.h" +#include "mozilla/RefPtr.h" // for already_AddRefed, RefCounted +#include "mozilla/gfx/Types.h" // for SurfaceFormat +#include "mozilla/layers/CompositorTypes.h" +#include "mozilla/layers/LayersTypes.h" // for LayersBackend, TextureDumpMode +#include "mozilla/layers/TextureClient.h" // for TextureClient +#include "mozilla/webrender/WebRenderTypes.h" // for RenderRoot +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc + +namespace mozilla { +namespace layers { + +class CompositableClient; +class ImageBridgeChild; +class ImageContainer; +class CompositableForwarder; +class CompositableChild; +class TextureClientRecycleAllocator; +class ContentClientRemoteBuffer; + +/** + * CompositableClient manages the texture-specific logic for composite layers, + * independently of the layer. It is the content side of a CompositableClient/ + * CompositableHost pair. + * + * CompositableClient's purpose is to send texture data to the compositor side + * along with any extra information about how the texture is to be composited. + * Things like opacity or transformation belong to layer and not compositable. + * + * Since Compositables are independent of layers it is possible to create one, + * connect it to the compositor side, and start sending images to it. This alone + * is arguably not very useful, but it means that as long as a shadow layer can + * do the proper magic to find a reference to the right CompositableHost on the + * Compositor side, a Compositable client can be used outside of the main + * shadow layer forwarder machinery that is used on the main thread. + * + * The first step is to create a Compositable client and call Connect(). + * Connect() creates the underlying IPDL actor (see CompositableChild) and the + * corresponding CompositableHost on the other side. + * + * To do in-transaction texture transfer (the default), call + * ShadowLayerForwarder::Attach(CompositableClient*, ShadowableLayer*). This + * will let the LayerComposite on the compositor side know which + * CompositableHost to use for compositing. + * + * To do async texture transfer (like async-video), the CompositableClient + * should be created with a different CompositableForwarder (like + * ImageBridgeChild) and attachment is done with + * CompositableForwarder::AttachAsyncCompositable that takes an identifier + * instead of a CompositableChild, since the CompositableClient is not managed + * by this layer forwarder (the matching uses a global map on the compositor + * side, see CompositableMap in ImageBridgeParent.cpp) + * + * Subclasses: Painted layers use ContentClients, ImageLayers use ImageClients, + * Canvas layers use CanvasClients (but ImageHosts). We have a different + * subclass where we have a different way of interfacing with the textures - in + * terms of drawing into the compositable and/or passing its contents to the + * compostior. + */ +class CompositableClient { + protected: + virtual ~CompositableClient(); + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CompositableClient) + + explicit CompositableClient(CompositableForwarder* aForwarder, + TextureFlags aFlags = TextureFlags::NO_FLAGS); + + virtual void Dump(std::stringstream& aStream, const char* aPrefix = "", + bool aDumpHtml = false, + TextureDumpMode aCompress = TextureDumpMode::Compress){}; + + virtual TextureInfo GetTextureInfo() const = 0; + + LayersBackend GetCompositorBackendType() const; + + already_AddRefed CreateBufferTextureClient( + gfx::SurfaceFormat aFormat, gfx::IntSize aSize, + gfx::BackendType aMoz2dBackend = gfx::BackendType::NONE, + TextureFlags aFlags = TextureFlags::DEFAULT); + + already_AddRefed CreateTextureClientForDrawing( + gfx::SurfaceFormat aFormat, gfx::IntSize aSize, BackendSelector aSelector, + TextureFlags aTextureFlags, + TextureAllocationFlags aAllocFlags = ALLOC_DEFAULT); + + already_AddRefed CreateTextureClientFromSurface( + gfx::SourceSurface* aSurface, BackendSelector aSelector, + TextureFlags aTextureFlags, + TextureAllocationFlags aAllocFlags = ALLOC_DEFAULT); + + /** + * Establishes the connection with compositor side through IPDL + */ + virtual bool Connect(ImageContainer* aImageContainer = nullptr); + + void Destroy(); + + bool IsConnected() const; + + CompositableForwarder* GetForwarder() const { return mForwarder; } + + /** + * This identifier is what lets us attach async compositables with a shadow + * layer. It is not used if the compositable is used with the regular shadow + * layer forwarder. + * + * If this returns empty, it means the compositable is not async (it is used + * on the main thread). + */ + CompositableHandle GetAsyncHandle() const; + + /** + * Handle for IPDL communication. + */ + CompositableHandle GetIPCHandle() const { return mHandle; } + + /** + * Tells the Compositor to create a TextureHost for this TextureClient. + */ + virtual bool AddTextureClient(TextureClient* aClient); + + /** + * A hook for the when the Compositable is detached from it's layer. + */ + virtual void OnDetach() {} + + /** + * Clear any resources that are not immediately necessary. This may be called + * in low-memory conditions. + */ + virtual void ClearCachedResources(); + + /** + * Shrink memory usage. + * Called when "memory-pressure" is observed. + */ + virtual void HandleMemoryPressure(); + + /** + * Should be called when deataching a TextureClient from a Compositable, + * because some platforms need to do some extra book keeping when this + * happens. + * + * See AutoRemoveTexture to automatically invoke this at the end of a scope. + */ + virtual void RemoveTexture(TextureClient* aTexture); + + void InitIPDL(const CompositableHandle& aHandle); + + TextureFlags GetTextureFlags() const { return mTextureFlags; } + + TextureClientRecycleAllocator* GetTextureClientRecycler(); + + bool HasTextureClientRecycler() { + auto lock = mTextureClientRecycler.Lock(); + return !!(*lock); + } + + static void DumpTextureClient(std::stringstream& aStream, + TextureClient* aTexture, + TextureDumpMode aCompress); + + protected: + RefPtr mForwarder; + // Some layers may want to enforce some flags to all their textures + // (like disallowing tiling) + Atomic mTextureFlags; + DataMutex> mTextureClientRecycler; + + // Only ever accessed via mTextureClientRecycler's Lock + CompositableHandle mHandle; + bool mIsAsync; + + friend class CompositableChild; +}; + +/** + * Helper to call RemoveTexture at the end of a scope. + */ +struct AutoRemoveTexture { + explicit AutoRemoveTexture(CompositableClient* aCompositable, + TextureClient* aTexture = nullptr) + : mTexture(aTexture), mCompositable(aCompositable) {} + + ~AutoRemoveTexture(); + + RefPtr mTexture; + + private: + CompositableClient* mCompositable; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/client/ContentClient.cpp b/gfx/layers/client/ContentClient.cpp new file mode 100644 index 0000000000..4e465f0a02 --- /dev/null +++ b/gfx/layers/client/ContentClient.cpp @@ -0,0 +1,881 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/ContentClient.h" +#include "BasicLayers.h" // for BasicLayerManager +#include "gfxContext.h" // for gfxContext, etc +#include "gfxPlatform.h" // for gfxPlatform +#include "gfxEnv.h" // for gfxEnv + +#include "gfxPoint.h" // for IntSize, gfxPoint +#include "gfxUtils.h" // for gfxUtils +#include "ipc/ShadowLayers.h" // for ShadowLayerForwarder +#include "mozilla/ArrayUtils.h" // for ArrayLength +#include "mozilla/gfx/2D.h" // for DrawTarget, Factory +#include "mozilla/gfx/BasePoint.h" // for BasePoint +#include "mozilla/gfx/BaseSize.h" // for BaseSize +#include "mozilla/gfx/Rect.h" // for Rect +#include "mozilla/gfx/Types.h" +#include "mozilla/layers/CompositorBridgeChild.h" // for CompositorBridgeChild +#include "mozilla/layers/LayerManagerComposite.h" +#include "mozilla/layers/LayersMessages.h" // for ThebesBufferData +#include "mozilla/layers/LayersTypes.h" +#include "mozilla/layers/PaintThread.h" +#include "nsDebug.h" // for NS_ASSERTION, NS_WARNING, etc +#include "nsISupportsImpl.h" // for gfxContext::Release, etc +#include "nsIWidget.h" // for nsIWidget +#include "nsLayoutUtils.h" +#ifdef XP_WIN +# include "gfxWindowsPlatform.h" +#endif +#ifdef MOZ_WIDGET_GTK +# include "gfxPlatformGtk.h" +#endif +#ifdef MOZ_WAYLAND +# include "mozilla/widget/nsWaylandDisplay.h" +#endif +#include "ReadbackLayer.h" + +#include +#include + +namespace mozilla { + +using namespace gfx; + +namespace layers { + +static TextureFlags TextureFlagsForContentClientFlags(uint32_t aBufferFlags) { + TextureFlags result = TextureFlags::NO_FLAGS; + + if (aBufferFlags & ContentClient::BUFFER_COMPONENT_ALPHA) { + result |= TextureFlags::COMPONENT_ALPHA; + } + + return result; +} + +static IntRect ComputeBufferRect(const IntRect& aRequestedRect) { + IntRect rect(aRequestedRect); + // Set a minimum width to guarantee a minimum size of buffers we + // allocate (and work around problems on some platforms with smaller + // dimensions). 64 used to be the magic number needed to work around + // a rendering glitch on b2g (see bug 788411). Now that we don't support + // this device anymore we should be fine with 8 pixels as the minimum. + rect.SetWidth(std::max(aRequestedRect.Width(), 8)); + return rect; +} + +/* static */ +already_AddRefed ContentClient::CreateContentClient( + CompositableForwarder* aForwarder) { + LayersBackend backend = aForwarder->GetCompositorBackendType(); + if (backend != LayersBackend::LAYERS_OPENGL && + backend != LayersBackend::LAYERS_D3D11 && + backend != LayersBackend::LAYERS_WR && + backend != LayersBackend::LAYERS_BASIC) { + return nullptr; + } + + bool useDoubleBuffering = false; + +#ifdef XP_WIN + if (backend == LayersBackend::LAYERS_D3D11) { + useDoubleBuffering = gfxWindowsPlatform::GetPlatform()->IsDirect2DBackend(); + } else +#endif + { +#ifdef MOZ_WIDGET_GTK +# ifdef MOZ_WAYLAND + if (widget::GetDMABufDevice()->IsDMABufTexturesEnabled()) { + useDoubleBuffering = true; + } else +# endif + // We can't use double buffering when using image content with + // Xrender support on Linux, as ContentHostDoubleBuffered is not + // suited for direct uploads to the server. + if (!gfxPlatformGtk::GetPlatform()->UseImageOffscreenSurfaces() || + !gfxVars::UseXRender()) +#endif + { + useDoubleBuffering = backend == LayersBackend::LAYERS_BASIC; + } + } + + if (useDoubleBuffering || gfxEnv::ForceDoubleBuffering()) { + return MakeAndAddRef(aForwarder); + } + return MakeAndAddRef(aForwarder); +} + +void ContentClient::Clear() { mBuffer = nullptr; } + +ContentClient::PaintState ContentClient::BeginPaint(PaintedLayer* aLayer, + uint32_t aFlags) { + BufferDecision dest = CalculateBufferForPaint(aLayer, aFlags); + + PaintState result; + result.mAsyncPaint = (aFlags & PAINT_ASYNC); + result.mContentType = dest.mBufferContentType; + + if (!dest.mCanKeepBufferContents) { + // We're effectively clearing the valid region, so we need to draw + // the entire needed region now. + MOZ_ASSERT(!dest.mCanReuseBuffer); + MOZ_ASSERT(dest.mValidRegion.IsEmpty()); + + result.mRegionToInvalidate = aLayer->GetValidRegion(); + +#if defined(MOZ_DUMP_PAINTING) + if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { + if (result.mContentType != mBuffer->GetContentType()) { + printf_stderr( + "Invalidating entire rotated buffer (layer %p): content type " + "changed\n", + aLayer); + } else if ((dest.mBufferMode == SurfaceMode::SURFACE_COMPONENT_ALPHA) != + mBuffer->HaveBufferOnWhite()) { + printf_stderr( + "Invalidating entire rotated buffer (layer %p): component alpha " + "changed\n", + aLayer); + } + } +#endif + Clear(); + } + + result.mRegionToDraw.Sub(dest.mNeededRegion, dest.mValidRegion); + + if (result.mRegionToDraw.IsEmpty()) return result; + + // We need to disable rotation if we're going to be resampled when + // drawing, because we might sample across the rotation boundary. + // Also disable buffer rotation when using webrender. + bool canHaveRotation = + gfxPlatform::BufferRotationEnabled() && + !(aFlags & (PAINT_WILL_RESAMPLE | PAINT_NO_ROTATION)) && + !(aLayer->Manager()->AsWebRenderLayerManager()); + bool canDrawRotated = aFlags & PAINT_CAN_DRAW_ROTATED; + OpenMode readMode = + result.mAsyncPaint ? OpenMode::OPEN_READ_ASYNC : OpenMode::OPEN_READ; + OpenMode writeMode = result.mAsyncPaint ? OpenMode::OPEN_READ_WRITE_ASYNC + : OpenMode::OPEN_READ_WRITE; + + IntRect drawBounds = result.mRegionToDraw.GetBounds(); + + if (result.mAsyncPaint) { + result.mAsyncTask.reset(new PaintTask()); + } + + // Try to acquire the back buffer, copy over contents if we are using a new + // buffer, and rotate or unrotate the buffer as necessary + if (mBuffer && dest.mCanReuseBuffer) { + if (mBuffer->Lock(writeMode)) { + auto newParameters = mBuffer->AdjustedParameters(dest.mBufferRect); + + bool needsUnrotate = + (!canHaveRotation && newParameters.IsRotated()) || + (!canDrawRotated && newParameters.RectWrapsBuffer(drawBounds)); + bool canUnrotate = + !result.mAsyncPaint || mBuffer->BufferRotation() == IntPoint(0, 0); + + // Only begin a frame and copy over the previous frame if we don't need + // to unrotate, or we can try to unrotate it. This is to ensure that we + // don't have a paint task that depends on another paint task. + if (!needsUnrotate || canUnrotate) { + // If we're async painting then begin to capture draw commands + if (result.mAsyncPaint) { + mBuffer->BeginCapture(); + } + + // Do not modify result.mRegionToDraw or result.mContentType after this + // call. + FinalizeFrame(result); + } + + // Try to rotate the buffer or unrotate it if we cannot be rotated + if (needsUnrotate) { + if (canUnrotate && mBuffer->UnrotateBufferTo(newParameters)) { + newParameters.SetUnrotated(); + mBuffer->SetParameters(newParameters); + } else { + MOZ_ASSERT(GetFrontBuffer()); + mBuffer->Unlock(); + dest.mBufferRect = ComputeBufferRect(dest.mNeededRegion.GetBounds()); + dest.mCanReuseBuffer = false; + } + } else { + mBuffer->SetParameters(newParameters); + } + } else { + result.mRegionToDraw = dest.mNeededRegion; + dest.mCanReuseBuffer = false; + Clear(); + } + } + + MOZ_ASSERT(dest.mBufferRect.Contains(result.mRegionToDraw.GetBounds())); + + NS_ASSERTION(!(aFlags & PAINT_WILL_RESAMPLE) || + dest.mBufferRect == dest.mNeededRegion.GetBounds(), + "If we're resampling, we need to validate the entire buffer"); + + // We never had a buffer, the buffer wasn't big enough, the content changed + // types, or we failed to unrotate the buffer when requested. In any case, + // we need to allocate a new one and prepare it for drawing. + if (!dest.mCanReuseBuffer) { + uint32_t bufferFlags = 0; + if (dest.mBufferMode == SurfaceMode::SURFACE_COMPONENT_ALPHA) { + bufferFlags |= BUFFER_COMPONENT_ALPHA; + } + + RefPtr newBuffer = + CreateBuffer(result.mContentType, dest.mBufferRect, bufferFlags); + + if (!newBuffer) { + if (Factory::ReasonableSurfaceSize( + IntSize(dest.mBufferRect.Width(), dest.mBufferRect.Height()))) { + gfxCriticalNote << "Failed buffer for " << dest.mBufferRect.X() << ", " + << dest.mBufferRect.Y() << ", " + << dest.mBufferRect.Width() << ", " + << dest.mBufferRect.Height(); + } + result.mAsyncTask = nullptr; + Clear(); + return result; + } + + if (!newBuffer->Lock(writeMode)) { + gfxCriticalNote << "Failed to lock new back buffer."; + result.mAsyncTask = nullptr; + Clear(); + return result; + } + + if (result.mAsyncPaint) { + newBuffer->BeginCapture(); + } + + // If we have an existing front buffer, copy it into the new back buffer + RefPtr frontBuffer = GetFrontBuffer(); + + if (frontBuffer && frontBuffer->Lock(readMode)) { + nsIntRegion updateRegion = newBuffer->BufferRect(); + updateRegion.Sub(updateRegion, result.mRegionToDraw); + + if (!updateRegion.IsEmpty()) { + newBuffer->UpdateDestinationFrom(*frontBuffer, + updateRegion.GetBounds()); + } + + frontBuffer->Unlock(); + } else { + result.mRegionToDraw = dest.mNeededRegion; + } + + Clear(); + mBuffer = newBuffer; + } + + NS_ASSERTION(canHaveRotation || mBuffer->BufferRotation() == IntPoint(0, 0), + "Rotation disabled, but we have nonzero rotation?"); + + if (result.mAsyncPaint) { + result.mAsyncTask->mTarget = mBuffer->GetBufferTarget(); + result.mAsyncTask->mClients.AppendElement(mBuffer->GetClient()); + if (mBuffer->GetClientOnWhite()) { + result.mAsyncTask->mClients.AppendElement(mBuffer->GetClientOnWhite()); + } + } + + nsIntRegion invalidate; + invalidate.Sub(aLayer->GetValidRegion(), dest.mBufferRect); + result.mRegionToInvalidate.Or(result.mRegionToInvalidate, invalidate); + + result.mClip = DrawRegionClip::DRAW; + result.mMode = dest.mBufferMode; + + return result; +} + +void ContentClient::EndPaint( + PaintState& aPaintState, + nsTArray* aReadbackUpdates) { + if (aPaintState.mAsyncTask) { + aPaintState.mAsyncTask->mCapture = mBuffer->EndCapture(); + } +} + +static nsIntRegion ExpandDrawRegion(ContentClient::PaintState& aPaintState, + RotatedBuffer::DrawIterator* aIter, + BackendType aBackendType) { + nsIntRegion* drawPtr = &aPaintState.mRegionToDraw; + if (aIter) { + // The iterators draw region currently only contains the bounds of the + // region, this makes it the precise region. + aIter->mDrawRegion.And(aIter->mDrawRegion, aPaintState.mRegionToDraw); + drawPtr = &aIter->mDrawRegion; + } + if (aBackendType == BackendType::DIRECT2D || + aBackendType == BackendType::DIRECT2D1_1) { + // Simplify the draw region to avoid hitting expensive drawing paths + // for complex regions. + drawPtr->SimplifyOutwardByArea(100 * 100); + } + return *drawPtr; +} + +DrawTarget* ContentClient::BorrowDrawTargetForPainting( + ContentClient::PaintState& aPaintState, + RotatedBuffer::DrawIterator* aIter /* = nullptr */) { + if (aPaintState.mMode == SurfaceMode::SURFACE_NONE || !mBuffer) { + return nullptr; + } + + DrawTarget* result = mBuffer->BorrowDrawTargetForQuadrantUpdate( + aPaintState.mRegionToDraw.GetBounds(), aIter); + if (!result || !result->IsValid()) { + if (result) { + mBuffer->ReturnDrawTarget(result); + } + return nullptr; + } + + nsIntRegion regionToDraw = + ExpandDrawRegion(aPaintState, aIter, result->GetBackendType()); + + if (aPaintState.mMode == SurfaceMode::SURFACE_COMPONENT_ALPHA || + aPaintState.mContentType == gfxContentType::COLOR_ALPHA) { + // HaveBuffer() => we have an existing buffer that we must clear + for (auto iter = regionToDraw.RectIter(); !iter.Done(); iter.Next()) { + const IntRect& rect = iter.Get(); + result->ClearRect(Rect(rect.X(), rect.Y(), rect.Width(), rect.Height())); + } + } + + return result; +} + +void ContentClient::ReturnDrawTarget(gfx::DrawTarget*& aReturned) { + mBuffer->ReturnDrawTarget(aReturned); +} + +ContentClient::BufferDecision ContentClient::CalculateBufferForPaint( + PaintedLayer* aLayer, uint32_t aFlags) { + gfxContentType layerContentType = aLayer->CanUseOpaqueSurface() + ? gfxContentType::COLOR + : gfxContentType::COLOR_ALPHA; + + SurfaceMode mode; + gfxContentType contentType; + IntRect destBufferRect; + nsIntRegion neededRegion; + nsIntRegion validRegion = aLayer->GetValidRegion(); + + bool canReuseBuffer = !!mBuffer; + bool canKeepBufferContents = true; + + while (true) { + mode = aLayer->GetSurfaceMode(); + neededRegion = aLayer->GetVisibleRegion().ToUnknownRegion(); + canReuseBuffer = + canReuseBuffer && + ValidBufferSize(mBufferSizePolicy, mBuffer->BufferRect().Size(), + neededRegion.GetBounds().Size()); + contentType = layerContentType; + + if (canReuseBuffer) { + if (mBuffer->BufferRect().Contains(neededRegion.GetBounds())) { + // We don't need to adjust mBufferRect. + destBufferRect = mBuffer->BufferRect(); + } else if (neededRegion.GetBounds().Size() <= + mBuffer->BufferRect().Size()) { + // The buffer's big enough but doesn't contain everything that's + // going to be visible. We'll move it. + destBufferRect = IntRect(neededRegion.GetBounds().TopLeft(), + mBuffer->BufferRect().Size()); + } else { + destBufferRect = neededRegion.GetBounds(); + } + } else { + destBufferRect = ComputeBufferRect(neededRegion.GetBounds()); + } + + if (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) { +#if defined(MOZ_GFX_OPTIMIZE_MOBILE) + mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA; +#else + if (!aLayer->GetParent() || + !aLayer->GetParent()->SupportsComponentAlphaChildren() || + !aLayer->AsShadowableLayer() || + !aLayer->AsShadowableLayer()->HasShadow()) { + mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA; + } else { + contentType = gfxContentType::COLOR; + } +#endif + } + + if ((aFlags & PAINT_WILL_RESAMPLE) && + (!neededRegion.GetBounds().IsEqualInterior(destBufferRect) || + neededRegion.GetNumRects() > 1)) { + // The area we add to neededRegion might not be painted opaquely. + if (mode == SurfaceMode::SURFACE_OPAQUE) { + contentType = gfxContentType::COLOR_ALPHA; + mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA; + } + + // We need to validate the entire buffer, to make sure that only valid + // pixels are sampled. + neededRegion = destBufferRect; + } + + // If we have an existing buffer, but the content type has changed or we + // have transitioned into/out of component alpha, then we need to recreate + // it. + bool needsComponentAlpha = (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA); + bool backBufferChangedSurface = + mBuffer && (contentType != mBuffer->GetContentType() || + needsComponentAlpha != mBuffer->HaveBufferOnWhite()); + if (canKeepBufferContents && backBufferChangedSurface) { + // Restart the decision process; we won't re-enter since we guard on + // being able to keep the buffer contents. + canReuseBuffer = false; + canKeepBufferContents = false; + validRegion.SetEmpty(); + continue; + } + break; + } + + NS_ASSERTION(destBufferRect.Contains(neededRegion.GetBounds()), + "Destination rect doesn't contain what we need to paint"); + + BufferDecision dest; + dest.mNeededRegion = std::move(neededRegion); + dest.mValidRegion = std::move(validRegion); + dest.mBufferRect = destBufferRect; + dest.mBufferMode = mode; + dest.mBufferContentType = contentType; + dest.mCanReuseBuffer = canReuseBuffer; + dest.mCanKeepBufferContents = canKeepBufferContents; + return dest; +} + +bool ContentClient::ValidBufferSize(BufferSizePolicy aPolicy, + const gfx::IntSize& aBufferSize, + const gfx::IntSize& aVisibleBoundsSize) { + return ( + aVisibleBoundsSize == aBufferSize || + (SizedToVisibleBounds != aPolicy && aVisibleBoundsSize < aBufferSize)); +} + +void ContentClient::PrintInfo(std::stringstream& aStream, const char* aPrefix) { + aStream << aPrefix; + aStream << nsPrintfCString("ContentClient (0x%p)", this).get(); +} + +// We pass a null pointer for the ContentClient Forwarder argument, which means +// this client will not have a ContentHost on the other side. +ContentClientBasic::ContentClientBasic(gfx::BackendType aBackend) + : ContentClient(nullptr, ContainsVisibleBounds), mBackend(aBackend) {} + +void ContentClientBasic::DrawTo(PaintedLayer* aLayer, gfx::DrawTarget* aTarget, + float aOpacity, gfx::CompositionOp aOp, + gfx::SourceSurface* aMask, + const gfx::Matrix* aMaskTransform) { + if (!mBuffer) { + return; + } + + mBuffer->DrawTo(aLayer, aTarget, aOpacity, aOp, aMask, aMaskTransform); +} + +RefPtr ContentClientBasic::CreateBuffer(gfxContentType aType, + const IntRect& aRect, + uint32_t aFlags) { + MOZ_ASSERT(!(aFlags & BUFFER_COMPONENT_ALPHA)); + if (aFlags & BUFFER_COMPONENT_ALPHA) { + gfxDevCrash(LogReason::AlphaWithBasicClient) + << "Asking basic content client for component alpha"; + } + + IntSize size(aRect.Width(), aRect.Height()); + RefPtr drawTarget; + +#ifdef XP_WIN + if (mBackend == BackendType::CAIRO && + (aType == gfxContentType::COLOR || + aType == gfxContentType::COLOR_ALPHA)) { + RefPtr surf = new gfxWindowsSurface( + size, aType == gfxContentType::COLOR ? gfxImageFormat::X8R8G8B8_UINT32 + : gfxImageFormat::A8R8G8B8_UINT32); + drawTarget = gfxPlatform::CreateDrawTargetForSurface(surf, size); + } +#endif + + if (!drawTarget) { + drawTarget = gfxPlatform::GetPlatform()->CreateDrawTargetForBackend( + mBackend, size, + gfxPlatform::GetPlatform()->Optimal2DFormatForContent(aType)); + } + + if (!drawTarget) { + return nullptr; + } + + return new DrawTargetRotatedBuffer(drawTarget, nullptr, aRect, + IntPoint(0, 0)); +} + +class RemoteBufferReadbackProcessor : public TextureReadbackSink { + public: + RemoteBufferReadbackProcessor( + nsTArray* aReadbackUpdates, + const IntRect& aBufferRect, const nsIntPoint& aBufferRotation) + : mReadbackUpdates(aReadbackUpdates->Clone()), + mBufferRect(aBufferRect), + mBufferRotation(aBufferRotation) { + for (uint32_t i = 0; i < mReadbackUpdates.Length(); ++i) { + mLayerRefs.push_back(mReadbackUpdates[i].mLayer); + } + } + + virtual void ProcessReadback( + gfx::DataSourceSurface* aSourceSurface) override { + SourceRotatedBuffer rotBuffer(aSourceSurface, nullptr, mBufferRect, + mBufferRotation); + + for (uint32_t i = 0; i < mReadbackUpdates.Length(); ++i) { + ReadbackProcessor::Update& update = mReadbackUpdates[i]; + nsIntPoint offset = update.mLayer->GetBackgroundLayerOffset(); + + ReadbackSink* sink = update.mLayer->GetSink(); + + if (!sink) { + continue; + } + + if (!aSourceSurface) { + sink->SetUnknown(update.mSequenceCounter); + continue; + } + + RefPtr dt = sink->BeginUpdate(update.mUpdateRect + offset, + update.mSequenceCounter); + if (!dt) { + continue; + } + + dt->SetTransform(Matrix::Translation(offset.x, offset.y)); + + rotBuffer.DrawBufferWithRotation(dt); + + update.mLayer->GetSink()->EndUpdate(update.mUpdateRect + offset); + } + } + + private: + nsTArray mReadbackUpdates; + // This array is used to keep the layers alive until the callback. + std::vector> mLayerRefs; + + IntRect mBufferRect; + nsIntPoint mBufferRotation; +}; + +void ContentClientRemoteBuffer::EndPaint( + PaintState& aPaintState, + nsTArray* aReadbackUpdates) { + MOZ_ASSERT(!mBuffer || !mBuffer->HaveBufferOnWhite() || !aReadbackUpdates || + aReadbackUpdates->Length() == 0); + + RemoteRotatedBuffer* remoteBuffer = GetRemoteBuffer(); + + if (remoteBuffer && remoteBuffer->IsLocked()) { + if (aReadbackUpdates && aReadbackUpdates->Length() > 0) { + RefPtr readbackSink = + new RemoteBufferReadbackProcessor(aReadbackUpdates, + remoteBuffer->BufferRect(), + remoteBuffer->BufferRotation()); + + remoteBuffer->GetClient()->SetReadbackSink(readbackSink); + } + + remoteBuffer->Unlock(); + remoteBuffer->SyncWithObject(mForwarder->GetSyncObject()); + } + + ContentClient::EndPaint(aPaintState, aReadbackUpdates); +} + +RefPtr ContentClientRemoteBuffer::CreateBuffer( + gfxContentType aType, const IntRect& aRect, uint32_t aFlags) { + // If we hit this assertion, then it might be due to an empty transaction + // followed by a real transaction. Our buffers should be created (but not + // painted in the empty transaction) and then painted (but not created) in the + // real transaction. That is kind of fragile, and this assert will catch + // circumstances where we screw that up, e.g., by unnecessarily recreating our + // buffers. + MOZ_ASSERT(!mIsNewBuffer, + "Bad! Did we create a buffer twice without painting?"); + + gfx::SurfaceFormat format = + gfxPlatform::GetPlatform()->Optimal2DFormatForContent(aType); + + TextureFlags textureFlags = TextureFlagsForContentClientFlags(aFlags); + if (aFlags & BUFFER_COMPONENT_ALPHA) { + textureFlags |= TextureFlags::COMPONENT_ALPHA; + } + + RefPtr buffer = + CreateBufferInternal(aRect, format, textureFlags); + + if (!buffer) { + return nullptr; + } + + mIsNewBuffer = true; + mTextureFlags = textureFlags; + + return buffer; +} + +RefPtr ContentClientRemoteBuffer::CreateBufferInternal( + const gfx::IntRect& aRect, gfx::SurfaceFormat aFormat, + TextureFlags aFlags) { + TextureAllocationFlags textureAllocFlags = + TextureAllocationFlags::ALLOC_DEFAULT; + + RefPtr textureClient = CreateTextureClientForDrawing( + aFormat, aRect.Size(), BackendSelector::Content, + aFlags | ExtraTextureFlags() | TextureFlags::BLOCKING_READ_LOCK, + textureAllocFlags); + + if (!textureClient || !AddTextureClient(textureClient)) { + return nullptr; + } + + RefPtr textureClientOnWhite; + if (aFlags & TextureFlags::COMPONENT_ALPHA) { + TextureAllocationFlags allocFlags = TextureAllocationFlags::ALLOC_DEFAULT; + if (mForwarder->SupportsTextureDirectMapping()) { + allocFlags = + TextureAllocationFlags(allocFlags | ALLOC_ALLOW_DIRECT_MAPPING); + } + textureClientOnWhite = + textureClient->CreateSimilar(mForwarder->GetCompositorBackendType(), + aFlags | ExtraTextureFlags(), allocFlags); + if (!textureClientOnWhite || !AddTextureClient(textureClientOnWhite)) { + return nullptr; + } + // We don't enable the readlock for the white buffer since we always + // use them together and waiting on the lock for the black + // should be sufficient. + } + + return new RemoteRotatedBuffer(textureClient, textureClientOnWhite, aRect, + IntPoint(0, 0)); +} + +nsIntRegion ContentClientRemoteBuffer::GetUpdatedRegion( + const nsIntRegion& aRegionToDraw, const nsIntRegion& aVisibleRegion) { + nsIntRegion updatedRegion; + if (mIsNewBuffer || mBuffer->DidSelfCopy()) { + // A buffer reallocation clears both buffers. The front buffer has all the + // content by now, but the back buffer is still clear. Here, in effect, we + // are saying to copy all of the pixels of the front buffer to the back. + // Also when we self-copied in the buffer, the buffer space + // changes and some changed buffer content isn't reflected in the + // draw or invalidate region (on purpose!). When this happens, we + // need to read back the entire buffer too. + updatedRegion = aVisibleRegion.GetBounds(); + mIsNewBuffer = false; + } else { + updatedRegion = aRegionToDraw; + } + + MOZ_ASSERT(mBuffer, "should have a back buffer by now"); + NS_ASSERTION(mBuffer->BufferRect().Contains(aRegionToDraw.GetBounds()), + "Update outside of buffer rect!"); + + return updatedRegion; +} + +void ContentClientRemoteBuffer::Updated(const nsIntRegion& aRegionToDraw, + const nsIntRegion& aVisibleRegion) { + nsIntRegion updatedRegion = GetUpdatedRegion(aRegionToDraw, aVisibleRegion); + + RemoteRotatedBuffer* remoteBuffer = GetRemoteBuffer(); + + MOZ_ASSERT(remoteBuffer && remoteBuffer->GetClient()); + if (remoteBuffer->HaveBufferOnWhite()) { + mForwarder->UseComponentAlphaTextures(this, remoteBuffer->GetClient(), + remoteBuffer->GetClientOnWhite()); + } else { + AutoTArray textures; + CompositableForwarder::TimedTextureClient* t = textures.AppendElement(); + t->mTextureClient = remoteBuffer->GetClient(); + IntSize size = remoteBuffer->GetClient()->GetSize(); + t->mPictureRect = nsIntRect(0, 0, size.width, size.height); + + GetForwarder()->UseTextures(this, textures); + } + + // This forces a synchronous transaction, so we can swap buffers now + // and know that we'll have sole ownership of the old front buffer + // by the time we paint next. + mForwarder->UpdateTextureRegion( + this, + ThebesBufferData(remoteBuffer->BufferRect(), + remoteBuffer->BufferRotation()), + updatedRegion); + SwapBuffers(updatedRegion); +} + +void ContentClientRemoteBuffer::Dump(std::stringstream& aStream, + const char* aPrefix, bool aDumpHtml, + TextureDumpMode aCompress) { + RemoteRotatedBuffer* remoteBuffer = GetRemoteBuffer(); + + // TODO We should combine the OnWhite/OnBlack here an just output a single + // image. + if (!aDumpHtml) { + aStream << "\n" << aPrefix << "Surface: "; + } + CompositableClient::DumpTextureClient( + aStream, remoteBuffer ? remoteBuffer->GetClient() : nullptr, aCompress); +} + +void ContentClientDoubleBuffered::Dump(std::stringstream& aStream, + const char* aPrefix, bool aDumpHtml, + TextureDumpMode aCompress) { + // TODO We should combine the OnWhite/OnBlack here an just output a single + // image. + if (!aDumpHtml) { + aStream << "\n" << aPrefix << "Surface: "; + } + CompositableClient::DumpTextureClient( + aStream, mFrontBuffer ? mFrontBuffer->GetClient() : nullptr, aCompress); +} + +void ContentClientDoubleBuffered::Clear() { + ContentClient::Clear(); + mFrontBuffer = nullptr; +} + +void ContentClientDoubleBuffered::SwapBuffers( + const nsIntRegion& aFrontUpdatedRegion) { + mFrontUpdatedRegion = aFrontUpdatedRegion; + + RefPtr frontBuffer = mFrontBuffer; + RefPtr backBuffer = GetRemoteBuffer(); + + std::swap(frontBuffer, backBuffer); + + mFrontBuffer = frontBuffer; + mBuffer = backBuffer; + + mFrontAndBackBufferDiffer = true; +} + +ContentClient::PaintState ContentClientDoubleBuffered::BeginPaint( + PaintedLayer* aLayer, uint32_t aFlags) { + EnsureBackBufferIfFrontBuffer(); + + mIsNewBuffer = false; + + if (!mFrontBuffer || !mBuffer) { + mFrontAndBackBufferDiffer = false; + } + + if (mFrontAndBackBufferDiffer) { + if (mFrontBuffer->DidSelfCopy()) { + // We can't easily draw our front buffer into us, since we're going to be + // copying stuff around anyway it's easiest if we just move our situation + // to non-rotated while we're at it. If this situation occurs we'll have + // hit a self-copy path in PaintThebes before as well anyway. + gfx::IntRect backBufferRect = mBuffer->BufferRect(); + backBufferRect.MoveTo(mFrontBuffer->BufferRect().TopLeft()); + + mBuffer->SetBufferRect(backBufferRect); + mBuffer->SetBufferRotation(IntPoint(0, 0)); + } else { + mBuffer->SetBufferRect(mFrontBuffer->BufferRect()); + mBuffer->SetBufferRotation(mFrontBuffer->BufferRotation()); + } + } + + return ContentClient::BeginPaint(aLayer, aFlags); +} + +// Sync front/back buffers content +// After executing, the new back buffer has the same (interesting) pixels as +// the new front buffer, and mValidRegion et al. are correct wrt the new +// back buffer (i.e. as they were for the old back buffer) +void ContentClientDoubleBuffered::FinalizeFrame(PaintState& aPaintState) { + if (!mFrontAndBackBufferDiffer) { + MOZ_ASSERT(!mFrontBuffer || !mFrontBuffer->DidSelfCopy(), + "If the front buffer did a self copy then our front and back " + "buffer must be different."); + return; + } + + MOZ_ASSERT(mFrontBuffer && mBuffer); + if (!mFrontBuffer || !mBuffer) { + return; + } + + MOZ_LAYERS_LOG( + ("BasicShadowableThebes(%p): reading back ", this, + mFrontUpdatedRegion.GetBounds().X(), mFrontUpdatedRegion.GetBounds().Y(), + mFrontUpdatedRegion.GetBounds().Width(), + mFrontUpdatedRegion.GetBounds().Height())); + + mFrontAndBackBufferDiffer = false; + + nsIntRegion updateRegion = mFrontUpdatedRegion; + if (mFrontBuffer->DidSelfCopy()) { + mFrontBuffer->ClearDidSelfCopy(); + updateRegion = mBuffer->BufferRect(); + } + + // No point in sync'ing what we are going to draw over anyway. And if there is + // nothing to sync at all, there is nothing to do and we can go home early. + updateRegion.Sub(updateRegion, aPaintState.mRegionToDraw); + if (updateRegion.IsEmpty()) { + return; + } + + OpenMode openMode = aPaintState.mAsyncPaint ? OpenMode::OPEN_READ_ASYNC + : OpenMode::OPEN_READ_ONLY; + + if (mFrontBuffer->Lock(openMode)) { + mBuffer->UpdateDestinationFrom(*mFrontBuffer, updateRegion.GetBounds()); + + if (aPaintState.mAsyncPaint) { + aPaintState.mAsyncTask->mClients.AppendElement(mFrontBuffer->GetClient()); + if (mFrontBuffer->GetClientOnWhite()) { + aPaintState.mAsyncTask->mClients.AppendElement( + mFrontBuffer->GetClientOnWhite()); + } + } + + mFrontBuffer->Unlock(); + } +} + +void ContentClientDoubleBuffered::EnsureBackBufferIfFrontBuffer() { + if (!mBuffer && mFrontBuffer) { + mBuffer = CreateBufferInternal(mFrontBuffer->BufferRect(), + mFrontBuffer->GetFormat(), mTextureFlags); + MOZ_ASSERT(mBuffer); + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/ContentClient.h b/gfx/layers/client/ContentClient.h new file mode 100644 index 0000000000..0763e41b3a --- /dev/null +++ b/gfx/layers/client/ContentClient.h @@ -0,0 +1,362 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_CONTENTCLIENT_H +#define MOZILLA_GFX_CONTENTCLIENT_H + +#include // for uint32_t +#include "RotatedBuffer.h" // for RotatedBuffer, etc +#include "gfxTypes.h" +#include "gfxPlatform.h" // for gfxPlatform +#include "mozilla/Assertions.h" // for MOZ_CRASH +#include "mozilla/Attributes.h" // for override +#include "mozilla/RefPtr.h" // for RefPtr, already_AddRefed +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/layers/CompositableClient.h" // for CompositableClient +#include "mozilla/layers/CompositableForwarder.h" +#include "mozilla/layers/CompositorTypes.h" // for TextureInfo, etc +#include "mozilla/layers/ISurfaceAllocator.h" +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor +#include "mozilla/layers/LayersTypes.h" // for TextureDumpMode +#include "mozilla/layers/TextureClient.h" // for TextureClient +#include "mozilla/layers/PaintThread.h" // for PaintTask +#include "mozilla/Maybe.h" // for Maybe +#include "mozilla/mozalloc.h" // for operator delete +#include "mozilla/UniquePtr.h" // for UniquePtr +#include "ReadbackProcessor.h" // For ReadbackProcessor::Update +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsPoint.h" // for nsIntPoint +#include "nsRect.h" // for mozilla::gfx::IntRect +#include "nsRegion.h" // for nsIntRegion +#include "nsTArray.h" // for nsTArray + +namespace mozilla { +namespace gfx { +class DrawTarget; +} // namespace gfx + +namespace layers { + +class PaintedLayer; + +/** + * A compositable client for PaintedLayers. These are different to Image/Canvas + * clients due to sending a valid region across IPC and because we do a lot more + * optimisation work, encapsulated in RotatedBuffers. + * + * We use content clients for OMTC and non-OMTC, basic rendering so that + * BasicPaintedLayer has only one interface to deal with. We support single and + * double buffered flavours. For tiled layers, we do not use a ContentClient + * although we do have a ContentHost, and we do use texture clients and texture + * hosts. + * + * The interface presented by ContentClient is used by the BasicPaintedLayer + * methods - PaintThebes, which is the same for MT and OMTC, and PaintBuffer + * which is different (the OMTC one does a little more). + */ +class ContentClient : public CompositableClient { + public: + typedef gfxContentType ContentType; + + /** + * Creates, configures, and returns a new content client. If necessary, a + * message will be sent to the compositor to create a corresponding content + * host. + */ + static already_AddRefed CreateContentClient( + CompositableForwarder* aFwd); + + /** + * Controls the size of the backing buffer of this. + * - SizedToVisibleBounds: the backing buffer is exactly the same + * size as the bounds of PaintedLayer's visible region + * - ContainsVisibleBounds: the backing buffer is large enough to + * fit visible bounds. May be larger. + */ + enum BufferSizePolicy { SizedToVisibleBounds, ContainsVisibleBounds }; + + ContentClient(CompositableForwarder* aForwarder, + BufferSizePolicy aBufferSizePolicy) + : CompositableClient(aForwarder), mBufferSizePolicy(aBufferSizePolicy) {} + + virtual void PrintInfo(std::stringstream& aStream, const char* aPrefix); + + virtual void Clear(); + + /** + * This is returned by BeginPaint. The caller should draw into mTarget. + * mRegionToDraw must be drawn. mRegionToInvalidate has been invalidated + * by ContentClient and must be redrawn on the screen. + * mRegionToInvalidate is set when the buffer has changed from + * opaque to transparent or vice versa, since the details of rendering can + * depend on the buffer type. + */ + struct PaintState { + PaintState() + : mRegionToDraw(), + mRegionToInvalidate(), + mMode(SurfaceMode::SURFACE_NONE), + mClip(DrawRegionClip::NONE), + mContentType(gfxContentType::SENTINEL), + mAsyncPaint(false), + mAsyncTask(nullptr) {} + + nsIntRegion mRegionToDraw; + nsIntRegion mRegionToInvalidate; + SurfaceMode mMode; + DrawRegionClip mClip; + gfxContentType mContentType; + bool mAsyncPaint; + UniquePtr mAsyncTask; + }; + + enum { + PAINT_WILL_RESAMPLE = 0x01, + PAINT_NO_ROTATION = 0x02, + PAINT_CAN_DRAW_ROTATED = 0x04, + PAINT_ASYNC = 0x08, + }; + + /** + * Start a drawing operation. This returns a PaintState describing what + * needs to be drawn to bring the buffer up to date in the visible region. + * This queries aLayer to get the currently valid and visible regions. + * The returned mTarget may be null if mRegionToDraw is empty. + * Otherwise it must not be null. + * mRegionToInvalidate will contain mRegionToDraw. + * @param aFlags when PAINT_WILL_RESAMPLE is passed, this indicates that + * buffer will be resampled when rendering (i.e the effective transform + * combined with the scale for the resolution is not just an integer + * translation). This will disable buffer rotation (since we don't want + * to resample across the rotation boundary) and will ensure that we + * make the entire buffer contents valid (since we don't want to sample + * invalid pixels outside the visible region, if the visible region doesn't + * fill the buffer bounds). + * PAINT_CAN_DRAW_ROTATED can be passed if the caller supports drawing + * rotated content that crosses the physical buffer boundary. The caller + * will need to call BorrowDrawTargetForPainting multiple times to achieve + * this. + */ + virtual PaintState BeginPaint(PaintedLayer* aLayer, uint32_t aFlags); + virtual void EndPaint( + PaintState& aPaintState, + nsTArray* aReadbackUpdates = nullptr); + + /** + * Fetch a DrawTarget for rendering. The DrawTarget remains owned by + * this. See notes on BorrowDrawTargetForQuadrantUpdate. + * May return null. If the return value is non-null, it must be + * 'un-borrowed' using ReturnDrawTarget. + * + * If PAINT_CAN_DRAW_ROTATED was specified for BeginPaint, then the caller + * must call this function repeatedly (with an iterator) until it returns + * nullptr. The caller should draw the mDrawRegion of the iterator instead + * of mRegionToDraw in the PaintState. + * + * @param aPaintState Paint state data returned by a call to BeginPaint + * @param aIter Paint state iterator. Only required if PAINT_CAN_DRAW_ROTATED + * was specified to BeginPaint. + */ + virtual gfx::DrawTarget* BorrowDrawTargetForPainting( + PaintState& aPaintState, RotatedBuffer::DrawIterator* aIter = nullptr); + + void ReturnDrawTarget(gfx::DrawTarget*& aReturned); + + enum { + BUFFER_COMPONENT_ALPHA = 0x02 // Dual buffers should be created for drawing + // with component alpha. + }; + + protected: + struct BufferDecision { + nsIntRegion mNeededRegion; + nsIntRegion mValidRegion; + gfx::IntRect mBufferRect; + SurfaceMode mBufferMode; + gfxContentType mBufferContentType; + bool mCanReuseBuffer; + bool mCanKeepBufferContents; + }; + + /** + * Decide whether we can keep our current buffer and its contents, + * and return a struct containing the regions to paint, invalidate, + * the new buffer rect, surface mode, and content type. + */ + BufferDecision CalculateBufferForPaint(PaintedLayer* aLayer, uint32_t aFlags); + + static bool ValidBufferSize(BufferSizePolicy aPolicy, + const gfx::IntSize& aBufferSize, + const gfx::IntSize& aVisibleBoundsSize); + + /** + * Any actions that should be performed at the last moment before we begin + * rendering the next frame. I.e., after we calculate what we will draw, + * but before we rotate the buffer and possibly create new buffers. + * aRegionToDraw is the region which is guaranteed to be overwritten when + * drawing the next frame. + */ + virtual void FinalizeFrame(PaintState& aPaintState) {} + + virtual RefPtr GetFrontBuffer() const { return mBuffer; } + + /** + * Create a new rotated buffer for the specified content type, buffer rect, + * and buffer flags. + */ + virtual RefPtr CreateBuffer(gfxContentType aType, + const gfx::IntRect& aRect, + uint32_t aFlags) = 0; + + RefPtr mBuffer; + BufferSizePolicy mBufferSizePolicy; +}; + +// Thin wrapper around DrawTargetRotatedBuffer, for on-mtc +class ContentClientBasic final : public ContentClient { + public: + explicit ContentClientBasic(gfx::BackendType aBackend); + + void DrawTo(PaintedLayer* aLayer, gfx::DrawTarget* aTarget, float aOpacity, + gfx::CompositionOp aOp, gfx::SourceSurface* aMask, + const gfx::Matrix* aMaskTransform); + + TextureInfo GetTextureInfo() const override { + MOZ_CRASH("GFX: Should not be called on non-remote ContentClient"); + } + + protected: + RefPtr CreateBuffer(gfxContentType aType, + const gfx::IntRect& aRect, + uint32_t aFlags) override; + + private: + gfx::BackendType mBackend; +}; + +/** + * A ContentClient backed by a RemoteRotatedBuffer. + * + * When using a ContentClientRemoteBuffer, SurfaceDescriptors are created on + * the rendering side and destroyed on the compositing side. They are only + * passed from one side to the other when the TextureClient/Hosts are created. + * *Ownership* of the SurfaceDescriptor moves from the rendering side to the + * compositing side with the create message (send from CreateBuffer) which + * tells the compositor that TextureClients have been created and that the + * compositor should assign the corresponding TextureHosts to our corresponding + * ContentHost. + * + * If the size or type of our buffer(s) change(s), then we simply destroy and + * create them. + */ +class ContentClientRemoteBuffer : public ContentClient { + public: + explicit ContentClientRemoteBuffer(CompositableForwarder* aForwarder) + : ContentClient(aForwarder, ContainsVisibleBounds), mIsNewBuffer(false) {} + + void Dump(std::stringstream& aStream, const char* aPrefix = "", + bool aDumpHtml = false, + TextureDumpMode aCompress = TextureDumpMode::Compress) override; + + void EndPaint( + PaintState& aPaintState, + nsTArray* aReadbackUpdates = nullptr) override; + + void Updated(const nsIntRegion& aRegionToDraw, + const nsIntRegion& aVisibleRegion); + + TextureFlags ExtraTextureFlags() const { + return TextureFlags::IMMEDIATE_UPLOAD; + } + + protected: + /** + * Called when we have been updated and should swap references to our + * buffers. + */ + virtual void SwapBuffers(const nsIntRegion& aFrontUpdatedRegion) {} + + virtual nsIntRegion GetUpdatedRegion(const nsIntRegion& aRegionToDraw, + const nsIntRegion& aVisibleRegion); + + RefPtr CreateBuffer(gfxContentType aType, + const gfx::IntRect& aRect, + uint32_t aFlags) override; + + RefPtr CreateBufferInternal(const gfx::IntRect& aRect, + gfx::SurfaceFormat aFormat, + TextureFlags aFlags); + + RemoteRotatedBuffer* GetRemoteBuffer() const { + return static_cast(mBuffer.get()); + } + + bool mIsNewBuffer; +}; + +/** + * A double buffered ContentClientRemoteBuffer. mBuffer is the back buffer, + * which we draw into. mFrontBuffer is the front buffer which we may read from, + * but not write to, when the compositor does not have the 'soft' lock. + * + * The ContentHost keeps a reference to both corresponding texture hosts, in + * response to our UpdateTextureRegion message, the compositor swaps its + * references. + */ +class ContentClientDoubleBuffered : public ContentClientRemoteBuffer { + public: + explicit ContentClientDoubleBuffered(CompositableForwarder* aFwd) + : ContentClientRemoteBuffer(aFwd), mFrontAndBackBufferDiffer(false) {} + + void Dump(std::stringstream& aStream, const char* aPrefix = "", + bool aDumpHtml = false, + TextureDumpMode aCompress = TextureDumpMode::Compress) override; + + void Clear() override; + + void SwapBuffers(const nsIntRegion& aFrontUpdatedRegion) override; + + PaintState BeginPaint(PaintedLayer* aLayer, uint32_t aFlags) override; + + void FinalizeFrame(PaintState& aPaintState) override; + + RefPtr GetFrontBuffer() const override { return mFrontBuffer; } + + TextureInfo GetTextureInfo() const override { + return TextureInfo(CompositableType::CONTENT_DOUBLE, mTextureFlags); + } + + private: + void EnsureBackBufferIfFrontBuffer(); + + RefPtr mFrontBuffer; + nsIntRegion mFrontUpdatedRegion; + bool mFrontAndBackBufferDiffer; +}; + +/** + * A single buffered ContentClientRemoteBuffer. We have a single + * TextureClient/Host which we update and then send a message to the + * compositor that we are done updating. It is not safe for the compositor + * to use the corresponding TextureHost's memory directly, it must upload + * it to video memory of some kind. We are free to modify the TextureClient + * once we receive reply from the compositor. + */ +class ContentClientSingleBuffered : public ContentClientRemoteBuffer { + public: + explicit ContentClientSingleBuffered(CompositableForwarder* aFwd) + : ContentClientRemoteBuffer(aFwd) {} + + TextureInfo GetTextureInfo() const override { + return TextureInfo(CompositableType::CONTENT_SINGLE, + mTextureFlags | ExtraTextureFlags()); + } +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/client/GPUVideoTextureClient.cpp b/gfx/layers/client/GPUVideoTextureClient.cpp new file mode 100644 index 0000000000..8a8f6f6e69 --- /dev/null +++ b/gfx/layers/client/GPUVideoTextureClient.cpp @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GPUVideoTextureClient.h" +#include "GPUVideoImage.h" +#include "mozilla/gfx/2D.h" + +namespace mozilla { +namespace layers { + +using namespace gfx; + +GPUVideoTextureData::GPUVideoTextureData(IGPUVideoSurfaceManager* aManager, + const SurfaceDescriptorGPUVideo& aSD, + const gfx::IntSize& aSize) + : mManager(aManager), mSD(aSD), mSize(aSize) {} + +GPUVideoTextureData::~GPUVideoTextureData() = default; + +bool GPUVideoTextureData::Serialize(SurfaceDescriptor& aOutDescriptor) { + aOutDescriptor = mSD; + return true; +} + +void GPUVideoTextureData::FillInfo(TextureData::Info& aInfo) const { + aInfo.size = mSize; + // TODO: We should probably try do better for this. + // layers::Image doesn't expose a format, so it's hard + // to figure out in VideoDecoderParent. + aInfo.format = SurfaceFormat::B8G8R8X8; + aInfo.hasIntermediateBuffer = false; + aInfo.hasSynchronization = false; + aInfo.supportsMoz2D = false; + aInfo.canExposeMappedData = false; +} + +already_AddRefed GPUVideoTextureData::GetAsSourceSurface() { + return mManager->Readback(mSD); +} + +void GPUVideoTextureData::Deallocate(LayersIPCChannel* aAllocator) { + mManager->DeallocateSurfaceDescriptor(mSD); + mSD = SurfaceDescriptorGPUVideo(); +} + +void GPUVideoTextureData::Forget(LayersIPCChannel* aAllocator) { + // We always need to manually deallocate on the client side. + // Ideally we'd set up our TextureClient with the DEALLOCATE_CLIENT + // flag, but that forces texture destruction to be synchronous. + // Instead let's just deallocate from here as well. + Deallocate(aAllocator); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/GPUVideoTextureClient.h b/gfx/layers/client/GPUVideoTextureClient.h new file mode 100644 index 0000000000..93b2e761ca --- /dev/null +++ b/gfx/layers/client/GPUVideoTextureClient.h @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_GPUVIDEOTEXTURECLIENT_H +#define MOZILLA_GFX_GPUVIDEOTEXTURECLIENT_H + +#include "mozilla/layers/TextureClient.h" + +namespace mozilla { +namespace gfx { +class SourceSurface; +} + +namespace layers { +class IGPUVideoSurfaceManager; + +class GPUVideoTextureData : public TextureData { + public: + GPUVideoTextureData(IGPUVideoSurfaceManager* aManager, + const SurfaceDescriptorGPUVideo& aSD, + const gfx::IntSize& aSize); + virtual ~GPUVideoTextureData(); + + void FillInfo(TextureData::Info& aInfo) const override; + + bool Lock(OpenMode) override { return true; }; + + void Unlock() override{}; + + bool Serialize(SurfaceDescriptor& aOutDescriptor) override; + + void Deallocate(LayersIPCChannel* aAllocator) override; + + void Forget(LayersIPCChannel* aAllocator) override; + + already_AddRefed GetAsSourceSurface(); + + GPUVideoTextureData* AsGPUVideoTextureData() override { return this; } + + protected: + RefPtr mManager; + SurfaceDescriptorGPUVideo mSD; + gfx::IntSize mSize; + + public: + const decltype(mSD)& SD() const { return mSD; } +}; + +} // namespace layers +} // namespace mozilla + +#endif // MOZILLA_GFX_GPUVIDEOTEXTURECLIENT_H diff --git a/gfx/layers/client/ImageClient.cpp b/gfx/layers/client/ImageClient.cpp new file mode 100644 index 0000000000..2aed939b9d --- /dev/null +++ b/gfx/layers/client/ImageClient.cpp @@ -0,0 +1,306 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ImageClient.h" + +#include // for uint32_t + +#include "ClientLayerManager.h" // for ClientLayer +#include "ImageContainer.h" // for Image, PlanarYCbCrImage, etc +#include "ImageTypes.h" // for ImageFormat::PLANAR_YCBCR, etc +#include "GLImages.h" // for SurfaceTextureImage::Data, etc +#include "gfx2DGlue.h" // for ImageFormatToSurfaceFormat +#include "gfxPlatform.h" // for gfxPlatform +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/RefPtr.h" // for RefPtr, already_AddRefed +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/BaseSize.h" // for BaseSize +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/gfx/Types.h" // for SurfaceFormat, etc +#include "mozilla/layers/CompositableClient.h" // for CompositableClient +#include "mozilla/layers/CompositableForwarder.h" +#include "mozilla/layers/CompositorTypes.h" // for CompositableType, etc +#include "mozilla/layers/ISurfaceAllocator.h" +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor, etc +#include "mozilla/layers/ShadowLayers.h" // for ShadowLayerForwarder +#include "mozilla/layers/TextureClient.h" // for TextureClient, etc +#include "mozilla/layers/TextureClientOGL.h" // for SurfaceTextureClient +#include "mozilla/mozalloc.h" // for operator delete, etc +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsDebug.h" // for NS_WARNING, NS_ASSERTION +#include "nsISupportsImpl.h" // for Image::Release, etc +#include "nsRect.h" // for mozilla::gfx::IntRect + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +/* static */ +already_AddRefed ImageClient::CreateImageClient( + CompositableType aCompositableHostType, CompositableForwarder* aForwarder, + TextureFlags aFlags) { + RefPtr result = nullptr; + switch (aCompositableHostType) { + case CompositableType::IMAGE: + result = + new ImageClientSingle(aForwarder, aFlags, CompositableType::IMAGE); + break; + case CompositableType::IMAGE_BRIDGE: + result = new ImageClientBridge(aForwarder, aFlags); + break; + case CompositableType::UNKNOWN: + result = nullptr; + break; + default: + MOZ_CRASH("GFX: unhandled program type image"); + } + + NS_ASSERTION(result, "Failed to create ImageClient"); + + return result.forget(); +} + +void ImageClient::RemoveTexture(TextureClient* aTexture) { + GetForwarder()->RemoveTextureFromCompositable(this, aTexture); +} + +ImageClientSingle::ImageClientSingle(CompositableForwarder* aFwd, + TextureFlags aFlags, + CompositableType aType) + : ImageClient(aFwd, aFlags, aType) {} + +TextureInfo ImageClientSingle::GetTextureInfo() const { + return TextureInfo(CompositableType::IMAGE); +} + +void ImageClientSingle::FlushAllImages() { + for (auto& b : mBuffers) { + // It should be safe to just assume a default render root here, even if + // the texture actually presents in a content render root, as the only + // risk would be if the content render root has not / is not going to + // generate a frame before the texture gets cleared. + RemoveTexture(b.mTextureClient); + } + mBuffers.Clear(); +} + +/* static */ +already_AddRefed ImageClient::CreateTextureClientForImage( + Image* aImage, KnowsCompositor* aKnowsCompositor) { + RefPtr texture; + if (aImage->GetFormat() == ImageFormat::PLANAR_YCBCR) { + PlanarYCbCrImage* ycbcr = static_cast(aImage); + const PlanarYCbCrData* data = ycbcr->GetData(); + if (!data) { + return nullptr; + } + texture = TextureClient::CreateForYCbCr( + aKnowsCompositor, data->GetPictureRect(), data->mYSize, data->mYStride, + data->mCbCrSize, data->mCbCrStride, data->mStereoMode, + data->mColorDepth, data->mYUVColorSpace, data->mColorRange, + TextureFlags::DEFAULT); + if (!texture) { + return nullptr; + } + + TextureClientAutoLock autoLock(texture, OpenMode::OPEN_WRITE_ONLY); + if (!autoLock.Succeeded()) { + return nullptr; + } + + bool status = UpdateYCbCrTextureClient(texture, *data); + MOZ_ASSERT(status); + if (!status) { + return nullptr; + } +#ifdef MOZ_WIDGET_ANDROID + } else if (aImage->GetFormat() == ImageFormat::SURFACE_TEXTURE) { + gfx::IntSize size = aImage->GetSize(); + SurfaceTextureImage* typedImage = aImage->AsSurfaceTextureImage(); + texture = AndroidSurfaceTextureData::CreateTextureClient( + typedImage->GetHandle(), size, typedImage->GetContinuous(), + typedImage->GetOriginPos(), typedImage->GetHasAlpha(), + aKnowsCompositor->GetTextureForwarder(), TextureFlags::DEFAULT); +#endif + } else { + RefPtr surface = aImage->GetAsSourceSurface(); + MOZ_ASSERT(surface); + texture = TextureClient::CreateForDrawing( + aKnowsCompositor, surface->GetFormat(), aImage->GetSize(), + BackendSelector::Content, TextureFlags::DEFAULT); + if (!texture) { + return nullptr; + } + + MOZ_ASSERT(texture->CanExposeDrawTarget()); + + if (!texture->Lock(OpenMode::OPEN_WRITE_ONLY)) { + return nullptr; + } + + { + // We must not keep a reference to the DrawTarget after it has been + // unlocked. + DrawTarget* dt = texture->BorrowDrawTarget(); + if (!dt) { + gfxWarning() + << "ImageClientSingle::UpdateImage failed in BorrowDrawTarget"; + return nullptr; + } + MOZ_ASSERT(surface.get()); + dt->CopySurface(surface, IntRect(IntPoint(), surface->GetSize()), + IntPoint()); + } + + texture->Unlock(); + } + return texture.forget(); +} + +bool ImageClientSingle::UpdateImage(ImageContainer* aContainer, + uint32_t aContentFlags) { + AutoTArray images; + uint32_t generationCounter; + aContainer->GetCurrentImages(&images, &generationCounter); + + if (mLastUpdateGenerationCounter == generationCounter) { + return true; + } + mLastUpdateGenerationCounter = generationCounter; + + // Don't try to update to invalid images. + images.RemoveElementsBy( + [](const auto& image) { return !image.mImage->IsValid(); }); + if (images.IsEmpty()) { + // This can happen if a ClearAllImages raced with SetCurrentImages from + // another thread and ClearImagesFromImageBridge ran after the + // SetCurrentImages call but before UpdateImageClientNow. + // This can also happen if all images in the list are invalid. + // We return true because the caller would attempt to recreate the + // ImageClient otherwise, and that isn't going to help. + for (auto& b : mBuffers) { + RemoveTexture(b.mTextureClient); + } + mBuffers.Clear(); + return true; + } + + nsTArray newBuffers; + AutoTArray textures; + + for (auto& img : images) { + Image* image = img.mImage; + + RefPtr texture = image->GetTextureClient(GetForwarder()); + const bool hasTextureClient = !!texture; + + for (int32_t i = mBuffers.Length() - 1; i >= 0; --i) { + if (mBuffers[i].mImageSerial == image->GetSerial()) { + if (hasTextureClient) { + MOZ_ASSERT(image->GetTextureClient(GetForwarder()) == + mBuffers[i].mTextureClient); + } else { + texture = mBuffers[i].mTextureClient; + } + // Remove this element from mBuffers so mBuffers only contains + // images that aren't present in 'images' + mBuffers.RemoveElementAt(i); + } + } + + if (!texture) { + // Slow path, we should not be hitting it very often and if we do it means + // we are using an Image class that is not backed by textureClient and we + // should fix it. + texture = CreateTextureClientForImage(image, GetForwarder()); + } + + if (!texture) { + return false; + } + + // We check if the texture's allocator is still open, since in between media + // decoding a frame and adding it to the compositable, we could have + // restarted the GPU process. + if (!texture->GetAllocator()->IPCOpen()) { + continue; + } + if (!AddTextureClient(texture)) { + return false; + } + + CompositableForwarder::TimedTextureClient* t = textures.AppendElement(); + t->mTextureClient = texture; + t->mTimeStamp = img.mTimeStamp; + t->mPictureRect = image->GetPictureRect(); + t->mFrameID = img.mFrameID; + t->mProducerID = img.mProducerID; + + Buffer* newBuf = newBuffers.AppendElement(); + newBuf->mImageSerial = image->GetSerial(); + newBuf->mTextureClient = texture; + + texture->SyncWithObject(GetForwarder()->GetSyncObject()); + } + + GetForwarder()->UseTextures(this, textures); + + for (auto& b : mBuffers) { + RemoveTexture(b.mTextureClient); + } + mBuffers = std::move(newBuffers); + + return true; +} + +RefPtr ImageClientSingle::GetForwardedTexture() { + if (mBuffers.Length() == 0) { + return nullptr; + } + return mBuffers[0].mTextureClient; +} + +bool ImageClientSingle::AddTextureClient(TextureClient* aTexture) { + MOZ_ASSERT((mTextureFlags & aTexture->GetFlags()) == mTextureFlags); + return CompositableClient::AddTextureClient(aTexture); +} + +void ImageClientSingle::OnDetach() { mBuffers.Clear(); } + +ImageClient::ImageClient(CompositableForwarder* aFwd, TextureFlags aFlags, + CompositableType aType) + : CompositableClient(aFwd, aFlags), + mLayer(nullptr), + mType(aType), + mLastUpdateGenerationCounter(0) {} + +ImageClientBridge::ImageClientBridge(CompositableForwarder* aFwd, + TextureFlags aFlags) + : ImageClient(aFwd, aFlags, CompositableType::IMAGE_BRIDGE) {} + +bool ImageClientBridge::UpdateImage(ImageContainer* aContainer, + uint32_t aContentFlags) { + if (!GetForwarder() || !mLayer) { + return false; + } + if (mAsyncContainerHandle == aContainer->GetAsyncContainerHandle()) { + return true; + } + + mAsyncContainerHandle = aContainer->GetAsyncContainerHandle(); + if (!mAsyncContainerHandle) { + // If we couldn't contact a working ImageBridgeParent, just return. + return true; + } + + static_cast(GetForwarder()) + ->AttachAsyncCompositable(mAsyncContainerHandle, mLayer); + return true; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/ImageClient.h b/gfx/layers/client/ImageClient.h new file mode 100644 index 0000000000..0e699179c8 --- /dev/null +++ b/gfx/layers/client/ImageClient.h @@ -0,0 +1,143 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_IMAGECLIENT_H +#define MOZILLA_GFX_IMAGECLIENT_H + +#include // for uint32_t, uint64_t +#include // for int32_t +#include "mozilla/Attributes.h" // for override +#include "mozilla/RefPtr.h" // for RefPtr, already_AddRefed +#include "mozilla/gfx/Types.h" // for SurfaceFormat +#include "mozilla/layers/CompositableClient.h" // for CompositableClient +#include "mozilla/layers/CompositorTypes.h" // for CompositableType, etc +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor +#include "mozilla/layers/TextureClient.h" // for TextureClient, etc +#include "mozilla/mozalloc.h" // for operator delete +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsRect.h" // for mozilla::gfx::IntRect + +namespace mozilla { +namespace layers { + +class ClientLayer; +class CompositableForwarder; +class Image; +class ImageContainer; +class ShadowableLayer; +class ImageClientSingle; + +/** + * Image clients are used by basic image layers on the content thread, they + * always match with an ImageHost on the compositor thread. See + * CompositableClient.h for information on connecting clients to hosts. + */ +class ImageClient : public CompositableClient { + public: + /** + * Creates, configures, and returns a new image client. If necessary, a + * message will be sent to the compositor to create a corresponding image + * host. + */ + static already_AddRefed CreateImageClient( + CompositableType aImageHostType, CompositableForwarder* aFwd, + TextureFlags aFlags); + + virtual ~ImageClient() = default; + + /** + * Update this ImageClient from aContainer in aLayer + * returns false if this is the wrong kind of ImageClient for aContainer. + * Note that returning true does not necessarily imply success + */ + virtual bool UpdateImage(ImageContainer* aContainer, + uint32_t aContentFlags) = 0; + + void SetLayer(ClientLayer* aLayer) { mLayer = aLayer; } + ClientLayer* GetLayer() const { return mLayer; } + + /** + * asynchronously remove all the textures used by the image client. + * + */ + virtual void FlushAllImages() {} + + virtual void RemoveTexture(TextureClient* aTexture) override; + + virtual ImageClientSingle* AsImageClientSingle() { return nullptr; } + + static already_AddRefed CreateTextureClientForImage( + Image* aImage, KnowsCompositor* aForwarder); + + uint32_t GetLastUpdateGenerationCounter() { + return mLastUpdateGenerationCounter; + } + + virtual RefPtr GetForwardedTexture() { return nullptr; } + + protected: + ImageClient(CompositableForwarder* aFwd, TextureFlags aFlags, + CompositableType aType); + + ClientLayer* mLayer; + CompositableType mType; + uint32_t mLastUpdateGenerationCounter; +}; + +/** + * An image client which uses a single texture client. + */ +class ImageClientSingle : public ImageClient { + public: + ImageClientSingle(CompositableForwarder* aFwd, TextureFlags aFlags, + CompositableType aType); + + bool UpdateImage(ImageContainer* aContainer, uint32_t aContentFlag) override; + + void OnDetach() override; + + bool AddTextureClient(TextureClient* aTexture) override; + + TextureInfo GetTextureInfo() const override; + + void FlushAllImages() override; + + ImageClientSingle* AsImageClientSingle() override { return this; } + + RefPtr GetForwardedTexture() override; + + bool IsEmpty() { return mBuffers.IsEmpty(); } + + protected: + struct Buffer { + RefPtr mTextureClient; + int32_t mImageSerial; + }; + nsTArray mBuffers; +}; + +/** + * Image class to be used for async image uploads using the image bridge + * protocol. + * We store the ImageBridge id in the TextureClientIdentifier. + */ +class ImageClientBridge : public ImageClient { + public: + ImageClientBridge(CompositableForwarder* aFwd, TextureFlags aFlags); + + bool UpdateImage(ImageContainer* aContainer, uint32_t aContentFlags) override; + bool Connect(ImageContainer* aImageContainer) override { return false; } + + TextureInfo GetTextureInfo() const override { return TextureInfo(mType); } + + protected: + CompositableHandle mAsyncContainerHandle; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/client/MultiTiledContentClient.cpp b/gfx/layers/client/MultiTiledContentClient.cpp new file mode 100644 index 0000000000..2ab5c83e39 --- /dev/null +++ b/gfx/layers/client/MultiTiledContentClient.cpp @@ -0,0 +1,685 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/MultiTiledContentClient.h" + +#include "ClientTiledPaintedLayer.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/layers/APZUtils.h" +#include "mozilla/layers/LayerMetricsWrapper.h" + +namespace mozilla { + +using namespace gfx; + +namespace layers { + +MultiTiledContentClient::MultiTiledContentClient( + ClientTiledPaintedLayer& aPaintedLayer, ClientLayerManager* aManager) + : TiledContentClient(aManager, "Multi"), + mTiledBuffer(aPaintedLayer, *this, aManager, &mSharedFrameMetricsHelper), + mLowPrecisionTiledBuffer(aPaintedLayer, *this, aManager, + &mSharedFrameMetricsHelper) { + MOZ_COUNT_CTOR(MultiTiledContentClient); + mLowPrecisionTiledBuffer.SetResolution( + StaticPrefs::layers_low_precision_resolution()); + mHasLowPrecision = StaticPrefs::layers_low_precision_buffer(); +} + +void MultiTiledContentClient::ClearCachedResources() { + CompositableClient::ClearCachedResources(); + mTiledBuffer.DiscardBuffers(); + mLowPrecisionTiledBuffer.DiscardBuffers(); +} + +void MultiTiledContentClient::UpdatedBuffer(TiledBufferType aType) { + ClientMultiTiledLayerBuffer* buffer = aType == LOW_PRECISION_TILED_BUFFER + ? &mLowPrecisionTiledBuffer + : &mTiledBuffer; + + MOZ_ASSERT(aType != LOW_PRECISION_TILED_BUFFER || mHasLowPrecision); + + mForwarder->UseTiledLayerBuffer(this, buffer->GetSurfaceDescriptorTiles()); +} + +ClientMultiTiledLayerBuffer::ClientMultiTiledLayerBuffer( + ClientTiledPaintedLayer& aPaintedLayer, + CompositableClient& aCompositableClient, ClientLayerManager* aManager, + SharedFrameMetricsHelper* aHelper) + : ClientTiledLayerBuffer(aPaintedLayer, aCompositableClient), + mManager(aManager), + mCallback(nullptr), + mCallbackData(nullptr), + mSharedFrameMetricsHelper(aHelper) {} + +void ClientMultiTiledLayerBuffer::DiscardBuffers() { + for (TileClient& tile : mRetainedTiles) { + tile.DiscardBuffers(); + } +} + +SurfaceDescriptorTiles +ClientMultiTiledLayerBuffer::GetSurfaceDescriptorTiles() { + nsTArray tiles; + + for (TileClient& tile : mRetainedTiles) { + TileDescriptor tileDesc = tile.GetTileDescriptor(); + tiles.AppendElement(tileDesc); + // Reset the update rect + tile.mUpdateRect = IntRect(); + } + return SurfaceDescriptorTiles( + mValidRegion, tiles, mTileOrigin, mTileSize, mTiles.mFirst.x, + mTiles.mFirst.y, mTiles.mSize.width, mTiles.mSize.height, mResolution, + mFrameResolution.xScale, mFrameResolution.yScale, + mWasLastPaintProgressive); +} + +void ClientMultiTiledLayerBuffer::PaintThebes( + const nsIntRegion& aNewValidRegion, const nsIntRegion& aPaintRegion, + const nsIntRegion& aDirtyRegion, + LayerManager::DrawPaintedLayerCallback aCallback, void* aCallbackData, + TilePaintFlags aFlags) { + TILING_LOG("TILING %p: PaintThebes painting region %s\n", &mPaintedLayer, + Stringify(aPaintRegion).c_str()); + TILING_LOG("TILING %p: PaintThebes new valid region %s\n", &mPaintedLayer, + Stringify(aNewValidRegion).c_str()); + + mCallback = aCallback; + mCallbackData = aCallbackData; + mWasLastPaintProgressive = !!(aFlags & TilePaintFlags::Progressive); + +#ifdef GFX_TILEDLAYER_PREF_WARNINGS + long start = PR_IntervalNow(); +#endif + +#ifdef GFX_TILEDLAYER_PREF_WARNINGS + if (PR_IntervalNow() - start > 30) { + const IntRect bounds = aPaintRegion.GetBounds(); + printf_stderr("Time to draw %i: %i, %i, %i, %i\n", PR_IntervalNow() - start, + bounds.x, bounds.y, bounds.width, bounds.height); + if (aPaintRegion.IsComplex()) { + printf_stderr("Complex region\n"); + for (auto iter = aPaintRegion.RectIter(); !iter.Done(); iter.Next()) { + const IntRect& rect = iter.Get(); + printf_stderr(" rect %i, %i, %i, %i\n", rect.x, rect.y, rect.width, + rect.height); + } + } + } + start = PR_IntervalNow(); +#endif + + AUTO_PROFILER_LABEL("ClientMultiTiledLayerBuffer::PaintThebes", GRAPHICS); + + mNewValidRegion = aNewValidRegion; + Update(aNewValidRegion, aPaintRegion, aDirtyRegion, aFlags); + +#ifdef GFX_TILEDLAYER_PREF_WARNINGS + if (PR_IntervalNow() - start > 10) { + const IntRect bounds = aPaintRegion.GetBounds(); + printf_stderr("Time to tile %i: %i, %i, %i, %i\n", PR_IntervalNow() - start, + bounds.x, bounds.y, bounds.width, bounds.height); + } +#endif + + mLastPaintContentType = GetContentType(&mLastPaintSurfaceMode); + mCallback = nullptr; + mCallbackData = nullptr; +} + +void ClientMultiTiledLayerBuffer::MaybeSyncTextures( + const nsIntRegion& aPaintRegion, const TilesPlacement& aNewTiles, + const IntSize& aScaledTileSize) { + if (mManager->AsShadowForwarder()->SupportsTextureDirectMapping()) { + AutoTArray syncTextureSerials; + SurfaceMode mode; + Unused << GetContentType(&mode); + + // Pre-pass through the tiles (mirroring the filter logic below) to gather + // texture IDs that we need to ensure are unused by the GPU before we + // continue. + if (!aPaintRegion.IsEmpty()) { + MOZ_ASSERT(mPaintTasks.IsEmpty()); + for (size_t i = 0; i < mRetainedTiles.Length(); ++i) { + const TileCoordIntPoint tileCoord = aNewTiles.TileCoord(i); + + IntPoint tileOffset = GetTileOffset(tileCoord); + nsIntRegion tileDrawRegion = IntRect(tileOffset, aScaledTileSize); + tileDrawRegion.AndWith(aPaintRegion); + + if (tileDrawRegion.IsEmpty()) { + continue; + } + + TileClient& tile = mRetainedTiles[i]; + tile.GetSyncTextureSerials(mode, syncTextureSerials); + } + } + + if (syncTextureSerials.Length() > 0) { + mManager->AsShadowForwarder()->SyncTextures(syncTextureSerials); + } + } +} + +void ClientMultiTiledLayerBuffer::Update(const nsIntRegion& newValidRegion, + const nsIntRegion& aPaintRegion, + const nsIntRegion& aDirtyRegion, + TilePaintFlags aFlags) { + const IntSize scaledTileSize = GetScaledTileSize(); + const gfx::IntRect newBounds = newValidRegion.GetBounds(); + + const TilesPlacement oldTiles = mTiles; + const TilesPlacement newTiles( + floor_div(newBounds.X(), scaledTileSize.width), + floor_div(newBounds.Y(), scaledTileSize.height), + floor_div( + GetTileStart(newBounds.X(), scaledTileSize.width) + newBounds.Width(), + scaledTileSize.width) + + 1, + floor_div(GetTileStart(newBounds.Y(), scaledTileSize.height) + + newBounds.Height(), + scaledTileSize.height) + + 1); + + const size_t oldTileCount = mRetainedTiles.Length(); + const size_t newTileCount = newTiles.mSize.width * newTiles.mSize.height; + + nsTArray oldRetainedTiles = std::move(mRetainedTiles); + mRetainedTiles.SetLength(newTileCount); + + for (size_t oldIndex = 0; oldIndex < oldTileCount; oldIndex++) { + const TileCoordIntPoint tileCoord = oldTiles.TileCoord(oldIndex); + const size_t newIndex = newTiles.TileIndex(tileCoord); + // First, get the already existing tiles to the right place in the new + // array. Leave placeholders (default constructor) where there was no tile. + if (newTiles.HasTile(tileCoord)) { + mRetainedTiles[newIndex] = oldRetainedTiles[oldIndex]; + } else { + // release tiles that we are not going to reuse before allocating new ones + // to avoid allocating unnecessarily. + oldRetainedTiles[oldIndex].DiscardBuffers(); + } + } + + oldRetainedTiles.Clear(); + + nsIntRegion paintRegion = aPaintRegion; + nsIntRegion dirtyRegion = aDirtyRegion; + + MaybeSyncTextures(paintRegion, newTiles, scaledTileSize); + + if (!paintRegion.IsEmpty()) { + MOZ_ASSERT(mPaintTasks.IsEmpty()); + + for (size_t i = 0; i < newTileCount; ++i) { + const TileCoordIntPoint tileCoord = newTiles.TileCoord(i); + + IntPoint tileOffset = GetTileOffset(tileCoord); + nsIntRegion tileDrawRegion = IntRect(tileOffset, scaledTileSize); + tileDrawRegion.AndWith(paintRegion); + + if (tileDrawRegion.IsEmpty()) { + continue; + } + + TileClient& tile = mRetainedTiles[i]; + if (!ValidateTile(tile, GetTileOffset(tileCoord), tileDrawRegion, + aFlags)) { + gfxCriticalError() << "ValidateTile failed"; + } + + // Validating the tile may have required more to be painted. + paintRegion.OrWith(tileDrawRegion); + dirtyRegion.OrWith(tileDrawRegion); + } + + if (!mPaintTiles.IsEmpty()) { + // Create a tiled draw target + gfx::TileSet tileset; + for (size_t i = 0; i < mPaintTiles.Length(); ++i) { + mPaintTiles[i].mTileOrigin -= mTilingOrigin; + } + tileset.mTiles = mPaintTiles.Elements(); + tileset.mTileCount = mPaintTiles.Length(); + RefPtr drawTarget = + gfx::Factory::CreateTiledDrawTarget(tileset); + if (!drawTarget || !drawTarget->IsValid()) { + gfxDevCrash(LogReason::InvalidContext) << "Invalid tiled draw target"; + return; + } + drawTarget->SetTransform(Matrix()); + + // Draw into the tiled draw target + RefPtr ctx = gfxContext::CreateOrNull(drawTarget); + MOZ_ASSERT(ctx); // already checked the draw target above + ctx->SetMatrix(ctx->CurrentMatrix() + .PreScale(mResolution, mResolution) + .PreTranslate(-mTilingOrigin)); + + mCallback(&mPaintedLayer, ctx, paintRegion, dirtyRegion, + DrawRegionClip::DRAW, nsIntRegion(), mCallbackData); + ctx = nullptr; + + // Edge padding allows us to avoid resampling artifacts + if (StaticPrefs::layers_tiles_edge_padding_AtStartup() && + mResolution == 1) { + drawTarget->PadEdges(newValidRegion.MovedBy(-mTilingOrigin)); + } + + // Reset + mPaintTiles.Clear(); + mTilingOrigin = IntPoint(std::numeric_limits::max(), + std::numeric_limits::max()); + } + + // Dispatch to the paint thread + if (aFlags & TilePaintFlags::Async) { + bool queuedTask = false; + + while (!mPaintTasks.IsEmpty()) { + UniquePtr task = mPaintTasks.PopLastElement(); + if (!task->mCapture->IsEmpty()) { + PaintThread::Get()->QueuePaintTask(std::move(task)); + queuedTask = true; + } + } + + if (queuedTask) { + mManager->SetQueuedAsyncPaints(); + } + + mPaintTasks.Clear(); + } + + for (uint32_t i = 0; i < mRetainedTiles.Length(); ++i) { + TileClient& tile = mRetainedTiles[i]; + UnlockTile(tile); + } + } + + mTiles = newTiles; + mValidRegion = newValidRegion; +} + +bool ClientMultiTiledLayerBuffer::ValidateTile(TileClient& aTile, + const nsIntPoint& aTileOrigin, + nsIntRegion& aDirtyRegion, + TilePaintFlags aFlags) { +#ifdef GFX_TILEDLAYER_PREF_WARNINGS + if (aDirtyRegion.IsComplex()) { + printf_stderr("Complex region\n"); + } +#endif + + SurfaceMode mode; + gfxContentType content = GetContentType(&mode); + + if (!aTile.mAllocator) { + aTile.SetTextureAllocator( + mManager->GetCompositorBridgeChild()->GetTexturePool( + mManager->AsShadowForwarder(), + gfxPlatform::GetPlatform()->Optimal2DFormatForContent(content), + TextureFlags::DISALLOW_BIGIMAGE | TextureFlags::IMMEDIATE_UPLOAD | + TextureFlags::NON_BLOCKING_READ_LOCK)); + MOZ_ASSERT(aTile.mAllocator); + } + + nsIntRegion tileDirtyRegion = aDirtyRegion.MovedBy(-aTileOrigin); + tileDirtyRegion.ScaleRoundOut(mResolution, mResolution); + + nsIntRegion tileVisibleRegion = mNewValidRegion.MovedBy(-aTileOrigin); + tileVisibleRegion.ScaleRoundOut(mResolution, mResolution); + + std::vector> asyncPaintClients; + + Maybe backBuffer = + aTile.AcquireBackBuffer(mCompositableClient, tileDirtyRegion, + tileVisibleRegion, content, mode, aFlags); + + if (!backBuffer) { + return false; + } + + // Mark the area we need to paint in the back buffer as invalid in the + // front buffer as they will become out of sync. + aTile.mInvalidFront.OrWith(tileDirtyRegion); + + // Add the backbuffer's invalid region intersected with the visible region to + // the dirty region we will be painting. This will be empty if we are able to + // copy from the front into the back. + nsIntRegion tileInvalidRegion = aTile.mInvalidBack; + tileInvalidRegion.AndWith(tileVisibleRegion); + + nsIntRegion invalidRegion = tileInvalidRegion; + invalidRegion.MoveBy(aTileOrigin); + invalidRegion.ScaleInverseRoundOut(mResolution, mResolution); + + tileDirtyRegion.OrWith(tileInvalidRegion); + aDirtyRegion.OrWith(invalidRegion); + + // Mark the region we will be painting and the region we copied from the front + // buffer as needing to be uploaded to the compositor + aTile.mUpdateRect = + tileDirtyRegion.GetBounds().Union(backBuffer->mUpdatedRect); + + // We need to clear the dirty region of the tile before painting + // if we are painting non-opaque content + if (mode != SurfaceMode::SURFACE_OPAQUE) { + for (auto iter = tileDirtyRegion.RectIter(); !iter.Done(); iter.Next()) { + const gfx::Rect drawRect(iter.Get().X(), iter.Get().Y(), + iter.Get().Width(), iter.Get().Height()); + backBuffer->mTarget->ClearRect(drawRect); + } + } + + gfx::Tile paintTile; + paintTile.mTileOrigin = gfx::IntPoint(aTileOrigin.x, aTileOrigin.y); + paintTile.mDrawTarget = backBuffer->mTarget; + mPaintTiles.AppendElement(paintTile); + + if (aFlags & TilePaintFlags::Async) { + UniquePtr task(new PaintTask()); + task->mCapture = backBuffer->mCapture; + task->mTarget = backBuffer->mBackBuffer; + task->mClients = std::move(backBuffer->mTextureClients); + mPaintTasks.AppendElement(std::move(task)); + } else { + MOZ_RELEASE_ASSERT(backBuffer->mTarget == backBuffer->mBackBuffer); + MOZ_RELEASE_ASSERT(backBuffer->mCapture == nullptr); + } + + mTilingOrigin.x = std::min(mTilingOrigin.x, paintTile.mTileOrigin.x); + mTilingOrigin.y = std::min(mTilingOrigin.y, paintTile.mTileOrigin.y); + + // The new buffer is now validated, remove the dirty region from it. + aTile.mInvalidBack.SubOut(tileDirtyRegion); + + aTile.Flip(); + + return true; +} + +/** + * This function takes the transform stored in aTransformToCompBounds + * (which was generated in GetTransformToAncestorsParentLayer), and + * modifies it with the ViewTransform from the compositor side so that + * it reflects what the compositor is actually rendering. This operation + * basically adds in the layer's async transform. + * This function then returns the scroll ancestor's composition bounds, + * transformed into the painted layer's LayerPixel coordinates, accounting + * for the compositor state. + */ +static Maybe GetCompositorSideCompositionBounds( + const LayerMetricsWrapper& aScrollAncestor, + const LayerToParentLayerMatrix4x4& aTransformToCompBounds, + const AsyncTransform& aAPZTransform, const LayerRect& aClip) { + LayerToParentLayerMatrix4x4 transform = + aTransformToCompBounds * AsyncTransformComponentMatrix(aAPZTransform); + + return UntransformBy(transform.Inverse(), + aScrollAncestor.Metrics().GetCompositionBounds(), aClip); +} + +bool ClientMultiTiledLayerBuffer::ComputeProgressiveUpdateRegion( + const nsIntRegion& aInvalidRegion, const nsIntRegion& aOldValidRegion, + nsIntRegion& aRegionToPaint, BasicTiledLayerPaintData* aPaintData, + bool aIsRepeated) { + aRegionToPaint = aInvalidRegion; + + // If the composition bounds rect is empty, we can't make any sensible + // decision about how to update coherently. In this case, just update + // everything in one transaction. + if (aPaintData->mCompositionBounds.IsEmpty()) { + aPaintData->mPaintFinished = true; + return false; + } + + // If this is a low precision buffer, we force progressive updates. The + // assumption is that the contents is less important, so visual coherency + // is lower priority than speed. + bool drawingLowPrecision = IsLowPrecision(); + + // Find out if we have any non-stale content to update. + nsIntRegion staleRegion; + staleRegion.And(aInvalidRegion, aOldValidRegion); + + TILING_LOG("TILING %p: Progressive update stale region %s\n", &mPaintedLayer, + Stringify(staleRegion).c_str()); + + LayerMetricsWrapper scrollAncestor; + mPaintedLayer.GetAncestorLayers(&scrollAncestor, nullptr, nullptr); + + // Find out the current view transform to determine which tiles to draw + // first, and see if we should just abort this paint. Aborting is usually + // caused by there being an incoming, more relevant paint. + AsyncTransform viewTransform; + MOZ_ASSERT(mSharedFrameMetricsHelper); + + bool abortPaint = mSharedFrameMetricsHelper->UpdateFromCompositorFrameMetrics( + scrollAncestor, !staleRegion.Contains(aInvalidRegion), + drawingLowPrecision, viewTransform); + + TILING_LOG( + "TILING %p: Progressive update view transform %s zoom %f abort %d\n", + &mPaintedLayer, ToString(viewTransform.mTranslation).c_str(), + viewTransform.mScale.scale, abortPaint); + + if (abortPaint) { + // We ignore if front-end wants to abort if this is the first, + // non-low-precision paint, as in that situation, we're about to override + // front-end's page/viewport metrics. + if (!aPaintData->mFirstPaint || drawingLowPrecision) { + AUTO_PROFILER_LABEL( + "ClientMultiTiledLayerBuffer::ComputeProgressiveUpdateRegion", + GRAPHICS); + + aRegionToPaint.SetEmpty(); + return aIsRepeated; + } + } + + Maybe transformedCompositionBounds = + GetCompositorSideCompositionBounds( + scrollAncestor, aPaintData->mTransformToCompBounds, viewTransform, + LayerRect(mPaintedLayer.GetVisibleRegion().GetBounds())); + + if (!transformedCompositionBounds) { + aPaintData->mPaintFinished = true; + return false; + } + + TILING_LOG("TILING %p: Progressive update transformed compositor bounds %s\n", + &mPaintedLayer, Stringify(*transformedCompositionBounds).c_str()); + + // Compute a "coherent update rect" that we should paint all at once in a + // single transaction. This is to avoid rendering glitches on animated + // page content, and when layers change size/shape. + // On Fennec uploads are more expensive because we're not using gralloc, so + // we use a coherent update rect that is intersected with the screen at the + // time of issuing the draw command. This will paint faster but also + // potentially make the progressive paint more visible to the user while + // scrolling. + IntRect coherentUpdateRect(RoundedOut( +#ifdef MOZ_WIDGET_ANDROID + transformedCompositionBounds->Intersect( + aPaintData->mCompositionBounds) +#else + *transformedCompositionBounds +#endif + ) + .ToUnknownRect()); + + TILING_LOG("TILING %p: Progressive update final coherency rect %s\n", + &mPaintedLayer, Stringify(coherentUpdateRect).c_str()); + + aRegionToPaint.And(aInvalidRegion, coherentUpdateRect); + aRegionToPaint.Or(aRegionToPaint, staleRegion); + bool drawingStale = !aRegionToPaint.IsEmpty(); + if (!drawingStale) { + aRegionToPaint = aInvalidRegion; + } + + // Prioritise tiles that are currently visible on the screen. + bool paintingVisible = false; + if (aRegionToPaint.Intersects(coherentUpdateRect)) { + aRegionToPaint.And(aRegionToPaint, coherentUpdateRect); + paintingVisible = true; + } + + TILING_LOG("TILING %p: Progressive update final paint region %s\n", + &mPaintedLayer, Stringify(aRegionToPaint).c_str()); + + // Paint area that's visible and overlaps previously valid content to avoid + // visible glitches in animated elements, such as gifs. + bool paintInSingleTransaction = + paintingVisible && (drawingStale || aPaintData->mFirstPaint); + + TILING_LOG( + "TILING %p: paintingVisible %d drawingStale %d firstPaint %d " + "singleTransaction %d\n", + &mPaintedLayer, paintingVisible, drawingStale, aPaintData->mFirstPaint, + paintInSingleTransaction); + + // The following code decides what order to draw tiles in, based on the + // current scroll direction of the primary scrollable layer. + NS_ASSERTION(!aRegionToPaint.IsEmpty(), "Unexpectedly empty paint region!"); + IntRect paintBounds = aRegionToPaint.GetBounds(); + + int startX, incX, startY, incY; + gfx::IntSize scaledTileSize = GetScaledTileSize(); + if (aPaintData->mScrollOffset.x >= aPaintData->mLastScrollOffset.x) { + startX = RoundDownToTileEdge(paintBounds.X(), scaledTileSize.width); + incX = scaledTileSize.width; + } else { + startX = RoundDownToTileEdge(paintBounds.XMost() - 1, scaledTileSize.width); + incX = -scaledTileSize.width; + } + + if (aPaintData->mScrollOffset.y >= aPaintData->mLastScrollOffset.y) { + startY = RoundDownToTileEdge(paintBounds.Y(), scaledTileSize.height); + incY = scaledTileSize.height; + } else { + startY = + RoundDownToTileEdge(paintBounds.YMost() - 1, scaledTileSize.height); + incY = -scaledTileSize.height; + } + + // Find a tile to draw. + IntRect tileBounds(startX, startY, scaledTileSize.width, + scaledTileSize.height); + int32_t scrollDiffX = + aPaintData->mScrollOffset.x - aPaintData->mLastScrollOffset.x; + int32_t scrollDiffY = + aPaintData->mScrollOffset.y - aPaintData->mLastScrollOffset.y; + // This loop will always terminate, as there is at least one tile area + // along the first/last row/column intersecting with regionToPaint, or its + // bounds would have been smaller. + while (true) { + aRegionToPaint.And(aInvalidRegion, tileBounds); + if (!aRegionToPaint.IsEmpty()) { + if (mResolution != 1) { + // Paint the entire tile for low-res. This is aimed to fixing low-res + // resampling and to avoid doing costly region accurate painting for a + // small area. + aRegionToPaint = tileBounds; + } + break; + } + if (Abs(scrollDiffY) >= Abs(scrollDiffX)) { + tileBounds.MoveByX(incX); + } else { + tileBounds.MoveByY(incY); + } + } + + if (!aRegionToPaint.Contains(aInvalidRegion)) { + // The region needed to paint is larger then our progressive chunk size + // therefore update what we want to paint and ask for a new paint + // transaction. + + // If we need to draw more than one tile to maintain coherency, make + // sure it happens in the same transaction by requesting this work be + // repeated immediately. + // If this is unnecessary, the remaining work will be done tile-by-tile in + // subsequent transactions. The caller code is responsible for scheduling + // the subsequent transactions as long as we don't set the mPaintFinished + // flag to true. + return (!drawingLowPrecision && paintInSingleTransaction); + } + + // We're not repeating painting and we've not requested a repeat transaction, + // so the paint is finished. If there's still a separate low precision + // paint to do, it will get marked as unfinished later. + aPaintData->mPaintFinished = true; + return false; +} + +bool ClientMultiTiledLayerBuffer::ProgressiveUpdate( + const nsIntRegion& aValidRegion, const nsIntRegion& aInvalidRegion, + const nsIntRegion& aOldValidRegion, nsIntRegion& aOutDrawnRegion, + BasicTiledLayerPaintData* aPaintData, + LayerManager::DrawPaintedLayerCallback aCallback, void* aCallbackData) { + TILING_LOG("TILING %p: Progressive update valid region %s\n", &mPaintedLayer, + Stringify(aValidRegion).c_str()); + TILING_LOG("TILING %p: Progressive update invalid region %s\n", + &mPaintedLayer, Stringify(aInvalidRegion).c_str()); + TILING_LOG("TILING %p: Progressive update old valid region %s\n", + &mPaintedLayer, Stringify(aOldValidRegion).c_str()); + + bool repeat = false; + bool isBufferChanged = false; + nsIntRegion remainingInvalidRegion = aInvalidRegion; + nsIntRegion updatedValidRegion = aValidRegion; + do { + // Compute the region that should be updated. Repeat as many times as + // is required. + nsIntRegion regionToPaint; + repeat = + ComputeProgressiveUpdateRegion(remainingInvalidRegion, aOldValidRegion, + regionToPaint, aPaintData, repeat); + + TILING_LOG( + "TILING %p: Progressive update computed paint region %s repeat %d\n", + &mPaintedLayer, Stringify(regionToPaint).c_str(), repeat); + + // There's no further work to be done. + if (regionToPaint.IsEmpty()) { + break; + } + + isBufferChanged = true; + + // Keep track of what we're about to refresh. + aOutDrawnRegion.OrWith(regionToPaint); + updatedValidRegion.OrWith(regionToPaint); + + // aValidRegion may have been altered by InvalidateRegion, but we still + // want to display stale content until it gets progressively updated. + // Create a region that includes stale content. + nsIntRegion validOrStale; + validOrStale.Or(updatedValidRegion, aOldValidRegion); + + // Paint the computed region and subtract it from the invalid region. + PaintThebes(validOrStale, regionToPaint, remainingInvalidRegion, aCallback, + aCallbackData, TilePaintFlags::Progressive); + remainingInvalidRegion.SubOut(regionToPaint); + } while (repeat); + + TILING_LOG( + "TILING %p: Progressive update final valid region %s buffer changed %d\n", + &mPaintedLayer, Stringify(updatedValidRegion).c_str(), isBufferChanged); + TILING_LOG("TILING %p: Progressive update final invalid region %s\n", + &mPaintedLayer, Stringify(remainingInvalidRegion).c_str()); + + // Return false if nothing has been drawn, or give what has been drawn + // to the shadow layer to upload. + return isBufferChanged; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/MultiTiledContentClient.h b/gfx/layers/client/MultiTiledContentClient.h new file mode 100644 index 0000000000..8b51c3b0d0 --- /dev/null +++ b/gfx/layers/client/MultiTiledContentClient.h @@ -0,0 +1,199 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_MULTITILEDCONTENTCLIENT_H +#define MOZILLA_GFX_MULTITILEDCONTENTCLIENT_H + +#include "ClientLayerManager.h" // for ClientLayerManager +#include "nsRegion.h" // for nsIntRegion +#include "mozilla/gfx/2D.h" // for gfx::Tile +#include "mozilla/gfx/Point.h" // for IntPoint +#include "mozilla/layers/CompositableClient.h" // for CompositableClient +#include "mozilla/layers/LayersMessages.h" // for TileDescriptor +#include "mozilla/layers/TiledContentClient.h" // for ClientTiledPaintedLayer +#include "mozilla/UniquePtr.h" // for UniquePtr +#include "TiledLayerBuffer.h" // for TiledLayerBuffer + +namespace mozilla { +namespace layers { + +class ClientLayerManager; + +class ClientMultiTiledLayerBuffer + : public TiledLayerBuffer, + public ClientTiledLayerBuffer { + friend class TiledLayerBuffer; + + public: + ClientMultiTiledLayerBuffer(ClientTiledPaintedLayer& aPaintedLayer, + CompositableClient& aCompositableClient, + ClientLayerManager* aManager, + SharedFrameMetricsHelper* aHelper); + + void PaintThebes(const nsIntRegion& aNewValidRegion, + const nsIntRegion& aPaintRegion, + const nsIntRegion& aDirtyRegion, + LayerManager::DrawPaintedLayerCallback aCallback, + void* aCallbackData, + TilePaintFlags aFlags = TilePaintFlags::None) override; + + bool SupportsProgressiveUpdate() override { return true; } + /** + * Performs a progressive update of a given tiled buffer. + * See ComputeProgressiveUpdateRegion below for parameter documentation. + * aOutDrawnRegion is an outparameter that contains the region that was + * drawn, and which can now be added to the layer's valid region. + */ + bool ProgressiveUpdate(const nsIntRegion& aValidRegion, + const nsIntRegion& aInvalidRegion, + const nsIntRegion& aOldValidRegion, + nsIntRegion& aOutDrawnRegion, + BasicTiledLayerPaintData* aPaintData, + LayerManager::DrawPaintedLayerCallback aCallback, + void* aCallbackData) override; + + void ResetPaintedAndValidState() override { + mValidRegion.SetEmpty(); + mTiles.mSize.width = 0; + mTiles.mSize.height = 0; + DiscardBuffers(); + mRetainedTiles.Clear(); + } + + const nsIntRegion& GetValidRegion() override { + return TiledLayerBuffer::GetValidRegion(); + } + + bool IsLowPrecision() const override { + return TiledLayerBuffer::IsLowPrecision(); + } + + void Dump(std::stringstream& aStream, const char* aPrefix, bool aDumpHtml, + TextureDumpMode aCompress) override { + TiledLayerBuffer::Dump(aStream, aPrefix, aDumpHtml, aCompress); + } + + void ReadLock(); + + void Release(); + + void DiscardBuffers(); + + SurfaceDescriptorTiles GetSurfaceDescriptorTiles(); + + void SetResolution(float aResolution) { + if (mResolution == aResolution) { + return; + } + + Update(nsIntRegion(), nsIntRegion(), nsIntRegion(), TilePaintFlags::None); + mResolution = aResolution; + } + + protected: + bool ValidateTile(TileClient& aTile, const nsIntPoint& aTileRect, + nsIntRegion& aDirtyRegion, TilePaintFlags aFlags); + + void Update(const nsIntRegion& aNewValidRegion, + const nsIntRegion& aPaintRegion, const nsIntRegion& aDirtyRegion, + TilePaintFlags aFlags); + + TileClient GetPlaceholderTile() const { return TileClient(); } + + private: + RefPtr mManager; + LayerManager::DrawPaintedLayerCallback mCallback; + void* mCallbackData; + + // The region that will be made valid during Update(). Once Update() is + // completed then this is identical to mValidRegion. + nsIntRegion mNewValidRegion; + + SharedFrameMetricsHelper* mSharedFrameMetricsHelper; + + // Parameters that are collected during Update for a paint before they + // are either executed or replayed on the paint thread. + AutoTArray mPaintTiles; + AutoTArray, 4> mPaintTasks; + + /** + * While we're adding tiles, this is used to keep track of the position of + * the top-left of the top-left-most tile. When we come to wrap the tiles in + * TiledDrawTarget we subtract the value of this member from each tile's + * offset so that all the tiles have a positive offset, then add a + * translation to the TiledDrawTarget to compensate. This is important so + * that the mRect of the TiledDrawTarget is always at a positive x/y + * position, otherwise its GetSize() methods will be broken. + */ + gfx::IntPoint mTilingOrigin; + /** + * Calculates the region to update in a single progressive update transaction. + * This employs some heuristics to update the most 'sensible' region to + * update at this point in time, and how large an update should be performed + * at once to maintain visual coherency. + * + * aInvalidRegion is the current invalid region. + * aOldValidRegion is the valid region of mTiledBuffer at the beginning of the + * current transaction. + * aRegionToPaint will be filled with the region to update. This may be empty, + * which indicates that there is no more work to do. + * aIsRepeated should be true if this function has already been called during + * this transaction. + * + * Returns true if it should be called again, false otherwise. In the case + * that aRegionToPaint is empty, this will return aIsRepeated for convenience. + */ + bool ComputeProgressiveUpdateRegion(const nsIntRegion& aInvalidRegion, + const nsIntRegion& aOldValidRegion, + nsIntRegion& aRegionToPaint, + BasicTiledLayerPaintData* aPaintData, + bool aIsRepeated); + + void MaybeSyncTextures(const nsIntRegion& aPaintRegion, + const TilesPlacement& aNewTiles, + const gfx::IntSize& aScaledTileSize); +}; + +/** + * An implementation of TiledContentClient that supports + * multiple tiles and a low precision buffer. + */ +class MultiTiledContentClient : public TiledContentClient { + public: + MultiTiledContentClient(ClientTiledPaintedLayer& aPaintedLayer, + ClientLayerManager* aManager); + + protected: + ~MultiTiledContentClient() { + MOZ_COUNT_DTOR(MultiTiledContentClient); + + mTiledBuffer.DiscardBuffers(); + mLowPrecisionTiledBuffer.DiscardBuffers(); + } + + public: + void ClearCachedResources() override; + void UpdatedBuffer(TiledBufferType aType) override; + + ClientTiledLayerBuffer* GetTiledBuffer() override { return &mTiledBuffer; } + ClientTiledLayerBuffer* GetLowPrecisionTiledBuffer() override { + if (mHasLowPrecision) { + return &mLowPrecisionTiledBuffer; + } + return nullptr; + } + + private: + SharedFrameMetricsHelper mSharedFrameMetricsHelper; + ClientMultiTiledLayerBuffer mTiledBuffer; + ClientMultiTiledLayerBuffer mLowPrecisionTiledBuffer; + bool mHasLowPrecision; +}; + +} // namespace layers +} // namespace mozilla + +#endif // MOZILLA_GFX_MULTITILEDCONTENTCLIENT_H diff --git a/gfx/layers/client/SingleTiledContentClient.cpp b/gfx/layers/client/SingleTiledContentClient.cpp new file mode 100644 index 0000000000..eabbc1a9ca --- /dev/null +++ b/gfx/layers/client/SingleTiledContentClient.cpp @@ -0,0 +1,266 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/SingleTiledContentClient.h" + +#include "ClientTiledPaintedLayer.h" +#include "mozilla/Maybe.h" +#include "mozilla/UniquePtr.h" +#include "TiledLayerBuffer.h" + +namespace mozilla { +namespace layers { + +SingleTiledContentClient::SingleTiledContentClient( + ClientTiledPaintedLayer& aPaintedLayer, ClientLayerManager* aManager) + : TiledContentClient(aManager, "Single") { + MOZ_COUNT_CTOR(SingleTiledContentClient); + + mTiledBuffer = + new ClientSingleTiledLayerBuffer(aPaintedLayer, *this, aManager); +} + +void SingleTiledContentClient::ClearCachedResources() { + CompositableClient::ClearCachedResources(); + mTiledBuffer->DiscardBuffers(); +} + +void SingleTiledContentClient::UpdatedBuffer(TiledBufferType aType) { + mForwarder->UseTiledLayerBuffer(this, + mTiledBuffer->GetSurfaceDescriptorTiles()); +} + +/* static */ +bool SingleTiledContentClient::ClientSupportsLayerSize( + const gfx::IntSize& aSize, ClientLayerManager* aManager) { + int32_t maxTextureSize = aManager->GetMaxTextureSize(); + return aSize.width <= maxTextureSize && aSize.height <= maxTextureSize; +} + +ClientSingleTiledLayerBuffer::ClientSingleTiledLayerBuffer( + ClientTiledPaintedLayer& aPaintedLayer, + CompositableClient& aCompositableClient, ClientLayerManager* aManager) + : ClientTiledLayerBuffer(aPaintedLayer, aCompositableClient), + mManager(aManager), + mWasLastPaintProgressive(false), + mFormat(gfx::SurfaceFormat::UNKNOWN) {} + +void ClientSingleTiledLayerBuffer::ReleaseTiles() { + if (!mTile.IsPlaceholderTile()) { + mTile.DiscardBuffers(); + } + mTile.SetTextureAllocator(nullptr); +} + +void ClientSingleTiledLayerBuffer::DiscardBuffers() { + if (!mTile.IsPlaceholderTile()) { + mTile.DiscardFrontBuffer(); + mTile.DiscardBackBuffer(); + } +} + +SurfaceDescriptorTiles +ClientSingleTiledLayerBuffer::GetSurfaceDescriptorTiles() { + nsTArray tiles; + + TileDescriptor tileDesc = mTile.GetTileDescriptor(); + tiles.AppendElement(tileDesc); + mTile.mUpdateRect = gfx::IntRect(); + + return SurfaceDescriptorTiles(mValidRegion, tiles, mTilingOrigin, mSize, 0, 0, + 1, 1, 1.0, mFrameResolution.xScale, + mFrameResolution.yScale, + mWasLastPaintProgressive); +} + +already_AddRefed +ClientSingleTiledLayerBuffer::GetTextureClient() { + MOZ_ASSERT(mFormat != gfx::SurfaceFormat::UNKNOWN); + return mCompositableClient.CreateTextureClientForDrawing( + gfx::ImageFormatToSurfaceFormat(mFormat), mSize, BackendSelector::Content, + TextureFlags::DISALLOW_BIGIMAGE | TextureFlags::IMMEDIATE_UPLOAD | + TextureFlags::NON_BLOCKING_READ_LOCK); +} + +void ClientSingleTiledLayerBuffer::PaintThebes( + const nsIntRegion& aNewValidRegion, const nsIntRegion& aPaintRegion, + const nsIntRegion& aDirtyRegion, + LayerManager::DrawPaintedLayerCallback aCallback, void* aCallbackData, + TilePaintFlags aFlags) { + mWasLastPaintProgressive = !!(aFlags & TilePaintFlags::Progressive); + bool asyncPaint = !!(aFlags & TilePaintFlags::Async); + + // Compare layer valid region size to current backbuffer size, discard if not + // matching. + gfx::IntSize size = aNewValidRegion.GetBounds().Size(); + gfx::IntPoint origin = aNewValidRegion.GetBounds().TopLeft(); + nsIntRegion paintRegion = aPaintRegion; + + RefPtr discardedFrontBuffer = nullptr; + RefPtr discardedFrontBufferOnWhite = nullptr; + nsIntRegion discardedValidRegion; + + if (mSize != size || mTilingOrigin != origin) { + discardedFrontBuffer = mTile.mFrontBuffer; + discardedFrontBufferOnWhite = mTile.mFrontBufferOnWhite; + discardedValidRegion = mValidRegion; + + TILING_LOG( + "TILING %p: Single-tile valid region changed. Discarding buffers.\n", + &mPaintedLayer); + ResetPaintedAndValidState(); + mSize = size; + mTilingOrigin = origin; + paintRegion = aNewValidRegion; + } + + SurfaceMode mode; + gfxContentType content = GetContentType(&mode); + mFormat = gfxPlatform::GetPlatform()->OptimalFormatForContent(content); + + if (mTile.IsPlaceholderTile()) { + mTile.SetTextureAllocator(this); + } + + if (mManager->AsShadowForwarder()->SupportsTextureDirectMapping()) { + AutoTArray syncTextureSerials; + mTile.GetSyncTextureSerials(mode, syncTextureSerials); + if (syncTextureSerials.Length() > 0) { + mManager->AsShadowForwarder()->SyncTextures(syncTextureSerials); + } + } + + // The dirty region relative to the top-left of the tile. + nsIntRegion tileVisibleRegion = aNewValidRegion.MovedBy(-mTilingOrigin); + nsIntRegion tileDirtyRegion = paintRegion.MovedBy(-mTilingOrigin); + + Maybe backBuffer = + mTile.AcquireBackBuffer(mCompositableClient, tileDirtyRegion, + tileVisibleRegion, content, mode, aFlags); + + if (!backBuffer) { + return; + } + + // Mark the area we need to paint in the back buffer as invalid in the + // front buffer as they will become out of sync. + mTile.mInvalidFront.OrWith(tileDirtyRegion); + + // Add backbuffer's invalid region to the dirty region to be painted. + // This will be empty if we were able to copy from the front in to the back. + nsIntRegion tileInvalidRegion = mTile.mInvalidBack; + tileInvalidRegion.AndWith(tileVisibleRegion); + + paintRegion.OrWith(tileInvalidRegion.MovedBy(mTilingOrigin)); + tileDirtyRegion.OrWith(tileInvalidRegion); + + // Mark the region we will be painting and the region we copied from the front + // buffer as needing to be uploaded to the compositor + mTile.mUpdateRect = + tileDirtyRegion.GetBounds().Union(backBuffer->mUpdatedRect); + + // If the old frontbuffer was discarded then attempt to copy what we + // can from it to the new backbuffer. + if (discardedFrontBuffer) { + nsIntRegion copyableRegion; + copyableRegion.And(aNewValidRegion, discardedValidRegion); + copyableRegion.SubOut(aDirtyRegion); + + OpenMode readMode = + asyncPaint ? OpenMode::OPEN_READ_ASYNC : OpenMode::OPEN_READ; + + DualTextureClientAutoLock discardedBuffer( + discardedFrontBuffer, discardedFrontBufferOnWhite, readMode); + + if (discardedBuffer.Succeeded()) { + RefPtr discardedSurface = discardedBuffer->Snapshot(); + + for (auto iter = copyableRegion.RectIter(); !iter.Done(); iter.Next()) { + const gfx::IntRect src = + iter.Get() - discardedValidRegion.GetBounds().TopLeft(); + const gfx::IntPoint dest = iter.Get().TopLeft() - mTilingOrigin; + + backBuffer->mTarget->CopySurface(discardedSurface, src, dest); + } + + TILING_LOG("TILING %p: Region copied from discarded frontbuffer %s\n", + &mPaintedLayer, Stringify(copyableRegion).c_str()); + + // We don't need to repaint valid content that was just copied. + paintRegion.SubOut(copyableRegion); + copyableRegion.MoveBy(-mTilingOrigin); + tileDirtyRegion.SubOut(copyableRegion); + } else { + gfxWarning() << "[Tiling:Client] Failed to aquire the discarded front " + "buffer's draw target"; + } + } + + if (mode != SurfaceMode::SURFACE_OPAQUE) { + for (auto iter = tileDirtyRegion.RectIter(); !iter.Done(); iter.Next()) { + const gfx::Rect drawRect(iter.Get().X(), iter.Get().Y(), + iter.Get().Width(), iter.Get().Height()); + backBuffer->mTarget->ClearRect(drawRect); + } + } + + // Paint into the target + { + RefPtr ctx = gfxContext::CreateOrNull(backBuffer->mTarget); + if (!ctx) { + gfxDevCrash(gfx::LogReason::InvalidContext) + << "SingleTiledContextClient context problem " + << gfx::hexa(backBuffer->mTarget); + return; + } + ctx->SetMatrix( + ctx->CurrentMatrix().PreTranslate(-mTilingOrigin.x, -mTilingOrigin.y)); + + aCallback(&mPaintedLayer, ctx, paintRegion, paintRegion, + DrawRegionClip::DRAW, nsIntRegion(), aCallbackData); + } + + if (asyncPaint) { + if (!backBuffer->mCapture->IsEmpty()) { + UniquePtr task(new PaintTask()); + task->mCapture = backBuffer->mCapture; + task->mTarget = backBuffer->mBackBuffer; + task->mClients = std::move(backBuffer->mTextureClients); + if (discardedFrontBuffer) { + task->mClients.AppendElement(discardedFrontBuffer); + } + if (discardedFrontBufferOnWhite) { + task->mClients.AppendElement(discardedFrontBufferOnWhite); + } + + // The target is an alias for the capture, and the paint thread expects + // to be the only one with a reference to the capture + backBuffer->mTarget = nullptr; + backBuffer->mCapture = nullptr; + + PaintThread::Get()->QueuePaintTask(std::move(task)); + mManager->SetQueuedAsyncPaints(); + } + } else { + MOZ_ASSERT(backBuffer->mTarget == backBuffer->mBackBuffer); + MOZ_ASSERT(!backBuffer->mCapture); + } + + // The new buffer is now validated, remove the dirty region from it. + mTile.mInvalidBack.SubOut(tileDirtyRegion); + + backBuffer = Nothing(); + + mTile.Flip(); + UnlockTile(mTile); + + mValidRegion = aNewValidRegion; + mLastPaintSurfaceMode = mode; + mLastPaintContentType = content; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/SingleTiledContentClient.h b/gfx/layers/client/SingleTiledContentClient.h new file mode 100644 index 0000000000..70cb37c147 --- /dev/null +++ b/gfx/layers/client/SingleTiledContentClient.h @@ -0,0 +1,128 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_SINGLETILEDCONTENTCLIENT_H +#define MOZILLA_GFX_SINGLETILEDCONTENTCLIENT_H + +#include "TiledContentClient.h" + +namespace mozilla { +namespace layers { + +class ClientTiledPaintedLayer; +class ClientLayerManager; + +/** + * Provide an instance of TiledLayerBuffer backed by drawable TextureClients. + * This buffer provides an implementation of ValidateTile using a + * thebes callback and can support painting using a single paint buffer. + * Whether a single paint buffer is used is controlled by + * StaticPrefs::PerTileDrawing(). + */ +class ClientSingleTiledLayerBuffer : public ClientTiledLayerBuffer, + public TextureClientAllocator { + virtual ~ClientSingleTiledLayerBuffer() = default; + + public: + ClientSingleTiledLayerBuffer(ClientTiledPaintedLayer& aPaintedLayer, + CompositableClient& aCompositableClient, + ClientLayerManager* aManager); + + // TextureClientAllocator + already_AddRefed GetTextureClient() override; + void ReturnTextureClientDeferred(TextureClient* aClient) override {} + void ReportClientLost() override {} + + // ClientTiledLayerBuffer + void PaintThebes(const nsIntRegion& aNewValidRegion, + const nsIntRegion& aPaintRegion, + const nsIntRegion& aDirtyRegion, + LayerManager::DrawPaintedLayerCallback aCallback, + void* aCallbackData, + TilePaintFlags aFlags = TilePaintFlags::None) override; + + bool SupportsProgressiveUpdate() override { return false; } + bool ProgressiveUpdate(const nsIntRegion& aValidRegion, + const nsIntRegion& aInvalidRegion, + const nsIntRegion& aOldValidRegion, + nsIntRegion& aOutDrawnRegion, + BasicTiledLayerPaintData* aPaintData, + LayerManager::DrawPaintedLayerCallback aCallback, + void* aCallbackData) override { + MOZ_ASSERT(false, "ProgressiveUpdate not supported!"); + return false; + } + + void ResetPaintedAndValidState() override { + mValidRegion.SetEmpty(); + mTile.DiscardBuffers(); + } + + const nsIntRegion& GetValidRegion() override { return mValidRegion; } + + bool IsLowPrecision() const override { return false; } + + void ReleaseTiles(); + + void DiscardBuffers(); + + SurfaceDescriptorTiles GetSurfaceDescriptorTiles(); + + private: + TileClient mTile; + + RefPtr mManager; + + nsIntRegion mValidRegion; + bool mWasLastPaintProgressive; + + /** + * While we're adding tiles, this is used to keep track of the position of + * the top-left of the top-left-most tile. When we come to wrap the tiles in + * TiledDrawTarget we subtract the value of this member from each tile's + * offset so that all the tiles have a positive offset, then add a + * translation to the TiledDrawTarget to compensate. This is important so + * that the mRect of the TiledDrawTarget is always at a positive x/y + * position, otherwise its GetSize() methods will be broken. + */ + gfx::IntPoint mTilingOrigin; + gfx::IntSize mSize; + gfxImageFormat mFormat; +}; + +class SingleTiledContentClient : public TiledContentClient { + public: + SingleTiledContentClient(ClientTiledPaintedLayer& aPaintedLayer, + ClientLayerManager* aManager); + + protected: + ~SingleTiledContentClient() { + MOZ_COUNT_DTOR(SingleTiledContentClient); + + mTiledBuffer->ReleaseTiles(); + } + + public: + static bool ClientSupportsLayerSize(const gfx::IntSize& aSize, + ClientLayerManager* aManager); + + void ClearCachedResources() override; + + void UpdatedBuffer(TiledBufferType aType) override; + + ClientTiledLayerBuffer* GetTiledBuffer() override { return mTiledBuffer; } + ClientTiledLayerBuffer* GetLowPrecisionTiledBuffer() override { + return nullptr; + } + + private: + RefPtr mTiledBuffer; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/client/TextureClient.cpp b/gfx/layers/client/TextureClient.cpp new file mode 100644 index 0000000000..c4d4077223 --- /dev/null +++ b/gfx/layers/client/TextureClient.cpp @@ -0,0 +1,1934 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/TextureClient.h" + +#include // for uint8_t, uint32_t, etc + +#include "BufferTexture.h" +#include "GeckoProfiler.h" +#include "IPDLActor.h" +#include "ImageContainer.h" // for PlanarYCbCrData, etc +#include "Layers.h" // for Layer, etc +#include "MainThreadUtils.h" +#include "gfx2DGlue.h" +#include "gfxPlatform.h" // for gfxPlatform +#include "gfxUtils.h" // for gfxUtils::GetAsLZ4Base64Str +#include "mozilla/Atomics.h" +#include "mozilla/Mutex.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/DataSurfaceHelpers.h" // for CreateDataSourceSurfaceByCloning +#include "mozilla/gfx/Logging.h" // for gfxDebug +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/ipc/CrossProcessSemaphore.h" +#include "mozilla/ipc/SharedMemory.h" // for SharedMemory, etc +#include "mozilla/layers/CompositableForwarder.h" +#include "mozilla/layers/ISurfaceAllocator.h" +#include "mozilla/layers/ImageBridgeChild.h" +#include "mozilla/layers/ImageDataSerializer.h" +#include "mozilla/layers/PTextureChild.h" +#include "mozilla/layers/PaintThread.h" +#include "mozilla/layers/ShadowLayers.h" +#include "mozilla/layers/TextureClientOGL.h" +#include "mozilla/layers/TextureClientRecycleAllocator.h" +#include "mozilla/layers/TextureRecorded.h" +#include "nsDebug.h" // for NS_ASSERTION, NS_WARNING, etc +#include "nsISerialEventTarget.h" +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "nsPrintfCString.h" // for nsPrintfCString + +#ifdef XP_WIN +# include "gfx2DGlue.h" +# include "gfxWindowsPlatform.h" +# include "mozilla/gfx/DeviceManagerDx.h" +# include "mozilla/layers/TextureD3D11.h" +# include "mozilla/layers/TextureDIB.h" +#endif +#ifdef MOZ_X11 +# include "GLXLibrary.h" +# include "mozilla/layers/TextureClientX11.h" +#endif +#ifdef MOZ_WAYLAND +# include + +# include "gfxPlatformGtk.h" +# include "mozilla/layers/DMABUFTextureClientOGL.h" +# include "mozilla/widget/nsWaylandDisplay.h" +#endif + +#ifdef XP_MACOSX +# include "mozilla/layers/MacIOSurfaceTextureClientOGL.h" +#endif + +#if 0 +# define RECYCLE_LOG(...) printf_stderr(__VA_ARGS__) +#else +# define RECYCLE_LOG(...) \ + do { \ + } while (0) +#endif + +namespace mozilla::layers { + +using namespace mozilla::ipc; +using namespace mozilla::gl; +using namespace mozilla::gfx; + +struct TextureDeallocParams { + TextureData* data; + RefPtr actor; + RefPtr allocator; + bool clientDeallocation; + bool syncDeallocation; + bool workAroundSharedSurfaceOwnershipIssue; +}; + +void DeallocateTextureClient(TextureDeallocParams params); + +/** + * TextureChild is the content-side incarnation of the PTexture IPDL actor. + * + * TextureChild is used to synchronize a texture client and its corresponding + * TextureHost if needed (a TextureClient that is not shared with the compositor + * does not have a TextureChild) + * + * During the deallocation phase, a TextureChild may hold its recently destroyed + * TextureClient's data until the compositor side confirmed that it is safe to + * deallocte or recycle the it. + */ +class TextureChild final : PTextureChild { + ~TextureChild() { + // We should have deallocated mTextureData in ActorDestroy + MOZ_ASSERT(!mTextureData); + MOZ_ASSERT_IF(!mOwnerCalledDestroy, !mTextureClient); + } + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TextureChild) + + TextureChild() + : mCompositableForwarder(nullptr), + mTextureForwarder(nullptr), + mTextureClient(nullptr), + mTextureData(nullptr), + mDestroyed(false), + mMainThreadOnly(false), + mIPCOpen(false), + mOwnsTextureData(false), + mOwnerCalledDestroy(false), + mUsesImageBridge(false) {} + + mozilla::ipc::IPCResult Recv__delete__() override { return IPC_OK(); } + + LayersIPCChannel* GetAllocator() { return mTextureForwarder; } + + void ActorDestroy(ActorDestroyReason why) override; + + bool IPCOpen() const { return mIPCOpen; } + + void Lock() const { + if (mUsesImageBridge) { + mLock.Enter(); + } + } + + void Unlock() const { + if (mUsesImageBridge) { + mLock.Leave(); + } + } + + private: + // AddIPDLReference and ReleaseIPDLReference are only to be called by + // CreateIPDLActor and DestroyIPDLActor, respectively. We intentionally make + // them private to prevent misuse. The purpose of these methods is to be aware + // of when the IPC system around this actor goes down: mIPCOpen is then set to + // false. + void AddIPDLReference() { + MOZ_ASSERT(mIPCOpen == false); + mIPCOpen = true; + AddRef(); + } + void ReleaseIPDLReference() { + MOZ_ASSERT(mIPCOpen == false); + Release(); + } + + /// The normal way to destroy the actor. + /// + /// This will asynchronously send a Destroy message to the parent actor, whom + /// will send the delete message. + void Destroy(const TextureDeallocParams& aParams); + + // This lock is used order to prevent several threads to access the + // TextureClient's data concurrently. In particular, it prevents shutdown + // code to destroy a texture while another thread is reading or writing into + // it. + // In most places, the lock is held in short and bounded scopes in which we + // don't block on any other resource. There are few exceptions to this, which + // are discussed below. + // + // The locking pattern of TextureClient may in some case upset deadlock + // detection tools such as TSan. Typically our tile rendering code will lock + // all of its tiles, render into them and unlock them all right after that, + // which looks something like: + // + // Lock tile A + // Lock tile B + // Lock tile C + // Apply drawing commands to tiles A, B and C + // Unlock tile A + // Unlock tile B + // Unlock tile C + // + // And later, we may end up rendering a tile buffer that has the same tiles, + // in a different order, for example: + // + // Lock tile B + // Lock tile A + // Lock tile D + // Apply drawing commands to tiles A, B and D + // Unlock tile B + // Unlock tile A + // Unlock tile D + // + // This is because textures being expensive to create, we recycle them as much + // as possible and they may reappear in the tile buffer in a different order. + // + // Unfortunately this is not very friendly to TSan's analysis, which will see + // that B was once locked while A was locked, and then A locked while B was + // locked. TSan identifies this as a potential dead-lock which would be the + // case if this kind of inconsistent and dependent locking order was happening + // concurrently. + // In the case of TextureClient, dependent locking only ever happens on the + // thread that draws into the texture (let's call it the producer thread). + // Other threads may call into a method that can lock the texture in a short + // and bounded scope inside of which it is not allowed to do anything that + // could cause the thread to block. A given texture can only have one producer + // thread. + // + // Another example of TSan-unfriendly locking pattern is when copying a + // texture into another, which also never happens outside of the producer + // thread. Copying A into B looks like this: + // + // Lock texture B + // Lock texture A + // Copy A into B + // Unlock A + // Unlock B + // + // In a given frame we may need to copy A into B and in another frame copy + // B into A. For example A and B can be the Front and Back buffers, + // alternating roles and the copy is needed to avoid the cost of re-drawing + // the valid region. + // + // The important rule is that all of the dependent locking must occur only + // in the texture's producer thread to avoid deadlocks. + mutable gfx::CriticalSection mLock; + + RefPtr mCompositableForwarder; + RefPtr mTextureForwarder; + + TextureClient* mTextureClient; + TextureData* mTextureData; + Atomic mDestroyed; + bool mMainThreadOnly; + bool mIPCOpen; + bool mOwnsTextureData; + bool mOwnerCalledDestroy; + bool mUsesImageBridge; + + friend class TextureClient; + friend void DeallocateTextureClient(TextureDeallocParams params); +}; + +static inline gfx::BackendType BackendTypeForBackendSelector( + LayersBackend aLayersBackend, BackendSelector aSelector) { + switch (aSelector) { + case BackendSelector::Canvas: + return gfxPlatform::GetPlatform()->GetPreferredCanvasBackend(); + case BackendSelector::Content: + return gfxPlatform::GetPlatform()->GetContentBackendFor(aLayersBackend); + default: + MOZ_ASSERT_UNREACHABLE("Unknown backend selector"); + return gfx::BackendType::NONE; + } +}; + +static TextureType GetTextureType(gfx::SurfaceFormat aFormat, + gfx::IntSize aSize, + KnowsCompositor* aKnowsCompositor, + BackendSelector aSelector, + TextureAllocationFlags aAllocFlags) { + LayersBackend layersBackend = aKnowsCompositor->GetCompositorBackendType(); + gfx::BackendType moz2DBackend = + BackendTypeForBackendSelector(layersBackend, aSelector); + Unused << moz2DBackend; + +#ifdef XP_WIN + int32_t maxTextureSize = aKnowsCompositor->GetMaxTextureSize(); + if ((layersBackend == LayersBackend::LAYERS_D3D11 || + (layersBackend == LayersBackend::LAYERS_WR && + !aKnowsCompositor->UsingSoftwareWebRender())) && + (moz2DBackend == gfx::BackendType::DIRECT2D || + moz2DBackend == gfx::BackendType::DIRECT2D1_1 || + (!!(aAllocFlags & ALLOC_FOR_OUT_OF_BAND_CONTENT))) && + aSize.width <= maxTextureSize && aSize.height <= maxTextureSize && + !(aAllocFlags & ALLOC_UPDATE_FROM_SURFACE)) { + return TextureType::D3D11; + } + + if (layersBackend != LayersBackend::LAYERS_WR && + aFormat == SurfaceFormat::B8G8R8X8 && + moz2DBackend == gfx::BackendType::CAIRO && NS_IsMainThread()) { + return TextureType::DIB; + } +#endif + +#ifdef MOZ_WAYLAND + if ((layersBackend == LayersBackend::LAYERS_OPENGL || + (layersBackend == LayersBackend::LAYERS_WR && + !aKnowsCompositor->UsingSoftwareWebRender())) && + widget::GetDMABufDevice()->IsDMABufTexturesEnabled() && + aFormat != SurfaceFormat::A8) { + return TextureType::DMABUF; + } +#endif + +#ifdef MOZ_X11 + gfxSurfaceType type = + gfxPlatform::GetPlatform()->ScreenReferenceSurface()->GetType(); + + if (layersBackend == LayersBackend::LAYERS_BASIC && + moz2DBackend == gfx::BackendType::CAIRO && type == gfxSurfaceType::Xlib) { + return TextureType::X11; + } + if (layersBackend == LayersBackend::LAYERS_OPENGL && + type == gfxSurfaceType::Xlib && aFormat != SurfaceFormat::A8 && + gl::sGLXLibrary.UseTextureFromPixmap()) { + return TextureType::X11; + } +#endif + +#ifdef XP_MACOSX + if (StaticPrefs::gfx_use_iosurface_textures_AtStartup()) { + return TextureType::MacIOSurface; + } +#endif + +#ifdef MOZ_WIDGET_ANDROID + if (gfxVars::UseAHardwareBufferContent() && + aSelector == BackendSelector::Content) { + return TextureType::AndroidHardwareBuffer; + } + if (StaticPrefs::gfx_use_surfacetexture_textures_AtStartup()) { + return TextureType::AndroidNativeWindow; + } +#endif + + return TextureType::Unknown; +} + +TextureType PreferredCanvasTextureType(KnowsCompositor* aKnowsCompositor) { + return GetTextureType(gfx::SurfaceFormat::R8G8B8A8, {1, 1}, aKnowsCompositor, + BackendSelector::Canvas, + TextureAllocationFlags::ALLOC_DEFAULT); +} + +static bool ShouldRemoteTextureType(TextureType aTextureType, + BackendSelector aSelector) { + if (!XRE_IsContentProcess()) { + return false; + } + + if (aSelector != BackendSelector::Canvas || !gfxVars::RemoteCanvasEnabled()) { + return false; + } + + switch (aTextureType) { + case TextureType::D3D11: + return true; + default: + return false; + } +} + +/* static */ +TextureData* TextureData::Create(TextureForwarder* aAllocator, + gfx::SurfaceFormat aFormat, gfx::IntSize aSize, + KnowsCompositor* aKnowsCompositor, + BackendSelector aSelector, + TextureFlags aTextureFlags, + TextureAllocationFlags aAllocFlags) { + TextureType textureType = + GetTextureType(aFormat, aSize, aKnowsCompositor, aSelector, aAllocFlags); + + if (ShouldRemoteTextureType(textureType, aSelector)) { + RefPtr canvasChild = aAllocator->GetCanvasChild(); + if (canvasChild) { + return new RecordedTextureData(canvasChild.forget(), aSize, aFormat, + textureType); + } + + // We don't have a CanvasChild, but are supposed to be remote. + // Fall back to software. + textureType = TextureType::Unknown; + } + +#if defined(XP_MACOSX) || defined(MOZ_WAYLAND) + gfx::BackendType moz2DBackend = BackendTypeForBackendSelector( + aKnowsCompositor->GetCompositorBackendType(), aSelector); +#endif + + switch (textureType) { +#ifdef XP_WIN + case TextureType::D3D11: + return D3D11TextureData::Create(aSize, aFormat, aAllocFlags); + case TextureType::DIB: + return DIBTextureData::Create(aSize, aFormat, aAllocator); +#endif + +#ifdef MOZ_WAYLAND + case TextureType::DMABUF: + return DMABUFTextureData::Create(aSize, aFormat, moz2DBackend); +#endif + +#ifdef MOZ_X11 + case TextureType::X11: + return X11TextureData::Create(aSize, aFormat, aTextureFlags, aAllocator); +#endif +#ifdef XP_MACOSX + case TextureType::MacIOSurface: + return MacIOSurfaceTextureData::Create(aSize, aFormat, moz2DBackend); +#endif +#ifdef MOZ_WIDGET_ANDROID + case TextureType::AndroidHardwareBuffer: + return AndroidHardwareBufferTextureData::Create(aSize, aFormat); + case TextureType::AndroidNativeWindow: + return AndroidNativeWindowTextureData::Create(aSize, aFormat); +#endif + default: + return nullptr; + } +} + +/* static */ +bool TextureData::IsRemote(KnowsCompositor* aKnowsCompositor, + BackendSelector aSelector) { + TextureType textureType = GetTextureType( + gfx::SurfaceFormat::UNKNOWN, gfx::IntSize(1, 1), aKnowsCompositor, + aSelector, TextureAllocationFlags::ALLOC_DEFAULT); + + return ShouldRemoteTextureType(textureType, aSelector); +} + +static void DestroyTextureData(TextureData* aTextureData, + LayersIPCChannel* aAllocator, bool aDeallocate, + bool aMainThreadOnly) { + if (!aTextureData) { + return; + } + + if (aMainThreadOnly && !NS_IsMainThread()) { + RefPtr allocatorRef = aAllocator; + SchedulerGroup::Dispatch( + TaskCategory::Other, + NS_NewRunnableFunction( + "layers::DestroyTextureData", + [aTextureData, allocatorRef, aDeallocate]() -> void { + DestroyTextureData(aTextureData, allocatorRef, aDeallocate, true); + })); + return; + } + + if (aDeallocate) { + aTextureData->Deallocate(aAllocator); + } else { + aTextureData->Forget(aAllocator); + } + delete aTextureData; +} + +void TextureChild::ActorDestroy(ActorDestroyReason why) { + AUTO_PROFILER_LABEL("TextureChild::ActorDestroy", GRAPHICS); + MOZ_ASSERT(mIPCOpen); + mIPCOpen = false; + + if (mTextureData) { + DestroyTextureData(mTextureData, GetAllocator(), mOwnsTextureData, + mMainThreadOnly); + mTextureData = nullptr; + } +} + +void TextureChild::Destroy(const TextureDeallocParams& aParams) { + MOZ_ASSERT(!mOwnerCalledDestroy); + if (mOwnerCalledDestroy) { + return; + } + + mOwnerCalledDestroy = true; + + if (!IPCOpen()) { + DestroyTextureData(aParams.data, aParams.allocator, + aParams.clientDeallocation, mMainThreadOnly); + return; + } + + // DestroyTextureData will be called by TextureChild::ActorDestroy + mTextureData = aParams.data; + mOwnsTextureData = aParams.clientDeallocation; + + if (!mCompositableForwarder || + !mCompositableForwarder->DestroyInTransaction(this)) { + this->SendDestroy(); + } +} + +/* static */ +Atomic TextureClient::sSerialCounter(0); + +static void DeallocateTextureClientSyncProxy(TextureDeallocParams params, + ReentrantMonitor* aBarrier, + bool* aDone) { + DeallocateTextureClient(params); + ReentrantMonitorAutoEnter autoMon(*aBarrier); + *aDone = true; + aBarrier->NotifyAll(); +} + +/// The logic for synchronizing a TextureClient's deallocation goes here. +/// +/// This funciton takes care of dispatching work to the right thread using +/// a synchronous proxy if needed, and handles client/host deallocation. +void DeallocateTextureClient(TextureDeallocParams params) { + if (!params.actor && !params.data) { + // Nothing to do + return; + } + + TextureChild* actor = params.actor; + nsCOMPtr ipdlThread; + + if (params.allocator) { + ipdlThread = params.allocator->GetThread(); + if (!ipdlThread) { + // An allocator with no thread means we are too late in the shutdown + // sequence. + gfxCriticalError() << "Texture deallocated too late during shutdown"; + return; + } + } + + // First make sure that the work is happening on the IPDL thread. + if (ipdlThread && !ipdlThread->IsOnCurrentThread()) { + if (params.syncDeallocation) { + bool done = false; + ReentrantMonitor barrier("DeallocateTextureClient"); + ReentrantMonitorAutoEnter autoMon(barrier); + ipdlThread->Dispatch(NewRunnableFunction( + "DeallocateTextureClientSyncProxyRunnable", + DeallocateTextureClientSyncProxy, params, &barrier, &done)); + while (!done) { + barrier.Wait(); + } + } else { + ipdlThread->Dispatch(NewRunnableFunction( + "DeallocateTextureClientRunnable", DeallocateTextureClient, params)); + } + // The work has been forwarded to the IPDL thread, we are done. + return; + } + + // Below this line, we are either in the IPDL thread or ther is no IPDL + // thread anymore. + + if (!ipdlThread) { + // If we don't have a thread we can't know for sure that we are in + // the IPDL thread and use the LayersIPCChannel. + // This should ideally not happen outside of gtest, but some shutdown + // raciness could put us in this situation. + params.allocator = nullptr; + } + + if (!actor) { + // We don't have an IPDL actor, probably because we destroyed the + // TextureClient before sharing it with the compositor. It means the data + // cannot be owned by the TextureHost since we never created the + // TextureHost... + // ..except if the lovely mWorkaroundAnnoyingSharedSurfaceOwnershipIssues + // member is set to true. In this case we are in a special situation where + // this TextureClient is in wrapped into another TextureClient which assumes + // it owns our data. + bool shouldDeallocate = !params.workAroundSharedSurfaceOwnershipIssue; + DestroyTextureData(params.data, params.allocator, shouldDeallocate, + false); // main-thread deallocation + return; + } + + actor->Destroy(params); +} + +void TextureClient::Destroy() { + // Async paints should have been flushed by now. + MOZ_RELEASE_ASSERT(mPaintThreadRefs == 0); + + if (mActor && !mIsLocked) { + mActor->Lock(); + } + + mBorrowedDrawTarget = nullptr; + mReadLock = nullptr; + + RefPtr actor = mActor; + mActor = nullptr; + + if (actor && !actor->mDestroyed.compareExchange(false, true)) { + actor->Unlock(); + actor = nullptr; + } + + TextureData* data = mData; + if (!mWorkaroundAnnoyingSharedSurfaceLifetimeIssues) { + mData = nullptr; + } + + if (data || actor) { + TextureDeallocParams params; + params.actor = actor; + params.allocator = mAllocator; + params.clientDeallocation = !!(mFlags & TextureFlags::DEALLOCATE_CLIENT); + params.workAroundSharedSurfaceOwnershipIssue = + mWorkaroundAnnoyingSharedSurfaceOwnershipIssues; + if (mWorkaroundAnnoyingSharedSurfaceLifetimeIssues) { + params.data = nullptr; + } else { + params.data = data; + } + // At the moment we always deallocate synchronously when deallocating on the + // client side, but having asynchronous deallocate in some of the cases will + // be a worthwhile optimization. + params.syncDeallocation = !!(mFlags & TextureFlags::DEALLOCATE_CLIENT); + + // Release the lock before calling DeallocateTextureClient because the + // latter may wait for the main thread which could create a dead-lock. + + if (actor) { + actor->Unlock(); + } + + DeallocateTextureClient(params); + } +} + +void TextureClient::LockActor() const { + if (mActor) { + mActor->Lock(); + } +} + +void TextureClient::UnlockActor() const { + if (mActor) { + mActor->Unlock(); + } +} + +bool TextureClient::IsReadLocked() const { + if (!mReadLock) { + return false; + } + MOZ_ASSERT(mReadLock->AsNonBlockingLock(), + "Can only check locked for non-blocking locks!"); + return mReadLock->AsNonBlockingLock()->GetReadCount() > 1; +} + +bool TextureClient::TryReadLock() { + if (!mReadLock || mIsReadLocked) { + return true; + } + + if (mReadLock->AsNonBlockingLock()) { + if (IsReadLocked()) { + return false; + } + } + + if (!mReadLock->TryReadLock(TimeDuration::FromMilliseconds(500))) { + return false; + } + + mIsReadLocked = true; + return true; +} + +void TextureClient::ReadUnlock() { + if (!mIsReadLocked) { + return; + } + MOZ_ASSERT(mReadLock); + mReadLock->ReadUnlock(); + mIsReadLocked = false; +} + +bool TextureClient::Lock(OpenMode aMode) { + MOZ_ASSERT(IsValid()); + MOZ_ASSERT(!mIsLocked); + if (!IsValid()) { + return false; + } + if (mIsLocked) { + return mOpenMode == aMode; + } + + if ((aMode & OpenMode::OPEN_WRITE || !mInfo.canConcurrentlyReadLock) && + !TryReadLock()) { + // Only warn if attempting to write. Attempting to read is acceptable usage. + if (aMode & OpenMode::OPEN_WRITE) { + NS_WARNING( + "Attempt to Lock a texture that is being read by the compositor!"); + } + return false; + } + + LockActor(); + + mIsLocked = mData->Lock(aMode); + mOpenMode = aMode; + + auto format = GetFormat(); + if (mIsLocked && CanExposeDrawTarget() && + (aMode & OpenMode::OPEN_READ_WRITE) == OpenMode::OPEN_READ_WRITE && + NS_IsMainThread() && + // the formats that we apparently expect, in the cairo backend. Any other + // format will trigger an assertion in GfxFormatToCairoFormat. + (format == SurfaceFormat::A8R8G8B8_UINT32 || + format == SurfaceFormat::X8R8G8B8_UINT32 || + format == SurfaceFormat::A8 || format == SurfaceFormat::R5G6B5_UINT16)) { + if (!BorrowDrawTarget()) { + // Failed to get a DrawTarget, means we won't be able to write into the + // texture, might as well fail now. + Unlock(); + return false; + } + } + + if (!mIsLocked) { + UnlockActor(); + ReadUnlock(); + } + + return mIsLocked; +} + +void TextureClient::Unlock() { + MOZ_ASSERT(IsValid()); + MOZ_ASSERT(mIsLocked); + if (!IsValid() || !mIsLocked) { + return; + } + + if (mBorrowedDrawTarget) { + if (!(mOpenMode & OpenMode::OPEN_ASYNC)) { + if (mOpenMode & OpenMode::OPEN_WRITE) { + mBorrowedDrawTarget->Flush(); + if (mReadbackSink && !mData->ReadBack(mReadbackSink)) { + // Fallback implementation for reading back, because mData does not + // have a backend-specific implementation and returned false. + RefPtr snapshot = mBorrowedDrawTarget->Snapshot(); + RefPtr dataSurf = snapshot->GetDataSurface(); + mReadbackSink->ProcessReadback(dataSurf); + } + } + + mBorrowedDrawTarget->DetachAllSnapshots(); + // If this assertion is hit, it means something is holding a strong + // reference to our DrawTarget externally, which is not allowed. + MOZ_ASSERT(mBorrowedDrawTarget->refCount() <= mExpectedDtRefs); + } + + mBorrowedDrawTarget = nullptr; + } + + if (mOpenMode & OpenMode::OPEN_WRITE) { + mUpdated = true; + } + + if (mData) { + mData->Unlock(); + } + mIsLocked = false; + mOpenMode = OpenMode::OPEN_NONE; + + UnlockActor(); + ReadUnlock(); +} + +void TextureClient::EnableReadLock() { + if (!mReadLock) { + if (mAllocator->GetTileLockAllocator()) { + mReadLock = NonBlockingTextureReadLock::Create(mAllocator); + } else { + // IPC is down + gfxCriticalError() << "TextureClient::EnableReadLock IPC is down"; + } + } +} + +bool TextureClient::OnForwardedToHost() { + if (mData) { + mData->OnForwardedToHost(); + } + + if (mReadLock && mUpdated) { + // Take a read lock on behalf of the TextureHost. The latter will unlock + // after the shared data is available again for drawing. + mReadLock->ReadLock(); + mUpdated = false; + return true; + } + + return false; +} + +TextureClient::~TextureClient() { + // TextureClients should be kept alive while there are references on the + // paint thread. + MOZ_ASSERT(mPaintThreadRefs == 0); + mReadLock = nullptr; + Destroy(); +} + +void TextureClient::UpdateFromSurface(gfx::SourceSurface* aSurface) { + MOZ_ASSERT(IsValid()); + MOZ_ASSERT(mIsLocked); + MOZ_ASSERT(aSurface); + // If you run into this assertion, make sure the texture was locked write-only + // rather than read-write. + MOZ_ASSERT(!mBorrowedDrawTarget); + + // XXX - It would be better to first try the DrawTarget approach and fallback + // to the backend-specific implementation because the latter will usually do + // an expensive read-back + cpu-side copy if the texture is on the gpu. + // There is a bug with the DrawTarget approach, though specific to reading + // back from WebGL (where R and B channel end up inverted) to figure out + // first. + if (mData->UpdateFromSurface(aSurface)) { + return; + } + if (CanExposeDrawTarget() && NS_IsMainThread()) { + RefPtr dt = BorrowDrawTarget(); + + MOZ_ASSERT(dt); + if (dt) { + dt->CopySurface(aSurface, + gfx::IntRect(gfx::IntPoint(0, 0), aSurface->GetSize()), + gfx::IntPoint(0, 0)); + return; + } + } + NS_WARNING("TextureClient::UpdateFromSurface failed"); +} + +already_AddRefed TextureClient::CreateSimilar( + LayersBackend aLayersBackend, TextureFlags aFlags, + TextureAllocationFlags aAllocFlags) const { + MOZ_ASSERT(IsValid()); + + MOZ_ASSERT(!mIsLocked); + if (mIsLocked) { + return nullptr; + } + + LockActor(); + TextureData* data = + mData->CreateSimilar(mAllocator, aLayersBackend, aFlags, aAllocFlags); + UnlockActor(); + + if (!data) { + return nullptr; + } + + return MakeAndAddRef(data, aFlags, mAllocator); +} + +gfx::DrawTarget* TextureClient::BorrowDrawTarget() { + MOZ_ASSERT(IsValid()); + MOZ_ASSERT(mIsLocked); + // TODO- We can't really assert that at the moment because there is code that + // Borrows the DrawTarget, just to get a snapshot, which is legit in term of + // OpenMode but we should have a way to get a SourceSurface directly instead. + // MOZ_ASSERT(mOpenMode & OpenMode::OPEN_WRITE); + + if (!IsValid() || !mIsLocked) { + return nullptr; + } + + if (!mBorrowedDrawTarget) { + mBorrowedDrawTarget = mData->BorrowDrawTarget(); +#ifdef DEBUG + mExpectedDtRefs = mBorrowedDrawTarget ? mBorrowedDrawTarget->refCount() : 0; +#endif + } + + return mBorrowedDrawTarget; +} + +already_AddRefed TextureClient::BorrowSnapshot() { + MOZ_ASSERT(mIsLocked); + + RefPtr surface = mData->BorrowSnapshot(); + if (!surface) { + surface = BorrowDrawTarget()->Snapshot(); + } + + return surface.forget(); +} + +bool TextureClient::BorrowMappedData(MappedTextureData& aMap) { + MOZ_ASSERT(IsValid()); + + // TODO - SharedRGBImage just accesses the buffer without properly locking + // the texture. It's bad. + // MOZ_ASSERT(mIsLocked); + // if (!mIsLocked) { + // return nullptr; + //} + + return mData ? mData->BorrowMappedData(aMap) : false; +} + +bool TextureClient::BorrowMappedYCbCrData(MappedYCbCrTextureData& aMap) { + MOZ_ASSERT(IsValid()); + + return mData ? mData->BorrowMappedYCbCrData(aMap) : false; +} + +bool TextureClient::ToSurfaceDescriptor(SurfaceDescriptor& aOutDescriptor) { + MOZ_ASSERT(IsValid()); + + return mData ? mData->Serialize(aOutDescriptor) : false; +} + +// static +PTextureChild* TextureClient::CreateIPDLActor() { + TextureChild* c = new TextureChild(); + c->AddIPDLReference(); + return c; +} + +// static +bool TextureClient::DestroyIPDLActor(PTextureChild* actor) { + static_cast(actor)->ReleaseIPDLReference(); + return true; +} + +// static +already_AddRefed TextureClient::AsTextureClient( + PTextureChild* actor) { + if (!actor) { + return nullptr; + } + + TextureChild* tc = static_cast(actor); + + tc->Lock(); + + // Since TextureClient may be destroyed asynchronously with respect to its + // IPDL actor, we must acquire a reference within a lock. The mDestroyed bit + // tells us whether or not the main thread has disconnected the TextureClient + // from its actor. + if (tc->mDestroyed) { + tc->Unlock(); + return nullptr; + } + + RefPtr texture = tc->mTextureClient; + tc->Unlock(); + + return texture.forget(); +} + +bool TextureClient::IsSharedWithCompositor() const { + return mActor && mActor->IPCOpen(); +} + +void TextureClient::AddFlags(TextureFlags aFlags) { + MOZ_ASSERT( + !IsSharedWithCompositor() || + ((GetFlags() & TextureFlags::RECYCLE) && !IsAddedToCompositableClient())); + mFlags |= aFlags; +} + +void TextureClient::RemoveFlags(TextureFlags aFlags) { + MOZ_ASSERT( + !IsSharedWithCompositor() || + ((GetFlags() & TextureFlags::RECYCLE) && !IsAddedToCompositableClient())); + mFlags &= ~aFlags; +} + +void TextureClient::RecycleTexture(TextureFlags aFlags) { + MOZ_ASSERT(GetFlags() & TextureFlags::RECYCLE); + MOZ_ASSERT(!mIsLocked); + + mAddedToCompositableClient = false; + if (mFlags != aFlags) { + mFlags = aFlags; + } +} + +void TextureClient::SetAddedToCompositableClient() { + if (!mAddedToCompositableClient) { + mAddedToCompositableClient = true; + if (!(GetFlags() & TextureFlags::RECYCLE)) { + return; + } + MOZ_ASSERT(!mIsLocked); + LockActor(); + if (IsValid() && mActor && !mActor->mDestroyed && mActor->IPCOpen()) { + mActor->SendRecycleTexture(mFlags); + } + UnlockActor(); + } +} + +static void CancelTextureClientNotifyNotUsed(uint64_t aTextureId, + LayersIPCChannel* aAllocator) { + if (!aAllocator) { + return; + } + nsCOMPtr thread = aAllocator->GetThread(); + if (!thread) { + return; + } + if (thread->IsOnCurrentThread()) { + aAllocator->CancelWaitForNotifyNotUsed(aTextureId); + } else { + thread->Dispatch(NewRunnableFunction( + "CancelTextureClientNotifyNotUsedRunnable", + CancelTextureClientNotifyNotUsed, aTextureId, aAllocator)); + } +} + +void TextureClient::CancelWaitForNotifyNotUsed() { + if (GetFlags() & TextureFlags::RECYCLE) { + CancelTextureClientNotifyNotUsed(mSerial, GetAllocator()); + return; + } +} + +/* static */ +void TextureClient::TextureClientRecycleCallback(TextureClient* aClient, + void* aClosure) { + MOZ_ASSERT(aClient->GetRecycleAllocator()); + aClient->GetRecycleAllocator()->RecycleTextureClient(aClient); +} + +void TextureClient::SetRecycleAllocator( + ITextureClientRecycleAllocator* aAllocator) { + mRecycleAllocator = aAllocator; + if (aAllocator) { + SetRecycleCallback(TextureClientRecycleCallback, nullptr); + } else { + ClearRecycleCallback(); + } +} + +bool TextureClient::InitIPDLActor(CompositableForwarder* aForwarder) { + MOZ_ASSERT(aForwarder && aForwarder->GetTextureForwarder()->GetThread() == + mAllocator->GetThread()); + + if (mActor && !mActor->IPCOpen()) { + return false; + } + + if (mActor && !mActor->mDestroyed) { + CompositableForwarder* currentFwd = mActor->mCompositableForwarder; + TextureForwarder* currentTexFwd = mActor->mTextureForwarder; + if (currentFwd != aForwarder) { + // It's a bit iffy but right now ShadowLayerForwarder inherits + // TextureForwarder even though it should not. + // ShadowLayerForwarder::GetTextureForwarder actually returns a pointer to + // the CompositorBridgeChild. It's Ok for a texture to move from a + // ShadowLayerForwarder to another, but not form a CompositorBridgeChild + // to another (they use different channels). + if (currentTexFwd && currentTexFwd != aForwarder->GetTextureForwarder()) { + gfxCriticalError() + << "Attempt to move a texture to a different channel CF."; + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + return false; + } + if (currentFwd && currentFwd->GetCompositorBackendType() != + aForwarder->GetCompositorBackendType()) { + gfxCriticalError() + << "Attempt to move a texture to different compositor backend."; + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + return false; + } + if (ShadowLayerForwarder* forwarder = aForwarder->AsLayerForwarder()) { + // Do the DOM labeling. + if (nsISerialEventTarget* target = forwarder->GetEventTarget()) { + forwarder->GetCompositorBridgeChild()->ReplaceEventTargetForActor( + mActor, target); + } + } + mActor->mCompositableForwarder = aForwarder; + mActor->mUsesImageBridge = + aForwarder->GetTextureForwarder()->UsesImageBridge(); + } + return true; + } + MOZ_ASSERT(!mActor || mActor->mDestroyed, + "Cannot use a texture on several IPC channels."); + + SurfaceDescriptor desc; + if (!ToSurfaceDescriptor(desc)) { + return false; + } + + // Try external image id allocation. + mExternalImageId = + aForwarder->GetTextureForwarder()->GetNextExternalImageId(); + + nsISerialEventTarget* target = nullptr; + // Get the layers id if the forwarder is a ShadowLayerForwarder. + if (ShadowLayerForwarder* forwarder = aForwarder->AsLayerForwarder()) { + target = forwarder->GetEventTarget(); + } + + ReadLockDescriptor readLockDescriptor = null_t(); + if (mReadLock) { + mReadLock->Serialize(readLockDescriptor, GetAllocator()->GetParentPid()); + } + + PTextureChild* actor = aForwarder->GetTextureForwarder()->CreateTexture( + desc, readLockDescriptor, aForwarder->GetCompositorBackendType(), + GetFlags(), mSerial, mExternalImageId, target); + + if (!actor) { + gfxCriticalNote << static_cast(desc.type()) << ", " + << static_cast( + aForwarder->GetCompositorBackendType()) + << ", " << static_cast(GetFlags()) << ", " + << mSerial; + return false; + } + + mActor = static_cast(actor); + mActor->mCompositableForwarder = aForwarder; + mActor->mTextureForwarder = aForwarder->GetTextureForwarder(); + mActor->mTextureClient = this; + mActor->mMainThreadOnly = !!(mFlags & TextureFlags::DEALLOCATE_MAIN_THREAD); + + // If the TextureClient is already locked, we have to lock TextureChild's + // mutex since it will be unlocked in TextureClient::Unlock. + if (mIsLocked) { + LockActor(); + } + + return mActor->IPCOpen(); +} + +bool TextureClient::InitIPDLActor(KnowsCompositor* aKnowsCompositor) { + MOZ_ASSERT(aKnowsCompositor && + aKnowsCompositor->GetTextureForwarder()->GetThread() == + mAllocator->GetThread()); + TextureForwarder* fwd = aKnowsCompositor->GetTextureForwarder(); + if (mActor && !mActor->mDestroyed) { + CompositableForwarder* currentFwd = mActor->mCompositableForwarder; + TextureForwarder* currentTexFwd = mActor->mTextureForwarder; + + if (currentFwd) { + gfxCriticalError() + << "Attempt to remove a texture from a CompositableForwarder."; + return false; + } + + if (currentTexFwd && currentTexFwd != fwd) { + gfxCriticalError() + << "Attempt to move a texture to a different channel TF."; + return false; + } + mActor->mTextureForwarder = fwd; + return true; + } + MOZ_ASSERT(!mActor || mActor->mDestroyed, + "Cannot use a texture on several IPC channels."); + + SurfaceDescriptor desc; + if (!ToSurfaceDescriptor(desc)) { + return false; + } + + // Try external image id allocation. + mExternalImageId = + aKnowsCompositor->GetTextureForwarder()->GetNextExternalImageId(); + + ReadLockDescriptor readLockDescriptor = null_t(); + if (mReadLock) { + mReadLock->Serialize(readLockDescriptor, GetAllocator()->GetParentPid()); + } + + PTextureChild* actor = fwd->CreateTexture( + desc, readLockDescriptor, aKnowsCompositor->GetCompositorBackendType(), + GetFlags(), mSerial, mExternalImageId); + if (!actor) { + gfxCriticalNote << static_cast(desc.type()) << ", " + << static_cast( + aKnowsCompositor->GetCompositorBackendType()) + << ", " << static_cast(GetFlags()) << ", " + << mSerial; + return false; + } + + mActor = static_cast(actor); + mActor->mTextureForwarder = fwd; + mActor->mTextureClient = this; + mActor->mMainThreadOnly = !!(mFlags & TextureFlags::DEALLOCATE_MAIN_THREAD); + + // If the TextureClient is already locked, we have to lock TextureChild's + // mutex since it will be unlocked in TextureClient::Unlock. + if (mIsLocked) { + LockActor(); + } + + return mActor->IPCOpen(); +} + +PTextureChild* TextureClient::GetIPDLActor() { return mActor; } + +// static +already_AddRefed TextureClient::CreateForDrawing( + KnowsCompositor* aAllocator, gfx::SurfaceFormat aFormat, gfx::IntSize aSize, + BackendSelector aSelector, TextureFlags aTextureFlags, + TextureAllocationFlags aAllocFlags) { + if (aAllocator->SupportsTextureDirectMapping() && + std::max(aSize.width, aSize.height) <= aAllocator->GetMaxTextureSize()) { + aAllocFlags = + TextureAllocationFlags(aAllocFlags | ALLOC_ALLOW_DIRECT_MAPPING); + } + return TextureClient::CreateForDrawing(aAllocator->GetTextureForwarder(), + aFormat, aSize, aAllocator, aSelector, + aTextureFlags, aAllocFlags); +} + +// static +already_AddRefed TextureClient::CreateForDrawing( + TextureForwarder* aAllocator, gfx::SurfaceFormat aFormat, + gfx::IntSize aSize, KnowsCompositor* aKnowsCompositor, + BackendSelector aSelector, TextureFlags aTextureFlags, + TextureAllocationFlags aAllocFlags) { + LayersBackend layersBackend = aKnowsCompositor->GetCompositorBackendType(); + gfx::BackendType moz2DBackend = + BackendTypeForBackendSelector(layersBackend, aSelector); + + // also test the validity of aAllocator + if (!aAllocator || !aAllocator->IPCOpen()) { + return nullptr; + } + + if (!gfx::Factory::AllowedSurfaceSize(aSize)) { + return nullptr; + } + + TextureData* data = + TextureData::Create(aAllocator, aFormat, aSize, aKnowsCompositor, + aSelector, aTextureFlags, aAllocFlags); + + if (data) { + return MakeAndAddRef(data, aTextureFlags, aAllocator); + } + + // Can't do any better than a buffer texture client. + return TextureClient::CreateForRawBufferAccess(aAllocator, aFormat, aSize, + moz2DBackend, layersBackend, + aTextureFlags, aAllocFlags); +} + +// static +already_AddRefed TextureClient::CreateFromSurface( + KnowsCompositor* aAllocator, gfx::SourceSurface* aSurface, + BackendSelector aSelector, TextureFlags aTextureFlags, + TextureAllocationFlags aAllocFlags) { + // also test the validity of aAllocator + if (!aAllocator || !aAllocator->GetTextureForwarder()->IPCOpen()) { + return nullptr; + } + + gfx::IntSize size = aSurface->GetSize(); + + if (!gfx::Factory::AllowedSurfaceSize(size)) { + return nullptr; + } + + TextureData* data = nullptr; +#if defined(XP_WIN) + LayersBackend layersBackend = aAllocator->GetCompositorBackendType(); + gfx::BackendType moz2DBackend = + BackendTypeForBackendSelector(layersBackend, aSelector); + + int32_t maxTextureSize = aAllocator->GetMaxTextureSize(); + + if ((layersBackend == LayersBackend::LAYERS_D3D11 || + layersBackend == LayersBackend::LAYERS_WR) && + (moz2DBackend == gfx::BackendType::DIRECT2D || + moz2DBackend == gfx::BackendType::DIRECT2D1_1 || + (!!(aAllocFlags & ALLOC_FOR_OUT_OF_BAND_CONTENT) && + DeviceManagerDx::Get()->GetContentDevice())) && + size.width <= maxTextureSize && size.height <= maxTextureSize) { + data = D3D11TextureData::Create(aSurface, aAllocFlags); + } +#endif + + if (data) { + return MakeAndAddRef(data, aTextureFlags, + aAllocator->GetTextureForwarder()); + } + + // Fall back to using UpdateFromSurface + + TextureAllocationFlags allocFlags = + TextureAllocationFlags(aAllocFlags | ALLOC_UPDATE_FROM_SURFACE); + RefPtr client = + CreateForDrawing(aAllocator, aSurface->GetFormat(), size, aSelector, + aTextureFlags, allocFlags); + if (!client) { + return nullptr; + } + + TextureClientAutoLock autoLock(client, OpenMode::OPEN_WRITE_ONLY); + if (!autoLock.Succeeded()) { + return nullptr; + } + + client->UpdateFromSurface(aSurface); + return client.forget(); +} + +// static +already_AddRefed TextureClient::CreateForRawBufferAccess( + KnowsCompositor* aAllocator, gfx::SurfaceFormat aFormat, gfx::IntSize aSize, + gfx::BackendType aMoz2DBackend, TextureFlags aTextureFlags, + TextureAllocationFlags aAllocFlags) { + // If we exceed the max texture size for the GPU, then just fall back to no + // texture direct mapping. If it becomes a problem we can implement tiling + // logic inside DirectMapTextureSource to allow this. + bool supportsTextureDirectMapping = + aAllocator->SupportsTextureDirectMapping() && + std::max(aSize.width, aSize.height) <= aAllocator->GetMaxTextureSize(); + if (supportsTextureDirectMapping) { + aAllocFlags = + TextureAllocationFlags(aAllocFlags | ALLOC_ALLOW_DIRECT_MAPPING); + } else { + aAllocFlags = + TextureAllocationFlags(aAllocFlags & ~ALLOC_ALLOW_DIRECT_MAPPING); + } + return CreateForRawBufferAccess( + aAllocator->GetTextureForwarder(), aFormat, aSize, aMoz2DBackend, + aAllocator->GetCompositorBackendType(), aTextureFlags, aAllocFlags); +} + +// static +already_AddRefed TextureClient::CreateForRawBufferAccess( + LayersIPCChannel* aAllocator, gfx::SurfaceFormat aFormat, + gfx::IntSize aSize, gfx::BackendType aMoz2DBackend, + LayersBackend aLayersBackend, TextureFlags aTextureFlags, + TextureAllocationFlags aAllocFlags) { + // also test the validity of aAllocator + if (!aAllocator || !aAllocator->IPCOpen()) { + return nullptr; + } + + if (aAllocFlags & ALLOC_DISALLOW_BUFFERTEXTURECLIENT) { + return nullptr; + } + + if (!gfx::Factory::AllowedSurfaceSize(aSize)) { + return nullptr; + } + + if (aFormat == SurfaceFormat::B8G8R8X8) { + // Skia doesn't support RGBX, so ensure we clear the buffer for the proper + // alpha values. + aAllocFlags = TextureAllocationFlags(aAllocFlags | ALLOC_CLEAR_BUFFER); + } + + // Note that we ignore the backend type if we get here. It should only be D2D + // or Skia, and D2D does not support data surfaces. Therefore it is safe to + // force the buffer to be Skia. + NS_WARNING_ASSERTION(aMoz2DBackend == gfx::BackendType::SKIA || + aMoz2DBackend == gfx::BackendType::DIRECT2D || + aMoz2DBackend == gfx::BackendType::DIRECT2D1_1, + "Unsupported TextureClient backend type"); + + TextureData* texData = BufferTextureData::Create( + aSize, aFormat, gfx::BackendType::SKIA, aLayersBackend, aTextureFlags, + aAllocFlags, aAllocator); + if (!texData) { + return nullptr; + } + + return MakeAndAddRef(texData, aTextureFlags, aAllocator); +} + +// static +already_AddRefed TextureClient::CreateForYCbCr( + KnowsCompositor* aAllocator, const gfx::IntRect& aDisplay, + const gfx::IntSize& aYSize, uint32_t aYStride, + const gfx::IntSize& aCbCrSize, uint32_t aCbCrStride, StereoMode aStereoMode, + gfx::ColorDepth aColorDepth, gfx::YUVColorSpace aYUVColorSpace, + gfx::ColorRange aColorRange, TextureFlags aTextureFlags) { + if (!aAllocator || !aAllocator->GetLayersIPCActor()->IPCOpen()) { + return nullptr; + } + + if (!gfx::Factory::AllowedSurfaceSize(aYSize)) { + return nullptr; + } + + TextureData* data = BufferTextureData::CreateForYCbCr( + aAllocator, aDisplay, aYSize, aYStride, aCbCrSize, aCbCrStride, + aStereoMode, aColorDepth, aYUVColorSpace, aColorRange, aTextureFlags); + if (!data) { + return nullptr; + } + + return MakeAndAddRef(data, aTextureFlags, + aAllocator->GetTextureForwarder()); +} + +TextureClient::TextureClient(TextureData* aData, TextureFlags aFlags, + LayersIPCChannel* aAllocator) + : AtomicRefCountedWithFinalize("TextureClient"), + mAllocator(aAllocator), + mActor(nullptr), + mData(aData), + mFlags(aFlags), + mOpenMode(OpenMode::OPEN_NONE) +#ifdef DEBUG + , + mExpectedDtRefs(0) +#endif + , + mIsLocked(false), + mIsReadLocked(false), + mUpdated(false), + mAddedToCompositableClient(false), + mWorkaroundAnnoyingSharedSurfaceLifetimeIssues(false), + mWorkaroundAnnoyingSharedSurfaceOwnershipIssues(false), + mFwdTransactionId(0), + mSerial(++sSerialCounter) +#ifdef GFX_DEBUG_TRACK_CLIENTS_IN_POOL + , + mPoolTracker(nullptr) +#endif +{ + mData->FillInfo(mInfo); + mFlags |= mData->GetTextureFlags(); + + if (mFlags & TextureFlags::NON_BLOCKING_READ_LOCK) { + MOZ_ASSERT(!(mFlags & TextureFlags::BLOCKING_READ_LOCK)); + EnableReadLock(); + } else if (mFlags & TextureFlags::BLOCKING_READ_LOCK) { + MOZ_ASSERT(!(mFlags & TextureFlags::NON_BLOCKING_READ_LOCK)); + EnableBlockingReadLock(); + } +} + +bool TextureClient::CopyToTextureClient(TextureClient* aTarget, + const gfx::IntRect* aRect, + const gfx::IntPoint* aPoint) { + MOZ_ASSERT(IsLocked()); + MOZ_ASSERT(aTarget->IsLocked()); + + if (!aTarget->CanExposeDrawTarget() || !CanExposeDrawTarget()) { + return false; + } + + RefPtr destinationTarget = aTarget->BorrowDrawTarget(); + if (!destinationTarget) { + gfxWarning() << "TextureClient::CopyToTextureClient (dest) failed in " + "BorrowDrawTarget"; + return false; + } + + RefPtr sourceTarget = BorrowDrawTarget(); + if (!sourceTarget) { + gfxWarning() << "TextureClient::CopyToTextureClient (src) failed in " + "BorrowDrawTarget"; + return false; + } + + RefPtr source = sourceTarget->Snapshot(); + destinationTarget->CopySurface( + source, aRect ? *aRect : gfx::IntRect(gfx::IntPoint(0, 0), GetSize()), + aPoint ? *aPoint : gfx::IntPoint(0, 0)); + return true; +} + +already_AddRefed TextureClient::GetAsSurface() { + if (!Lock(OpenMode::OPEN_READ)) { + return nullptr; + } + RefPtr data; + { // scope so that the DrawTarget is destroyed before Unlock() + RefPtr dt = BorrowDrawTarget(); + if (dt) { + RefPtr surf = dt->Snapshot(); + if (surf) { + data = surf->GetDataSurface(); + } + } + } + Unlock(); + return data.forget(); +} + +void TextureClient::PrintInfo(std::stringstream& aStream, const char* aPrefix) { + aStream << aPrefix; + aStream << nsPrintfCString("TextureClient (0x%p)", this).get() + << " [size=" << GetSize() << "]" + << " [format=" << GetFormat() << "]" + << " [flags=" << mFlags << "]"; + +#ifdef MOZ_DUMP_PAINTING + if (StaticPrefs::layers_dump_texture()) { + nsAutoCString pfx(aPrefix); + pfx += " "; + + aStream << "\n" << pfx.get() << "Surface: "; + RefPtr dSurf = GetAsSurface(); + if (dSurf) { + aStream << gfxUtils::GetAsLZ4Base64Str(dSurf).get(); + } + } +#endif +} + +void TextureClient::GetSurfaceDescriptorRemoteDecoder( + SurfaceDescriptorRemoteDecoder* const aOutDesc) { + const auto handle = GetSerial(); + + RemoteDecoderVideoSubDescriptor subDesc = null_t(); + MOZ_RELEASE_ASSERT(mData); + mData->GetSubDescriptor(&subDesc); + + *aOutDesc = + SurfaceDescriptorRemoteDecoder(handle, std::move(subDesc), Nothing()); +} + +class MemoryTextureReadLock : public NonBlockingTextureReadLock { + public: + MemoryTextureReadLock(); + + virtual ~MemoryTextureReadLock(); + + bool ReadLock() override; + + int32_t ReadUnlock() override; + + int32_t GetReadCount() override; + + LockType GetType() override { return TYPE_NONBLOCKING_MEMORY; } + + bool IsValid() const override { return true; }; + + bool Serialize(ReadLockDescriptor& aOutput, base::ProcessId aOther) override; + + Atomic mReadCount; +}; + +// The cross-prcess implementation of TextureReadLock. +// +// Since we don't use cross-process reference counting for the ReadLock objects, +// we use the lock's internal counter as a way to know when to deallocate the +// underlying shmem section: when the counter is equal to 1, it means that the +// lock is not "held" (the texture is writable), when the counter is equal to 0 +// it means that we can safely deallocate the shmem section without causing a +// race condition with the other process. +class ShmemTextureReadLock : public NonBlockingTextureReadLock { + public: + struct ShmReadLockInfo { + int32_t readCount; + }; + + explicit ShmemTextureReadLock(LayersIPCChannel* aAllocator); + + virtual ~ShmemTextureReadLock(); + + bool ReadLock() override; + + int32_t ReadUnlock() override; + + int32_t GetReadCount() override; + + bool IsValid() const override { return mAllocSuccess; }; + + LockType GetType() override { return TYPE_NONBLOCKING_SHMEM; } + + bool Serialize(ReadLockDescriptor& aOutput, base::ProcessId aOther) override; + + mozilla::layers::ShmemSection& GetShmemSection() { return mShmemSection; } + + explicit ShmemTextureReadLock( + const mozilla::layers::ShmemSection& aShmemSection) + : mShmemSection(aShmemSection), mAllocSuccess(true) { + MOZ_COUNT_CTOR(ShmemTextureReadLock); + } + + ShmReadLockInfo* GetShmReadLockInfoPtr() { + return reinterpret_cast( + mShmemSection.shmem().get() + mShmemSection.offset()); + } + + RefPtr mClientAllocator; + mozilla::layers::ShmemSection mShmemSection; + bool mAllocSuccess; +}; + +class CrossProcessSemaphoreReadLock : public TextureReadLock { + public: + CrossProcessSemaphoreReadLock() + : mSemaphore(CrossProcessSemaphore::Create("TextureReadLock", 1)), + mShared(false) {} + explicit CrossProcessSemaphoreReadLock(CrossProcessSemaphoreHandle aHandle) + : mSemaphore(CrossProcessSemaphore::Create(aHandle)), mShared(false) {} + + bool ReadLock() override { + if (!IsValid()) { + return false; + } + return mSemaphore->Wait(); + } + bool TryReadLock(TimeDuration aTimeout) override { + if (!IsValid()) { + return false; + } + return mSemaphore->Wait(Some(aTimeout)); + } + int32_t ReadUnlock() override { + if (!IsValid()) { + return 1; + } + mSemaphore->Signal(); + return 1; + } + bool IsValid() const override { return !!mSemaphore; } + + bool Serialize(ReadLockDescriptor& aOutput, base::ProcessId aOther) override; + + LockType GetType() override { return TYPE_CROSS_PROCESS_SEMAPHORE; } + + UniquePtr mSemaphore; + bool mShared; +}; + +// static +already_AddRefed TextureReadLock::Deserialize( + const ReadLockDescriptor& aDescriptor, ISurfaceAllocator* aAllocator) { + switch (aDescriptor.type()) { + case ReadLockDescriptor::TShmemSection: { + const ShmemSection& section = aDescriptor.get_ShmemSection(); + MOZ_RELEASE_ASSERT(section.shmem().IsReadable()); + return MakeAndAddRef(section); + } + case ReadLockDescriptor::Tuintptr_t: { + if (!aAllocator->IsSameProcess()) { + // Trying to use a memory based lock instead of a shmem based one in + // the cross-process case is a bad security violation. + NS_ERROR( + "A client process may be trying to peek at the host's address " + "space!"); + return nullptr; + } + RefPtr lock = + reinterpret_cast(aDescriptor.get_uintptr_t()); + + MOZ_ASSERT(lock); + if (lock) { + // The corresponding AddRef is in MemoryTextureReadLock::Serialize + lock.get()->Release(); + } + + return lock.forget(); + } + case ReadLockDescriptor::TCrossProcessSemaphoreDescriptor: { + return MakeAndAddRef( + aDescriptor.get_CrossProcessSemaphoreDescriptor().sem()); + } + case ReadLockDescriptor::Tnull_t: { + return nullptr; + } + default: { + // Invalid descriptor. + MOZ_DIAGNOSTIC_ASSERT(false); + } + } + return nullptr; +} +// static +already_AddRefed NonBlockingTextureReadLock::Create( + LayersIPCChannel* aAllocator) { + if (aAllocator->IsSameProcess()) { + // If our compositor is in the same process, we can save some cycles by not + // using shared memory. + return MakeAndAddRef(); + } + + return MakeAndAddRef(aAllocator); +} + +MemoryTextureReadLock::MemoryTextureReadLock() : mReadCount(1) { + MOZ_COUNT_CTOR(MemoryTextureReadLock); +} + +MemoryTextureReadLock::~MemoryTextureReadLock() { + // One read count that is added in constructor. + MOZ_ASSERT(mReadCount == 1); + MOZ_COUNT_DTOR(MemoryTextureReadLock); +} + +bool MemoryTextureReadLock::Serialize(ReadLockDescriptor& aOutput, + base::ProcessId aOther) { + // AddRef here and Release when receiving on the host side to make sure the + // reference count doesn't go to zero before the host receives the message. + // see TextureReadLock::Deserialize + this->AddRef(); + aOutput = ReadLockDescriptor(uintptr_t(this)); + return true; +} + +bool MemoryTextureReadLock::ReadLock() { + NS_ASSERT_OWNINGTHREAD(MemoryTextureReadLock); + + ++mReadCount; + return true; +} + +int32_t MemoryTextureReadLock::ReadUnlock() { + int32_t readCount = --mReadCount; + MOZ_ASSERT(readCount >= 0); + + return readCount; +} + +int32_t MemoryTextureReadLock::GetReadCount() { + NS_ASSERT_OWNINGTHREAD(MemoryTextureReadLock); + return mReadCount; +} + +ShmemTextureReadLock::ShmemTextureReadLock(LayersIPCChannel* aAllocator) + : mClientAllocator(aAllocator), mAllocSuccess(false) { + MOZ_COUNT_CTOR(ShmemTextureReadLock); + MOZ_ASSERT(mClientAllocator); + MOZ_ASSERT(mClientAllocator->GetTileLockAllocator()); +#define MOZ_ALIGN_WORD(x) (((x) + 3) & ~3) + if (mClientAllocator->GetTileLockAllocator()->AllocShmemSection( + MOZ_ALIGN_WORD(sizeof(ShmReadLockInfo)), &mShmemSection)) { + ShmReadLockInfo* info = GetShmReadLockInfoPtr(); + info->readCount = 1; + mAllocSuccess = true; + } +} + +ShmemTextureReadLock::~ShmemTextureReadLock() { + if (mClientAllocator) { + // Release one read count that is added in constructor. + // The count is kept for calling GetReadCount() by TextureClientPool. + ReadUnlock(); + } + MOZ_COUNT_DTOR(ShmemTextureReadLock); +} + +bool ShmemTextureReadLock::Serialize(ReadLockDescriptor& aOutput, + base::ProcessId aOther) { + aOutput = ReadLockDescriptor(GetShmemSection()); + return true; +} + +bool ShmemTextureReadLock::ReadLock() { + NS_ASSERT_OWNINGTHREAD(ShmemTextureReadLock); + if (!mAllocSuccess) { + return false; + } + ShmReadLockInfo* info = GetShmReadLockInfoPtr(); + PR_ATOMIC_INCREMENT(&info->readCount); + return true; +} + +int32_t ShmemTextureReadLock::ReadUnlock() { + if (!mAllocSuccess) { + return 0; + } + ShmReadLockInfo* info = GetShmReadLockInfoPtr(); + int32_t readCount = PR_ATOMIC_DECREMENT(&info->readCount); + MOZ_ASSERT(readCount >= 0); + if (readCount <= 0) { + if (mClientAllocator && mClientAllocator->GetTileLockAllocator()) { + mClientAllocator->GetTileLockAllocator()->DeallocShmemSection( + mShmemSection); + } else { + // we are on the compositor process, or IPC is down. + FixedSizeSmallShmemSectionAllocator::FreeShmemSection(mShmemSection); + } + } + return readCount; +} + +int32_t ShmemTextureReadLock::GetReadCount() { + NS_ASSERT_OWNINGTHREAD(ShmemTextureReadLock); + if (!mAllocSuccess) { + return 0; + } + ShmReadLockInfo* info = GetShmReadLockInfoPtr(); + return info->readCount; +} + +bool CrossProcessSemaphoreReadLock::Serialize(ReadLockDescriptor& aOutput, + base::ProcessId aOther) { + if (!mShared && IsValid()) { + aOutput = ReadLockDescriptor( + CrossProcessSemaphoreDescriptor(mSemaphore->ShareToProcess(aOther))); + mSemaphore->CloseHandle(); + mShared = true; + return true; + } else { + return mShared; + } +} + +void TextureClient::EnableBlockingReadLock() { + if (!mReadLock) { + mReadLock = new CrossProcessSemaphoreReadLock(); + } +} + +void TextureClient::AddPaintThreadRef() { + MOZ_ASSERT(NS_IsMainThread()); + mPaintThreadRefs += 1; +} + +void TextureClient::DropPaintThreadRef() { + MOZ_RELEASE_ASSERT(PaintThread::Get()->IsOnPaintWorkerThread()); + MOZ_RELEASE_ASSERT(mPaintThreadRefs >= 1); + mPaintThreadRefs -= 1; +} + +bool UpdateYCbCrTextureClient(TextureClient* aTexture, + const PlanarYCbCrData& aData) { + MOZ_ASSERT(aTexture); + MOZ_ASSERT(aTexture->IsLocked()); + MOZ_ASSERT(aTexture->GetFormat() == gfx::SurfaceFormat::YUV, + "This textureClient can only use YCbCr data"); + MOZ_ASSERT(!aTexture->IsImmutable()); + MOZ_ASSERT(aTexture->IsValid()); + MOZ_ASSERT(aData.mCbSkip == aData.mCrSkip); + + MappedYCbCrTextureData mapped; + if (!aTexture->BorrowMappedYCbCrData(mapped)) { + NS_WARNING("Failed to extract YCbCr info!"); + return false; + } + + uint32_t bytesPerPixel = + BytesPerPixel(SurfaceFormatForColorDepth(aData.mColorDepth)); + MappedYCbCrTextureData srcData; + srcData.y.data = aData.mYChannel; + srcData.y.size = aData.mYSize; + srcData.y.stride = aData.mYStride; + srcData.y.skip = aData.mYSkip; + srcData.y.bytesPerPixel = bytesPerPixel; + srcData.cb.data = aData.mCbChannel; + srcData.cb.size = aData.mCbCrSize; + srcData.cb.stride = aData.mCbCrStride; + srcData.cb.skip = aData.mCbSkip; + srcData.cb.bytesPerPixel = bytesPerPixel; + srcData.cr.data = aData.mCrChannel; + srcData.cr.size = aData.mCbCrSize; + srcData.cr.stride = aData.mCbCrStride; + srcData.cr.skip = aData.mCrSkip; + srcData.cr.bytesPerPixel = bytesPerPixel; + srcData.metadata = nullptr; + + if (!srcData.CopyInto(mapped)) { + NS_WARNING("Failed to copy image data!"); + return false; + } + + if (TextureRequiresLocking(aTexture->GetFlags())) { + // We don't have support for proper locking yet, so we'll + // have to be immutable instead. + aTexture->MarkImmutable(); + } + return true; +} + +already_AddRefed TextureClient::CreateWithData( + TextureData* aData, TextureFlags aFlags, LayersIPCChannel* aAllocator) { + if (!aData) { + return nullptr; + } + return MakeAndAddRef(aData, aFlags, aAllocator); +} + +template +static void copyData(PixelDataType* aDst, + const MappedYCbCrChannelData& aChannelDst, + PixelDataType* aSrc, + const MappedYCbCrChannelData& aChannelSrc) { + uint8_t* srcByte = reinterpret_cast(aSrc); + const int32_t srcSkip = aChannelSrc.skip + 1; + uint8_t* dstByte = reinterpret_cast(aDst); + const int32_t dstSkip = aChannelDst.skip + 1; + for (int32_t i = 0; i < aChannelSrc.size.height; ++i) { + for (int32_t j = 0; j < aChannelSrc.size.width; ++j) { + *aDst = *aSrc; + aSrc += srcSkip; + aDst += dstSkip; + } + srcByte += aChannelSrc.stride; + aSrc = reinterpret_cast(srcByte); + dstByte += aChannelDst.stride; + aDst = reinterpret_cast(dstByte); + } +} + +bool MappedYCbCrChannelData::CopyInto(MappedYCbCrChannelData& aDst) { + if (!data || !aDst.data || size != aDst.size) { + return false; + } + + if (stride == aDst.stride && skip == aDst.skip) { + // fast path! + // We assume that the padding in the destination is there for alignment + // purposes and doesn't contain useful data. + memcpy(aDst.data, data, stride * size.height); + return true; + } + + if (aDst.skip == 0 && skip == 0) { + // fast-ish path + for (int32_t i = 0; i < size.height; ++i) { + memcpy(aDst.data + i * aDst.stride, data + i * stride, + size.width * bytesPerPixel); + } + return true; + } + + MOZ_ASSERT(bytesPerPixel == 1 || bytesPerPixel == 2); + // slow path + if (bytesPerPixel == 1) { + copyData(aDst.data, aDst, data, *this); + } else if (bytesPerPixel == 2) { + copyData(reinterpret_cast(aDst.data), aDst, + reinterpret_cast(data), *this); + } + return true; +} + +} // namespace mozilla::layers diff --git a/gfx/layers/client/TextureClient.h b/gfx/layers/client/TextureClient.h new file mode 100644 index 0000000000..e8812bf6d3 --- /dev/null +++ b/gfx/layers/client/TextureClient.h @@ -0,0 +1,932 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_TEXTURECLIENT_H +#define MOZILLA_GFX_TEXTURECLIENT_H + +#include // for size_t +#include // for uint32_t, uint8_t, uint64_t + +#include "GLTextureImage.h" // for TextureImage +#include "GfxTexturesReporter.h" +#include "ImageTypes.h" // for StereoMode +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" // for override +#include "mozilla/DebugOnly.h" +#include "mozilla/RefPtr.h" // for RefPtr, RefCounted +#include "mozilla/gfx/2D.h" // for DrawTarget +#include "mozilla/gfx/CriticalSection.h" +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/gfx/Types.h" // for SurfaceFormat +#include "mozilla/ipc/FileDescriptor.h" +#include "mozilla/ipc/Shmem.h" // for Shmem +#include "mozilla/layers/AtomicRefCountedWithFinalize.h" +#include "mozilla/layers/CompositorTypes.h" // for TextureFlags, etc +#include "mozilla/layers/ISurfaceAllocator.h" +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor +#include "mozilla/layers/LayersTypes.h" +#include "mozilla/layers/SyncObject.h" +#include "mozilla/mozalloc.h" // for operator delete +#include "mozilla/webrender/WebRenderTypes.h" +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsISupportsImpl.h" // for TextureImage::AddRef, etc +#include "nsThreadUtils.h" +#include "pratom.h" + +class gfxImageSurface; +struct ID3D11Device; + +namespace mozilla { + +// When defined, we track which pool the tile came from and test for +// any inconsistencies. This can be defined in release build as well. +#ifdef DEBUG +# define GFX_DEBUG_TRACK_CLIENTS_IN_POOL 1 +#endif + +namespace layers { + +class AndroidHardwareBufferTextureData; +class BufferTextureData; +class CompositableForwarder; +class KnowsCompositor; +class LayersIPCChannel; +class CompositableClient; +struct PlanarYCbCrData; +class Image; +class PTextureChild; +class TextureChild; +class TextureData; +class GPUVideoTextureData; +struct RawTextureBuffer; +class RawYCbCrTextureBuffer; +class TextureClient; +class ITextureClientRecycleAllocator; +#ifdef GFX_DEBUG_TRACK_CLIENTS_IN_POOL +class TextureClientPool; +#endif +class TextureForwarder; +class KeepAlive; + +/** + * TextureClient is the abstraction that allows us to share data between the + * content and the compositor side. + */ + +enum TextureAllocationFlags { + ALLOC_DEFAULT = 0, + ALLOC_CLEAR_BUFFER = + 1 << 1, // Clear the buffer to whatever is best for the draw target + ALLOC_CLEAR_BUFFER_WHITE = 1 << 2, // explicit all white + ALLOC_CLEAR_BUFFER_BLACK = 1 << 3, // explicit all black + ALLOC_DISALLOW_BUFFERTEXTURECLIENT = 1 << 4, + + // Allocate the texture for out-of-band content updates. This is mostly for + // TextureClientD3D11, which may otherwise choose D3D10 or non-KeyedMutex + // surfaces when used on the main thread. + ALLOC_FOR_OUT_OF_BAND_CONTENT = 1 << 5, + + // Disable any cross-device synchronization. This is also for + // TextureClientD3D11, and creates a texture without KeyedMutex. + ALLOC_MANUAL_SYNCHRONIZATION = 1 << 6, + + // The texture is going to be updated using UpdateFromSurface and needs to + // support that call. + ALLOC_UPDATE_FROM_SURFACE = 1 << 7, + + // In practice, this means we support the APPLE_client_storage extension, + // meaning the buffer will not be internally copied by the graphics driver. + ALLOC_ALLOW_DIRECT_MAPPING = 1 << 8, +}; + +/** + * This class may be used to asynchronously receive an update when the content + * drawn to this texture client is available for reading in CPU memory. This + * can only be used on texture clients that support draw target creation. + */ +class TextureReadbackSink { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TextureReadbackSink) + public: + /** + * Callback function to implement in order to receive a DataSourceSurface + * containing the data read back from the texture client. This will always + * be called on the main thread, and this may not hold on to the + * DataSourceSurface beyond the execution of this function. + */ + virtual void ProcessReadback(gfx::DataSourceSurface* aSourceSurface) = 0; + + protected: + virtual ~TextureReadbackSink() = default; +}; + +enum class BackendSelector { Content, Canvas }; + +/// Temporary object providing direct access to a Texture's memory. +/// +/// see TextureClient::CanExposeMappedData() and +/// TextureClient::BorrowMappedData(). +struct MappedTextureData { + uint8_t* data; + gfx::IntSize size; + int32_t stride; + gfx::SurfaceFormat format; +}; + +struct MappedYCbCrChannelData { + uint8_t* data; + gfx::IntSize size; + int32_t stride; + int32_t skip; + uint32_t bytesPerPixel; + + bool CopyInto(MappedYCbCrChannelData& aDst); +}; + +struct MappedYCbCrTextureData { + MappedYCbCrChannelData y; + MappedYCbCrChannelData cb; + MappedYCbCrChannelData cr; + // Sad but because of how SharedPlanarYCbCrData is used we have to expose this + // for now. + uint8_t* metadata; + StereoMode stereoMode; + + bool CopyInto(MappedYCbCrTextureData& aDst) { + return y.CopyInto(aDst.y) && cb.CopyInto(aDst.cb) && cr.CopyInto(aDst.cr); + } +}; + +class ReadLockDescriptor; +class NonBlockingTextureReadLock; + +// A class to help implement copy-on-write semantics for shared textures. +// +// A TextureClient/Host pair can opt into using a ReadLock by calling +// TextureClient::EnableReadLock. This will equip the TextureClient with a +// ReadLock object that will be automatically ReadLock()'ed by the texture +// itself when it is written into (see TextureClient::Unlock). A +// TextureReadLock's counter starts at 1 and is expected to be equal to 1 when +// the lock is destroyed. See ShmemTextureReadLock for explanations about why we +// use 1 instead of 0 as the initial state. TextureReadLock is mostly internally +// managed by the TextureClient/Host pair, and the compositable only has to +// forward it during updates. If an update message contains a null_t lock, it +// means that the texture was not written into on the content side, and there is +// no synchronization required on the compositor side (or it means that the +// texture pair did not opt into using ReadLocks). On the compositor side, the +// TextureHost can receive a ReadLock during a transaction, and will both +// ReadUnlock() it and drop it as soon as the shared data is available again for +// writing (the texture upload is done, or the compositor not reading the +// texture anymore). The lock is dropped to make sure it is ReadUnlock()'ed only +// once. +class TextureReadLock { + protected: + virtual ~TextureReadLock() = default; + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TextureReadLock) + + virtual bool ReadLock() = 0; + virtual bool TryReadLock(TimeDuration aTimeout) { return ReadLock(); } + virtual int32_t ReadUnlock() = 0; + virtual bool IsValid() const = 0; + + static already_AddRefed Deserialize( + const ReadLockDescriptor& aDescriptor, ISurfaceAllocator* aAllocator); + + virtual bool Serialize(ReadLockDescriptor& aOutput, + base::ProcessId aOther) = 0; + + enum LockType { + TYPE_NONBLOCKING_MEMORY, + TYPE_NONBLOCKING_SHMEM, + TYPE_CROSS_PROCESS_SEMAPHORE + }; + virtual LockType GetType() = 0; + + virtual NonBlockingTextureReadLock* AsNonBlockingLock() { return nullptr; } + + protected: + NS_DECL_OWNINGTHREAD +}; + +class NonBlockingTextureReadLock : public TextureReadLock { + public: + virtual int32_t GetReadCount() = 0; + + static already_AddRefed Create(LayersIPCChannel* aAllocator); + + NonBlockingTextureReadLock* AsNonBlockingLock() override { return this; } +}; + +#ifdef XP_WIN +class D3D11TextureData; +class DXGIYCbCrTextureData; +#endif + +class TextureData { + public: + struct Info { + gfx::IntSize size; + gfx::SurfaceFormat format; + bool hasIntermediateBuffer; + bool hasSynchronization; + bool supportsMoz2D; + bool canExposeMappedData; + bool canConcurrentlyReadLock; + + Info() + : format(gfx::SurfaceFormat::UNKNOWN), + hasIntermediateBuffer(false), + hasSynchronization(false), + supportsMoz2D(false), + canExposeMappedData(false), + canConcurrentlyReadLock(true) {} + }; + + static TextureData* Create(TextureForwarder* aAllocator, + gfx::SurfaceFormat aFormat, gfx::IntSize aSize, + KnowsCompositor* aKnowsCompositor, + BackendSelector aSelector, + TextureFlags aTextureFlags, + TextureAllocationFlags aAllocFlags); + + static bool IsRemote(KnowsCompositor* aKnowsCompositor, + BackendSelector aSelector); + + MOZ_COUNTED_DTOR_VIRTUAL(TextureData) + + virtual void FillInfo(TextureData::Info& aInfo) const = 0; + + virtual bool Lock(OpenMode aMode) = 0; + + virtual void Unlock() = 0; + + virtual already_AddRefed BorrowDrawTarget() { + return nullptr; + } + + virtual already_AddRefed BorrowSnapshot() { + return nullptr; + } + + virtual bool BorrowMappedData(MappedTextureData&) { return false; } + + virtual bool BorrowMappedYCbCrData(MappedYCbCrTextureData&) { return false; } + + virtual void Deallocate(LayersIPCChannel* aAllocator) = 0; + + /// Depending on the texture's flags either Deallocate or Forget is called. + virtual void Forget(LayersIPCChannel* aAllocator) {} + + virtual bool Serialize(SurfaceDescriptor& aDescriptor) = 0; + virtual void GetSubDescriptor(RemoteDecoderVideoSubDescriptor* aOutDesc) {} + + virtual void OnForwardedToHost() {} + + virtual TextureData* CreateSimilar( + LayersIPCChannel* aAllocator, LayersBackend aLayersBackend, + TextureFlags aFlags = TextureFlags::DEFAULT, + TextureAllocationFlags aAllocFlags = ALLOC_DEFAULT) const { + return nullptr; + } + + virtual bool UpdateFromSurface(gfx::SourceSurface* aSurface) { + return false; + }; + + virtual bool ReadBack(TextureReadbackSink* aReadbackSink) { return false; } + + virtual void SyncWithObject(RefPtr aSyncObject){}; + + virtual TextureFlags GetTextureFlags() const { + return TextureFlags::NO_FLAGS; + } + +#ifdef XP_WIN + virtual D3D11TextureData* AsD3D11TextureData() { return nullptr; } + virtual DXGIYCbCrTextureData* AsDXGIYCbCrTextureData() { return nullptr; } +#endif + + virtual BufferTextureData* AsBufferTextureData() { return nullptr; } + + virtual GPUVideoTextureData* AsGPUVideoTextureData() { return nullptr; } + + virtual AndroidHardwareBufferTextureData* + AsAndroidHardwareBufferTextureData() { + return nullptr; + } + + // It is used by AndroidHardwareBufferTextureData and + // SharedSurfaceTextureData. Returns buffer id when it owns + // AndroidHardwareBuffer. It is used only on android. + virtual Maybe GetBufferId() const { return Nothing(); } + + // The acquire fence is a fence that is used for waiting until rendering to + // its AHardwareBuffer is completed. + // It is used only on android. + virtual mozilla::ipc::FileDescriptor GetAcquireFence() { + return mozilla::ipc::FileDescriptor(); + } + + protected: + MOZ_COUNTED_DEFAULT_CTOR(TextureData) +}; + +/** + * TextureClient is a thin abstraction over texture data that need to be shared + * between the content process and the compositor process. It is the + * content-side half of a TextureClient/TextureHost pair. A corresponding + * TextureHost lives on the compositor-side. + * + * TextureClient's primary purpose is to present texture data in a way that is + * understood by the IPC system. There are two ways to use it: + * - Use it to serialize image data that is not IPC-friendly (most likely + * involving a copy into shared memory) + * - preallocate it and paint directly into it, which avoids copy but requires + * the painting code to be aware of TextureClient (or at least the underlying + * shared memory). + * + * There is always one and only one TextureClient per TextureHost, and the + * TextureClient/Host pair only owns one buffer of image data through its + * lifetime. This means that the lifetime of the underlying shared data + * matches the lifetime of the TextureClient/Host pair. It also means + * TextureClient/Host do not implement double buffering, which is the + * responsibility of the compositable (which would use pairs of Textures). + * In order to send several different buffers to the compositor side, use + * several TextureClients. + */ +class TextureClient : public AtomicRefCountedWithFinalize { + public: + TextureClient(TextureData* aData, TextureFlags aFlags, + LayersIPCChannel* aAllocator); + + virtual ~TextureClient(); + + static already_AddRefed CreateWithData( + TextureData* aData, TextureFlags aFlags, LayersIPCChannel* aAllocator); + + // Creates and allocates a TextureClient usable with Moz2D. + static already_AddRefed CreateForDrawing( + KnowsCompositor* aAllocator, gfx::SurfaceFormat aFormat, + gfx::IntSize aSize, BackendSelector aSelector, TextureFlags aTextureFlags, + TextureAllocationFlags flags = ALLOC_DEFAULT); + + static already_AddRefed CreateFromSurface( + KnowsCompositor* aAllocator, gfx::SourceSurface* aSurface, + BackendSelector aSelector, TextureFlags aTextureFlags, + TextureAllocationFlags aAllocFlags); + + // Creates and allocates a TextureClient supporting the YCbCr format. + static already_AddRefed CreateForYCbCr( + KnowsCompositor* aAllocator, const gfx::IntRect& aDisplay, + const gfx::IntSize& aYSize, uint32_t aYStride, + const gfx::IntSize& aCbCrSize, uint32_t aCbCrStride, + StereoMode aStereoMode, gfx::ColorDepth aColorDepth, + gfx::YUVColorSpace aYUVColorSpace, gfx::ColorRange aColorRange, + TextureFlags aTextureFlags); + + // Creates and allocates a TextureClient (can be accessed through raw + // pointers). + static already_AddRefed CreateForRawBufferAccess( + KnowsCompositor* aAllocator, gfx::SurfaceFormat aFormat, + gfx::IntSize aSize, gfx::BackendType aMoz2dBackend, + TextureFlags aTextureFlags, TextureAllocationFlags flags = ALLOC_DEFAULT); + + // Creates and allocates a TextureClient of the same type. + already_AddRefed CreateSimilar( + LayersBackend aLayersBackend = LayersBackend::LAYERS_NONE, + TextureFlags aFlags = TextureFlags::DEFAULT, + TextureAllocationFlags aAllocFlags = ALLOC_DEFAULT) const; + + /** + * Locks the shared data, allowing the caller to get access to it. + * + * Please always lock/unlock when accessing the shared data. + * If Lock() returns false, you should not attempt to access the shared data. + */ + bool Lock(OpenMode aMode); + + void Unlock(); + + bool IsLocked() const { return mIsLocked; } + + gfx::IntSize GetSize() const { return mInfo.size; } + + gfx::SurfaceFormat GetFormat() const { return mInfo.format; } + + /** + * Returns true if this texture has a synchronization mechanism (mutex, fence, + * etc.). Textures that do not implement synchronization should be immutable + * or should use immediate uploads (see TextureFlags in CompositorTypes.h) + * Even if a texture does not implement synchronization, Lock and Unlock need + * to be used appropriately since the latter are also there to map/numap data. + */ + bool HasSynchronization() const { return mInfo.hasSynchronization; } + + /** + * Indicates whether the TextureClient implementation is backed by an + * in-memory buffer. The consequence of this is that locking the + * TextureClient does not contend with locking the texture on the host side. + */ + bool HasIntermediateBuffer() const { return mInfo.hasIntermediateBuffer; } + + bool CanExposeDrawTarget() const { return mInfo.supportsMoz2D; } + + bool CanExposeMappedData() const { return mInfo.canExposeMappedData; } + + /** + * Returns a DrawTarget to draw into the TextureClient. + * This function should never be called when not on the main thread! + * + * This must never be called on a TextureClient that is not sucessfully + * locked. When called several times within one Lock/Unlock pair, this method + * should return the same DrawTarget. The DrawTarget is automatically flushed + * by the TextureClient when the latter is unlocked, and the DrawTarget that + * will be returned within the next lock/unlock pair may or may not be the + * same object. Do not keep references to the DrawTarget outside of the + * lock/unlock pair. + * + * This is typically used as follows: + * + * if (!texture->Lock(OpenMode::OPEN_READ_WRITE)) { + * return false; + * } + * { + * // Restrict this code's scope to ensure all references to dt are gone + * // when Unlock is called. + * DrawTarget* dt = texture->BorrowDrawTarget(); + * // use the draw target ... + * } + * texture->Unlock(); + * + */ + gfx::DrawTarget* BorrowDrawTarget(); + + already_AddRefed BorrowSnapshot(); + + /** + * Similar to BorrowDrawTarget but provides direct access to the texture's + * bits instead of a DrawTarget. + */ + bool BorrowMappedData(MappedTextureData&); + bool BorrowMappedYCbCrData(MappedYCbCrTextureData&); + + /** + * This function can be used to update the contents of the TextureClient + * off the main thread. + */ + void UpdateFromSurface(gfx::SourceSurface* aSurface); + + /** + * This method is strictly for debugging. It causes locking and + * needless copies. + */ + already_AddRefed GetAsSurface(); + + virtual void PrintInfo(std::stringstream& aStream, const char* aPrefix); + + /** + * Copies a rectangle from this texture client to a position in aTarget. + * It is assumed that the necessary locks are in place; so this should at + * least have a read lock and aTarget should at least have a write lock. + */ + bool CopyToTextureClient(TextureClient* aTarget, const gfx::IntRect* aRect, + const gfx::IntPoint* aPoint); + + /** + * Allocate and deallocate a TextureChild actor. + * + * TextureChild is an implementation detail of TextureClient that is not + * exposed to the rest of the code base. CreateIPDLActor and DestroyIPDLActor + * are for use with the managing IPDL protocols only (so that they can + * implement AllocPextureChild and DeallocPTextureChild). + */ + static PTextureChild* CreateIPDLActor(); + static bool DestroyIPDLActor(PTextureChild* actor); + + /** + * Get the TextureClient corresponding to the actor passed in parameter. + */ + static already_AddRefed AsTextureClient(PTextureChild* actor); + + /** + * TextureFlags contain important information about various aspects + * of the texture, like how its liferime is managed, and how it + * should be displayed. + * See TextureFlags in CompositorTypes.h. + */ + TextureFlags GetFlags() const { return mFlags; } + + bool HasFlags(TextureFlags aFlags) const { + return (mFlags & aFlags) == aFlags; + } + + void AddFlags(TextureFlags aFlags); + + void RemoveFlags(TextureFlags aFlags); + + // Must not be called when TextureClient is in use by CompositableClient. + void RecycleTexture(TextureFlags aFlags); + + /** + * After being shared with the compositor side, an immutable texture is never + * modified, it can only be read. It is safe to not Lock/Unlock immutable + * textures. + */ + bool IsImmutable() const { return !!(mFlags & TextureFlags::IMMUTABLE); } + + void MarkImmutable() { AddFlags(TextureFlags::IMMUTABLE); } + + bool IsSharedWithCompositor() const; + + /** + * If this method returns false users of TextureClient are not allowed + * to access the shared data. + */ + bool IsValid() const { return !!mData; } + + /** + * Called when TextureClient is added to CompositableClient. + */ + void SetAddedToCompositableClient(); + + /** + * If this method retuns false, TextureClient is already added to + * CompositableClient, since its creation or recycling. + */ + bool IsAddedToCompositableClient() const { + return mAddedToCompositableClient; + } + + /** + * Create and init the TextureChild/Parent IPDL actor pair + * with a CompositableForwarder. + * + * Should be called only once per TextureClient. + * The TextureClient must not be locked when calling this method. + */ + bool InitIPDLActor(CompositableForwarder* aForwarder); + + /** + * Create and init the TextureChild/Parent IPDL actor pair + * with a TextureForwarder. + * + * Should be called only once per TextureClient. + * The TextureClient must not be locked when calling this method. + */ + bool InitIPDLActor(KnowsCompositor* aKnowsCompositor); + + /** + * Return a pointer to the IPDLActor. + * + * This is to be used with IPDL messages only. Do not store the returned + * pointer. + */ + PTextureChild* GetIPDLActor(); + + /** + * Triggers the destruction of the shared data and the corresponding + * TextureHost. + * + * If the texture flags contain TextureFlags::DEALLOCATE_CLIENT, the + * destruction will be synchronously coordinated with the compositor side, + * otherwise it will be done asynchronously. + */ + void Destroy(); + + /** + * Track how much of this texture is wasted. + * For example we might allocate a 256x256 tile but only use 10x10. + */ + void SetWaste(int aWasteArea) { + mWasteTracker.Update(aWasteArea, BytesPerPixel(GetFormat())); + } + + /** + * This sets the readback sink that this texture is to use. This will + * receive the data for this texture as soon as it becomes available after + * texture unlock. + */ + virtual void SetReadbackSink(TextureReadbackSink* aReadbackSink) { + mReadbackSink = aReadbackSink; + } + + void SyncWithObject(RefPtr aSyncObject) { + mData->SyncWithObject(aSyncObject); + } + + LayersIPCChannel* GetAllocator() { return mAllocator; } + + ITextureClientRecycleAllocator* GetRecycleAllocator() { + return mRecycleAllocator; + } + void SetRecycleAllocator(ITextureClientRecycleAllocator* aAllocator); + + /// If you add new code that uses this method, you are probably doing + /// something wrong. + TextureData* GetInternalData() { return mData; } + const TextureData* GetInternalData() const { return mData; } + + uint64_t GetSerial() const { return mSerial; } + void GetSurfaceDescriptorRemoteDecoder( + SurfaceDescriptorRemoteDecoder* aOutDesc); + + void CancelWaitForNotifyNotUsed(); + + /** + * Set last transaction id of CompositableForwarder. + * + * Called when TextureClient has TextureFlags::RECYCLE flag. + * When CompositableForwarder forwards the TextureClient with + * TextureFlags::RECYCLE, it holds TextureClient's ref until host side + * releases it. The host side sends TextureClient release message. + * The id is used to check if the message is for the last TextureClient + * forwarding. + */ + void SetLastFwdTransactionId(uint64_t aTransactionId) { + MOZ_ASSERT(mFwdTransactionId <= aTransactionId); + mFwdTransactionId = aTransactionId; + } + + uint64_t GetLastFwdTransactionId() { return mFwdTransactionId; } + + TextureReadLock* GetReadLock() { return mReadLock; } + + bool IsReadLocked() const; + + bool TryReadLock(); + void ReadUnlock(); + + bool OnForwardedToHost(); + + // Mark that the TextureClient will be used by the paint thread, and should + // not free its underlying texture data. This must only be called from the + // main thread. + void AddPaintThreadRef(); + + // Mark that the TextureClient is no longer in use by the PaintThread. This + // must only be called from the PaintThread. + void DropPaintThreadRef(); + + wr::MaybeExternalImageId GetExternalImageKey() { return mExternalImageId; } + + private: + static void TextureClientRecycleCallback(TextureClient* aClient, + void* aClosure); + + // Internal helpers for creating texture clients using the actual forwarder + // instead of KnowsCompositor. TextureClientPool uses these to let it cache + // texture clients per-process instead of per ShadowLayerForwarder, but + // everyone else should use the public functions instead. + friend class TextureClientPool; + static already_AddRefed CreateForDrawing( + TextureForwarder* aAllocator, gfx::SurfaceFormat aFormat, + gfx::IntSize aSize, KnowsCompositor* aKnowsCompositor, + BackendSelector aSelector, TextureFlags aTextureFlags, + TextureAllocationFlags aAllocFlags = ALLOC_DEFAULT); + + static already_AddRefed CreateForRawBufferAccess( + LayersIPCChannel* aAllocator, gfx::SurfaceFormat aFormat, + gfx::IntSize aSize, gfx::BackendType aMoz2dBackend, + LayersBackend aLayersBackend, TextureFlags aTextureFlags, + TextureAllocationFlags flags = ALLOC_DEFAULT); + + void EnableReadLock(); + void EnableBlockingReadLock(); + + /** + * Called once, during the destruction of the Texture, on the thread in which + * texture's reference count reaches 0 (could be any thread). + * + * Here goes the shut-down code that uses virtual methods. + * Must only be called by Release(). + */ + void Finalize() {} + + friend class AtomicRefCountedWithFinalize; + + protected: + /** + * Should only be called *once* per texture, in TextureClient::InitIPDLActor. + * Some texture implementations rely on the fact that the descriptor will be + * deserialized. + * Calling ToSurfaceDescriptor again after it has already returned true, + * or never constructing a TextureHost with aDescriptor may result in a memory + * leak (see TextureClientD3D9 for example). + */ + bool ToSurfaceDescriptor(SurfaceDescriptor& aDescriptor); + + void LockActor() const; + void UnlockActor() const; + + TextureData::Info mInfo; + + RefPtr mAllocator; + RefPtr mActor; + RefPtr mRecycleAllocator; + RefPtr mReadLock; + + TextureData* mData; + RefPtr mBorrowedDrawTarget; + + TextureFlags mFlags; + + gl::GfxTextureWasteTracker mWasteTracker; + + OpenMode mOpenMode; +#ifdef DEBUG + uint32_t mExpectedDtRefs; +#endif + bool mIsLocked; + bool mIsReadLocked; + // This member tracks that the texture was written into until the update + // is sent to the compositor. We need this remember to lock mReadLock on + // behalf of the compositor just before sending the notification. + bool mUpdated; + + // Used when TextureClient is recycled with TextureFlags::RECYCLE flag. + bool mAddedToCompositableClient; + + bool mWorkaroundAnnoyingSharedSurfaceLifetimeIssues; + bool mWorkaroundAnnoyingSharedSurfaceOwnershipIssues; + + RefPtr mReadbackSink; + + uint64_t mFwdTransactionId; + + // Serial id of TextureClient. It is unique in current process. + const uint64_t mSerial; + + // When non-zero, texture data must not be freed. + mozilla::Atomic mPaintThreadRefs; + + // External image id. It is unique if it is allocated. + // The id is allocated in TextureClient::InitIPDLActor(). + // Its allocation is supported by + // CompositorBridgeChild and ImageBridgeChild for now. + wr::MaybeExternalImageId mExternalImageId; + + // Used to assign serial ids of TextureClient. + static mozilla::Atomic sSerialCounter; + + friend class TextureChild; + friend void TestTextureClientSurface(TextureClient*, gfxImageSurface*); + friend void TestTextureClientYCbCr(TextureClient*, PlanarYCbCrData&); + friend already_AddRefed CreateTextureHostWithBackend( + TextureClient*, ISurfaceAllocator*, LayersBackend&); + +#ifdef GFX_DEBUG_TRACK_CLIENTS_IN_POOL + public: + // Pointer to the pool this tile came from. + TextureClientPool* mPoolTracker; +#endif +}; + +/** + * Task that releases TextureClient pointer on a specified thread. + */ +class TextureClientReleaseTask : public Runnable { + public: + explicit TextureClientReleaseTask(TextureClient* aClient) + : Runnable("layers::TextureClientReleaseTask"), mTextureClient(aClient) {} + + NS_IMETHOD Run() override { + mTextureClient = nullptr; + return NS_OK; + } + + private: + RefPtr mTextureClient; +}; + +// Automatically lock and unlock a texture. Since texture locking is fallible, +// Succeeded() must be checked on the guard object before proceeding. +class MOZ_RAII TextureClientAutoLock { + public: + TextureClientAutoLock(TextureClient* aTexture, OpenMode aMode) + : mTexture(aTexture), mSucceeded(false) { + mSucceeded = mTexture->Lock(aMode); +#ifdef DEBUG + mChecked = false; +#endif + } + ~TextureClientAutoLock() { + MOZ_ASSERT(mChecked); + if (mSucceeded) { + mTexture->Unlock(); + } + } + + bool Succeeded() { +#ifdef DEBUG + mChecked = true; +#endif + return mSucceeded; + } + + private: + TextureClient* mTexture; +#ifdef DEBUG + bool mChecked; +#endif + bool mSucceeded; +}; + +// Automatically locks and unlocks two texture clients, and exposes them as a +// a single draw target dual. Since texture locking is fallible, Succeeded() +// must be checked on the guard object before proceeding. +class MOZ_RAII DualTextureClientAutoLock { + public: + DualTextureClientAutoLock(TextureClient* aTexture, + TextureClient* aTextureOnWhite, OpenMode aMode) + : mTarget(nullptr), mTexture(aTexture), mTextureOnWhite(aTextureOnWhite) { + if (!mTexture->Lock(aMode)) { + return; + } + + mTarget = mTexture->BorrowDrawTarget(); + + if (!mTarget) { + mTexture->Unlock(); + return; + } + + if (!mTextureOnWhite) { + return; + } + + if (!mTextureOnWhite->Lock(aMode)) { + mTarget = nullptr; + mTexture->Unlock(); + return; + } + + RefPtr targetOnWhite = mTextureOnWhite->BorrowDrawTarget(); + + if (!targetOnWhite) { + mTarget = nullptr; + mTexture->Unlock(); + mTextureOnWhite->Unlock(); + return; + } + + mTarget = gfx::Factory::CreateDualDrawTarget(mTarget, targetOnWhite); + + if (!mTarget) { + mTarget = nullptr; + mTexture->Unlock(); + mTextureOnWhite->Unlock(); + } + } + + ~DualTextureClientAutoLock() { + if (Succeeded()) { + mTarget = nullptr; + + mTexture->Unlock(); + if (mTextureOnWhite) { + mTextureOnWhite->Unlock(); + } + } + } + + bool Succeeded() const { return !!mTarget; } + + operator gfx::DrawTarget*() const { return mTarget; } + gfx::DrawTarget* operator->() const { return mTarget; } + + RefPtr mTarget; + + private: + RefPtr mTexture; + RefPtr mTextureOnWhite; +}; + +class KeepAlive { + public: + virtual ~KeepAlive() = default; +}; + +template +class TKeepAlive : public KeepAlive { + public: + explicit TKeepAlive(T* aData) : mData(aData) {} + + protected: + RefPtr mData; +}; + +/// Convenience function to set the content of ycbcr texture. +bool UpdateYCbCrTextureClient(TextureClient* aTexture, + const PlanarYCbCrData& aData); + +TextureType PreferredCanvasTextureType(KnowsCompositor* aKnowsCompositor); + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/client/TextureClientPool.cpp b/gfx/layers/client/TextureClientPool.cpp new file mode 100644 index 0000000000..7dacbe1ab3 --- /dev/null +++ b/gfx/layers/client/TextureClientPool.cpp @@ -0,0 +1,314 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TextureClientPool.h" +#include "CompositableClient.h" +#include "mozilla/layers/CompositableForwarder.h" +#include "mozilla/layers/TextureForwarder.h" +#include "mozilla/layers/TiledContentClient.h" +#include "mozilla/StaticPrefs_layers.h" + +#include "nsComponentManagerUtils.h" + +#define TCP_LOG(...) +//#define TCP_LOG(...) printf_stderr(__VA_ARGS__); + +namespace mozilla { +namespace layers { + +// We want to shrink to our maximum size of N unused tiles +// after a timeout to allow for short-term budget requirements +static void ShrinkCallback(nsITimer* aTimer, void* aClosure) { + static_cast(aClosure)->ShrinkToMaximumSize(); +} + +// After a certain amount of inactivity, let's clear the pool so that +// we don't hold onto tiles needlessly. In general, allocations are +// cheap enough that re-allocating isn't an issue unless we're allocating +// at an inopportune time (e.g. mid-animation). +static void ClearCallback(nsITimer* aTimer, void* aClosure) { + static_cast(aClosure)->Clear(); +} + +TextureClientPool::TextureClientPool( + KnowsCompositor* aKnowsCompositor, gfx::SurfaceFormat aFormat, + gfx::IntSize aSize, TextureFlags aFlags, uint32_t aShrinkTimeoutMsec, + uint32_t aClearTimeoutMsec, uint32_t aInitialPoolSize, + uint32_t aPoolUnusedSize, TextureForwarder* aAllocator) + : mKnowsCompositor(aKnowsCompositor), + mFormat(aFormat), + mSize(aSize), + mFlags(aFlags), + mShrinkTimeoutMsec(aShrinkTimeoutMsec), + mClearTimeoutMsec(aClearTimeoutMsec), + mInitialPoolSize(aInitialPoolSize), + mPoolUnusedSize(aPoolUnusedSize), + mOutstandingClients(0), + mSurfaceAllocator(aAllocator), + mDestroyed(false) { + TCP_LOG("TexturePool %p created with maximum unused texture clients %u\n", + this, mInitialPoolSize); + mShrinkTimer = NS_NewTimer(); + mClearTimer = NS_NewTimer(); + if (aFormat == gfx::SurfaceFormat::UNKNOWN) { + gfxWarning() << "Creating texture pool for SurfaceFormat::UNKNOWN format"; + } +} + +TextureClientPool::~TextureClientPool() { + mShrinkTimer->Cancel(); + mClearTimer->Cancel(); +} + +#ifdef GFX_DEBUG_TRACK_CLIENTS_IN_POOL +static bool TestClientPool(const char* what, TextureClient* aClient, + TextureClientPool* aPool) { + if (!aClient || !aPool) { + return false; + } + + TextureClientPool* actual = aClient->mPoolTracker; + bool ok = (actual == aPool); + if (ok) { + ok = (aClient->GetFormat() == aPool->GetFormat()); + } + + if (!ok) { + if (actual) { + gfxCriticalError() << "Pool error(" << what << "): " << aPool << "-" + << aPool->GetFormat() << ", " << actual << "-" + << actual->GetFormat() << ", " << aClient->GetFormat(); + MOZ_CRASH("GFX: Crashing with actual"); + } else { + gfxCriticalError() << "Pool error(" << what << "): " << aPool << "-" + << aPool->GetFormat() << ", nullptr, " + << aClient->GetFormat(); + MOZ_CRASH("GFX: Crashing without actual"); + } + } + return ok; +} +#endif + +already_AddRefed TextureClientPool::GetTextureClient() { + // Try to fetch a client from the pool + RefPtr textureClient; + + // We initially allocate mInitialPoolSize for our pool. If we run + // out of TextureClients, we allocate additional TextureClients to try and + // keep around mPoolUnusedSize + if (mTextureClients.empty()) { + AllocateTextureClient(); + } + + if (mTextureClients.empty()) { + // All our allocations failed, return nullptr + return nullptr; + } + + mOutstandingClients++; + textureClient = mTextureClients.top(); + mTextureClients.pop(); +#ifdef GFX_DEBUG_TRACK_CLIENTS_IN_POOL + if (textureClient) { + textureClient->mPoolTracker = this; + } + DebugOnly ok = TestClientPool("fetch", textureClient, this); + MOZ_ASSERT(ok); +#endif + TCP_LOG("TexturePool %p giving %p from pool; size %u outstanding %u\n", this, + textureClient.get(), mTextureClients.size(), mOutstandingClients); + + return textureClient.forget(); +} + +void TextureClientPool::AllocateTextureClient() { + TCP_LOG("TexturePool %p allocating TextureClient, outstanding %u\n", this, + mOutstandingClients); + + TextureAllocationFlags allocFlags = ALLOC_DEFAULT; + + if (mKnowsCompositor->SupportsTextureDirectMapping() && + std::max(mSize.width, mSize.height) <= GetMaxTextureSize()) { + allocFlags = + TextureAllocationFlags(allocFlags | ALLOC_ALLOW_DIRECT_MAPPING); + } + + RefPtr newClient; + if (StaticPrefs::layers_force_shmem_tiles_AtStartup()) { + // gfx::BackendType::NONE means use the content backend + newClient = TextureClient::CreateForRawBufferAccess( + mSurfaceAllocator, mFormat, mSize, gfx::BackendType::NONE, GetBackend(), + mFlags, allocFlags); + } else { + newClient = TextureClient::CreateForDrawing( + mSurfaceAllocator, mFormat, mSize, mKnowsCompositor, + BackendSelector::Content, mFlags, allocFlags); + } + + if (newClient) { + mTextureClients.push(newClient); + } +} + +void TextureClientPool::ResetTimers() { + // Shrink down if we're beyond our maximum size + if (mShrinkTimeoutMsec && + mTextureClients.size() + mTextureClientsDeferred.size() > + mPoolUnusedSize) { + TCP_LOG("TexturePool %p scheduling a shrink-to-max-size\n", this); + mShrinkTimer->InitWithNamedFuncCallback( + ShrinkCallback, this, mShrinkTimeoutMsec, nsITimer::TYPE_ONE_SHOT, + "layers::TextureClientPool::ResetTimers"); + } + + // Clear pool after a period of inactivity to reduce memory consumption + if (mClearTimeoutMsec) { + TCP_LOG("TexturePool %p scheduling a clear\n", this); + mClearTimer->InitWithNamedFuncCallback( + ClearCallback, this, mClearTimeoutMsec, nsITimer::TYPE_ONE_SHOT, + "layers::TextureClientPool::ResetTimers"); + } +} + +void TextureClientPool::ReturnTextureClient(TextureClient* aClient) { + if (!aClient || mDestroyed) { + return; + } +#ifdef GFX_DEBUG_TRACK_CLIENTS_IN_POOL + DebugOnly ok = TestClientPool("return", aClient, this); + MOZ_ASSERT(ok); +#endif + // Add the client to the pool: + MOZ_ASSERT(mOutstandingClients > mTextureClientsDeferred.size()); + mOutstandingClients--; + mTextureClients.push(aClient); + TCP_LOG("TexturePool %p had client %p returned; size %u outstanding %u\n", + this, aClient, mTextureClients.size(), mOutstandingClients); + + ResetTimers(); +} + +void TextureClientPool::ReturnTextureClientDeferred(TextureClient* aClient) { + if (!aClient || mDestroyed) { + return; + } + MOZ_ASSERT(aClient->GetReadLock()); +#ifdef GFX_DEBUG_TRACK_CLIENTS_IN_POOL + DebugOnly ok = TestClientPool("defer", aClient, this); + MOZ_ASSERT(ok); +#endif + mTextureClientsDeferred.push_back(aClient); + TCP_LOG( + "TexturePool %p had client %p defer-returned, size %u outstanding %u\n", + this, aClient, mTextureClientsDeferred.size(), mOutstandingClients); + + ResetTimers(); +} + +void TextureClientPool::ShrinkToMaximumSize() { + // We're over our desired maximum size, immediately shrink down to the + // maximum. + // + // We cull from the deferred TextureClients first, as we can't reuse those + // until they get returned. + uint32_t totalUnusedTextureClients = + mTextureClients.size() + mTextureClientsDeferred.size(); + + // If we have > mInitialPoolSize outstanding, then we want to keep around + // mPoolUnusedSize at a maximum. If we have fewer than mInitialPoolSize + // outstanding, then keep around the entire initial pool size. + uint32_t targetUnusedClients; + if (mOutstandingClients > mInitialPoolSize) { + targetUnusedClients = mPoolUnusedSize; + } else { + targetUnusedClients = mInitialPoolSize; + } + + TCP_LOG( + "TexturePool %p shrinking to maximum unused size %u; current pool size " + "%u; total outstanding %u\n", + this, targetUnusedClients, totalUnusedTextureClients, + mOutstandingClients); + + while (totalUnusedTextureClients > targetUnusedClients) { + if (!mTextureClientsDeferred.empty()) { + mOutstandingClients--; + TCP_LOG("TexturePool %p dropped deferred client %p; %u remaining\n", this, + mTextureClientsDeferred.front().get(), + mTextureClientsDeferred.size() - 1); + mTextureClientsDeferred.pop_front(); + } else { + TCP_LOG("TexturePool %p dropped non-deferred client %p; %u remaining\n", + this, mTextureClients.top().get(), mTextureClients.size() - 1); + mTextureClients.pop(); + } + totalUnusedTextureClients--; + } +} + +void TextureClientPool::ReturnDeferredClients() { + if (mTextureClientsDeferred.empty()) { + return; + } + + TCP_LOG("TexturePool %p returning %u deferred clients to pool\n", this, + mTextureClientsDeferred.size()); + + ReturnUnlockedClients(); + ShrinkToMaximumSize(); +} + +void TextureClientPool::ReturnUnlockedClients() { + for (auto it = mTextureClientsDeferred.begin(); + it != mTextureClientsDeferred.end();) { + MOZ_ASSERT((*it)->GetReadLock()->AsNonBlockingLock()->GetReadCount() >= 1); + // Last count is held by the lock itself. + if (!(*it)->IsReadLocked()) { + mTextureClients.push(*it); + it = mTextureClientsDeferred.erase(it); + + MOZ_ASSERT(mOutstandingClients > 0); + mOutstandingClients--; + } else { + it++; + } + } +} + +void TextureClientPool::ReportClientLost() { + MOZ_ASSERT(mOutstandingClients > mTextureClientsDeferred.size()); + mOutstandingClients--; + TCP_LOG("TexturePool %p getting report client lost; down to %u outstanding\n", + this, mOutstandingClients); +} + +void TextureClientPool::Clear() { + TCP_LOG("TexturePool %p getting cleared\n", this); + while (!mTextureClients.empty()) { + TCP_LOG("TexturePool %p releasing client %p\n", this, + mTextureClients.top().get()); + mTextureClients.pop(); + } + while (!mTextureClientsDeferred.empty()) { + MOZ_ASSERT(mOutstandingClients > 0); + mOutstandingClients--; + TCP_LOG("TexturePool %p releasing deferred client %p\n", this, + mTextureClientsDeferred.front().get()); + mTextureClientsDeferred.pop_front(); + } +} + +void TextureClientPool::Destroy() { + Clear(); + mDestroyed = true; + mInitialPoolSize = 0; + mPoolUnusedSize = 0; + mKnowsCompositor = nullptr; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/TextureClientPool.h b/gfx/layers/client/TextureClientPool.h new file mode 100644 index 0000000000..d557ca9a03 --- /dev/null +++ b/gfx/layers/client/TextureClientPool.h @@ -0,0 +1,175 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_TEXTURECLIENTPOOL_H +#define MOZILLA_GFX_TEXTURECLIENTPOOL_H + +#include "mozilla/gfx/Types.h" +#include "mozilla/gfx/Point.h" +#include "mozilla/RefPtr.h" +#include "mozilla/layers/KnowsCompositor.h" +#include "TextureClient.h" +#include "nsITimer.h" +#include +#include + +namespace mozilla { +namespace layers { + +class ISurfaceAllocator; +class TextureForwarder; +class TextureReadLock; + +class TextureClientAllocator { + protected: + virtual ~TextureClientAllocator() = default; + + public: + NS_INLINE_DECL_REFCOUNTING(TextureClientAllocator) + + virtual already_AddRefed GetTextureClient() = 0; + + /** + * Return a TextureClient that is not yet ready to be reused, but will be + * imminently. + */ + virtual void ReturnTextureClientDeferred(TextureClient* aClient) = 0; + + virtual void ReportClientLost() = 0; +}; + +class TextureClientPool final : public TextureClientAllocator { + virtual ~TextureClientPool(); + + public: + TextureClientPool(KnowsCompositor* aKnowsCompositor, + gfx::SurfaceFormat aFormat, gfx::IntSize aSize, + TextureFlags aFlags, uint32_t aShrinkTimeoutMsec, + uint32_t aClearTimeoutMsec, uint32_t aInitialPoolSize, + uint32_t aPoolUnusedSize, TextureForwarder* aAllocator); + + /** + * Gets an allocated TextureClient of size and format that are determined + * by the initialisation parameters given to the pool. This will either be + * a cached client that was returned to the pool, or a newly allocated + * client if one isn't available. + * + * All clients retrieved by this method should be returned using the return + * functions, or reported lost so that the pool can manage its size correctly. + */ + already_AddRefed GetTextureClient() override; + + /** + * Return a TextureClient that is no longer being used and is ready for + * immediate re-use or destruction. + */ + void ReturnTextureClient(TextureClient* aClient); + + /** + * Return a TextureClient that is not yet ready to be reused, but will be + * imminently. + */ + void ReturnTextureClientDeferred(TextureClient* aClient) override; + + /** + * Return any clients to the pool that were previously returned in + * ReturnTextureClientDeferred. + */ + void ReturnDeferredClients(); + + /** + * Attempt to shrink the pool so that there are no more than + * mInitialPoolSize outstanding. + */ + void ShrinkToMaximumSize(); + + /** + * Report that a client retrieved via GetTextureClient() has become + * unusable, so that it will no longer be tracked. + */ + void ReportClientLost() override; + + /** + * Calling this will cause the pool to attempt to relinquish any unused + * clients. + */ + void Clear(); + + LayersBackend GetBackend() const { + return mKnowsCompositor->GetCompositorBackendType(); + } + int32_t GetMaxTextureSize() const { + return mKnowsCompositor->GetMaxTextureSize(); + } + gfx::SurfaceFormat GetFormat() { return mFormat; } + TextureFlags GetFlags() const { return mFlags; } + + /** + * Clear the pool and put it in a state where it won't recycle any new + * texture. + */ + void Destroy(); + + private: + void ReturnUnlockedClients(); + + /// Allocate a single TextureClient to be returned from the pool. + void AllocateTextureClient(); + + /// Reset and/or initialise timers for shrinking/clearing the pool. + void ResetTimers(); + + /// KnowsCompositor passed to the TextureClient for buffer creation. + RefPtr mKnowsCompositor; + + /// Format is passed to the TextureClient for buffer creation. + gfx::SurfaceFormat mFormat; + + /// The width and height of the tiles to be used. + gfx::IntSize mSize; + + /// Flags passed to the TextureClient for buffer creation. + const TextureFlags mFlags; + + /// How long to wait after a TextureClient is returned before trying + /// to shrink the pool to its maximum size of mPoolUnusedSize. + uint32_t mShrinkTimeoutMsec; + + /// How long to wait after a TextureClient is returned before trying + /// to clear the pool. + uint32_t mClearTimeoutMsec; + + // The initial number of unused texture clients to seed the pool with + // on construction + uint32_t mInitialPoolSize; + + // How many unused texture clients to try and keep around if we go over + // the initial allocation + uint32_t mPoolUnusedSize; + + /// This is a total number of clients in the wild and in the stack of + /// deferred clients (see below). So, the total number of clients in + /// existence is always mOutstandingClients + the size of mTextureClients. + uint32_t mOutstandingClients; + + std::stack> mTextureClients; + + std::list> mTextureClientsDeferred; + RefPtr mShrinkTimer; + RefPtr mClearTimer; + // This mSurfaceAllocator owns us, so no need to hold a ref to it + TextureForwarder* mSurfaceAllocator; + + // Keep track of whether this pool has been destroyed or not. If it has, + // we won't accept returns of TextureClients anymore, and the refcounting + // should take care of their destruction. + bool mDestroyed; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* MOZILLA_GFX_TEXTURECLIENTPOOL_H */ diff --git a/gfx/layers/client/TextureClientRecycleAllocator.cpp b/gfx/layers/client/TextureClientRecycleAllocator.cpp new file mode 100644 index 0000000000..6407fd8bc2 --- /dev/null +++ b/gfx/layers/client/TextureClientRecycleAllocator.cpp @@ -0,0 +1,246 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxPlatform.h" +#include "ImageContainer.h" +#include "mozilla/layers/BufferTexture.h" +#include "mozilla/layers/ISurfaceAllocator.h" +#include "mozilla/layers/TextureForwarder.h" +#include "TextureClientRecycleAllocator.h" + +namespace mozilla { +namespace layers { + +// Used to keep TextureClient's reference count stable as not to disrupt +// recycling. +class TextureClientHolder final { + ~TextureClientHolder() = default; + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TextureClientHolder) + + explicit TextureClientHolder(TextureClient* aClient) + : mTextureClient(aClient), mWillRecycle(true) {} + + TextureClient* GetTextureClient() { return mTextureClient; } + + bool WillRecycle() { return mWillRecycle; } + + void ClearWillRecycle() { mWillRecycle = false; } + + void ClearTextureClient() { mTextureClient = nullptr; } + + protected: + RefPtr mTextureClient; + bool mWillRecycle; +}; + +class MOZ_RAII DefaultTextureClientAllocationHelper + : public ITextureClientAllocationHelper { + public: + DefaultTextureClientAllocationHelper( + TextureClientRecycleAllocator* aAllocator, gfx::SurfaceFormat aFormat, + gfx::IntSize aSize, BackendSelector aSelector, TextureFlags aTextureFlags, + TextureAllocationFlags aAllocationFlags) + : ITextureClientAllocationHelper(aFormat, aSize, aSelector, aTextureFlags, + aAllocationFlags), + mAllocator(aAllocator) {} + + bool IsCompatible(TextureClient* aTextureClient) override { + if (aTextureClient->GetFormat() != mFormat || + aTextureClient->GetSize() != mSize) { + return false; + } + return true; + } + + already_AddRefed Allocate( + KnowsCompositor* aKnowsCompositor) override { + return mAllocator->Allocate(mFormat, mSize, mSelector, mTextureFlags, + mAllocationFlags); + } + + protected: + TextureClientRecycleAllocator* mAllocator; +}; + +YCbCrTextureClientAllocationHelper::YCbCrTextureClientAllocationHelper( + const PlanarYCbCrData& aData, TextureFlags aTextureFlags) + : ITextureClientAllocationHelper(gfx::SurfaceFormat::YUV, aData.mYSize, + BackendSelector::Content, aTextureFlags, + ALLOC_DEFAULT), + mData(aData) {} + +bool YCbCrTextureClientAllocationHelper::IsCompatible( + TextureClient* aTextureClient) { + MOZ_ASSERT(aTextureClient->GetFormat() == gfx::SurfaceFormat::YUV); + + BufferTextureData* bufferData = + aTextureClient->GetInternalData()->AsBufferTextureData(); + if (!bufferData || aTextureClient->GetSize() != mData.mYSize || + bufferData->GetCbCrSize().isNothing() || + bufferData->GetCbCrSize().ref() != mData.mCbCrSize || + bufferData->GetYStride().isNothing() || + bufferData->GetYStride().ref() != mData.mYStride || + bufferData->GetCbCrStride().isNothing() || + bufferData->GetCbCrStride().ref() != mData.mCbCrStride || + bufferData->GetYUVColorSpace().isNothing() || + bufferData->GetYUVColorSpace().ref() != mData.mYUVColorSpace || + bufferData->GetColorDepth().isNothing() || + bufferData->GetColorDepth().ref() != mData.mColorDepth || + bufferData->GetStereoMode().isNothing() || + bufferData->GetStereoMode().ref() != mData.mStereoMode) { + return false; + } + return true; +} + +already_AddRefed YCbCrTextureClientAllocationHelper::Allocate( + KnowsCompositor* aKnowsCompositor) { + return TextureClient::CreateForYCbCr( + aKnowsCompositor, mData.GetPictureRect(), mData.mYSize, mData.mYStride, + mData.mCbCrSize, mData.mCbCrStride, mData.mStereoMode, mData.mColorDepth, + mData.mYUVColorSpace, mData.mColorRange, mTextureFlags); +} + +TextureClientRecycleAllocator::TextureClientRecycleAllocator( + KnowsCompositor* aKnowsCompositor) + : mKnowsCompositor(aKnowsCompositor), + mMaxPooledSize(kMaxPooledSized), + mLock("TextureClientRecycleAllocatorImp.mLock"), + mIsDestroyed(false) {} + +TextureClientRecycleAllocator::~TextureClientRecycleAllocator() { + MutexAutoLock lock(mLock); + while (!mPooledClients.empty()) { + mPooledClients.pop(); + } + MOZ_ASSERT(mInUseClients.empty()); +} + +void TextureClientRecycleAllocator::SetMaxPoolSize(uint32_t aMax) { + mMaxPooledSize = aMax; +} + +already_AddRefed TextureClientRecycleAllocator::CreateOrRecycle( + gfx::SurfaceFormat aFormat, gfx::IntSize aSize, BackendSelector aSelector, + TextureFlags aTextureFlags, TextureAllocationFlags aAllocFlags) { + MOZ_ASSERT(!(aTextureFlags & TextureFlags::RECYCLE)); + DefaultTextureClientAllocationHelper helper(this, aFormat, aSize, aSelector, + aTextureFlags, aAllocFlags); + return CreateOrRecycle(helper); +} + +already_AddRefed TextureClientRecycleAllocator::CreateOrRecycle( + ITextureClientAllocationHelper& aHelper) { + MOZ_ASSERT(aHelper.mTextureFlags & TextureFlags::RECYCLE); + + RefPtr textureHolder; + + { + MutexAutoLock lock(mLock); + if (mIsDestroyed || !mKnowsCompositor->GetTextureForwarder()) { + return nullptr; + } + if (!mPooledClients.empty()) { + textureHolder = mPooledClients.top(); + mPooledClients.pop(); + // If the texture's allocator is not open or a pooled TextureClient is + // not compatible, release it. + if (!textureHolder->GetTextureClient()->GetAllocator()->IPCOpen() || + !aHelper.IsCompatible(textureHolder->GetTextureClient())) { + // Release TextureClient. + RefPtr task = + new TextureClientReleaseTask(textureHolder->GetTextureClient()); + textureHolder->ClearTextureClient(); + textureHolder = nullptr; + mKnowsCompositor->GetTextureForwarder()->GetThread()->Dispatch( + task.forget()); + } else { + textureHolder->GetTextureClient()->RecycleTexture( + aHelper.mTextureFlags); + } + } + } + + if (!textureHolder) { + // Allocate new TextureClient + RefPtr texture = aHelper.Allocate(mKnowsCompositor); + if (!texture) { + return nullptr; + } + textureHolder = new TextureClientHolder(texture); + } + + { + MutexAutoLock lock(mLock); + MOZ_ASSERT(mInUseClients.find(textureHolder->GetTextureClient()) == + mInUseClients.end()); + // Register TextureClient + mInUseClients[textureHolder->GetTextureClient()] = textureHolder; + } + RefPtr client(textureHolder->GetTextureClient()); + + // Make sure the texture holds a reference to us, and ask it to call + // RecycleTextureClient when its ref count drops to 1. + client->SetRecycleAllocator(this); + return client.forget(); +} + +already_AddRefed TextureClientRecycleAllocator::Allocate( + gfx::SurfaceFormat aFormat, gfx::IntSize aSize, BackendSelector aSelector, + TextureFlags aTextureFlags, TextureAllocationFlags aAllocFlags) { + return TextureClient::CreateForDrawing(mKnowsCompositor, aFormat, aSize, + aSelector, aTextureFlags, aAllocFlags); +} + +void TextureClientRecycleAllocator::ShrinkToMinimumSize() { + MutexAutoLock lock(mLock); + while (!mPooledClients.empty()) { + mPooledClients.pop(); + } + // We can not clear using TextureClients safely. + // Just clear WillRecycle here. + std::map >::iterator it; + for (it = mInUseClients.begin(); it != mInUseClients.end(); it++) { + RefPtr holder = it->second; + holder->ClearWillRecycle(); + } +} + +void TextureClientRecycleAllocator::Destroy() { + MutexAutoLock lock(mLock); + while (!mPooledClients.empty()) { + mPooledClients.pop(); + } + mIsDestroyed = true; +} + +void TextureClientRecycleAllocator::RecycleTextureClient( + TextureClient* aClient) { + // Clearing the recycle allocator drops a reference, so make sure we stay + // alive for the duration of this function. + RefPtr kungFuDeathGrip(this); + aClient->SetRecycleAllocator(nullptr); + + RefPtr textureHolder; + { + MutexAutoLock lock(mLock); + if (mInUseClients.find(aClient) != mInUseClients.end()) { + textureHolder = + mInUseClients[aClient]; // Keep reference count of + // TextureClientHolder within lock. + if (textureHolder->WillRecycle() && !mIsDestroyed && + mPooledClients.size() < mMaxPooledSize) { + mPooledClients.push(textureHolder); + } + mInUseClients.erase(aClient); + } + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/TextureClientRecycleAllocator.h b/gfx/layers/client/TextureClientRecycleAllocator.h new file mode 100644 index 0000000000..2cace0e1f0 --- /dev/null +++ b/gfx/layers/client/TextureClientRecycleAllocator.h @@ -0,0 +1,133 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_TEXTURECLIENT_RECYCLE_ALLOCATOR_H +#define MOZILLA_GFX_TEXTURECLIENT_RECYCLE_ALLOCATOR_H + +#include +#include +#include "mozilla/gfx/Types.h" +#include "mozilla/layers/TextureForwarder.h" +#include "mozilla/RefPtr.h" +#include "TextureClient.h" +#include "mozilla/Mutex.h" + +namespace mozilla { +namespace layers { + +class TextureClientHolder; +struct PlanarYCbCrData; + +class ITextureClientRecycleAllocator { + protected: + virtual ~ITextureClientRecycleAllocator() = default; + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ITextureClientRecycleAllocator) + + protected: + friend class TextureClient; + virtual void RecycleTextureClient(TextureClient* aClient) = 0; +}; + +class ITextureClientAllocationHelper { + public: + ITextureClientAllocationHelper(gfx::SurfaceFormat aFormat, gfx::IntSize aSize, + BackendSelector aSelector, + TextureFlags aTextureFlags, + TextureAllocationFlags aAllocationFlags) + : mFormat(aFormat), + mSize(aSize), + mSelector(aSelector), + mTextureFlags(aTextureFlags | + TextureFlags::RECYCLE) // Set recycle flag + , + mAllocationFlags(aAllocationFlags) {} + + virtual already_AddRefed Allocate( + KnowsCompositor* aKnowsCompositor) = 0; + virtual bool IsCompatible(TextureClient* aTextureClient) = 0; + + const gfx::SurfaceFormat mFormat; + const gfx::IntSize mSize; + const BackendSelector mSelector; + const TextureFlags mTextureFlags; + const TextureAllocationFlags mAllocationFlags; +}; + +class MOZ_RAII YCbCrTextureClientAllocationHelper + : public ITextureClientAllocationHelper { + public: + YCbCrTextureClientAllocationHelper(const PlanarYCbCrData& aData, + TextureFlags aTextureFlags); + + bool IsCompatible(TextureClient* aTextureClient) override; + + already_AddRefed Allocate( + KnowsCompositor* aKnowsCompositor) override; + + protected: + const PlanarYCbCrData& mData; +}; + +/** + * TextureClientRecycleAllocator provides TextureClients allocation and + * recycling capabilities. It expects allocations of same sizes and + * attributres. If a recycled TextureClient is different from + * requested one, the recycled one is dropped and new TextureClient is + * allocated. + * + * By default this uses TextureClient::CreateForDrawing to allocate new texture + * clients. + */ +class TextureClientRecycleAllocator : public ITextureClientRecycleAllocator { + protected: + virtual ~TextureClientRecycleAllocator(); + + public: + explicit TextureClientRecycleAllocator(KnowsCompositor* aKnowsCompositor); + + void SetMaxPoolSize(uint32_t aMax); + + // Creates and allocates a TextureClient. + already_AddRefed CreateOrRecycle( + gfx::SurfaceFormat aFormat, gfx::IntSize aSize, BackendSelector aSelector, + TextureFlags aTextureFlags, TextureAllocationFlags flags = ALLOC_DEFAULT); + + already_AddRefed CreateOrRecycle( + ITextureClientAllocationHelper& aHelper); + + void ShrinkToMinimumSize(); + + void Destroy(); + + KnowsCompositor* GetKnowsCompositor() { return mKnowsCompositor; } + + protected: + virtual already_AddRefed Allocate( + gfx::SurfaceFormat aFormat, gfx::IntSize aSize, BackendSelector aSelector, + TextureFlags aTextureFlags, TextureAllocationFlags aAllocFlags); + + const RefPtr mKnowsCompositor; + + friend class DefaultTextureClientAllocationHelper; + void RecycleTextureClient(TextureClient* aClient) override; + + static const uint32_t kMaxPooledSized = 2; + uint32_t mMaxPooledSize; + + std::map > mInUseClients; + + // stack is good from Graphics cache usage point of view. + std::stack > mPooledClients; + Mutex mLock; + bool mIsDestroyed; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* MOZILLA_GFX_TEXTURECLIENT_RECYCLE_ALLOCATOR_H */ diff --git a/gfx/layers/client/TextureClientSharedSurface.cpp b/gfx/layers/client/TextureClientSharedSurface.cpp new file mode 100644 index 0000000000..d7a9f5bced --- /dev/null +++ b/gfx/layers/client/TextureClientSharedSurface.cpp @@ -0,0 +1,177 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TextureClientSharedSurface.h" + +#include "GLContext.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Logging.h" // for gfxDebug +#include "mozilla/layers/ISurfaceAllocator.h" +#include "mozilla/Unused.h" +#include "nsThreadUtils.h" +#include "SharedSurface.h" + +#ifdef MOZ_WIDGET_ANDROID +# include "mozilla/layers/AndroidHardwareBuffer.h" +#endif + +using namespace mozilla::gl; + +namespace mozilla { +namespace layers { + +/* static */ +already_AddRefed SharedSurfaceTextureData::CreateTextureClient( + const layers::SurfaceDescriptor& aDesc, const gfx::SurfaceFormat aFormat, + gfx::IntSize aSize, TextureFlags aFlags, LayersIPCChannel* aAllocator) { + auto data = MakeUnique(aDesc, aFormat, aSize); + return TextureClient::CreateWithData(data.release(), aFlags, aAllocator); +} + +SharedSurfaceTextureData::SharedSurfaceTextureData( + const SurfaceDescriptor& desc, const gfx::SurfaceFormat format, + const gfx::IntSize size) + : mDesc(desc), mFormat(format), mSize(size) {} + +SharedSurfaceTextureData::~SharedSurfaceTextureData() = default; + +void SharedSurfaceTextureData::Deallocate(LayersIPCChannel*) {} + +void SharedSurfaceTextureData::FillInfo(TextureData::Info& aInfo) const { + aInfo.size = mSize; + aInfo.format = mFormat; + aInfo.hasIntermediateBuffer = false; + aInfo.hasSynchronization = false; + aInfo.supportsMoz2D = false; + aInfo.canExposeMappedData = false; +} + +bool SharedSurfaceTextureData::Serialize(SurfaceDescriptor& aOutDescriptor) { + if (mDesc.type() != + SurfaceDescriptor::TSurfaceDescriptorAndroidHardwareBuffer) { + aOutDescriptor = mDesc; + return true; + } + +#ifdef MOZ_WIDGET_ANDROID + // File descriptor is created heare for reducing its allocation. + const SurfaceDescriptorAndroidHardwareBuffer& desc = + mDesc.get_SurfaceDescriptorAndroidHardwareBuffer(); + RefPtr buffer = + AndroidHardwareBufferManager::Get()->GetBuffer(desc.bufferId()); + if (!buffer) { + return false; + } + + int fd[2] = {}; + if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fd) != 0) { + return false; + } + + UniqueFileHandle readerFd(fd[0]); + UniqueFileHandle writerFd(fd[1]); + + // Send the AHardwareBuffer to an AF_UNIX socket. It does not acquire or + // retain a reference to the buffer object. The caller is therefore + // responsible for ensuring that the buffer remains alive through the lifetime + // of this file descriptor. + int ret = buffer->SendHandleToUnixSocket(writerFd.get()); + if (ret < 0) { + return false; + } + + aOutDescriptor = layers::SurfaceDescriptorAndroidHardwareBuffer( + ipc::FileDescriptor(readerFd.release()), buffer->mId, buffer->mSize, + buffer->mFormat); + return true; +#else + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + return false; +#endif +} + +TextureFlags SharedSurfaceTextureData::GetTextureFlags() const { + TextureFlags flags = TextureFlags::NO_FLAGS; + +#ifdef MOZ_WIDGET_ANDROID + if (mDesc.type() == + SurfaceDescriptor::TSurfaceDescriptorAndroidHardwareBuffer) { + flags |= TextureFlags::WAIT_HOST_USAGE_END; + } +#endif + return flags; +} + +Maybe SharedSurfaceTextureData::GetBufferId() const { +#ifdef MOZ_WIDGET_ANDROID + if (mDesc.type() == + SurfaceDescriptor::TSurfaceDescriptorAndroidHardwareBuffer) { + const SurfaceDescriptorAndroidHardwareBuffer& desc = + mDesc.get_SurfaceDescriptorAndroidHardwareBuffer(); + return Some(desc.bufferId()); + } +#endif + return Nothing(); +} + +mozilla::ipc::FileDescriptor SharedSurfaceTextureData::GetAcquireFence() { +#ifdef MOZ_WIDGET_ANDROID + if (mDesc.type() == + SurfaceDescriptor::TSurfaceDescriptorAndroidHardwareBuffer) { + const SurfaceDescriptorAndroidHardwareBuffer& desc = + mDesc.get_SurfaceDescriptorAndroidHardwareBuffer(); + RefPtr buffer = + AndroidHardwareBufferManager::Get()->GetBuffer(desc.bufferId()); + if (!buffer) { + return ipc::FileDescriptor(); + } + + return buffer->GetAcquireFence(); + } +#endif + return ipc::FileDescriptor(); +} + +/* +static TextureFlags FlagsFrom(const SharedSurfaceDescriptor& desc) { + auto flags = TextureFlags::ORIGIN_BOTTOM_LEFT; + if (!desc.isPremultAlpha) { + flags |= TextureFlags::NON_PREMULTIPLIED; + } + return flags; +} + +SharedSurfaceTextureClient::SharedSurfaceTextureClient( + const SharedSurfaceDescriptor& aDesc, LayersIPCChannel* aAllocator) + : TextureClient(new SharedSurfaceTextureData(desc), FlagsFrom(desc), +aAllocator) { mWorkaroundAnnoyingSharedSurfaceLifetimeIssues = true; +} + +SharedSurfaceTextureClient::~SharedSurfaceTextureClient() { + // XXX - Things break when using the proper destruction handshake with + // SharedSurfaceTextureData because the TextureData outlives its gl + // context. Having a strong reference to the gl context creates a cycle. + // This needs to be fixed in a better way, though, because deleting + // the TextureData here can race with the compositor and cause flashing. + TextureData* data = mData; + mData = nullptr; + + Destroy(); + + if (data) { + // Destroy mData right away without doing the proper deallocation handshake, + // because SharedSurface depends on things that may not outlive the + // texture's destructor so we can't wait until we know the compositor isn't + // using the texture anymore. It goes without saying that this is really bad + // and we should fix the bugs that block doing the right thing such as bug + // 1224199 sooner rather than later. + delete data; + } +} +*/ + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/TextureClientSharedSurface.h b/gfx/layers/client/TextureClientSharedSurface.h new file mode 100644 index 0000000000..d18eb9e2cc --- /dev/null +++ b/gfx/layers/client/TextureClientSharedSurface.h @@ -0,0 +1,85 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_TEXTURECLIENT_SHAREDSURFACE_H +#define MOZILLA_GFX_TEXTURECLIENT_SHAREDSURFACE_H + +#include // for size_t +#include // for uint32_t, uint8_t, uint64_t +#include "GLContextTypes.h" // for GLContext (ptr only), etc +#include "TextureClient.h" +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/RefPtr.h" // for RefPtr, RefCounted +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/gfx/Types.h" // for SurfaceFormat +#include "mozilla/layers/CompositorTypes.h" // for TextureFlags, etc +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor + +namespace mozilla { +namespace gl { +class GLContext; +class SharedSurface; +class SurfaceFactory; +} // namespace gl + +namespace layers { + +class SharedSurfaceTextureClient; + +class SharedSurfaceTextureData : public TextureData { + friend class SharedSurfaceTextureClient; + + public: + const SurfaceDescriptor mDesc; + const gfx::SurfaceFormat mFormat; + const gfx::IntSize mSize; + + static already_AddRefed CreateTextureClient( + const layers::SurfaceDescriptor& aDesc, const gfx::SurfaceFormat aFormat, + gfx::IntSize aSize, TextureFlags aFlags, LayersIPCChannel* aAllocator); + + SharedSurfaceTextureData(const SurfaceDescriptor&, gfx::SurfaceFormat, + gfx::IntSize); + virtual ~SharedSurfaceTextureData(); + + bool Lock(OpenMode) override { return false; } + + void Unlock() override {} + + void FillInfo(TextureData::Info& aInfo) const override; + + bool Serialize(SurfaceDescriptor& aOutDescriptor) override; + + void Deallocate(LayersIPCChannel*) override; + + TextureFlags GetTextureFlags() const override; + + Maybe GetBufferId() const override; + + mozilla::ipc::FileDescriptor GetAcquireFence() override; +}; +/* +class SharedSurfaceTextureClient : public TextureClient { + public: + SharedSurfaceTextureClient(SharedSurfaceTextureData* aData, + TextureFlags aFlags, LayersIPCChannel* aAllocator); + + ~SharedSurfaceTextureClient(); + + static RefPtr Create( + UniquePtr surf, gl::SurfaceFactory* factory, + LayersIPCChannel* aAllocator, TextureFlags aFlags); + + const auto& Desc() const { + return static_cast(GetInternalData()) + ->mDesc; + } +}; +*/ +} // namespace layers +} // namespace mozilla + +#endif // MOZILLA_GFX_TEXTURECLIENT_SHAREDSURFACE_H diff --git a/gfx/layers/client/TextureRecorded.cpp b/gfx/layers/client/TextureRecorded.cpp new file mode 100644 index 0000000000..a53b42a81b --- /dev/null +++ b/gfx/layers/client/TextureRecorded.cpp @@ -0,0 +1,105 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TextureRecorded.h" + +#include "mozilla/gfx/gfxVars.h" +#include "RecordedCanvasEventImpl.h" + +namespace mozilla { +namespace layers { + +// The texture ID is used in the GPU process both to lookup the real texture in +// the canvas threads and to lookup the SurfaceDescriptor for that texture in +// the compositor thread. It is therefore important that the ID is unique (per +// recording process), otherwise an old descriptor can be picked up. This means +// we can't use the pointer in the recording process as an ID like we do for +// other objects. +static int64_t sNextRecordedTextureId = 0; + +RecordedTextureData::RecordedTextureData( + already_AddRefed aCanvasChild, gfx::IntSize aSize, + gfx::SurfaceFormat aFormat, TextureType aTextureType) + : mCanvasChild(aCanvasChild), mSize(aSize), mFormat(aFormat) { + mCanvasChild->EnsureRecorder(aTextureType); +} + +RecordedTextureData::~RecordedTextureData() { + mCanvasChild->RecordEvent(RecordedTextureDestruction(mTextureId)); +} + +void RecordedTextureData::FillInfo(TextureData::Info& aInfo) const { + aInfo.size = mSize; + aInfo.format = mFormat; + aInfo.supportsMoz2D = true; + aInfo.hasIntermediateBuffer = false; + aInfo.hasSynchronization = true; +} + +bool RecordedTextureData::Lock(OpenMode aMode) { + mCanvasChild->EnsureBeginTransaction(); + if (!mDT) { + mTextureId = sNextRecordedTextureId++; + mCanvasChild->RecordEvent(RecordedNextTextureId(mTextureId)); + mDT = mCanvasChild->CreateDrawTarget(mSize, mFormat); + if (!mDT) { + return false; + } + + // We lock the TextureData when we create it to get the remote DrawTarget. + mCanvasChild->OnTextureWriteLock(); + return true; + } + + mCanvasChild->RecordEvent(RecordedTextureLock(mTextureId, aMode)); + if (aMode & OpenMode::OPEN_WRITE) { + mCanvasChild->OnTextureWriteLock(); + mSnapshot = nullptr; + } + return true; +} + +void RecordedTextureData::Unlock() { + mCanvasChild->RecordEvent(RecordedTextureUnlock(mTextureId)); +} + +already_AddRefed RecordedTextureData::BorrowDrawTarget() { + return do_AddRef(mDT); +} + +already_AddRefed RecordedTextureData::BorrowSnapshot() { + MOZ_ASSERT(mDT); + + mSnapshot = mDT->Snapshot(); + return mCanvasChild->WrapSurface(mSnapshot); +} + +void RecordedTextureData::Deallocate(LayersIPCChannel* aAllocator) {} + +bool RecordedTextureData::Serialize(SurfaceDescriptor& aDescriptor) { + aDescriptor = SurfaceDescriptorRecorded(mTextureId); + return true; +} + +void RecordedTextureData::OnForwardedToHost() { + mCanvasChild->OnTextureForwarded(); + if (mSnapshot && mCanvasChild->ShouldCacheDataSurface()) { + mCanvasChild->RecordEvent(RecordedCacheDataSurface(mSnapshot.get())); + } +} + +TextureFlags RecordedTextureData::GetTextureFlags() const { + TextureFlags flags = TextureFlags::NO_FLAGS; + // With WebRender, resource open happens asynchronously on RenderThread. + // Use WAIT_HOST_USAGE_END to keep TextureClient alive during host side usage. + if (gfx::gfxVars::UseWebRender()) { + flags |= TextureFlags::WAIT_HOST_USAGE_END; + } + return flags; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/TextureRecorded.h b/gfx/layers/client/TextureRecorded.h new file mode 100644 index 0000000000..2db9caf8a6 --- /dev/null +++ b/gfx/layers/client/TextureRecorded.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_TextureRecorded_h +#define mozilla_layers_TextureRecorded_h + +#include "TextureClient.h" +#include "mozilla/layers/CanvasChild.h" +#include "mozilla/layers/LayersTypes.h" + +namespace mozilla { +namespace layers { + +class RecordedTextureData final : public TextureData { + public: + RecordedTextureData(already_AddRefed aCanvasChild, + gfx::IntSize aSize, gfx::SurfaceFormat aFormat, + TextureType aTextureType); + + void FillInfo(TextureData::Info& aInfo) const final; + + bool Lock(OpenMode aMode) final; + + void Unlock() final; + + already_AddRefed BorrowDrawTarget() final; + + already_AddRefed BorrowSnapshot() final; + + void Deallocate(LayersIPCChannel* aAllocator) final; + + bool Serialize(SurfaceDescriptor& aDescriptor) final; + + void OnForwardedToHost() final; + + TextureFlags GetTextureFlags() const final; + + private: + DISALLOW_COPY_AND_ASSIGN(RecordedTextureData); + + ~RecordedTextureData() override; + + int64_t mTextureId; + RefPtr mCanvasChild; + gfx::IntSize mSize; + gfx::SurfaceFormat mFormat; + RefPtr mDT; + RefPtr mSnapshot; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_TextureRecorded_h diff --git a/gfx/layers/client/TiledContentClient.cpp b/gfx/layers/client/TiledContentClient.cpp new file mode 100644 index 0000000000..a5a95ef36b --- /dev/null +++ b/gfx/layers/client/TiledContentClient.cpp @@ -0,0 +1,734 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/TiledContentClient.h" +#include // for ceil, ceilf, floor +#include +#include "ClientTiledPaintedLayer.h" // for ClientTiledPaintedLayer +#include "GeckoProfiler.h" // for AUTO_PROFILER_LABEL +#include "ClientLayerManager.h" // for ClientLayerManager +#include "gfxContext.h" // for gfxContext, etc +#include "gfxPlatform.h" // for gfxPlatform +#include "gfxRect.h" // for gfxRect +#include "mozilla/MathAlgorithms.h" // for Abs +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/gfx/Rect.h" // for Rect +#include "mozilla/gfx/Tools.h" // for BytesPerPixel +#include "mozilla/layers/APZUtils.h" // for AboutToCheckerboard +#include "mozilla/layers/CompositableForwarder.h" +#include "mozilla/layers/CompositorBridgeChild.h" // for CompositorBridgeChild +#include "mozilla/layers/LayerMetricsWrapper.h" +#include "mozilla/layers/ShadowLayers.h" // for ShadowLayerForwarder +#include "mozilla/layers/PaintThread.h" // for PaintThread +#include "TextureClientPool.h" +#include "nsISupportsImpl.h" // for gfxContext::AddRef, etc +#include "nsExpirationTracker.h" // for nsExpirationTracker +#include "nsMathUtils.h" // for NS_lroundf +#include "TiledLayerBuffer.h" +#include "UnitTransforms.h" // for TransformTo +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/UniquePtr.h" + +#ifdef GFX_TILEDLAYER_DEBUG_OVERLAY +# include "cairo.h" +# include +using mozilla::layers::Layer; +static void DrawDebugOverlay(mozilla::gfx::DrawTarget* dt, int x, int y, + int width, int height) { + gfxContext c(dt); + + // Draw border + c.NewPath(); + c.SetDeviceColor(Color(0.f, 0.f, 0.f)); + c.Rectangle(gfxRect(0, 0, width, height)); + c.Stroke(); + + // Build tile description + std::stringstream ss; + ss << x << ", " << y; + + // Draw text using cairo toy text API + // XXX: this drawing will silently fail if |dt| doesn't have a Cairo backend + cairo_t* cr = gfxFont::RefCairo(dt); + cairo_set_font_size(cr, 25); + cairo_text_extents_t extents; + cairo_text_extents(cr, ss.str().c_str(), &extents); + + int textWidth = extents.width + 6; + + c.NewPath(); + c.SetDeviceColor(Color(0.f, 0.f, 0.f)); + c.Rectangle(gfxRect(gfxPoint(2, 2), gfxSize(textWidth, 30))); + c.Fill(); + + c.NewPath(); + c.SetDeviceColor(Color(1.0, 0.0, 0.0)); + c.Rectangle(gfxRect(gfxPoint(2, 2), gfxSize(textWidth, 30))); + c.Stroke(); + + c.NewPath(); + cairo_move_to(cr, 4, 28); + cairo_show_text(cr, ss.str().c_str()); +} + +#endif + +namespace mozilla { + +using namespace gfx; + +namespace layers { + +SharedFrameMetricsHelper::SharedFrameMetricsHelper() + : mLastProgressiveUpdateWasLowPrecision(false), + mProgressiveUpdateWasInDanger(false) { + MOZ_COUNT_CTOR(SharedFrameMetricsHelper); +} + +SharedFrameMetricsHelper::~SharedFrameMetricsHelper() { + MOZ_COUNT_DTOR(SharedFrameMetricsHelper); +} + +static inline bool FuzzyEquals(float a, float b) { + return (fabsf(a - b) < 1e-6); +} + +static AsyncTransform ComputeViewTransform( + const FrameMetrics& aContentMetrics, + const FrameMetrics& aCompositorMetrics) { + // This is basically the same code as + // AsyncPanZoomController::GetCurrentAsyncTransform but with aContentMetrics + // used in place of mLastContentPaintMetrics, because they should be + // equivalent, modulo race conditions while transactions are inflight. + + ParentLayerPoint translation = (aCompositorMetrics.GetVisualScrollOffset() - + aContentMetrics.GetLayoutScrollOffset()) * + aCompositorMetrics.GetZoom(); + return AsyncTransform(aCompositorMetrics.GetAsyncZoom(), -translation); +} + +bool SharedFrameMetricsHelper::UpdateFromCompositorFrameMetrics( + const LayerMetricsWrapper& aLayer, bool aHasPendingNewThebesContent, + bool aLowPrecision, AsyncTransform& aViewTransform) { + MOZ_ASSERT(aLayer); + + CompositorBridgeChild* compositor = nullptr; + if (aLayer.Manager() && aLayer.Manager()->AsClientLayerManager()) { + compositor = + aLayer.Manager()->AsClientLayerManager()->GetCompositorBridgeChild(); + } + + if (!compositor) { + return false; + } + + const FrameMetrics& contentMetrics = aLayer.Metrics(); + FrameMetrics compositorMetrics; + + if (!compositor->LookupCompositorFrameMetrics(contentMetrics.GetScrollId(), + compositorMetrics)) { + return false; + } + + aViewTransform = ComputeViewTransform(contentMetrics, compositorMetrics); + + // Reset the checkerboard risk flag when switching to low precision + // rendering. + if (aLowPrecision && !mLastProgressiveUpdateWasLowPrecision) { + // Skip low precision rendering until we're at risk of checkerboarding. + if (!mProgressiveUpdateWasInDanger) { + TILING_LOG( + "TILING: Aborting low-precision rendering because not at risk of " + "checkerboarding\n"); + return true; + } + mProgressiveUpdateWasInDanger = false; + } + mLastProgressiveUpdateWasLowPrecision = aLowPrecision; + + // Always abort updates if the resolution has changed. There's no use + // in drawing at the incorrect resolution. + if (!FuzzyEquals(compositorMetrics.GetZoom().xScale, + contentMetrics.GetZoom().xScale) || + !FuzzyEquals(compositorMetrics.GetZoom().yScale, + contentMetrics.GetZoom().yScale)) { + TILING_LOG("TILING: Aborting because resolution changed from %s to %s\n", + ToString(contentMetrics.GetZoom()).c_str(), + ToString(compositorMetrics.GetZoom()).c_str()); + return true; + } + + // Never abort drawing if we can't be sure we've sent a more recent + // display-port. If we abort updating when we shouldn't, we can end up + // with blank regions on the screen and we open up the risk of entering + // an endless updating cycle. + // XXX Suspicious comparisons between layout and visual scroll offsets. + // This may not do the right thing when we're zoomed in. + // However, note that the code as written will err on the side of returning + // false (whenever we're zoomed in and there's a persistent nonzero offset + // between the layout and visual viewports), which is the safer option. + if (fabsf(contentMetrics.GetLayoutScrollOffset().x - + compositorMetrics.GetVisualScrollOffset().x) <= 2 && + fabsf(contentMetrics.GetLayoutScrollOffset().y - + compositorMetrics.GetVisualScrollOffset().y) <= 2 && + fabsf(contentMetrics.GetDisplayPort().X() - + compositorMetrics.GetDisplayPort().X()) <= 2 && + fabsf(contentMetrics.GetDisplayPort().Y() - + compositorMetrics.GetDisplayPort().Y()) <= 2 && + fabsf(contentMetrics.GetDisplayPort().Width() - + compositorMetrics.GetDisplayPort().Width()) <= 2 && + fabsf(contentMetrics.GetDisplayPort().Height() - + compositorMetrics.GetDisplayPort().Height()) <= 2) { + return false; + } + + // When not a low precision pass and the page is in danger of checker boarding + // abort update. + if (!aLowPrecision && !mProgressiveUpdateWasInDanger) { + const nsTArray& scrollUpdates = + aLayer.Metadata().GetScrollUpdates(); + bool scrollUpdatePending = !scrollUpdates.IsEmpty() && + compositorMetrics.GetScrollGeneration() < + scrollUpdates.LastElement().GetGeneration(); + // If scrollUpdatePending is true, then that means the content-side + // metrics has a new scroll offset that is going to be forced into the + // compositor but it hasn't gotten there yet. + // Even though right now comparing the metrics might indicate we're + // about to checkerboard (and that's true), the checkerboarding will + // disappear as soon as the new scroll offset update is processed + // on the compositor side. To avoid leaving things in a low-precision + // paint, we need to detect and handle this case (bug 1026756). + if (!scrollUpdatePending && + apz::AboutToCheckerboard(contentMetrics, compositorMetrics)) { + mProgressiveUpdateWasInDanger = true; + return true; + } + } + + // Abort drawing stale low-precision content if there's a more recent + // display-port in the pipeline. + if (aLowPrecision && !aHasPendingNewThebesContent) { + TILING_LOG( + "TILING: Aborting low-precision because of new pending content\n"); + return true; + } + + return false; +} + +bool ClientTiledLayerBuffer::HasFormatChanged() const { + SurfaceMode mode; + gfxContentType content = GetContentType(&mode); + return content != mLastPaintContentType || mode != mLastPaintSurfaceMode; +} + +gfxContentType ClientTiledLayerBuffer::GetContentType( + SurfaceMode* aMode) const { + gfxContentType content = mPaintedLayer.CanUseOpaqueSurface() + ? gfxContentType::COLOR + : gfxContentType::COLOR_ALPHA; + SurfaceMode mode = mPaintedLayer.GetSurfaceMode(); + + if (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) { +#if defined(MOZ_GFX_OPTIMIZE_MOBILE) + mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA; +#else + if (!mPaintedLayer.GetParent() || + !mPaintedLayer.GetParent()->SupportsComponentAlphaChildren()) { + mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA; + } else { + content = gfxContentType::COLOR; + } +#endif + } else if (mode == SurfaceMode::SURFACE_OPAQUE) { +#if defined(MOZ_GFX_OPTIMIZE_MOBILE) + if (IsLowPrecision()) { + // If we're in low-res mode, drawing can sample from outside the visible + // region. Make sure that we only sample transparency if that happens. + mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA; + content = gfxContentType::COLOR_ALPHA; + } +#else + if (mPaintedLayer.MayResample()) { + mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA; + content = gfxContentType::COLOR_ALPHA; + } +#endif + } + + if (aMode) { + *aMode = mode; + } + return content; +} + +class TileExpiry final : public nsExpirationTracker { + public: + TileExpiry() : nsExpirationTracker(1000, "TileExpiry") {} + + static void AddTile(TileClient* aTile) { + if (!sTileExpiry) { + sTileExpiry = MakeUnique(); + } + + sTileExpiry->AddObject(aTile); + } + + static void RemoveTile(TileClient* aTile) { + MOZ_ASSERT(sTileExpiry); + sTileExpiry->RemoveObject(aTile); + } + + static void Shutdown() { sTileExpiry = nullptr; } + + private: + virtual void NotifyExpired(TileClient* aTile) override { + aTile->DiscardBackBuffer(); + } + + static UniquePtr sTileExpiry; +}; +UniquePtr TileExpiry::sTileExpiry; + +void ShutdownTileCache() { TileExpiry::Shutdown(); } + +void TileClient::PrivateProtector::Set(TileClient* const aContainer, + RefPtr aNewValue) { + if (mBuffer) { + TileExpiry::RemoveTile(aContainer); + } + mBuffer = aNewValue; + if (mBuffer) { + TileExpiry::AddTile(aContainer); + } +} + +void TileClient::PrivateProtector::Set(TileClient* const aContainer, + TextureClient* aNewValue) { + Set(aContainer, RefPtr(aNewValue)); +} + +// Placeholder +TileClient::TileClient() : mWasPlaceholder(false) {} + +TileClient::~TileClient() { + if (mExpirationState.IsTracked()) { + MOZ_ASSERT(mBackBuffer); + TileExpiry::RemoveTile(this); + } +} + +TileClient::TileClient(const TileClient& o) { + mBackBuffer.Set(this, o.mBackBuffer); + mBackBufferOnWhite = o.mBackBufferOnWhite; + mFrontBuffer = o.mFrontBuffer; + mFrontBufferOnWhite = o.mFrontBufferOnWhite; + mWasPlaceholder = o.mWasPlaceholder; + mUpdateRect = o.mUpdateRect; +#ifdef GFX_TILEDLAYER_DEBUG_OVERLAY + mLastUpdate = o.mLastUpdate; +#endif + mAllocator = o.mAllocator; + mInvalidFront = o.mInvalidFront; + mInvalidBack = o.mInvalidBack; +} + +TileClient& TileClient::operator=(const TileClient& o) { + if (this == &o) return *this; + mBackBuffer.Set(this, o.mBackBuffer); + mBackBufferOnWhite = o.mBackBufferOnWhite; + mFrontBuffer = o.mFrontBuffer; + mFrontBufferOnWhite = o.mFrontBufferOnWhite; + mWasPlaceholder = o.mWasPlaceholder; + mUpdateRect = o.mUpdateRect; +#ifdef GFX_TILEDLAYER_DEBUG_OVERLAY + mLastUpdate = o.mLastUpdate; +#endif + mAllocator = o.mAllocator; + mInvalidFront = o.mInvalidFront; + mInvalidBack = o.mInvalidBack; + return *this; +} + +void TileClient::Dump(std::stringstream& aStream) { + aStream << "TileClient(bb=" << (TextureClient*)mBackBuffer + << " fb=" << mFrontBuffer.get(); + if (mBackBufferOnWhite) { + aStream << " bbow=" << mBackBufferOnWhite.get(); + } + if (mFrontBufferOnWhite) { + aStream << " fbow=" << mFrontBufferOnWhite.get(); + } + aStream << ")"; +} + +void TileClient::Flip() { + RefPtr frontBuffer = mFrontBuffer; + RefPtr frontBufferOnWhite = mFrontBufferOnWhite; + mFrontBuffer = mBackBuffer; + mFrontBufferOnWhite = mBackBufferOnWhite; + mBackBuffer.Set(this, frontBuffer); + mBackBufferOnWhite = frontBufferOnWhite; + nsIntRegion invalidFront = mInvalidFront; + mInvalidFront = mInvalidBack; + mInvalidBack = invalidFront; +} + +void TileClient::ValidateFromFront( + const nsIntRegion& aDirtyRegion, const nsIntRegion& aVisibleRegion, + gfx::DrawTarget* aBackBuffer, TilePaintFlags aFlags, IntRect* aCopiedRect, + AutoTArray, 4>* aClients) { + if (!mBackBuffer || !mFrontBuffer) { + return; + } + + gfx::IntSize tileSize = mFrontBuffer->GetSize(); + const IntRect tileRect = IntRect(0, 0, tileSize.width, tileSize.height); + + if (aDirtyRegion.Contains(tileRect)) { + // The dirty region means that we no longer need the front buffer, so + // discard it. + DiscardFrontBuffer(); + return; + } + + // Region that needs copying. + nsIntRegion regionToCopy = mInvalidBack; + + regionToCopy.Sub(regionToCopy, aDirtyRegion); + regionToCopy.And(regionToCopy, aVisibleRegion); + + *aCopiedRect = regionToCopy.GetBounds(); + + if (regionToCopy.IsEmpty()) { + // Just redraw it all. + return; + } + + // Copy the bounding rect of regionToCopy. As tiles are quite small, it + // is unlikely that we'd save much by copying each individual rect of the + // region, but we can reevaluate this if it becomes an issue. + const IntRect rectToCopy = regionToCopy.GetBounds(); + OpenMode readMode = !!(aFlags & TilePaintFlags::Async) + ? OpenMode::OPEN_READ_ASYNC + : OpenMode::OPEN_READ; + + DualTextureClientAutoLock frontBuffer(mFrontBuffer, mFrontBufferOnWhite, + readMode); + if (!frontBuffer.Succeeded()) { + return; + } + + RefPtr frontSurface = frontBuffer->Snapshot(); + aBackBuffer->CopySurface(frontSurface, rectToCopy, rectToCopy.TopLeft()); + + if (aFlags & TilePaintFlags::Async) { + aClients->AppendElement(mFrontBuffer); + if (mFrontBufferOnWhite) { + aClients->AppendElement(mFrontBufferOnWhite); + } + } + + mInvalidBack.Sub(mInvalidBack, aVisibleRegion); +} + +void TileClient::DiscardFrontBuffer() { + if (mFrontBuffer) { + MOZ_ASSERT(mFrontBuffer->GetReadLock()); + + MOZ_ASSERT(mAllocator); + if (mAllocator) { + mAllocator->ReturnTextureClientDeferred(mFrontBuffer); + if (mFrontBufferOnWhite) { + mAllocator->ReturnTextureClientDeferred(mFrontBufferOnWhite); + } + } + + if (mFrontBuffer->IsLocked()) { + mFrontBuffer->Unlock(); + } + if (mFrontBufferOnWhite && mFrontBufferOnWhite->IsLocked()) { + mFrontBufferOnWhite->Unlock(); + } + mFrontBuffer = nullptr; + mFrontBufferOnWhite = nullptr; + } +} + +static void DiscardTexture(TextureClient* aTexture, + TextureClientAllocator* aAllocator) { + MOZ_ASSERT(aAllocator); + if (aTexture && aAllocator) { + MOZ_ASSERT(aTexture->GetReadLock()); + if (!aTexture->HasSynchronization() && aTexture->IsReadLocked()) { + // Our current back-buffer is still locked by the compositor. This can + // occur when the client is producing faster than the compositor can + // consume. In this case we just want to drop it and not return it to the + // pool. + aAllocator->ReportClientLost(); + } else { + aAllocator->ReturnTextureClientDeferred(aTexture); + } + if (aTexture->IsLocked()) { + aTexture->Unlock(); + } + } +} + +void TileClient::DiscardBackBuffer() { + if (mBackBuffer) { + DiscardTexture(mBackBuffer, mAllocator); + mBackBuffer.Set(this, nullptr); + DiscardTexture(mBackBufferOnWhite, mAllocator); + mBackBufferOnWhite = nullptr; + } +} + +static already_AddRefed CreateBackBufferTexture( + TextureClient* aCurrentTexture, CompositableClient& aCompositable, + TextureClientAllocator* aAllocator) { + if (aCurrentTexture) { + // Our current back-buffer is still locked by the compositor. This can occur + // when the client is producing faster than the compositor can consume. In + // this case we just want to drop it and not return it to the pool. + aAllocator->ReportClientLost(); + } + + RefPtr texture = aAllocator->GetTextureClient(); + + if (!texture) { + gfxCriticalError() << "[Tiling:Client] Failed to allocate a TextureClient"; + return nullptr; + } + + if (!aCompositable.AddTextureClient(texture)) { + gfxCriticalError() << "[Tiling:Client] Failed to connect a TextureClient"; + return nullptr; + } + + return texture.forget(); +} + +void TileClient::GetSyncTextureSerials(SurfaceMode aMode, + nsTArray& aSerials) { + if (mFrontBuffer && mFrontBuffer->HasIntermediateBuffer() && + !mFrontBuffer->IsReadLocked() && + (aMode != SurfaceMode::SURFACE_COMPONENT_ALPHA || + (mFrontBufferOnWhite && !mFrontBufferOnWhite->IsReadLocked()))) { + return; + } + + if (mBackBuffer && !mBackBuffer->HasIntermediateBuffer() && + mBackBuffer->IsReadLocked()) { + aSerials.AppendElement(mBackBuffer->GetSerial()); + } + + if (aMode == SurfaceMode::SURFACE_COMPONENT_ALPHA && mBackBufferOnWhite && + !mBackBufferOnWhite->HasIntermediateBuffer() && + mBackBufferOnWhite->IsReadLocked()) { + aSerials.AppendElement(mBackBufferOnWhite->GetSerial()); + } +} + +Maybe TileClient::AcquireBackBuffer( + CompositableClient& aCompositable, const nsIntRegion& aDirtyRegion, + const nsIntRegion& aVisibleRegion, gfxContentType aContent, + SurfaceMode aMode, TilePaintFlags aFlags) { + AUTO_PROFILER_LABEL("TileClient::AcquireBackBuffer", GRAPHICS_TileAllocation); + if (!mAllocator) { + gfxCriticalError() << "[TileClient] Missing TextureClientAllocator."; + return Nothing(); + } + if (aMode != SurfaceMode::SURFACE_COMPONENT_ALPHA) { + // It can happen that a component-alpha layer stops being on component alpha + // on the next frame, just drop the buffers on white if that happens. + if (mBackBufferOnWhite) { + mAllocator->ReportClientLost(); + mBackBufferOnWhite = nullptr; + } + if (mFrontBufferOnWhite) { + mAllocator->ReportClientLost(); + mFrontBufferOnWhite = nullptr; + } + } + + // Try to re-use the front-buffer if possible + if (mFrontBuffer && mFrontBuffer->HasIntermediateBuffer() && + !mFrontBuffer->IsReadLocked() && + (aMode != SurfaceMode::SURFACE_COMPONENT_ALPHA || + (mFrontBufferOnWhite && !mFrontBufferOnWhite->IsReadLocked()))) { + // If we had a backbuffer we no longer need it since we can re-use the + // front buffer here. It can be worth it to hold on to the back buffer + // so we don't need to pay the cost of initializing a new back buffer + // later (copying pixels and texture upload). But this could increase + // our memory usage and lead to OOM more frequently from spikes in usage, + // so we have this behavior behind a pref. + if (!StaticPrefs::layers_tiles_retain_back_buffer()) { + DiscardBackBuffer(); + } + Flip(); + } else { + if (!mBackBuffer || mBackBuffer->IsReadLocked()) { + mBackBuffer.Set(this, CreateBackBufferTexture(mBackBuffer, aCompositable, + mAllocator)); + if (!mBackBuffer) { + DiscardBackBuffer(); + DiscardFrontBuffer(); + return Nothing(); + } + mInvalidBack = IntRect(IntPoint(), mBackBuffer->GetSize()); + } + + if (aMode == SurfaceMode::SURFACE_COMPONENT_ALPHA) { + if (!mBackBufferOnWhite || mBackBufferOnWhite->IsReadLocked()) { + mBackBufferOnWhite = CreateBackBufferTexture(mBackBufferOnWhite, + aCompositable, mAllocator); + if (!mBackBufferOnWhite) { + DiscardBackBuffer(); + DiscardFrontBuffer(); + return Nothing(); + } + mInvalidBack = IntRect(IntPoint(), mBackBufferOnWhite->GetSize()); + } + } + } + + // Lock the texture clients + OpenMode lockMode = aFlags & TilePaintFlags::Async + ? OpenMode::OPEN_READ_WRITE_ASYNC + : OpenMode::OPEN_READ_WRITE; + + if (!mBackBuffer->Lock(lockMode)) { + gfxCriticalError() << "[Tiling:Client] Failed to lock a tile (B)"; + DiscardBackBuffer(); + DiscardFrontBuffer(); + return Nothing(); + } + + if (mBackBufferOnWhite && !mBackBufferOnWhite->Lock(lockMode)) { + gfxCriticalError() << "[Tiling:Client] Failed to lock a tile (W)"; + DiscardBackBuffer(); + DiscardFrontBuffer(); + return Nothing(); + } + + // Borrow their draw targets + RefPtr backBuffer = mBackBuffer->BorrowDrawTarget(); + RefPtr backBufferOnWhite = nullptr; + if (mBackBufferOnWhite) { + backBufferOnWhite = mBackBufferOnWhite->BorrowDrawTarget(); + } + + if (!backBuffer || (mBackBufferOnWhite && !backBufferOnWhite)) { + gfxCriticalError() + << "[Tiling:Client] Failed to acquire draw targets for tile"; + DiscardBackBuffer(); + DiscardFrontBuffer(); + return Nothing(); + } + + // Construct a dual target if necessary + RefPtr target; + if (backBufferOnWhite) { + backBuffer = Factory::CreateDualDrawTarget(backBuffer, backBufferOnWhite); + backBufferOnWhite = nullptr; + target = backBuffer; + } else { + target = backBuffer; + } + + // Construct a capture draw target if necessary + RefPtr capture; + if (aFlags & TilePaintFlags::Async) { + capture = Factory::CreateCaptureDrawTargetForTarget( + target, StaticPrefs::layers_omtp_capture_limit_AtStartup()); + target = capture; + } + + // Gather texture clients + AutoTArray, 4> clients; + clients.AppendElement(RefPtr(mBackBuffer)); + if (mBackBufferOnWhite) { + clients.AppendElement(mBackBufferOnWhite); + } + + // Copy from the front buerr to the back if necessary + IntRect updatedRect; + ValidateFromFront(aDirtyRegion, aVisibleRegion, target, aFlags, &updatedRect, + &clients); + + return Some(AcquiredBackBuffer{ + target, + capture, + backBuffer, + std::move(updatedRect), + std::move(clients), + }); +} + +TileDescriptor TileClient::GetTileDescriptor() { + if (IsPlaceholderTile()) { + mWasPlaceholder = true; + return PlaceholderTileDescriptor(); + } + bool wasPlaceholder = mWasPlaceholder; + mWasPlaceholder = false; + + bool readLocked = mFrontBuffer->OnForwardedToHost(); + bool readLockedOnWhite = false; + + if (mFrontBufferOnWhite) { + readLockedOnWhite = mFrontBufferOnWhite->OnForwardedToHost(); + } + + return TexturedTileDescriptor( + nullptr, mFrontBuffer->GetIPDLActor(), Nothing(), + mFrontBufferOnWhite ? Some(mFrontBufferOnWhite->GetIPDLActor()) + : Nothing(), + mUpdateRect, readLocked, readLockedOnWhite, wasPlaceholder); +} + +void ClientTiledLayerBuffer::UnlockTile(TileClient& aTile) { + // We locked the back buffer, and flipped so we now need to unlock the front + if (aTile.mFrontBuffer && aTile.mFrontBuffer->IsLocked()) { + aTile.mFrontBuffer->Unlock(); + aTile.mFrontBuffer->SyncWithObject( + mCompositableClient.GetForwarder()->GetSyncObject()); + } + if (aTile.mFrontBufferOnWhite && aTile.mFrontBufferOnWhite->IsLocked()) { + aTile.mFrontBufferOnWhite->Unlock(); + aTile.mFrontBufferOnWhite->SyncWithObject( + mCompositableClient.GetForwarder()->GetSyncObject()); + } + if (aTile.mBackBuffer && aTile.mBackBuffer->IsLocked()) { + aTile.mBackBuffer->Unlock(); + } + if (aTile.mBackBufferOnWhite && aTile.mBackBufferOnWhite->IsLocked()) { + aTile.mBackBufferOnWhite->Unlock(); + } +} + +void TiledContentClient::PrintInfo(std::stringstream& aStream, + const char* aPrefix) { + aStream << aPrefix; + aStream << nsPrintfCString("%sTiledContentClient (0x%p)", mName, this).get(); +} + +void TiledContentClient::Dump(std::stringstream& aStream, const char* aPrefix, + bool aDumpHtml, TextureDumpMode aCompress) { + GetTiledBuffer()->Dump(aStream, aPrefix, aDumpHtml, aCompress); +} + +void BasicTiledLayerPaintData::ResetPaintData() { + mLowPrecisionPaintCount = 0; + mPaintFinished = false; + mHasTransformAnimation = false; + mCompositionBounds.SetEmpty(); + mCriticalDisplayPort = Nothing(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/client/TiledContentClient.h b/gfx/layers/client/TiledContentClient.h new file mode 100644 index 0000000000..5657572a15 --- /dev/null +++ b/gfx/layers/client/TiledContentClient.h @@ -0,0 +1,406 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_TILEDCONTENTCLIENT_H +#define MOZILLA_GFX_TILEDCONTENTCLIENT_H + +#include // for uint64_t, uint16_t, uint8_t +#include // for stringstream +#include // for move +#include "ClientLayerManager.h" // for ClientLayerManager +#include "Units.h" // for CSSToParentLayerScale2D, ParentLayerPoint, LayerIntRect, LayerRect, LayerToParent... +#include "gfxTypes.h" // for gfxContentType, gfxContentType::COLOR +#include "mozilla/Maybe.h" // for Maybe +#include "mozilla/RefPtr.h" // for RefPtr, operator==, operator!= +#include "mozilla/TypedEnumBits.h" // for CastableTypedEnumResult, UnsignedIntegerTypeForEnum, MOZ_MAKE_ENUM_CLASS_BITWISE_... +#include "mozilla/gfx/2D.h" // for DrawTarget, DrawTargetCapture +#include "mozilla/gfx/Rect.h" // for IntRect +#include "mozilla/layers/CompositableClient.h" // for CompositableClient +#include "mozilla/layers/CompositorTypes.h" // for TextureInfo, CompositableType, CompositableType::CONTENT_TILED +#include "mozilla/layers/LayerManager.h" // for LayerManager, LayerManager::DrawPaintedLayerCallback +#include "mozilla/layers/LayersMessages.h" // for TileDescriptor +#include "mozilla/layers/LayersTypes.h" // for SurfaceMode, TextureDumpMode, Compress, SurfaceMode::SURFACE_OPAQUE +#include "mozilla/layers/ShadowLayers.h" // for ShadowLayerForwarder +#include "mozilla/layers/TextureClient.h" // for TextureClient +#include "mozilla/layers/TextureClientPool.h" // for TextureClientAllocator +#include "nsExpirationTracker.h" // for nsExpirationState +#include "nsRegion.h" // for nsIntRegion +#include "nsTArray.h" // for AutoTArray, nsTArray (ptr only) + +namespace mozilla { +namespace layers { + +class ClientTiledPaintedLayer; +class LayerMetricsWrapper; +struct AsyncTransform; +struct FrameMetrics; + +enum class TilePaintFlags : uint8_t { + None = 0x0, + Async = 0x1, + Progressive = 0x2, +}; +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(TilePaintFlags) + +void ShutdownTileCache(); + +struct AcquiredBackBuffer { + AcquiredBackBuffer(gfx::DrawTarget* aTarget, gfx::DrawTargetCapture* aCapture, + gfx::DrawTarget* aBackBuffer, + const gfx::IntRect& aUpdatedRect, + AutoTArray, 4>&& aTextureClients) + : mTarget(aTarget), + mCapture(aCapture), + mBackBuffer(aBackBuffer), + mUpdatedRect(aUpdatedRect), + mTextureClients(std::move(aTextureClients)) {} + + AcquiredBackBuffer(const AcquiredBackBuffer&) = delete; + AcquiredBackBuffer& operator=(const AcquiredBackBuffer&) = delete; + + AcquiredBackBuffer(AcquiredBackBuffer&&) = default; + AcquiredBackBuffer& operator=(AcquiredBackBuffer&&) = default; + + RefPtr mTarget; + RefPtr mCapture; + RefPtr mBackBuffer; + gfx::IntRect mUpdatedRect; + AutoTArray, 4> mTextureClients; +}; + +/** + * Represent a single tile in tiled buffer. The buffer keeps tiles, + * each tile keeps a reference to a texture client and a read-lock. This + * read-lock is used to help implement a copy-on-write mechanism. The tile + * should be locked before being sent to the compositor. The compositor should + * unlock the read-lock as soon as it has finished with the buffer in the + * TextureHost to prevent more textures being created than is necessary. + * Ideal place to store per tile debug information. + */ +struct TileClient { + // Placeholder + TileClient(); + ~TileClient(); + + TileClient(const TileClient& o); + + TileClient& operator=(const TileClient& o); + + bool operator==(const TileClient& o) const { + return mFrontBuffer == o.mFrontBuffer; + } + + bool operator!=(const TileClient& o) const { + return mFrontBuffer != o.mFrontBuffer; + } + + void SetTextureAllocator(TextureClientAllocator* aAllocator) { + mAllocator = aAllocator; + } + + bool IsPlaceholderTile() const { + return mBackBuffer == nullptr && mFrontBuffer == nullptr; + } + + void DiscardBuffers() { + DiscardFrontBuffer(); + DiscardBackBuffer(); + } + + nsExpirationState* GetExpirationState() { return &mExpirationState; } + + TileDescriptor GetTileDescriptor(); + + /** + * For debugging. + */ + void Dump(std::stringstream& aStream); + + /** + * Swaps the front and back buffers. + */ + void Flip(); + + void DumpTexture(std::stringstream& aStream, TextureDumpMode aCompress) { + // TODO We should combine the OnWhite/OnBlack here an just output a single + // image. + CompositableClient::DumpTextureClient(aStream, mFrontBuffer, aCompress); + } + + void GetSyncTextureSerials(SurfaceMode aMode, nsTArray& aSerials); + + /** + * Returns an unlocked TextureClient that can be used for writing new + * data to the tile. This may flip the front-buffer to the back-buffer if + * the front-buffer is still locked by the host, or does not have an + * internal buffer (and so will always be locked). + * + * If getting the back buffer required copying pixels from the front buffer + * then the copied region is stored in aAddPaintedRegion so the host side + * knows to upload it. + * + * If nullptr is returned, aTextureClientOnWhite is undefined. + */ + Maybe AcquireBackBuffer(CompositableClient&, + const nsIntRegion& aDirtyRegion, + const nsIntRegion& aVisibleRegion, + gfxContentType aContent, + SurfaceMode aMode, + TilePaintFlags aFlags); + + void DiscardFrontBuffer(); + + void DiscardBackBuffer(); + + /* We wrap the back buffer in a class that disallows assignment + * so that we can track when ever it changes so that we can update + * the expiry tracker for expiring the back buffers */ + class PrivateProtector { + public: + void Set(TileClient* container, RefPtr); + void Set(TileClient* container, TextureClient*); + // Implicitly convert to TextureClient* because we can't chain + // implicit conversion that would happen on RefPtr + operator TextureClient*() const { return mBuffer; } + RefPtr operator->() { return mBuffer; } + + private: + PrivateProtector& operator=(const PrivateProtector&); + RefPtr mBuffer; + } mBackBuffer; + RefPtr mBackBufferOnWhite; + RefPtr mFrontBuffer; + RefPtr mFrontBufferOnWhite; + RefPtr mAllocator; + gfx::IntRect mUpdateRect; + bool mWasPlaceholder; +#ifdef GFX_TILEDLAYER_DEBUG_OVERLAY + TimeStamp mLastUpdate; +#endif + nsIntRegion mInvalidFront; + nsIntRegion mInvalidBack; + nsExpirationState mExpirationState; + + private: + /* + * Copies dirty pixels from the front buffer into the back buffer, + * and records the copied region in aAddPaintedRegion. + */ + void ValidateFromFront(const nsIntRegion& aDirtyRegion, + const nsIntRegion& aVisibleRegion, + gfx::DrawTarget* aBackBuffer, TilePaintFlags aFlags, + gfx::IntRect* aCopiedRegion, + AutoTArray, 4>* aClients); +}; + +/** + * This struct stores all the data necessary to perform a paint so that it + * doesn't need to be recalculated on every repeated transaction. + */ +struct BasicTiledLayerPaintData { + /* + * The scroll offset of the content from the nearest ancestor layer that + * represents scrollable content with a display port set. + */ + ParentLayerPoint mScrollOffset; + + /* + * The scroll offset of the content from the nearest ancestor layer that + * represents scrollable content with a display port set, for the last + * layer update transaction. + */ + ParentLayerPoint mLastScrollOffset; + + /* + * The transform matrix to go from this layer's Layer units to + * the scroll ancestor's ParentLayer units. The "scroll ancestor" is + * the closest ancestor layer which scrolls, and is used to obtain + * the composition bounds that are relevant for this layer. + */ + LayerToParentLayerMatrix4x4 mTransformToCompBounds; + + /* + * The critical displayport of the content from the nearest ancestor layer + * that represents scrollable content with a display port set. isNothing() + * if a critical displayport is not set. + */ + Maybe mCriticalDisplayPort; + + /* + * The render resolution of the document that the content this layer + * represents is in. + */ + CSSToParentLayerScale2D mResolution; + + /* + * The composition bounds of the layer, in Layer coordinates. This is + * used to make sure that tiled updates to regions that are visible to the + * user are grouped coherently. + */ + LayerRect mCompositionBounds; + + /* + * Low precision updates are always executed a tile at a time in repeated + * transactions. This counter is set to 1 on the first transaction of a low + * precision update, and incremented for each subsequent transaction. + */ + uint16_t mLowPrecisionPaintCount; + + /* + * Whether this is the first time this layer is painting + */ + bool mFirstPaint : 1; + + /* + * Whether there is further work to complete this paint. This is used to + * determine whether or not to repeat the transaction when painting + * progressively. + */ + bool mPaintFinished : 1; + + /* + * Whether or not there is an async transform animation active + */ + bool mHasTransformAnimation : 1; + + /* + * Initializes/clears data to prepare for paint action. + */ + void ResetPaintData(); +}; + +class SharedFrameMetricsHelper { + public: + SharedFrameMetricsHelper(); + ~SharedFrameMetricsHelper(); + + /** + * This is called by the BasicTileLayer to determine if it is still interested + * in the update of this display-port to continue. We can return true here + * to abort the current update and continue with any subsequent ones. This + * is useful for slow-to-render pages when the display-port starts lagging + * behind enough that continuing to draw it is wasted effort. + */ + bool UpdateFromCompositorFrameMetrics(const LayerMetricsWrapper& aLayer, + bool aHasPendingNewThebesContent, + bool aLowPrecision, + AsyncTransform& aViewTransform); + + /** + * Determines if the compositor's upcoming composition bounds has fallen + * outside of the contents display port. If it has then the compositor + * will start to checker board. Checker boarding is when the compositor + * tries to composite a tile and it is not available. Historically + * a tile with a checker board pattern was used. Now a blank tile is used. + */ + bool AboutToCheckerboard(const FrameMetrics& aContentMetrics, + const FrameMetrics& aCompositorMetrics); + + private: + bool mLastProgressiveUpdateWasLowPrecision; + bool mProgressiveUpdateWasInDanger; +}; + +/** + * Provide an instance of TiledLayerBuffer backed by drawable TextureClients. + * This buffer provides an implementation of ValidateTile using a + * thebes callback and can support painting using a single paint buffer. + * Whether a single paint buffer is used is controlled by + * StaticPrefs::PerTileDrawing(). + */ +class ClientTiledLayerBuffer { + public: + ClientTiledLayerBuffer(ClientTiledPaintedLayer& aPaintedLayer, + CompositableClient& aCompositableClient) + : mPaintedLayer(aPaintedLayer), + mCompositableClient(aCompositableClient), + mLastPaintContentType(gfxContentType::COLOR), + mLastPaintSurfaceMode(SurfaceMode::SURFACE_OPAQUE), + mWasLastPaintProgressive(false) {} + + virtual void PaintThebes(const nsIntRegion& aNewValidRegion, + const nsIntRegion& aPaintRegion, + const nsIntRegion& aDirtyRegion, + LayerManager::DrawPaintedLayerCallback aCallback, + void* aCallbackData, TilePaintFlags aFlags) = 0; + virtual void GetSyncTextureSerials(const nsIntRegion& aPaintRegion, + const nsIntRegion& aDirtyRegion, + nsTArray& aSerials) { + return; + } + + virtual bool SupportsProgressiveUpdate() = 0; + virtual bool ProgressiveUpdate( + const nsIntRegion& aValidRegion, const nsIntRegion& aInvalidRegion, + const nsIntRegion& aOldValidRegion, nsIntRegion& aOutDrawnRegion, + BasicTiledLayerPaintData* aPaintData, + LayerManager::DrawPaintedLayerCallback aCallback, + void* aCallbackData) = 0; + virtual void ResetPaintedAndValidState() = 0; + + virtual const nsIntRegion& GetValidRegion() = 0; + + virtual bool IsLowPrecision() const = 0; + + virtual void Dump(std::stringstream& aStream, const char* aPrefix, + bool aDumpHtml, TextureDumpMode aCompress) {} + + const CSSToParentLayerScale2D& GetFrameResolution() { + return mFrameResolution; + } + void SetFrameResolution(const CSSToParentLayerScale2D& aResolution) { + mFrameResolution = aResolution; + } + + bool HasFormatChanged() const; + + protected: + void UnlockTile(TileClient& aTile); + gfxContentType GetContentType(SurfaceMode* aMode = nullptr) const; + + ClientTiledPaintedLayer& mPaintedLayer; + CompositableClient& mCompositableClient; + + gfxContentType mLastPaintContentType; + SurfaceMode mLastPaintSurfaceMode; + CSSToParentLayerScale2D mFrameResolution; + + bool mWasLastPaintProgressive; +}; + +class TiledContentClient : public CompositableClient { + public: + TiledContentClient(ClientLayerManager* aManager, const char* aName = "") + : CompositableClient(aManager->AsShadowForwarder()), mName(aName) {} + + protected: + ~TiledContentClient() {} + + public: + virtual void PrintInfo(std::stringstream& aStream, const char* aPrefix); + + void Dump(std::stringstream& aStream, const char* aPrefix = "", + bool aDumpHtml = false, + TextureDumpMode aCompress = TextureDumpMode::Compress) override; + + TextureInfo GetTextureInfo() const override { + return TextureInfo(CompositableType::CONTENT_TILED); + } + + virtual ClientTiledLayerBuffer* GetTiledBuffer() = 0; + virtual ClientTiledLayerBuffer* GetLowPrecisionTiledBuffer() = 0; + + enum TiledBufferType { TILED_BUFFER, LOW_PRECISION_TILED_BUFFER }; + virtual void UpdatedBuffer(TiledBufferType aType) = 0; + + private: + const char* mName; +}; + +} // namespace layers +} // namespace mozilla + +#endif // MOZILLA_GFX_TILEDCONTENTCLIENT_H diff --git a/gfx/layers/composite/AsyncCompositionManager.cpp b/gfx/layers/composite/AsyncCompositionManager.cpp new file mode 100644 index 0000000000..3c155d0df0 --- /dev/null +++ b/gfx/layers/composite/AsyncCompositionManager.cpp @@ -0,0 +1,1314 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/AsyncCompositionManager.h" +#include // for uint32_t +#include "LayerManagerComposite.h" // for LayerManagerComposite, etc +#include "Layers.h" // for Layer, ContainerLayer, etc +#include "gfxPoint.h" // for gfxPoint, gfxSize +#include "mozilla/ServoBindings.h" // for Servo_AnimationValue_GetOpacity, etc +#include "mozilla/ScopeExit.h" // for MakeScopeExit +#include "mozilla/StaticPrefs_apz.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/WidgetUtils.h" // for ComputeTransformForRotation +#include "mozilla/gfx/BaseRect.h" // for BaseRect +#include "mozilla/gfx/Point.h" // for RoundedToInt, PointTyped +#include "mozilla/gfx/Rect.h" // for RoundedToInt, RectTyped +#include "mozilla/gfx/ScaleFactor.h" // for ScaleFactor +#include "mozilla/layers/AnimationHelper.h" +#include "mozilla/layers/APZSampler.h" // for APZSampler +#include "mozilla/layers/APZUtils.h" // for CompleteAsyncTransform +#include "mozilla/layers/Compositor.h" // for Compositor +#include "mozilla/layers/CompositorAnimationStorage.h" // for CompositorAnimationStorage +#include "mozilla/layers/CompositorBridgeParent.h" // for CompositorBridgeParent, etc +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/layers/LayerAnimationUtils.h" // for TimingFunctionToComputedTimingFunction +#include "mozilla/layers/LayerMetricsWrapper.h" // for LayerMetricsWrapper +#include "mozilla/layers/SampleTime.h" +#include "nsCoord.h" // for NSAppUnitsToFloatPixels, etc +#include "nsDebug.h" // for NS_ASSERTION, etc +#include "nsDeviceContext.h" // for nsDeviceContext +#include "nsDisplayList.h" // for nsDisplayTransform, etc +#include "nsMathUtils.h" // for NS_round +#include "nsPoint.h" // for nsPoint +#include "nsRect.h" // for mozilla::gfx::IntRect +#include "nsRegion.h" // for nsIntRegion +#include "nsTArray.h" // for nsTArray, nsTArray_Impl, etc +#include "nsTArrayForwardDeclare.h" // for nsTArray +#include "UnitTransforms.h" // for TransformTo +#if defined(MOZ_WIDGET_ANDROID) +# include +# include "mozilla/layers/UiCompositorControllerParent.h" +# include "mozilla/widget/AndroidCompositorWidget.h" +#endif +#include "GeckoProfiler.h" +#include "FrameUniformityData.h" +#include "TreeTraversal.h" // for ForEachNode, BreadthFirstSearch +#include "VsyncSource.h" + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +static bool IsSameDimension(hal::ScreenOrientation o1, + hal::ScreenOrientation o2) { + bool isO1portrait = (o1 == hal::eScreenOrientation_PortraitPrimary || + o1 == hal::eScreenOrientation_PortraitSecondary); + bool isO2portrait = (o2 == hal::eScreenOrientation_PortraitPrimary || + o2 == hal::eScreenOrientation_PortraitSecondary); + return !(isO1portrait ^ isO2portrait); +} + +static bool ContentMightReflowOnOrientationChange(const IntRect& rect) { + return rect.Width() != rect.Height(); +} + +AsyncCompositionManager::AsyncCompositionManager( + CompositorBridgeParent* aParent, HostLayerManager* aManager) + : mLayerManager(aManager), + mIsFirstPaint(true), + mLayersUpdated(false), + mReadyForCompose(true), + mCompositorBridge(aParent) { + MOZ_ASSERT(mCompositorBridge); +} + +AsyncCompositionManager::~AsyncCompositionManager() = default; + +void AsyncCompositionManager::ResolveRefLayers( + CompositorBridgeParent* aCompositor, bool* aHasRemoteContent, + bool* aResolvePlugins) { + if (aHasRemoteContent) { + *aHasRemoteContent = false; + } + +#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) + // If valid *aResolvePlugins indicates if we need to update plugin geometry + // when we walk the tree. + bool resolvePlugins = (aCompositor && aResolvePlugins && *aResolvePlugins); +#endif + + if (!mLayerManager->GetRoot()) { + // Updated the return value since this result controls completing + // composition. + if (aResolvePlugins) { + *aResolvePlugins = false; + } + return; + } + + mReadyForCompose = true; + bool hasRemoteContent = false; + bool didResolvePlugins = false; + + ForEachNode(mLayerManager->GetRoot(), [&](Layer* layer) { + RefLayer* refLayer = layer->AsRefLayer(); + if (!refLayer) { + return; + } + + hasRemoteContent = true; + const CompositorBridgeParent::LayerTreeState* state = + CompositorBridgeParent::GetIndirectShadowTree( + refLayer->GetReferentId()); + if (!state) { + return; + } + + Layer* referent = state->mRoot; + if (!referent) { + return; + } + + if (!refLayer->GetLocalVisibleRegion().IsEmpty()) { + hal::ScreenOrientation chromeOrientation = mTargetConfig.orientation(); + hal::ScreenOrientation contentOrientation = + state->mTargetConfig.orientation(); + if (!IsSameDimension(chromeOrientation, contentOrientation) && + ContentMightReflowOnOrientationChange( + mTargetConfig.naturalBounds())) { + mReadyForCompose = false; + } + } + + refLayer->ConnectReferentLayer(referent); + +#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) + if (resolvePlugins) { + didResolvePlugins |= + aCompositor->UpdatePluginWindowState(refLayer->GetReferentId()); + } +#endif + }); + + if (aHasRemoteContent) { + *aHasRemoteContent = hasRemoteContent; + } + if (aResolvePlugins) { + *aResolvePlugins = didResolvePlugins; + } +} + +void AsyncCompositionManager::DetachRefLayers() { + if (!mLayerManager->GetRoot()) { + return; + } + + mReadyForCompose = false; + + ForEachNodePostOrder( + mLayerManager->GetRoot(), [&](Layer* layer) { + RefLayer* refLayer = layer->AsRefLayer(); + if (!refLayer) { + return; + } + + const CompositorBridgeParent::LayerTreeState* state = + CompositorBridgeParent::GetIndirectShadowTree( + refLayer->GetReferentId()); + if (!state) { + return; + } + + Layer* referent = state->mRoot; + if (referent) { + refLayer->DetachReferentLayer(referent); + } + }); +} + +void AsyncCompositionManager::ComputeRotation() { + if (!mTargetConfig.naturalBounds().IsEmpty()) { + mWorldTransform = ComputeTransformForRotation(mTargetConfig.naturalBounds(), + mTargetConfig.rotation()); + } +} + +static void GetBaseTransform(Layer* aLayer, Matrix4x4* aTransform) { + // Start with the animated transform if there is one + *aTransform = (aLayer->AsHostLayer()->GetShadowTransformSetByAnimation() + ? aLayer->GetLocalTransform() + : aLayer->GetTransform()); +} + +static void TransformClipRect( + Layer* aLayer, const ParentLayerToParentLayerMatrix4x4& aTransform) { + MOZ_ASSERT(aTransform.Is2D()); + const Maybe& clipRect = + aLayer->AsHostLayer()->GetShadowClipRect(); + if (clipRect) { + ParentLayerIntRect transformed = TransformBy(aTransform, *clipRect); + aLayer->AsHostLayer()->SetShadowClipRect(Some(transformed)); + } +} + +// Similar to TransformFixedClip(), but only transforms the fixed part of the +// clip. +static void TransformFixedClip( + Layer* aLayer, const ParentLayerToParentLayerMatrix4x4& aTransform, + AsyncCompositionManager::ClipParts& aClipParts) { + MOZ_ASSERT(aTransform.Is2D()); + if (aClipParts.mFixedClip) { + *aClipParts.mFixedClip = TransformBy(aTransform, *aClipParts.mFixedClip); + aLayer->AsHostLayer()->SetShadowClipRect(aClipParts.Intersect()); + } +} + +/** + * Set the given transform as the shadow transform on the layer, assuming + * that the given transform already has the pre- and post-scales applied. + * That is, this function cancels out the pre- and post-scales from aTransform + * before setting it as the shadow transform on the layer, so that when + * the layer's effective transform is computed, the pre- and post-scales will + * only be applied once. + */ +static void SetShadowTransform(Layer* aLayer, + LayerToParentLayerMatrix4x4 aTransform) { + if (ContainerLayer* c = aLayer->AsContainerLayer()) { + aTransform.PreScale(1.0f / c->GetPreXScale(), 1.0f / c->GetPreYScale(), 1); + } + aTransform.PostScale(1.0f / aLayer->GetPostXScale(), + 1.0f / aLayer->GetPostYScale(), 1); + aLayer->AsHostLayer()->SetShadowBaseTransform(aTransform.ToUnknownMatrix()); +} + +static void TranslateShadowLayer( + Layer* aLayer, const ParentLayerPoint& aTranslation, bool aAdjustClipRect, + AsyncCompositionManager::ClipPartsCache* aClipPartsCache) { + // This layer might also be a scrollable layer and have an async transform. + // To make sure we don't clobber that, we start with the shadow transform. + // (i.e. GetLocalTransform() instead of GetTransform()). + // Note that the shadow transform is reset on every frame of composition so + // we don't have to worry about the adjustments compounding over successive + // frames. + LayerToParentLayerMatrix4x4 layerTransform = aLayer->GetLocalTransformTyped(); + + // Apply the translation to the layer transform. + layerTransform.PostTranslate(aTranslation); + + SetShadowTransform(aLayer, layerTransform); + aLayer->AsHostLayer()->SetShadowTransformSetByAnimation(false); + + if (aAdjustClipRect) { + auto transform = + ParentLayerToParentLayerMatrix4x4::Translation(aTranslation); + // If we're passed a clip parts cache, only transform the fixed part of + // the clip. + if (aClipPartsCache) { + auto iter = aClipPartsCache->find(aLayer); + MOZ_ASSERT(iter != aClipPartsCache->end()); + TransformFixedClip(aLayer, transform, iter->second); + } else { + TransformClipRect(aLayer, transform); + } + + // If a fixed- or sticky-position layer has a mask layer, that mask should + // move along with the layer, so apply the translation to the mask layer + // too. + if (Layer* maskLayer = aLayer->GetMaskLayer()) { + TranslateShadowLayer(maskLayer, aTranslation, false, aClipPartsCache); + } + } +} + +static void AccumulateLayerTransforms(Layer* aLayer, Layer* aAncestor, + Matrix4x4& aMatrix) { + // Accumulate the transforms between this layer and the subtree root layer. + for (Layer* l = aLayer; l && l != aAncestor; l = l->GetParent()) { + Matrix4x4 transform; + GetBaseTransform(l, &transform); + aMatrix *= transform; + } +} + +/** + * Finds the metrics on |aLayer| with scroll id |aScrollId|, and returns a + * LayerMetricsWrapper representing the (layer, metrics) pair, or the null + * LayerMetricsWrapper if no matching metrics could be found. + */ +static LayerMetricsWrapper FindMetricsWithScrollId( + Layer* aLayer, ScrollableLayerGuid::ViewID aScrollId) { + for (uint64_t i = 0; i < aLayer->GetScrollMetadataCount(); ++i) { + if (aLayer->GetFrameMetrics(i).GetScrollId() == aScrollId) { + return LayerMetricsWrapper(aLayer, i); + } + } + return LayerMetricsWrapper(); +} + +/** + * Checks whether the (layer, metrics) pair (aTransformedLayer, + * aTransformedMetrics) is on the path from |aFixedLayer| to the metrics with + * scroll id |aFixedWithRespectTo|, inclusive. + */ +static bool AsyncTransformShouldBeUnapplied( + Layer* aFixedLayer, ScrollableLayerGuid::ViewID aFixedWithRespectTo, + Layer* aTransformedLayer, ScrollableLayerGuid::ViewID aTransformedMetrics) { + LayerMetricsWrapper transformed = + FindMetricsWithScrollId(aTransformedLayer, aTransformedMetrics); + if (!transformed.IsValid()) { + return false; + } + // It's important to start at the bottom, because the fixed layer itself + // could have the transformed metrics, and they can be at the bottom. + LayerMetricsWrapper current(aFixedLayer, + LayerMetricsWrapper::StartAt::BOTTOM); + bool encounteredTransformedLayer = false; + // The transformed layer is on the path from |aFixedLayer| to the fixed-to + // layer if as we walk up the (layer, metrics) tree starting from + // |aFixedLayer|, we *first* encounter the transformed layer, and *then* (or + // at the same time) the fixed-to layer. + while (current) { + if (!encounteredTransformedLayer && current == transformed) { + encounteredTransformedLayer = true; + } + if (current.Metrics().GetScrollId() == aFixedWithRespectTo) { + return encounteredTransformedLayer; + } + current = current.GetParent(); + // It's possible that we reach a layers id boundary before we reach an + // ancestor with the scroll id |aFixedWithRespectTo| (this could happen + // e.g. if the scroll frame with that scroll id uses containerless + // scrolling). In such a case, stop the walk, as a new layers id could + // have a different layer with scroll id |aFixedWithRespectTo| which we + // don't intend to match. + if (current && current.AsRefLayer() != nullptr) { + break; + } + } + return false; +} + +// If |aLayer| is fixed or sticky, returns the scroll id of the scroll frame +// that it's fixed or sticky to. Otherwise, returns Nothing(). +static Maybe IsFixedOrSticky(Layer* aLayer) { + bool isRootOfFixedSubtree = aLayer->GetIsFixedPosition() && + !aLayer->GetParent()->GetIsFixedPosition(); + if (isRootOfFixedSubtree) { + return Some(aLayer->GetFixedPositionScrollContainerId()); + } + if (aLayer->GetIsStickyPosition()) { + return Some(aLayer->GetStickyScrollContainerId()); + } + return Nothing(); +} + +void AsyncCompositionManager::AlignFixedAndStickyLayers( + Layer* aTransformedSubtreeRoot, Layer* aStartTraversalAt, + SideBits aStuckSides, ScrollableLayerGuid::ViewID aTransformScrollId, + const LayerToParentLayerMatrix4x4& aPreviousTransformForRoot, + const LayerToParentLayerMatrix4x4& aCurrentTransformForRoot, + const ScreenMargin& aFixedLayerMargins, ClipPartsCache& aClipPartsCache, + const ScreenMargin& aGeckoFixedLayerMargins) { + Layer* layer = aStartTraversalAt; + bool needsAsyncTransformUnapplied = false; + if (Maybe fixedTo = IsFixedOrSticky(layer)) { + needsAsyncTransformUnapplied = AsyncTransformShouldBeUnapplied( + layer, *fixedTo, aTransformedSubtreeRoot, aTransformScrollId); + } + + // We want to process all the fixed and sticky descendants of + // aTransformedSubtreeRoot. Once we do encounter such a descendant, we don't + // need to recurse any deeper because the adjustment to the fixed or sticky + // layer will apply to its subtree. + if (!needsAsyncTransformUnapplied) { + for (Layer* child = layer->GetFirstChild(); child; + child = child->GetNextSibling()) { + AlignFixedAndStickyLayers(aTransformedSubtreeRoot, child, aStuckSides, + aTransformScrollId, aPreviousTransformForRoot, + aCurrentTransformForRoot, aFixedLayerMargins, + aClipPartsCache, aGeckoFixedLayerMargins); + } + return; + } + + AdjustFixedOrStickyLayer(aTransformedSubtreeRoot, layer, aStuckSides, + aTransformScrollId, aPreviousTransformForRoot, + aCurrentTransformForRoot, aFixedLayerMargins, + aClipPartsCache, aGeckoFixedLayerMargins); +} + +// Determine the amount of overlap between the 1D vector |aTranslation| +// and the interval [aMin, aMax]. +static gfxFloat IntervalOverlap(gfxFloat aTranslation, gfxFloat aMin, + gfxFloat aMax) { + if (aTranslation > 0) { + return std::max(0.0, std::min(aMax, aTranslation) - std::max(aMin, 0.0)); + } + + return std::min(0.0, std::max(aMin, aTranslation) - std::min(aMax, 0.0)); +} + +void AsyncCompositionManager::AdjustFixedOrStickyLayer( + Layer* aTransformedSubtreeRoot, Layer* aFixedOrSticky, SideBits aStuckSides, + ScrollableLayerGuid::ViewID aTransformScrollId, + const LayerToParentLayerMatrix4x4& aPreviousTransformForRoot, + const LayerToParentLayerMatrix4x4& aCurrentTransformForRoot, + const ScreenMargin& aFixedLayerMargins, ClipPartsCache& aClipPartsCache, + const ScreenMargin& aGeckoFixedLayerMargins) { + Layer* layer = aFixedOrSticky; + + // Insert a translation so that the position of the anchor point is the same + // before and after the change to the transform of aTransformedSubtreeRoot. + + // Accumulate the transforms between this layer and the subtree root layer. + Matrix4x4 ancestorTransform; + if (layer != aTransformedSubtreeRoot) { + AccumulateLayerTransforms(layer->GetParent(), aTransformedSubtreeRoot, + ancestorTransform); + } + ancestorTransform.NudgeToIntegersFixedEpsilon(); + + // A transform creates a containing block for fixed-position descendants, + // so there shouldn't be a transform in between the fixed layer and + // the subtree root layer. + if (layer->GetIsFixedPosition()) { + MOZ_ASSERT(ancestorTransform.IsIdentity()); + } + + // Calculate the cumulative transforms between the subtree root with the + // old transform and the current transform. + // For coordinate purposes, we'll treat the subtree root layer as the + // "parent" layer, even though it could be a farther ancestor. + auto oldCumulativeTransform = ViewAs( + ancestorTransform * aPreviousTransformForRoot.ToUnknownMatrix()); + auto newCumulativeTransform = ViewAs( + ancestorTransform * aCurrentTransformForRoot.ToUnknownMatrix()); + + // We're going to be inverting |newCumulativeTransform|. If it's singular, + // there's nothing we can do. + if (newCumulativeTransform.IsSingular()) { + return; + } + + // Since we create container layers for fixed layers, there shouldn't + // a local CSS or OMTA transform on the fixed layer, either (any local + // transform would go onto a descendant layer inside the container + // layer). +#ifdef DEBUG + Matrix4x4 localTransform; + GetBaseTransform(layer, &localTransform); + localTransform.NudgeToIntegersFixedEpsilon(); + MOZ_ASSERT(localTransform.IsIdentity()); +#endif + + // Now work out the translation necessary to make sure the layer doesn't + // move given the new sub-tree root transform. + + // Get the layer's fixed anchor point, in the layer's local coordinate space + // (before any transform is applied). + LayerPoint anchor = layer->GetFixedPositionAnchor(); + + SideBits sideBits = layer->GetFixedPositionSides(); + if (layer->GetIsStickyPosition()) { + // For sticky items, it may be that only some of the sides are actively + // stuck. Only take into account those sides. + sideBits &= aStuckSides; + } + + // Offset the layer's anchor point to make sure fixed position content + // respects content document fixed position margins. + ScreenPoint offset = apz::ComputeFixedMarginsOffset( + aFixedLayerMargins, sideBits, + // For sticky layers, we don't need to factor aGeckoFixedLayerMargins + // because Gecko doesn't shift the position of sticky elements for dynamic + // toolbar movements. + layer->GetIsStickyPosition() ? ScreenMargin() : aGeckoFixedLayerMargins); + + // Fixed margins only apply to layers fixed to the root, so we can view + // the offset in layer space. + LayerPoint offsetAnchor = + anchor + ViewAs( + offset, PixelCastJustification::ScreenIsParentLayerForRoot); + + // Additionally transform the anchor to compensate for the change + // from the old transform to the new transform. We do + // this by using the old transform to take the offset anchor back into + // subtree root space, and then the inverse of the new transform + // to bring it back to layer space. + ParentLayerPoint offsetAnchorInSubtreeRootSpace = + oldCumulativeTransform.TransformPoint(offsetAnchor); + LayerPoint transformedAnchor = + newCumulativeTransform.Inverse().TransformPoint( + offsetAnchorInSubtreeRootSpace); + + // We want to translate the layer by the difference between + // |transformedAnchor| and |anchor|. + LayerPoint translation = transformedAnchor - anchor; + + // A fixed layer will "consume" (be unadjusted by) the entire translation + // calculated above. A sticky layer may consume all, part, or none of it, + // depending on where we are relative to its sticky scroll range. + // The remainder of the translation (the unconsumed portion) needs to + // be propagated to descendant fixed/sticky layers. + LayerPoint unconsumedTranslation; + + if (layer->GetIsStickyPosition()) { + // For sticky positioned layers, the difference between the two rectangles + // defines a pair of translation intervals in each dimension through which + // the layer should not move relative to the scroll container. To + // accomplish this, we limit each dimension of the |translation| to that + // part of it which overlaps those intervals. + const LayerRectAbsolute& stickyOuter = layer->GetStickyScrollRangeOuter(); + const LayerRectAbsolute& stickyInner = layer->GetStickyScrollRangeInner(); + + LayerPoint originalTranslation = translation; + translation.y = + IntervalOverlap(translation.y, stickyOuter.Y(), stickyOuter.YMost()) - + IntervalOverlap(translation.y, stickyInner.Y(), stickyInner.YMost()); + translation.x = + IntervalOverlap(translation.x, stickyOuter.X(), stickyOuter.XMost()) - + IntervalOverlap(translation.x, stickyInner.X(), stickyInner.XMost()); + unconsumedTranslation = translation - originalTranslation; + } + + // Finally, apply the translation to the layer transform. Note that in cases + // where the async transform on |aTransformedSubtreeRoot| affects this layer's + // clip rect, we need to apply the same translation to said clip rect, so + // that the effective transform on the clip rect takes it back to where it was + // originally, had there been no async scroll. + TranslateShadowLayer( + layer, + ViewAs(translation, + PixelCastJustification::NoTransformOnLayer), + true, &aClipPartsCache); + + // Propragate the unconsumed portion of the translation to descendant + // fixed/sticky layers. + if (unconsumedTranslation != LayerPoint()) { + // Take the computations we performed to derive |translation| from + // |aCurrentTransformForRoot|, and perform them in reverse, keeping other + // quantities fixed, to come up with a new transform |newTransform| that + // would produce |unconsumedTranslation|. + LayerPoint newTransformedAnchor = unconsumedTranslation + anchor; + ParentLayerPoint newTransformedAnchorInSubtreeRootSpace = + oldCumulativeTransform.TransformPoint(newTransformedAnchor); + LayerToParentLayerMatrix4x4 newTransform = aPreviousTransformForRoot; + newTransform.PostTranslate(newTransformedAnchorInSubtreeRootSpace - + offsetAnchorInSubtreeRootSpace); + + // Propagate this new transform to our descendants as the new value of + // |aCurrentTransformForRoot|. This allows them to consume the unconsumed + // translation. + for (Layer* child = layer->GetFirstChild(); child; + child = child->GetNextSibling()) { + AlignFixedAndStickyLayers(aTransformedSubtreeRoot, child, aStuckSides, + aTransformScrollId, aPreviousTransformForRoot, + newTransform, aFixedLayerMargins, + aClipPartsCache, aGeckoFixedLayerMargins); + } + } +} + +bool AsyncCompositionManager::SampleAnimations(Layer* aLayer, + TimeStamp aCurrentFrameTime) { + CompositorAnimationStorage* storage = + mCompositorBridge->GetAnimationStorage(); + MOZ_ASSERT(storage); + + return storage->SampleAnimations(aLayer, mCompositorBridge, + mPreviousFrameTimeStamp, aCurrentFrameTime); +} + +void AsyncCompositionManager::RecordShadowTransforms(Layer* aLayer) { + MOZ_ASSERT(StaticPrefs::gfx_vsync_collect_scroll_transforms()); + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + + ForEachNodePostOrder(aLayer, [this](Layer* layer) { + for (uint32_t i = 0; i < layer->GetScrollMetadataCount(); i++) { + if (!layer->GetFrameMetrics(i).IsScrollable()) { + continue; + } + gfx::Matrix4x4 shadowTransform = + layer->AsHostLayer()->GetShadowBaseTransform(); + if (!shadowTransform.Is2D()) { + continue; + } + + Matrix transform = shadowTransform.As2D(); + if (transform.IsTranslation() && !shadowTransform.IsIdentity()) { + Point translation = transform.GetTranslation(); + mLayerTransformRecorder.RecordTransform(layer, translation); + return; + } + } + }); +} + +static AsyncTransformComponentMatrix AdjustForClip( + const AsyncTransformComponentMatrix& asyncTransform, Layer* aLayer) { + AsyncTransformComponentMatrix result = asyncTransform; + + // Container layers start at the origin, but they are clipped to where they + // actually have content on the screen. The tree transform is meant to apply + // to the clipped area. If the tree transform includes a scale component, + // then applying it to container as-is will produce incorrect results. To + // avoid this, translate the layer so that the clip rect starts at the origin, + // apply the tree transform, and translate back. + if (const Maybe& shadowClipRect = + aLayer->AsHostLayer()->GetShadowClipRect()) { + if (shadowClipRect->TopLeft() != + ParentLayerIntPoint()) { // avoid a gratuitous change of basis + result.ChangeBasis(shadowClipRect->X(), shadowClipRect->Y(), 0); + } + } + return result; +} + +static void ExpandRootClipRect(Layer* aLayer, + const ScreenMargin& aFixedLayerMargins) { + // For Fennec we want to expand the root scrollable layer clip rect based on + // the fixed position margins. In particular, we want this while the dynamic + // toolbar is in the process of sliding offscreen and the area of the + // LayerView visible to the user is larger than the viewport size that Gecko + // knows about (and therefore larger than the clip rect). We could also just + // clear the clip rect on aLayer entirely but this seems more precise. + Maybe rootClipRect = + aLayer->AsHostLayer()->GetShadowClipRect(); + if (rootClipRect && aFixedLayerMargins != ScreenMargin()) { +#ifndef MOZ_WIDGET_ANDROID + // We should never enter here on anything other than Fennec, since + // aFixedLayerMargins should be empty everywhere else. + MOZ_ASSERT(false); +#endif + ParentLayerRect rect(rootClipRect.value()); + rect.Deflate(ViewAs( + aFixedLayerMargins, + PixelCastJustification::ScreenIsParentLayerForRoot)); + aLayer->AsHostLayer()->SetShadowClipRect(Some(RoundedOut(rect))); + } +} + +#ifdef MOZ_WIDGET_ANDROID +static void MoveScrollbarForLayerMargin( + Layer* aRoot, ScrollableLayerGuid::ViewID aRootScrollId, + const ScreenMargin& aFixedLayerMargins) { + // See bug 1223928 comment 9 - once we can detect the RCD with just the + // isRootContent flag on the metrics, we can probably move this code into + // ApplyAsyncTransformToScrollbar rather than having it as a separate + // adjustment on the layer tree. + Layer* scrollbar = + BreadthFirstSearch(aRoot, [aRootScrollId](Layer* aNode) { + return (aNode->GetScrollbarData().IsThumb() && + aNode->GetScrollbarData().mDirection.isSome() && + *aNode->GetScrollbarData().mDirection == + ScrollDirection::eHorizontal && + aNode->GetScrollbarData().mTargetViewId == aRootScrollId); + }); + if (scrollbar) { + // Shift the horizontal scrollbar down into the new space exposed by the + // dynamic toolbar hiding. Technically we should also scale the vertical + // scrollbar a bit to expand into the new space but it's not as noticeable + // and it would add a lot more complexity, so we're going with the "it's not + // worth it" justification. + TranslateShadowLayer(scrollbar, + ParentLayerPoint(0, -aFixedLayerMargins.bottom), true, + nullptr); + if (scrollbar->GetParent()) { + // The layer that has the HORIZONTAL direction sits inside another + // ContainerLayer. This ContainerLayer also has a clip rect that causes + // the scrollbar to get clipped. We need to expand that clip rect to + // prevent that from happening. This is kind of ugly in that we're + // assuming a particular layer tree structure but short of adding more + // flags to the layer there doesn't appear to be a good way to do this. + ExpandRootClipRect(scrollbar->GetParent(), aFixedLayerMargins); + } + } +} +#endif + +bool AsyncCompositionManager::ApplyAsyncContentTransformToTree( + Layer* aLayer, bool* aOutFoundRoot) { + bool appliedTransform = false; + std::stack> stackDeferredClips; + std::stack layersIds; + layersIds.push(mCompositorBridge->RootLayerTreeId()); + + // Maps layers to their ClipParts. The parts are not stored individually + // on the layer, but during AlignFixedAndStickyLayers we need access to + // the individual parts for descendant layers. + ClipPartsCache clipPartsCache; + + Layer* zoomContainer = nullptr; + Maybe zoomedMetrics; + + ForEachNode( + aLayer, + [&](Layer* layer) { + if (layer->AsRefLayer()) { + layersIds.push(layer->AsRefLayer()->GetReferentId()); + } + + stackDeferredClips.push(Maybe()); + + // If we encounter the async zoom container, find the corresponding + // APZC and stash it into |zoomedMetrics|. + // (We stash it in the form of a LayerMetricsWrapper because + // APZSampler requires going through that rather than using the APZC + // directly.) + // We do this on the way down the tree (i.e. here in the pre-action) + // so that by the time we encounter the layers with the RCD-RSF's + // scroll metadata (which will be descendants of the async zoom + // container), we can check for it and know we should only apply the + // scroll portion of the async transform to those layers (as the zoom + // portion will go on the async zoom container). + if (Maybe zoomedScrollId = + layer->IsAsyncZoomContainer()) { + zoomContainer = layer; + ForEachNode( + LayerMetricsWrapper(layer), + [zoomedScrollId, &zoomedMetrics](LayerMetricsWrapper aWrapper) { + // Do not descend into layer subtrees with a different layers + // id. + if (aWrapper.AsRefLayer()) { + return TraversalFlag::Skip; + } + + if (aWrapper.Metrics().GetScrollId() == *zoomedScrollId) { + zoomedMetrics = Some(aWrapper); + MOZ_ASSERT(zoomedMetrics->GetApzc()); + return TraversalFlag::Abort; + } + + return TraversalFlag::Continue; + }); + } + }, + [&](Layer* layer) { + Maybe clipDeferredFromChildren = + stackDeferredClips.top(); + stackDeferredClips.pop(); + MOZ_ASSERT(!layersIds.empty()); + LayersId currentLayersId = layersIds.top(); + LayerToParentLayerMatrix4x4 oldTransform = + layer->GetTransformTyped() * AsyncTransformMatrix(); + + AsyncTransformComponentMatrix combinedAsyncTransform; + bool hasAsyncTransform = false; + // Only set on the root layer for Android. + ScreenMargin fixedLayerMargins; + + // Each layer has multiple clips: + // - Its local clip, which is fixed to the layer contents, i.e. it + // moves with those async transforms which the layer contents move + // with. + // - Its scrolled clip, which moves with all async transforms. + // - For each ScrollMetadata on the layer, a scroll clip. This + // includes the composition bounds and any other clips induced by + // layout. This moves with async transforms from ScrollMetadatas + // above it. + // In this function, these clips are combined into two shadow clip + // parts: + // - The fixed clip, which consists of the local clip only, initially + // transformed by all async transforms. + // - The scrolled clip, which consists of the other clips, transformed + // by the appropriate transforms. + // These two parts are kept separate for now, because for fixed layers, + // we need to adjust the fixed clip (to cancel out some async + // transforms). The parts are kept in a cache which is cleared at the + // beginning of every composite. The final shadow clip for the layer is + // the intersection of the (possibly adjusted) fixed clip and the + // scrolled clip. + ClipParts& clipParts = clipPartsCache[layer]; + clipParts.mFixedClip = layer->GetClipRect(); + clipParts.mScrolledClip = layer->GetScrolledClipRect(); + + // If we are a perspective transform ContainerLayer, apply the clip + // deferred from our child (if there is any) before we iterate over our + // frame metrics, because this clip is subject to all async transforms + // of this layer. Since this clip came from the a scroll clip on the + // child, it becomes part of our scrolled clip. + clipParts.mScrolledClip = IntersectMaybeRects(clipDeferredFromChildren, + clipParts.mScrolledClip); + + // The transform of a mask layer is relative to the masked layer's + // parent layer. So whenever we apply an async transform to a layer, we + // need to apply that same transform to the layer's own mask layer. A + // layer can also have "ancestor" mask layers for any rounded clips from + // its ancestor scroll frames. A scroll frame mask layer only needs to + // be async transformed for async scrolls of this scroll frame's + // ancestor scroll frames, not for async scrolls of this scroll frame + // itself. In the loop below, we iterate over scroll frames from inside + // to outside. At each iteration, this array contains the layer's + // ancestor mask layers of all scroll frames inside the current one. + nsTArray ancestorMaskLayers; + + // The layer's scrolled clip can have an ancestor mask layer as well, + // which is moved by all async scrolls on this layer. + if (const Maybe& scrolledClip = layer->GetScrolledClip()) { + if (scrolledClip->GetMaskLayerIndex()) { + ancestorMaskLayers.AppendElement(layer->GetAncestorMaskLayerAt( + *scrolledClip->GetMaskLayerIndex())); + } + } + + if (RefPtr sampler = mCompositorBridge->GetAPZSampler()) { + for (uint32_t i = 0; i < layer->GetScrollMetadataCount(); i++) { + LayerMetricsWrapper wrapper(layer, i); + if (!wrapper.GetApzc()) { + continue; + } + + const FrameMetrics& metrics = wrapper.Metrics(); + MOZ_ASSERT(metrics.IsScrollable()); + + hasAsyncTransform = true; + + AsyncTransformComponents asyncTransformComponents = + (zoomedMetrics && + sampler->GetGuid(*zoomedMetrics) == sampler->GetGuid(wrapper)) + ? AsyncTransformComponents{AsyncTransformComponent::eLayout} + : LayoutAndVisual; + AsyncTransform asyncTransformWithoutOverscroll = + sampler->GetCurrentAsyncTransform(wrapper, + asyncTransformComponents); + Maybe payload = + sampler->NotifyScrollSampling(wrapper); + // The scroll latency should be measured between composition and the + // first scrolling event. Otherwise we observe metrics with <16ms + // latency even when frame.delay is enabled. + if (payload.isSome()) { + mLayerManager->RegisterPayload(*payload); + } + + AsyncTransformComponentMatrix overscrollTransform = + sampler->GetOverscrollTransform(wrapper); + AsyncTransformComponentMatrix asyncTransform = + AsyncTransformComponentMatrix(asyncTransformWithoutOverscroll) * + overscrollTransform; + + if (!layer->IsScrollableWithoutContent()) { + sampler->MarkAsyncTransformAppliedToContent(wrapper); + } + + const ScrollMetadata& scrollMetadata = wrapper.Metadata(); + +#if defined(MOZ_WIDGET_ANDROID) + // If we find a metrics which is the root content doc, use that. If + // not, use the root layer. Since this function recurses on children + // first we should only end up using the root layer if the entire + // tree was devoid of a root content metrics. This is a temporary + // solution; in the long term we should not need the root content + // metrics at all. See bug 1201529 comment 6 for details. + if (!(*aOutFoundRoot)) { + *aOutFoundRoot = + metrics.IsRootContent() || /* RCD */ + (layer->GetParent() == nullptr && /* rootmost metrics */ + i + 1 >= layer->GetScrollMetadataCount()); + if (*aOutFoundRoot) { + mRootScrollableId = metrics.GetScrollId(); + Compositor* compositor = mLayerManager->GetCompositor(); + if (CompositorBridgeParent* bridge = + compositor->GetCompositorBridgeParent()) { + LayersId rootLayerTreeId = bridge->RootLayerTreeId(); + GeckoViewMetrics gvMetrics = + sampler->GetGeckoViewMetrics(wrapper); + if (mIsFirstPaint || GeckoViewMetricsHaveUpdated(gvMetrics)) { + if (RefPtr uiController = + UiCompositorControllerParent:: + GetFromRootLayerTreeId(rootLayerTreeId)) { + uiController->NotifyUpdateScreenMetrics(gvMetrics); + } + mLastMetrics = gvMetrics; + } + if (mIsFirstPaint) { + if (RefPtr uiController = + UiCompositorControllerParent:: + GetFromRootLayerTreeId(rootLayerTreeId)) { + uiController->NotifyFirstPaint(); + } + mIsFirstPaint = false; + } + if (mLayersUpdated) { + LayersId rootLayerTreeId = bridge->RootLayerTreeId(); + if (RefPtr uiController = + UiCompositorControllerParent:: + GetFromRootLayerTreeId(rootLayerTreeId)) { + uiController->NotifyLayersUpdated(); + } + mLayersUpdated = false; + } + } + fixedLayerMargins = GetFixedLayerMargins(); + } + } +#else + *aOutFoundRoot = false; + // Non-Android platforms still care about this flag being cleared + // after the first call to TransformShadowTree(). + mIsFirstPaint = false; +#endif + + // Transform the current local clips by this APZC's async transform. + MOZ_ASSERT(asyncTransform.Is2D()); + if (clipParts.mFixedClip) { + *clipParts.mFixedClip = + TransformBy(asyncTransform, *clipParts.mFixedClip); + } + if (clipParts.mScrolledClip) { + *clipParts.mScrolledClip = + TransformBy(asyncTransform, *clipParts.mScrolledClip); + } + // Note: we don't set the layer's shadow clip rect property yet; + // AlignFixedAndStickyLayers will use the clip parts from the clip + // parts cache. + + combinedAsyncTransform *= asyncTransform; + + // For the purpose of aligning fixed and sticky layers, we disregard + // the overscroll transform as well as any OMTA transform when + // computing the 'aCurrentTransformForRoot' parameter. This ensures + // that the overscroll and OMTA transforms are not unapplied, and + // therefore that the visual effects apply to fixed and sticky + // layers. We do this by using GetTransform() as the base transform + // rather than GetLocalTransform(), which would include those + // factors. + LayerToParentLayerMatrix4x4 transformWithoutOverscrollOrOmta = + layer->GetTransformTyped() * + CompleteAsyncTransform(AdjustForClip(asyncTransform, layer)); + // See bug 1630274 for why we pass eNone here; fixing that bug will + // probably end up changing this to be more correct. + AlignFixedAndStickyLayers(layer, layer, SideBits::eNone, + metrics.GetScrollId(), oldTransform, + transformWithoutOverscrollOrOmta, + fixedLayerMargins, clipPartsCache, + sampler->GetGeckoFixedLayerMargins()); + + // Combine the local clip with the ancestor scrollframe clip. This + // is not included in the async transform above, since the ancestor + // clip should not move with this APZC. + if (scrollMetadata.HasScrollClip()) { + ParentLayerIntRect clip = + scrollMetadata.ScrollClip().GetClipRect(); + if (layer->GetParent() && + layer->GetParent()->GetTransformIsPerspective()) { + // If our parent layer has a perspective transform, we want to + // apply our scroll clip to it instead of to this layer (see bug + // 1168263). A layer with a perspective transform shouldn't have + // multiple children with FrameMetrics, nor a child with + // multiple FrameMetrics. (A child with multiple FrameMetrics + // would mean that there's *another* scrollable element between + // the one with the CSS perspective and the transformed element. + // But you'd have to use preserve-3d on the inner scrollable + // element in order to have the perspective apply to the + // transformed child, and preserve-3d is not supported on + // scrollable elements, so this case can't occur.) + MOZ_ASSERT(!stackDeferredClips.top()); + stackDeferredClips.top().emplace(clip); + } else { + clipParts.mScrolledClip = + IntersectMaybeRects(Some(clip), clipParts.mScrolledClip); + } + } + + // Do the same for the ancestor mask layers: ancestorMaskLayers + // contains the ancestor mask layers for scroll frames *inside* the + // current scroll frame, so these are the ones we need to shift by + // our async transform. + for (Layer* ancestorMaskLayer : ancestorMaskLayers) { + SetShadowTransform( + ancestorMaskLayer, + ancestorMaskLayer->GetLocalTransformTyped() * asyncTransform); + } + + // Append the ancestor mask layer for this scroll frame to + // ancestorMaskLayers. + if (scrollMetadata.HasScrollClip()) { + const LayerClip& scrollClip = scrollMetadata.ScrollClip(); + if (scrollClip.GetMaskLayerIndex()) { + size_t maskLayerIndex = scrollClip.GetMaskLayerIndex().value(); + Layer* ancestorMaskLayer = + layer->GetAncestorMaskLayerAt(maskLayerIndex); + ancestorMaskLayers.AppendElement(ancestorMaskLayer); + } + } + } + + if (Maybe zoomedScrollId = + layer->IsAsyncZoomContainer()) { + if (zoomedMetrics) { + AsyncTransform zoomTransform = sampler->GetCurrentAsyncTransform( + *zoomedMetrics, {AsyncTransformComponent::eVisual}); + hasAsyncTransform = true; + combinedAsyncTransform *= + AsyncTransformComponentMatrix(zoomTransform); + } else { + // TODO: Is this normal? It happens on some pages, such as + // about:config on mobile, for just one frame or so, before the + // scroll metadata for zoomedScrollId appears in the layer tree. + } + } + + auto IsFixedToZoomContainer = [&](Layer* aFixedLayer) { + if (!zoomedMetrics) { + return false; + } + ScrollableLayerGuid::ViewID targetId = + aFixedLayer->GetFixedPositionScrollContainerId(); + MOZ_ASSERT(targetId != ScrollableLayerGuid::NULL_SCROLL_ID); + ScrollableLayerGuid rootContent = sampler->GetGuid(*zoomedMetrics); + return rootContent.mScrollId == targetId && + rootContent.mLayersId == currentLayersId; + }; + + auto SidesStuckToZoomContainer = [&](Layer* aLayer) -> SideBits { + SideBits result = SideBits::eNone; + if (!zoomedMetrics) { + return result; + } + if (!aLayer->GetIsStickyPosition()) { + return result; + } + + ScrollableLayerGuid::ViewID targetId = + aLayer->GetStickyScrollContainerId(); + if (targetId == ScrollableLayerGuid::NULL_SCROLL_ID) { + return result; + } + + ScrollableLayerGuid rootContent = sampler->GetGuid(*zoomedMetrics); + if (rootContent.mScrollId != targetId || + rootContent.mLayersId != currentLayersId) { + return result; + } + + ParentLayerPoint translation = + sampler + ->GetCurrentAsyncTransform( + *zoomedMetrics, {AsyncTransformComponent::eLayout}) + .mTranslation; + + if (apz::IsStuckAtBottom(translation.y, + aLayer->GetStickyScrollRangeInner(), + aLayer->GetStickyScrollRangeOuter())) { + result |= SideBits::eBottom; + } + if (apz::IsStuckAtTop(translation.y, + aLayer->GetStickyScrollRangeInner(), + aLayer->GetStickyScrollRangeOuter())) { + result |= SideBits::eTop; + } + return result; + }; + + // Layers fixed to the RCD-RSF no longer need + // AdjustFixedOrStickyLayer() to scroll them by the eVisual transform, + // as that's now applied to the async zoom container itself. However, + // we still need to adjust them by the fixed layer margins to + // account for dynamic toolbar transitions. This is also handled by + // AdjustFixedOrStickyLayer(), so we now call it with empty transforms + // to get it to perform just the fixed margins adjustment. + SideBits stuckSides = SidesStuckToZoomContainer(layer); + if (zoomedMetrics && ((layer->GetIsFixedPosition() && + !layer->GetParent()->GetIsFixedPosition() && + IsFixedToZoomContainer(layer)) || + stuckSides != SideBits::eNone)) { + LayerToParentLayerMatrix4x4 emptyTransform; + ScreenMargin marginsForFixedLayer = GetFixedLayerMargins(); + AdjustFixedOrStickyLayer(zoomContainer, layer, stuckSides, + sampler->GetGuid(*zoomedMetrics).mScrollId, + emptyTransform, emptyTransform, + marginsForFixedLayer, clipPartsCache, + sampler->GetGeckoFixedLayerMargins()); + } + } + + bool clipChanged = (hasAsyncTransform || clipDeferredFromChildren || + layer->GetScrolledClipRect()); + if (clipChanged) { + // Intersect the two clip parts and apply them to the layer. + // During ApplyAsyncContentTransformTree on an ancestor layer, + // AlignFixedAndStickyLayers may overwrite this with a new clip it + // computes from the clip parts, but if that doesn't happen, this + // is the layer's final clip rect. + layer->AsHostLayer()->SetShadowClipRect(clipParts.Intersect()); + } + + if (hasAsyncTransform) { + // Apply the APZ transform on top of GetLocalTransform() here (rather + // than GetTransform()) in case the OMTA code in SampleAnimations + // already set a shadow transform; in that case we want to apply ours + // on top of that one rather than clobber it. + SetShadowTransform(layer, + layer->GetLocalTransformTyped() * + AdjustForClip(combinedAsyncTransform, layer)); + + // Do the same for the layer's own mask layer, if it has one. + if (Layer* maskLayer = layer->GetMaskLayer()) { + SetShadowTransform(maskLayer, maskLayer->GetLocalTransformTyped() * + combinedAsyncTransform); + } + + appliedTransform = true; + } + + ExpandRootClipRect(layer, fixedLayerMargins); + + if (layer->GetScrollbarData().mScrollbarLayerType == + layers::ScrollbarLayerType::Thumb) { + ApplyAsyncTransformToScrollbar(layer); + } + + if (layer->AsRefLayer()) { + MOZ_ASSERT(layersIds.size() > 1); + layersIds.pop(); + } + }); + + return appliedTransform; +} + +#if defined(MOZ_WIDGET_ANDROID) +bool AsyncCompositionManager::GeckoViewMetricsHaveUpdated( + const GeckoViewMetrics& aMetrics) { + return RoundedToInt(mLastMetrics.mVisualScrollOffset) != + RoundedToInt(aMetrics.mVisualScrollOffset) || + mLastMetrics.mZoom != aMetrics.mZoom; +} +#endif + +static bool LayerIsScrollbarTarget(const LayerMetricsWrapper& aTarget, + Layer* aScrollbar) { + if (!aTarget.GetApzc()) { + return false; + } + const FrameMetrics& metrics = aTarget.Metrics(); + MOZ_ASSERT(metrics.IsScrollable()); + if (metrics.GetScrollId() != aScrollbar->GetScrollbarData().mTargetViewId) { + return false; + } + return !metrics.IsScrollInfoLayer(); +} + +static void ApplyAsyncTransformToScrollbarForContent( + const RefPtr& aSampler, Layer* aScrollbar, + const LayerMetricsWrapper& aContent, bool aScrollbarIsDescendant) { + AsyncTransformComponentMatrix clipTransform; + + MOZ_ASSERT(aSampler); + LayerToParentLayerMatrix4x4 transform = + aSampler->ComputeTransformForScrollThumb( + aScrollbar->GetLocalTransformTyped(), aContent, + aScrollbar->GetScrollbarData(), aScrollbarIsDescendant, + &clipTransform); + + if (aScrollbarIsDescendant) { + // We also need to make a corresponding change on the clip rect of all the + // layers on the ancestor chain from the scrollbar layer up to but not + // including the layer with the async transform. Otherwise the scrollbar + // shifts but gets clipped and so appears to flicker. + for (Layer* ancestor = aScrollbar; ancestor != aContent.GetLayer(); + ancestor = ancestor->GetParent()) { + TransformClipRect(ancestor, clipTransform); + } + } + + SetShadowTransform(aScrollbar, transform); +} + +static LayerMetricsWrapper FindScrolledLayerForScrollbar(Layer* aScrollbar, + bool* aOutIsAncestor) { + // First check if the scrolled layer is an ancestor of the scrollbar layer. + LayerMetricsWrapper root(aScrollbar->Manager()->GetRoot()); + LayerMetricsWrapper prevAncestor(aScrollbar); + LayerMetricsWrapper scrolledLayer; + + for (LayerMetricsWrapper ancestor(aScrollbar); ancestor; + ancestor = ancestor.GetParent()) { + // Don't walk into remote layer trees; the scrollbar will always be in + // the same layer space. + if (ancestor.AsRefLayer()) { + root = prevAncestor; + break; + } + prevAncestor = ancestor; + + if (LayerIsScrollbarTarget(ancestor, aScrollbar)) { + *aOutIsAncestor = true; + return ancestor; + } + } + + // Search the entire layer space of the scrollbar. + ForEachNode(root, [&root, &scrolledLayer, &aScrollbar]( + LayerMetricsWrapper aLayerMetrics) { + // Do not recurse into RefLayers, since our initial aSubtreeRoot is the + // root (or RefLayer root) of a single layer space to search. + if (root != aLayerMetrics && aLayerMetrics.AsRefLayer()) { + return TraversalFlag::Skip; + } + if (LayerIsScrollbarTarget(aLayerMetrics, aScrollbar)) { + scrolledLayer = aLayerMetrics; + return TraversalFlag::Abort; + } + return TraversalFlag::Continue; + }); + return scrolledLayer; +} + +void AsyncCompositionManager::ApplyAsyncTransformToScrollbar(Layer* aLayer) { + // If this layer corresponds to a scrollbar, then there should be a layer that + // is a previous sibling or a parent that has a matching ViewID on its + // FrameMetrics. That is the content that this scrollbar is for. We pick up + // the transient async transform from that layer and use it to update the + // scrollbar position. Note that it is possible that the content layer is no + // longer there; in this case we don't need to do anything because there can't + // be an async transform on the content. + bool isAncestor = false; + const LayerMetricsWrapper& scrollTarget = + FindScrolledLayerForScrollbar(aLayer, &isAncestor); + if (scrollTarget) { + ApplyAsyncTransformToScrollbarForContent(mCompositorBridge->GetAPZSampler(), + aLayer, scrollTarget, isAncestor); + } +} + +void AsyncCompositionManager::GetFrameUniformity( + FrameUniformityData* aOutData) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + mLayerTransformRecorder.EndTest(aOutData); +} + +bool AsyncCompositionManager::TransformShadowTree( + const SampleTime& aCurrentFrame, TimeDuration aVsyncRate, + CompositorBridgeParentBase::TransformsToSkip aSkip) { + AUTO_PROFILER_LABEL("AsyncCompositionManager::TransformShadowTree", GRAPHICS); + + Layer* root = mLayerManager->GetRoot(); + if (!root) { + return false; + } + + // First, compute and set the shadow transforms from OMT animations. + // NB: we must sample animations *before* sampling pan/zoom + // transforms. + bool wantNextFrame = SampleAnimations(root, aCurrentFrame.Time()); + + // Advance animations to the next expected vsync timestamp, if we can + // get it. + SampleTime nextFrame = aCurrentFrame; + + MOZ_ASSERT(aVsyncRate != TimeDuration::Forever()); + if (aVsyncRate != TimeDuration::Forever()) { + nextFrame = nextFrame + aVsyncRate; + } + + // Reset the previous time stamp if we don't already have any running + // animations to avoid using the time which is far behind for newly + // started animations. + mPreviousFrameTimeStamp = wantNextFrame ? aCurrentFrame.Time() : TimeStamp(); + + if (!(aSkip & CompositorBridgeParentBase::TransformsToSkip::APZ)) { + bool apzAnimating = false; + if (RefPtr apz = mCompositorBridge->GetAPZSampler()) { + apzAnimating = apz->AdvanceAnimations(nextFrame); + } + wantNextFrame |= apzAnimating; + + // Apply an async content transform to any layer that has + // an async pan zoom controller. + bool foundRoot = false; + if (ApplyAsyncContentTransformToTree(root, &foundRoot)) { +#if defined(MOZ_WIDGET_ANDROID) + MOZ_ASSERT(foundRoot); + if (foundRoot && GetFixedLayerMargins() != ScreenMargin()) { + MoveScrollbarForLayerMargin(root, mRootScrollableId, + GetFixedLayerMargins()); + } +#endif + } + } + + HostLayer* rootComposite = root->AsHostLayer(); + + gfx::Matrix4x4 trans = rootComposite->GetShadowBaseTransform(); + trans *= gfx::Matrix4x4::From2D(mWorldTransform); + rootComposite->SetShadowBaseTransform(trans); + + if (StaticPrefs::gfx_vsync_collect_scroll_transforms()) { + RecordShadowTransforms(root); + } + + return wantNextFrame; +} + +void AsyncCompositionManager::SetFixedLayerMargins(ScreenIntCoord aTop, + ScreenIntCoord aBottom) { + mFixedLayerMargins.top = aTop; + mFixedLayerMargins.bottom = aBottom; +} +ScreenMargin AsyncCompositionManager::GetFixedLayerMargins() const { + ScreenMargin result = mFixedLayerMargins; + if (StaticPrefs::apz_fixed_margin_override_enabled()) { + result.top = StaticPrefs::apz_fixed_margin_override_top(); + result.bottom = StaticPrefs::apz_fixed_margin_override_bottom(); + } + return result; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/AsyncCompositionManager.h b/gfx/layers/composite/AsyncCompositionManager.h new file mode 100644 index 0000000000..474f653f99 --- /dev/null +++ b/gfx/layers/composite/AsyncCompositionManager.h @@ -0,0 +1,273 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_ASYNCCOMPOSITIONMANAGER_H +#define GFX_ASYNCCOMPOSITIONMANAGER_H + +#include "Units.h" // for ScreenPoint, etc +#include "FrameMetrics.h" // for FrameMetrics +#include "mozilla/layers/APZUtils.h" // for GeckoViewMetrics +#include "mozilla/layers/CompositorBridgeParent.h" // for TransformsToSkip +#include "mozilla/layers/LayerManagerComposite.h" // for LayerManagerComposite +#include "mozilla/Attributes.h" // for final, etc +#include "mozilla/RefPtr.h" // for RefCounted +#include "mozilla/TimeStamp.h" // for TimeStamp +#include "mozilla/gfx/BasePoint.h" // for BasePoint +#include "mozilla/gfx/Matrix.h" // for Matrix4x4 +#include "mozilla/HalScreenConfiguration.h" // For ScreenOrientation +#include "mozilla/layers/FrameUniformityData.h" // For FrameUniformityData +#include "mozilla/layers/LayersMessages.h" // for TargetConfig +#include "mozilla/RefPtr.h" // for nsRefPtr +#include "nsISupportsImpl.h" // for LayerManager::AddRef, etc + +namespace mozilla { +namespace layers { + +class Layer; +class LayerManagerComposite; +class AutoResolveRefLayers; +class CompositorBridgeParent; +class SampleTime; + +/** + * Manage async composition effects. This class is only used with OMTC and only + * lives on the compositor thread. It is a layer on top of the layer manager + * (LayerManagerComposite) which deals with elements of composition which are + * usually dealt with by dom or layout when main thread rendering, but which can + * short circuit that stuff to directly affect layers as they are composited, + * for example, off-main thread animation, async video, async pan/zoom. + */ +class AsyncCompositionManager final { + friend class AutoResolveRefLayers; + ~AsyncCompositionManager(); + + public: + NS_INLINE_DECL_REFCOUNTING(AsyncCompositionManager) + + AsyncCompositionManager(CompositorBridgeParent* aParent, + HostLayerManager* aManager); + + /** + * This forces the is-first-paint flag to true. This is intended to + * be called by the widget code when it loses its viewport information + * (or for whatever reason wants to refresh the viewport information). + * The information refresh happens because the compositor will call + * AndroidDynamicToolbarAnimator::FirstPaint() on the next frame of + * composition. + */ + void ForceIsFirstPaint() { mIsFirstPaint = true; } + + // Sample transforms for layer trees. Return true to request + // another animation frame. + bool TransformShadowTree( + const SampleTime& aCurrentFrame, TimeDuration aVsyncRate, + CompositorBridgeParentBase::TransformsToSkip aSkip = + CompositorBridgeParentBase::TransformsToSkip::NoneOfThem); + + // Calculates the correct rotation and applies the transform to + // our layer manager + void ComputeRotation(); + + // Call after updating our layer tree. + void Updated(bool isFirstPaint, const TargetConfig& aTargetConfig) { + mIsFirstPaint |= isFirstPaint; + mLayersUpdated = true; + mTargetConfig = aTargetConfig; + } + + bool RequiresReorientation(hal::ScreenOrientation aOrientation) const { + return mTargetConfig.orientation() != aOrientation; + } + + // True if the underlying layer tree is ready to be composited. + bool ReadyForCompose() { return mReadyForCompose; } + + // Returns true if the next composition will be the first for a + // particular document. + bool IsFirstPaint() { return mIsFirstPaint; } + + // GetFrameUniformity will return the frame uniformity for each layer attached + // to an APZ from the recorded data in RecordShadowTransform + void GetFrameUniformity(FrameUniformityData* aFrameUniformityData); + + // Stores the clip rect of a layer in two parts: a fixed part and a scrolled + // part. When a layer is fixed, the clip needs to be adjusted to account for + // async transforms. Only the fixed part needs to be adjusted, so we need + // to store the two parts separately. + struct ClipParts { + Maybe mFixedClip; + Maybe mScrolledClip; + + Maybe Intersect() const { + return IntersectMaybeRects(mFixedClip, mScrolledClip); + } + }; + + typedef std::map ClipPartsCache; + + private: + // Return true if an AsyncPanZoomController content transform was + // applied for |aLayer|. |*aOutFoundRoot| is set to true on Android only, if + // one of the metrics on one of the layers was determined to be the "root" + // and its state was synced to the Java front-end. |aOutFoundRoot| must be + // non-null. + bool ApplyAsyncContentTransformToTree(Layer* aLayer, bool* aOutFoundRoot); + /** + * Update the shadow transform for aLayer assuming that is a scrollbar, + * so that it stays in sync with the content that is being scrolled by APZ. + */ + void ApplyAsyncTransformToScrollbar(Layer* aLayer); + + /** + * Adds a translation to the transform of any fixed position (whose parent + * layer is not fixed) or sticky position layer descendant of + * |aTransformedSubtreeRoot|. The translation is chosen so that the layer's + * anchor point relative to |aTransformedSubtreeRoot|'s parent layer is the + * same as it was when |aTransformedSubtreeRoot|'s GetLocalTransform() was + * |aPreviousTransformForRoot|. |aCurrentTransformForRoot| is + * |aTransformedSubtreeRoot|'s current GetLocalTransform() modulo any + * overscroll-related transform, which we don't want to adjust for. + * For sticky position layers, the translation is further intersected with + * the layer's sticky scroll ranges. + * This function will also adjust layers so that the given content document + * fixed position margins will be respected during asynchronous panning and + * zooming. + * |aTransformScrollId| is the scroll id of the scroll frame that scrolls + * |aTransformedSubtreeRoot|. + * |aClipPartsCache| maps layers to separate fixed and scrolled + * clips, so we can only adjust the fixed portion. + * This function has a recursive implementation; aStartTraversalAt specifies + * where to start the current recursion of the traversal. For the initial + * call, it should be the same as aTrasnformedSubtreeRoot. + */ + void AlignFixedAndStickyLayers( + Layer* aTransformedSubtreeRoot, Layer* aStartTraversalAt, + SideBits aStuckSides, ScrollableLayerGuid::ViewID aTransformScrollId, + const LayerToParentLayerMatrix4x4& aPreviousTransformForRoot, + const LayerToParentLayerMatrix4x4& aCurrentTransformForRoot, + const ScreenMargin& aCompositorFixedLayerMargins, + ClipPartsCache& aClipPartsCache, + const ScreenMargin& aGeckoFixedLayerMargins); + + /** + * Helper function for AlignFixedAndStickyLayers() to perform a transform + * adjustment for a single fixed or sticky layer, rather than all such + * layers rooted at a subtree. May also be called directly. + */ + void AdjustFixedOrStickyLayer( + Layer* aTransformedSubtreeRoot, Layer* aFixedOrSticky, + SideBits aStuckSides, ScrollableLayerGuid::ViewID aTransformScrollId, + const LayerToParentLayerMatrix4x4& aPreviousTransformForRoot, + const LayerToParentLayerMatrix4x4& aCurrentTransformForRoot, + const ScreenMargin& aCompositorFixedLayerMargins, + ClipPartsCache& aClipPartsCache, + const ScreenMargin& aGeckoFixedLayerMargins); + + /** + * DRAWING PHASE ONLY + * + * For reach RefLayer in our layer tree, look up its referent and connect it + * to the layer tree, if found. + * aHasRemoteContent - indicates if the layer tree contains a remote reflayer. + * May be null. + * aResolvePlugins - incoming value indicates if plugin windows should be + * updated through a call on aCompositor's UpdatePluginWindowState. Applies + * to linux and windows only, may be null. On return value indicates + * if any updates occured. + */ + void ResolveRefLayers(CompositorBridgeParent* aCompositor, + bool* aHasRemoteContent, bool* aResolvePlugins); + + /** + * Detaches all referents resolved by ResolveRefLayers. + * Assumes that mLayerManager->GetRoot() and mTargetConfig have not changed + * since ResolveRefLayers was called. + */ + void DetachRefLayers(); + + // Records the shadow transforms for the tree of layers rooted at the given + // layer + void RecordShadowTransforms(Layer* aLayer); + + bool SampleAnimations(Layer* aLayer, TimeStamp aCurrentFrameTime); + + TargetConfig mTargetConfig; + CSSRect mContentRect; + + RefPtr mLayerManager; + // When this flag is set, the next composition will be the first for a + // particular document (i.e. the document displayed on the screen will + // change). This happens when loading a new page or switching tabs. We notify + // the front-end (e.g. Java on Android) about this so that it take the new + // page size and zoom into account when providing us with the next view + // transform. + bool mIsFirstPaint; + + // This flag is set during a layers update, so that the first composition + // after a layers update has it set. It is cleared after that first + // composition. + bool mLayersUpdated; + + bool mReadyForCompose; + + gfx::Matrix mWorldTransform; + LayerTransformRecorder mLayerTransformRecorder; + + TimeStamp mPreviousFrameTimeStamp; + + MOZ_NON_OWNING_REF CompositorBridgeParent* mCompositorBridge; + + public: + void SetFixedLayerMargins(ScreenIntCoord aTop, ScreenIntCoord aBottom); + ScreenMargin GetFixedLayerMargins() const; + + private: + ScreenMargin mFixedLayerMargins; + +#ifdef MOZ_WIDGET_ANDROID + private: + // This calculates whether GeckoView metrics should be sent to Java. + bool GeckoViewMetricsHaveUpdated(const GeckoViewMetrics& aMetrics); + // This holds the most recent GeckoView metrics sent to Java, and is used + // to send new updates when it changes. + GeckoViewMetrics mLastMetrics; + // The following two fields are only needed on Fennec with C++ APZ, because + // then we need to reposition the gecko scrollbar to deal with the + // dynamic toolbar shifting content around. + ScrollableLayerGuid::ViewID mRootScrollableId; +#endif +}; + +class MOZ_STACK_CLASS AutoResolveRefLayers { + public: + explicit AutoResolveRefLayers(AsyncCompositionManager* aManager, + CompositorBridgeParent* aCompositor = nullptr, + bool* aHasRemoteContent = nullptr, + bool* aResolvePlugins = nullptr) + : mManager(aManager) { + if (mManager) { + mManager->ResolveRefLayers(aCompositor, aHasRemoteContent, + aResolvePlugins); + } + } + + ~AutoResolveRefLayers() { + if (mManager) { + mManager->DetachRefLayers(); + } + } + + private: + AsyncCompositionManager* mManager; + + AutoResolveRefLayers(const AutoResolveRefLayers&) = delete; + AutoResolveRefLayers& operator=(const AutoResolveRefLayers&) = delete; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/composite/CanvasLayerComposite.cpp b/gfx/layers/composite/CanvasLayerComposite.cpp new file mode 100644 index 0000000000..9b1d4aa78b --- /dev/null +++ b/gfx/layers/composite/CanvasLayerComposite.cpp @@ -0,0 +1,141 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "CanvasLayerComposite.h" +#include "composite/CompositableHost.h" // for CompositableHost +#include "gfx2DGlue.h" // for ToFilter +#include "gfxEnv.h" // for gfxEnv, etc +#include "mozilla/gfx/Matrix.h" // for Matrix4x4 +#include "mozilla/gfx/Point.h" // for Point +#include "mozilla/gfx/Rect.h" // for Rect +#include "mozilla/layers/Compositor.h" // for Compositor +#include "mozilla/layers/Effects.h" // for EffectChain +#include "mozilla/layers/LayerManagerCompositeUtils.h" +#include "mozilla/mozalloc.h" // for operator delete +#include "nsAString.h" +#include "mozilla/RefPtr.h" // for nsRefPtr +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "nsString.h" // for nsAutoCString + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +CanvasLayerComposite::CanvasLayerComposite(LayerManagerComposite* aManager) + : CanvasLayer(aManager, nullptr), + LayerComposite(aManager), + mCompositableHost(nullptr) { + MOZ_COUNT_CTOR(CanvasLayerComposite); + mImplData = static_cast(this); +} + +CanvasLayerComposite::~CanvasLayerComposite() { + MOZ_COUNT_DTOR(CanvasLayerComposite); + + CleanupResources(); +} + +bool CanvasLayerComposite::SetCompositableHost(CompositableHost* aHost) { + switch (aHost->GetType()) { + case CompositableType::IMAGE: { + if (mCompositableHost && aHost != mCompositableHost) { + mCompositableHost->Detach(this); + } + mCompositableHost = aHost; + return true; + } + default: + return false; + } +} + +Layer* CanvasLayerComposite::GetLayer() { return this; } + +void CanvasLayerComposite::SetLayerManager(HostLayerManager* aManager) { + LayerComposite::SetLayerManager(aManager); + mManager = aManager; + if (mCompositableHost && mCompositor) { + mCompositableHost->SetTextureSourceProvider(mCompositor); + } +} + +void CanvasLayerComposite::RenderLayer(const IntRect& aClipRect, + const Maybe& aGeometry) { + if (!mCompositableHost || !mCompositableHost->IsAttached()) { + return; + } + + mCompositor->MakeCurrent(); + +#ifdef MOZ_DUMP_PAINTING + if (gfxEnv::DumpCompositorTextures()) { + RefPtr surf = mCompositableHost->GetAsSurface(); + if (surf) { + WriteSnapshotToDumpFile(this, surf); + } + } +#endif + + RenderWithAllMasks(this, mCompositor, aClipRect, + [&](EffectChain& effectChain, const IntRect& clipRect) { + mCompositableHost->Composite( + mCompositor, this, effectChain, + GetEffectiveOpacity(), GetEffectiveTransform(), + GetSamplingFilter(), clipRect); + }); + + mCompositableHost->BumpFlashCounter(); +} + +CompositableHost* CanvasLayerComposite::GetCompositableHost() { + if (mCompositableHost && mCompositableHost->IsAttached()) { + return mCompositableHost.get(); + } + + return nullptr; +} + +void CanvasLayerComposite::CleanupResources() { + if (mCompositableHost) { + mCompositableHost->Detach(this); + } + mCompositableHost = nullptr; +} + +gfx::SamplingFilter CanvasLayerComposite::GetSamplingFilter() { + gfx::SamplingFilter filter = mSamplingFilter; +#ifdef ANDROID + // Bug 691354 + // Using the LINEAR filter we get unexplained artifacts. + // Use NEAREST when no scaling is required. + Matrix matrix; + bool is2D = GetEffectiveTransform().Is2D(&matrix); + if (is2D && !ThebesMatrix(matrix).HasNonTranslationOrFlip()) { + filter = SamplingFilter::POINT; + } +#endif + return filter; +} + +void CanvasLayerComposite::GenEffectChain(EffectChain& aEffect) { + aEffect.mLayerRef = this; + aEffect.mPrimaryEffect = mCompositableHost->GenEffect(GetSamplingFilter()); +} + +void CanvasLayerComposite::PrintInfo(std::stringstream& aStream, + const char* aPrefix) { + CanvasLayer::PrintInfo(aStream, aPrefix); + aStream << "\n"; + if (mCompositableHost && mCompositableHost->IsAttached()) { + nsAutoCString pfx(aPrefix); + pfx += " "; + mCompositableHost->PrintInfo(aStream, pfx.get()); + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/CanvasLayerComposite.h b/gfx/layers/composite/CanvasLayerComposite.h new file mode 100644 index 0000000000..dbe29fbb6d --- /dev/null +++ b/gfx/layers/composite/CanvasLayerComposite.h @@ -0,0 +1,72 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_CanvasLayerComposite_H +#define GFX_CanvasLayerComposite_H + +#include "Layers.h" // for CanvasLayer, etc +#include "mozilla/Attributes.h" // for override +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/layers/LayerManagerComposite.h" // for LayerComposite, etc +#include "mozilla/layers/LayersTypes.h" // for LayerRenderState, etc +#include "nsRect.h" // for mozilla::gfx::IntRect +#include "nscore.h" // for nsACString + +namespace mozilla { +namespace layers { + +class CompositableHost; +// Canvas layers use ImageHosts (but CanvasClients) because compositing a +// canvas is identical to compositing an image. +class ImageHost; + +class CanvasLayerComposite : public CanvasLayer, public LayerComposite { + public: + explicit CanvasLayerComposite(LayerManagerComposite* aManager); + + protected: + virtual ~CanvasLayerComposite(); + + public: + bool SetCompositableHost(CompositableHost* aHost) override; + + void Disconnect() override { Destroy(); } + + void SetLayerManager(HostLayerManager* aManager) override; + + Layer* GetLayer() override; + void RenderLayer(const gfx::IntRect& aClipRect, + const Maybe& aGeometry) override; + + void CleanupResources() override; + + void GenEffectChain(EffectChain& aEffect) override; + + CompositableHost* GetCompositableHost() override; + + HostLayer* AsHostLayer() override { return this; } + + const char* Name() const override { return "CanvasLayerComposite"; } + + protected: + RefPtr CreateCanvasRendererInternal() override { + MOZ_CRASH("Incompatible surface type"); + return nullptr; + } + + void PrintInfo(std::stringstream& aStream, const char* aPrefix) override; + + private: + gfx::SamplingFilter GetSamplingFilter(); + + private: + RefPtr mCompositableHost; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_CanvasLayerComposite_H */ diff --git a/gfx/layers/composite/ColorLayerComposite.cpp b/gfx/layers/composite/ColorLayerComposite.cpp new file mode 100644 index 0000000000..4b17a216e8 --- /dev/null +++ b/gfx/layers/composite/ColorLayerComposite.cpp @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ColorLayerComposite.h" +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/gfx/Matrix.h" // for Matrix4x4 +#include "mozilla/gfx/Point.h" // for Point +#include "mozilla/gfx/Rect.h" // for Rect +#include "mozilla/gfx/Types.h" // for Color +#include "mozilla/layers/Compositor.h" // for Compositor +#include "mozilla/layers/CompositorTypes.h" // for DiagnosticFlags::COLOR +#include "mozilla/layers/Effects.h" // for Effect, EffectChain, etc +#include "mozilla/layers/LayerManagerCompositeUtils.h" +#include "mozilla/mozalloc.h" // for operator delete, etc +#include "UnitTransforms.h" // for ViewAs + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +void ColorLayerComposite::RenderLayer(const gfx::IntRect& aClipRect, + const Maybe& aGeometry) { + const Matrix4x4& transform = GetEffectiveTransform(); + +#ifdef MOZ_GFX_OPTIMIZE_MOBILE + // On desktop we want to draw a single rectangle to avoid possible + // seams if we're resampling. On mobile we'd prefer to use the accurate + // region for better performance. + LayerIntRegion drawRegion = GetLocalVisibleRegion(); +#else + LayerIntRegion drawRegion = ViewAs(GetBounds()); +#endif + + for (auto iter = drawRegion.RectIter(); !iter.Done(); iter.Next()) { + const LayerIntRect& rect = iter.Get(); + Rect graphicsRect(rect.X(), rect.Y(), rect.Width(), rect.Height()); + + RenderWithAllMasks(this, mCompositor, aClipRect, + [&](EffectChain& effectChain, const IntRect& clipRect) { + GenEffectChain(effectChain); + + mCompositor->DrawGeometry( + graphicsRect, clipRect, effectChain, + GetEffectiveOpacity(), transform, aGeometry); + }); + } + + Rect rect(GetBounds()); + mCompositor->DrawDiagnostics(DiagnosticFlags::COLOR, rect, aClipRect, + transform); +} + +void ColorLayerComposite::GenEffectChain(EffectChain& aEffect) { + aEffect.mLayerRef = this; + aEffect.mPrimaryEffect = new EffectSolidColor(GetColor()); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/ColorLayerComposite.h b/gfx/layers/composite/ColorLayerComposite.h new file mode 100644 index 0000000000..b499112486 --- /dev/null +++ b/gfx/layers/composite/ColorLayerComposite.h @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_ColorLayerComposite_H +#define GFX_ColorLayerComposite_H + +#include "Layers.h" // for ColorLayer, etc +#include "mozilla/Attributes.h" // for override +#include "mozilla/layers/LayerManagerComposite.h" // for LayerComposite, etc +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc + +namespace mozilla { +namespace layers { + +class CompositableHost; + +class ColorLayerComposite : public ColorLayer, public LayerComposite { + public: + explicit ColorLayerComposite(LayerManagerComposite* aManager) + : ColorLayer(aManager, nullptr), LayerComposite(aManager) { + MOZ_COUNT_CTOR(ColorLayerComposite); + mImplData = static_cast(this); + } + + protected: + virtual ~ColorLayerComposite() { + MOZ_COUNT_DTOR(ColorLayerComposite); + Destroy(); + } + + public: + // LayerComposite Implementation + Layer* GetLayer() override { return this; } + + void SetLayerManager(HostLayerManager* aManager) override { + LayerComposite::SetLayerManager(aManager); + mManager = aManager; + } + + void Destroy() override { mDestroyed = true; } + + void RenderLayer(const gfx::IntRect& aClipRect, + const Maybe& aGeometry) override; + + void CleanupResources() override{}; + + void GenEffectChain(EffectChain& aEffect) override; + + CompositableHost* GetCompositableHost() override { return nullptr; } + + HostLayer* AsHostLayer() override { return this; } + + const char* Name() const override { return "ColorLayerComposite"; } +}; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_ColorLayerComposite_H */ diff --git a/gfx/layers/composite/CompositableHost.cpp b/gfx/layers/composite/CompositableHost.cpp new file mode 100644 index 0000000000..056f26de04 --- /dev/null +++ b/gfx/layers/composite/CompositableHost.cpp @@ -0,0 +1,167 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "CompositableHost.h" +#include // for _Rb_tree_iterator, map, etc +#include // for pair +#include "ContentHost.h" // for ContentHostDoubleBuffered, etc +#include "Effects.h" // for EffectMask, Effect, etc +#include "gfxUtils.h" +#include "ImageHost.h" // for ImageHostBuffered, etc +#include "Layers.h" +#include "TiledContentHost.h" // for TiledContentHost +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor +#include "mozilla/layers/TextureHost.h" // for TextureHost, etc +#include "mozilla/layers/WebRenderImageHost.h" +#include "mozilla/RefPtr.h" // for nsRefPtr +#include "nsDebug.h" // for NS_WARNING +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "gfxPlatform.h" // for gfxPlatform +#include "IPDLActor.h" + +namespace mozilla { + +using namespace gfx; + +namespace layers { + +class Compositor; + +CompositableHost::CompositableHost(const TextureInfo& aTextureInfo) + : mTextureInfo(aTextureInfo), + mCompositorBridgeID(0), + mLayer(nullptr), + mFlashCounter(0), + mAttached(false), + mKeepAttached(false) { + MOZ_COUNT_CTOR(CompositableHost); +} + +CompositableHost::~CompositableHost() { MOZ_COUNT_DTOR(CompositableHost); } + +void CompositableHost::UseTextureHost(const nsTArray& aTextures) { + if (mTextureSourceProvider) { + for (auto& texture : aTextures) { + texture.mTexture->SetTextureSourceProvider(mTextureSourceProvider); + } + } +} + +void CompositableHost::UseComponentAlphaTextures(TextureHost* aTextureOnBlack, + TextureHost* aTextureOnWhite) { + MOZ_ASSERT(aTextureOnBlack && aTextureOnWhite); + if (mTextureSourceProvider) { + aTextureOnBlack->SetTextureSourceProvider(mTextureSourceProvider); + aTextureOnWhite->SetTextureSourceProvider(mTextureSourceProvider); + } +} + +void CompositableHost::RemoveTextureHost(TextureHost* aTexture) {} + +void CompositableHost::SetTextureSourceProvider( + TextureSourceProvider* aProvider) { + MOZ_ASSERT(aProvider); + mTextureSourceProvider = aProvider; +} + +bool CompositableHost::AddMaskEffect(EffectChain& aEffects, + const gfx::Matrix4x4& aTransform) { + CompositableTextureSourceRef source; + RefPtr host = GetAsTextureHost(); + + if (!host) { + NS_WARNING("Using compositable with no valid TextureHost as mask"); + return false; + } + + if (!host->Lock()) { + NS_WARNING("Failed to lock the mask texture"); + return false; + } + + if (!host->BindTextureSource(source)) { + NS_WARNING( + "The TextureHost was successfully locked but can't provide a " + "TextureSource"); + host->Unlock(); + return false; + } + MOZ_ASSERT(source); + + RefPtr effect = + new EffectMask(source, source->GetSize(), aTransform); + aEffects.mSecondaryEffects[EffectTypes::MASK] = effect; + return true; +} + +void CompositableHost::RemoveMaskEffect() { + RefPtr host = GetAsTextureHost(); + if (host) { + host->Unlock(); + } +} + +/* static */ +already_AddRefed CompositableHost::Create( + const TextureInfo& aTextureInfo, bool aUseWebRender) { + RefPtr result; + switch (aTextureInfo.mCompositableType) { + case CompositableType::IMAGE_BRIDGE: + NS_ERROR("Cannot create an image bridge compositable this way"); + break; + case CompositableType::CONTENT_TILED: + result = new TiledContentHost(aTextureInfo); + break; + case CompositableType::IMAGE: + if (aUseWebRender) { + result = new WebRenderImageHost(aTextureInfo); + } else { + result = new ImageHost(aTextureInfo); + } + break; + case CompositableType::CONTENT_SINGLE: + if (aUseWebRender) { + result = new WebRenderImageHost(aTextureInfo); + } else { + result = new ContentHostSingleBuffered(aTextureInfo); + } + break; + case CompositableType::CONTENT_DOUBLE: + MOZ_ASSERT(!aUseWebRender); + result = new ContentHostDoubleBuffered(aTextureInfo); + break; + default: + NS_ERROR("Unknown CompositableType"); + } + return result.forget(); +} + +void CompositableHost::DumpTextureHost(std::stringstream& aStream, + TextureHost* aTexture) { + if (!aTexture) { + return; + } + RefPtr dSurf = aTexture->GetAsSurface(); + if (!dSurf) { + return; + } + aStream << gfxUtils::GetAsDataURI(dSurf).get(); +} + +HostLayerManager* CompositableHost::GetLayerManager() const { + if (!mLayer || !mLayer->Manager()) { + return nullptr; + } + return mLayer->Manager()->AsHostLayerManager(); +} + +TextureSourceProvider* CompositableHost::GetTextureSourceProvider() const { + return mTextureSourceProvider; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/CompositableHost.h b/gfx/layers/composite/CompositableHost.h new file mode 100644 index 0000000000..6f749d76b8 --- /dev/null +++ b/gfx/layers/composite/CompositableHost.h @@ -0,0 +1,278 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_BUFFERHOST_H +#define MOZILLA_GFX_BUFFERHOST_H + +#include // for uint64_t +#include // for FILE +#include "gfxRect.h" // for gfxRect +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/Attributes.h" // for override +#include "mozilla/RefPtr.h" // for RefPtr, RefCounted, etc +#include "mozilla/gfx/MatrixFwd.h" // for Matrix4x4 +#include "mozilla/gfx/Point.h" // for Point +#include "mozilla/gfx/Polygon.h" // for Polygon +#include "mozilla/gfx/Rect.h" // for Rect +#include "mozilla/gfx/Types.h" // for SamplingFilter +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/layers/Compositor.h" // for Compositor +#include "mozilla/layers/CompositorTypes.h" // for TextureInfo, etc +#include "mozilla/layers/Effects.h" // for Texture Effect +#include "mozilla/layers/LayersTypes.h" // for LayerRenderState, etc +#include "mozilla/layers/LayersMessages.h" +#include "mozilla/layers/TextureHost.h" // for TextureHost +#include "mozilla/mozalloc.h" // for operator delete +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsRegion.h" // for nsIntRegion +#include "nscore.h" // for nsACString +#include "Units.h" // for CSSToScreenScale + +namespace mozilla { +namespace gfx { +class DataSourceSurface; +} // namespace gfx + +namespace layers { + +class Layer; +class LayerComposite; +class ImageHost; +class Compositor; +class ThebesBufferData; +class TiledContentHost; +class CompositableParentManager; +class WebRenderImageHost; +class ContentHost; +class ContentHostTexture; +class HostLayerManager; +struct EffectChain; + +struct ImageCompositeNotificationInfo { + base::ProcessId mImageBridgeProcessId; + ImageCompositeNotification mNotification; +}; + +struct AsyncCompositableRef { + AsyncCompositableRef() : mProcessId(mozilla::ipc::kInvalidProcessId) {} + AsyncCompositableRef(base::ProcessId aProcessId, + const CompositableHandle& aHandle) + : mProcessId(aProcessId), mHandle(aHandle) {} + explicit operator bool() const { return !!mHandle; } + base::ProcessId mProcessId; + CompositableHandle mHandle; +}; + +/** + * The compositor-side counterpart to CompositableClient. Responsible for + * updating textures and data about textures from IPC and how textures are + * composited (tiling, double buffering, etc.). + * + * Update (for images/canvases) and UpdateThebes (for Thebes) are called during + * the layers transaction to update the Compositbale's textures from the + * content side. The actual update (and any syncronous upload) is done by the + * TextureHost, but it is coordinated by the CompositableHost. + * + * Composite is called by the owning layer when it is composited. + * CompositableHost will use its TextureHost(s) and call Compositor::DrawQuad to + * do the actual rendering. + */ +class CompositableHost { + protected: + virtual ~CompositableHost(); + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CompositableHost) + explicit CompositableHost(const TextureInfo& aTextureInfo); + + static already_AddRefed Create( + const TextureInfo& aTextureInfo, bool aUseWebRender); + + virtual CompositableType GetType() = 0; + + // If base class overrides, it should still call the parent implementation + virtual void SetTextureSourceProvider(TextureSourceProvider* aProvider); + + // composite the contents of this buffer host to the compositor's surface + virtual void Composite(Compositor* aCompositor, LayerComposite* aLayer, + EffectChain& aEffectChain, float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::SamplingFilter aSamplingFilter, + const gfx::IntRect& aClipRect, + const nsIntRegion* aVisibleRegion = nullptr, + const Maybe& aGeometry = Nothing()) = 0; + + /** + * Update the content host. + * aUpdated is the region which should be updated. + */ + virtual bool UpdateThebes(const ThebesBufferData& aData, + const nsIntRegion& aUpdated, + const nsIntRegion& aOldValidRegionBack) { + NS_ERROR("should be implemented or not used"); + return false; + } + + /** + * Returns the front buffer. + * *aPictureRect (if non-null, and the returned TextureHost is non-null) + * is set to the picture rect. + */ + virtual TextureHost* GetAsTextureHost(gfx::IntRect* aPictureRect = nullptr) { + return nullptr; + } + + virtual gfx::IntSize GetImageSize() { + MOZ_ASSERT(false, "Should have been overridden"); + return gfx::IntSize(); + } + + const TextureInfo& GetTextureInfo() const { return mTextureInfo; } + + /** + * Adds a mask effect using this texture as the mask, if possible. + * @return true if the effect was added, false otherwise. + */ + bool AddMaskEffect(EffectChain& aEffects, const gfx::Matrix4x4& aTransform); + + void RemoveMaskEffect(); + + TextureSourceProvider* GetTextureSourceProvider() const; + + Layer* GetLayer() const { return mLayer; } + void SetLayer(Layer* aLayer) { mLayer = aLayer; } + + virtual ContentHost* AsContentHost() { return nullptr; } + virtual ContentHostTexture* AsContentHostTexture() { return nullptr; } + virtual ImageHost* AsImageHost() { return nullptr; } + virtual TiledContentHost* AsTiledContentHost() { return nullptr; } + virtual WebRenderImageHost* AsWebRenderImageHost() { return nullptr; } + + typedef uint32_t AttachFlags; + static const AttachFlags NO_FLAGS = 0; + static const AttachFlags ALLOW_REATTACH = 1; + static const AttachFlags KEEP_ATTACHED = 2; + static const AttachFlags FORCE_DETACH = 2; + + virtual void Attach(Layer* aLayer, TextureSourceProvider* aProvider, + AttachFlags aFlags = NO_FLAGS) { + MOZ_ASSERT(aProvider); + NS_ASSERTION(aFlags & ALLOW_REATTACH || !mAttached, + "Re-attaching compositables must be explicitly authorised"); + SetTextureSourceProvider(aProvider); + SetLayer(aLayer); + mAttached = true; + mKeepAttached = aFlags & KEEP_ATTACHED; + } + // Detach this compositable host from its layer. + // If we are used for async video, then it is not safe to blindly detach since + // we might be re-attached to a different layer. aLayer is the layer which the + // caller expects us to be attached to, we will only detach if we are in fact + // attached to that layer. If we are part of a normal layer, then we will be + // detached in any case. if aLayer is null, then we will only detach if we are + // not async. + // Only force detach if the IPDL tree is being shutdown. + virtual void Detach(Layer* aLayer = nullptr, AttachFlags aFlags = NO_FLAGS) { + if (!mKeepAttached || aLayer == mLayer || aFlags & FORCE_DETACH) { + SetLayer(nullptr); + mAttached = false; + mKeepAttached = false; + } + } + bool IsAttached() { return mAttached; } + + virtual void Dump(std::stringstream& aStream, const char* aPrefix = "", + bool aDumpHtml = false) {} + static void DumpTextureHost(std::stringstream& aStream, + TextureHost* aTexture); + + virtual already_AddRefed GetAsSurface() { + return nullptr; + } + + virtual void PrintInfo(std::stringstream& aStream, const char* aPrefix) = 0; + + struct TimedTexture { + CompositableTextureHostRef mTexture; + TimeStamp mTimeStamp; + gfx::IntRect mPictureRect; + int32_t mFrameID; + int32_t mProducerID; + }; + virtual void UseTextureHost(const nsTArray& aTextures); + virtual void UseComponentAlphaTextures(TextureHost* aTextureOnBlack, + TextureHost* aTextureOnWhite); + virtual void RemoveTextureHost(TextureHost* aTexture); + + // Called every time this is composited + void BumpFlashCounter() { + mFlashCounter = mFlashCounter >= DIAGNOSTIC_FLASH_COUNTER_MAX + ? DIAGNOSTIC_FLASH_COUNTER_MAX + : mFlashCounter + 1; + } + + uint64_t GetCompositorBridgeID() const { return mCompositorBridgeID; } + + const AsyncCompositableRef& GetAsyncRef() const { return mAsyncRef; } + void SetAsyncRef(const AsyncCompositableRef& aRef) { mAsyncRef = aRef; } + + void SetCompositorBridgeID(uint64_t aID) { mCompositorBridgeID = aID; } + + virtual bool Lock() { return false; } + + virtual void Unlock() {} + + virtual already_AddRefed GenEffect( + const gfx::SamplingFilter aSamplingFilter) { + return nullptr; + } + + /// Called when shutting down the layer tree. + /// This is a good place to clear all potential gpu resources before the + /// widget is is destroyed. + virtual void CleanupResources() {} + + virtual void BindTextureSource() {} + + virtual uint32_t GetDroppedFrames() { return 0; } + + protected: + HostLayerManager* GetLayerManager() const; + + protected: + TextureInfo mTextureInfo; + AsyncCompositableRef mAsyncRef; + uint64_t mCompositorBridgeID; + RefPtr mTextureSourceProvider; + Layer* mLayer; + uint32_t mFlashCounter; // used when the pref "layers.flash-borders" is true. + bool mAttached; + bool mKeepAttached; +}; + +class AutoLockCompositableHost final { + public: + explicit AutoLockCompositableHost(CompositableHost* aHost) : mHost(aHost) { + mSucceeded = (mHost && mHost->Lock()); + } + + ~AutoLockCompositableHost() { + if (mSucceeded && mHost) { + mHost->Unlock(); + } + } + + bool Failed() const { return !mSucceeded; } + + private: + RefPtr mHost; + bool mSucceeded; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/composite/ConsolasFontData.h b/gfx/layers/composite/ConsolasFontData.h new file mode 100644 index 0000000000..a01c417f14 --- /dev/null +++ b/gfx/layers/composite/ConsolasFontData.h @@ -0,0 +1,457 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This is explicitly not guarded as we want only 1 file to include this and +// it's good if things break if someone else does. + +namespace mozilla { +namespace layers { +namespace consolas_font { +const unsigned char sFontPNG[] = { + 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, + 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, + 0x8, 0x0, 0x0, 0x0, 0x0, 0x79, 0x19, 0xf7, 0xba, 0x0, 0x0, 0x0, + 0x4, 0x67, 0x41, 0x4d, 0x41, 0x0, 0x0, 0xb1, 0x8f, 0xb, 0xfc, 0x61, + 0x5, 0x0, 0x0, 0x0, 0x20, 0x63, 0x48, 0x52, 0x4d, 0x0, 0x0, 0x7a, + 0x26, 0x0, 0x0, 0x80, 0x84, 0x0, 0x0, 0xfa, 0x0, 0x0, 0x0, 0x80, + 0xe8, 0x0, 0x0, 0x75, 0x30, 0x0, 0x0, 0xea, 0x60, 0x0, 0x0, 0x3a, + 0x98, 0x0, 0x0, 0x17, 0x70, 0x9c, 0xba, 0x51, 0x3c, 0x0, 0x0, 0x0, + 0x2, 0x62, 0x4b, 0x47, 0x44, 0x0, 0xff, 0x87, 0x8f, 0xcc, 0xbf, 0x0, + 0x0, 0x0, 0x9, 0x70, 0x48, 0x59, 0x73, 0x0, 0x0, 0x19, 0xd6, 0x0, + 0x0, 0x19, 0xd6, 0x1, 0x18, 0xd1, 0xca, 0xed, 0x0, 0x0, 0x0, 0x7, + 0x74, 0x49, 0x4d, 0x45, 0x7, 0xe1, 0x4, 0x2, 0x12, 0x27, 0x25, 0xc0, + 0x26, 0x1d, 0xf0, 0x0, 0x0, 0x12, 0x53, 0x49, 0x44, 0x41, 0x54, 0x78, + 0xda, 0xed, 0x9d, 0x3d, 0x9a, 0xe4, 0xaa, 0xe, 0x86, 0xb5, 0x41, 0xf6, + 0xc1, 0x22, 0x58, 0x2, 0x2b, 0xf0, 0x6, 0x9c, 0x3b, 0x27, 0x76, 0x4a, + 0xea, 0x90, 0xd0, 0x19, 0x3b, 0xf0, 0x1, 0x5c, 0x75, 0xee, 0x3d, 0xa5, + 0x4f, 0x65, 0xe8, 0xb2, 0x87, 0x9e, 0x6e, 0xeb, 0x99, 0xe9, 0xc0, 0xe5, + 0xc2, 0xf2, 0x5b, 0xfc, 0x4a, 0x42, 0xd0, 0xf6, 0xcb, 0x85, 0x7a, 0x2b, + 0xd0, 0x5b, 0x6e, 0x0, 0x7f, 0xe2, 0x21, 0xd1, 0xc5, 0xab, 0x9f, 0x50, + 0x7d, 0xb1, 0x5, 0x80, 0xb7, 0x13, 0xba, 0x6c, 0xed, 0x36, 0x59, 0xdf, + 0xa2, 0x5e, 0xa0, 0x95, 0x5f, 0x1c, 0xd5, 0x50, 0x9e, 0xc1, 0xef, 0x9e, + 0x86, 0xb9, 0x45, 0x9f, 0x52, 0x16, 0x78, 0x80, 0x41, 0x17, 0x9b, 0x0, + 0x38, 0xb2, 0xf0, 0x2b, 0xb4, 0xd, 0x4, 0x54, 0xf4, 0x76, 0xdd, 0x6, + 0xa0, 0xa2, 0x1f, 0x34, 0x71, 0x60, 0x4e, 0x39, 0x35, 0x6e, 0x51, 0xb9, + 0xd7, 0xf, 0x16, 0x4a, 0x62, 0xd0, 0x83, 0x67, 0xac, 0x4f, 0x56, 0x14, + 0xdd, 0xef, 0x49, 0x5f, 0x6, 0xc0, 0x51, 0x40, 0xb7, 0xa7, 0xcf, 0x6, + 0x76, 0x79, 0xcc, 0x2f, 0xc4, 0x4b, 0x4a, 0xf5, 0x28, 0xff, 0xe3, 0x3a, + 0xea, 0x54, 0x5d, 0xc, 0x2a, 0x3f, 0x55, 0x24, 0xc, 0x20, 0x8, 0x6f, + 0x3a, 0x1, 0x6d, 0x9a, 0x0, 0x78, 0x1, 0x80, 0x81, 0x0, 0xc2, 0x40, + 0x21, 0xd0, 0xf8, 0xfa, 0x41, 0x24, 0x5a, 0xd3, 0x9b, 0xb2, 0xf6, 0x38, + 0xea, 0xa8, 0x87, 0x19, 0x94, 0x93, 0xf8, 0x26, 0x0, 0xa8, 0xf6, 0x6, + 0xc2, 0x4d, 0xc0, 0x42, 0x5c, 0x85, 0xa5, 0xf0, 0x41, 0x25, 0x0, 0x81, + 0xb8, 0xf7, 0x5b, 0xe0, 0xbd, 0x5a, 0xa0, 0x87, 0xf0, 0xeb, 0xab, 0x6, + 0x8a, 0xaf, 0x8a, 0xd4, 0xa2, 0xc0, 0x7, 0x3, 0x4d, 0x13, 0x7e, 0x70, + 0x74, 0xf0, 0x7d, 0x56, 0xdc, 0x60, 0xb6, 0xdc, 0x66, 0xc6, 0x2b, 0x0, + 0x48, 0x77, 0x1b, 0x13, 0x66, 0x9a, 0x99, 0x8e, 0x3a, 0x41, 0x81, 0x7a, + 0x4, 0xd4, 0x0, 0xd2, 0x6b, 0x2a, 0xaa, 0xd0, 0xfb, 0xff, 0xc4, 0x91, + 0x13, 0x3e, 0x89, 0xa4, 0x3e, 0x4, 0xd0, 0xa4, 0x48, 0xc2, 0xe5, 0x41, + 0x9d, 0x8b, 0x36, 0x11, 0x50, 0x68, 0xd4, 0x98, 0x61, 0xd, 0x4d, 0x7d, + 0x86, 0x5a, 0x57, 0xb1, 0xc3, 0xe7, 0x62, 0xe5, 0x8a, 0x4e, 0xc7, 0xa3, + 0xfc, 0x7b, 0x0, 0xee, 0xf0, 0xfb, 0xff, 0xca, 0x1a, 0x68, 0x8, 0x23, + 0xcd, 0x60, 0xf0, 0x4d, 0xdd, 0x31, 0x21, 0x32, 0x6a, 0x72, 0x4a, 0x2f, + 0xec, 0x66, 0x9b, 0x3a, 0xf5, 0x85, 0x4e, 0x1, 0x60, 0x3f, 0x3, 0xb0, + 0xb6, 0x8c, 0xf6, 0xf6, 0xd1, 0x5, 0x0, 0x66, 0x69, 0x78, 0x30, 0xe0, + 0xfa, 0xa0, 0x13, 0x61, 0xab, 0x58, 0x39, 0x4b, 0x19, 0x38, 0xea, 0x1f, + 0xfd, 0x6, 0x80, 0xfa, 0xc, 0xc0, 0xe6, 0x1b, 0x26, 0x70, 0xeb, 0x4c, + 0x53, 0x48, 0xc3, 0x17, 0xfb, 0x4a, 0xdc, 0x46, 0x93, 0x0, 0xb0, 0x89, + 0xc3, 0x42, 0x8b, 0xa3, 0x32, 0x76, 0xbe, 0xbc, 0x8e, 0xcf, 0xa3, 0xc0, + 0x71, 0xe3, 0xfd, 0x57, 0xe4, 0x3e, 0x60, 0xfb, 0xb0, 0xf, 0x98, 0xab, + 0x66, 0x12, 0x4f, 0x9, 0xe9, 0x87, 0xb0, 0x60, 0x5e, 0x47, 0x5a, 0x29, + 0x5, 0x3a, 0xea, 0x34, 0x11, 0xf4, 0xb4, 0x8c, 0xaf, 0x2a, 0xce, 0x94, + 0x5a, 0x86, 0x6e, 0xa9, 0x1, 0x8b, 0x38, 0xa, 0xf8, 0xf, 0x47, 0x81, + 0x59, 0x2c, 0x19, 0x49, 0x9e, 0x1b, 0x28, 0xe, 0x60, 0xcd, 0x6d, 0x3, + 0x8c, 0x83, 0x43, 0x7e, 0x73, 0x3, 0x5e, 0x74, 0xca, 0xf7, 0x27, 0xc, + 0x35, 0xf3, 0xd8, 0x5d, 0xf4, 0x55, 0xf3, 0x80, 0xad, 0x5e, 0x87, 0xf7, + 0xcf, 0x98, 0xc5, 0x8f, 0xe0, 0x13, 0x42, 0xe3, 0x73, 0x17, 0xa1, 0xa6, + 0xce, 0x35, 0xc3, 0xf8, 0x1f, 0x58, 0xd, 0x86, 0x8a, 0xdf, 0xe1, 0x33, + 0x19, 0xe1, 0x9c, 0x37, 0x54, 0x75, 0x24, 0x3f, 0xc3, 0x1e, 0x0, 0xd7, + 0xe, 0xf6, 0xe3, 0xd5, 0xe0, 0x5f, 0x24, 0x78, 0x8e, 0x5c, 0xf3, 0xcd, + 0x1f, 0x2, 0xe0, 0xeb, 0x72, 0x3, 0xe8, 0xad, 0x40, 0x6f, 0xb9, 0x1, + 0xf4, 0x56, 0xa0, 0xb7, 0xdc, 0x0, 0x7a, 0x2b, 0xd0, 0x5b, 0x6e, 0x0, + 0x5b, 0xe3, 0xba, 0xff, 0x5c, 0x99, 0xec, 0xd5, 0x2e, 0x93, 0x63, 0x0, + 0x31, 0x6c, 0x69, 0x41, 0xdd, 0x7, 0x41, 0x44, 0xcb, 0x98, 0x25, 0x2d, + 0x86, 0x7, 0xc4, 0xc5, 0x61, 0xfb, 0xc6, 0xa8, 0x48, 0x81, 0x65, 0xaf, + 0x4f, 0xc5, 0xa8, 0x63, 0xbb, 0x52, 0x76, 0x72, 0x4c, 0x34, 0xa9, 0x6, + 0x3, 0xc4, 0x89, 0x32, 0x81, 0x65, 0x7f, 0x5a, 0x9, 0x5b, 0xd, 0xc0, + 0xe4, 0x17, 0x42, 0x0, 0x2c, 0x69, 0x8b, 0xcc, 0xa8, 0x4e, 0x5b, 0xab, + 0x8e, 0xd, 0x2, 0xb4, 0x97, 0x3c, 0x22, 0xe2, 0x82, 0x19, 0xfa, 0x44, + 0x41, 0xdc, 0x55, 0xf6, 0x9, 0x58, 0xbe, 0x84, 0x36, 0xca, 0x41, 0x0, + 0xeb, 0x9c, 0xa1, 0xe1, 0xbe, 0x6c, 0x39, 0x5e, 0x10, 0xd2, 0x16, 0x8a, + 0xc5, 0x2, 0x35, 0x81, 0x26, 0xa3, 0xe8, 0x57, 0xc4, 0x3, 0xd3, 0xe7, + 0x52, 0x16, 0xf1, 0x86, 0x2f, 0xe5, 0x43, 0x94, 0x8d, 0xbc, 0x41, 0x4, + 0x70, 0x68, 0x11, 0xa0, 0xd4, 0xb2, 0x2, 0x79, 0x68, 0xa1, 0xbf, 0x1c, + 0x80, 0x26, 0x5e, 0xf1, 0xb2, 0x85, 0x2f, 0x28, 0x82, 0x36, 0xe, 0x11, + 0x80, 0x47, 0xb6, 0xab, 0xe0, 0x9c, 0x52, 0x87, 0x75, 0xb8, 0x14, 0x99, + 0x9e, 0xc9, 0x35, 0x9, 0x61, 0xa6, 0x31, 0x34, 0x58, 0x67, 0x5c, 0x96, + 0x96, 0x4e, 0x3d, 0x20, 0x3b, 0x46, 0x2, 0x30, 0x91, 0x50, 0xdb, 0x25, + 0x0, 0x51, 0xd1, 0x82, 0x4a, 0x22, 0x32, 0x55, 0x0, 0x60, 0x5b, 0x7f, + 0xba, 0xba, 0xea, 0x9d, 0x43, 0xe5, 0xf6, 0x96, 0x5e, 0x63, 0xc0, 0x2e, + 0xd6, 0xfc, 0xcc, 0xd0, 0x54, 0x3, 0xc, 0xec, 0xeb, 0x62, 0x8, 0xa9, + 0xb0, 0xa3, 0x5f, 0x44, 0x9e, 0x8, 0x5d, 0x5e, 0x3, 0xe0, 0x18, 0x98, + 0xfb, 0xb3, 0x59, 0x6a, 0xbc, 0x2, 0x0, 0x23, 0xf7, 0xf5, 0xf6, 0xb0, + 0x15, 0x7f, 0xc5, 0x33, 0x34, 0xb5, 0xb9, 0xcc, 0xd2, 0xfd, 0x6, 0x5f, + 0x46, 0x3d, 0xef, 0xee, 0xcf, 0xb3, 0xd0, 0x90, 0x8a, 0x1, 0x98, 0x37, + 0x5e, 0x24, 0x7d, 0x68, 0x5e, 0xff, 0xa, 0x80, 0x34, 0x6c, 0x34, 0xd9, + 0x6d, 0x85, 0x96, 0x21, 0xcc, 0x3d, 0x6, 0xd2, 0x6e, 0x0, 0xc8, 0x26, + 0x9b, 0x9e, 0x6b, 0xf9, 0xa4, 0xd5, 0x8, 0x1e, 0xa9, 0xc9, 0xb9, 0x49, + 0xd7, 0xc, 0x83, 0xb2, 0x48, 0xf3, 0x80, 0xb5, 0xad, 0xa9, 0xa7, 0x9f, + 0x1a, 0xd5, 0x75, 0x2f, 0xfd, 0x70, 0xd9, 0x31, 0x6, 0x66, 0xc8, 0x56, + 0x78, 0x51, 0xc9, 0x25, 0x57, 0xee, 0x1f, 0xf, 0x9b, 0xe4, 0x97, 0x16, + 0x43, 0x2d, 0x8e, 0xab, 0x42, 0xc, 0x5d, 0xd4, 0x72, 0xff, 0x74, 0xce, + 0xfc, 0x2b, 0x86, 0x9a, 0xe, 0xa9, 0x1d, 0x40, 0x74, 0xb6, 0xc1, 0x6f, + 0x25, 0x17, 0xd3, 0xe0, 0x0, 0xbf, 0x52, 0xda, 0x1, 0x4, 0x3d, 0x5c, + 0x3d, 0x43, 0xfe, 0xde, 0x0, 0x7e, 0x98, 0xdc, 0x0, 0x7a, 0x2b, 0xd0, + 0x5b, 0x6e, 0x0, 0xbd, 0x15, 0xe8, 0x2d, 0x37, 0x80, 0xde, 0xa, 0xf4, + 0x96, 0x1b, 0x40, 0x6f, 0x5, 0x7a, 0xcb, 0xd, 0x20, 0xff, 0x49, 0x2b, + 0x4d, 0xbe, 0x5e, 0xf1, 0x36, 0xb, 0x9a, 0xb0, 0x87, 0x11, 0x5c, 0x5f, + 0xed, 0x2e, 0x7c, 0x99, 0xe0, 0x54, 0x5a, 0xdd, 0x81, 0xf5, 0xd0, 0x84, + 0xed, 0xf9, 0x1d, 0x0, 0x4, 0xb8, 0x60, 0x77, 0x92, 0x45, 0x6c, 0x84, + 0xd7, 0x83, 0xb4, 0x2c, 0x4d, 0x8b, 0x61, 0x37, 0x82, 0xf5, 0x63, 0xb6, + 0xe7, 0xab, 0xa6, 0x70, 0xec, 0xcb, 0x0, 0x68, 0x1a, 0x20, 0x0, 0x6c, + 0xe, 0xf1, 0x6f, 0x2c, 0x8d, 0x11, 0xd8, 0xe0, 0x8a, 0x3b, 0x87, 0x87, + 0xb3, 0xae, 0x99, 0x49, 0xc4, 0xa6, 0x15, 0xc9, 0x1f, 0x21, 0xfa, 0x29, + 0xe6, 0x86, 0xa8, 0x42, 0xe, 0x20, 0xbd, 0xa9, 0x6b, 0x0, 0x60, 0x90, + 0x5, 0xf6, 0x21, 0x23, 0x30, 0x96, 0x95, 0x28, 0x59, 0xc5, 0xc0, 0xec, + 0xdb, 0x51, 0xe0, 0xca, 0x7a, 0x10, 0x4c, 0x99, 0xd2, 0xf5, 0xf4, 0xd8, + 0x63, 0xf3, 0xb7, 0xc, 0x20, 0x2a, 0xbd, 0x61, 0x0, 0xda, 0x8e, 0x40, + 0x3d, 0xa2, 0xd9, 0x5a, 0x6c, 0x69, 0x41, 0x15, 0x20, 0xef, 0x1, 0xb2, + 0xc0, 0x34, 0xb7, 0x47, 0x37, 0xa2, 0x20, 0xea, 0xfc, 0xa2, 0x50, 0x57, + 0xe9, 0xba, 0x25, 0x3, 0x2a, 0xc0, 0xa3, 0x49, 0x1e, 0x91, 0xa1, 0x52, + 0x3b, 0x9d, 0xd8, 0x7, 0xb0, 0x5f, 0xf4, 0xd1, 0xd6, 0x15, 0x22, 0x30, + 0xc2, 0x26, 0x9d, 0xb, 0xd2, 0xfc, 0xf6, 0xd4, 0x5, 0xa6, 0x3e, 0x0, + 0x56, 0xb3, 0x59, 0xd0, 0x1a, 0x5e, 0x5f, 0xd, 0xee, 0x48, 0x1e, 0x9d, + 0xf2, 0x51, 0xdb, 0xa0, 0x25, 0xbf, 0xa3, 0x13, 0x40, 0x79, 0xde, 0x46, + 0x3, 0xed, 0xbe, 0x3b, 0x34, 0x3c, 0x28, 0x54, 0x8c, 0x27, 0x3d, 0x23, + 0xe3, 0x64, 0x1c, 0xb5, 0x1a, 0x8e, 0xad, 0xb6, 0x87, 0xa2, 0x9b, 0x42, + 0xba, 0x39, 0x80, 0x67, 0x9c, 0xbf, 0x64, 0x72, 0x7, 0x9b, 0xa0, 0x36, + 0x61, 0x43, 0x19, 0xde, 0x65, 0x56, 0x9a, 0xbf, 0x11, 0xe2, 0x85, 0x75, + 0x9b, 0x7d, 0x19, 0x49, 0x34, 0xd8, 0xea, 0x5e, 0x5b, 0x3, 0xa6, 0x7c, + 0x97, 0x22, 0x83, 0xc7, 0x23, 0xc5, 0x15, 0x2c, 0x6e, 0xa8, 0x11, 0xd5, + 0x0, 0x58, 0x1, 0x76, 0xbf, 0xe5, 0x84, 0x7b, 0x54, 0xbc, 0x3b, 0x2a, + 0x58, 0xdc, 0xfd, 0x3a, 0x21, 0x7a, 0x7d, 0x80, 0xa1, 0xe5, 0xd5, 0x7d, + 0x40, 0xa1, 0x5, 0xee, 0x9b, 0x26, 0x37, 0x2a, 0xe0, 0xbd, 0x73, 0xa4, + 0xa6, 0x91, 0xa0, 0x5b, 0x13, 0x42, 0x4c, 0x65, 0xb8, 0x89, 0x8f, 0x2, + 0x79, 0xf6, 0xa5, 0x70, 0x50, 0xbc, 0xe4, 0x77, 0x18, 0xa4, 0xd7, 0x99, + 0xf0, 0x9e, 0xa4, 0x2a, 0x91, 0x1, 0x94, 0xbd, 0x4e, 0xe8, 0xa7, 0x28, + 0x1, 0xfd, 0x40, 0x13, 0x85, 0xd5, 0x5b, 0xb3, 0xe7, 0x42, 0xf3, 0xa1, + 0x33, 0x95, 0x6f, 0x26, 0x38, 0x98, 0x8, 0x9e, 0xa4, 0xbc, 0xd3, 0x4e, + 0xf8, 0x3d, 0xfd, 0x47, 0xf3, 0x0, 0x51, 0x44, 0xbb, 0x7a, 0x95, 0xc1, + 0xfd, 0x83, 0xfb, 0xa5, 0xb7, 0x31, 0x87, 0xae, 0xce, 0x73, 0x1, 0x7c, + 0x2f, 0x89, 0x6e, 0x68, 0xd8, 0x4a, 0xf6, 0x3, 0x1, 0x4, 0x35, 0x2c, + 0x9f, 0x97, 0xf2, 0x17, 0x3, 0xb8, 0x48, 0x6e, 0x0, 0xbd, 0x15, 0xe8, + 0x2d, 0x37, 0x80, 0xde, 0xa, 0xf4, 0x96, 0x1b, 0x40, 0x6f, 0x5, 0x7a, + 0xcb, 0xd, 0xa0, 0xb7, 0x2, 0xbd, 0xe5, 0xd, 0x80, 0xc9, 0x4e, 0x78, + 0xea, 0xe5, 0xd, 0xfd, 0x9c, 0x20, 0x91, 0x37, 0x0, 0xac, 0x10, 0x27, + 0x3a, 0x92, 0x75, 0xd0, 0x90, 0x31, 0x40, 0xf3, 0x43, 0x31, 0x4b, 0x40, + 0x1b, 0xe2, 0xa8, 0x89, 0x5b, 0xf3, 0x8a, 0x7f, 0x61, 0x0, 0xab, 0xdb, + 0xdd, 0x4f, 0x61, 0xab, 0xaf, 0x6f, 0x6b, 0x5a, 0xcd, 0x93, 0x39, 0x9a, + 0x3e, 0xbf, 0x1, 0xb0, 0x6, 0x8f, 0xe2, 0xed, 0x8b, 0xd, 0x6d, 0x53, + 0xdc, 0x50, 0x10, 0x71, 0xfe, 0x88, 0x1c, 0xdc, 0xa7, 0x50, 0x5c, 0x59, + 0x5a, 0x26, 0x5b, 0x6e, 0x71, 0xa, 0xbb, 0xa9, 0x10, 0xd9, 0x21, 0xb0, + 0xe1, 0x4a, 0xba, 0xbe, 0xaa, 0x52, 0x7e, 0x45, 0xa4, 0xa8, 0x91, 0x9d, + 0x13, 0xb, 0x28, 0x58, 0x17, 0x13, 0xdc, 0xc8, 0x3f, 0x98, 0x9, 0x1a, + 0xa7, 0x4a, 0x11, 0x60, 0x25, 0x3f, 0x92, 0x41, 0xd5, 0xa2, 0xa4, 0xae, + 0x9, 0x8a, 0xdb, 0xa, 0x25, 0x33, 0xbd, 0x74, 0x5d, 0xef, 0x3f, 0xde, + 0xa1, 0x49, 0x2c, 0xff, 0x12, 0x46, 0x5a, 0x66, 0x3, 0x0, 0x2b, 0xcd, + 0x8b, 0xb5, 0x66, 0xe2, 0x6f, 0x64, 0x9, 0x86, 0xed, 0xd3, 0xee, 0x7b, + 0x59, 0xf9, 0x75, 0x58, 0x3b, 0xf7, 0xdc, 0x3d, 0x20, 0x9, 0x54, 0x23, + 0x80, 0x8a, 0xbd, 0x12, 0x4f, 0x0, 0xd9, 0xaf, 0x0, 0x39, 0x39, 0xa7, + 0x79, 0x13, 0x70, 0x94, 0x93, 0x62, 0xd0, 0xc2, 0x1, 0xa4, 0x27, 0x22, + 0x3f, 0x7, 0x51, 0x58, 0x6, 0x68, 0x5e, 0x87, 0xa, 0xed, 0x0, 0x40, + 0x4e, 0x4, 0x97, 0xba, 0x1e, 0xe7, 0x80, 0xeb, 0x31, 0x87, 0x74, 0xf3, + 0x98, 0x6e, 0x97, 0x11, 0xa6, 0xf, 0xaa, 0xa2, 0xc5, 0x67, 0x9c, 0xb2, + 0x23, 0xb5, 0x2c, 0x6e, 0x81, 0xb0, 0x6a, 0xa6, 0xc5, 0x2a, 0x9e, 0x15, + 0x21, 0xf7, 0xd, 0x33, 0x68, 0xbb, 0x82, 0xcb, 0xf0, 0x3d, 0x0, 0xf0, + 0xa9, 0xe4, 0xab, 0x14, 0xae, 0x97, 0x8d, 0x17, 0xb8, 0x57, 0xe2, 0x0, + 0xb2, 0xbf, 0x13, 0xd4, 0x1, 0x8d, 0x54, 0x34, 0x76, 0xa2, 0x4d, 0x5b, + 0x9e, 0x32, 0x69, 0x4c, 0x10, 0x57, 0x50, 0xef, 0x52, 0xd, 0xc8, 0xbd, + 0xe9, 0xab, 0x59, 0xfc, 0xa8, 0x6, 0xb0, 0xde, 0x44, 0xf8, 0xa5, 0xa5, + 0x9a, 0xe1, 0xd2, 0x8f, 0x97, 0x83, 0x5a, 0xab, 0x0, 0x38, 0x6c, 0x55, + 0x9d, 0x60, 0xcc, 0xbe, 0xb5, 0xb4, 0xe5, 0x64, 0x47, 0xaf, 0x75, 0x2b, + 0x9b, 0xd6, 0x2d, 0x68, 0xd8, 0x82, 0x1f, 0x41, 0xf0, 0x8b, 0x9e, 0xd6, + 0x7, 0xec, 0x19, 0x60, 0x5c, 0x15, 0x0, 0xa9, 0xf, 0x58, 0x60, 0xbb, + 0xb0, 0x3, 0xad, 0xe9, 0xcf, 0xeb, 0x4f, 0x9d, 0x7e, 0xfc, 0x34, 0x1a, + 0x6b, 0xde, 0x68, 0x68, 0xf7, 0xe, 0xb3, 0x4e, 0x40, 0x63, 0x2b, 0xfa, + 0x73, 0x14, 0x60, 0x5d, 0x4c, 0x23, 0x80, 0x58, 0x9a, 0x75, 0xd, 0x0, + 0xc9, 0xb3, 0x92, 0xa3, 0xe2, 0x1, 0x1, 0x63, 0x53, 0x8f, 0x1, 0x7c, + 0x7a, 0xbb, 0xeb, 0x3, 0x24, 0x31, 0xcc, 0xf3, 0x0, 0x94, 0x4a, 0x2b, + 0x8d, 0x30, 0x9, 0x19, 0x9c, 0x7, 0x18, 0xb8, 0xf, 0x90, 0x34, 0x9c, + 0xf0, 0x38, 0x71, 0x5b, 0x47, 0xaa, 0x92, 0xba, 0x6a, 0x1e, 0x20, 0x84, + 0x69, 0x44, 0x85, 0x53, 0x93, 0x6c, 0xf3, 0xbc, 0x3a, 0xbe, 0xad, 0x6f, + 0xaf, 0xd1, 0xbc, 0x62, 0x17, 0x57, 0x2a, 0x9a, 0x3a, 0x87, 0xe2, 0x30, + 0x0, 0xbe, 0x47, 0x22, 0xb4, 0x97, 0x57, 0x9e, 0x8, 0x9, 0xef, 0x98, + 0x23, 0x53, 0x48, 0x7d, 0x30, 0x13, 0xc4, 0x59, 0x49, 0x67, 0x3a, 0xd1, + 0x36, 0x7b, 0xf1, 0x8a, 0x62, 0xad, 0x70, 0x48, 0x34, 0xaf, 0x6, 0x63, + 0xd9, 0x8b, 0xe7, 0x4f, 0xca, 0xb1, 0xd4, 0x5f, 0xda, 0x97, 0xc3, 0xbe, + 0xd4, 0xc4, 0x5f, 0xc, 0x60, 0x8b, 0xb3, 0x6b, 0x49, 0x33, 0xf7, 0xcd, + 0xe5, 0x36, 0x88, 0xf4, 0x56, 0xa0, 0xb7, 0xdc, 0x0, 0x7a, 0x2b, 0xd0, + 0x5b, 0x6e, 0x0, 0xbd, 0x15, 0xe8, 0x2d, 0x37, 0x80, 0xde, 0xa, 0xf4, + 0x96, 0x1b, 0xc0, 0x9e, 0xc4, 0xda, 0xf3, 0xf5, 0xd7, 0xf4, 0xd6, 0xe, + 0xcf, 0x97, 0x6b, 0xc5, 0x29, 0x30, 0xf0, 0xf, 0xa4, 0x2c, 0xd9, 0xd6, + 0x86, 0xfc, 0xc7, 0xb3, 0xdb, 0xf3, 0x25, 0xf4, 0xa5, 0x39, 0xe7, 0x17, + 0x5a, 0x41, 0x39, 0x49, 0x60, 0xc0, 0x2d, 0xbe, 0xff, 0x15, 0xc0, 0x6e, + 0x81, 0x1, 0x6b, 0xca, 0x29, 0x2d, 0xcb, 0x95, 0x0, 0x0, 0x2d, 0xb3, + 0xa9, 0xc4, 0x1c, 0xf3, 0xb0, 0xd5, 0x20, 0x19, 0x3e, 0xd2, 0x9a, 0xdf, + 0x73, 0xbb, 0x42, 0x28, 0xf9, 0x53, 0xc0, 0x97, 0xd2, 0xfa, 0x3e, 0xfb, + 0x17, 0xd8, 0x24, 0x3c, 0x2d, 0xfb, 0xd, 0xca, 0xb9, 0x92, 0x96, 0xcf, + 0x46, 0xe1, 0x98, 0xe6, 0x3a, 0x0, 0xef, 0xb2, 0xcb, 0x3b, 0xc, 0x60, + 0x45, 0x8b, 0x24, 0x11, 0x80, 0x56, 0xd9, 0x37, 0x4, 0x0, 0x90, 0x7, + 0x5f, 0xda, 0x4d, 0x68, 0x6, 0x59, 0x9c, 0xc2, 0x16, 0x35, 0xb3, 0x39, + 0x3e, 0xef, 0x3f, 0xda, 0x93, 0x72, 0x2a, 0x0, 0xd3, 0x50, 0x4a, 0xa0, + 0x91, 0x82, 0x9a, 0x0, 0x80, 0x9c, 0x86, 0x9a, 0x7f, 0x69, 0x37, 0x38, + 0x43, 0x8b, 0x53, 0x40, 0x36, 0x47, 0xf1, 0x7e, 0xe, 0x40, 0x3b, 0x87, + 0xad, 0xa7, 0xad, 0x0, 0x2c, 0x8e, 0xfc, 0x95, 0x0, 0x2c, 0xe9, 0xb, + 0x2b, 0xfb, 0x30, 0x64, 0xab, 0x6b, 0xe0, 0x5f, 0x1a, 0xcb, 0x23, 0x81, + 0x2d, 0xb9, 0x0, 0xe0, 0xb1, 0xcb, 0xe2, 0xfd, 0x1c, 0x80, 0x98, 0x15, + 0xbe, 0x15, 0x0, 0xf6, 0xf5, 0x88, 0x0, 0x52, 0x85, 0xd1, 0x1b, 0x2, + 0xe0, 0xc9, 0xf2, 0x2f, 0x59, 0x19, 0xc0, 0x9c, 0xd3, 0xe5, 0x84, 0xda, + 0xfb, 0x39, 0x0, 0x13, 0x42, 0x18, 0xcf, 0x0, 0x30, 0x40, 0x6f, 0x94, + 0x50, 0x8a, 0xa3, 0x9c, 0xa6, 0x7, 0x2, 0xc8, 0xb9, 0x5f, 0xd8, 0xf5, + 0x41, 0x6, 0x40, 0x28, 0x1b, 0x7f, 0x3, 0x80, 0xf3, 0xfa, 0x0, 0x8b, + 0x52, 0x43, 0xc9, 0x0, 0xd6, 0x34, 0x10, 0x42, 0x0, 0x33, 0x71, 0x9b, + 0x79, 0x71, 0x75, 0x21, 0x97, 0x59, 0x1a, 0x4d, 0x90, 0x81, 0xe6, 0x79, + 0xff, 0x51, 0x7e, 0xf8, 0xaf, 0x1, 0x40, 0x69, 0x7e, 0xf6, 0x51, 0x60, + 0xa9, 0x2d, 0xe5, 0xb1, 0x49, 0x5, 0x2, 0xd8, 0x14, 0xf7, 0x75, 0x2d, + 0x65, 0x44, 0xb3, 0x78, 0x14, 0x0, 0xf2, 0xbc, 0xff, 0xd0, 0x2c, 0xde, + 0x3c, 0xf, 0x70, 0x26, 0x75, 0x99, 0x20, 0x6c, 0x7b, 0x9f, 0x7, 0xf0, + 0x46, 0x10, 0x8a, 0xc7, 0x84, 0x85, 0x4e, 0x3c, 0x0, 0x30, 0xf7, 0x5b, + 0xd1, 0xc7, 0xa1, 0x83, 0x89, 0x8a, 0xc3, 0x0, 0xba, 0xde, 0xe0, 0x9b, + 0x19, 0xe1, 0xfe, 0x6a, 0x0, 0x56, 0xb0, 0xc3, 0x7b, 0x1c, 0xfe, 0xbf, + 0x2b, 0xa2, 0x79, 0x23, 0x8, 0x78, 0xe7, 0xc6, 0xc3, 0xb7, 0x66, 0x21, + 0x80, 0x88, 0xbc, 0xa0, 0x25, 0x62, 0x85, 0xcf, 0x33, 0xc4, 0x4d, 0x21, + 0xc2, 0xfd, 0xaf, 0x5f, 0x3f, 0xba, 0x1, 0x48, 0x6b, 0xf8, 0xff, 0x69, + 0xd2, 0xf8, 0xe0, 0xf0, 0xe9, 0x49, 0x53, 0x7f, 0xbb, 0xa4, 0x7a, 0x34, + 0x8c, 0x87, 0x13, 0xa1, 0xde, 0x5a, 0x5e, 0x29, 0x5e, 0xc1, 0xa4, 0x5c, + 0xbf, 0x7, 0x40, 0xcd, 0x91, 0x83, 0x3f, 0x1c, 0xc0, 0xb1, 0xdc, 0x0, + 0x7a, 0x2b, 0xd0, 0x5b, 0x6e, 0x0, 0xbd, 0x15, 0xe8, 0x2d, 0x37, 0x80, + 0xde, 0xa, 0xf4, 0x96, 0x1b, 0x40, 0x6f, 0x5, 0xae, 0x13, 0x27, 0x66, + 0x45, 0xf8, 0x2f, 0x0, 0xd1, 0xae, 0xee, 0x60, 0x82, 0xfe, 0x67, 0xbe, + 0xa0, 0x89, 0x5d, 0xdf, 0xfd, 0xb, 0xcc, 0x36, 0x67, 0xcb, 0xca, 0xd1, + 0xbe, 0x4e, 0x49, 0x1f, 0x51, 0x98, 0x2c, 0x18, 0x73, 0xb1, 0x79, 0x5, + 0xe3, 0x98, 0x46, 0xce, 0x66, 0x13, 0x44, 0xe4, 0xeb, 0x73, 0x39, 0x7f, + 0x51, 0xe, 0x21, 0xad, 0x0, 0x50, 0x72, 0x9c, 0x80, 0xd5, 0xe7, 0x54, + 0x2, 0xfc, 0xd8, 0xf5, 0x35, 0x7b, 0x5, 0x14, 0x7, 0xf0, 0x5c, 0x56, + 0x73, 0x63, 0x66, 0x7e, 0x21, 0x1e, 0x43, 0x3b, 0x8, 0xcb, 0xe1, 0x2d, + 0x47, 0xca, 0x47, 0x42, 0xf1, 0x83, 0x1b, 0x4c, 0x53, 0x23, 0xda, 0x34, + 0x8f, 0x83, 0x24, 0x9f, 0x0, 0x4a, 0x4c, 0x24, 0x70, 0x38, 0x48, 0xf1, + 0x70, 0x30, 0x53, 0x82, 0x4, 0x60, 0xf, 0x5b, 0x77, 0x4c, 0xf1, 0x87, + 0x41, 0x84, 0x3, 0xc8, 0xc7, 0x3c, 0xa2, 0x74, 0x3c, 0xd9, 0xb8, 0x23, + 0x1d, 0x23, 0x8b, 0x1c, 0x32, 0x4d, 0x0, 0x40, 0x7c, 0xe3, 0x46, 0xe2, + 0x31, 0x81, 0x4d, 0x0, 0xf6, 0xbd, 0x12, 0x3c, 0xf9, 0x50, 0x6, 0xe0, + 0xc2, 0x66, 0xf9, 0x1b, 0x69, 0x82, 0x39, 0xd1, 0x73, 0x15, 0x18, 0xf1, + 0x3b, 0xe1, 0x4c, 0xcf, 0xd, 0x0, 0xe2, 0x8, 0x8, 0xe6, 0xc, 0xf, + 0x18, 0x41, 0x1b, 0x80, 0xb2, 0xeb, 0x1f, 0x45, 0xbf, 0x87, 0x6c, 0xb0, + 0x0, 0x36, 0xb7, 0x45, 0x8, 0x72, 0xb7, 0x34, 0xb, 0x15, 0x0, 0x67, + 0x17, 0xaf, 0x7, 0x20, 0xa4, 0xa0, 0x16, 0xcf, 0xb, 0x6e, 0x3, 0x90, + 0xf, 0x3f, 0xf0, 0x28, 0xf8, 0x3b, 0xa4, 0x5e, 0x6, 0x1, 0xc8, 0x26, + 0x51, 0x7c, 0xf2, 0xb6, 0x10, 0xfd, 0xef, 0xb0, 0xfb, 0xa7, 0x1e, 0x80, + 0x13, 0x3c, 0x68, 0x71, 0xc2, 0xaa, 0xb4, 0x1, 0x48, 0xf, 0x88, 0x23, + 0xb7, 0xdb, 0x7b, 0xa, 0xda, 0x10, 0x4a, 0xa3, 0xef, 0xa5, 0xa3, 0xaa, + 0x2d, 0xae, 0x0, 0x52, 0xaa, 0xf3, 0x96, 0x3e, 0x40, 0xc9, 0xc7, 0x96, + 0xc2, 0x73, 0x84, 0xdf, 0x0, 0xe0, 0xf7, 0x5b, 0xf2, 0x20, 0x8f, 0x78, + 0x48, 0x15, 0xda, 0x13, 0xca, 0x3d, 0xa5, 0xc8, 0xe1, 0x57, 0x12, 0xd2, + 0xd4, 0x48, 0xe9, 0xf5, 0x5b, 0x0, 0x4c, 0x92, 0x7, 0x0, 0x66, 0x7a, + 0x82, 0x8a, 0xac, 0x8f, 0x61, 0x8a, 0x77, 0x1b, 0x78, 0x37, 0x59, 0xf1, + 0xe, 0xf, 0x60, 0x7f, 0x81, 0x2b, 0xa3, 0x0, 0xf2, 0x67, 0x60, 0x0, + 0x52, 0x76, 0xa1, 0x26, 0x0, 0x70, 0xdc, 0x35, 0x65, 0xc4, 0xaf, 0x56, + 0x44, 0x93, 0x81, 0x79, 0x3, 0xcb, 0x7e, 0x42, 0x8e, 0x45, 0x8a, 0xf, + 0x28, 0xaa, 0x44, 0xaa, 0x6, 0x9f, 0x6e, 0xd5, 0xd2, 0x44, 0xa8, 0x61, + 0x18, 0xe4, 0x55, 0x31, 0xef, 0x3b, 0x14, 0xec, 0xea, 0x18, 0xc0, 0x7e, + 0xc0, 0x30, 0xba, 0x5f, 0xe3, 0xb0, 0x81, 0x34, 0x32, 0x46, 0xe, 0x60, + 0xd7, 0x4, 0x92, 0x14, 0x9a, 0xde, 0xa7, 0x13, 0x21, 0x59, 0x42, 0x6b, + 0x38, 0x7f, 0x37, 0x87, 0x1, 0x94, 0xcf, 0x1, 0xfc, 0xe5, 0x52, 0xb6, + 0x99, 0xfd, 0x6e, 0x0, 0x75, 0xab, 0xc1, 0x1f, 0x2b, 0x31, 0x84, 0xdf, + 0x5d, 0x3, 0xea, 0xe4, 0x6, 0xd0, 0x5b, 0x81, 0xde, 0x72, 0x3, 0xe8, + 0xad, 0x40, 0x6f, 0xb9, 0x1, 0xf4, 0x56, 0xa0, 0xb7, 0xdc, 0x0, 0xb6, + 0x51, 0x8e, 0x85, 0x3b, 0xce, 0xbf, 0x50, 0x21, 0x53, 0xe3, 0x99, 0x5a, + 0xd3, 0x71, 0x60, 0xd3, 0x49, 0x5f, 0x7a, 0x0, 0x10, 0xd7, 0xc, 0xde, + 0x9, 0x36, 0xc8, 0xc5, 0x36, 0x71, 0x69, 0x3c, 0x9e, 0xd0, 0xb6, 0x1d, + 0x65, 0xf7, 0xc1, 0x97, 0x1e, 0x0, 0x36, 0x39, 0xa0, 0x5a, 0x38, 0x70, + 0x50, 0xab, 0xb6, 0xc7, 0x2d, 0xae, 0xe5, 0xe7, 0xf9, 0xf3, 0x0, 0x46, + 0x31, 0xbf, 0x37, 0x6, 0xe0, 0x68, 0xd5, 0x47, 0x1, 0xa8, 0x35, 0xe2, + 0x61, 0x52, 0x98, 0xfc, 0x2e, 0xde, 0x41, 0x73, 0x74, 0xc4, 0x5f, 0xc8, + 0x5, 0x69, 0x98, 0xd5, 0xdc, 0x55, 0x1c, 0x9c, 0x5c, 0xbc, 0x22, 0x52, + 0x2f, 0x0, 0x1, 0xc4, 0x7c, 0x50, 0xb7, 0xe8, 0x33, 0xa8, 0x17, 0x21, + 0x7b, 0x7d, 0x2, 0x90, 0x3b, 0x1f, 0x40, 0x78, 0x92, 0x33, 0x46, 0xc1, + 0x14, 0xda, 0x86, 0x24, 0xeb, 0xea, 0xb, 0x80, 0xf4, 0x48, 0xa1, 0x8a, + 0x42, 0x0, 0xc5, 0x95, 0x61, 0xf, 0xb7, 0xa2, 0x1c, 0x4a, 0x4e, 0x8, + 0x83, 0x3c, 0x52, 0x96, 0x94, 0xf, 0x7c, 0x7, 0x48, 0x76, 0x17, 0xb8, + 0x10, 0x78, 0x87, 0x5a, 0x72, 0x1d, 0x19, 0x94, 0xa9, 0x6a, 0xae, 0x49, + 0xde, 0x9e, 0x1, 0x78, 0x89, 0x13, 0x4, 0x50, 0xaa, 0xd5, 0x7a, 0xca, + 0xa9, 0xdc, 0x21, 0x18, 0x94, 0x90, 0xca, 0x3f, 0x63, 0xbd, 0xff, 0x23, + 0x42, 0x5a, 0xe5, 0xf5, 0x71, 0x48, 0x2b, 0xcf, 0xeb, 0x44, 0x69, 0x35, + 0x3c, 0xd4, 0xa5, 0xd1, 0xd1, 0xc2, 0x6c, 0xe0, 0xd2, 0xa3, 0xa7, 0xe3, + 0x80, 0xab, 0xae, 0xc5, 0x5b, 0x60, 0x36, 0x8d, 0xfb, 0xb9, 0xf0, 0x38, + 0xaa, 0x83, 0x9f, 0x61, 0x22, 0x6e, 0x4, 0xe1, 0x0, 0xa4, 0x91, 0xf0, + 0x52, 0x0, 0x39, 0x29, 0x21, 0xae, 0x1, 0x1, 0xd6, 0x0, 0x83, 0xfb, + 0x6a, 0x9, 0x40, 0x2c, 0x35, 0xa0, 0x32, 0x95, 0x96, 0x34, 0x12, 0x5e, + 0xa, 0xe0, 0x91, 0x64, 0xd, 0x3, 0x0, 0x1b, 0x12, 0x6, 0x6c, 0xff, + 0x2f, 0x4d, 0xc0, 0x83, 0x9a, 0xa4, 0xea, 0xd2, 0x7c, 0x3c, 0x53, 0x69, + 0x9, 0x74, 0x2f, 0x4, 0x90, 0xcf, 0x1d, 0x50, 0xb0, 0x9, 0x8c, 0xce, + 0xc2, 0xfd, 0x87, 0xd9, 0x1, 0x80, 0x9c, 0xc9, 0x76, 0x22, 0x98, 0x78, + 0x49, 0x4d, 0xce, 0x1d, 0xc6, 0x8b, 0xd3, 0x3, 0xa3, 0xb0, 0xb1, 0xe9, + 0x42, 0x0, 0xc5, 0x5, 0x8, 0xb2, 0xf2, 0x95, 0xd1, 0xe, 0x85, 0x38, + 0xa7, 0xa1, 0x1, 0x6d, 0x90, 0x5c, 0xd3, 0xe5, 0x9, 0x1d, 0x92, 0x32, + 0x95, 0x3c, 0x42, 0x75, 0x0, 0x84, 0x91, 0xf0, 0x52, 0x0, 0x72, 0x16, + 0xa1, 0x18, 0xda, 0xbe, 0x21, 0x9f, 0x83, 0x50, 0x31, 0x54, 0x3d, 0x0, + 0x2c, 0x96, 0xcf, 0x6c, 0xd6, 0x30, 0x37, 0x0, 0x8, 0x4f, 0xb9, 0x84, + 0xd5, 0x75, 0x72, 0x90, 0x54, 0xb5, 0x1e, 0xc0, 0xf3, 0xb0, 0x9a, 0x2f, + 0x4f, 0xca, 0xbf, 0x1f, 0x80, 0xb5, 0x62, 0x10, 0xf9, 0x9f, 0xb8, 0xa7, + 0x7c, 0x2b, 0xf7, 0xd8, 0x47, 0x0, 0x7e, 0x87, 0xdc, 0x0, 0x7a, 0x2b, + 0xd0, 0x5b, 0x6e, 0x0, 0xbd, 0x15, 0xe8, 0x2d, 0x37, 0x80, 0xde, 0xa, + 0xf4, 0x96, 0x1b, 0x40, 0xfe, 0xb3, 0x58, 0x65, 0xaa, 0x8d, 0xf7, 0xb2, + 0x9, 0x3e, 0x88, 0x9b, 0xf5, 0xd, 0x38, 0x13, 0x54, 0x2a, 0xc7, 0xe3, + 0x64, 0xf9, 0x57, 0x2, 0xc8, 0xa7, 0x82, 0xda, 0x8a, 0x7d, 0xc6, 0xbb, + 0xc8, 0x16, 0x68, 0x27, 0x45, 0xa4, 0x44, 0x14, 0x27, 0x27, 0x95, 0xe3, + 0x73, 0x56, 0xdd, 0x3f, 0xb, 0xc0, 0x36, 0x9d, 0x7b, 0xf9, 0xc6, 0x4, + 0xef, 0xa4, 0x0, 0xfb, 0xc5, 0xb5, 0x94, 0x73, 0x9c, 0xf7, 0xe2, 0x64, + 0x0, 0xc2, 0x2, 0x26, 0x96, 0xa9, 0x3d, 0x48, 0xce, 0x81, 0xed, 0xf6, + 0x21, 0xdf, 0xd, 0x8a, 0xf1, 0xc2, 0xa, 0xa1, 0x0, 0x98, 0x11, 0x33, + 0x8, 0x20, 0xb8, 0x98, 0x9e, 0x3a, 0x3b, 0x50, 0xfe, 0x26, 0xf8, 0x11, + 0x66, 0xe7, 0x8e, 0x53, 0x0, 0x53, 0x5a, 0xc7, 0xe6, 0x3c, 0x2c, 0x7c, + 0xd9, 0xb3, 0x2a, 0x82, 0x87, 0x6c, 0x58, 0x7c, 0x59, 0xc, 0xca, 0xb2, + 0xc2, 0x1a, 0x31, 0x3, 0xc0, 0x47, 0x2b, 0x42, 0x0, 0x8e, 0xf2, 0x89, + 0x14, 0xc0, 0xd0, 0x6f, 0xf3, 0x7, 0x40, 0x9f, 0xdd, 0x5f, 0x70, 0xec, + 0x17, 0x10, 0x23, 0x2d, 0xf3, 0x69, 0xd4, 0x2b, 0x48, 0x41, 0x90, 0xed, + 0xf6, 0xb, 0xb0, 0xe7, 0xc7, 0x30, 0x43, 0x0, 0x79, 0x51, 0x69, 0x30, + 0x0, 0x8f, 0x33, 0x5d, 0x9, 0x0, 0x6c, 0xde, 0x32, 0x1, 0x53, 0x6b, + 0x40, 0x7d, 0x34, 0x8d, 0x61, 0x51, 0x87, 0xad, 0x5b, 0xae, 0x1, 0x65, + 0x33, 0xb, 0x3c, 0x33, 0xdc, 0xb, 0xd, 0x58, 0x6e, 0xbb, 0xf8, 0x48, + 0xcf, 0x59, 0x48, 0xea, 0x2e, 0x0, 0x58, 0x2, 0x4d, 0x10, 0x0, 0xd4, + 0x27, 0x14, 0xeb, 0xd9, 0xb1, 0x63, 0x56, 0xee, 0x3, 0xd2, 0x77, 0xc3, + 0x4, 0x5d, 0x57, 0xe1, 0x24, 0x0, 0x24, 0x5a, 0xe3, 0x21, 0x80, 0x90, + 0x2d, 0x74, 0x8, 0x0, 0xd4, 0x67, 0x7e, 0x9c, 0x53, 0x70, 0x34, 0xa2, + 0xca, 0x0, 0x66, 0xe1, 0xf0, 0xf9, 0xf3, 0x0, 0xcc, 0xe2, 0x11, 0x3, + 0x27, 0x0, 0xd8, 0x53, 0x31, 0xcd, 0x1f, 0x0, 0x50, 0x1a, 0x5b, 0x1b, + 0xcf, 0x3, 0x10, 0x1c, 0x36, 0xda, 0x9e, 0x2, 0x60, 0x77, 0x3b, 0xda, + 0x43, 0x2f, 0xee, 0x1b, 0x0, 0x64, 0x47, 0x7, 0x3c, 0xfb, 0x12, 0x80, + 0x1c, 0x4f, 0x41, 0x82, 0x43, 0x5a, 0x0, 0x90, 0x7b, 0xf0, 0xad, 0xb2, + 0x9c, 0x56, 0x0, 0xe9, 0x92, 0x9e, 0x6c, 0x9d, 0x59, 0x5c, 0xda, 0xa0, + 0x42, 0x30, 0x3f, 0x9a, 0xfc, 0x40, 0xd9, 0x17, 0x27, 0x1, 0xd8, 0x14, + 0x1a, 0xd6, 0x60, 0x39, 0xcd, 0x0, 0x3e, 0xcd, 0x23, 0xb4, 0x96, 0x5e, + 0xf4, 0xb8, 0xd, 0x55, 0x89, 0xe9, 0x63, 0x2b, 0xae, 0xb1, 0xe9, 0x8a, + 0x0, 0xf6, 0x61, 0x64, 0x68, 0x8b, 0xef, 0x41, 0x32, 0x85, 0x30, 0x57, + 0x1e, 0x7a, 0xd4, 0x43, 0xe4, 0x59, 0xb7, 0x11, 0xce, 0xf5, 0x69, 0x7e, + 0x82, 0xb4, 0xfd, 0xf0, 0x7b, 0xc8, 0x9b, 0x65, 0x47, 0x9a, 0xdc, 0xcf, + 0x27, 0x9c, 0xa3, 0xb0, 0xe2, 0xc0, 0x9e, 0xef, 0x22, 0xb7, 0x41, 0xa4, + 0xb7, 0x2, 0xbd, 0xe5, 0x6, 0xd0, 0x5b, 0x81, 0xde, 0x72, 0x3, 0xe8, + 0xad, 0x40, 0x6f, 0xb9, 0x1, 0xf4, 0x56, 0xa0, 0xb7, 0xdc, 0x0, 0xae, + 0x7f, 0xc4, 0x6a, 0xfe, 0xa0, 0x9f, 0xe3, 0x3b, 0x2, 0x8, 0xe7, 0xac, + 0x28, 0x2f, 0x3, 0x20, 0xdb, 0xd5, 0x7d, 0x9a, 0xc5, 0xcf, 0x3c, 0xf7, + 0x85, 0x8b, 0xc1, 0xc1, 0x2d, 0x10, 0xb3, 0x3, 0xd7, 0x83, 0x1b, 0x4b, + 0xfe, 0x7a, 0x50, 0x7a, 0x7c, 0xfc, 0x79, 0xb9, 0x5c, 0x56, 0xce, 0xec, + 0xb, 0x8f, 0xb, 0xe1, 0xf5, 0xb, 0x6b, 0xf9, 0x80, 0x1f, 0x80, 0x28, + 0x95, 0xc3, 0x1, 0xec, 0x76, 0x75, 0x6e, 0x99, 0x91, 0xe2, 0xf0, 0x5d, + 0xf1, 0xb, 0xa8, 0x88, 0xef, 0x77, 0xfc, 0x76, 0x21, 0x41, 0xb3, 0x68, + 0xcc, 0xcc, 0xaa, 0x78, 0xa6, 0x90, 0x7d, 0x66, 0x9e, 0x7a, 0x7d, 0x70, + 0x49, 0x21, 0xa2, 0xaa, 0xcb, 0x41, 0x0, 0x70, 0x7c, 0xbe, 0x14, 0x87, + 0xef, 0x72, 0xd2, 0x9d, 0x1, 0xf9, 0x11, 0xb2, 0x7f, 0x15, 0xba, 0xb, + 0xc, 0xa, 0x1f, 0x94, 0x2c, 0x39, 0xc5, 0x96, 0x37, 0x30, 0x7d, 0x6, + 0x29, 0xf3, 0x54, 0x4e, 0x22, 0x83, 0x42, 0xe9, 0x1f, 0xe5, 0x1c, 0xfb, + 0x5, 0x84, 0xf8, 0x7c, 0x31, 0xe, 0xbf, 0xdc, 0xa, 0xe, 0x65, 0x49, + 0x35, 0x1d, 0x3f, 0x4c, 0xe8, 0x3, 0x24, 0x0, 0xa3, 0x94, 0x78, 0xc9, + 0x2f, 0xe9, 0xc9, 0x60, 0xb3, 0xce, 0x4c, 0xa, 0x5, 0x7b, 0xb, 0xe5, + 0x20, 0x0, 0x30, 0x3e, 0x5f, 0xa, 0x43, 0x17, 0x63, 0xeb, 0x73, 0xcc, + 0x2e, 0xd, 0x28, 0xc6, 0xb7, 0xd, 0x40, 0x36, 0x45, 0xcd, 0xdc, 0x10, + 0xe5, 0x29, 0x1b, 0x4b, 0x37, 0x54, 0x96, 0x21, 0x6c, 0xa3, 0x86, 0xe5, + 0x88, 0x0, 0x6, 0xf6, 0xfd, 0x46, 0x0, 0x49, 0xc7, 0x11, 0xfe, 0x14, + 0x8d, 0x0, 0x72, 0x8, 0xbd, 0x41, 0x5c, 0x46, 0x95, 0x1a, 0x2a, 0x78, + 0x78, 0xee, 0x7c, 0x50, 0x74, 0x3, 0x2e, 0x47, 0x0, 0xc0, 0xed, 0xe7, + 0x62, 0x1c, 0xfe, 0xbb, 0xdc, 0x24, 0x2b, 0xf4, 0xeb, 0xe2, 0xdd, 0x8, + 0xf9, 0xb9, 0xa8, 0xfc, 0x6c, 0x88, 0x45, 0xdf, 0x48, 0x15, 0xdd, 0xa7, + 0xff, 0xfc, 0xe1, 0xa9, 0x17, 0x54, 0x28, 0xf3, 0x9d, 0x50, 0xe, 0x0, + 0x80, 0xe3, 0xf3, 0x4b, 0x1c, 0x7e, 0x3, 0x80, 0x71, 0x9a, 0x97, 0x1, + 0x3e, 0x31, 0x87, 0xb3, 0xf3, 0x9f, 0x68, 0x20, 0x3, 0xe3, 0xfc, 0x8b, + 0x15, 0x71, 0x86, 0x57, 0xd3, 0x77, 0x50, 0x9f, 0x91, 0x8f, 0xa4, 0x40, + 0x56, 0x57, 0xa2, 0x8a, 0xcd, 0x6d, 0xb4, 0xdb, 0xe1, 0x41, 0x7c, 0x7e, + 0x89, 0xc3, 0x6f, 0x68, 0x2, 0x65, 0xb, 0x90, 0x41, 0xf3, 0x83, 0x9c, + 0xe3, 0x1c, 0x6c, 0x77, 0x93, 0xe2, 0xfc, 0xf3, 0x49, 0xe8, 0xa0, 0x94, + 0x7c, 0x7e, 0x87, 0xe7, 0xce, 0x34, 0xff, 0x38, 0x49, 0xc2, 0xd6, 0x96, + 0x3, 0x0, 0x2c, 0x42, 0x43, 0x9, 0xb1, 0x6d, 0x47, 0xa6, 0x1c, 0x2a, + 0x8f, 0x3f, 0xb9, 0x3a, 0xed, 0x90, 0xad, 0xb1, 0xe9, 0xd3, 0xfb, 0x77, + 0xfc, 0xfa, 0x96, 0xd4, 0xde, 0x12, 0xc3, 0x58, 0xe5, 0x8d, 0xf8, 0xb1, + 0x0, 0xd2, 0x7c, 0x4d, 0xd5, 0x18, 0xf5, 0x69, 0x5b, 0xdf, 0xbd, 0xe2, + 0xfa, 0xb7, 0xbe, 0x7f, 0x5a, 0x34, 0xd4, 0x79, 0x23, 0x6e, 0x7b, 0x40, + 0x6f, 0x5, 0x7a, 0xcb, 0xd, 0xa0, 0xb7, 0x2, 0xbd, 0xe5, 0x6, 0xd0, + 0x5b, 0x81, 0xde, 0xf2, 0x15, 0x0, 0xf1, 0x94, 0xe4, 0x1, 0xdf, 0x44, + 0xbe, 0x74, 0xd4, 0xd6, 0xb5, 0x3b, 0x6a, 0xbf, 0x11, 0x0, 0xaf, 0xe0, + 0xab, 0x6, 0xe1, 0x6a, 0x53, 0x38, 0xd, 0x9, 0xa1, 0xc5, 0xa7, 0xca, + 0x68, 0xf4, 0xb1, 0x49, 0x4c, 0x96, 0x95, 0x14, 0xac, 0xec, 0x18, 0x40, + 0x74, 0xc7, 0x47, 0xbc, 0xfe, 0x7, 0x80, 0x68, 0xaf, 0x5a, 0xcc, 0x49, + 0x41, 0x35, 0x69, 0x95, 0x6f, 0x3f, 0x1, 0xe0, 0x48, 0x32, 0xf2, 0x9, + 0x4d, 0xa0, 0xc2, 0xfe, 0x50, 0x1, 0xc0, 0xe7, 0x44, 0x1, 0xea, 0x84, + 0x56, 0x16, 0x6b, 0x56, 0x43, 0xf4, 0x88, 0xe7, 0xc7, 0x9, 0x83, 0x84, + 0xed, 0xea, 0x22, 0x80, 0x86, 0x3a, 0x2d, 0x3, 0x50, 0xc5, 0x8c, 0x71, + 0xc2, 0x52, 0xb9, 0xca, 0x23, 0x43, 0xe2, 0xc1, 0x8a, 0x9d, 0x0, 0xac, + 0x8e, 0x54, 0x8, 0x27, 0x4, 0x67, 0xa1, 0x53, 0x1d, 0x20, 0x80, 0x1c, + 0xcf, 0xaf, 0xe0, 0xad, 0x63, 0x2b, 0x0, 0x98, 0x8d, 0x26, 0xce, 0xb0, + 0x21, 0x49, 0x0, 0xec, 0x49, 0xd1, 0x79, 0x5b, 0x4e, 0xc, 0xae, 0xed, + 0x61, 0xa8, 0x68, 0xd1, 0x1, 0x6e, 0xdc, 0xf0, 0xa3, 0xe4, 0x55, 0x11, + 0x1, 0x38, 0xd2, 0xec, 0x93, 0xa8, 0x8a, 0xdb, 0x89, 0x39, 0x34, 0xc4, + 0x26, 0x70, 0x7c, 0x4e, 0x6a, 0x9d, 0x54, 0xa6, 0x33, 0xd8, 0x13, 0x29, + 0x21, 0xd3, 0x81, 0x15, 0xf, 0x19, 0x11, 0x1, 0x64, 0x33, 0xdf, 0xeb, + 0xb5, 0x99, 0xdc, 0x94, 0xb8, 0xb0, 0xdf, 0x54, 0x2, 0x30, 0xa9, 0xfc, + 0xbb, 0xb5, 0x65, 0xe0, 0x13, 0xa4, 0xca, 0x9a, 0x43, 0xc5, 0xae, 0xe, + 0x2b, 0x69, 0x4, 0xa7, 0x2, 0xbc, 0x7, 0x30, 0x0, 0x64, 0xde, 0xee, + 0xf9, 0x52, 0x5e, 0x3f, 0x91, 0x0, 0x78, 0x9b, 0x6, 0xdf, 0x73, 0x42, + 0x2b, 0xab, 0x12, 0xe9, 0x90, 0x7c, 0x40, 0xc1, 0x89, 0x9d, 0x60, 0xe4, + 0xf6, 0x4f, 0xb1, 0x9, 0xac, 0xcd, 0x59, 0x38, 0xa4, 0x7d, 0x31, 0x55, + 0x7d, 0x32, 0x65, 0xe3, 0xb1, 0xb0, 0x57, 0xb3, 0xd3, 0x30, 0x68, 0xf3, + 0x59, 0xa9, 0xa6, 0xa1, 0x23, 0xb0, 0x78, 0xe7, 0x49, 0xd5, 0x34, 0xa0, + 0xe7, 0x30, 0x28, 0x4e, 0x85, 0x47, 0x78, 0x98, 0xb8, 0x2c, 0x42, 0x9d, + 0xf1, 0x35, 0xa7, 0xee, 0x1e, 0xcc, 0x4, 0x71, 0x2f, 0x28, 0x3, 0x38, + 0x2b, 0x2a, 0xbe, 0x71, 0x1e, 0x80, 0x9e, 0x6b, 0xad, 0xa2, 0xe3, 0x6d, + 0x93, 0xef, 0x1, 0x2c, 0x64, 0x16, 0xbc, 0xaf, 0xf, 0x2, 0x8, 0x43, + 0x15, 0xf2, 0xb3, 0x25, 0x3a, 0xb8, 0xf5, 0x57, 0x93, 0xa9, 0xaa, 0x44, + 0x6f, 0x2b, 0xed, 0x48, 0x6d, 0xab, 0x41, 0xdd, 0x23, 0x85, 0x4e, 0xd0, + 0xc3, 0x27, 0xe6, 0x89, 0xf7, 0xad, 0x36, 0x42, 0xef, 0x95, 0x60, 0x10, + 0xf9, 0xeb, 0x92, 0x48, 0x55, 0x0, 0xf8, 0x5, 0x72, 0x3, 0xe8, 0xad, + 0x40, 0x6f, 0xb9, 0x1, 0xf4, 0x56, 0xa0, 0xb7, 0xdc, 0x0, 0x7a, 0x2b, + 0xd0, 0x5b, 0x6e, 0x0, 0xbd, 0x15, 0xe8, 0x2d, 0x37, 0x80, 0xde, 0xa, + 0xf4, 0x96, 0x1b, 0x40, 0x6f, 0x5, 0x7a, 0xcb, 0xd, 0xa0, 0xb7, 0x2, + 0xbd, 0xe5, 0x6, 0xd0, 0x5b, 0x81, 0xde, 0x72, 0x3, 0xe8, 0xad, 0x40, + 0x6f, 0xf9, 0xf5, 0x0, 0xfe, 0x1, 0x35, 0x69, 0x45, 0xec, 0xa0, 0xf0, + 0xdf, 0x8c, 0x0, 0x0, 0x0, 0x25, 0x74, 0x45, 0x58, 0x74, 0x64, 0x61, + 0x74, 0x65, 0x3a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x0, 0x32, 0x30, + 0x31, 0x37, 0x2d, 0x30, 0x34, 0x2d, 0x30, 0x32, 0x54, 0x31, 0x38, 0x3a, + 0x33, 0x39, 0x3a, 0x33, 0x37, 0x2b, 0x30, 0x30, 0x3a, 0x30, 0x30, 0x98, + 0xbb, 0xf, 0xb3, 0x0, 0x0, 0x0, 0x25, 0x74, 0x45, 0x58, 0x74, 0x64, + 0x61, 0x74, 0x65, 0x3a, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x0, 0x32, + 0x30, 0x31, 0x37, 0x2d, 0x30, 0x34, 0x2d, 0x30, 0x32, 0x54, 0x31, 0x38, + 0x3a, 0x33, 0x39, 0x3a, 0x33, 0x37, 0x2b, 0x30, 0x30, 0x3a, 0x30, 0x30, + 0xe9, 0xe6, 0xb7, 0xf, 0x0, 0x0, 0x0, 0x11, 0x74, 0x45, 0x58, 0x74, + 0x70, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x0, 0x55, 0x6e, 0x64, 0x65, + 0x66, 0x69, 0x6e, 0x65, 0x64, 0xdb, 0xa5, 0x33, 0x1b, 0x0, 0x0, 0x0, + 0x4a, 0x74, 0x45, 0x58, 0x74, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x0, 0x65, 0x34, 0x39, 0x30, 0x38, 0x64, 0x62, 0x62, 0x30, + 0x62, 0x64, 0x38, 0x62, 0x36, 0x62, 0x66, 0x64, 0x32, 0x38, 0x39, 0x33, + 0x34, 0x39, 0x38, 0x39, 0x35, 0x39, 0x39, 0x62, 0x66, 0x32, 0x66, 0x61, + 0x39, 0x38, 0x63, 0x63, 0x33, 0x64, 0x35, 0x36, 0x35, 0x31, 0x39, 0x66, + 0x36, 0x66, 0x36, 0x35, 0x31, 0x64, 0x61, 0x32, 0x65, 0x34, 0x36, 0x30, + 0x34, 0x38, 0x38, 0x38, 0x33, 0x34, 0x35, 0xfb, 0xa7, 0xec, 0xfc, 0x0, + 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82}; +} // namespace consolas_font +const unsigned int sGlyphWidth = 9; +const unsigned int sTextureWidth = 256; +const unsigned int sTextureHeight = 256; +const unsigned int sCellWidth = 24; +const unsigned int sCellHeight = 24; +const unsigned int sFirstChar = 32; + +const FontBitmapInfo sFixedWidthCompositorFont = { + mozilla::Some(9), + mozilla::Nothing(), + 256, + 256, + 24, + 24, + 32, + consolas_font::sFontPNG, + sizeof(consolas_font::sFontPNG)}; + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/ContainerLayerComposite.cpp b/gfx/layers/composite/ContainerLayerComposite.cpp new file mode 100644 index 0000000000..34b91714e0 --- /dev/null +++ b/gfx/layers/composite/ContainerLayerComposite.cpp @@ -0,0 +1,820 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ContainerLayerComposite.h" +#include // for min +#include "FrameMetrics.h" // for FrameMetrics +#include "Units.h" // for LayerRect, LayerPixel, etc +#include "CompositableHost.h" // for CompositableHost +#include "gfxEnv.h" // for gfxEnv +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/StaticPrefs_apz.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/UniquePtr.h" // for UniquePtr +#include "mozilla/gfx/BaseRect.h" // for BaseRect +#include "mozilla/gfx/Matrix.h" // for Matrix4x4 +#include "mozilla/gfx/Point.h" // for Point, IntPoint +#include "mozilla/gfx/Rect.h" // for IntRect, Rect +#include "mozilla/layers/APZSampler.h" // for APZSampler +#include "mozilla/layers/BSPTree.h" +#include "mozilla/layers/Compositor.h" // for Compositor, etc +#include "mozilla/layers/CompositorBridgeParent.h" // for CompositorBridgeParent +#include "mozilla/layers/CompositorTypes.h" // for DiagnosticFlags::CONTAINER +#include "mozilla/layers/Effects.h" // for Effect, EffectChain, etc +#include "mozilla/layers/TextureHost.h" // for CompositingRenderTarget +#include "mozilla/layers/APZUtils.h" // for AsyncTransform +#include "mozilla/layers/LayerManagerCompositeUtils.h" +#include "mozilla/layers/LayerMetricsWrapper.h" // for LayerMetricsWrapper +#include "mozilla/layers/LayersHelpers.h" +#include "mozilla/mozalloc.h" // for operator delete, etc +#include "mozilla/RefPtr.h" // for nsRefPtr +#include "nsDebug.h" // for NS_ASSERTION +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "nsISupportsUtils.h" // for NS_ADDREF, NS_RELEASE +#include "nsRegion.h" // for nsIntRegion +#include "nsTArray.h" // for AutoTArray +#include +#include "TextRenderer.h" // for TextRenderer +#include +#include "GeckoProfiler.h" // for GeckoProfiler + +static mozilla::LazyLogModule sGfxCullLog("gfx.culling"); +#define CULLING_LOG(...) MOZ_LOG(sGfxCullLog, LogLevel::Debug, (__VA_ARGS__)) + +#define DUMP(...) \ + do { \ + if (gfxEnv::DumpDebug()) { \ + printf_stderr(__VA_ARGS__); \ + } \ + } while (0) +#define XYWH(k) (k).X(), (k).Y(), (k).Width(), (k).Height() +#define XY(k) (k).X(), (k).Y() +#define WH(k) (k).Width(), (k).Height() + +namespace mozilla { +namespace layers { + +using namespace gfx; + +static void DrawLayerInfo(const RenderTargetIntRect& aClipRect, + LayerManagerComposite* aManager, Layer* aLayer) { + if (aLayer->GetType() == Layer::LayerType::TYPE_CONTAINER) { + // XXX - should figure out a way to render this, but for now this + // is hard to do, since it will often get superimposed over the first + // child of the layer, which is bad. + return; + } + + std::stringstream ss; + aLayer->PrintInfo(ss, ""); + + LayerIntRegion visibleRegion = aLayer->GetVisibleRegion(); + + uint32_t maxWidth = + std::min(visibleRegion.GetBounds().Width(), 500); + + IntPoint topLeft = visibleRegion.GetBounds().ToUnknownRect().TopLeft(); + aManager->GetTextRenderer()->RenderText( + aManager->GetCompositor(), ss.str().c_str(), topLeft, + aLayer->GetEffectiveTransform(), 16, maxWidth); +} + +static void PrintUniformityInfo(Layer* aLayer) { +#if defined(MOZ_GECKO_PROFILER) + if (!profiler_thread_is_being_profiled()) { + return; + } + + // Don't want to print a log for smaller layers + if (aLayer->GetLocalVisibleRegion().GetBounds().Width() < 300 || + aLayer->GetLocalVisibleRegion().GetBounds().Height() < 300) { + return; + } + + Matrix4x4 transform = aLayer->AsHostLayer()->GetShadowBaseTransform(); + if (!transform.Is2D()) { + return; + } + + Point translation = transform.As2D().GetTranslation(); + + // Contains the translation applied to a 2d layer so we can track the layer + // position at each frame. + struct LayerTranslationMarker { + static constexpr Span MarkerTypeName() { + return MakeStringSpan("LayerTranslation"); + } + static void StreamJSONMarkerData( + baseprofiler::SpliceableJSONWriter& aWriter, + ProfileBufferRawPointer aLayer, gfx::Point aPoint) { + const size_t bufferSize = 32; + char buffer[bufferSize]; + SprintfLiteral(buffer, "%p", aLayer.mRawPointer); + + aWriter.StringProperty("layer", buffer); + aWriter.IntProperty("x", aPoint.x); + aWriter.IntProperty("y", aPoint.y); + } + static MarkerSchema MarkerTypeDisplay() { + using MS = MarkerSchema; + MS schema{MS::Location::markerChart, MS::Location::markerTable}; + schema.AddKeyLabelFormat("layer", "Layer", MS::Format::string); + schema.AddKeyLabelFormat("x", "X", MS::Format::integer); + schema.AddKeyLabelFormat("y", "Y", MS::Format::integer); + return schema; + } + }; + + profiler_add_marker("LayerTranslation", geckoprofiler::category::GRAPHICS, {}, + LayerTranslationMarker{}, + WrapProfileBufferRawPointer(aLayer), translation); +#endif +} + +static Maybe SelectLayerGeometry( + const Maybe& aParentGeometry, + const Maybe& aChildGeometry) { + // Both the parent and the child layer were split. + if (aParentGeometry && aChildGeometry) { + return Some(aParentGeometry->ClipPolygon(*aChildGeometry)); + } + + // The parent layer was split. + if (aParentGeometry) { + return aParentGeometry; + } + + // The child layer was split. + if (aChildGeometry) { + return aChildGeometry; + } + + // No split. + return Nothing(); +} + +void TransformLayerGeometry(Layer* aLayer, Maybe& aGeometry) { + Layer* parent = aLayer; + gfx::Matrix4x4 transform; + + // Collect all parent transforms. + while (parent != nullptr && !parent->Is3DContextLeaf()) { + transform = transform * parent->GetLocalTransform(); + parent = parent->GetParent(); + } + + // Transform the geometry to the parent 3D context leaf coordinate space. + transform = transform.ProjectTo2D(); + + if (!transform.IsSingular()) { + aGeometry->TransformToScreenSpace(transform.Inverse(), transform); + } else { + // Discard the geometry since the result might not be correct. + aGeometry.reset(); + } +} + +template +static gfx::IntRect ContainerVisibleRect(ContainerT* aContainer) { + gfx::IntRect surfaceRect = + aContainer->GetLocalVisibleRegion().GetBounds().ToUnknownRect(); + return surfaceRect; +} + +/* all of the per-layer prepared data we need to maintain */ +struct PreparedLayer { + PreparedLayer(Layer* aLayer, RenderTargetIntRect aClipRect, + Maybe&& aGeometry) + : mLayer(aLayer), mClipRect(aClipRect), mGeometry(std::move(aGeometry)) {} + + RefPtr mLayer; + RenderTargetIntRect mClipRect; + Maybe mGeometry; +}; + +/* all of the prepared data that we need in RenderLayer() */ +struct PreparedData { + RefPtr mTmpTarget; + AutoTArray mLayers; + bool mNeedsSurfaceCopy; +}; + +// ContainerPrepare is shared between RefLayer and ContainerLayer +template +void ContainerPrepare(ContainerT* aContainer, LayerManagerComposite* aManager, + const RenderTargetIntRect& aClipRect) { + // We can end up calling prepare multiple times if we duplicated + // layers due to preserve-3d plane splitting. The results + // should be identical, so we only need to do it once. + if (aContainer->mPrepared) { + return; + } + aContainer->mPrepared = MakeUnique(); + aContainer->mPrepared->mNeedsSurfaceCopy = false; + + const ContainerLayerComposite::SortMode sortMode = + aManager->GetCompositor()->SupportsLayerGeometry() + ? ContainerLayerComposite::SortMode::WITH_GEOMETRY + : ContainerLayerComposite::SortMode::WITHOUT_GEOMETRY; + + nsTArray polygons = + aContainer->SortChildrenBy3DZOrder(sortMode); + + for (LayerPolygon& layer : polygons) { + LayerComposite* layerToRender = + static_cast(layer.layer->ImplData()); + + RenderTargetIntRect clipRect = + layerToRender->GetLayer()->CalculateScissorRect(aClipRect); + + if (layerToRender->GetLayer()->IsBackfaceHidden()) { + continue; + } + + // We don't want to skip container layers because otherwise their mPrepared + // may be null which is not allowed. + if (!layerToRender->GetLayer()->AsContainerLayer()) { + if (!layerToRender->GetLayer()->IsVisible()) { + CULLING_LOG("Sublayer %p has no effective visible region\n", + layerToRender->GetLayer()); + continue; + } + + if (clipRect.IsEmpty()) { + CULLING_LOG("Sublayer %p has an empty world clip rect\n", + layerToRender->GetLayer()); + continue; + } + } + + CULLING_LOG("Preparing sublayer %p\n", layerToRender->GetLayer()); + + layerToRender->Prepare(clipRect); + aContainer->mPrepared->mLayers.AppendElement(PreparedLayer( + layerToRender->GetLayer(), clipRect, std::move(layer.geometry))); + } + + CULLING_LOG("Preparing container layer %p\n", aContainer->GetLayer()); + + /** + * Setup our temporary surface for rendering the contents of this container. + */ + + gfx::IntRect surfaceRect = ContainerVisibleRect(aContainer); + if (surfaceRect.IsEmpty()) { + return; + } + + bool surfaceCopyNeeded; + // DefaultComputeSupportsComponentAlphaChildren can mutate aContainer so call + // it unconditionally + aContainer->DefaultComputeSupportsComponentAlphaChildren(&surfaceCopyNeeded); + if (aContainer->UseIntermediateSurface()) { + if (!surfaceCopyNeeded) { + RefPtr surface = nullptr; + + RefPtr& lastSurf = + aContainer->mLastIntermediateSurface; + if (lastSurf && !aContainer->mChildrenChanged && + lastSurf->GetRect().IsEqualEdges(surfaceRect)) { + surface = lastSurf; + } + + if (!surface) { + // If we don't need a copy we can render to the intermediate now to + // avoid unecessary render target switching. This brings a big perf + // boost on mobile gpus. + surface = CreateOrRecycleTarget(aContainer, aManager); + + MOZ_PERFORMANCE_WARNING( + "gfx", + "[%p] Container layer requires intermediate surface rendering\n", + aContainer); + RenderIntermediate(aContainer, aManager, aClipRect.ToUnknownRect(), + surface); + aContainer->SetChildrenChanged(false); + } + + aContainer->mPrepared->mTmpTarget = surface; + } else { + MOZ_PERFORMANCE_WARNING( + "gfx", "[%p] Container layer requires intermediate surface copy\n", + aContainer); + aContainer->mPrepared->mNeedsSurfaceCopy = true; + aContainer->mLastIntermediateSurface = nullptr; + } + } else { + aContainer->mLastIntermediateSurface = nullptr; + } +} + +template +void RenderMinimap(ContainerT* aContainer, const RefPtr& aSampler, + LayerManagerComposite* aManager, + const RenderTargetIntRect& aClipRect, Layer* aLayer) { + Compositor* compositor = aManager->GetCompositor(); + MOZ_ASSERT(aSampler); + + if (aLayer->GetScrollMetadataCount() < 1) { + return; + } + + LayerMetricsWrapper wrapper(aLayer, 0); + if (!wrapper.GetApzc()) { + return; + } + const FrameMetrics& fm = wrapper.Metrics(); + MOZ_ASSERT(fm.IsScrollable()); + + ParentLayerPoint scrollOffset = + aSampler->GetCurrentAsyncScrollOffset(wrapper); + + // Options + const int verticalPadding = 10; + const int horizontalPadding = 5; + gfx::DeviceColor backgroundColor(0.3f, 0.3f, 0.3f, 0.3f); + gfx::DeviceColor tileActiveColor(1, 1, 1, 0.4f); + gfx::DeviceColor tileBorderColor(0, 0, 0, 0.1f); + gfx::DeviceColor pageBorderColor(0, 0, 0); + gfx::DeviceColor criticalDisplayPortColor(1.f, 1.f, 0); + gfx::DeviceColor displayPortColor(0, 1.f, 0); + gfx::DeviceColor layoutPortColor(1.f, 0, 0); + gfx::DeviceColor visualPortColor(0, 0, 1.f, 0.3f); + + // Rects + ParentLayerRect compositionBounds = fm.GetCompositionBounds(); + LayerRect scrollRect = fm.GetScrollableRect() * fm.LayersPixelsPerCSSPixel(); + LayerRect visualRect = + ParentLayerRect(scrollOffset, compositionBounds.Size()) / + LayerToParentLayerScale(1); + LayerRect dp = (fm.GetDisplayPort() + fm.GetLayoutScrollOffset()) * + fm.LayersPixelsPerCSSPixel(); + Maybe layoutRect; + Maybe cdp; + if (fm.IsRootContent()) { + CSSRect viewport = aSampler->GetCurrentAsyncLayoutViewport(wrapper); + layoutRect = Some(viewport * fm.LayersPixelsPerCSSPixel()); + } + if (!fm.GetCriticalDisplayPort().IsEmpty()) { + cdp = Some((fm.GetCriticalDisplayPort() + fm.GetLayoutScrollOffset()) * + fm.LayersPixelsPerCSSPixel()); + } + + // Don't render trivial minimap. They can show up from textboxes and other + // tiny frames. + if (visualRect.Width() < 64 && visualRect.Height() < 64) { + return; + } + + // Compute a scale with an appropriate aspect ratio + // We allocate up to 100px of width and the height of this layer. + float scaleFactor; + float scaleFactorX; + float scaleFactorY; + Rect dest = Rect(aClipRect.ToUnknownRect()); + if (aLayer->GetLocalClipRect()) { + dest = Rect(aLayer->GetLocalClipRect().value().ToUnknownRect()); + } else { + dest = aContainer->GetEffectiveTransform().Inverse().TransformBounds(dest); + } + dest = dest.Intersect(compositionBounds.ToUnknownRect()); + scaleFactorX = std::min(100.f, dest.Width() - (2 * horizontalPadding)) / + scrollRect.Width(); + scaleFactorY = (dest.Height() - (2 * verticalPadding)) / scrollRect.Height(); + scaleFactor = std::min(scaleFactorX, scaleFactorY); + if (scaleFactor <= 0) { + return; + } + + Matrix4x4 transform = Matrix4x4::Scaling(scaleFactor, scaleFactor, 1); + transform.PostTranslate(horizontalPadding + dest.X(), + verticalPadding + dest.Y(), 0); + + Rect transformedScrollRect = + transform.TransformBounds(scrollRect.ToUnknownRect()); + + IntRect clipRect = + RoundedOut(aContainer->GetEffectiveTransform().TransformBounds( + transformedScrollRect)); + + // Render the scrollable area. + compositor->FillRect(transformedScrollRect, backgroundColor, clipRect, + aContainer->GetEffectiveTransform()); + compositor->SlowDrawRect(transformedScrollRect, pageBorderColor, clipRect, + aContainer->GetEffectiveTransform()); + + // Render the displayport. + Rect r = transform.TransformBounds(dp.ToUnknownRect()); + compositor->FillRect(r, tileActiveColor, clipRect, + aContainer->GetEffectiveTransform()); + compositor->SlowDrawRect(r, displayPortColor, clipRect, + aContainer->GetEffectiveTransform()); + + // Render the critical displayport if there is one + if (cdp) { + r = transform.TransformBounds(cdp->ToUnknownRect()); + compositor->SlowDrawRect(r, criticalDisplayPortColor, clipRect, + aContainer->GetEffectiveTransform()); + } + + // Render the layout viewport if it exists (which is only in the root + // content APZC). + if (layoutRect) { + r = transform.TransformBounds(layoutRect->ToUnknownRect()); + compositor->SlowDrawRect(r, layoutPortColor, clipRect, + aContainer->GetEffectiveTransform()); + } + + // Render the visual viewport. + r = transform.TransformBounds(visualRect.ToUnknownRect()); + compositor->SlowDrawRect(r, visualPortColor, clipRect, + aContainer->GetEffectiveTransform(), 2); +} + +template +void RenderLayers(ContainerT* aContainer, LayerManagerComposite* aManager, + const RenderTargetIntRect& aClipRect, + const Maybe& aGeometry) { + Compositor* compositor = aManager->GetCompositor(); + + RefPtr sampler; + if (CompositorBridgeParent* cbp = compositor->GetCompositorBridgeParent()) { + sampler = cbp->GetAPZSampler(); + } + + for (size_t i = 0u; i < aContainer->mPrepared->mLayers.Length(); i++) { + PreparedLayer& preparedData = aContainer->mPrepared->mLayers[i]; + + const gfx::IntRect clipRect = preparedData.mClipRect.ToUnknownRect(); + LayerComposite* layerToRender = + static_cast(preparedData.mLayer->ImplData()); + const Maybe& childGeometry = preparedData.mGeometry; + + Layer* layer = layerToRender->GetLayer(); + + if (layerToRender->HasStaleCompositor()) { + continue; + } + + if (StaticPrefs::layers_acceleration_draw_fps()) { + for (const auto& metadata : layer->GetAllScrollMetadata()) { + if (metadata.IsApzForceDisabled()) { + aManager->DisabledApzWarning(); + break; + } + } + } + + if (layerToRender->HasLayerBeenComposited()) { + // Composer2D will compose this layer so skip GPU composition + // this time. The flag will be reset for the next composition phase + // at the beginning of LayerManagerComposite::Rener(). + gfx::IntRect clearRect = layerToRender->GetClearRect(); + if (!clearRect.IsEmpty()) { + // Clear layer's visible rect on FrameBuffer with transparent pixels + gfx::Rect fbRect(clearRect.X(), clearRect.Y(), clearRect.Width(), + clearRect.Height()); + compositor->ClearRect(fbRect); + layerToRender->SetClearRect(gfx::IntRect(0, 0, 0, 0)); + } + } else { + // Since we force an intermediate surface for nested 3D contexts, + // aGeometry and childGeometry are both in the same coordinate space. + Maybe geometry = + SelectLayerGeometry(aGeometry, childGeometry); + + // If we are dealing with a nested 3D context, we might need to transform + // the geometry back to the coordinate space of the current layer before + // rendering the layer. + ContainerLayer* container = layer->AsContainerLayer(); + const bool isLeafLayer = + !container || container->UseIntermediateSurface(); + + if (geometry && isLeafLayer) { + TransformLayerGeometry(layer, geometry); + } + + layerToRender->RenderLayer(clipRect, geometry); + } + + if (StaticPrefs::layers_uniformity_info_AtStartup()) { + PrintUniformityInfo(layer); + } + + if (StaticPrefs::layers_draw_layer_info()) { + DrawLayerInfo(preparedData.mClipRect, aManager, layer); + } + + // Draw a border around scrollable layers. + // A layer can be scrolled by multiple scroll frames. Draw a border + // for each. + // Within the list of scroll frames for a layer, the layer border for a + // scroll frame lower down is affected by the async transforms on scroll + // frames higher up, so loop from the top down, and accumulate an async + // transform as we go along. + Matrix4x4 asyncTransform; + if (sampler) { + for (uint32_t i = layer->GetScrollMetadataCount(); i > 0; --i) { + LayerMetricsWrapper wrapper(layer, i - 1); + if (wrapper.GetApzc()) { + MOZ_ASSERT(wrapper.Metrics().IsScrollable()); + // Since the composition bounds are in the parent layer's coordinates, + // use the parent's effective transform rather than the layer's own. + ParentLayerRect compositionBounds = + wrapper.Metrics().GetCompositionBounds(); + aManager->GetCompositor()->DrawDiagnostics( + DiagnosticFlags::CONTAINER, compositionBounds.ToUnknownRect(), + aClipRect.ToUnknownRect(), + asyncTransform * aContainer->GetEffectiveTransform()); + asyncTransform = + sampler->GetCurrentAsyncTransformWithOverscroll(wrapper) + .ToUnknownMatrix() * + asyncTransform; + } + } + + if (StaticPrefs::apz_minimap_enabled()) { + RenderMinimap(aContainer, sampler, aManager, aClipRect, layer); + } + } + + // invariant: our GL context should be current here, I don't think we can + // assert it though + } +} + +template +RefPtr CreateOrRecycleTarget( + ContainerT* aContainer, LayerManagerComposite* aManager) { + Compositor* compositor = aManager->GetCompositor(); + SurfaceInitMode mode = INIT_MODE_CLEAR; + gfx::IntRect surfaceRect = ContainerVisibleRect(aContainer); + if (aContainer->GetLocalVisibleRegion().GetNumRects() == 1 && + (aContainer->GetContentFlags() & Layer::CONTENT_OPAQUE)) { + mode = INIT_MODE_NONE; + } + + RefPtr& lastSurf = + aContainer->mLastIntermediateSurface; + if (lastSurf && lastSurf->GetRect().IsEqualEdges(surfaceRect)) { + if (mode == INIT_MODE_CLEAR) { + lastSurf->ClearOnBind(); + } + + return lastSurf; + } else { + lastSurf = compositor->CreateRenderTarget(surfaceRect, mode); + + return lastSurf; + } +} + +template +RefPtr CreateTemporaryTargetAndCopyFromBackground( + ContainerT* aContainer, LayerManagerComposite* aManager) { + Compositor* compositor = aManager->GetCompositor(); + gfx::IntRect visibleRect = + aContainer->GetLocalVisibleRegion().GetBounds().ToUnknownRect(); + RefPtr previousTarget = + compositor->GetCurrentRenderTarget(); + gfx::IntRect surfaceRect = + gfx::IntRect(visibleRect.X(), visibleRect.Y(), visibleRect.Width(), + visibleRect.Height()); + + gfx::IntPoint sourcePoint = gfx::IntPoint(visibleRect.X(), visibleRect.Y()); + + gfx::Matrix4x4 transform = aContainer->GetEffectiveTransform(); + DebugOnly transform2d; + MOZ_ASSERT(transform.Is2D(&transform2d) && + !gfx::ThebesMatrix(transform2d).HasNonIntegerTranslation()); + sourcePoint += gfx::IntPoint::Truncate(transform._41, transform._42); + + sourcePoint -= previousTarget->GetOrigin(); + + return compositor->CreateRenderTargetFromSource(surfaceRect, previousTarget, + sourcePoint); +} + +template +void RenderIntermediate(ContainerT* aContainer, LayerManagerComposite* aManager, + const gfx::IntRect& aClipRect, + RefPtr surface) { + Compositor* compositor = aManager->GetCompositor(); + RefPtr previousTarget = + compositor->GetCurrentRenderTarget(); + + if (!surface) { + return; + } + + compositor->SetRenderTarget(surface); + // pre-render all of the layers into our temporary + RenderLayers(aContainer, aManager, + RenderTargetIntRect::FromUnknownRect(aClipRect), Nothing()); + + // Unbind the current surface and rebind the previous one. + compositor->SetRenderTarget(previousTarget); +} + +template +void ContainerRender(ContainerT* aContainer, LayerManagerComposite* aManager, + const gfx::IntRect& aClipRect, + const Maybe& aGeometry) { + MOZ_ASSERT(aContainer->mPrepared); + + if (aContainer->UseIntermediateSurface()) { + RefPtr surface; + + if (aContainer->mPrepared->mNeedsSurfaceCopy) { + // we needed to copy the background so we waited until now to render the + // intermediate + surface = + CreateTemporaryTargetAndCopyFromBackground(aContainer, aManager); + RenderIntermediate(aContainer, aManager, aClipRect, surface); + } else { + surface = aContainer->mPrepared->mTmpTarget; + } + + if (!surface) { + return; + } + + gfx::Rect visibleRect( + aContainer->GetLocalVisibleRegion().GetBounds().ToUnknownRect()); + + RefPtr compositor = aManager->GetCompositor(); +#ifdef MOZ_DUMP_PAINTING + if (gfxEnv::DumpCompositorTextures()) { + RefPtr surf = surface->Dump(compositor); + if (surf) { + WriteSnapshotToDumpFile(aContainer, surf); + } + } +#endif + + RefPtr container = aContainer; + RenderWithAllMasks(aContainer, compositor, aClipRect, + [&, surface, compositor, container]( + EffectChain& effectChain, const IntRect& clipRect) { + effectChain.mPrimaryEffect = + new EffectRenderTarget(surface); + + compositor->DrawGeometry( + visibleRect, clipRect, effectChain, + container->GetEffectiveOpacity(), + container->GetEffectiveTransform(), aGeometry); + }); + + } else { + RenderLayers(aContainer, aManager, + RenderTargetIntRect::FromUnknownRect(aClipRect), aGeometry); + } + + // If it is a scrollable container layer with no child layers, and one of the + // APZCs attached to it has a nonempty async transform, then that transform is + // not applied to any visible content. Display a warning box (conditioned on + // the FPS display being enabled). + if (StaticPrefs::layers_acceleration_draw_fps() && + aContainer->IsScrollableWithoutContent()) { + RefPtr sampler = + aManager->GetCompositor()->GetCompositorBridgeParent()->GetAPZSampler(); + // Since aContainer doesn't have any children we can just iterate from the + // top metrics on it down to the bottom using GetFirstChild and not worry + // about walking onto another underlying layer. + for (LayerMetricsWrapper i(aContainer); i; i = i.GetFirstChild()) { + if (sampler->HasUnusedAsyncTransform(i)) { + aManager->UnusedApzTransformWarning(); + break; + } + } + } +} + +ContainerLayerComposite::ContainerLayerComposite( + LayerManagerComposite* aManager) + : ContainerLayer(aManager, nullptr), LayerComposite(aManager) { + MOZ_COUNT_CTOR(ContainerLayerComposite); + mImplData = static_cast(this); +} + +ContainerLayerComposite::~ContainerLayerComposite() { + MOZ_COUNT_DTOR(ContainerLayerComposite); + + // We don't Destroy() on destruction here because this destructor + // can be called after remote content has crashed, and it may not be + // safe to free the IPC resources of our children. Those resources + // are automatically cleaned up by IPDL-generated code. + // + // In the common case of normal shutdown, either + // LayerManagerComposite::Destroy(), a parent + // *ContainerLayerComposite::Destroy(), or Disconnect() will trigger + // cleanup of our resources. + RemoveAllChildren(); +} + +void ContainerLayerComposite::Destroy() { + if (!mDestroyed) { + while (mFirstChild) { + GetFirstChildComposite()->Destroy(); + RemoveChild(mFirstChild); + } + mDestroyed = true; + } +} + +LayerComposite* ContainerLayerComposite::GetFirstChildComposite() { + if (!mFirstChild) { + return nullptr; + } + return static_cast(mFirstChild->AsHostLayer()); +} + +void ContainerLayerComposite::Cleanup() { + mPrepared = nullptr; + + for (Layer* l = GetFirstChild(); l; l = l->GetNextSibling()) { + static_cast(l->AsHostLayer())->Cleanup(); + } +} + +void ContainerLayerComposite::RenderLayer( + const gfx::IntRect& aClipRect, const Maybe& aGeometry) { + ContainerRender(this, mCompositeManager, aClipRect, aGeometry); +} + +void ContainerLayerComposite::Prepare(const RenderTargetIntRect& aClipRect) { + ContainerPrepare(this, mCompositeManager, aClipRect); +} + +void ContainerLayerComposite::CleanupResources() { + mLastIntermediateSurface = nullptr; + mPrepared = nullptr; + + for (Layer* l = GetFirstChild(); l; l = l->GetNextSibling()) { + static_cast(l->AsHostLayer())->CleanupResources(); + } +} + +const LayerIntRegion& ContainerLayerComposite::GetShadowVisibleRegion() { + if (!UseIntermediateSurface()) { + RecomputeShadowVisibleRegionFromChildren(); + } + + return mShadowVisibleRegion; +} + +const LayerIntRegion& RefLayerComposite::GetShadowVisibleRegion() { + if (!UseIntermediateSurface()) { + RecomputeShadowVisibleRegionFromChildren(); + } + + return mShadowVisibleRegion; +} + +RefLayerComposite::RefLayerComposite(LayerManagerComposite* aManager) + : RefLayer(aManager, nullptr), LayerComposite(aManager) { + mImplData = static_cast(this); +} + +RefLayerComposite::~RefLayerComposite() { Destroy(); } + +void RefLayerComposite::Destroy() { + MOZ_ASSERT(!mFirstChild); + mDestroyed = true; +} + +LayerComposite* RefLayerComposite::GetFirstChildComposite() { + if (!mFirstChild) { + return nullptr; + } + return static_cast(mFirstChild->AsHostLayer()); +} + +void RefLayerComposite::RenderLayer(const gfx::IntRect& aClipRect, + const Maybe& aGeometry) { + ContainerRender(this, mCompositeManager, aClipRect, aGeometry); +} + +void RefLayerComposite::Prepare(const RenderTargetIntRect& aClipRect) { + ContainerPrepare(this, mCompositeManager, aClipRect); +} + +void RefLayerComposite::Cleanup() { + mPrepared = nullptr; + + for (Layer* l = GetFirstChild(); l; l = l->GetNextSibling()) { + static_cast(l->AsHostLayer())->Cleanup(); + } +} + +void RefLayerComposite::CleanupResources() { + mLastIntermediateSurface = nullptr; + mPrepared = nullptr; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/ContainerLayerComposite.h b/gfx/layers/composite/ContainerLayerComposite.h new file mode 100644 index 0000000000..68ad26db2f --- /dev/null +++ b/gfx/layers/composite/ContainerLayerComposite.h @@ -0,0 +1,197 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_ContainerLayerComposite_H +#define GFX_ContainerLayerComposite_H + +#include "Layers.h" // for Layer (ptr only), etc +#include "mozilla/Attributes.h" // for override +#include "mozilla/UniquePtr.h" // for UniquePtr +#include "mozilla/layers/LayerManagerComposite.h" +#include "mozilla/gfx/Rect.h" + +namespace mozilla { +namespace layers { + +class APZSampler; +class CompositableHost; +class CompositingRenderTarget; +struct PreparedData; + +class ContainerLayerComposite : public ContainerLayer, public LayerComposite { + template + friend void ContainerPrepare(ContainerT* aContainer, + LayerManagerComposite* aManager, + const RenderTargetIntRect& aClipRect); + template + friend void ContainerRender(ContainerT* aContainer, + LayerManagerComposite* aManager, + const RenderTargetIntRect& aClipRect, + const Maybe& aGeometry); + template + friend void RenderLayers(ContainerT* aContainer, + LayerManagerComposite* aManager, + const RenderTargetIntRect& aClipRect, + const Maybe& aGeometry); + template + friend void RenderIntermediate(ContainerT* aContainer, + LayerManagerComposite* aManager, + const gfx::IntRect& aClipRect, + RefPtr surface); + template + friend RefPtr + CreateTemporaryTargetAndCopyFromBackground( + ContainerT* aContainer, LayerManagerComposite* aManager, + const RenderTargetIntRect& aClipRect); + template + friend RefPtr CreateOrRecycleTarget( + ContainerT* aContainer, LayerManagerComposite* aManager, + const RenderTargetIntRect& aClipRect); + + template + void RenderMinimap(ContainerT* aContainer, const RefPtr& aSampler, + LayerManagerComposite* aManager, + const RenderTargetIntRect& aClipRect, Layer* aLayer); + + public: + explicit ContainerLayerComposite(LayerManagerComposite* aManager); + + protected: + virtual ~ContainerLayerComposite(); + + public: + // LayerComposite Implementation + Layer* GetLayer() override { return this; } + + void SetLayerManager(HostLayerManager* aManager) override { + LayerComposite::SetLayerManager(aManager); + mManager = aManager; + mLastIntermediateSurface = nullptr; + } + + void Destroy() override; + + LayerComposite* GetFirstChildComposite() override; + + void Cleanup() override; + + void RenderLayer(const gfx::IntRect& aClipRect, + const Maybe& aGeometry) override; + + void Prepare(const RenderTargetIntRect& aClipRect) override; + + void ComputeEffectiveTransforms( + const gfx::Matrix4x4& aTransformToSurface) override { + DefaultComputeEffectiveTransforms(aTransformToSurface); + } + + const LayerIntRegion& GetShadowVisibleRegion() override; + + void CleanupResources() override; + + HostLayer* AsHostLayer() override { return this; } + + // container layers don't use a compositable + CompositableHost* GetCompositableHost() override { return nullptr; } + + // If the layer has a pres shell resolution, add a post-scale + // to the layer's transform equal to the pres shell resolution we're + // scaling to. This cancels out the post scale of '1 / resolution' + // added by Layout. TODO: It would be nice to get rid of both of these + // post-scales. + float GetPostXScale() const override { + return mSimpleAttrs.GetPostXScale() * mPresShellResolution; + } + float GetPostYScale() const override { + return mSimpleAttrs.GetPostYScale() * mPresShellResolution; + } + + const char* Name() const override { return "ContainerLayerComposite"; } + UniquePtr mPrepared; + + RefPtr mLastIntermediateSurface; +}; + +class RefLayerComposite : public RefLayer, public LayerComposite { + template + friend void ContainerPrepare(ContainerT* aContainer, + LayerManagerComposite* aManager, + const RenderTargetIntRect& aClipRect); + template + friend void ContainerRender(ContainerT* aContainer, + LayerManagerComposite* aManager, + const gfx::IntRect& aClipRect, + const Maybe& aGeometry); + template + friend void RenderLayers(ContainerT* aContainer, + LayerManagerComposite* aManager, + const gfx::IntRect& aClipRect, + const Maybe& aGeometry); + template + friend void RenderIntermediate(ContainerT* aContainer, + LayerManagerComposite* aManager, + const gfx::IntRect& aClipRect, + RefPtr surface); + template + friend RefPtr + CreateTemporaryTargetAndCopyFromBackground(ContainerT* aContainer, + LayerManagerComposite* aManager, + const gfx::IntRect& aClipRect); + template + friend RefPtr CreateTemporaryTarget( + ContainerT* aContainer, LayerManagerComposite* aManager, + const gfx::IntRect& aClipRect); + + public: + explicit RefLayerComposite(LayerManagerComposite* aManager); + + protected: + virtual ~RefLayerComposite(); + + public: + /** LayerOGL implementation */ + Layer* GetLayer() override { return this; } + + void SetLayerManager(HostLayerManager* aManager) override { + LayerComposite::SetLayerManager(aManager); + mManager = aManager; + mLastIntermediateSurface = nullptr; + } + + void Destroy() override; + + LayerComposite* GetFirstChildComposite() override; + + void RenderLayer(const gfx::IntRect& aClipRect, + const Maybe& aGeometry) override; + + void Prepare(const RenderTargetIntRect& aClipRect) override; + + void ComputeEffectiveTransforms( + const gfx::Matrix4x4& aTransformToSurface) override { + DefaultComputeEffectiveTransforms(aTransformToSurface); + } + + const LayerIntRegion& GetShadowVisibleRegion() override; + + void Cleanup() override; + + void CleanupResources() override; + + HostLayer* AsHostLayer() override { return this; } + + // ref layers don't use a compositable + CompositableHost* GetCompositableHost() override { return nullptr; } + + const char* Name() const override { return "RefLayerComposite"; } + UniquePtr mPrepared; + RefPtr mLastIntermediateSurface; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_ContainerLayerComposite_H */ diff --git a/gfx/layers/composite/ContentHost.cpp b/gfx/layers/composite/ContentHost.cpp new file mode 100644 index 0000000000..d4be5b0dc7 --- /dev/null +++ b/gfx/layers/composite/ContentHost.cpp @@ -0,0 +1,460 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/ContentHost.h" +#include "gfx2DGlue.h" // for ContentForFormat +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/gfx/BaseRect.h" // for BaseRect +#include "mozilla/layers/Compositor.h" // for Compositor +#include "mozilla/layers/Effects.h" // for TexturedEffect, Effect, etc +#include "mozilla/layers/LayersMessages.h" // for ThebesBufferData +#include "nsAString.h" +#include "nsPrintfCString.h" // for nsPrintfCString +#include "nsString.h" // for nsAutoCString +#include "mozilla/layers/TextureHostOGL.h" // for TextureHostOGL + +namespace mozilla { +using namespace gfx; + +namespace layers { + +ContentHostBase::ContentHostBase(const TextureInfo& aTextureInfo) + : ContentHost(aTextureInfo), mInitialised(false) {} + +ContentHostBase::~ContentHostBase() = default; + +void ContentHostTexture::Composite( + Compositor* aCompositor, LayerComposite* aLayer, EffectChain& aEffectChain, + float aOpacity, const gfx::Matrix4x4& aTransform, + const SamplingFilter aSamplingFilter, const IntRect& aClipRect, + const nsIntRegion* aVisibleRegion, const Maybe& aGeometry) { + NS_ASSERTION(aVisibleRegion, "Requires a visible region"); + + AutoLockCompositableHost lock(this); + if (lock.Failed()) { + return; + } + + if (!mTextureHost->BindTextureSource(mTextureSource)) { + return; + } + MOZ_ASSERT(mTextureSource.get()); + + if (!mTextureHostOnWhite) { + mTextureSourceOnWhite = nullptr; + } + if (mTextureHostOnWhite && + !mTextureHostOnWhite->BindTextureSource(mTextureSourceOnWhite)) { + return; + } + + RefPtr effect = CreateTexturedEffect( + mTextureSource.get(), mTextureSourceOnWhite.get(), aSamplingFilter, true); + if (!effect) { + return; + } + + aEffectChain.mPrimaryEffect = effect; + + nsIntRegion tmpRegion; + const nsIntRegion* renderRegion; +#ifndef MOZ_IGNORE_PAINT_WILL_RESAMPLE + if (PaintWillResample()) { + // If we're resampling, then the texture image will contain exactly the + // entire visible region's bounds, and we should draw it all in one quad + // to avoid unexpected aliasing. + tmpRegion = aVisibleRegion->GetBounds(); + renderRegion = &tmpRegion; + } else { + renderRegion = aVisibleRegion; + } +#else + renderRegion = aVisibleRegion; +#endif + + nsIntRegion region(*renderRegion); + nsIntPoint origin = GetOriginOffset(); + // translate into TexImage space, buffer origin might not be at texture (0,0) + region.MoveBy(-origin); + + // Figure out the intersecting draw region + gfx::IntSize texSize = mTextureSource->GetSize(); + IntRect textureRect = IntRect(0, 0, texSize.width, texSize.height); + textureRect.MoveBy(region.GetBounds().TopLeft()); + nsIntRegion subregion; + subregion.And(region, textureRect); + if (subregion.IsEmpty()) { + // Region is empty, nothing to draw + return; + } + + nsIntRegion screenRects; + nsIntRegion regionRects; + + // Collect texture/screen coordinates for drawing + for (auto iter = subregion.RectIter(); !iter.Done(); iter.Next()) { + IntRect regionRect = iter.Get(); + IntRect screenRect = iter.Get(); + screenRect.MoveBy(origin); + + screenRects.Or(screenRects, screenRect); + regionRects.Or(regionRects, regionRect); + } + + BigImageIterator* bigImgIter = mTextureSource->AsBigImageIterator(); + BigImageIterator* iterOnWhite = nullptr; + if (bigImgIter) { + bigImgIter->BeginBigImageIteration(); + } + + if (mTextureSourceOnWhite) { + iterOnWhite = mTextureSourceOnWhite->AsBigImageIterator(); + MOZ_ASSERT(!bigImgIter || + bigImgIter->GetTileCount() == iterOnWhite->GetTileCount(), + "Tile count mismatch on component alpha texture"); + if (iterOnWhite) { + iterOnWhite->BeginBigImageIteration(); + } + } + + bool usingTiles = (bigImgIter && bigImgIter->GetTileCount() > 1); + do { + if (iterOnWhite && bigImgIter) { + MOZ_ASSERT(iterOnWhite->GetTileRect() == bigImgIter->GetTileRect(), + "component alpha textures should be the same size."); + } + + IntRect texRect = bigImgIter ? bigImgIter->GetTileRect() + : IntRect(0, 0, texSize.width, texSize.height); + + // Draw texture. If we're using tiles, we do repeating manually, as texture + // repeat would cause each individual tile to repeat instead of the + // compound texture as a whole. This involves drawing at most 4 sections, + // 2 for each axis that has texture repeat. + for (int y = 0; y < (usingTiles ? 2 : 1); y++) { + for (int x = 0; x < (usingTiles ? 2 : 1); x++) { + IntRect currentTileRect(texRect); + currentTileRect.MoveBy(x * texSize.width, y * texSize.height); + + for (auto screenIter = screenRects.RectIter(), + regionIter = regionRects.RectIter(); + !screenIter.Done() && !regionIter.Done(); + screenIter.Next(), regionIter.Next()) { + const IntRect& screenRect = screenIter.Get(); + const IntRect& regionRect = regionIter.Get(); + IntRect tileScreenRect(screenRect); + IntRect tileRegionRect(regionRect); + + // When we're using tiles, find the intersection between the tile + // rect and this region rect. Tiling is then handled by the + // outer for-loops and modifying the tile rect. + if (usingTiles) { + tileScreenRect.MoveBy(-origin); + tileScreenRect = tileScreenRect.Intersect(currentTileRect); + tileScreenRect.MoveBy(origin); + + if (tileScreenRect.IsEmpty()) continue; + + tileRegionRect = regionRect.Intersect(currentTileRect); + tileRegionRect.MoveBy(-currentTileRect.TopLeft()); + } + gfx::Rect rect(tileScreenRect.X(), tileScreenRect.Y(), + tileScreenRect.Width(), tileScreenRect.Height()); + + effect->mTextureCoords = + Rect(Float(tileRegionRect.X()) / texRect.Width(), + Float(tileRegionRect.Y()) / texRect.Height(), + Float(tileRegionRect.Width()) / texRect.Width(), + Float(tileRegionRect.Height()) / texRect.Height()); + + aCompositor->DrawGeometry(rect, aClipRect, aEffectChain, aOpacity, + aTransform, aGeometry); + + if (usingTiles) { + DiagnosticFlags diagnostics = + DiagnosticFlags::CONTENT | DiagnosticFlags::BIGIMAGE; + if (iterOnWhite) { + diagnostics |= DiagnosticFlags::COMPONENT_ALPHA; + } + aCompositor->DrawDiagnostics(diagnostics, rect, aClipRect, + aTransform, mFlashCounter); + } + } + } + } + + if (iterOnWhite) { + iterOnWhite->NextTile(); + } + } while (usingTiles && bigImgIter->NextTile()); + + if (bigImgIter) { + bigImgIter->EndBigImageIteration(); + } + if (iterOnWhite) { + iterOnWhite->EndBigImageIteration(); + } + + DiagnosticFlags diagnostics = DiagnosticFlags::CONTENT; + if (iterOnWhite) { + diagnostics |= DiagnosticFlags::COMPONENT_ALPHA; + } + aCompositor->DrawDiagnostics(diagnostics, nsIntRegion(mBufferRect), aClipRect, + aTransform, mFlashCounter); +} + +RefPtr ContentHostTexture::AcquireTextureSource() { + if (!mTextureHost || !mTextureHost->AcquireTextureSource(mTextureSource)) { + return nullptr; + } + return mTextureSource.get(); +} + +RefPtr ContentHostTexture::AcquireTextureSourceOnWhite() { + if (!mTextureHostOnWhite || + !mTextureHostOnWhite->AcquireTextureSource(mTextureSourceOnWhite)) { + return nullptr; + } + return mTextureSourceOnWhite.get(); +} + +void ContentHostTexture::UseTextureHost( + const nsTArray& aTextures) { + ContentHostBase::UseTextureHost(aTextures); + MOZ_ASSERT(aTextures.Length() == 1); + const TimedTexture& t = aTextures[0]; + MOZ_ASSERT(t.mPictureRect.IsEqualInterior( + nsIntRect(nsIntPoint(0, 0), nsIntSize(t.mTexture->GetSize()))), + "Only default picture rect supported"); + + if (t.mTexture != mTextureHost) { + mReceivedNewHost = true; + } + + mTextureHost = t.mTexture; + mTextureHostOnWhite = nullptr; + mTextureSourceOnWhite = nullptr; + if (mTextureHost) { + mTextureHost->PrepareTextureSource(mTextureSource); + } +} + +void ContentHostTexture::UseComponentAlphaTextures( + TextureHost* aTextureOnBlack, TextureHost* aTextureOnWhite) { + ContentHostBase::UseComponentAlphaTextures(aTextureOnBlack, aTextureOnWhite); + mTextureHost = aTextureOnBlack; + mTextureHostOnWhite = aTextureOnWhite; + if (mTextureHost) { + mTextureHost->PrepareTextureSource(mTextureSource); + } + if (mTextureHostOnWhite) { + mTextureHostOnWhite->PrepareTextureSource(mTextureSourceOnWhite); + } +} + +void ContentHostTexture::SetTextureSourceProvider( + TextureSourceProvider* aProvider) { + ContentHostBase::SetTextureSourceProvider(aProvider); + if (mTextureHost) { + mTextureHost->SetTextureSourceProvider(aProvider); + } + if (mTextureHostOnWhite) { + mTextureHostOnWhite->SetTextureSourceProvider(aProvider); + } +} + +void ContentHostTexture::Dump(std::stringstream& aStream, const char* aPrefix, + bool aDumpHtml) { +#ifdef MOZ_DUMP_PAINTING + if (aDumpHtml) { + aStream << "
      "; + } + if (mTextureHost) { + aStream << aPrefix; + if (aDumpHtml) { + aStream << "
    • Front buffer
    • "; + } else { + aStream << "\n"; + } + } + if (mTextureHostOnWhite) { + aStream << aPrefix; + if (aDumpHtml) { + aStream << "
    • Front buffer on white
    • "; + } else { + aStream << "\n"; + } + } + if (aDumpHtml) { + aStream << "
    "; + } +#endif +} + +static inline void AddWrappedRegion(const nsIntRegion& aInput, + nsIntRegion& aOutput, const IntSize& aSize, + const nsIntPoint& aShift) { + nsIntRegion tempRegion; + tempRegion.And(IntRect(aShift, aSize), aInput); + tempRegion.MoveBy(-aShift); + aOutput.Or(aOutput, tempRegion); +} + +bool ContentHostSingleBuffered::UpdateThebes( + const ThebesBufferData& aData, const nsIntRegion& aUpdated, + const nsIntRegion& aOldValidRegionBack) { + if (!mTextureHost) { + mInitialised = false; + return true; // FIXME should we return false? Returning true for now + } // to preserve existing behavior of NOT causing IPC errors. + + // updated is in screen coordinates. Convert it to buffer coordinates. + nsIntRegion destRegion(aUpdated); + + if (mReceivedNewHost) { + destRegion.Or(destRegion, aOldValidRegionBack); + mReceivedNewHost = false; + } + destRegion.MoveBy(-aData.rect().TopLeft()); + + if (!aData.rect().Contains(aUpdated.GetBounds()) || + aData.rotation().x > aData.rect().Width() || + aData.rotation().y > aData.rect().Height()) { + NS_ERROR("Invalid update data"); + return false; + } + + // destRegion is now in logical coordinates relative to the buffer, but we + // need to account for rotation. We do that by moving the region to the + // rotation offset and then wrapping any pixels that extend off the + // bottom/right edges. + + // Shift to the rotation point + destRegion.MoveBy(aData.rotation()); + + IntSize bufferSize = aData.rect().Size(); + + // Select only the pixels that are still within the buffer. + nsIntRegion finalRegion; + finalRegion.And(IntRect(IntPoint(), bufferSize), destRegion); + + // For each of the overlap areas (right, bottom-right, bottom), select those + // pixels and wrap them around to the opposite edge of the buffer rect. + AddWrappedRegion(destRegion, finalRegion, bufferSize, + nsIntPoint(aData.rect().Width(), 0)); + AddWrappedRegion(destRegion, finalRegion, bufferSize, + nsIntPoint(aData.rect().Width(), aData.rect().Height())); + AddWrappedRegion(destRegion, finalRegion, bufferSize, + nsIntPoint(0, aData.rect().Height())); + + MOZ_ASSERT(IntRect(0, 0, aData.rect().Width(), aData.rect().Height()) + .Contains(finalRegion.GetBounds())); + + mTextureHost->Updated(&finalRegion); + if (mTextureHostOnWhite) { + mTextureHostOnWhite->Updated(&finalRegion); + } + mInitialised = true; + + mBufferRect = aData.rect(); + mBufferRotation = aData.rotation(); + + return true; +} + +bool ContentHostDoubleBuffered::UpdateThebes( + const ThebesBufferData& aData, const nsIntRegion& aUpdated, + const nsIntRegion& aOldValidRegionBack) { + if (!mTextureHost) { + mInitialised = false; + return true; + } + + // We don't need to calculate an update region because we assume that if we + // are using double buffering then we have render-to-texture and thus no + // upload to do. + mTextureHost->Updated(); + if (mTextureHostOnWhite) { + mTextureHostOnWhite->Updated(); + } + mInitialised = true; + + mBufferRect = aData.rect(); + mBufferRotation = aData.rotation(); + + // Save the current valid region of our front buffer, because if + // we're double buffering, it's going to be the valid region for the + // next back buffer sent back to the renderer. + // + // NB: we rely here on the fact that mValidRegion is initialized to + // empty, and that the first time Swap() is called we don't have a + // valid front buffer that we're going to return to content. + mValidRegionForNextBackBuffer = aOldValidRegionBack; + + return true; +} + +void ContentHostTexture::PrintInfo(std::stringstream& aStream, + const char* aPrefix) { + aStream << aPrefix; + aStream << nsPrintfCString("ContentHost (0x%p)", this).get() + << " [buffer-rect=" << mBufferRect << "]" + << " [buffer-rotation=" << mBufferRotation << "]"; + if (PaintWillResample()) { + aStream << " [paint-will-resample]"; + } + + if (mTextureHost) { + nsAutoCString pfx(aPrefix); + pfx += " "; + + aStream << "\n"; + mTextureHost->PrintInfo(aStream, pfx.get()); + } +} + +already_AddRefed ContentHostTexture::GenEffect( + const gfx::SamplingFilter aSamplingFilter) { + if (!mTextureHost) { + return nullptr; + } + if (!mTextureHost->BindTextureSource(mTextureSource)) { + return nullptr; + } + if (!mTextureHostOnWhite) { + mTextureSourceOnWhite = nullptr; + } + if (mTextureHostOnWhite && + !mTextureHostOnWhite->BindTextureSource(mTextureSourceOnWhite)) { + return nullptr; + } + return CreateTexturedEffect(mTextureSource.get(), mTextureSourceOnWhite.get(), + aSamplingFilter, true); +} + +already_AddRefed ContentHostTexture::GetAsSurface() { + if (!mTextureHost) { + return nullptr; + } + + return mTextureHost->GetAsSurface(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/ContentHost.h b/gfx/layers/composite/ContentHost.h new file mode 100644 index 0000000000..6833ebd74f --- /dev/null +++ b/gfx/layers/composite/ContentHost.h @@ -0,0 +1,236 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_CONTENTHOST_H +#define GFX_CONTENTHOST_H + +#include // for uint32_t +#include // for FILE +#include "mozilla-config.h" // for MOZ_DUMP_PAINTING +#include "CompositableHost.h" // for CompositableHost, etc +#include "RotatedBuffer.h" // for RotatedBuffer, etc +#include "mozilla/Attributes.h" // for override +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/gfx/BasePoint.h" // for BasePoint +#include "mozilla/gfx/MatrixFwd.h" // for Matrix4x4 +#include "mozilla/gfx/Point.h" // for Point +#include "mozilla/gfx/Polygon.h" // for Polygon +#include "mozilla/gfx/Rect.h" // for Rect +#include "mozilla/gfx/Types.h" // for SamplingFilter +#include "mozilla/layers/CompositorTypes.h" // for TextureInfo, etc +#include "mozilla/layers/ContentClient.h" // for ContentClient +#include "mozilla/layers/ISurfaceAllocator.h" // for ISurfaceAllocator +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor +#include "mozilla/layers/LayersTypes.h" // for etc +#include "mozilla/layers/TextureHost.h" // for TextureHost +#include "mozilla/mozalloc.h" // for operator delete +#include "mozilla/UniquePtr.h" // for UniquePtr +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "nsPoint.h" // for nsIntPoint +#include "nsRect.h" // for mozilla::gfx::IntRect +#include "nsRegion.h" // for nsIntRegion +#include "nsTArray.h" // for nsTArray +#include "nscore.h" // for nsACString + +namespace mozilla { +namespace layers { +class Compositor; +class ThebesBufferData; +struct EffectChain; + +struct TexturedEffect; + +/** + * ContentHosts are used for compositing Painted layers, always matched by a + * ContentClient of the same type. + * + * ContentHosts support only UpdateThebes(), not Update(). + */ +class ContentHost : public CompositableHost { + public: + virtual bool UpdateThebes( + const ThebesBufferData& aData, const nsIntRegion& aUpdated, + const nsIntRegion& aOldValidRegionBack) override = 0; + + virtual void SetPaintWillResample(bool aResample) { + mPaintWillResample = aResample; + } + bool PaintWillResample() { return mPaintWillResample; } + + // We use this to allow TiledContentHost to invalidate regions where + // tiles are fading in. + virtual void AddAnimationInvalidation(nsIntRegion& aRegion) {} + + virtual gfx::IntRect GetBufferRect() { + MOZ_ASSERT_UNREACHABLE("Must be implemented in derived class"); + return gfx::IntRect(); + } + + ContentHost* AsContentHost() override { return this; } + + protected: + explicit ContentHost(const TextureInfo& aTextureInfo) + : CompositableHost(aTextureInfo), mPaintWillResample(false) {} + + bool mPaintWillResample; +}; + +/** + * Base class for non-tiled ContentHosts. + * + * Ownership of the SurfaceDescriptor and the resources it represents is passed + * from the ContentClient to the ContentHost when the TextureClient/Hosts are + * created, that is recevied here by SetTextureHosts which assigns one or two + * texture hosts (for single and double buffering) to the ContentHost. + * + * It is the responsibility of the ContentHost to destroy its resources when + * they are recreated or the ContentHost dies. + */ +class ContentHostBase : public ContentHost { + public: + typedef ContentClient::ContentType ContentType; + typedef ContentClient::PaintState PaintState; + + explicit ContentHostBase(const TextureInfo& aTextureInfo); + virtual ~ContentHostBase(); + + gfx::IntRect GetBufferRect() override { return mBufferRect; } + + virtual nsIntPoint GetOriginOffset() { + return mBufferRect.TopLeft() - mBufferRotation; + } + + gfx::IntPoint GetBufferRotation() { return mBufferRotation.ToUnknownPoint(); } + + protected: + gfx::IntRect mBufferRect; + nsIntPoint mBufferRotation; + bool mInitialised; +}; + +/** + * Shared ContentHostBase implementation for content hosts that + * use up to two TextureHosts. + */ +class ContentHostTexture : public ContentHostBase { + public: + explicit ContentHostTexture(const TextureInfo& aTextureInfo) + : ContentHostBase(aTextureInfo), + mLocked(false), + mReceivedNewHost(false) {} + + void Composite(Compositor* aCompositor, LayerComposite* aLayer, + EffectChain& aEffectChain, float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::SamplingFilter aSamplingFilter, + const gfx::IntRect& aClipRect, + const nsIntRegion* aVisibleRegion = nullptr, + const Maybe& aGeometry = Nothing()) override; + + void SetTextureSourceProvider(TextureSourceProvider* aProvider) override; + + already_AddRefed GetAsSurface() override; + + void Dump(std::stringstream& aStream, const char* aPrefix = "", + bool aDumpHtml = false) override; + + void PrintInfo(std::stringstream& aStream, const char* aPrefix) override; + + void UseTextureHost(const nsTArray& aTextures) override; + void UseComponentAlphaTextures(TextureHost* aTextureOnBlack, + TextureHost* aTextureOnWhite) override; + + bool Lock() override { + MOZ_ASSERT(!mLocked); + if (!mTextureHost) { + return false; + } + if (!mTextureHost->Lock()) { + return false; + } + + if (mTextureHostOnWhite && !mTextureHostOnWhite->Lock()) { + return false; + } + + mLocked = true; + return true; + } + void Unlock() override { + MOZ_ASSERT(mLocked); + mTextureHost->Unlock(); + if (mTextureHostOnWhite) { + mTextureHostOnWhite->Unlock(); + } + mLocked = false; + } + + bool HasComponentAlpha() const { return !!mTextureHostOnWhite; } + + RefPtr AcquireTextureSource(); + RefPtr AcquireTextureSourceOnWhite(); + + ContentHostTexture* AsContentHostTexture() override { return this; } + + already_AddRefed GenEffect( + const gfx::SamplingFilter aSamplingFilter) override; + + protected: + CompositableTextureHostRef mTextureHost; + CompositableTextureHostRef mTextureHostOnWhite; + CompositableTextureSourceRef mTextureSource; + CompositableTextureSourceRef mTextureSourceOnWhite; + bool mLocked; + bool mReceivedNewHost; +}; + +/** + * Double buffering is implemented by swapping the front and back TextureHosts. + * We assume that whenever we use double buffering, then we have + * render-to-texture and thus no texture upload to do. + */ +class ContentHostDoubleBuffered : public ContentHostTexture { + public: + explicit ContentHostDoubleBuffered(const TextureInfo& aTextureInfo) + : ContentHostTexture(aTextureInfo) {} + + virtual ~ContentHostDoubleBuffered() = default; + + CompositableType GetType() override { + return CompositableType::CONTENT_DOUBLE; + } + + virtual bool UpdateThebes(const ThebesBufferData& aData, + const nsIntRegion& aUpdated, + const nsIntRegion& aOldValidRegionBack) override; + + protected: + nsIntRegion mValidRegionForNextBackBuffer; +}; + +/** + * Single buffered, therefore we must synchronously upload the image from the + * TextureHost in the layers transaction (i.e., in UpdateThebes). + */ +class ContentHostSingleBuffered : public ContentHostTexture { + public: + explicit ContentHostSingleBuffered(const TextureInfo& aTextureInfo) + : ContentHostTexture(aTextureInfo) {} + virtual ~ContentHostSingleBuffered() = default; + + CompositableType GetType() override { + return CompositableType::CONTENT_SINGLE; + } + + bool UpdateThebes(const ThebesBufferData& aData, const nsIntRegion& aUpdated, + const nsIntRegion& aOldValidRegionBack) override; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/composite/Diagnostics.cpp b/gfx/layers/composite/Diagnostics.cpp new file mode 100644 index 0000000000..0be1f978c8 --- /dev/null +++ b/gfx/layers/composite/Diagnostics.cpp @@ -0,0 +1,96 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Diagnostics.h" +#include "mozilla/layers/LayersMessages.h" +#include "nsPrintfCString.h" + +namespace mozilla { +namespace layers { + +float TimedMetric::Average() const { + // We take at most 2 seconds of history. + TimeStamp latest = TimeStamp::Now(); + float total = 0.0f; + size_t count = 0; + for (auto iter = mHistory.rbegin(); iter != mHistory.rend(); iter++) { + if ((latest - iter->second).ToSeconds() > 2.0f) { + break; + } + total += iter->first; + count++; + } + + if (!count) { + return 0.0f; + } + return total / float(count); +} + +Diagnostics::Diagnostics() + : mCompositeFps("Compositor"), mTransactionFps("LayerTransactions") {} + +void Diagnostics::RecordPaintTimes(const PaintTiming& aPaintTimes) { + mDlbMs.Add(aPaintTimes.dlMs()); + mDlb2Ms.Add(aPaintTimes.dl2Ms()); + mFlbMs.Add(aPaintTimes.flbMs()); + mRasterMs.Add(aPaintTimes.rasterMs()); + mSerializeMs.Add(aPaintTimes.serializeMs()); + mSendMs.Add(aPaintTimes.sendMs()); +} + +std::string Diagnostics::GetFrameOverlayString(const GPUStats& aStats) { + TimeStamp now = TimeStamp::Now(); + unsigned fps = unsigned(mCompositeFps.AddFrameAndGetFps(now)); + unsigned txnFps = unsigned(mTransactionFps.GetFPS(now)); + + float pixelFillRatio = + aStats.mInvalidPixels + ? float(aStats.mPixelsFilled) / float(aStats.mInvalidPixels) + : 0.0f; + float screenFillRatio = aStats.mScreenPixels ? float(aStats.mPixelsFilled) / + float(aStats.mScreenPixels) + : 0.0f; + + if (aStats.mDrawTime) { + mGPUDrawMs.Add(aStats.mDrawTime.value()); + } + + std::string gpuTimeString; + if (mGPUDrawMs.Empty()) { + gpuTimeString = "N/A"; + } else { + gpuTimeString = nsPrintfCString("%0.1fms", mGPUDrawMs.Average()).get(); + } + + // DL = nsDisplayListBuilder, p = partial, f = full + // FLB = FrameLayerBuilder + // R = ClientLayerManager::EndTransaction + // CP = ShadowLayerForwarder::EndTransaction (txn build) + // TX = LayerTransactionChild::SendUpdate (IPDL serialize+send) + // UP = LayerTransactionParent::RecvUpdate (IPDL deserialize, update, APZ + // update) + // CC_BUILD = Container prepare/composite frame building + // CC_EXEC = Container render/composite drawing + nsPrintfCString line1("FPS: %d (TXN: %d)", fps, txnFps); + nsPrintfCString line2( + "[CC] Build: %0.1fms Exec: %0.1fms GPU: %s Fill Ratio: %0.1f/%0.1f", + mPrepareMs.Average(), mCompositeMs.Average(), gpuTimeString.c_str(), + pixelFillRatio, screenFillRatio); + nsPrintfCString line3( + "[Content] DL p: %0.1f DL f: %0.1fms FLB: %0.1fms Raster: %0.1fms", + mDlbMs.Average(), mDlb2Ms.Average(), mFlbMs.Average(), + mRasterMs.Average()); + nsPrintfCString line4("[IPDL] Build: %0.1fms Send: %0.1fms Update: %0.1fms", + mSerializeMs.Average(), mSendMs.Average(), + mUpdateMs.Average()); + + return std::string(line1.get()) + "\n" + std::string(line2.get()) + "\n" + + std::string(line3.get()) + "\n" + std::string(line4.get()); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/Diagnostics.h b/gfx/layers/composite/Diagnostics.h new file mode 100644 index 0000000000..4619ff6aa5 --- /dev/null +++ b/gfx/layers/composite/Diagnostics.h @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_layers_composite_Diagnostics_h +#define mozilla_gfx_layers_composite_Diagnostics_h + +#include "FPSCounter.h" +#include "mozilla/Maybe.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/TimeStamp.h" +#include +#include +#include + +namespace mozilla { +namespace layers { + +class PaintTiming; + +class TimedMetric { + typedef std::pair Entry; + + public: + void Add(float aValue) { + if (mHistory.size() > kMaxHistory) { + mHistory.pop_front(); + } + mHistory.push_back(Entry(aValue, TimeStamp::Now())); + } + + float Average() const; + bool Empty() const { return mHistory.empty(); } + + private: + static const size_t kMaxHistory = 60; + + std::deque mHistory; +}; + +// These statistics are collected by layers backends, preferably by the GPU +struct GPUStats { + GPUStats() : mInvalidPixels(0), mScreenPixels(0), mPixelsFilled(0) {} + + uint32_t mInvalidPixels; + uint32_t mScreenPixels; + uint32_t mPixelsFilled; + Maybe mDrawTime; +}; + +// Collects various diagnostics about layers performance. +class Diagnostics { + public: + Diagnostics(); + + void RecordPaintTimes(const PaintTiming& aPaintTimes); + void RecordUpdateTime(float aValue) { mUpdateMs.Add(aValue); } + void RecordPrepareTime(float aValue) { mPrepareMs.Add(aValue); } + void RecordCompositeTime(float aValue) { mCompositeMs.Add(aValue); } + void AddTxnFrame() { mTransactionFps.AddFrame(TimeStamp::Now()); } + + std::string GetFrameOverlayString(const GPUStats& aStats); + + class Record { + public: + explicit Record(TimeStamp aStart = TimeStamp()) { + if (StaticPrefs::layers_acceleration_draw_fps()) { + mStart = aStart.IsNull() ? TimeStamp::Now() : aStart; + } + } + bool Recording() const { return !mStart.IsNull(); } + float Duration() const { + return (TimeStamp::Now() - mStart).ToMilliseconds(); + } + + private: + TimeStamp mStart; + }; + + private: + FPSCounter mCompositeFps; + FPSCounter mTransactionFps; + TimedMetric mDlbMs; + TimedMetric mDlb2Ms; + TimedMetric mFlbMs; + TimedMetric mRasterMs; + TimedMetric mSerializeMs; + TimedMetric mSendMs; + TimedMetric mUpdateMs; + TimedMetric mPrepareMs; + TimedMetric mCompositeMs; + TimedMetric mGPUDrawMs; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_gfx_layers_composite_Diagnostics_h diff --git a/gfx/layers/composite/FPSCounter.cpp b/gfx/layers/composite/FPSCounter.cpp new file mode 100644 index 0000000000..cf3d0725ed --- /dev/null +++ b/gfx/layers/composite/FPSCounter.cpp @@ -0,0 +1,343 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include // for size_t +#include "Units.h" // for ScreenIntRect +#include "gfxRect.h" // for gfxRect +#include "mozilla/gfx/Point.h" // for IntSize, Point +#include "mozilla/gfx/Rect.h" // for Rect +#include "mozilla/gfx/Types.h" // for Color, SurfaceFormat +#include "mozilla/layers/Compositor.h" // for Compositor +#include "mozilla/layers/CompositorTypes.h" +#include "mozilla/layers/Effects.h" // for Effect, EffectChain, etc +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/TimeStamp.h" // for TimeStamp, TimeDuration +#include "nsPoint.h" // for nsIntPoint +#include "nsRect.h" // for mozilla::gfx::IntRect +#include "nsIFile.h" // for nsIFile +#include "nsDirectoryServiceDefs.h" // for NS_OS_TMP_DIR +#include "mozilla/Sprintf.h" +#include "FPSCounter.h" + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +FPSCounter::FPSCounter(const char* aName) + : mWriteIndex(0), mIteratorIndex(-1), mFPSName(aName) { + Init(); +} + +FPSCounter::~FPSCounter() = default; + +void FPSCounter::Init() { + for (int i = 0; i < kMaxFrames; i++) { + mFrameTimestamps.AppendElement(TimeStamp()); + } + mLastInterval = TimeStamp::Now(); +} + +// Returns true if we captured a full interval of data +bool FPSCounter::CapturedFullInterval(TimeStamp aTimestamp) { + TimeDuration duration = aTimestamp - mLastInterval; + return duration.ToSeconds() >= kFpsDumpInterval; +} + +void FPSCounter::AddFrame(TimeStamp aTimestamp) { + NS_ASSERTION(mWriteIndex < kMaxFrames, + "We probably have a bug with the circular buffer"); + NS_ASSERTION(mWriteIndex >= 0, + "Circular Buffer index should never be negative"); + + int index = mWriteIndex++; + if (mWriteIndex == kMaxFrames) { + mWriteIndex = 0; + } + + mFrameTimestamps[index] = aTimestamp; + + if (CapturedFullInterval(aTimestamp)) { + PrintFPS(); + WriteFrameTimeStamps(); + mLastInterval = aTimestamp; + } +} + +double FPSCounter::AddFrameAndGetFps(TimeStamp aTimestamp) { + AddFrame(aTimestamp); + return GetFPS(aTimestamp); +} + +int FPSCounter::GetLatestReadIndex() { + if (mWriteIndex == 0) { + return kMaxFrames - 1; + } + + return mWriteIndex - 1; +} + +TimeStamp FPSCounter::GetLatestTimeStamp() { + TimeStamp timestamp = mFrameTimestamps[GetLatestReadIndex()]; + MOZ_ASSERT(!timestamp.IsNull(), "Cannot use null timestamps"); + return timestamp; +} + +// Returns true if we iterated over a full interval of data +bool FPSCounter::IteratedFullInterval(TimeStamp aTimestamp, double aDuration) { + MOZ_ASSERT(mIteratorIndex >= 0, "Cannot be negative"); + MOZ_ASSERT(mIteratorIndex < kMaxFrames, + "Iterator index cannot be greater than kMaxFrames"); + + TimeStamp currentStamp = mFrameTimestamps[mIteratorIndex]; + TimeDuration duration = aTimestamp - currentStamp; + return duration.ToSeconds() >= aDuration; +} + +void FPSCounter::ResetReverseIterator() { + mIteratorIndex = GetLatestReadIndex(); +} + +/*** + * Returns true if we have another timestamp that is valid and + * is within the given duration that we're interested in. + * Duration is in seconds + */ +bool FPSCounter::HasNext(TimeStamp aTimestamp, double aDuration) { + // Order of evaluation here has to stay the same + // otherwise IteratedFullInterval reads from mFrameTimestamps which cannot + // be null + return (mIteratorIndex != mWriteIndex) // Didn't loop around the buffer + && !mFrameTimestamps[mIteratorIndex].IsNull() // valid data + && !IteratedFullInterval(aTimestamp, aDuration); +} + +TimeStamp FPSCounter::GetNextTimeStamp() { + TimeStamp timestamp = mFrameTimestamps[mIteratorIndex--]; + MOZ_ASSERT(!timestamp.IsNull(), "Reading Invalid Timestamp Data"); + + if (mIteratorIndex == -1) { + mIteratorIndex = kMaxFrames - 1; + } + return timestamp; +} + +/** + * GetFPS calculates how many frames we've already composited from the current + * frame timestamp and we iterate from the latest timestamp we recorded, + * going back in time. When we hit a frame that is longer than the 1 second + * from the current composited frame, we return how many frames we've counted. + * Just a visualization: + * + * aTimestamp + * Frames: 1 2 3 4 5 6 7 8 9 10 11 12 + * Time --------------------------> + * + * GetFPS iterates from aTimestamp, which is the current frame. + * Then starting at frame 12, going back to frame 11, 10, etc, we calculate + * the duration of the recorded frame timestamp from aTimestamp. + * Once duration is greater than 1 second, we return how many frames + * we composited. + */ +double FPSCounter::GetFPS(TimeStamp aTimestamp) { + int frameCount = 0; + int duration = 1.0; // Only care about the last 1s of data + + ResetReverseIterator(); + while (HasNext(aTimestamp, duration)) { + GetNextTimeStamp(); + frameCount++; + } + + return frameCount; +} + +// Iterate the same way we do in GetFPS() +int FPSCounter::BuildHistogram(std::map& aFpsData) { + TimeStamp currentIntervalStart = GetLatestTimeStamp(); + TimeStamp currentTimeStamp = GetLatestTimeStamp(); + TimeStamp startTimeStamp = GetLatestTimeStamp(); + + int frameCount = 0; + int totalFrameCount = 0; + + ResetReverseIterator(); + while (HasNext(startTimeStamp)) { + currentTimeStamp = GetNextTimeStamp(); + TimeDuration interval = currentIntervalStart - currentTimeStamp; + + if (interval.ToSeconds() >= 1.0) { + currentIntervalStart = currentTimeStamp; + aFpsData[frameCount]++; + frameCount = 0; + } + + frameCount++; + totalFrameCount++; + } + + TimeDuration totalTime = currentIntervalStart - currentTimeStamp; + printf_stderr("Discarded %d frames over %f ms in histogram for %s\n", + frameCount, totalTime.ToMilliseconds(), mFPSName); + return totalFrameCount; +} + +// Iterate the same way we do in GetFPS() +void FPSCounter::WriteFrameTimeStamps(PRFileDesc* fd) { + const int bufferSize = 256; + char buffer[bufferSize]; + int writtenCount = SprintfLiteral(buffer, "FPS Data for: %s\n", mFPSName); + MOZ_ASSERT(writtenCount < bufferSize); + if (writtenCount >= bufferSize) { + return; + } + PR_Write(fd, buffer, writtenCount); + + ResetReverseIterator(); + TimeStamp startTimeStamp = GetLatestTimeStamp(); + + MOZ_ASSERT(HasNext(startTimeStamp)); + TimeStamp previousSample = GetNextTimeStamp(); + + MOZ_ASSERT(HasNext(startTimeStamp)); + TimeStamp nextTimeStamp = GetNextTimeStamp(); + + while (HasNext(startTimeStamp)) { + TimeDuration duration = previousSample - nextTimeStamp; + writtenCount = SprintfLiteral(buffer, "%f,\n", duration.ToMilliseconds()); + MOZ_ASSERT(writtenCount < bufferSize); + if (writtenCount >= bufferSize) { + continue; + } + PR_Write(fd, buffer, writtenCount); + + previousSample = nextTimeStamp; + nextTimeStamp = GetNextTimeStamp(); + } +} + +double FPSCounter::GetMean(std::map aHistogram) { + double average = 0.0; + double samples = 0.0; + + for (std::map::iterator iter = aHistogram.begin(); + iter != aHistogram.end(); ++iter) { + int fps = iter->first; + int count = iter->second; + + average += fps * count; + samples += count; + } + + return average / samples; +} + +double FPSCounter::GetStdDev(std::map aHistogram) { + double sumOfDifferences = 0; + double average = GetMean(aHistogram); + double samples = 0.0; + + for (std::map::iterator iter = aHistogram.begin(); + iter != aHistogram.end(); ++iter) { + int fps = iter->first; + int count = iter->second; + + double diff = ((double)fps) - average; + diff *= diff; + + for (int i = 0; i < count; i++) { + sumOfDifferences += diff; + } + samples += count; + } + + double stdDev = sumOfDifferences / samples; + return sqrt(stdDev); +} + +void FPSCounter::PrintFPS() { + if (!StaticPrefs::layers_acceleration_draw_fps_print_histogram()) { + return; + } + + std::map histogram; + int totalFrames = BuildHistogram(histogram); + + TimeDuration measurementInterval = + mFrameTimestamps[GetLatestReadIndex()] - mLastInterval; + printf_stderr("FPS for %s. Total Frames: %d Time Interval: %f seconds\n", + mFPSName, totalFrames, + measurementInterval.ToSecondsSigDigits()); + + PrintHistogram(histogram); +} + +void FPSCounter::PrintHistogram(std::map& aHistogram) { + if (aHistogram.empty()) { + return; + } + + int length = 0; + const int kBufferLength = 512; + int availableSpace = kBufferLength; + char buffer[kBufferLength]; + + for (std::map::iterator iter = aHistogram.begin(); + iter != aHistogram.end(); iter++) { + int fps = iter->first; + int count = iter->second; + + int lengthRequired = + snprintf(buffer + length, availableSpace, "FPS: %d = %d. ", fps, count); + // Ran out of buffer space. Oh well - just print what we have. + if (lengthRequired > availableSpace) { + break; + } + length += lengthRequired; + availableSpace -= lengthRequired; + } + + printf_stderr("%s\n", buffer); + printf_stderr("Mean: %f , std dev %f\n", GetMean(aHistogram), + GetStdDev(aHistogram)); +} + +// Write FPS timestamp data to a file only if +// draw-fps.write-to-file is true +nsresult FPSCounter::WriteFrameTimeStamps() { + if (!StaticPrefs::layers_acceleration_draw_fps_write_to_file()) { + return NS_OK; + } + + MOZ_ASSERT(mWriteIndex == 0); + + nsCOMPtr resultFile; + nsresult rv = + NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(resultFile)); + NS_ENSURE_SUCCESS(rv, rv); + + if (!strncmp(mFPSName, "Compositor", strlen(mFPSName))) { + resultFile->Append(u"fps.txt"_ns); + } else { + resultFile->Append(u"txn.txt"_ns); + } + + PRFileDesc* fd = nullptr; + int mode = 644; + int openFlags = PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE; + rv = resultFile->OpenNSPRFileDesc(openFlags, mode, &fd); + NS_ENSURE_SUCCESS(rv, rv); + + WriteFrameTimeStamps(fd); + PR_Close(fd); + + printf_stderr("Wrote FPS data to file: %s\n", + resultFile->HumanReadablePath().get()); + return NS_OK; +} + +} // end namespace layers +} // end namespace mozilla diff --git a/gfx/layers/composite/FPSCounter.h b/gfx/layers/composite/FPSCounter.h new file mode 100644 index 0000000000..c61b13a7b5 --- /dev/null +++ b/gfx/layers/composite/FPSCounter.h @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_opengl_FPSCounter_h_ +#define mozilla_layers_opengl_FPSCounter_h_ + +#include // for min +#include // for size_t +#include // for std::map +#include "GLDefs.h" // for GLuint +#include "mozilla/RefPtr.h" // for already_AddRefed, RefCounted +#include "mozilla/TimeStamp.h" // for TimeStamp, TimeDuration +#include "nsTArray.h" // for AutoTArray, nsTArray_Impl, etc +#include "prio.h" // for NSPR file i/o + +namespace mozilla { +namespace layers { + +class DataTextureSource; +class Compositor; + +// Dump the FPS histogram every 10 seconds or kMaxFrameFPS +const int kFpsDumpInterval = 10; + +// On desktop, we can have 240 hz monitors, so 10 seconds +// times 240 frames = 2400 +const int kMaxFrames = 2400; + +/** + * The FPSCounter tracks how often we composite or have a layer transaction. + * At each composite / layer transaction, we record the timestamp. + * After kFpsDumpInterval number of composites / transactions, we calculate + * the average and standard deviation of frames composited. We dump a histogram, + * which allows for more statistically significant measurements. We also dump + * absolute frame composite times to a file on the device. + * The FPS counters displayed on screen are based on how many frames we + * composited within the last ~1 second. The more accurate measurement is to + * grab the histogram from stderr or grab the FPS timestamp dumps written to + * file. + * + * To enable dumping to file, enable + * layers.acceleration.draw-fps.write-to-file pref. + + double AddFrameAndGetFps(TimeStamp aCurrentFrame) { + AddFrame(aCurrentFrame); + return EstimateFps(aCurrentFrame); + } + * To enable printing histogram data to logcat, + * enable layers.acceleration.draw-fps.print-histogram + * + * Use the HasNext(), GetNextTimeStamp() like an iterator to read the data, + * backwards in time. This abstracts away the mechanics of reading the data. + */ +class FPSCounter final { + public: + explicit FPSCounter(const char* aName); + ~FPSCounter(); + + void AddFrame(TimeStamp aTimestamp); + double AddFrameAndGetFps(TimeStamp aTimestamp); + double GetFPS(TimeStamp aTimestamp); + + private: + void Init(); + bool CapturedFullInterval(TimeStamp aTimestamp); + + // Used while iterating backwards over the data + void ResetReverseIterator(); + bool HasNext(TimeStamp aTimestamp, double aDuration = kFpsDumpInterval); + TimeStamp GetNextTimeStamp(); + int GetLatestReadIndex(); + TimeStamp GetLatestTimeStamp(); + void WriteFrameTimeStamps(PRFileDesc* fd); + bool IteratedFullInterval(TimeStamp aTimestamp, double aDuration); + + void PrintFPS(); + int BuildHistogram(std::map& aHistogram); + void PrintHistogram(std::map& aHistogram); + double GetMean(std::map aHistogram); + double GetStdDev(std::map aHistogram); + nsresult WriteFrameTimeStamps(); + + /*** + * mFrameTimestamps is a psuedo circular buffer + * Since we have a constant write time and don't + * read at an offset except our latest write + * we don't need an explicit read pointer. + */ + AutoTArray mFrameTimestamps; + int mWriteIndex; // points to next open write slot + int mIteratorIndex; // used only when iterating + const char* mFPSName; + TimeStamp mLastInterval; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_opengl_FPSCounter_h_ diff --git a/gfx/layers/composite/FontData.h b/gfx/layers/composite/FontData.h new file mode 100644 index 0000000000..aa315f2519 --- /dev/null +++ b/gfx/layers/composite/FontData.h @@ -0,0 +1,304 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This is explicitly not guarded as we want only 1 file to include this and +// it's good if things break if someone else does. + +namespace mozilla { +namespace layers { +namespace normal_font { +const unsigned char sFontPNG[] = { + 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, + 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, + 0x8, 0x0, 0x0, 0x0, 0x0, 0x79, 0x19, 0xf7, 0xba, 0x0, 0x0, 0xb, + 0xfe, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0xed, 0x5d, 0xd1, 0xb6, 0xe3, + 0x20, 0x8, 0x74, 0x72, 0xf2, 0xff, 0xbf, 0x3c, 0xfb, 0xd0, 0xbb, 0xbd, + 0x51, 0x8, 0x48, 0xd4, 0xc4, 0xde, 0xda, 0xb3, 0x67, 0x77, 0x6d, 0x13, + 0x63, 0x10, 0x81, 0x41, 0x40, 0x30, 0x7d, 0xf7, 0x67, 0x4b, 0x8b, 0x0, + 0xdf, 0xfd, 0xd9, 0x2f, 0xdc, 0x3, 0xc6, 0xda, 0x76, 0x67, 0x29, 0xa5, + 0x94, 0xb8, 0x38, 0x20, 0x42, 0x33, 0xc0, 0x68, 0xaa, 0x37, 0x64, 0x14, + 0x47, 0xc2, 0x67, 0x13, 0x80, 0x5, 0xbf, 0x91, 0xb0, 0x5f, 0x9f, 0x73, + 0x71, 0x40, 0x39, 0x61, 0x1c, 0xcd, 0xff, 0x39, 0x7d, 0x8, 0x1e, 0x29, + 0x72, 0x3f, 0x1, 0x90, 0x12, 0x8a, 0xf9, 0xc, 0x52, 0x20, 0x9f, 0x51, + 0x2, 0x8e, 0xd0, 0x99, 0x8d, 0x3, 0x7a, 0x2f, 0x2, 0x7a, 0x4, 0xa4, + 0x90, 0xba, 0x8, 0x68, 0x1, 0x28, 0x32, 0x1a, 0x87, 0x6f, 0x50, 0x71, + 0x7f, 0x5f, 0x29, 0xf, 0x85, 0xa3, 0x18, 0x5b, 0x10, 0xc, 0x10, 0x80, + 0xf2, 0x1d, 0x79, 0x4e, 0x5e, 0x8f, 0xfc, 0xaf, 0x1, 0xb0, 0x60, 0x50, + 0x4, 0x17, 0x40, 0xc6, 0xd3, 0xf4, 0xd5, 0x2e, 0x3a, 0xdb, 0x1, 0x5d, + 0x85, 0x78, 0x8a, 0xbd, 0x7e, 0x33, 0xc3, 0x24, 0xe4, 0x52, 0x70, 0xff, + 0xbc, 0xf5, 0x8f, 0xf0, 0x82, 0xe2, 0x5c, 0x1c, 0xd0, 0x9b, 0x83, 0x1c, + 0x7a, 0x50, 0xb4, 0x33, 0xa9, 0xf9, 0xf5, 0x58, 0x0, 0xb, 0xe, 0x2f, + 0x2, 0x2c, 0x2, 0x7c, 0x3d, 0x1, 0xea, 0x8c, 0xb9, 0xc, 0x4f, 0x16, + 0x70, 0x14, 0x21, 0xc5, 0x8e, 0x60, 0xfb, 0x1, 0xe, 0x80, 0xf5, 0xc2, + 0x52, 0x8c, 0xb6, 0x9, 0x52, 0xdc, 0xfb, 0xc6, 0x35, 0x4, 0x40, 0xa, + 0x81, 0x8d, 0x66, 0xbd, 0xce, 0xc9, 0x8, 0x80, 0x6f, 0x93, 0x1, 0x6d, + 0xef, 0xcf, 0xf1, 0xf4, 0xc2, 0xc0, 0xf9, 0xd9, 0xdb, 0xe7, 0x7f, 0x30, + 0x5, 0x50, 0x98, 0x6b, 0x48, 0xe8, 0x9, 0xaf, 0x77, 0x14, 0xf8, 0xbe, + 0xf5, 0x7d, 0xa0, 0xfa, 0xb, 0x18, 0xa2, 0x6f, 0x3e, 0x9e, 0xc2, 0x5c, + 0x65, 0x5f, 0xe3, 0x75, 0xa7, 0x30, 0x87, 0x3d, 0xa, 0xd8, 0x42, 0x8b, + 0x8a, 0x3f, 0x81, 0xb6, 0x7f, 0xa1, 0xa6, 0x8f, 0x80, 0x7, 0xa2, 0x5d, + 0x8, 0xd2, 0xd1, 0xfb, 0x7f, 0x4a, 0x4a, 0x6e, 0xfe, 0xc, 0x3c, 0xab, + 0xa6, 0x9e, 0x31, 0x84, 0x4c, 0x9e, 0xa3, 0xfb, 0x45, 0x47, 0x7f, 0xc5, + 0xc2, 0x2, 0xcb, 0x1f, 0xb0, 0x38, 0x60, 0x11, 0x60, 0x7a, 0x2, 0xe0, + 0x6f, 0x11, 0x0, 0x1e, 0x5a, 0x85, 0x3, 0x97, 0x5f, 0xde, 0x1, 0x94, + 0x5f, 0x9c, 0x93, 0x2b, 0xf2, 0x73, 0x77, 0x68, 0xd1, 0xce, 0x1, 0x60, + 0x6e, 0x3b, 0x82, 0x44, 0x26, 0x5c, 0xe1, 0x6d, 0x5f, 0xd3, 0xb2, 0x43, + 0x91, 0xb4, 0xcd, 0xb0, 0x27, 0x97, 0x0, 0x12, 0x4, 0x53, 0xc, 0x54, + 0x25, 0x44, 0x6f, 0x4d, 0x95, 0x77, 0xe7, 0x13, 0x80, 0xd2, 0x30, 0xf4, + 0x2d, 0x1f, 0xf0, 0xb4, 0x45, 0x47, 0x11, 0x13, 0xb7, 0xda, 0x49, 0x7b, + 0xf7, 0x29, 0x22, 0x12, 0xc1, 0x23, 0xa2, 0x3, 0x1b, 0xec, 0xd, 0x30, + 0xc7, 0x7f, 0x3f, 0x4b, 0x82, 0x9d, 0x18, 0xe0, 0x2, 0x1, 0xca, 0xdd, + 0xd9, 0x32, 0xe4, 0xe2, 0x35, 0xb6, 0xdf, 0xab, 0xd0, 0x86, 0xaf, 0x59, + 0xf0, 0x80, 0x4b, 0xcc, 0xe0, 0xd6, 0xe1, 0x7e, 0x41, 0x4, 0xb8, 0xf, + 0x11, 0xf8, 0x9a, 0x37, 0x1a, 0x9c, 0xc1, 0x27, 0x6d, 0xd1, 0xe, 0x5e, + 0x22, 0x80, 0x21, 0x99, 0xcc, 0x89, 0xc, 0xee, 0x72, 0x28, 0xbe, 0x1d, + 0x10, 0x17, 0xd3, 0x60, 0x4a, 0x7c, 0x7, 0xee, 0x10, 0x0, 0x81, 0x71, + 0x73, 0x1c, 0xdd, 0x86, 0xe8, 0xf, 0x86, 0x1a, 0xfb, 0x50, 0xc8, 0x77, + 0xe3, 0xe3, 0xb7, 0xfb, 0x57, 0xdd, 0x5c, 0x78, 0x76, 0x86, 0xd5, 0x99, + 0x8d, 0xe1, 0xee, 0x1, 0x2d, 0x7f, 0xc0, 0x82, 0xc3, 0x8b, 0x0, 0xdf, + 0xfd, 0xd9, 0x15, 0xcb, 0x1a, 0x22, 0xb6, 0x94, 0x56, 0xbb, 0x94, 0x62, + 0xa5, 0x62, 0x80, 0x68, 0x9a, 0xbf, 0xf7, 0xdd, 0xf9, 0xd2, 0xed, 0x82, + 0x2c, 0x52, 0x54, 0x40, 0x15, 0xc8, 0xb7, 0xcb, 0xd0, 0x88, 0xb0, 0xc6, + 0x6d, 0x6c, 0x5c, 0x5e, 0xcf, 0xe2, 0xe, 0xbf, 0xff, 0x36, 0x59, 0xe, + 0x7b, 0x3c, 0x9b, 0x7b, 0x3, 0x3b, 0x68, 0xfd, 0x96, 0x94, 0x92, 0x8a, + 0x0, 0x12, 0x33, 0x5c, 0xbc, 0xb4, 0x42, 0xe9, 0x81, 0xa1, 0xf0, 0xb, + 0x96, 0x70, 0x4f, 0x2e, 0xa9, 0xd4, 0x64, 0xeb, 0x79, 0x33, 0x80, 0xa4, + 0xf0, 0x63, 0xc6, 0x72, 0x16, 0x76, 0xdb, 0x2f, 0x30, 0x98, 0x83, 0x5, + 0x29, 0x4d, 0x1b, 0x7b, 0x86, 0xc2, 0xd6, 0x7d, 0x88, 0x63, 0x93, 0x8c, + 0xa7, 0x6, 0x63, 0x4, 0x90, 0x8f, 0x28, 0xd7, 0xac, 0x39, 0x0, 0xfa, + 0xbd, 0x83, 0x85, 0x43, 0x25, 0x6, 0xc6, 0xfc, 0x68, 0xf1, 0x73, 0x7, + 0xd5, 0xde, 0x24, 0x60, 0xfe, 0xcf, 0x57, 0xc8, 0x9, 0x1, 0xc6, 0x58, + 0xde, 0x5f, 0x22, 0xd9, 0xe3, 0xc5, 0x12, 0x40, 0x63, 0xb0, 0xb4, 0x97, + 0x14, 0xc7, 0xce, 0xc6, 0xfc, 0x85, 0xfb, 0x33, 0x96, 0xa4, 0xdb, 0x9f, + 0x92, 0xae, 0x60, 0xe9, 0x69, 0x57, 0x6f, 0x2b, 0x5a, 0xd1, 0x6e, 0x33, + 0xa5, 0xa6, 0xeb, 0x5b, 0xad, 0x84, 0x47, 0xc1, 0xd7, 0x74, 0xf8, 0x6d, + 0xfb, 0xf2, 0xf7, 0x5f, 0x70, 0x78, 0xa1, 0xc1, 0x45, 0x80, 0x45, 0x80, + 0xaf, 0xf7, 0x7, 0x28, 0xd1, 0x9c, 0xb4, 0xc0, 0x45, 0x91, 0x4c, 0xaa, + 0xda, 0xd, 0x87, 0x6f, 0xdc, 0xfb, 0x99, 0x3f, 0x12, 0x34, 0xf1, 0xbb, + 0x7b, 0xfd, 0xff, 0x76, 0xfe, 0x6f, 0xb9, 0xbd, 0xc8, 0x23, 0x1, 0xde, + 0x9b, 0x78, 0xbf, 0xf0, 0x4e, 0xdb, 0xeb, 0xcb, 0x9e, 0x50, 0x98, 0x5e, + 0x25, 0xe0, 0x37, 0xd1, 0x1b, 0x63, 0xda, 0x47, 0xcb, 0x2c, 0xb5, 0x6e, + 0xff, 0x79, 0x63, 0xdb, 0x90, 0x7a, 0x7f, 0xb9, 0xfd, 0xfe, 0xbf, 0x12, + 0x82, 0xbc, 0x2e, 0xf7, 0xf6, 0xb4, 0x83, 0xf7, 0x8f, 0x54, 0xde, 0x25, + 0x5a, 0x67, 0xf6, 0xe5, 0xf6, 0x42, 0x63, 0xc7, 0x98, 0x87, 0x32, 0xe4, + 0xe3, 0xd3, 0x6c, 0x21, 0x22, 0x42, 0xa2, 0xfd, 0x8d, 0x48, 0x6d, 0x8f, + 0x4c, 0x4, 0xed, 0x29, 0x42, 0x41, 0x1, 0xfc, 0xbc, 0x84, 0xfe, 0xaf, + 0xdc, 0x60, 0xee, 0xcd, 0xee, 0xa, 0x53, 0x6a, 0x3e, 0xbe, 0xd0, 0x9a, + 0xad, 0xf0, 0x50, 0x1c, 0x1e, 0xc2, 0xe0, 0x4b, 0x81, 0x2e, 0xb, 0x50, + 0x44, 0x30, 0xb8, 0x70, 0x18, 0x85, 0x2c, 0x9c, 0xd8, 0x44, 0x66, 0xd7, + 0xfd, 0xf6, 0x2d, 0xfd, 0x6c, 0x60, 0x57, 0xf7, 0x49, 0x28, 0x33, 0x16, + 0x61, 0x10, 0x84, 0x6e, 0x40, 0x8d, 0x60, 0xf3, 0x78, 0xfe, 0x78, 0x79, + 0xf1, 0xfc, 0xfd, 0xcd, 0x84, 0xb9, 0xfa, 0x3c, 0xf4, 0x51, 0xae, 0x69, + 0xc2, 0x5e, 0xe3, 0x5e, 0xcd, 0x8, 0x22, 0x22, 0x53, 0xa8, 0x6f, 0x13, + 0x14, 0xe3, 0x63, 0x15, 0xd5, 0xb5, 0xe7, 0x1f, 0x63, 0x79, 0xbe, 0xd2, + 0x12, 0xfc, 0x55, 0x0, 0x5f, 0x8a, 0x8b, 0x97, 0x3f, 0x60, 0xa1, 0xc1, + 0x45, 0x80, 0x45, 0x80, 0x2f, 0xf7, 0x7, 0x54, 0xe1, 0xfd, 0xf3, 0x8d, + 0x82, 0x12, 0xfe, 0x97, 0x96, 0xa4, 0x5, 0xcf, 0x7f, 0x77, 0xc5, 0x60, + 0xf8, 0x7, 0xe4, 0xe3, 0x2d, 0xbf, 0x3e, 0x82, 0x7b, 0xd1, 0x7b, 0x1d, + 0xde, 0x17, 0xe, 0x88, 0x77, 0xdb, 0xdb, 0xcd, 0x67, 0x79, 0x7d, 0xd1, + 0x13, 0xe5, 0xde, 0x20, 0xa5, 0xbb, 0xc1, 0xc1, 0x16, 0x96, 0xf7, 0xc0, + 0x89, 0xd3, 0x3d, 0x8b, 0xf, 0xf0, 0xf3, 0x67, 0xdb, 0x94, 0x2f, 0x8e, + 0x86, 0x99, 0x48, 0x99, 0x8, 0xc4, 0x4e, 0xb6, 0x22, 0xf7, 0xf1, 0x32, + 0x20, 0x6, 0xcf, 0x47, 0x18, 0x3a, 0x74, 0x6a, 0x89, 0x79, 0x78, 0xbf, + 0xf8, 0x9d, 0x68, 0xe3, 0x80, 0x17, 0x58, 0x3d, 0x40, 0x56, 0x5, 0xba, + 0x34, 0x6, 0xcf, 0xc6, 0x84, 0x60, 0x45, 0x3d, 0x38, 0xb0, 0xc5, 0x3f, + 0x50, 0xb1, 0x1e, 0x22, 0xc1, 0xf7, 0x21, 0x77, 0x87, 0xcc, 0x37, 0xb8, + 0xb4, 0x4, 0x1a, 0x53, 0x1e, 0x30, 0xb3, 0xc1, 0xbd, 0x55, 0xe1, 0xf5, + 0x23, 0x5, 0xd0, 0xba, 0xe6, 0xf9, 0x2b, 0xfd, 0x6b, 0x16, 0x29, 0x38, + 0x74, 0x7a, 0xf6, 0x3a, 0xbc, 0x7e, 0xe0, 0x33, 0x57, 0x6, 0x8, 0x4, + 0x4f, 0xd7, 0x89, 0x6d, 0x5f, 0x31, 0x14, 0xaa, 0xea, 0x4f, 0x9e, 0x19, + 0x23, 0x82, 0xad, 0x17, 0x54, 0xc8, 0x80, 0xbe, 0x19, 0x1e, 0x93, 0x9b, + 0xc2, 0x3, 0xec, 0x9c, 0xc1, 0x2c, 0x60, 0x8e, 0xae, 0x32, 0x81, 0x86, + 0xcb, 0x21, 0xb2, 0xd0, 0xe0, 0x22, 0xc0, 0xf, 0x1, 0xf0, 0xed, 0x4, + 0xf0, 0xec, 0x88, 0xba, 0xd2, 0x4f, 0xd7, 0x3b, 0x40, 0xe7, 0x76, 0x7c, + 0x9, 0xc, 0x16, 0x83, 0x51, 0x31, 0xdb, 0x23, 0x3c, 0xbf, 0x4d, 0x6, + 0x88, 0x3, 0xb, 0x0, 0xf3, 0x77, 0x79, 0x79, 0x79, 0x9e, 0x81, 0xe8, + 0xce, 0xea, 0xa0, 0x66, 0x86, 0x4d, 0xd3, 0x39, 0x1, 0xc7, 0x3e, 0x61, + 0x8f, 0x7f, 0x57, 0x67, 0xcc, 0xa, 0x77, 0x57, 0x32, 0x3c, 0x60, 0xdc, + 0x4f, 0x25, 0x40, 0x43, 0x44, 0x2c, 0x4, 0xb9, 0xc4, 0x45, 0x8f, 0x38, + 0x42, 0x58, 0x7b, 0xfc, 0x15, 0xc9, 0xd3, 0x74, 0xd9, 0xb4, 0xad, 0xd4, + 0x35, 0x3b, 0x2f, 0x2a, 0x25, 0xe1, 0x3e, 0x66, 0x9, 0xb6, 0xaa, 0x85, + 0xd1, 0x6a, 0x65, 0x78, 0x41, 0x45, 0x36, 0x3e, 0x63, 0xf8, 0x89, 0x19, + 0x5d, 0xcd, 0xd7, 0x8a, 0xa2, 0xaa, 0x70, 0x27, 0x1, 0x8d, 0x9a, 0xef, + 0x82, 0x8b, 0x65, 0x20, 0x7, 0x10, 0x5, 0x8d, 0x8b, 0x9c, 0x94, 0x12, + 0xee, 0x97, 0x29, 0x2e, 0x74, 0xf2, 0xfe, 0xa2, 0x29, 0x31, 0xe7, 0x3e, + 0x95, 0xeb, 0xd4, 0xe3, 0xd3, 0xc8, 0x1f, 0x1c, 0x73, 0xed, 0x47, 0x60, + 0x81, 0xc9, 0x4c, 0xef, 0x7, 0x38, 0x20, 0x96, 0x1a, 0xb, 0xfe, 0x39, + 0x2, 0x2c, 0x38, 0xbc, 0x8, 0xb0, 0x8, 0xf0, 0xa7, 0x8, 0x70, 0xbb, + 0x54, 0xb7, 0x4f, 0x33, 0x88, 0xfa, 0xb3, 0x3f, 0xef, 0x98, 0x1d, 0x17, + 0xa, 0x79, 0xa5, 0xc6, 0x52, 0x51, 0x4e, 0x4f, 0xc1, 0xcb, 0x79, 0x81, + 0x3, 0x17, 0xef, 0xcb, 0x19, 0xc1, 0x69, 0xf3, 0xdd, 0x86, 0x71, 0x7d, + 0x51, 0x8f, 0x12, 0x23, 0xeb, 0x50, 0xed, 0x1a, 0x5e, 0x36, 0x2b, 0xe, + 0x78, 0x78, 0xff, 0x82, 0x59, 0xb, 0xaf, 0x58, 0x39, 0xda, 0xd6, 0xa8, + 0xc5, 0x15, 0xaa, 0x4b, 0x8c, 0x41, 0x2a, 0x6, 0x4d, 0x71, 0x58, 0xfb, + 0xc5, 0xda, 0xde, 0x6a, 0x13, 0x8d, 0x41, 0x5a, 0x15, 0x26, 0xb6, 0xb, + 0x6f, 0x0, 0x44, 0x5f, 0xb8, 0xd, 0xd9, 0x4, 0x93, 0x9d, 0x29, 0x18, + 0xd8, 0x3a, 0xd3, 0xf2, 0x82, 0x16, 0xf0, 0xce, 0xf8, 0x8c, 0x4f, 0x11, + 0x4c, 0x8a, 0xd9, 0x45, 0x57, 0x5b, 0x8d, 0xdb, 0x2d, 0x5c, 0xc8, 0x16, + 0x35, 0xab, 0xce, 0x99, 0xf0, 0x88, 0xe0, 0xc0, 0xf0, 0x9a, 0xa2, 0x12, + 0xef, 0x9b, 0x54, 0xd6, 0xfc, 0x1, 0xc9, 0xf4, 0xf, 0xb8, 0x27, 0x82, + 0xd2, 0x3c, 0x42, 0x62, 0x6c, 0x7c, 0xc0, 0x9e, 0x3c, 0x17, 0xa7, 0xeb, + 0x24, 0x55, 0xda, 0xf4, 0xae, 0xe7, 0x69, 0xb3, 0xa7, 0xc0, 0xad, 0x61, + 0xcf, 0x9, 0xd, 0xa1, 0xb1, 0x3c, 0xcf, 0xd9, 0x2d, 0x41, 0xb7, 0x1e, + 0xb, 0xbd, 0xf8, 0x0, 0x4e, 0x44, 0xee, 0x85, 0x6, 0x3b, 0x31, 0x5, + 0xc6, 0xdd, 0xff, 0x11, 0x1c, 0x30, 0xb2, 0x32, 0xcf, 0xf3, 0x4, 0x78, + 0x78, 0x4, 0xfb, 0x15, 0x3d, 0x1b, 0x14, 0x34, 0x66, 0x21, 0x20, 0x50, + 0xad, 0x54, 0x86, 0xf3, 0x2, 0x5, 0xe7, 0x8a, 0x8d, 0xa7, 0xbf, 0x5a, + 0xa9, 0xb3, 0xd1, 0x74, 0xfe, 0xce, 0x73, 0x46, 0x59, 0x6a, 0x2e, 0x87, + 0xa7, 0x6e, 0x2a, 0xaf, 0x53, 0x80, 0xd0, 0x41, 0x93, 0xfb, 0x18, 0x6b, + 0x23, 0x9f, 0x81, 0xb2, 0xd6, 0x17, 0x12, 0xa3, 0x24, 0x66, 0xcb, 0x74, + 0x98, 0xd9, 0xe3, 0xf1, 0xc3, 0xdb, 0x82, 0xb1, 0xcf, 0x32, 0xd8, 0x7c, + 0xaa, 0x52, 0xc8, 0x7b, 0xdc, 0xa1, 0x31, 0xb6, 0x54, 0xfa, 0xeb, 0x78, + 0x2, 0xbc, 0x32, 0xec, 0x99, 0x64, 0x6c, 0x32, 0x4e, 0x57, 0x3a, 0xaf, + 0x50, 0xec, 0xa, 0x7, 0x8c, 0x3e, 0x81, 0x13, 0xc4, 0xfb, 0xaf, 0x54, + 0xe6, 0x27, 0x9c, 0x3c, 0xfd, 0xb2, 0xa2, 0xdb, 0x98, 0xc8, 0x4f, 0xb2, + 0x6, 0x7b, 0x57, 0xf7, 0xd8, 0xaa, 0x41, 0x7e, 0x37, 0xa1, 0x8, 0xc7, + 0x75, 0xad, 0xa0, 0x61, 0x8b, 0x2, 0x6e, 0x98, 0x9f, 0x8b, 0xc6, 0x11, + 0xe4, 0x20, 0xe1, 0xb0, 0x53, 0x3c, 0x78, 0x99, 0x6a, 0x8a, 0x15, 0x4, + 0x7c, 0xa9, 0xe5, 0x5f, 0x19, 0x30, 0xdc, 0xe, 0xe8, 0xae, 0x2, 0x59, + 0x1, 0xf2, 0x3d, 0xbc, 0x7a, 0x2c, 0xe9, 0xd0, 0xe8, 0xa2, 0x65, 0x13, + 0x83, 0x5c, 0x60, 0x80, 0xf, 0xfb, 0x80, 0xe3, 0xef, 0xf8, 0xd3, 0x40, + 0x6b, 0xf9, 0x3, 0x16, 0x1, 0x3e, 0x90, 0xeb, 0x8f, 0xba, 0x75, 0x9b, + 0x60, 0x38, 0x68, 0xbc, 0x20, 0xfa, 0x80, 0xcc, 0x6d, 0xed, 0x9f, 0x3d, + 0xee, 0x9d, 0x3f, 0xd8, 0x38, 0x3e, 0x69, 0x46, 0xbc, 0x77, 0xa7, 0xd1, + 0x7, 0x7b, 0x50, 0x3b, 0xc, 0x0, 0xe7, 0x4, 0x70, 0x2d, 0xa7, 0xbe, + 0x72, 0x53, 0x3f, 0xc4, 0x17, 0xae, 0x7d, 0x13, 0xb5, 0x2c, 0x72, 0xcb, + 0x11, 0x2c, 0xfc, 0x1, 0xd7, 0xd5, 0xc4, 0x18, 0x3b, 0x80, 0xe0, 0x7b, + 0xcb, 0xd0, 0x3, 0x6b, 0x48, 0x66, 0x78, 0xaa, 0xb0, 0x4, 0xf3, 0xfd, + 0x78, 0xf7, 0x7c, 0x1, 0x3a, 0x33, 0x46, 0xaf, 0xde, 0x7f, 0xf, 0x45, + 0x6d, 0x55, 0xb3, 0x7f, 0x7b, 0xb4, 0xc, 0x9c, 0x88, 0x3b, 0xb5, 0x0, + 0xba, 0x40, 0x2b, 0x9e, 0x1e, 0xd2, 0x78, 0x1, 0xe, 0xca, 0xd3, 0xef, + 0x52, 0xaa, 0x5e, 0x2, 0x1d, 0xe8, 0x91, 0xbd, 0x82, 0x5f, 0xe, 0x84, + 0x87, 0x3f, 0xa9, 0x36, 0x17, 0xfb, 0x72, 0xfe, 0xf9, 0x3e, 0x60, 0xf9, + 0x9a, 0xf9, 0x1a, 0x57, 0x8a, 0xe7, 0x57, 0x84, 0x48, 0x5c, 0x77, 0x88, + 0xc, 0x10, 0x60, 0x1c, 0xc1, 0x42, 0xfe, 0xfb, 0xe0, 0xaa, 0x3f, 0xe0, + 0x3, 0xc, 0xb7, 0xae, 0x5a, 0xc0, 0x96, 0xa9, 0xb, 0xb, 0x2c, 0x2, + 0x2c, 0x2, 0x2c, 0x2, 0x2c, 0x2, 0x7c, 0xd3, 0x67, 0xff, 0xb6, 0x17, + 0xbe, 0x52, 0x43, 0xc4, 0x2f, 0xd9, 0x27, 0x2e, 0x86, 0xb1, 0x97, 0x57, + 0xe6, 0xed, 0x81, 0x96, 0xe2, 0x6e, 0x87, 0x56, 0xf6, 0xc6, 0x40, 0xcd, + 0xe6, 0x72, 0x92, 0x25, 0xc, 0x3, 0xdb, 0xf5, 0xce, 0x79, 0x7f, 0x17, + 0x28, 0x78, 0x5, 0x9, 0x9d, 0x76, 0xe2, 0x9f, 0x3c, 0xd, 0x27, 0x7d, + 0x1c, 0x45, 0x82, 0x41, 0x4d, 0xe9, 0x30, 0xfb, 0xfd, 0x81, 0x63, 0xb0, + 0x69, 0x23, 0xb4, 0x14, 0xfd, 0xb9, 0x4, 0xa0, 0x8e, 0xce, 0x2c, 0xe0, + 0x1a, 0xf2, 0x89, 0xb8, 0xd1, 0xe6, 0xc8, 0xa2, 0xb1, 0x65, 0xd7, 0x71, + 0xbc, 0x6d, 0x45, 0x77, 0xef, 0xe1, 0x45, 0x81, 0x82, 0x27, 0x50, 0x54, + 0x7b, 0x87, 0x13, 0x41, 0x51, 0x75, 0xa4, 0x26, 0x4f, 0xfc, 0x37, 0x49, + 0xd6, 0x5b, 0xa8, 0xa8, 0x8b, 0xdd, 0x78, 0xe0, 0xa2, 0xe5, 0x4e, 0x50, + 0xc7, 0xe, 0xb6, 0xe1, 0xdf, 0x54, 0xd4, 0x14, 0x35, 0xdf, 0x5f, 0x1c, + 0x4f, 0xa0, 0xbc, 0x2d, 0xfd, 0x18, 0x21, 0x1b, 0xdf, 0x9b, 0x24, 0x64, + 0x99, 0x72, 0xe3, 0x48, 0x6e, 0x2f, 0xbb, 0xdc, 0xb9, 0xc0, 0x5d, 0x71, + 0x3c, 0x19, 0x63, 0xb5, 0x16, 0x50, 0x17, 0x9d, 0xa1, 0x5, 0xf0, 0xa, + 0x6a, 0x39, 0xed, 0x41, 0xf5, 0xdf, 0x44, 0xb4, 0x80, 0x26, 0xd6, 0x58, + 0xdf, 0xa1, 0xd3, 0x5f, 0xd, 0x1, 0x9c, 0x73, 0x85, 0x9d, 0x3e, 0xa3, + 0x6a, 0x30, 0x35, 0xd6, 0x2c, 0x6d, 0xb6, 0x3, 0x7a, 0x7b, 0x8, 0xbc, + 0x83, 0x93, 0xbe, 0xcf, 0x14, 0x9e, 0xcb, 0x5, 0xb3, 0x4d, 0x3e, 0xbe, + 0xf1, 0xd8, 0x60, 0xb9, 0xc4, 0x16, 0x1, 0x16, 0x1, 0xfa, 0x20, 0xec, + 0xcf, 0x25, 0x80, 0x13, 0xb6, 0xe8, 0xd4, 0x92, 0xab, 0x88, 0x33, 0xb, + 0xa6, 0x7f, 0x47, 0xb, 0x2, 0x94, 0xf9, 0xe6, 0x79, 0xfb, 0xff, 0xb3, + 0x71, 0x2a, 0x4, 0xa5, 0x61, 0xe5, 0x5, 0x36, 0x22, 0x18, 0x5c, 0x1e, + 0xb, 0x71, 0x80, 0x63, 0xfb, 0xeb, 0xd7, 0x83, 0x67, 0x6d, 0xc7, 0xf4, + 0xaa, 0x58, 0x2, 0x22, 0x82, 0x83, 0xee, 0x84, 0xb4, 0xac, 0xa, 0x36, + 0x5e, 0x2f, 0xec, 0x2e, 0xd5, 0xb6, 0x3f, 0x10, 0x80, 0x88, 0x15, 0x77, + 0xe7, 0xc3, 0x87, 0x11, 0xaa, 0xee, 0x0, 0x83, 0x49, 0x49, 0x24, 0x3, + 0xa0, 0x8a, 0x50, 0x59, 0x14, 0xc6, 0xaa, 0x80, 0xff, 0xd1, 0x39, 0x8a, + 0xd2, 0xab, 0xf0, 0x26, 0x88, 0x3c, 0xc9, 0x12, 0xe, 0xe2, 0x10, 0x5d, + 0xfc, 0xdb, 0x3e, 0x34, 0xff, 0x3, 0x66, 0xdd, 0xc9, 0xb6, 0x2b, 0x1c, + 0x85, 0xce, 0xdb, 0xdb, 0x8, 0x49, 0x10, 0xf7, 0x81, 0x5, 0x5, 0xf8, + 0x23, 0xc9, 0x90, 0xb5, 0x7f, 0x4f, 0x8d, 0x4b, 0xc5, 0xef, 0x8f, 0xdb, + 0x1, 0xcd, 0xa6, 0x67, 0x9d, 0xd4, 0x36, 0xfc, 0xb2, 0x59, 0x73, 0x1b, + 0xb4, 0x4c, 0x87, 0x1a, 0xef, 0x9a, 0x58, 0xb3, 0x95, 0x33, 0x4e, 0x4f, + 0xa1, 0xdd, 0x4f, 0x84, 0x38, 0x5b, 0x5e, 0x9f, 0x23, 0x21, 0x6, 0xb5, + 0xb2, 0x2e, 0x2c, 0xce, 0xd9, 0xcd, 0x24, 0x57, 0xf9, 0xfb, 0x60, 0x30, + 0xa4, 0x79, 0x18, 0xa6, 0xca, 0x5d, 0xcd, 0x17, 0xc8, 0xf3, 0x5b, 0x63, + 0xb7, 0xe7, 0x1b, 0xe4, 0x3e, 0xc7, 0x3b, 0x38, 0x60, 0x6a, 0x40, 0xbe, + 0xfc, 0x1, 0xaa, 0xd4, 0xe, 0xc6, 0x4b, 0x77, 0x57, 0x1a, 0x5e, 0xd5, + 0xda, 0x5e, 0x37, 0xff, 0x27, 0x0, 0x1d, 0x1d, 0x22, 0x4c, 0x19, 0x11, + 0x7f, 0x8d, 0xc8, 0x33, 0x5d, 0x5b, 0xd1, 0x7b, 0x7e, 0x76, 0x6c, 0x73, + 0xcb, 0xcd, 0x83, 0x96, 0xc0, 0x47, 0xe5, 0x51, 0xa9, 0xc9, 0xf3, 0xdd, + 0xdb, 0xb9, 0x11, 0xa8, 0x9d, 0x67, 0x78, 0x7e, 0x80, 0xa1, 0xdb, 0x6e, + 0x1b, 0xdc, 0xae, 0xb1, 0x74, 0xe7, 0x76, 0xa1, 0xea, 0xd4, 0xf3, 0xc, + 0x3, 0x60, 0xa0, 0x2f, 0x77, 0x6d, 0x77, 0x71, 0x59, 0xd1, 0x9a, 0x66, + 0x8d, 0x6c, 0xf2, 0xb0, 0xaf, 0xde, 0xed, 0xb9, 0x3f, 0xfb, 0x5d, 0x62, + 0x26, 0x55, 0x7, 0xf3, 0x46, 0xdb, 0xed, 0x4, 0xe8, 0x7d, 0xcc, 0x89, + 0xd6, 0xe6, 0x75, 0x7, 0xc0, 0x58, 0x19, 0xb0, 0xcb, 0x53, 0x5e, 0x47, + 0xb4, 0xe7, 0x5d, 0x12, 0xf, 0x38, 0x44, 0x90, 0x66, 0xa2, 0xc8, 0xaf, + 0xef, 0x6c, 0xe8, 0xbf, 0x98, 0xd6, 0xe, 0x78, 0xc4, 0x66, 0x9b, 0xc9, + 0x50, 0x7c, 0x64, 0x6f, 0x70, 0x26, 0x2d, 0xf9, 0x8, 0x1, 0x66, 0xe2, + 0x80, 0xe5, 0xf, 0xd0, 0x25, 0xa3, 0x3, 0x6f, 0xe1, 0x41, 0xf0, 0xd6, + 0x7c, 0xef, 0x26, 0x88, 0x1f, 0xd2, 0x31, 0x5b, 0xd, 0x9c, 0x75, 0xfc, + 0x5, 0x25, 0xe6, 0x76, 0xfd, 0x5, 0x51, 0x19, 0x11, 0x84, 0xf8, 0x33, + 0x9d, 0xe1, 0xf2, 0x9, 0x58, 0x40, 0x98, 0xda, 0x22, 0xb0, 0x50, 0xc4, + 0xcb, 0x37, 0xb6, 0x35, 0x86, 0x65, 0x51, 0x75, 0xa8, 0xb7, 0x65, 0x7a, + 0x3a, 0x9c, 0x3d, 0xa9, 0xdb, 0xe9, 0x96, 0xcf, 0xa4, 0xb5, 0x5d, 0xe5, + 0x93, 0x69, 0xd1, 0xb1, 0x8c, 0x3c, 0x6e, 0x93, 0xab, 0x4e, 0xdd, 0xcc, + 0x3c, 0xc4, 0xef, 0xb7, 0xb6, 0xb, 0x21, 0xf5, 0x33, 0xa0, 0x7e, 0xa6, + 0x41, 0x99, 0xb0, 0x60, 0x47, 0xa8, 0xdc, 0xbf, 0x31, 0xa2, 0x9d, 0x27, + 0x30, 0xb6, 0x40, 0xdf, 0x99, 0x53, 0xe6, 0xc5, 0x1, 0x4a, 0x69, 0xb3, + 0xc1, 0x86, 0xda, 0xf0, 0xf7, 0xf, 0xd5, 0xeb, 0xdf, 0xa8, 0x64, 0x7b, + 0x73, 0xf0, 0xf6, 0xee, 0xd3, 0x96, 0xf0, 0xf1, 0x64, 0xf1, 0xfb, 0x97, + 0x80, 0xac, 0xae, 0x3f, 0x58, 0x15, 0x2b, 0x1e, 0xa4, 0xc3, 0xf6, 0xf5, + 0xa6, 0x8e, 0x41, 0xca, 0xc4, 0xdf, 0xb, 0x9a, 0xdb, 0x49, 0xd4, 0x34, + 0x91, 0xd7, 0x67, 0x87, 0x98, 0xc4, 0xda, 0x72, 0xbe, 0x69, 0x95, 0x8c, + 0x4, 0x9f, 0xb6, 0x3, 0x5e, 0x23, 0xb0, 0xc2, 0x90, 0xa2, 0x6d, 0xbb, + 0xe0, 0xc4, 0xab, 0x82, 0xe2, 0x3a, 0x7c, 0xfd, 0x51, 0x38, 0xbc, 0x8, + 0xb0, 0x8, 0xb0, 0x8, 0xf0, 0xa7, 0x9, 0x80, 0x4f, 0x23, 0x40, 0xe7, + 0x0, 0x11, 0xc4, 0x83, 0x63, 0x1b, 0x1c, 0x40, 0x3d, 0x22, 0x44, 0x9a, + 0x3, 0x44, 0x10, 0x33, 0xc5, 0x9b, 0x1d, 0x40, 0xb0, 0xcc, 0xf6, 0xd0, + 0xdd, 0xcb, 0xe, 0xf8, 0x39, 0x61, 0xa2, 0xc9, 0xf0, 0xaa, 0x68, 0x67, + 0xb1, 0x79, 0x9d, 0xfb, 0x6f, 0x8e, 0x10, 0xd1, 0x22, 0x8b, 0x7b, 0xb7, + 0x8f, 0x5f, 0xf6, 0xee, 0xbf, 0x59, 0x8, 0xb6, 0xee, 0x4e, 0x5f, 0xd9, + 0xcd, 0xe6, 0x6c, 0x4b, 0xc0, 0x11, 0xfa, 0x1d, 0xda, 0xa8, 0x97, 0xe3, + 0xd1, 0x76, 0x2b, 0x1, 0x40, 0xe9, 0xa3, 0xeb, 0xdd, 0xb6, 0xe1, 0x6a, + 0x7, 0x96, 0x6a, 0x34, 0x84, 0x46, 0xf3, 0x80, 0x60, 0x2, 0xcc, 0xc3, + 0x4, 0xa0, 0x88, 0xef, 0xef, 0xdf, 0x4e, 0x19, 0x4, 0x9f, 0x4b, 0xb, + 0x3c, 0x61, 0x7, 0x4c, 0x65, 0x7b, 0x3c, 0x1, 0x86, 0xa6, 0xa, 0xa2, + 0x7b, 0x82, 0x0, 0x53, 0x71, 0xc0, 0x72, 0x89, 0x2d, 0x7f, 0xc0, 0x22, + 0x80, 0x62, 0x9a, 0xc5, 0x2, 0x44, 0x7a, 0x27, 0x94, 0xdc, 0x19, 0x20, + 0xf2, 0x3a, 0x78, 0xd9, 0xc6, 0xfb, 0xbd, 0xfd, 0x5, 0x63, 0xfd, 0x3, + 0xe2, 0x7a, 0x67, 0x7a, 0x3a, 0x63, 0xab, 0x9, 0x5, 0xaf, 0xf3, 0xb4, + 0x4d, 0x46, 0xf8, 0x24, 0xa7, 0x66, 0x44, 0x6b, 0x5b, 0x24, 0x7e, 0xe6, + 0x17, 0x88, 0xf8, 0x81, 0x58, 0x5b, 0xa9, 0x17, 0x21, 0xba, 0x3f, 0xfe, + 0xfe, 0x8a, 0x16, 0x2f, 0xf7, 0x92, 0x46, 0xfa, 0x7, 0xb4, 0x19, 0xba, + 0x3f, 0xd9, 0xd8, 0x40, 0x83, 0xe9, 0x86, 0xbd, 0x5a, 0xc5, 0x67, 0xd4, + 0xcf, 0xcc, 0xb4, 0xb, 0x0, 0x94, 0xbf, 0xdf, 0xbe, 0x3d, 0xae, 0xd6, + 0x96, 0x7b, 0xd0, 0x36, 0x9e, 0xc0, 0xe, 0xe8, 0x1e, 0x1f, 0x62, 0xf7, + 0x58, 0xfc, 0xfe, 0x8, 0x16, 0xf0, 0x78, 0xa2, 0xaf, 0x7b, 0x0, 0xae, + 0x84, 0x70, 0xaa, 0xe6, 0xf6, 0xf6, 0x12, 0x2b, 0xe3, 0x2b, 0xae, 0x2f, + 0xea, 0x33, 0x6, 0xdb, 0x22, 0x20, 0xa, 0x91, 0xf0, 0x81, 0xbf, 0xf0, + 0xf1, 0x2, 0x70, 0xc6, 0x97, 0xd0, 0x98, 0x5b, 0xa6, 0x38, 0x25, 0x34, + 0x3e, 0xfe, 0xf5, 0x13, 0x43, 0xbf, 0x7f, 0xbd, 0x3f, 0xe0, 0x1f, 0x5f, + 0x4e, 0x4b, 0x19, 0x56, 0xcb, 0xb5, 0x20, 0x0, 0x0, 0x0, 0x0, 0x49, + 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82}; +const unsigned short sGlyphWidths[256] = { + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 4, 3, 5, 7, 7, 12, + 9, 2, 4, 4, 5, 8, 4, 4, 4, 4, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 4, 4, 8, 8, 8, 7, 13, 9, 9, 9, 9, 9, 8, 10, 9, 3, 6, 9, + 7, 11, 9, 10, 9, 10, 9, 9, 7, 9, 9, 13, 7, 9, 7, 4, 4, 4, 5, + 7, 4, 7, 7, 7, 7, 7, 3, 7, 7, 3, 3, 7, 3, 11, 7, 7, 7, 7, + 4, 7, 4, 7, 5, 9, 7, 7, 7, 4, 3, 4, 8, 10, 7, 10, 3, 7, 4, + 13, 7, 7, 4, 14, 9, 4, 13, 10, 7, 10, 10, 3, 3, 4, 4, 5, 7, 13, + 4, 13, 7, 4, 12, 10, 7, 9, 4, 3, 7, 7, 7, 7, 3, 7, 4, 10, 4, + 7, 8, 4, 10, 7, 5, 7, 4, 4, 4, 7, 7, 4, 4, 4, 5, 7, 11, 11, + 11, 8, 9, 9, 9, 9, 9, 9, 13, 9, 9, 9, 9, 9, 3, 3, 3, 3, 9, + 9, 10, 10, 10, 10, 10, 8, 10, 9, 9, 9, 9, 9, 9, 9, 7, 7, 7, 7, + 7, 7, 12, 7, 7, 7, 7, 7, 3, 3, 3, 3, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7}; +} // namespace normal_font + +const FontBitmapInfo sDefaultCompositorFont = { + mozilla::Nothing(), + mozilla::Some(&normal_font::sGlyphWidths[0]), + 256, + 256, + 16, + 16, + 0, + normal_font::sFontPNG, + sizeof(normal_font::sFontPNG)}; + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/FrameUniformityData.cpp b/gfx/layers/composite/FrameUniformityData.cpp new file mode 100644 index 0000000000..397ec0e1dc --- /dev/null +++ b/gfx/layers/composite/FrameUniformityData.cpp @@ -0,0 +1,145 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FrameUniformityData.h" + +#include + +#include "Units.h" +#include "gfxPoint.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/dom/APZTestDataBinding.h" +#include "mozilla/dom/ToJSValue.h" +#include "nsTArray.h" + +namespace mozilla { +namespace layers { + +using namespace gfx; + +Point LayerTransforms::GetAverage() { + MOZ_ASSERT(!mTransforms.IsEmpty()); + + Point current = mTransforms[0]; + Point average; + size_t length = mTransforms.Length(); + + for (size_t i = 1; i < length; i++) { + Point nextTransform = mTransforms[i]; + Point movement = nextTransform - current; + average += Point(std::fabs(movement.x), std::fabs(movement.y)); + current = nextTransform; + } + + average = average / (float)length; + return average; +} + +Point LayerTransforms::GetStdDev() { + Point average = GetAverage(); + Point stdDev; + Point current = mTransforms[0]; + + for (size_t i = 1; i < mTransforms.Length(); i++) { + Point next = mTransforms[i]; + Point move = next - current; + move.x = fabs(move.x); + move.y = fabs(move.y); + + Point diff = move - average; + diff.x = diff.x * diff.x; + diff.y = diff.y * diff.y; + stdDev += diff; + + current = next; + } + + stdDev = stdDev / mTransforms.Length(); + stdDev.x = sqrt(stdDev.x); + stdDev.y = sqrt(stdDev.y); + return stdDev; +} + +bool LayerTransforms::Sanitize() { + // Remove leading and trailing zeros to isolate the composites that actually + // changed the transform + for (size_t i = 1; i < mTransforms.Length(); i++) { + if (mTransforms[i] != mTransforms[i - 1]) { + mTransforms.RemoveElementsAt(0, i - 1); + break; + } + } + for (size_t i = mTransforms.Length() - 1; i > 0; i--) { + if (mTransforms[i - 1] != mTransforms[i]) { + mTransforms.SetLength(i + 1); + break; + } + } + return !mTransforms.IsEmpty(); +} + +LayerTransformRecorder::~LayerTransformRecorder() { Reset(); } + +void LayerTransformRecorder::RecordTransform(Layer* aLayer, + const Point& aTransform) { + LayerTransforms* layerTransforms = GetLayerTransforms((uintptr_t)aLayer); + layerTransforms->mTransforms.AppendElement(aTransform); +} + +void LayerTransformRecorder::EndTest(FrameUniformityData* aOutData) { + for (const auto& [layer, transforms] : mFrameTransforms) { + (void)transforms; // suppress unused variable warning + + float uniformity = CalculateFrameUniformity(layer); + + std::pair result(layer, uniformity); + aOutData->mUniformities.insert(result); + } + + Reset(); +} + +LayerTransforms* LayerTransformRecorder::GetLayerTransforms(uintptr_t aLayer) { + auto [iter, inserted] = + mFrameTransforms.insert(FrameTransformMap::value_type{aLayer, nullptr}); + if (inserted) { + iter->second = MakeUnique(); + } + return iter->second.get(); +} + +void LayerTransformRecorder::Reset() { mFrameTransforms.clear(); } + +float LayerTransformRecorder::CalculateFrameUniformity(uintptr_t aLayer) { + LayerTransforms* layerTransform = GetLayerTransforms(aLayer); + float yUniformity = -1; + if (layerTransform->Sanitize()) { + Point stdDev = layerTransform->GetStdDev(); + yUniformity = stdDev.y; + } + return yUniformity; +} + +bool FrameUniformityData::ToJS(JS::MutableHandleValue aOutValue, + JSContext* aContext) { + dom::FrameUniformityResults results; + dom::Sequence& layers = + results.mLayerUniformities.Construct(); + + for (const auto& [layerAddr, uniformity] : mUniformities) { + // FIXME: Make this infallible after bug 968520 is done. + MOZ_ALWAYS_TRUE(layers.AppendElement(fallible)); + dom::FrameUniformity& entry = layers.LastElement(); + + entry.mLayerAddress.Construct() = layerAddr; + entry.mFrameUniformity.Construct() = uniformity; + } + + return dom::ToJSValue(aContext, results, aOutValue); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/FrameUniformityData.h b/gfx/layers/composite/FrameUniformityData.h new file mode 100644 index 0000000000..8cb639e60c --- /dev/null +++ b/gfx/layers/composite/FrameUniformityData.h @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_FrameUniformityData_h_ +#define mozilla_layers_FrameUniformityData_h_ + +#include "ipc/IPCMessageUtils.h" +#include "js/TypeDecls.h" +#include "ipc/IPCMessageUtilsSpecializations.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/gfx/Point.h" +#include "nsTArray.h" + +namespace mozilla { +namespace layers { +class Layer; + +class FrameUniformityData { + friend struct IPC::ParamTraits; + + public: + bool ToJS(JS::MutableHandleValue aOutValue, JSContext* aContext); + // Contains the calculated frame uniformities + std::map mUniformities; +}; + +struct LayerTransforms { + LayerTransforms() = default; + + gfx::Point GetAverage(); + gfx::Point GetStdDev(); + bool Sanitize(); + + // 60 fps * 5 seconds worth of data + AutoTArray mTransforms; +}; + +class LayerTransformRecorder { + public: + LayerTransformRecorder() = default; + ~LayerTransformRecorder(); + + void RecordTransform(Layer* aLayer, const gfx::Point& aTransform); + void Reset(); + void EndTest(FrameUniformityData* aOutData); + + private: + float CalculateFrameUniformity(uintptr_t aLayer); + LayerTransforms* GetLayerTransforms(uintptr_t aLayer); + using FrameTransformMap = + std::map>; + FrameTransformMap mFrameTransforms; +}; + +} // namespace layers +} // namespace mozilla + +namespace IPC { +template <> +struct ParamTraits { + typedef mozilla::layers::FrameUniformityData paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mUniformities); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return ParamTraitsStd>::Read( + aMsg, aIter, &aResult->mUniformities); + } +}; + +} // namespace IPC + +#endif // mozilla_layers_FrameUniformityData_h_ diff --git a/gfx/layers/composite/GPUVideoTextureHost.cpp b/gfx/layers/composite/GPUVideoTextureHost.cpp new file mode 100644 index 0000000000..ed6be1972d --- /dev/null +++ b/gfx/layers/composite/GPUVideoTextureHost.cpp @@ -0,0 +1,295 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GPUVideoTextureHost.h" + +#include "ImageContainer.h" +#include "mozilla/RemoteDecoderManagerParent.h" +#include "mozilla/layers/ImageBridgeParent.h" +#include "mozilla/layers/VideoBridgeParent.h" +#include "mozilla/webrender/RenderTextureHostWrapper.h" +#include "mozilla/webrender/RenderThread.h" + +namespace mozilla { +namespace layers { + +GPUVideoTextureHost::GPUVideoTextureHost( + TextureFlags aFlags, const SurfaceDescriptorGPUVideo& aDescriptor) + : TextureHost(aFlags), mDescriptor(aDescriptor) { + MOZ_COUNT_CTOR(GPUVideoTextureHost); +} + +GPUVideoTextureHost::~GPUVideoTextureHost() { + MOZ_COUNT_DTOR(GPUVideoTextureHost); +} + +GPUVideoTextureHost* GPUVideoTextureHost::CreateFromDescriptor( + TextureFlags aFlags, const SurfaceDescriptorGPUVideo& aDescriptor) { + return new GPUVideoTextureHost(aFlags, aDescriptor); +} + +TextureHost* GPUVideoTextureHost::EnsureWrappedTextureHost() { + if (mWrappedTextureHost) { + return mWrappedTextureHost; + } + + const auto& sd = + static_cast(mDescriptor); + VideoBridgeParent* parent = VideoBridgeParent::GetSingleton(sd.source()); + if (!parent) { + // The VideoBridge went away. This can happen if the RDD process + // crashes. + return nullptr; + } + mWrappedTextureHost = parent->LookupTexture(sd.handle()); + + if (!mWrappedTextureHost) { + // The TextureHost hasn't been registered yet. This is due to a race + // between the ImageBridge (content) and the VideoBridge (RDD) and the + // ImageBridge won. See bug + // https://bugzilla.mozilla.org/show_bug.cgi?id=1630733#c14 for more + // details. + return nullptr; + } + + if (mWrappedTextureHost->AsBufferTextureHost()) { + // TODO(miko): This code path is taken when WebRenderTextureHost wraps + // GPUVideoTextureHost, which wraps BufferTextureHost. + // Because this creates additional copies of the texture data, we should not + // do this. + mWrappedTextureHost->AsBufferTextureHost()->DisableExternalTextures(); + } + + if (mExternalImageId.isSome()) { + // External image id is allocated by mWrappedTextureHost. + mWrappedTextureHost->EnsureRenderTexture(Nothing()); + MOZ_ASSERT(mWrappedTextureHost->mExternalImageId.isSome()); + auto wrappedId = mWrappedTextureHost->mExternalImageId.ref(); + + RefPtr texture = + new wr::RenderTextureHostWrapper(wrappedId); + wr::RenderThread::Get()->RegisterExternalImage( + wr::AsUint64(mExternalImageId.ref()), texture.forget()); + } + + if (mPendingSourceProvider) { + RefPtr provider = mPendingSourceProvider.forget(); + mWrappedTextureHost->SetTextureSourceProvider(provider); + } + if (mPendingUpdatedInternal) { + mWrappedTextureHost->UpdatedInternal(mPendingIntRegion.ptrOr(nullptr)); + mPendingIntRegion.reset(); + mPendingUpdatedInternal = false; + } + if (mPendingPrepareTextureSource) { + mWrappedTextureHost->PrepareTextureSource(*mPendingPrepareTextureSource); + mPendingPrepareTextureSource.reset(); + } + + return mWrappedTextureHost; +} + +bool GPUVideoTextureHost::IsValid() { return !!EnsureWrappedTextureHost(); } + +bool GPUVideoTextureHost::Lock() { + if (!EnsureWrappedTextureHost()) { + return false; + } + return EnsureWrappedTextureHost()->Lock(); +} + +void GPUVideoTextureHost::Unlock() { + if (!EnsureWrappedTextureHost()) { + return; + } + EnsureWrappedTextureHost()->Unlock(); +} + +void GPUVideoTextureHost::PrepareTextureSource( + CompositableTextureSourceRef& aTexture) { + if (!EnsureWrappedTextureHost()) { + mPendingPrepareTextureSource = Some(aTexture); + return; + } + EnsureWrappedTextureHost()->PrepareTextureSource(aTexture); +} + +bool GPUVideoTextureHost::BindTextureSource( + CompositableTextureSourceRef& aTexture) { + MOZ_ASSERT(EnsureWrappedTextureHost(), "Image isn't valid yet"); + if (!EnsureWrappedTextureHost()) { + return false; + } + return EnsureWrappedTextureHost()->BindTextureSource(aTexture); +} + +bool GPUVideoTextureHost::AcquireTextureSource( + CompositableTextureSourceRef& aTexture) { + MOZ_ASSERT(EnsureWrappedTextureHost(), "Image isn't valid yet"); + if (!EnsureWrappedTextureHost()) { + return false; + } + return EnsureWrappedTextureHost()->AcquireTextureSource(aTexture); +} + +void GPUVideoTextureHost::SetTextureSourceProvider( + TextureSourceProvider* aProvider) { + if (!EnsureWrappedTextureHost()) { + mPendingSourceProvider = aProvider; + return; + } + EnsureWrappedTextureHost()->SetTextureSourceProvider(aProvider); +} + +gfx::YUVColorSpace GPUVideoTextureHost::GetYUVColorSpace() const { + MOZ_ASSERT(mWrappedTextureHost, "Image isn't valid yet"); + if (!mWrappedTextureHost) { + return gfx::YUVColorSpace::UNKNOWN; + } + return mWrappedTextureHost->GetYUVColorSpace(); +} + +gfx::ColorDepth GPUVideoTextureHost::GetColorDepth() const { + MOZ_ASSERT(mWrappedTextureHost, "Image isn't valid yet"); + if (!mWrappedTextureHost) { + return TextureHost::GetColorDepth(); + } + return mWrappedTextureHost->GetColorDepth(); +} + +gfx::ColorRange GPUVideoTextureHost::GetColorRange() const { + MOZ_ASSERT(mWrappedTextureHost, "Image isn't valid yet"); + if (!mWrappedTextureHost) { + return TextureHost::GetColorRange(); + } + return mWrappedTextureHost->GetColorRange(); +} + +gfx::IntSize GPUVideoTextureHost::GetSize() const { + MOZ_ASSERT(mWrappedTextureHost, "Image isn't valid yet"); + if (!mWrappedTextureHost) { + return gfx::IntSize(); + } + return mWrappedTextureHost->GetSize(); +} + +gfx::SurfaceFormat GPUVideoTextureHost::GetFormat() const { + MOZ_ASSERT(mWrappedTextureHost, "Image isn't valid yet"); + if (!mWrappedTextureHost) { + return gfx::SurfaceFormat::UNKNOWN; + } + return mWrappedTextureHost->GetFormat(); +} + +bool GPUVideoTextureHost::HasIntermediateBuffer() const { + MOZ_ASSERT(mWrappedTextureHost, "Image isn't valid yet"); + if (!mWrappedTextureHost) { + return false; + } + return mWrappedTextureHost->HasIntermediateBuffer(); +} + +void GPUVideoTextureHost::UpdatedInternal(const nsIntRegion* Region) { + if (!EnsureWrappedTextureHost()) { + mPendingUpdatedInternal = true; + if (Region) { + mPendingIntRegion = Some(*Region); + } else { + mPendingIntRegion.reset(); + } + return; + } + EnsureWrappedTextureHost()->UpdatedInternal(Region); +} + +void GPUVideoTextureHost::CreateRenderTexture( + const wr::ExternalImageId& aExternalImageId) { + MOZ_ASSERT(mExternalImageId.isSome()); + + // When mWrappedTextureHost already exist, call CreateRenderTexture() here. + // In other cases, EnsureWrappedTextureHost() handles CreateRenderTexture(). + + if (mWrappedTextureHost) { + // External image id is allocated by mWrappedTextureHost. + mWrappedTextureHost->EnsureRenderTexture(Nothing()); + MOZ_ASSERT(mWrappedTextureHost->mExternalImageId.isSome()); + auto wrappedId = mWrappedTextureHost->mExternalImageId.ref(); + + RefPtr texture = + new wr::RenderTextureHostWrapper(wrappedId); + wr::RenderThread::Get()->RegisterExternalImage( + wr::AsUint64(mExternalImageId.ref()), texture.forget()); + return; + } + + EnsureWrappedTextureHost(); +} + +void GPUVideoTextureHost::MaybeDestroyRenderTexture() { + if (mExternalImageId.isNothing() || !mWrappedTextureHost) { + // RenderTextureHost was not created + return; + } + // When GPUVideoTextureHost created RenderTextureHost, delete it here. + TextureHost::DestroyRenderTexture(mExternalImageId.ref()); +} + +uint32_t GPUVideoTextureHost::NumSubTextures() { + if (!EnsureWrappedTextureHost()) { + return 0; + } + return EnsureWrappedTextureHost()->NumSubTextures(); +} + +void GPUVideoTextureHost::PushResourceUpdates( + wr::TransactionBuilder& aResources, ResourceUpdateOp aOp, + const Range& aImageKeys, const wr::ExternalImageId& aExtID) { + MOZ_ASSERT(EnsureWrappedTextureHost(), "Image isn't valid yet"); + if (!EnsureWrappedTextureHost()) { + return; + } + EnsureWrappedTextureHost()->PushResourceUpdates(aResources, aOp, aImageKeys, + aExtID); +} + +void GPUVideoTextureHost::PushDisplayItems( + wr::DisplayListBuilder& aBuilder, const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, wr::ImageRendering aFilter, + const Range& aImageKeys, PushDisplayItemFlagSet aFlags) { + MOZ_ASSERT(EnsureWrappedTextureHost(), "Image isn't valid yet"); + MOZ_ASSERT(aImageKeys.length() > 0); + if (!EnsureWrappedTextureHost()) { + return; + } + + EnsureWrappedTextureHost()->PushDisplayItems(aBuilder, aBounds, aClip, + aFilter, aImageKeys, aFlags); +} + +bool GPUVideoTextureHost::SupportsExternalCompositing() { + if (!EnsureWrappedTextureHost()) { + return false; + } + return EnsureWrappedTextureHost()->SupportsExternalCompositing(); +} + +void GPUVideoTextureHost::UnbindTextureSource() { + if (EnsureWrappedTextureHost()) { + EnsureWrappedTextureHost()->UnbindTextureSource(); + } + // Handle read unlock + TextureHost::UnbindTextureSource(); +} + +void GPUVideoTextureHost::NotifyNotUsed() { + if (EnsureWrappedTextureHost()) { + EnsureWrappedTextureHost()->NotifyNotUsed(); + } + TextureHost::NotifyNotUsed(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/GPUVideoTextureHost.h b/gfx/layers/composite/GPUVideoTextureHost.h new file mode 100644 index 0000000000..1346d61ccd --- /dev/null +++ b/gfx/layers/composite/GPUVideoTextureHost.h @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_GPUVIDEOTEXTUREHOST_H +#define MOZILLA_GFX_GPUVIDEOTEXTUREHOST_H + +#include "mozilla/layers/TextureHost.h" + +namespace mozilla { +namespace layers { + +class GPUVideoTextureHost : public TextureHost { + public: + static GPUVideoTextureHost* CreateFromDescriptor( + TextureFlags aFlags, const SurfaceDescriptorGPUVideo& aDescriptor); + + virtual ~GPUVideoTextureHost(); + + void DeallocateDeviceData() override {} + + virtual void SetTextureSourceProvider( + TextureSourceProvider* aProvider) override; + + bool Lock() override; + + void Unlock() override; + + gfx::SurfaceFormat GetFormat() const override; + + void PrepareTextureSource(CompositableTextureSourceRef& aTexture) override; + + bool BindTextureSource(CompositableTextureSourceRef& aTexture) override; + bool AcquireTextureSource(CompositableTextureSourceRef& aTexture) override; + + already_AddRefed GetAsSurface() override { + return nullptr; // XXX - implement this (for MOZ_DUMP_PAINTING) + } + + gfx::YUVColorSpace GetYUVColorSpace() const override; + gfx::ColorDepth GetColorDepth() const override; + gfx::ColorRange GetColorRange() const override; + + gfx::IntSize GetSize() const override; + + bool IsValid() override; + +#ifdef MOZ_LAYERS_HAVE_LOG + const char* Name() override { return "GPUVideoTextureHost"; } +#endif + + bool HasIntermediateBuffer() const override; + + void CreateRenderTexture( + const wr::ExternalImageId& aExternalImageId) override; + + void MaybeDestroyRenderTexture() override; + + uint32_t NumSubTextures() override; + + void PushResourceUpdates(wr::TransactionBuilder& aResources, + ResourceUpdateOp aOp, + const Range& aImageKeys, + const wr::ExternalImageId& aExtID) override; + + void PushDisplayItems(wr::DisplayListBuilder& aBuilder, + const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, wr::ImageRendering aFilter, + const Range& aImageKeys, + PushDisplayItemFlagSet aFlags) override; + + bool SupportsExternalCompositing() override; + + void UnbindTextureSource() override; + void NotifyNotUsed() override; + + protected: + GPUVideoTextureHost(TextureFlags aFlags, + const SurfaceDescriptorGPUVideo& aDescriptor); + + TextureHost* EnsureWrappedTextureHost(); + + void UpdatedInternal(const nsIntRegion* Region) override; + + RefPtr mWrappedTextureHost; + RefPtr mPendingSourceProvider; + bool mPendingUpdatedInternal = false; + Maybe mPendingIntRegion; + Maybe mPendingPrepareTextureSource; + SurfaceDescriptorGPUVideo mDescriptor; +}; + +} // namespace layers +} // namespace mozilla + +#endif // MOZILLA_GFX_GPUVIDEOTEXTUREHOST_H diff --git a/gfx/layers/composite/ImageComposite.cpp b/gfx/layers/composite/ImageComposite.cpp new file mode 100644 index 0000000000..20e4ee2b5c --- /dev/null +++ b/gfx/layers/composite/ImageComposite.cpp @@ -0,0 +1,385 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ImageComposite.h" + +#include + +#include "GeckoProfiler.h" +#include "gfxPlatform.h" + +namespace mozilla { + +using namespace gfx; + +namespace layers { + +/* static */ const float ImageComposite::BIAS_TIME_MS = 1.0f; + +ImageComposite::ImageComposite() = default; + +ImageComposite::~ImageComposite() = default; + +TimeStamp ImageComposite::GetBiasedTime(const TimeStamp& aInput) const { + switch (mBias) { + case ImageComposite::BIAS_NEGATIVE: + return aInput - TimeDuration::FromMilliseconds(BIAS_TIME_MS); + case ImageComposite::BIAS_POSITIVE: + return aInput + TimeDuration::FromMilliseconds(BIAS_TIME_MS); + default: + return aInput; + } +} + +void ImageComposite::UpdateBias(size_t aImageIndex, bool aFrameChanged) { + MOZ_ASSERT(aImageIndex < ImagesCount()); + + TimeStamp compositionTime = GetCompositionTime(); + TimeStamp compositedImageTime = mImages[aImageIndex].mTimeStamp; + TimeStamp nextImageTime = aImageIndex + 1 < ImagesCount() + ? mImages[aImageIndex + 1].mTimeStamp + : TimeStamp(); + +#if MOZ_GECKO_PROFILER + if (profiler_can_accept_markers() && compositedImageTime && nextImageTime) { + TimeDuration offsetCurrent = compositedImageTime - compositionTime; + TimeDuration offsetNext = nextImageTime - compositionTime; + nsPrintfCString str("current %.2lfms, next %.2lfms", + offsetCurrent.ToMilliseconds(), + offsetNext.ToMilliseconds()); + PROFILER_MARKER_TEXT("Video frame offsets", GRAPHICS, {}, str); + } +#endif + + if (compositedImageTime.IsNull()) { + mBias = ImageComposite::BIAS_NONE; + return; + } + TimeDuration threshold = TimeDuration::FromMilliseconds(1.5); + if (compositionTime - compositedImageTime < threshold && + compositionTime - compositedImageTime > -threshold) { + // The chosen frame's time is very close to the composition time (probably + // just before the current composition time, but due to previously set + // negative bias, it could be just after the current composition time too). + // If the inter-frame time is almost exactly equal to (a multiple of) + // the inter-composition time, then we're in a dangerous situation because + // jitter might cause frames to fall one side or the other of the + // composition times, causing many frames to be skipped or duplicated. + // Try to prevent that by adding a negative bias to the frame times during + // the next composite; that should ensure the next frame's time is treated + // as falling just before a composite time. + mBias = ImageComposite::BIAS_NEGATIVE; + return; + } + if (!nextImageTime.IsNull() && nextImageTime - compositionTime < threshold && + nextImageTime - compositionTime > -threshold) { + // The next frame's time is very close to our composition time (probably + // just after the current composition time, but due to previously set + // positive bias, it could be just before the current composition time too). + // We're in a dangerous situation because jitter might cause frames to + // fall one side or the other of the composition times, causing many frames + // to be skipped or duplicated. + // Specifically, the next composite is at risk of picking the "next + 1" + // frame rather than the "next" frame, which would cause the "next" frame to + // be skipped. Try to prevent that by adding a positive bias to the frame + // times during the next composite; if the inter-frame time is almost + // exactly equal to the inter-composition time, that should ensure that the + // next + 1 frame falls just *after* the next composition time, and the next + // composite should then pick the next frame rather than the next + 1 frame. + mBias = ImageComposite::BIAS_POSITIVE; + return; + } + if (aFrameChanged) { + // The current and next video frames are a sufficient distance from the + // composition time and we can reliably pick the right frame without bias. + // Reset the bias. + // We only do this when the frame changed. Otherwise, when playing a 30fps + // video on a 60fps display, we'd keep resetting the bias during the "middle + // frames". + mBias = ImageComposite::BIAS_NONE; + } +} + +int ImageComposite::ChooseImageIndex() { + // ChooseImageIndex is called for all images in the layer when it is visible. + // Change to this behaviour would break dropped frames counting calculation: + // We rely on this assumption to determine if during successive runs an + // image is returned that isn't the one following immediately the previous one + if (mImages.IsEmpty()) { + return -1; + } + + TimeStamp compositionTime = GetCompositionTime(); + auto compositionOpportunityId = GetCompositionOpportunityId(); + if (compositionTime && + compositionOpportunityId != mLastChooseImageIndexComposition) { + // We are inside a composition, in the first call to ChooseImageIndex during + // this composition. + // Find the newest frame whose biased timestamp is at or before + // `compositionTime`. + uint32_t imageIndex = 0; + while (imageIndex + 1 < mImages.Length() && + mImages[imageIndex + 1].mTextureHost->IsValid() && + GetBiasedTime(mImages[imageIndex + 1].mTimeStamp) <= + compositionTime) { + ++imageIndex; + } + + if (!mImages[imageIndex].mTextureHost->IsValid()) { + // Still not ready to be shown. + return -1; + } + + bool wasVisibleAtPreviousComposition = + compositionOpportunityId == mLastChooseImageIndexComposition.Next(); + + bool frameChanged = + UpdateCompositedFrame(imageIndex, wasVisibleAtPreviousComposition); + UpdateBias(imageIndex, frameChanged); + + mLastChooseImageIndexComposition = compositionOpportunityId; + + return imageIndex; + } + + // We've been called before during this composition, or we're not in a + // composition. Just return the last image we picked (if it's one of the + // current images). + for (uint32_t i = 0; i < mImages.Length(); ++i) { + if (mImages[i].mFrameID == mLastFrameID && + mImages[i].mProducerID == mLastProducerID) { + return i; + } + } + + return 0; +} + +const ImageComposite::TimedImage* ImageComposite::ChooseImage() { + int index = ChooseImageIndex(); + return index >= 0 ? &mImages[index] : nullptr; +} + +void ImageComposite::RemoveImagesWithTextureHost(TextureHost* aTexture) { + for (int32_t i = mImages.Length() - 1; i >= 0; --i) { + if (mImages[i].mTextureHost == aTexture) { + aTexture->UnbindTextureSource(); + mImages.RemoveElementAt(i); + } + } +} + +void ImageComposite::ClearImages() { mImages.Clear(); } + +void ImageComposite::SetImages(nsTArray&& aNewImages) { + if (!aNewImages.IsEmpty()) { + DetectTimeStampJitter(&aNewImages[0]); + + // Frames older than the first frame in aNewImages that we haven't shown yet + // will never be shown. + CountSkippedFrames(&aNewImages[0]); + +#if MOZ_GECKO_PROFILER + if (profiler_can_accept_markers()) { + int len = aNewImages.Length(); + const auto& first = aNewImages[0]; + const auto& last = aNewImages.LastElement(); + nsPrintfCString str("%d %s, frameID %" PRId32 " (prod %" PRId32 + ") to frameID %" PRId32 " (prod %" PRId32 ")", + len, len == 1 ? "image" : "images", first.mFrameID, + first.mProducerID, last.mFrameID, last.mProducerID); + PROFILER_MARKER_TEXT("ImageComposite::SetImages", GRAPHICS, {}, str); + } +#endif + } + mImages = std::move(aNewImages); +} + +// Returns whether the frame changed. +bool ImageComposite::UpdateCompositedFrame( + int aImageIndex, bool aWasVisibleAtPreviousComposition) { + MOZ_RELEASE_ASSERT(aImageIndex >= 0); + MOZ_RELEASE_ASSERT(aImageIndex < static_cast(mImages.Length())); + const TimedImage& image = mImages[aImageIndex]; + + auto compositionOpportunityId = GetCompositionOpportunityId(); + TimeStamp compositionTime = GetCompositionTime(); + MOZ_RELEASE_ASSERT(compositionTime, + "Should only be called during a composition"); + +#if MOZ_GECKO_PROFILER + nsCString descr; + if (profiler_can_accept_markers()) { + nsCString relativeTimeString; + if (image.mTimeStamp) { + relativeTimeString.AppendPrintf( + " [relative timestamp %.1lfms]", + (image.mTimeStamp - compositionTime).ToMilliseconds()); + } + int remainingImages = mImages.Length() - 1 - aImageIndex; + static const char* kBiasStrings[] = {"NONE", "NEGATIVE", "POSITIVE"}; + descr.AppendPrintf( + "frameID %" PRId32 " (producerID %" PRId32 ") [composite %" PRIu64 + "] [bias %s] [%d remaining %s]%s", + image.mFrameID, image.mProducerID, compositionOpportunityId.mId, + kBiasStrings[mBias], remainingImages, + remainingImages == 1 ? "image" : "images", relativeTimeString.get()); + if (mLastProducerID != image.mProducerID) { + descr.AppendPrintf(", previous producerID: %" PRId32, mLastProducerID); + } else if (mLastFrameID != image.mFrameID) { + descr.AppendPrintf(", previous frameID: %" PRId32, mLastFrameID); + } else { + descr.AppendLiteral(", no change"); + } + } + PROFILER_MARKER_TEXT("UpdateCompositedFrame", GRAPHICS, {}, descr); +#endif + + if (mLastFrameID == image.mFrameID && mLastProducerID == image.mProducerID) { + // The frame didn't change. + return false; + } + + CountSkippedFrames(&image); + + int32_t dropped = mSkippedFramesSinceLastComposite; + mSkippedFramesSinceLastComposite = 0; + + if (!aWasVisibleAtPreviousComposition) { + // This video was not part of the on-screen scene during the previous + // composition opportunity, for example it may have been scrolled off-screen + // or in a background tab, or compositing might have been paused. + // Ignore any skipped frames and don't count them as dropped. + dropped = 0; + } + + if (dropped > 0) { + mDroppedFrames += dropped; +#if MOZ_GECKO_PROFILER + if (profiler_can_accept_markers()) { + const char* frameOrFrames = dropped == 1 ? "frame" : "frames"; + nsPrintfCString text("%" PRId32 " %s dropped: %" PRId32 " -> %" PRId32 + " (producer %" PRId32 ")", + dropped, frameOrFrames, mLastFrameID, image.mFrameID, + mLastProducerID); + PROFILER_MARKER_TEXT("Video frames dropped", GRAPHICS, {}, text); + } +#endif + } + + mLastFrameID = image.mFrameID; + mLastProducerID = image.mProducerID; + mLastFrameUpdateComposition = compositionOpportunityId; + + return true; +} + +void ImageComposite::OnFinishRendering(int aImageIndex, + const TimedImage* aImage, + base::ProcessId aProcessId, + const CompositableHandle& aHandle) { + if (mLastFrameUpdateComposition != GetCompositionOpportunityId()) { + // The frame did not change in this composition. + return; + } + + if (aHandle) { + ImageCompositeNotificationInfo info; + info.mImageBridgeProcessId = aProcessId; + info.mNotification = ImageCompositeNotification( + aHandle, aImage->mTimeStamp, GetCompositionTime(), mLastFrameID, + mLastProducerID); + AppendImageCompositeNotification(info); + } +} + +const ImageComposite::TimedImage* ImageComposite::GetImage( + size_t aIndex) const { + if (aIndex >= mImages.Length()) { + return nullptr; + } + return &mImages[aIndex]; +} + +void ImageComposite::CountSkippedFrames(const TimedImage* aImage) { + if (aImage->mProducerID != mLastProducerID) { + // Switched producers. + return; + } + + if (mImages.IsEmpty() || aImage->mFrameID <= mLastFrameID + 1) { + // No frames were skipped. + return; + } + + uint32_t targetFrameRate = gfxPlatform::TargetFrameRate(); + if (targetFrameRate == 0) { + // Can't know whether we could have reasonably displayed all video frames. + return; + } + + double targetFrameDurationMS = 1000.0 / targetFrameRate; + + // Count how many images in mImages were skipped between mLastFrameID and + // aImage.mFrameID. Only count frames for which we can estimate a duration by + // looking at the next frame's timestamp, and only if the video frame rate is + // no faster than the target frame rate. + int32_t skipped = 0; + for (size_t i = 0; i + 1 < mImages.Length(); i++) { + const auto& img = mImages[i]; + if (img.mProducerID != aImage->mProducerID || + img.mFrameID <= mLastFrameID || img.mFrameID >= aImage->mFrameID) { + continue; + } + + // We skipped img! Estimate img's time duration. + const auto& next = mImages[i + 1]; + if (next.mProducerID != aImage->mProducerID) { + continue; + } + + MOZ_ASSERT(next.mFrameID > img.mFrameID); + TimeDuration duration = next.mTimeStamp - img.mTimeStamp; + if (floor(duration.ToMilliseconds()) >= floor(targetFrameDurationMS)) { + // Count the frame. + skipped++; + } + } + + mSkippedFramesSinceLastComposite += skipped; +} + +void ImageComposite::DetectTimeStampJitter(const TimedImage* aNewImage) { +#if MOZ_GECKO_PROFILER + if (!profiler_can_accept_markers() || aNewImage->mTimeStamp.IsNull()) { + return; + } + + // Find aNewImage in mImages and compute its timestamp delta, if found. + // Ideally, a given video frame should never change its timestamp (jitter + // should be zero). However, we re-adjust video frame timestamps based on the + // audio clock. If the audio clock drifts compared to the system clock, or if + // there are bugs or inaccuracies in the computation of these timestamps, + // jitter will be non-zero. + Maybe jitter; + for (const auto& img : mImages) { + if (img.mProducerID == aNewImage->mProducerID && + img.mFrameID == aNewImage->mFrameID) { + if (!img.mTimeStamp.IsNull()) { + jitter = Some(aNewImage->mTimeStamp - img.mTimeStamp); + } + break; + } + } + if (jitter) { + nsPrintfCString text("%.2lfms", jitter->ToMilliseconds()); + PROFILER_MARKER_TEXT("VideoFrameTimeStampJitter", GRAPHICS, {}, text); + } +#endif +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/ImageComposite.h b/gfx/layers/composite/ImageComposite.h new file mode 100644 index 0000000000..05e812c8b3 --- /dev/null +++ b/gfx/layers/composite/ImageComposite.h @@ -0,0 +1,139 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_IMAGECOMPOSITE_H +#define MOZILLA_GFX_IMAGECOMPOSITE_H + +#include "CompositableHost.h" // for CompositableTextureHostRef +#include "mozilla/gfx/2D.h" +#include "mozilla/TimeStamp.h" // for TimeStamp +#include "nsTArray.h" + +namespace mozilla { +namespace layers { + +/** + * Implements Image selection logic. + */ +class ImageComposite { + public: + static const float BIAS_TIME_MS; + + explicit ImageComposite(); + virtual ~ImageComposite(); + + int32_t GetFrameID() { + const TimedImage* img = ChooseImage(); + return img ? img->mFrameID : -1; + } + + int32_t GetProducerID() { + const TimedImage* img = ChooseImage(); + return img ? img->mProducerID : -1; + } + + int32_t GetLastFrameID() const { return mLastFrameID; } + int32_t GetLastProducerID() const { return mLastProducerID; } + uint32_t GetDroppedFramesAndReset() { + uint32_t dropped = mDroppedFrames; + mDroppedFrames = 0; + return dropped; + } + + enum Bias { + // Don't apply bias to frame times + BIAS_NONE, + // Apply a negative bias to frame times to keep them before the vsync time + BIAS_NEGATIVE, + // Apply a positive bias to frame times to keep them after the vsync time + BIAS_POSITIVE, + }; + + protected: + virtual TimeStamp GetCompositionTime() const = 0; + virtual CompositionOpportunityId GetCompositionOpportunityId() const = 0; + virtual void AppendImageCompositeNotification( + const ImageCompositeNotificationInfo& aInfo) const = 0; + + struct TimedImage { + CompositableTextureHostRef mTextureHost; + TimeStamp mTimeStamp; + gfx::IntRect mPictureRect; + int32_t mFrameID; + int32_t mProducerID; + }; + + /** + * ChooseImage is guaranteed to return the same TimedImage every time it's + * called during the same composition, up to the end of Composite() --- + * it depends only on mImages, mCompositor->GetCompositionTime(), and mBias. + * mBias is updated at the end of Composite(). + */ + const TimedImage* ChooseImage(); + int ChooseImageIndex(); + const TimedImage* GetImage(size_t aIndex) const; + size_t ImagesCount() const { return mImages.Length(); } + const nsTArray& Images() const { return mImages; } + + void RemoveImagesWithTextureHost(TextureHost* aTexture); + void ClearImages(); + void SetImages(nsTArray&& aNewImages); + + protected: + // Send ImageComposite notifications and update the ChooseImage bias. + void OnFinishRendering(int aImageIndex, const TimedImage* aImage, + base::ProcessId aProcessId, + const CompositableHandle& aHandle); + + int32_t mLastFrameID = -1; + int32_t mLastProducerID = -1; + + private: + nsTArray mImages; + TimeStamp GetBiasedTime(const TimeStamp& aInput) const; + + // Called when we know that frames older than aImage will never get another + // chance to be displayed. + // Counts frames in mImages that were skipped, and adds the skipped count to + // mSkippedFramesSinceLastComposite. + void CountSkippedFrames(const TimedImage* aImage); + + // Update mLastFrameID and mLastProducerID, and report dropped frames. + // Returns whether the frame changed. + bool UpdateCompositedFrame(int aImageIndex, + bool aWasVisibleAtPreviousComposition); + + void UpdateBias(size_t aImageIndex, bool aFrameUpdated); + + // Emit a profiler marker about video frame timestamp jitter. + void DetectTimeStampJitter(const TimedImage* aNewImage); + + /** + * Bias to apply to the next frame. + */ + Bias mBias = BIAS_NONE; + + // Video frames that were in mImages but that will never reach the screen. + // This count is reset in UpdateCompositedFrame. + int32_t mSkippedFramesSinceLastComposite = 0; + + // The number of dropped frames since the last call to + // GetDroppedFramesAndReset(). Not all skipped frames are considered "dropped" + // - for example, videos that are scrolled out of view or videos in background + // tabs are expected to skip frames, and those skipped frames are not counted + // as dropped frames. + uint32_t mDroppedFrames = 0; + + // The composition opportunity IDs of the last calls to ChooseImageIndex and + // UpdateCompositedFrame, respectively. + CompositionOpportunityId mLastChooseImageIndexComposition; + CompositionOpportunityId mLastFrameUpdateComposition; +}; + +} // namespace layers +} // namespace mozilla + +#endif // MOZILLA_GFX_IMAGECOMPOSITE_H diff --git a/gfx/layers/composite/ImageHost.cpp b/gfx/layers/composite/ImageHost.cpp new file mode 100644 index 0000000000..3c23dc432b --- /dev/null +++ b/gfx/layers/composite/ImageHost.cpp @@ -0,0 +1,454 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ImageHost.h" + +#include + +#include "composite/CompositableHost.h" // for CompositableHost, etc +#include "ipc/IPCMessageUtils.h" // for null_t +#include "mozilla/layers/Compositor.h" // for Compositor +#include "mozilla/layers/Effects.h" // for TexturedEffect, Effect, etc +#include "mozilla/layers/LayerManagerComposite.h" // for TexturedEffect, Effect, etc +#include "nsAString.h" +#include "nsDebug.h" // for NS_WARNING, NS_ASSERTION +#include "nsPrintfCString.h" // for nsPrintfCString +#include "nsString.h" // for nsAutoCString + +namespace mozilla { + +using namespace gfx; + +namespace layers { + +class ISurfaceAllocator; + +ImageHost::ImageHost(const TextureInfo& aTextureInfo) + : CompositableHost(aTextureInfo), ImageComposite(), mLocked(false) {} + +ImageHost::~ImageHost() = default; + +void ImageHost::UseTextureHost(const nsTArray& aTextures) { + MOZ_ASSERT(!mLocked); + + CompositableHost::UseTextureHost(aTextures); + MOZ_ASSERT(aTextures.Length() >= 1); + + nsTArray newImages; + + for (uint32_t i = 0; i < aTextures.Length(); ++i) { + const TimedTexture& t = aTextures[i]; + MOZ_ASSERT(t.mTexture); + if (i + 1 < aTextures.Length() && t.mProducerID == mLastProducerID && + t.mFrameID < mLastFrameID) { + // Ignore frames before a frame that we already composited. We don't + // ever want to display these frames. This could be important if + // the frame producer adjusts timestamps (e.g. to track the audio clock) + // and the new frame times are earlier. + continue; + } + TimedImage& img = *newImages.AppendElement(); + img.mTextureHost = t.mTexture; + img.mTimeStamp = t.mTimeStamp; + img.mPictureRect = t.mPictureRect; + img.mFrameID = t.mFrameID; + img.mProducerID = t.mProducerID; + img.mTextureHost->SetCropRect(img.mPictureRect); + img.mTextureHost->Updated(); + } + + SetImages(std::move(newImages)); + + // If we only have one image we can upload it right away, otherwise we'll + // upload on-demand during composition after we have picked the proper + // timestamp. + if (ImagesCount() == 1) { + SetCurrentTextureHost(GetImage(0)->mTextureHost); + } + + HostLayerManager* lm = GetLayerManager(); + + // Video producers generally send replacement images with the same frameID but + // slightly different timestamps in order to sync with the audio clock. This + // means that any CompositeUntil() call we made in Composite() may no longer + // guarantee that we'll composite until the next frame is ready. Fix that + // here. + if (lm && mLastFrameID >= 0) { + for (const auto& img : Images()) { + bool frameComesAfter = + img.mFrameID > mLastFrameID || img.mProducerID != mLastProducerID; + if (frameComesAfter && !img.mTimeStamp.IsNull()) { + lm->CompositeUntil(img.mTimeStamp + + TimeDuration::FromMilliseconds(BIAS_TIME_MS)); + break; + } + } + } +} + +void ImageHost::SetCurrentTextureHost(TextureHost* aTexture) { + if (aTexture == mCurrentTextureHost.get()) { + return; + } + + bool swapTextureSources = !!mCurrentTextureHost && !!mCurrentTextureSource && + mCurrentTextureHost->IsValid() && + mCurrentTextureHost->HasIntermediateBuffer(); + + if (swapTextureSources) { + auto dataSource = mCurrentTextureSource->AsDataTextureSource(); + if (dataSource) { + // The current textureHost has an internal buffer in the form of the + // DataTextureSource. Removing the ownership of the texture source + // will enable the next texture host we bind to the texture source to + // acquire it instead of creating a new one. This is desirable in + // ImageHost because the current texture won't be used again with the + // same content. It wouldn't be desirable with ContentHost for instance, + // because the latter reuses the texture's valid regions. + dataSource->SetOwner(nullptr); + } + + RefPtr tmp = mExtraTextureSource; + mExtraTextureSource = mCurrentTextureSource.get(); + mCurrentTextureSource = tmp; + } else { + mExtraTextureSource = nullptr; + } + + mCurrentTextureHost = aTexture; + mCurrentTextureHost->PrepareTextureSource(mCurrentTextureSource); +} + +void ImageHost::CleanupResources() { + mExtraTextureSource = nullptr; + mCurrentTextureSource = nullptr; + mCurrentTextureHost = nullptr; +} + +void ImageHost::RemoveTextureHost(TextureHost* aTexture) { + MOZ_ASSERT(!mLocked); + + CompositableHost::RemoveTextureHost(aTexture); + RemoveImagesWithTextureHost(aTexture); +} + +TimeStamp ImageHost::GetCompositionTime() const { + TimeStamp time; + if (HostLayerManager* lm = GetLayerManager()) { + time = lm->GetCompositionTime(); + } + return time; +} + +CompositionOpportunityId ImageHost::GetCompositionOpportunityId() const { + CompositionOpportunityId id; + if (HostLayerManager* lm = GetLayerManager()) { + id = lm->GetCompositionOpportunityId(); + } + return id; +} + +void ImageHost::AppendImageCompositeNotification( + const ImageCompositeNotificationInfo& aInfo) const { + if (HostLayerManager* lm = GetLayerManager()) { + lm->AppendImageCompositeNotification(aInfo); + } +} + +TextureHost* ImageHost::GetAsTextureHost(IntRect* aPictureRect) { + const TimedImage* img = ChooseImage(); + if (!img) { + return nullptr; + } + SetCurrentTextureHost(img->mTextureHost); + if (aPictureRect) { + *aPictureRect = img->mPictureRect; + } + return img->mTextureHost; +} + +void ImageHost::Attach(Layer* aLayer, TextureSourceProvider* aProvider, + AttachFlags aFlags) { + CompositableHost::Attach(aLayer, aProvider, aFlags); + for (const auto& img : Images()) { + img.mTextureHost->SetTextureSourceProvider(aProvider); + img.mTextureHost->Updated(); + } +} + +void ImageHost::Composite(Compositor* aCompositor, LayerComposite* aLayer, + EffectChain& aEffectChain, float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::SamplingFilter aSamplingFilter, + const gfx::IntRect& aClipRect, + const nsIntRegion* aVisibleRegion, + const Maybe& aGeometry) { + RenderInfo info; + if (!PrepareToRender(aCompositor, &info)) { + return; + } + + const TimedImage* img = info.img; + + { + AutoLockCompositableHost autoLock(this); + if (autoLock.Failed()) { + NS_WARNING("failed to lock front buffer"); + return; + } + + if (!mCurrentTextureHost->BindTextureSource(mCurrentTextureSource)) { + return; + } + + if (!mCurrentTextureSource) { + // BindTextureSource above should have returned false! + MOZ_ASSERT(false); + return; + } + + bool isAlphaPremultiplied = + !(mCurrentTextureHost->GetFlags() & TextureFlags::NON_PREMULTIPLIED); + RefPtr effect = + CreateTexturedEffect(mCurrentTextureHost, mCurrentTextureSource.get(), + aSamplingFilter, isAlphaPremultiplied); + if (!effect) { + return; + } + + if (!aCompositor->SupportsEffect(effect->mType)) { + return; + } + + DiagnosticFlags diagnosticFlags = DiagnosticFlags::IMAGE; + if (effect->mType == EffectTypes::NV12) { + diagnosticFlags |= DiagnosticFlags::NV12; + } else if (effect->mType == EffectTypes::YCBCR) { + diagnosticFlags |= DiagnosticFlags::YCBCR; + } + + aEffectChain.mPrimaryEffect = effect; + gfx::Rect pictureRect(0, 0, img->mPictureRect.Width(), + img->mPictureRect.Height()); + BigImageIterator* it = mCurrentTextureSource->AsBigImageIterator(); + if (it) { + // This iteration does not work if we have multiple texture sources here + // (e.g. 3 YCbCr textures). There's nothing preventing the different + // planes from having different resolutions or tile sizes. For example, a + // YCbCr frame could have Cb and Cr planes that are half the resolution of + // the Y plane, in such a way that the Y plane overflows the maximum + // texture size and the Cb and Cr planes do not. Then the Y plane would be + // split into multiple tiles and the Cb and Cr planes would just be one + // tile each. + // To handle the general case correctly, we'd have to create a grid of + // intersected tiles over all planes, and then draw each grid tile using + // the corresponding source tiles from all planes, with appropriate + // per-plane per-tile texture coords. + // DrawQuad currently assumes that all planes use the same texture coords. + MOZ_ASSERT( + it->GetTileCount() == 1 || !mCurrentTextureSource->GetNextSibling(), + "Can't handle multi-plane BigImages"); + + it->BeginBigImageIteration(); + do { + IntRect tileRect = it->GetTileRect(); + gfx::Rect rect(tileRect.X(), tileRect.Y(), tileRect.Width(), + tileRect.Height()); + rect = rect.Intersect(pictureRect); + effect->mTextureCoords = + Rect(Float(rect.X() - tileRect.X()) / tileRect.Width(), + Float(rect.Y() - tileRect.Y()) / tileRect.Height(), + Float(rect.Width()) / tileRect.Width(), + Float(rect.Height()) / tileRect.Height()); + if (img->mTextureHost->GetFlags() & TextureFlags::ORIGIN_BOTTOM_LEFT) { + effect->mTextureCoords.SetRectY(effect->mTextureCoords.YMost(), + -effect->mTextureCoords.Height()); + } + aCompositor->DrawGeometry(rect, aClipRect, aEffectChain, aOpacity, + aTransform, aGeometry); + aCompositor->DrawDiagnostics( + diagnosticFlags | DiagnosticFlags::BIGIMAGE, rect, aClipRect, + aTransform, mFlashCounter); + } while (it->NextTile()); + it->EndBigImageIteration(); + // layer border + aCompositor->DrawDiagnostics(diagnosticFlags, pictureRect, aClipRect, + aTransform, mFlashCounter); + } else { + IntSize textureSize = mCurrentTextureSource->GetSize(); + effect->mTextureCoords = + Rect(Float(img->mPictureRect.X()) / textureSize.width, + Float(img->mPictureRect.Y()) / textureSize.height, + Float(img->mPictureRect.Width()) / textureSize.width, + Float(img->mPictureRect.Height()) / textureSize.height); + + if (img->mTextureHost->GetFlags() & TextureFlags::ORIGIN_BOTTOM_LEFT) { + effect->mTextureCoords.SetRectY(effect->mTextureCoords.YMost(), + -effect->mTextureCoords.Height()); + } + + aCompositor->DrawGeometry(pictureRect, aClipRect, aEffectChain, aOpacity, + aTransform, aGeometry); + aCompositor->DrawDiagnostics(diagnosticFlags, pictureRect, aClipRect, + aTransform, mFlashCounter); + } + } + + FinishRendering(info); +} + +bool ImageHost::PrepareToRender(TextureSourceProvider* aProvider, + RenderInfo* aOutInfo) { + HostLayerManager* lm = GetLayerManager(); + if (!lm) { + return false; + } + + int imageIndex = ChooseImageIndex(); + if (imageIndex < 0) { + return false; + } + + if (uint32_t(imageIndex) + 1 < ImagesCount()) { + lm->CompositeUntil(GetImage(imageIndex + 1)->mTimeStamp + + TimeDuration::FromMilliseconds(BIAS_TIME_MS)); + } + + const TimedImage* img = GetImage(imageIndex); + img->mTextureHost->SetTextureSourceProvider(aProvider); + SetCurrentTextureHost(img->mTextureHost); + + aOutInfo->imageIndex = imageIndex; + aOutInfo->img = img; + aOutInfo->host = mCurrentTextureHost; + return true; +} + +RefPtr ImageHost::AcquireTextureSource(const RenderInfo& aInfo) { + MOZ_ASSERT(aInfo.host == mCurrentTextureHost); + if (!aInfo.host->AcquireTextureSource(mCurrentTextureSource)) { + return nullptr; + } + return mCurrentTextureSource.get(); +} + +void ImageHost::FinishRendering(const RenderInfo& aInfo) { + OnFinishRendering(aInfo.imageIndex, aInfo.img, mAsyncRef.mProcessId, + mAsyncRef.mHandle); +} + +void ImageHost::SetTextureSourceProvider(TextureSourceProvider* aProvider) { + if (mTextureSourceProvider != aProvider) { + for (const auto& img : Images()) { + img.mTextureHost->SetTextureSourceProvider(aProvider); + } + } + CompositableHost::SetTextureSourceProvider(aProvider); +} + +void ImageHost::PrintInfo(std::stringstream& aStream, const char* aPrefix) { + aStream << aPrefix; + aStream << nsPrintfCString("ImageHost (0x%p)", this).get(); + + nsAutoCString pfx(aPrefix); + pfx += " "; + for (const auto& img : Images()) { + aStream << "\n"; + img.mTextureHost->PrintInfo(aStream, pfx.get()); + aStream << " [picture-rect=" << img.mPictureRect << "]"; + } +} + +void ImageHost::Dump(std::stringstream& aStream, const char* aPrefix, + bool aDumpHtml) { + for (const auto& img : Images()) { + aStream << aPrefix; + aStream << (aDumpHtml ? "
    • TextureHost: " : "TextureHost: "); + DumpTextureHost(aStream, img.mTextureHost); + aStream << (aDumpHtml ? "
    " : " "); + } +} + +already_AddRefed ImageHost::GetAsSurface() { + const TimedImage* img = ChooseImage(); + if (img) { + return img->mTextureHost->GetAsSurface(); + } + return nullptr; +} + +bool ImageHost::Lock() { + MOZ_ASSERT(!mLocked); + const TimedImage* img = ChooseImage(); + if (!img) { + return false; + } + + SetCurrentTextureHost(img->mTextureHost); + + if (!mCurrentTextureHost->Lock()) { + return false; + } + mLocked = true; + return true; +} + +void ImageHost::Unlock() { + MOZ_ASSERT(mLocked); + + if (mCurrentTextureHost) { + mCurrentTextureHost->Unlock(); + } + mLocked = false; +} + +IntSize ImageHost::GetImageSize() { + const TimedImage* img = ChooseImage(); + if (img) { + return IntSize(img->mPictureRect.Width(), img->mPictureRect.Height()); + } + return IntSize(); +} + +bool ImageHost::IsOpaque() { + const TimedImage* img = ChooseImage(); + if (!img) { + return false; + } + + if (img->mPictureRect.Width() == 0 || img->mPictureRect.Height() == 0 || + !img->mTextureHost) { + return false; + } + + gfx::SurfaceFormat format = img->mTextureHost->GetFormat(); + if (gfx::IsOpaque(format)) { + return true; + } + return false; +} + +already_AddRefed ImageHost::GenEffect( + const gfx::SamplingFilter aSamplingFilter) { + const TimedImage* img = ChooseImage(); + if (!img) { + return nullptr; + } + SetCurrentTextureHost(img->mTextureHost); + if (!mCurrentTextureHost->BindTextureSource(mCurrentTextureSource)) { + return nullptr; + } + bool isAlphaPremultiplied = true; + if (mCurrentTextureHost->GetFlags() & TextureFlags::NON_PREMULTIPLIED) { + isAlphaPremultiplied = false; + } + + return CreateTexturedEffect(mCurrentTextureHost, mCurrentTextureSource, + aSamplingFilter, isAlphaPremultiplied); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/ImageHost.h b/gfx/layers/composite/ImageHost.h new file mode 100644 index 0000000000..5d712d5bdd --- /dev/null +++ b/gfx/layers/composite/ImageHost.h @@ -0,0 +1,138 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_IMAGEHOST_H +#define MOZILLA_GFX_IMAGEHOST_H + +#include // for FILE +#include "CompositableHost.h" // for CompositableHost +#include "mozilla/Attributes.h" // for override +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/gfx/MatrixFwd.h" // for Matrix4x4 +#include "mozilla/gfx/Point.h" // for Point +#include "mozilla/gfx/Polygon.h" // for Polygon +#include "mozilla/gfx/Rect.h" // for Rect +#include "mozilla/gfx/Types.h" // for SamplingFilter +#include "mozilla/layers/CompositorTypes.h" // for TextureInfo, etc +#include "mozilla/layers/ImageComposite.h" // for ImageComposite +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor +#include "mozilla/layers/LayersTypes.h" // for LayerRenderState, etc +#include "mozilla/layers/TextureHost.h" // for TextureHost, etc +#include "mozilla/mozalloc.h" // for operator delete +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsRect.h" // for mozilla::gfx::IntRect +#include "nsRegionFwd.h" // for nsIntRegion +#include "nscore.h" // for nsACString + +namespace mozilla { +namespace layers { + +class Compositor; +struct EffectChain; +class HostLayerManager; + +/** + * ImageHost. Works with ImageClientSingle and ImageClientBuffered + */ +class ImageHost : public CompositableHost, public ImageComposite { + public: + explicit ImageHost(const TextureInfo& aTextureInfo); + virtual ~ImageHost(); + + CompositableType GetType() override { return mTextureInfo.mCompositableType; } + ImageHost* AsImageHost() override { return this; } + + void Composite(Compositor* aCompositor, LayerComposite* aLayer, + EffectChain& aEffectChain, float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::SamplingFilter aSamplingFilter, + const gfx::IntRect& aClipRect, + const nsIntRegion* aVisibleRegion = nullptr, + const Maybe& aGeometry = Nothing()) override; + + void UseTextureHost(const nsTArray& aTextures) override; + + void RemoveTextureHost(TextureHost* aTexture) override; + + TextureHost* GetAsTextureHost(gfx::IntRect* aPictureRect = nullptr) override; + + void Attach(Layer* aLayer, TextureSourceProvider* aProvider, + AttachFlags aFlags = NO_FLAGS) override; + + void SetTextureSourceProvider(TextureSourceProvider* aProvider) override; + + gfx::IntSize GetImageSize() override; + + void PrintInfo(std::stringstream& aStream, const char* aPrefix) override; + + void Dump(std::stringstream& aStream, const char* aPrefix = "", + bool aDumpHtml = false) override; + + already_AddRefed GetAsSurface() override; + + bool Lock() override; + + void Unlock() override; + + already_AddRefed GenEffect( + const gfx::SamplingFilter aSamplingFilter) override; + + void SetCurrentTextureHost(TextureHost* aTexture); + + void CleanupResources() override; + + bool IsOpaque(); + + uint32_t GetDroppedFrames() override { return GetDroppedFramesAndReset(); } + + struct RenderInfo { + int imageIndex; + const TimedImage* img; + RefPtr host; + + RenderInfo() : imageIndex(-1), img(nullptr) {} + }; + + // Acquire rendering information for the current frame. + bool PrepareToRender(TextureSourceProvider* aProvider, RenderInfo* aOutInfo); + + // Acquire the TextureSource for the currently prepared frame. + RefPtr AcquireTextureSource(const RenderInfo& aInfo); + + // Send ImageComposite notifications. + void FinishRendering(const RenderInfo& aInfo); + + // This should only be called inside a lock, or during rendering. It is + // infallible to enforce this. + TextureHost* CurrentTextureHost() const { + MOZ_ASSERT(mCurrentTextureHost); + return mCurrentTextureHost; + } + + protected: + // ImageComposite + TimeStamp GetCompositionTime() const override; + CompositionOpportunityId GetCompositionOpportunityId() const override; + void AppendImageCompositeNotification( + const ImageCompositeNotificationInfo& aInfo) const override; + + // Use a simple RefPtr because the same texture is already held by a + // a CompositableTextureHostRef in the array of TimedImage. + // See the comment in CompositableTextureRef for more details. + RefPtr mCurrentTextureHost; + CompositableTextureSourceRef mCurrentTextureSource; + // When doing texture uploads it's best to alternate between two (or three) + // texture sources so that the texture we upload to isn't being used by + // the GPU to composite the previous frame. + RefPtr mExtraTextureSource; + + bool mLocked; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/composite/ImageLayerComposite.cpp b/gfx/layers/composite/ImageLayerComposite.cpp new file mode 100644 index 0000000000..9fab48e280 --- /dev/null +++ b/gfx/layers/composite/ImageLayerComposite.cpp @@ -0,0 +1,209 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ImageLayerComposite.h" +#include "CompositableHost.h" // for CompositableHost +#include "Layers.h" // for WriteSnapshotToDumpFile, etc +#include "gfx2DGlue.h" // for ToFilter +#include "gfxEnv.h" // for gfxEnv +#include "gfxRect.h" // for gfxRect +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/gfx/Matrix.h" // for Matrix4x4 +#include "mozilla/gfx/Point.h" // for IntSize, Point +#include "mozilla/gfx/Rect.h" // for Rect +#include "mozilla/layers/Compositor.h" // for Compositor +#include "mozilla/layers/Effects.h" // for EffectChain +#include "mozilla/layers/ImageHost.h" // for ImageHost +#include "mozilla/layers/LayerManagerCompositeUtils.h" +#include "mozilla/layers/TextureHost.h" // for TextureHost, etc +#include "mozilla/mozalloc.h" // for operator delete +#include "nsAString.h" +#include "mozilla/RefPtr.h" // for nsRefPtr +#include "nsDebug.h" // for NS_ASSERTION +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "nsString.h" // for nsAutoCString + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +ImageLayerComposite::ImageLayerComposite(LayerManagerComposite* aManager) + : ImageLayer(aManager, nullptr), + LayerComposite(aManager), + mImageHost(nullptr) { + MOZ_COUNT_CTOR(ImageLayerComposite); + mImplData = static_cast(this); +} + +ImageLayerComposite::~ImageLayerComposite() { + MOZ_COUNT_DTOR(ImageLayerComposite); + MOZ_ASSERT(mDestroyed); + + CleanupResources(); +} + +bool ImageLayerComposite::SetCompositableHost(CompositableHost* aHost) { + switch (aHost->GetType()) { + case CompositableType::IMAGE: { + ImageHost* newImageHost = static_cast(aHost); + if (mImageHost && newImageHost != mImageHost) { + mImageHost->Detach(this); + } + mImageHost = newImageHost; + return true; + } + default: + return false; + } +} + +void ImageLayerComposite::Disconnect() { Destroy(); } + +Layer* ImageLayerComposite::GetLayer() { return this; } + +void ImageLayerComposite::SetLayerManager(HostLayerManager* aManager) { + LayerComposite::SetLayerManager(aManager); + mManager = aManager; + if (mImageHost) { + mImageHost->SetTextureSourceProvider(mCompositor); + if (aManager && mImageHost->GetAsyncRef()) { + mImageHost->SetCompositorBridgeID(aManager->GetCompositorBridgeID()); + } + } +} + +void ImageLayerComposite::RenderLayer(const IntRect& aClipRect, + const Maybe& aGeometry) { + if (!mImageHost || !mImageHost->IsAttached()) { + return; + } + +#ifdef MOZ_DUMP_PAINTING + if (gfxEnv::DumpCompositorTextures()) { + RefPtr surf = mImageHost->GetAsSurface(); + if (surf) { + WriteSnapshotToDumpFile(this, surf); + } + } +#endif + + mCompositor->MakeCurrent(); + + RenderWithAllMasks(this, mCompositor, aClipRect, + [&](EffectChain& effectChain, const IntRect& clipRect) { + mImageHost->SetTextureSourceProvider(mCompositor); + mImageHost->Composite(mCompositor, this, effectChain, + GetEffectiveOpacity(), + GetEffectiveTransformForBuffer(), + GetSamplingFilter(), clipRect); + }); + mImageHost->BumpFlashCounter(); +} + +void ImageLayerComposite::ComputeEffectiveTransforms( + const gfx::Matrix4x4& aTransformToSurface) { + gfx::Matrix4x4 local = GetLocalTransform(); + + // Snap image edges to pixel boundaries + gfxRect sourceRect(0, 0, 0, 0); + if (mImageHost && mImageHost->IsAttached()) { + IntSize size = mImageHost->GetImageSize(); + sourceRect.SizeTo(size.width, size.height); + } + // Snap our local transform first, and snap the inherited transform as well. + // This makes our snapping equivalent to what would happen if our content + // was drawn into a PaintedLayer (gfxContext would snap using the local + // transform, then we'd snap again when compositing the PaintedLayer). + mEffectiveTransform = SnapTransform(local, sourceRect, nullptr) * + SnapTransformTranslation(aTransformToSurface, nullptr); + + if (mScaleMode != ScaleMode::SCALE_NONE && !sourceRect.IsZeroArea()) { + NS_ASSERTION(mScaleMode == ScaleMode::STRETCH, + "No other scalemodes than stretch and none supported yet."); + local.PreScale(mScaleToSize.width / sourceRect.Width(), + mScaleToSize.height / sourceRect.Height(), 1.0); + + mEffectiveTransformForBuffer = + SnapTransform(local, sourceRect, nullptr) * + SnapTransformTranslation(aTransformToSurface, nullptr); + } else { + mEffectiveTransformForBuffer = mEffectiveTransform; + } + + ComputeEffectiveTransformForMaskLayers(aTransformToSurface); +} + +bool ImageLayerComposite::IsOpaque() { + if (!mImageHost || !mImageHost->IsAttached()) { + return false; + } + + // TODO: Handle ScaleMode::NONE where the image + // still covers the whole Layer. + if (mScaleMode == ScaleMode::STRETCH) { + if ((GetContentFlags() & CONTENT_OPAQUE) && !mImageHost->IsOpaque()) { + NS_WARNING("Must have an opaque ImageHost if we reported CONTENT_OPAQUE"); + } + return mImageHost->IsOpaque(); + } + return false; +} + +nsIntRegion ImageLayerComposite::GetFullyRenderedRegion() { + if (!mImageHost || !mImageHost->IsAttached()) { + return GetShadowVisibleRegion().ToUnknownRegion(); + } + + if (mScaleMode == ScaleMode::STRETCH) { + nsIntRegion shadowVisibleRegion; + shadowVisibleRegion.And(GetShadowVisibleRegion().ToUnknownRegion(), + nsIntRegion(gfx::IntRect(0, 0, mScaleToSize.width, + mScaleToSize.height))); + return shadowVisibleRegion; + } + + return GetShadowVisibleRegion().ToUnknownRegion(); +} + +CompositableHost* ImageLayerComposite::GetCompositableHost() { + if (mImageHost && mImageHost->IsAttached()) { + return mImageHost.get(); + } + + return nullptr; +} + +void ImageLayerComposite::CleanupResources() { + if (mImageHost) { + mImageHost->CleanupResources(); + mImageHost->Detach(this); + } + mImageHost = nullptr; +} + +gfx::SamplingFilter ImageLayerComposite::GetSamplingFilter() { + return mSamplingFilter; +} + +void ImageLayerComposite::GenEffectChain(EffectChain& aEffect) { + aEffect.mLayerRef = this; + aEffect.mPrimaryEffect = mImageHost->GenEffect(GetSamplingFilter()); +} + +void ImageLayerComposite::PrintInfo(std::stringstream& aStream, + const char* aPrefix) { + ImageLayer::PrintInfo(aStream, aPrefix); + if (mImageHost && mImageHost->IsAttached()) { + aStream << "\n"; + nsAutoCString pfx(aPrefix); + pfx += " "; + mImageHost->PrintInfo(aStream, pfx.get()); + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/ImageLayerComposite.h b/gfx/layers/composite/ImageLayerComposite.h new file mode 100644 index 0000000000..f0725c2852 --- /dev/null +++ b/gfx/layers/composite/ImageLayerComposite.h @@ -0,0 +1,78 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_ImageLayerComposite_H +#define GFX_ImageLayerComposite_H + +#include "GLTextureImage.h" // for TextureImage +#include "ImageLayers.h" // for ImageLayer +#include "mozilla/Attributes.h" // for override +#include "mozilla/gfx/Rect.h" +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/layers/LayerManagerComposite.h" // for LayerComposite, etc +#include "mozilla/layers/LayersTypes.h" // for LayerRenderState, etc +#include "nsISupportsImpl.h" // for TextureImage::AddRef, etc +#include "nscore.h" // for nsACString +#include "CompositableHost.h" // for CompositableHost + +namespace mozilla { +namespace layers { + +class ImageHost; +class Layer; + +class ImageLayerComposite : public ImageLayer, public LayerComposite { + typedef gl::TextureImage TextureImage; + + public: + explicit ImageLayerComposite(LayerManagerComposite* aManager); + + protected: + virtual ~ImageLayerComposite(); + + public: + void Disconnect() override; + + bool SetCompositableHost(CompositableHost* aHost) override; + + Layer* GetLayer() override; + + void SetLayerManager(HostLayerManager* aManager) override; + + void RenderLayer(const gfx::IntRect& aClipRect, + const Maybe& aGeometry) override; + + void ComputeEffectiveTransforms( + const mozilla::gfx::Matrix4x4& aTransformToSurface) override; + + void CleanupResources() override; + + CompositableHost* GetCompositableHost() override; + + void GenEffectChain(EffectChain& aEffect) override; + + HostLayer* AsHostLayer() override { return this; } + + const char* Name() const override { return "ImageLayerComposite"; } + + bool IsOpaque() override; + + nsIntRegion GetFullyRenderedRegion() override; + + protected: + void PrintInfo(std::stringstream& aStream, const char* aPrefix) override; + + private: + gfx::SamplingFilter GetSamplingFilter(); + + private: + RefPtr mImageHost; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_ImageLayerComposite_H */ diff --git a/gfx/layers/composite/LayerManagerComposite.cpp b/gfx/layers/composite/LayerManagerComposite.cpp new file mode 100644 index 0000000000..8ad4e0310d --- /dev/null +++ b/gfx/layers/composite/LayerManagerComposite.cpp @@ -0,0 +1,1780 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "LayerManagerComposite.h" +#include // for size_t +#include // for uint16_t, uint32_t +#include "CanvasLayerComposite.h" // for CanvasLayerComposite +#include "ColorLayerComposite.h" // for ColorLayerComposite +#include "CompositableHost.h" // for CompositableHost +#include "ContainerLayerComposite.h" // for ContainerLayerComposite, etc +#include "Diagnostics.h" +#include "FPSCounter.h" // for FPSState, FPSCounter +#include "FrameMetrics.h" // for FrameMetrics +#include "GeckoProfiler.h" // for profiler_* +#include "ImageLayerComposite.h" // for ImageLayerComposite +#include "Layers.h" // for Layer, ContainerLayer, etc +#include "LayerScope.h" // for LayerScope Tool +#include "LayerTreeInvalidation.h" +#include "protobuf/LayerScopePacket.pb.h" // for protobuf (LayerScope) +#include "PaintedLayerComposite.h" // for PaintedLayerComposite +#include "TiledContentHost.h" +#include "Units.h" // for ScreenIntRect +#include "UnitTransforms.h" // for ViewAs +#include "apz/src/AsyncPanZoomController.h" // for AsyncPanZoomController +#include "gfxEnv.h" // for gfxEnv + +#ifdef XP_MACOSX +# include "gfxPlatformMac.h" +#endif +#include "gfxRect.h" // for gfxRect +#include "gfxUtils.h" // for frame color util +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/RefPtr.h" // for RefPtr, already_AddRefed +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/gfx/2D.h" // for DrawTarget +#include "mozilla/gfx/Matrix.h" // for Matrix4x4 +#include "mozilla/gfx/Point.h" // for IntSize, Point +#include "mozilla/gfx/Rect.h" // for Rect +#include "mozilla/gfx/Types.h" // for Color, SurfaceFormat +#include "mozilla/layers/Compositor.h" // for Compositor +#include "mozilla/layers/CompositorOGL.h" +#include "mozilla/layers/CompositorTypes.h" +#include "mozilla/layers/Effects.h" // for Effect, EffectChain, etc +#include "mozilla/layers/LayerMetricsWrapper.h" // for LayerMetricsWrapper +#include "mozilla/layers/LayersTypes.h" // for etc +#include "mozilla/layers/NativeLayer.h" +#include "mozilla/layers/UiCompositorControllerParent.h" +#include "mozilla/widget/CompositorWidget.h" // for WidgetRenderingContext +#include "ipc/CompositorBench.h" // for CompositorBench +#include "ipc/SurfaceDescriptor.h" +#include "mozilla/mozalloc.h" // for operator new, etc +#include "nsAppRunner.h" +#include "mozilla/RefPtr.h" // for nsRefPtr +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsDebug.h" // for NS_WARNING, etc +#include "nsISupportsImpl.h" // for Layer::AddRef, etc +#include "nsPoint.h" // for nsIntPoint +#include "nsRect.h" // for mozilla::gfx::IntRect +#include "nsRegion.h" // for nsIntRegion, etc +#if defined(MOZ_WIDGET_ANDROID) +# include +# include +# include "mozilla/jni/Utils.h" +# include "mozilla/widget/AndroidCompositorWidget.h" +# include "GLConsts.h" +# include "GLContextEGL.h" +# include "GLContextProvider.h" +# include "mozilla/Unused.h" +# include "ScopedGLHelpers.h" +#endif +#include "GeckoProfiler.h" +#include "TextRenderer.h" // for TextRenderer +#include "mozilla/layers/CompositorBridgeParent.h" +#include "TreeTraversal.h" // for ForEachNode +#include "CompositionRecorder.h" + +#ifdef USE_SKIA +# include "PaintCounter.h" // For PaintCounter +#endif + +class gfxContext; + +namespace mozilla { +namespace layers { + +class ImageLayer; + +using namespace mozilla::gfx; +using namespace mozilla::gl; + +static LayerComposite* ToLayerComposite(Layer* aLayer) { + return static_cast(aLayer->ImplData()); +} + +static void ClearSubtree(Layer* aLayer) { + ForEachNode(aLayer, [](Layer* layer) { + ToLayerComposite(layer)->CleanupResources(); + }); +} + +void LayerManagerComposite::ClearCachedResources(Layer* aSubtree) { + MOZ_ASSERT(!aSubtree || aSubtree->Manager() == this); + Layer* subtree = aSubtree ? aSubtree : mRoot.get(); + if (!subtree) { + return; + } + + ClearSubtree(subtree); + // FIXME [bjacob] + // XXX the old LayerManagerOGL code had a mMaybeInvalidTree that it set to + // true here. Do we need that? +} + +HostLayerManager::HostLayerManager() + : mDebugOverlayWantsNextFrame(false), + mWarningLevel(0.0f), + mCompositorBridgeID(0), + mLastPaintTime(TimeDuration::Forever()), + mRenderStartTime(TimeStamp::Now()) {} + +HostLayerManager::~HostLayerManager() = default; + +void HostLayerManager::RecordPaintTimes(const PaintTiming& aTiming) { + mDiagnostics->RecordPaintTimes(aTiming); +} + +void HostLayerManager::RecordUpdateTime(float aValue) { + mDiagnostics->RecordUpdateTime(aValue); +} + +void HostLayerManager::WriteCollectedFrames() { + if (mCompositionRecorder) { + mCompositionRecorder->WriteCollectedFrames(); + mCompositionRecorder = nullptr; + } +} + +Maybe HostLayerManager::GetCollectedFrames() { + Maybe maybeFrames; + + if (mCompositionRecorder) { + maybeFrames.emplace(mCompositionRecorder->GetCollectedFrames()); + mCompositionRecorder = nullptr; + } + + return maybeFrames; +} + +/** + * LayerManagerComposite + */ +LayerManagerComposite::LayerManagerComposite(Compositor* aCompositor) + : mUnusedApzTransformWarning(false), + mDisabledApzWarning(false), + mCompositor(aCompositor), + mInTransaction(false), + mIsCompositorReady(false) +#if defined(MOZ_WIDGET_ANDROID) + , + mScreenPixelsTarget(nullptr) +#endif // defined(MOZ_WIDGET_ANDROID) +{ + mTextRenderer = new TextRenderer(); + mDiagnostics = MakeUnique(); + MOZ_ASSERT(aCompositor); + mNativeLayerRoot = aCompositor->GetWidget()->GetNativeLayerRoot(); + if (mNativeLayerRoot) { + mSurfacePoolHandle = aCompositor->GetSurfacePoolHandle(); + MOZ_RELEASE_ASSERT(mSurfacePoolHandle); + } + +#ifdef USE_SKIA + mPaintCounter = nullptr; +#endif +} + +LayerManagerComposite::~LayerManagerComposite() { Destroy(); } + +void LayerManagerComposite::Destroy() { + if (!mDestroyed) { + mCompositor->GetWidget()->CleanupWindowEffects(); + if (mRoot) { + RootLayer()->Destroy(); + } + mCompositor->CancelFrame(); + mRoot = nullptr; + mClonedLayerTreeProperties = nullptr; + mProfilerScreenshotGrabber.Destroy(); + + if (mNativeLayerRoot) { + if (mGPUStatsLayer) { + mNativeLayerRoot->RemoveLayer(mGPUStatsLayer); + mGPUStatsLayer = nullptr; + } + if (mUnusedTransformWarningLayer) { + mNativeLayerRoot->RemoveLayer(mUnusedTransformWarningLayer); + mUnusedTransformWarningLayer = nullptr; + } + if (mDisabledApzWarningLayer) { + mNativeLayerRoot->RemoveLayer(mDisabledApzWarningLayer); + mDisabledApzWarningLayer = nullptr; + } + for (const auto& nativeLayer : mNativeLayers) { + mNativeLayerRoot->RemoveLayer(nativeLayer); + } + mNativeLayers.clear(); + mNativeLayerRoot = nullptr; + } + mDestroyed = true; + +#ifdef USE_SKIA + mPaintCounter = nullptr; +#endif + } +} + +void LayerManagerComposite::UpdateRenderBounds(const IntRect& aRect) { + mRenderBounds = aRect; +} + +bool LayerManagerComposite::AreComponentAlphaLayersEnabled() { + return mCompositor->GetBackendType() != LayersBackend::LAYERS_BASIC && + mCompositor->SupportsEffect(EffectTypes::COMPONENT_ALPHA) && + LayerManager::AreComponentAlphaLayersEnabled(); +} + +bool LayerManagerComposite::BeginTransaction(const nsCString& aURL) { + mInTransaction = true; + + if (!mCompositor->Ready()) { + return false; + } + + mIsCompositorReady = true; + return true; +} + +void LayerManagerComposite::BeginTransactionWithDrawTarget( + DrawTarget* aTarget, const IntRect& aRect) { + mInTransaction = true; + + if (!mCompositor->Ready()) { + return; + } + +#ifdef MOZ_LAYERS_HAVE_LOG + MOZ_LAYERS_LOG(("[----- BeginTransaction")); + Log(); +#endif + + if (mDestroyed) { + NS_WARNING("Call on destroyed layer manager"); + return; + } + + mIsCompositorReady = true; + mTarget = aTarget; + mTargetBounds = aRect; +} + +template +static IntRectTyped TransformRect(const IntRectTyped& aRect, + const Matrix& aTransform, + bool aRoundIn = false) { + if (aRect.IsEmpty()) { + return IntRectTyped(); + } + + Rect rect(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()); + rect = aTransform.TransformBounds(rect); + if (aRoundIn) { + MOZ_ASSERT(aTransform.PreservesAxisAlignedRectangles()); + rect.RoundIn(); + } else { + rect.RoundOut(); + } + + IntRect intRect; + if (!rect.ToIntRect(&intRect)) { + intRect = IntRect::MaxIntRect(); + } + + return ViewAs(intRect); +} + +template +static IntRectTyped TransformRect(const IntRectTyped& aRect, + const Matrix4x4& aTransform, + bool aRoundIn = false) { + if (aRect.IsEmpty()) { + return IntRectTyped(); + } + + Rect rect(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()); + rect = aTransform.TransformAndClipBounds(rect, Rect::MaxIntRect()); + if (aRoundIn) { + rect.RoundIn(); + } else { + rect.RoundOut(); + } + + IntRect intRect; + if (!rect.ToIntRect(&intRect)) { + intRect = IntRect::MaxIntRect(); + } + + return ViewAs(intRect); +} + +template +static IntRectTyped TransformRectRoundIn( + const IntRectTyped& aRect, const MatrixType& aTransform) { + return TransformRect(aRect, aTransform, true); +} + +template +static void AddTransformedRegion(IntRegionTyped& aDest, + const IntRegionTyped& aSource, + const MatrixType& aTransform) { + for (auto iter = aSource.RectIter(); !iter.Done(); iter.Next()) { + aDest.Or(aDest, TransformRect(iter.Get(), aTransform)); + } + aDest.SimplifyOutward(20); +} + +template +static void AddTransformedRegionRoundIn(IntRegionTyped& aDest, + const IntRegionTyped& aSource, + const MatrixType& aTransform) { + for (auto iter = aSource.RectIter(); !iter.Done(); iter.Next()) { + aDest.Or(aDest, TransformRectRoundIn(iter.Get(), aTransform)); + } +} + +void LayerManagerComposite::PostProcessLayers(nsIntRegion& aOpaqueRegion) { + LayerIntRegion visible; + LayerComposite* rootComposite = + static_cast(mRoot->AsHostLayer()); + PostProcessLayers( + mRoot, aOpaqueRegion, visible, + ViewAs( + rootComposite->GetShadowClipRect(), + PixelCastJustification::RenderTargetIsParentLayerForRoot), + Nothing(), true); +} + +// We want to skip directly through ContainerLayers that don't have an +// intermediate surface. We compute occlusions for leaves and intermediate +// surfaces against the layer that they actually composite into so that we can +// use the final (snapped) effective transform. +static bool ShouldProcessLayer(Layer* aLayer) { + if (!aLayer->AsContainerLayer()) { + return true; + } + + return aLayer->AsContainerLayer()->UseIntermediateSurface(); +} + +void LayerManagerComposite::PostProcessLayers( + Layer* aLayer, nsIntRegion& aOpaqueRegion, LayerIntRegion& aVisibleRegion, + const Maybe& aRenderTargetClip, + const Maybe& aClipFromAncestors, + bool aCanContributeOpaque) { + // Compute a clip that's the combination of our layer clip with the clip + // from our ancestors. + LayerComposite* composite = + static_cast(aLayer->AsHostLayer()); + Maybe layerClip = composite->GetShadowClipRect(); + MOZ_ASSERT(!layerClip || !aLayer->Combines3DTransformWithAncestors(), + "The layer with a clip should not participate " + "a 3D rendering context"); + Maybe outsideClip = + IntersectMaybeRects(layerClip, aClipFromAncestors); + + Maybe insideClip; + if (aLayer->Extend3DContext()) { + // If we're preserve-3d just pass the clip rect down directly, and we'll do + // the conversion at the preserve-3d leaf Layer. + if (outsideClip) { + insideClip = Some(ViewAs( + *outsideClip, PixelCastJustification::MovingDownToChildren)); + } + } else if (outsideClip) { + // Convert the combined clip into our pre-transform coordinate space, so + // that it can later be intersected with our visible region. + // If our transform is a perspective, there's no meaningful insideClip rect + // we can compute (it would need to be a cone). + Matrix4x4 localTransform = aLayer->ComputeTransformToPreserve3DRoot(); + if (!localTransform.HasPerspectiveComponent() && localTransform.Invert()) { + LayerRect insideClipFloat = + UntransformBy(ViewAs(localTransform), + ParentLayerRect(*outsideClip), LayerRect::MaxIntRect()) + .valueOr(LayerRect()); + insideClipFloat.RoundOut(); + LayerIntRect insideClipInt; + if (insideClipFloat.ToIntRect(&insideClipInt)) { + insideClip = Some(insideClipInt); + } + } + } + + Maybe ancestorClipForChildren; + if (insideClip) { + ancestorClipForChildren = Some(ViewAs( + *insideClip, PixelCastJustification::MovingDownToChildren)); + } + + nsIntRegion dummy; + nsIntRegion& opaqueRegion = aOpaqueRegion; + if (aLayer->Extend3DContext() || aLayer->Combines3DTransformWithAncestors()) { + opaqueRegion = dummy; + } + + if (!ShouldProcessLayer(aLayer)) { + MOZ_ASSERT(aLayer->AsContainerLayer() && + !aLayer->AsContainerLayer()->UseIntermediateSurface()); + // For layers participating 3D rendering context, their visible + // region should be empty (invisible), so we pass through them + // without doing anything. + for (Layer* child = aLayer->GetLastChild(); child; + child = child->GetPrevSibling()) { + LayerComposite* childComposite = + static_cast(child->AsHostLayer()); + Maybe renderTargetClip = aRenderTargetClip; + if (childComposite->GetShadowClipRect()) { + RenderTargetIntRect clip = TransformBy( + ViewAs( + aLayer->GetEffectiveTransform(), + PixelCastJustification::RenderTargetIsParentLayerForRoot), + *childComposite->GetShadowClipRect()); + renderTargetClip = IntersectMaybeRects(renderTargetClip, Some(clip)); + } + + PostProcessLayers( + child, opaqueRegion, aVisibleRegion, renderTargetClip, + ancestorClipForChildren, + aCanContributeOpaque & + !(aLayer->GetContentFlags() & Layer::CONTENT_BACKFACE_HIDDEN)); + } + return; + } + + nsIntRegion localOpaque; + // Treat layers on the path to the root of the 3D rendering context as + // a giant layer if it is a leaf. + Matrix4x4 transform = aLayer->GetEffectiveTransform(); + Matrix transform2d; + bool canTransformOpaqueRegion = false; + // If aLayer has a simple transform (only an integer translation) then we + // can easily convert aOpaqueRegion into pre-transform coordinates and include + // that region. + if (aCanContributeOpaque && + !(aLayer->GetContentFlags() & Layer::CONTENT_BACKFACE_HIDDEN) && + transform.Is2D(&transform2d) && + transform2d.PreservesAxisAlignedRectangles()) { + Matrix inverse = transform2d; + inverse.Invert(); + AddTransformedRegionRoundIn(localOpaque, opaqueRegion, inverse); + canTransformOpaqueRegion = true; + } + + // Save the value of localOpaque, which currently stores the region obscured + // by siblings (and uncles and such), before our descendants contribute to it. + nsIntRegion obscured = localOpaque; + + // Recurse on our descendants, in front-to-back order. In this process: + // - Occlusions are computed for them, and they contribute to localOpaque. + // - They recalculate their visible regions, taking ancestorClipForChildren + // into account, and accumulate them into descendantsVisibleRegion. + LayerIntRegion descendantsVisibleRegion; + + bool hasPreserve3DChild = false; + for (Layer* child = aLayer->GetLastChild(); child; + child = child->GetPrevSibling()) { + MOZ_ASSERT(aLayer->AsContainerLayer()->UseIntermediateSurface()); + LayerComposite* childComposite = + static_cast(child->AsHostLayer()); + PostProcessLayers( + child, localOpaque, descendantsVisibleRegion, + ViewAs( + childComposite->GetShadowClipRect(), + PixelCastJustification::RenderTargetIsParentLayerForRoot), + ancestorClipForChildren, true); + if (child->Extend3DContext()) { + hasPreserve3DChild = true; + } + } + + // Recalculate our visible region. + LayerIntRegion visible = composite->GetShadowVisibleRegion(); + + // If we have descendants, throw away the visible region stored on this + // layer, and use the region accumulated by our descendants instead. + if (aLayer->GetFirstChild() && !hasPreserve3DChild) { + visible = descendantsVisibleRegion; + } + + // Subtract any areas that we know to be opaque. + if (!obscured.IsEmpty()) { + visible.SubOut(LayerIntRegion::FromUnknownRegion(obscured)); + } + + // Clip the visible region using the combined clip. + if (insideClip) { + visible.AndWith(*insideClip); + } + composite->SetShadowVisibleRegion(visible); + + // Transform the newly calculated visible region into our parent's space, + // apply our clip to it (if any), and accumulate it into |aVisibleRegion| + // for the caller to use. + ParentLayerIntRegion visibleParentSpace = + TransformBy(ViewAs(transform), visible); + aVisibleRegion.OrWith(ViewAs( + visibleParentSpace, PixelCastJustification::MovingDownToChildren)); + + // If we have a simple transform, then we can add our opaque area into + // aOpaqueRegion. + if (canTransformOpaqueRegion && !aLayer->HasMaskLayers() && + aLayer->IsOpaqueForVisibility()) { + if (aLayer->IsOpaque()) { + localOpaque.OrWith(composite->GetFullyRenderedRegion()); + } + nsIntRegion parentSpaceOpaque; + AddTransformedRegionRoundIn(parentSpaceOpaque, localOpaque, transform2d); + if (aRenderTargetClip) { + parentSpaceOpaque.AndWith(aRenderTargetClip->ToUnknownRect()); + } + opaqueRegion.OrWith(parentSpaceOpaque); + } +} + +void LayerManagerComposite::EndTransaction(const TimeStamp& aTimeStamp, + EndTransactionFlags aFlags) { + NS_ASSERTION(mInTransaction, "Didn't call BeginTransaction?"); + NS_ASSERTION(!(aFlags & END_NO_COMPOSITE), + "Shouldn't get END_NO_COMPOSITE here"); + mInTransaction = false; + mRenderStartTime = TimeStamp::Now(); + + // Ensure we read unlock textures, even if we end up + // not compositing this frame. + TextureSourceProvider::AutoReadUnlockTextures unlock(mCompositor); + + if (!mIsCompositorReady) { + return; + } + mIsCompositorReady = false; + +#ifdef MOZ_LAYERS_HAVE_LOG + MOZ_LAYERS_LOG((" ----- (beginning paint)")); + Log(); +#endif + + if (mDestroyed) { + NS_WARNING("Call on destroyed layer manager"); + return; + } + + // Set composition timestamp here because we need it in + // ComputeEffectiveTransforms (so the correct video frame size is picked) and + // also to compute invalid regions properly. + SetCompositionTime(aTimeStamp); + + if (mRoot && !(aFlags & END_NO_IMMEDIATE_REDRAW)) { + MOZ_ASSERT(!aTimeStamp.IsNull()); + UpdateAndRender(); + mCompositor->FlushPendingNotifyNotUsed(); + } + + mTarget = nullptr; + +#ifdef MOZ_LAYERS_HAVE_LOG + Log(); + MOZ_LAYERS_LOG(("]----- EndTransaction")); +#endif +} + +void LayerManagerComposite::SetRoot(Layer* aLayer) { mRoot = aLayer; } + +void LayerManagerComposite::UpdateAndRender() { + mCompositionOpportunityId = mCompositionOpportunityId.Next(); + + if (gfxEnv::SkipComposition()) { + mInvalidRegion.SetEmpty(); + return; + } + + // The results of our drawing always go directly into a pixel buffer, + // so we don't need to pass any global transform here. + mRoot->ComputeEffectiveTransforms(gfx::Matrix4x4()); + + nsIntRegion opaque; + PostProcessLayers(opaque); + + if (mClonedLayerTreeProperties) { + // We need to compute layer tree differences even if we're not going to + // immediately use the resulting damage area, since ComputeDifferences + // is also responsible for invalidates intermediate surfaces in + // ContainerLayers. + + nsIntRegion changed; + const bool overflowed = !mClonedLayerTreeProperties->ComputeDifferences( + mRoot, changed, nullptr); + + if (overflowed) { + changed = mRenderBounds; + } + + mInvalidRegion.Or(mInvalidRegion, changed); + } + + nsIntRegion invalid; + if (mTarget) { + // Since we're composing to an external target, we're not going to use + // the damage region from layers changes - we want to composite + // everything in the target bounds. The layers damage region has been + // stored in mInvalidRegion and will be picked up by the next window + // composite. + invalid = mTargetBounds; + } else { + if (!mClonedLayerTreeProperties) { + // If we didn't have a previous layer tree, invalidate the entire render + // area. + mInvalidRegion = mRenderBounds; + } + + invalid = mInvalidRegion; + } + + if (invalid.IsEmpty()) { + // Composition requested, but nothing has changed. Don't do any work. + mClonedLayerTreeProperties = LayerProperties::CloneFrom(GetRoot()); + mProfilerScreenshotGrabber.NotifyEmptyFrame(); + + // Discard the current payloads. These payloads did not require a composite + // (they caused no changes to anything visible), so we don't want to measure + // their latency. + mPayload.Clear(); + + return; + } + + // We don't want our debug overlay to cause more frames to happen + // so we will invalidate after we've decided if something changed. + // Only invalidate if we're not using native layers. When using native layers, + // UpdateDebugOverlayNativeLayers will repaint the appropriate layer areas. + if (!mNativeLayerRoot) { + InvalidateDebugOverlay(invalid, mRenderBounds); + } + + bool rendered = Render(invalid, opaque); +#if defined(MOZ_WIDGET_ANDROID) + RenderToPresentationSurface(); +#endif + + if (!mTarget && rendered) { + mInvalidRegion.SetEmpty(); + } + + // Update cached layer tree information. + mClonedLayerTreeProperties = LayerProperties::CloneFrom(GetRoot()); +} + +already_AddRefed LayerManagerComposite::CreateOptimalMaskDrawTarget( + const IntSize& aSize) { + MOZ_CRASH("Should only be called on the drawing side"); + return nullptr; +} + +LayerComposite* LayerManagerComposite::RootLayer() const { + if (mDestroyed) { + NS_WARNING("Call on destroyed layer manager"); + return nullptr; + } + + return ToLayerComposite(mRoot); +} + +void LayerManagerComposite::InvalidateDebugOverlay(nsIntRegion& aInvalidRegion, + const IntRect& aBounds) { + bool drawFps = StaticPrefs::layers_acceleration_draw_fps(); + bool drawFrameColorBars = StaticPrefs::gfx_draw_color_bars(); + + if (drawFps) { + aInvalidRegion.Or(aInvalidRegion, nsIntRect(0, 0, 650, 400)); + } + if (drawFrameColorBars) { + aInvalidRegion.Or(aInvalidRegion, nsIntRect(0, 0, 10, aBounds.Height())); + } + +#ifdef USE_SKIA + bool drawPaintTimes = StaticPrefs::gfx_content_always_paint(); + if (drawPaintTimes) { + aInvalidRegion.Or(aInvalidRegion, nsIntRect(PaintCounter::GetPaintRect())); + } +#endif +} + +#ifdef USE_SKIA +void LayerManagerComposite::DrawPaintTimes(Compositor* aCompositor) { + if (!mPaintCounter) { + mPaintCounter = new PaintCounter(); + } + + TimeDuration compositeTime = TimeStamp::Now() - mRenderStartTime; + mPaintCounter->Draw(aCompositor, mLastPaintTime, compositeTime); +} +#endif + +static Rect RectWithEdges(int32_t aTop, int32_t aRight, int32_t aBottom, + int32_t aLeft) { + return Rect(aLeft, aTop, aRight - aLeft, aBottom - aTop); +} + +void LayerManagerComposite::DrawBorder(const IntRect& aOuter, + int32_t aBorderWidth, + const DeviceColor& aColor, + const Matrix4x4& aTransform) { + EffectChain effects; + effects.mPrimaryEffect = new EffectSolidColor(aColor); + + IntRect inner(aOuter); + inner.Deflate(aBorderWidth); + // Top and bottom border sides + mCompositor->DrawQuad( + RectWithEdges(aOuter.Y(), aOuter.XMost(), inner.Y(), aOuter.X()), aOuter, + effects, 1, aTransform); + mCompositor->DrawQuad( + RectWithEdges(inner.YMost(), aOuter.XMost(), aOuter.YMost(), aOuter.X()), + aOuter, effects, 1, aTransform); + // Left and right border sides + mCompositor->DrawQuad( + RectWithEdges(inner.Y(), inner.X(), inner.YMost(), aOuter.X()), aOuter, + effects, 1, aTransform); + mCompositor->DrawQuad( + RectWithEdges(inner.Y(), aOuter.XMost(), inner.YMost(), inner.XMost()), + aOuter, effects, 1, aTransform); +} + +void LayerManagerComposite::DrawTranslationWarningOverlay( + const IntRect& aBounds) { + // Black blorder + IntRect blackBorderBounds(aBounds); + blackBorderBounds.Deflate(4); + DrawBorder(blackBorderBounds, 6, DeviceColor(0, 0, 0, 1), Matrix4x4()); + + // Warning border, yellow to red + IntRect warnBorder(aBounds); + warnBorder.Deflate(5); + DrawBorder(warnBorder, 4, DeviceColor(1, 1.f - mWarningLevel, 0, 1), + Matrix4x4()); +} + +static uint16_t sFrameCount = 0; +void LayerManagerComposite::RenderDebugOverlay(const IntRect& aBounds) { + bool drawFps = StaticPrefs::layers_acceleration_draw_fps(); + bool drawFrameColorBars = StaticPrefs::gfx_draw_color_bars(); + + // Don't draw diagnostic overlays if we want to snapshot the output. + if (mTarget) { + return; + } + + if (drawFps) { +#ifdef ANDROID + // Draw a translation delay warning overlay + if (!mWarnTime.IsNull() && (TimeStamp::Now() - mWarnTime).ToMilliseconds() < + kVisualWarningDuration) { + DrawTranslationWarningOverlay(aBounds); + SetDebugOverlayWantsNextFrame(true); + } +#endif + + GPUStats stats; + stats.mScreenPixels = mRenderBounds.Width() * mRenderBounds.Height(); + mCompositor->GetFrameStats(&stats); + + std::string text = mDiagnostics->GetFrameOverlayString(stats); + mTextRenderer->RenderText(mCompositor, text, IntPoint(2, 5), Matrix4x4(), + 24, 600, TextRenderer::FontType::FixedWidth); + + float alpha = 1; + if (mUnusedApzTransformWarning) { + // If we have an unused APZ transform on this composite, draw a 20x20 red + // box in the top-right corner + EffectChain effects; + effects.mPrimaryEffect = + new EffectSolidColor(gfx::DeviceColor(1, 0, 0, 1)); + mCompositor->DrawQuad(gfx::Rect(aBounds.Width() - 20, 0, 20, 20), aBounds, + effects, alpha, gfx::Matrix4x4()); + + mUnusedApzTransformWarning = false; + SetDebugOverlayWantsNextFrame(true); + } + if (mDisabledApzWarning) { + // If we have a disabled APZ on this composite, draw a 20x20 yellow box + // in the top-right corner, to the left of the unused-apz-transform + // warning box + EffectChain effects; + effects.mPrimaryEffect = + new EffectSolidColor(gfx::DeviceColor(1, 1, 0, 1)); + mCompositor->DrawQuad(gfx::Rect(aBounds.Width() - 40, 0, 20, 20), aBounds, + effects, alpha, gfx::Matrix4x4()); + + mDisabledApzWarning = false; + SetDebugOverlayWantsNextFrame(true); + } + } + + if (drawFrameColorBars) { + gfx::IntRect sideRect(0, 0, 10, aBounds.Height()); + + EffectChain effects; + effects.mPrimaryEffect = + new EffectSolidColor(gfxUtils::GetColorForFrameNumber(sFrameCount)); + mCompositor->DrawQuad(Rect(sideRect), sideRect, effects, 1.0, + gfx::Matrix4x4()); + + // We intentionally overflow at 2^16. + sFrameCount++; + } + +#ifdef USE_SKIA + bool drawPaintTimes = StaticPrefs::gfx_content_always_paint(); + if (drawPaintTimes) { + DrawPaintTimes(mCompositor); + } +#endif +} + +void LayerManagerComposite::UpdateDebugOverlayNativeLayers() { + // Remove all debug layers first because PlaceNativeLayers might have changed + // the z-order. By removing and re-adding, we keep the debug overlay layers + // on top. + if (mGPUStatsLayer) { + mNativeLayerRoot->RemoveLayer(mGPUStatsLayer); + } + if (mUnusedTransformWarningLayer) { + mNativeLayerRoot->RemoveLayer(mUnusedTransformWarningLayer); + } + if (mDisabledApzWarningLayer) { + mNativeLayerRoot->RemoveLayer(mDisabledApzWarningLayer); + } + + bool drawFps = StaticPrefs::layers_acceleration_draw_fps(); + + if (drawFps) { + GPUStats stats; + stats.mScreenPixels = mRenderBounds.Area(); + mCompositor->GetFrameStats(&stats); + + std::string text = mDiagnostics->GetFrameOverlayString(stats); + IntSize size = mTextRenderer->ComputeSurfaceSize( + text, 600, TextRenderer::FontType::FixedWidth); + + if (!mGPUStatsLayer || mGPUStatsLayer->GetSize() != size) { + mGPUStatsLayer = + mNativeLayerRoot->CreateLayer(size, false, mSurfacePoolHandle); + } + + mGPUStatsLayer->SetPosition(IntPoint(2, 5)); + IntRect bounds({}, size); + RefPtr dt = mGPUStatsLayer->NextSurfaceAsDrawTarget( + bounds, bounds, BackendType::SKIA); + mTextRenderer->RenderTextToDrawTarget(dt, text, 600, + TextRenderer::FontType::FixedWidth); + mGPUStatsLayer->NotifySurfaceReady(); + mNativeLayerRoot->AppendLayer(mGPUStatsLayer); + + IntSize square(20, 20); + // The two warning layers are created on demand and their content is only + // drawn once. After that, they only get moved (if the window size changes) + // and conditionally shown. + // The drawing would be unnecessary if we had native "color layers". + if (mUnusedApzTransformWarning) { + // If we have an unused APZ transform on this composite, draw a 20x20 red + // box in the top-right corner. + if (!mUnusedTransformWarningLayer) { + mUnusedTransformWarningLayer = + mNativeLayerRoot->CreateLayer(square, true, mSurfacePoolHandle); + RefPtr dt = + mUnusedTransformWarningLayer->NextSurfaceAsDrawTarget( + IntRect({}, square), IntRect({}, square), BackendType::SKIA); + dt->FillRect(Rect(0, 0, 20, 20), ColorPattern(DeviceColor(1, 0, 0, 1))); + mUnusedTransformWarningLayer->NotifySurfaceReady(); + } + mUnusedTransformWarningLayer->SetPosition( + IntPoint(mRenderBounds.XMost() - 20, mRenderBounds.Y())); + mNativeLayerRoot->AppendLayer(mUnusedTransformWarningLayer); + + mUnusedApzTransformWarning = false; + SetDebugOverlayWantsNextFrame(true); + } + + if (mDisabledApzWarning) { + // If we have a disabled APZ on this composite, draw a 20x20 yellow box + // in the top-right corner, to the left of the unused-apz-transform + // warning box. + if (!mDisabledApzWarningLayer) { + mDisabledApzWarningLayer = + mNativeLayerRoot->CreateLayer(square, true, mSurfacePoolHandle); + RefPtr dt = + mDisabledApzWarningLayer->NextSurfaceAsDrawTarget( + IntRect({}, square), IntRect({}, square), BackendType::SKIA); + dt->FillRect(Rect(0, 0, 20, 20), ColorPattern(DeviceColor(1, 1, 0, 1))); + mDisabledApzWarningLayer->NotifySurfaceReady(); + } + mDisabledApzWarningLayer->SetPosition( + IntPoint(mRenderBounds.XMost() - 40, mRenderBounds.Y())); + mNativeLayerRoot->AppendLayer(mDisabledApzWarningLayer); + + mDisabledApzWarning = false; + SetDebugOverlayWantsNextFrame(true); + } + } else { + mGPUStatsLayer = nullptr; + mUnusedTransformWarningLayer = nullptr; + mDisabledApzWarningLayer = nullptr; + } +} + +RefPtr +LayerManagerComposite::PushGroupForLayerEffects() { + // This is currently true, so just making sure that any new use of this + // method is flagged for investigation + MOZ_ASSERT(StaticPrefs::layers_effect_invert() || + StaticPrefs::layers_effect_grayscale() || + StaticPrefs::layers_effect_contrast() != 0.0); + + RefPtr previousTarget = + mCompositor->GetCurrentRenderTarget(); + // make our render target the same size as the destination target + // so that we don't have to change size if the drawing area changes. + IntRect rect(previousTarget->GetOrigin(), previousTarget->GetSize()); + // XXX: I'm not sure if this is true or not... + MOZ_ASSERT(rect.IsEqualXY(0, 0)); + if (!mTwoPassTmpTarget || + mTwoPassTmpTarget->GetSize() != previousTarget->GetSize() || + mTwoPassTmpTarget->GetOrigin() != previousTarget->GetOrigin()) { + mTwoPassTmpTarget = mCompositor->CreateRenderTarget(rect, INIT_MODE_NONE); + } + MOZ_ASSERT(mTwoPassTmpTarget); + mCompositor->SetRenderTarget(mTwoPassTmpTarget); + return previousTarget; +} + +void LayerManagerComposite::PopGroupForLayerEffects( + RefPtr aPreviousTarget, IntRect aClipRect, + bool aGrayscaleEffect, bool aInvertEffect, float aContrastEffect) { + MOZ_ASSERT(mTwoPassTmpTarget); + + // This is currently true, so just making sure that any new use of this + // method is flagged for investigation + MOZ_ASSERT(aInvertEffect || aGrayscaleEffect || aContrastEffect != 0.0); + + mCompositor->SetRenderTarget(aPreviousTarget); + + EffectChain effectChain(RootLayer()); + Matrix5x4 effectMatrix; + if (aGrayscaleEffect) { + // R' = G' = B' = luminance + // R' = 0.2126*R + 0.7152*G + 0.0722*B + // G' = 0.2126*R + 0.7152*G + 0.0722*B + // B' = 0.2126*R + 0.7152*G + 0.0722*B + Matrix5x4 grayscaleMatrix(0.2126f, 0.2126f, 0.2126f, 0, 0.7152f, 0.7152f, + 0.7152f, 0, 0.0722f, 0.0722f, 0.0722f, 0, 0, 0, 0, + 1, 0, 0, 0, 0); + effectMatrix = grayscaleMatrix; + } + + if (aInvertEffect) { + // R' = 1 - R + // G' = 1 - G + // B' = 1 - B + Matrix5x4 colorInvertMatrix(-1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, + 1, 1, 1, 1, 0); + effectMatrix = effectMatrix * colorInvertMatrix; + } + + if (aContrastEffect != 0.0) { + // Multiplying with: + // R' = (1 + c) * (R - 0.5) + 0.5 + // G' = (1 + c) * (G - 0.5) + 0.5 + // B' = (1 + c) * (B - 0.5) + 0.5 + float cP1 = aContrastEffect + 1; + float hc = 0.5 * aContrastEffect; + Matrix5x4 contrastMatrix(cP1, 0, 0, 0, 0, cP1, 0, 0, 0, 0, cP1, 0, 0, 0, 0, + 1, -hc, -hc, -hc, 0); + effectMatrix = effectMatrix * contrastMatrix; + } + + effectChain.mPrimaryEffect = new EffectRenderTarget(mTwoPassTmpTarget); + effectChain.mSecondaryEffects[EffectTypes::COLOR_MATRIX] = + new EffectColorMatrix(effectMatrix); + + mCompositor->DrawQuad(Rect(Point(0, 0), Size(mTwoPassTmpTarget->GetSize())), + aClipRect, effectChain, 1., Matrix4x4()); +} + +void LayerManagerComposite::PlaceNativeLayers( + const IntRegion& aRegion, bool aOpaque, + std::deque>* aLayersToRecycle, + IntRegion* aWindowInvalidRegion) { + IntSize tileSize(StaticPrefs::layers_compositing_tiles_width(), + StaticPrefs::layers_compositing_tiles_height()); + IntRect regionBounds = aRegion.GetBounds(); + for (int32_t y = 0; y < regionBounds.YMost(); y += tileSize.height) { + for (int32_t x = 0; x < regionBounds.XMost(); x += tileSize.width) { + IntRegion tileRegion; + tileRegion.And(aRegion, IntRect(IntPoint(x, y), tileSize)); + for (auto iter = tileRegion.RectIter(); !iter.Done(); iter.Next()) { + PlaceNativeLayer(iter.Get(), aOpaque, aLayersToRecycle, + aWindowInvalidRegion); + } + } + } +} + +void LayerManagerComposite::PlaceNativeLayer( + const IntRect& aRect, bool aOpaque, + std::deque>* aLayersToRecycle, + IntRegion* aWindowInvalidRegion) { + RefPtr layer; + if (aLayersToRecycle->empty() || + aLayersToRecycle->front()->GetSize() != aRect.Size() || + aLayersToRecycle->front()->IsOpaque() != aOpaque) { + layer = mNativeLayerRoot->CreateLayer(aRect.Size(), aOpaque, + mSurfacePoolHandle); + mNativeLayerRoot->AppendLayer(layer); + aWindowInvalidRegion->OrWith(aRect); + } else { + layer = aLayersToRecycle->front(); + aLayersToRecycle->pop_front(); + IntRect oldRect = layer->GetRect(); + if (!aRect.IsEqualInterior(oldRect)) { + aWindowInvalidRegion->OrWith(oldRect); + aWindowInvalidRegion->OrWith(aRect); + } + } + layer->SetPosition(aRect.TopLeft()); + mNativeLayers.push_back(layer); +} + +// Used to clear the 'mLayerComposited' flag at the beginning of each Render(). +static void ClearLayerFlags(Layer* aLayer) { + ForEachNode(aLayer, [](Layer* layer) { + if (layer->AsHostLayer()) { + static_cast(layer->AsHostLayer()) + ->SetLayerComposited(false); + } + }); +} + +bool LayerManagerComposite::Render(const nsIntRegion& aInvalidRegion, + const nsIntRegion& aOpaqueRegion) { + AUTO_PROFILER_LABEL("LayerManagerComposite::Render", GRAPHICS); + + if (mDestroyed || !mCompositor || mCompositor->IsDestroyed()) { + NS_WARNING("Call on destroyed layer manager"); + return false; + } + + mCompositor->RequestAllowFrameRecording(!!mCompositionRecorder); + + ClearLayerFlags(mRoot); + + // At this time, it doesn't really matter if these preferences change + // during the execution of the function; we should be safe in all + // permutations. However, may as well just get the values onces and + // then use them, just in case the consistency becomes important in + // the future. + bool invertVal = StaticPrefs::layers_effect_invert(); + bool grayscaleVal = StaticPrefs::layers_effect_grayscale(); + float contrastVal = StaticPrefs::layers_effect_contrast(); + bool haveLayerEffects = (invertVal || grayscaleVal || contrastVal != 0.0); + + // Set LayerScope begin/end frame + LayerScopeAutoFrame frame(PR_Now()); + + // If you're looking for the code to dump the layer tree, it was moved + // to CompositorBridgeParent::CompositeToTarget(). + + // Dump to LayerScope Viewer + if (LayerScope::CheckSendable()) { + // Create a LayersPacket, dump Layers into it and transfer the + // packet('s ownership) to LayerScope. + auto packet = MakeUnique(); + layerscope::LayersPacket* layersPacket = packet->mutable_layers(); + this->Dump(layersPacket); + LayerScope::SendLayerDump(std::move(packet)); + } + + mozilla::widget::WidgetRenderingContext widgetContext; +#if defined(XP_MACOSX) + if (CompositorOGL* compositorOGL = mCompositor->AsCompositorOGL()) { + widgetContext.mGL = compositorOGL->gl(); + } +#endif + + { + AUTO_PROFILER_LABEL("LayerManagerComposite::Render:Prerender", GRAPHICS); + + if (!mCompositor->GetWidget()->PreRender(&widgetContext)) { + return false; + } + } + + CompositorBench(mCompositor, mRenderBounds); + + MOZ_ASSERT(mRoot->GetOpacity() == 1); +#if defined(MOZ_WIDGET_ANDROID) + LayerMetricsWrapper wrapper = GetRootContentLayer(); + if (wrapper) { + mCompositor->SetClearColor(wrapper.Metadata().GetBackgroundColor()); + } else { + mCompositor->SetClearColorToDefault(); + } +#endif + + Maybe rootLayerClip = mRoot->GetClipRect().map( + [](const ParentLayerIntRect& r) { return r.ToUnknownRect(); }); + Maybe maybeBounds; + bool usingNativeLayers = false; + if (mTarget) { + maybeBounds = mCompositor->BeginFrameForTarget( + aInvalidRegion, rootLayerClip, mRenderBounds, aOpaqueRegion, mTarget, + mTargetBounds); + } else if (mNativeLayerRoot) { + mSurfacePoolHandle->OnBeginFrame(); + if (aInvalidRegion.Intersects(mRenderBounds)) { + mCompositor->BeginFrameForNativeLayers(); + maybeBounds = Some(mRenderBounds); + usingNativeLayers = true; + } + } else { + maybeBounds = mCompositor->BeginFrameForWindow( + aInvalidRegion, rootLayerClip, mRenderBounds, aOpaqueRegion); + } + + if (!maybeBounds) { + mProfilerScreenshotGrabber.NotifyEmptyFrame(); + mCompositor->GetWidget()->PostRender(&widgetContext); + + // Discard the current payloads. These payloads did not require a composite + // (they caused no changes to anything visible), so we don't want to measure + // their latency. + mPayload.Clear(); + + return true; + } + + IntRect bounds = *maybeBounds; + IntRect clipRect = rootLayerClip.valueOr(bounds); + + // Prepare our layers. + { + Diagnostics::Record record(mRenderStartTime); + RootLayer()->Prepare(RenderTargetIntRect::FromUnknownRect(clipRect)); + if (record.Recording()) { + mDiagnostics->RecordPrepareTime(record.Duration()); + } + } + + auto RenderOnce = [&](const IntRect& aClipRect) { + RefPtr previousTarget; + if (haveLayerEffects) { + previousTarget = PushGroupForLayerEffects(); + } else { + mTwoPassTmpTarget = nullptr; + } + + // Execute draw commands. + RootLayer()->RenderLayer(aClipRect, Nothing()); + + if (mTwoPassTmpTarget) { + MOZ_ASSERT(haveLayerEffects); + PopGroupForLayerEffects(previousTarget, aClipRect, grayscaleVal, + invertVal, contrastVal); + } + if (!mRegionToClear.IsEmpty()) { + for (auto iter = mRegionToClear.RectIter(); !iter.Done(); iter.Next()) { + mCompositor->ClearRect(Rect(iter.Get())); + } + } + mCompositor->NormalDrawingDone(); + }; + + { + Diagnostics::Record record; + + if (usingNativeLayers) { + // Update the placement of our native layers, so that transparent and + // opaque parts of the window are covered by different layers and we can + // update those parts separately. + IntRegion opaqueRegion; + opaqueRegion.And(aOpaqueRegion, mRenderBounds); + + // Limit the complexity of these regions. Usually, opaqueRegion should be + // only one or two rects, so this SimplifyInward call will not change the + // region if everything looks as expected. + opaqueRegion.SimplifyInward(4); + + IntRegion transparentRegion; + transparentRegion.Sub(mRenderBounds, opaqueRegion); + std::deque> layersToRecycle = + std::move(mNativeLayers); + IntRegion invalidRegion = aInvalidRegion; + PlaceNativeLayers(opaqueRegion, true, &layersToRecycle, &invalidRegion); + PlaceNativeLayers(transparentRegion, false, &layersToRecycle, + &invalidRegion); + for (const auto& unusedLayer : layersToRecycle) { + mNativeLayerRoot->RemoveLayer(unusedLayer); + } + + for (const auto& nativeLayer : mNativeLayers) { + Maybe maybeLayerRect = + mCompositor->BeginRenderingToNativeLayer( + invalidRegion, rootLayerClip, aOpaqueRegion, nativeLayer); + if (!maybeLayerRect) { + continue; + } + + if (rootLayerClip) { + RenderOnce(rootLayerClip->Intersect(*maybeLayerRect)); + } else { + RenderOnce(*maybeLayerRect); + } + mCompositor->EndRenderingToNativeLayer(); + } + } else { + RenderOnce(clipRect); + } + + if (record.Recording()) { + mDiagnostics->RecordCompositeTime(record.Duration()); + } + } + + RootLayer()->Cleanup(); + + WindowLMC window(mCompositor); + mProfilerScreenshotGrabber.MaybeGrabScreenshot(window, bounds.Size()); + + if (mCompositionRecorder) { + bool hasContentPaint = std::any_of( + mPayload.begin(), mPayload.end(), [](CompositionPayload& payload) { + return payload.mType == CompositionPayloadType::eContentPaint; + }); + + if (hasContentPaint) { + if (RefPtr frame = + mCompositor->RecordFrame(TimeStamp::Now())) { + mCompositionRecorder->RecordFrame(frame); + } + } + } + + if (usingNativeLayers) { + UpdateDebugOverlayNativeLayers(); + } else { +#if defined(MOZ_WIDGET_ANDROID) + HandlePixelsTarget(); +#endif // defined(MOZ_WIDGET_ANDROID) + + // Debugging + RenderDebugOverlay(bounds); + } + + { + AUTO_PROFILER_LABEL("LayerManagerComposite::Render:EndFrame", GRAPHICS); + + mCompositor->EndFrame(); + + if (usingNativeLayers) { + mNativeLayerRoot->CommitToScreen(); + } + } + + mCompositor->GetWidget()->PostRender(&widgetContext); + + mProfilerScreenshotGrabber.MaybeProcessQueue(); + + RecordFrame(); + + PayloadPresented(TimeStamp::Now()); + + // Our payload has now been presented. + mPayload.Clear(); + + if (usingNativeLayers) { + mSurfacePoolHandle->OnEndFrame(); + } + + mCompositor->WaitForGPU(); + + return true; +} + +#if defined(MOZ_WIDGET_ANDROID) +class ScopedCompostitorSurfaceSize { + public: + ScopedCompostitorSurfaceSize(CompositorOGL* aCompositor, + const gfx::IntSize& aSize) + : mCompositor(aCompositor), + mOriginalSize(mCompositor->GetDestinationSurfaceSize()) { + mCompositor->SetDestinationSurfaceSize(aSize); + } + ~ScopedCompostitorSurfaceSize() { + mCompositor->SetDestinationSurfaceSize(mOriginalSize); + } + + private: + CompositorOGL* const mCompositor; + const gfx::IntSize mOriginalSize; +}; + +class ScopedContextSurfaceOverride { + public: + ScopedContextSurfaceOverride(GLContextEGL* aContext, void* aSurface) + : mContext(aContext) { + MOZ_ASSERT(aSurface); + mContext->SetEGLSurfaceOverride(aSurface); + mContext->MakeCurrent(true); + } + ~ScopedContextSurfaceOverride() { + mContext->SetEGLSurfaceOverride(EGL_NO_SURFACE); + mContext->MakeCurrent(true); + } + + private: + GLContextEGL* const mContext; +}; + +void LayerManagerComposite::RenderToPresentationSurface() { + if (!mCompositor) { + return; + } + + widget::CompositorWidget* const widget = mCompositor->GetWidget(); + + if (!widget) { + return; + } + + ANativeWindow* window = widget->AsAndroid()->GetPresentationANativeWindow(); + + if (!window) { + return; + } + + CompositorOGL* compositor = mCompositor->AsCompositorOGL(); + GLContext* gl = compositor->gl(); + GLContextEGL* egl = GLContextEGL::Cast(gl); + + if (!egl) { + return; + } + + EGLSurface surface = widget->AsAndroid()->GetPresentationEGLSurface(); + + if (!surface) { + // create surface; + surface = egl->CreateCompatibleSurface(window); + if (!surface) { + return; + } + + widget->AsAndroid()->SetPresentationEGLSurface(surface); + } + + const IntSize windowSize(ANativeWindow_getWidth(window), + ANativeWindow_getHeight(window)); + + if ((windowSize.width <= 0) || (windowSize.height <= 0)) { + return; + } + + ScreenRotation rotation = compositor->GetScreenRotation(); + + const int actualWidth = windowSize.width; + const int actualHeight = windowSize.height; + + const gfx::IntSize originalSize = compositor->GetDestinationSurfaceSize(); + const nsIntRect originalRect = + nsIntRect(0, 0, originalSize.width, originalSize.height); + + int pageWidth = originalSize.width; + int pageHeight = originalSize.height; + if (rotation == ROTATION_90 || rotation == ROTATION_270) { + pageWidth = originalSize.height; + pageHeight = originalSize.width; + } + + float scale = 1.0; + + if ((pageWidth > actualWidth) || (pageHeight > actualHeight)) { + const float scaleWidth = (float)actualWidth / (float)pageWidth; + const float scaleHeight = (float)actualHeight / (float)pageHeight; + scale = scaleWidth <= scaleHeight ? scaleWidth : scaleHeight; + } + + const gfx::IntSize actualSize(actualWidth, actualHeight); + ScopedCompostitorSurfaceSize overrideSurfaceSize(compositor, actualSize); + + const ScreenPoint offset((actualWidth - (int)(scale * pageWidth)) / 2, 0); + ScopedContextSurfaceOverride overrideSurface(egl, surface); + + Matrix viewMatrix = ComputeTransformForRotation(originalRect, rotation); + viewMatrix.Invert(); // unrotate + viewMatrix.PostScale(scale, scale); + viewMatrix.PostTranslate(offset.x, offset.y); + Matrix4x4 matrix = Matrix4x4::From2D(viewMatrix); + + mRoot->ComputeEffectiveTransforms(matrix); + nsIntRegion opaque; + PostProcessLayers(opaque); + + nsIntRegion invalid; + IntRect bounds = IntRect::Truncate(0, 0, scale * pageWidth, actualHeight); + MOZ_ASSERT(mRoot->GetOpacity() == 1); + Unused << mCompositor->BeginFrameForWindow(invalid, Nothing(), bounds, + nsIntRegion()); + + // The Java side of Fennec sets a scissor rect that accounts for + // chrome such as the URL bar. Override that so that the entire frame buffer + // is cleared. + ScopedScissorRect scissorRect(egl, 0, 0, actualWidth, actualHeight); + egl->fClearColor(0.0, 0.0, 0.0, 0.0); + egl->fClear(LOCAL_GL_COLOR_BUFFER_BIT); + + const IntRect clipRect = IntRect::Truncate(0, 0, actualWidth, actualHeight); + + RootLayer()->Prepare(RenderTargetIntRect::FromUnknownRect(clipRect)); + RootLayer()->RenderLayer(clipRect, Nothing()); + + mCompositor->EndFrame(); +} + +// Used by robocop tests to get a snapshot of the frame buffer. +void LayerManagerComposite::HandlePixelsTarget() { + if (!mScreenPixelsTarget) { + return; + } + + int32_t bufferWidth = mRenderBounds.width; + int32_t bufferHeight = mRenderBounds.height; + ipc::Shmem mem; + if (!mScreenPixelsTarget->AllocPixelBuffer( + bufferWidth * bufferHeight * sizeof(uint32_t), &mem)) { + // Failed to alloc shmem, Just bail out. + return; + } + CompositorOGL* compositor = mCompositor->AsCompositorOGL(); + GLContext* gl = compositor->gl(); + MOZ_ASSERT(gl); + gl->fReadPixels(0, 0, bufferWidth, bufferHeight, LOCAL_GL_RGBA, + LOCAL_GL_UNSIGNED_BYTE, mem.get()); + Unused << mScreenPixelsTarget->SendScreenPixels( + std::move(mem), ScreenIntSize(bufferWidth, bufferHeight), true); + mScreenPixelsTarget = nullptr; +} +#endif + +already_AddRefed LayerManagerComposite::CreatePaintedLayer() { + if (mDestroyed) { + NS_WARNING("Call on destroyed layer manager"); + return nullptr; + } + return RefPtr(new PaintedLayerComposite(this)).forget(); +} + +already_AddRefed LayerManagerComposite::CreateContainerLayer() { + if (mDestroyed) { + NS_WARNING("Call on destroyed layer manager"); + return nullptr; + } + return RefPtr(new ContainerLayerComposite(this)).forget(); +} + +already_AddRefed LayerManagerComposite::CreateImageLayer() { + if (mDestroyed) { + NS_WARNING("Call on destroyed layer manager"); + return nullptr; + } + return RefPtr(new ImageLayerComposite(this)).forget(); +} + +already_AddRefed LayerManagerComposite::CreateColorLayer() { + if (LayerManagerComposite::mDestroyed) { + NS_WARNING("Call on destroyed layer manager"); + return nullptr; + } + return RefPtr(new ColorLayerComposite(this)).forget(); +} + +already_AddRefed LayerManagerComposite::CreateCanvasLayer() { + if (LayerManagerComposite::mDestroyed) { + NS_WARNING("Call on destroyed layer manager"); + return nullptr; + } + return RefPtr(new CanvasLayerComposite(this)).forget(); +} + +already_AddRefed LayerManagerComposite::CreateRefLayer() { + if (LayerManagerComposite::mDestroyed) { + NS_WARNING("Call on destroyed layer manager"); + return nullptr; + } + return RefPtr(new RefLayerComposite(this)).forget(); +} + +LayerManagerComposite::AutoAddMaskEffect::AutoAddMaskEffect( + Layer* aMaskLayer, EffectChain& aEffects) + : mCompositable(nullptr), mFailed(false) { + if (!aMaskLayer) { + return; + } + + mCompositable = ToLayerComposite(aMaskLayer)->GetCompositableHost(); + if (!mCompositable) { + NS_WARNING("Mask layer with no compositable host"); + mFailed = true; + return; + } + + if (!mCompositable->AddMaskEffect(aEffects, + aMaskLayer->GetEffectiveTransform())) { + mCompositable = nullptr; + mFailed = true; + } +} + +LayerManagerComposite::AutoAddMaskEffect::~AutoAddMaskEffect() { + if (!mCompositable) { + return; + } + + mCompositable->RemoveMaskEffect(); +} + +bool LayerManagerComposite::IsCompositingToScreen() const { return !mTarget; } + +LayerComposite::LayerComposite(LayerManagerComposite* aManager) + : HostLayer(aManager), + mCompositeManager(aManager), + mCompositor(aManager->GetCompositor()), + mDestroyed(false), + mLayerComposited(false) {} + +LayerComposite::~LayerComposite() = default; + +void LayerComposite::Destroy() { + if (!mDestroyed) { + mDestroyed = true; + CleanupResources(); + } +} + +void LayerComposite::AddBlendModeEffect(EffectChain& aEffectChain) { + gfx::CompositionOp blendMode = GetLayer()->GetEffectiveMixBlendMode(); + if (blendMode == gfx::CompositionOp::OP_OVER) { + return; + } + + aEffectChain.mSecondaryEffects[EffectTypes::BLEND_MODE] = + new EffectBlendMode(blendMode); +} + +bool LayerManagerComposite::CanUseCanvasLayerForSize(const IntSize& aSize) { + return mCompositor->CanUseCanvasLayerForSize( + gfx::IntSize(aSize.width, aSize.height)); +} + +void LayerManagerComposite::NotifyShadowTreeTransaction() { + if (StaticPrefs::layers_acceleration_draw_fps()) { + mDiagnostics->AddTxnFrame(); + } +} + +void LayerComposite::SetLayerManager(HostLayerManager* aManager) { + HostLayer::SetLayerManager(aManager); + mCompositeManager = static_cast(aManager); + mCompositor = mCompositeManager->GetCompositor(); +} + +bool LayerManagerComposite::AsyncPanZoomEnabled() const { + if (CompositorBridgeParent* bridge = + mCompositor->GetCompositorBridgeParent()) { + return bridge->GetOptions().UseAPZ(); + } + return false; +} + +bool LayerManagerComposite::AlwaysScheduleComposite() const { + return !!(mCompositor->GetDiagnosticTypes() & DiagnosticTypes::FLASH_BORDERS); +} + +nsIntRegion LayerComposite::GetFullyRenderedRegion() { + if (TiledContentHost* tiled = + GetCompositableHost() ? GetCompositableHost()->AsTiledContentHost() + : nullptr) { + nsIntRegion shadowVisibleRegion = + GetShadowVisibleRegion().ToUnknownRegion(); + // Discard the region which hasn't been drawn yet when doing + // progressive drawing. Note that if the shadow visible region + // shrunk the tiled valig region may not have discarded this yet. + shadowVisibleRegion.And(shadowVisibleRegion, tiled->GetValidRegion()); + return shadowVisibleRegion; + } else { + return GetShadowVisibleRegion().ToUnknownRegion(); + } +} + +Matrix4x4 HostLayer::GetShadowTransform() { + Matrix4x4 transform = mShadowTransform; + Layer* layer = GetLayer(); + + transform.PostScale(layer->GetPostXScale(), layer->GetPostYScale(), 1.0f); + if (const ContainerLayer* c = layer->AsContainerLayer()) { + transform.PreScale(c->GetPreXScale(), c->GetPreYScale(), 1.0f); + } + + return transform; +} + +// Async animations can move child layers without updating our visible region. +// PostProcessLayers will recompute visible regions for layers with an +// intermediate surface, but otherwise we need to do it now. +static void ComputeVisibleRegionForChildren(ContainerLayer* aContainer, + LayerIntRegion& aResult) { + for (Layer* l = aContainer->GetFirstChild(); l; l = l->GetNextSibling()) { + if (l->Extend3DContext()) { + MOZ_ASSERT(l->AsContainerLayer()); + ComputeVisibleRegionForChildren(l->AsContainerLayer(), aResult); + } else { + AddTransformedRegion(aResult, l->GetLocalVisibleRegion(), + l->ComputeTransformToPreserve3DRoot()); + } + } +} + +void HostLayer::RecomputeShadowVisibleRegionFromChildren() { + mShadowVisibleRegion.SetEmpty(); + ContainerLayer* container = GetLayer()->AsContainerLayer(); + MOZ_ASSERT(container); + // Layers that extend a 3d context have a local visible region + // that can only be represented correctly in 3d space. Since + // we can't do that, leave it empty instead to stop anyone + // from trying to use it. + NS_ASSERTION( + !GetLayer()->Extend3DContext(), + "Can't compute visible region for layers that extend a 3d context"); + if (container && !GetLayer()->Extend3DContext()) { + ComputeVisibleRegionForChildren(container, mShadowVisibleRegion); + } +} + +bool LayerComposite::HasStaleCompositor() const { + return mCompositeManager->GetCompositor() != mCompositor; +} + +#ifndef MOZ_HAVE_PLATFORM_SPECIFIC_LAYER_BUFFERS + +/*static*/ +bool LayerManagerComposite::SupportsDirectTexturing() { return false; } + +/*static*/ +void LayerManagerComposite::PlatformSyncBeforeReplyUpdate() {} + +#endif // !defined(MOZ_HAVE_PLATFORM_SPECIFIC_LAYER_BUFFERS) + +class RenderSourceLMC : public profiler_screenshots::RenderSource { + public: + explicit RenderSourceLMC(CompositingRenderTarget* aRT) + : RenderSource(aRT->GetSize()), mRT(aRT) {} + + const auto& RenderTarget() { return mRT; } + + protected: + virtual ~RenderSourceLMC() {} + + RefPtr mRT; +}; + +class DownscaleTargetLMC : public profiler_screenshots::DownscaleTarget { + public: + explicit DownscaleTargetLMC(CompositingRenderTarget* aRT, + Compositor* aCompositor) + : profiler_screenshots::DownscaleTarget(aRT->GetSize()), + mRenderSource(new RenderSourceLMC(aRT)), + mCompositor(aCompositor) {} + + already_AddRefed AsRenderSource() + override { + return do_AddRef(mRenderSource); + } + + bool DownscaleFrom(profiler_screenshots::RenderSource* aSource, + const IntRect& aSourceRect, + const IntRect& aDestRect) override { + MOZ_RELEASE_ASSERT(aSourceRect.TopLeft() == IntPoint()); + MOZ_RELEASE_ASSERT(aDestRect.TopLeft() == IntPoint()); + RefPtr previousTarget = + mCompositor->GetCurrentRenderTarget(); + + mCompositor->SetRenderTarget(mRenderSource->RenderTarget()); + bool result = mCompositor->BlitRenderTarget( + static_cast(aSource)->RenderTarget(), + aSourceRect.Size(), aDestRect.Size()); + + // Restore the old render target. + mCompositor->SetRenderTarget(previousTarget); + + return result; + } + + protected: + virtual ~DownscaleTargetLMC() {} + + RefPtr mRenderSource; + Compositor* mCompositor; +}; + +class AsyncReadbackBufferLMC + : public profiler_screenshots::AsyncReadbackBuffer { + public: + AsyncReadbackBufferLMC(mozilla::layers::AsyncReadbackBuffer* aARB, + Compositor* aCompositor) + : profiler_screenshots::AsyncReadbackBuffer(aARB->GetSize()), + mARB(aARB), + mCompositor(aCompositor) {} + void CopyFrom(profiler_screenshots::RenderSource* aSource) override { + mCompositor->ReadbackRenderTarget( + static_cast(aSource)->RenderTarget(), mARB); + } + bool MapAndCopyInto(DataSourceSurface* aSurface, + const IntSize& aReadSize) override { + return mARB->MapAndCopyInto(aSurface, aReadSize); + } + + protected: + virtual ~AsyncReadbackBufferLMC() {} + + RefPtr mARB; + Compositor* mCompositor; +}; + +already_AddRefed +WindowLMC::GetWindowContents(const gfx::IntSize& aWindowSize) { + RefPtr rt = mCompositor->GetWindowRenderTarget(); + if (!rt) { + return nullptr; + } + return MakeAndAddRef(rt); +} + +already_AddRefed +WindowLMC::CreateDownscaleTarget(const gfx::IntSize& aSize) { + RefPtr rt = + mCompositor->CreateRenderTarget(IntRect({}, aSize), INIT_MODE_NONE); + return MakeAndAddRef(rt, mCompositor); +} + +already_AddRefed +WindowLMC::CreateAsyncReadbackBuffer(const gfx::IntSize& aSize) { + RefPtr carb = + mCompositor->CreateAsyncReadbackBuffer(aSize); + if (!carb) { + return nullptr; + } + return MakeAndAddRef(carb, mCompositor); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/LayerManagerComposite.h b/gfx/layers/composite/LayerManagerComposite.h new file mode 100644 index 0000000000..18f8f0aea9 --- /dev/null +++ b/gfx/layers/composite/LayerManagerComposite.h @@ -0,0 +1,729 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_LayerManagerComposite_H +#define GFX_LayerManagerComposite_H + +#include // for uint64_t, int32_t +#include // for deque +#include // for operator new +#include // for remove_reference<>::type +#include // for move, forward +#include "CompositableHost.h" // for ImageCompositeNotificationInfo +#include "Units.h" // for LayerIntRegion, ParentLayerIntRect, RenderTargetIntRect +#include "mozilla/AlreadyAddRefed.h" // for already_AddRefed +#include "mozilla/Assertions.h" // for MOZ_CRASH, AssertionConditionType, MOZ_ASSERT, MOZ_AS... +#include "mozilla/Maybe.h" // for Maybe +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/TimeStamp.h" // for TimeStamp, BaseTimeDuration +#include "mozilla/UniquePtr.h" // for UniquePtr +#include "mozilla/gfx/2D.h" // for DrawTarget +#include "mozilla/gfx/Matrix.h" // for Matrix4x4 +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/gfx/Polygon.h" // for Polygon +#include "mozilla/gfx/Rect.h" // for IntRect +#include "mozilla/gfx/Types.h" // for DeviceColor (ptr only), SurfaceFormat +#include "mozilla/layers/CompositionRecorder.h" // for CompositionRecorder, CollectedFrames (ptr only) +#include "mozilla/layers/Compositor.h" // for Compositor +#include "mozilla/layers/CompositorTypes.h" // for DiagnosticTypes, TextureFactoryIdentifier +#include "mozilla/layers/LayerManager.h" // for LayerManager::END_DEFAULT, LayerManager::EndTransacti... +#include "mozilla/layers/LayersTypes.h" // for CompositionOpportunityId, LayersBackend, LayersBacken... +#include "mozilla/layers/ScreenshotGrabber.h" // for ScreenshotGrabber +#include "nsDebug.h" // for NS_WARNING +#include "nsIThread.h" // for TimeDuration +#include "nsRegion.h" // for nsIntRegion +#include "nsRegionFwd.h" // for IntRegion +#include "nsStringFwd.h" // for nsCString, nsAString +#include "nsTArray.h" // for nsTArray + +class gfxContext; + +#ifdef XP_WIN +# include +#endif + +namespace mozilla { +namespace layers { + +class CanvasLayer; +class CanvasLayerComposite; +class ColorLayer; +class ColorLayerComposite; +class ContainerLayer; +class ContainerLayerComposite; +class Diagnostics; +struct EffectChain; +class ImageLayer; +class ImageLayerComposite; +class LayerComposite; +class NativeLayer; +class NativeLayerRoot; +class RefLayerComposite; +class PaintTiming; +class PaintedLayer; +class PaintedLayerComposite; +class RefLayer; +class SurfacePoolHandle; +class TextRenderer; +class TextureSourceProvider; +class CompositingRenderTarget; +struct FPSState; +class PaintCounter; +class LayerMLGPU; +class LayerManagerMLGPU; +class UiCompositorControllerParent; +class Layer; +struct LayerProperties; + +static const int kVisualWarningDuration = 150; // ms + +// An implementation of LayerManager that acts as a pair with ClientLayerManager +// and is mirrored across IPDL. This gets managed/updated by +// LayerTransactionParent. +class HostLayerManager : public LayerManager { + public: + HostLayerManager(); + virtual ~HostLayerManager(); + + bool BeginTransactionWithTarget(gfxContext* aTarget, + const nsCString& aURL) override { + MOZ_CRASH("GFX: Use BeginTransactionWithDrawTarget"); + } + + bool EndEmptyTransaction(EndTransactionFlags aFlags = END_DEFAULT) override { + MOZ_CRASH("GFX: Use EndTransaction(aTimeStamp)"); + return false; + } + + void EndTransaction(DrawPaintedLayerCallback aCallback, void* aCallbackData, + EndTransactionFlags aFlags = END_DEFAULT) override { + MOZ_CRASH("GFX: Use EndTransaction(aTimeStamp)"); + } + + int32_t GetMaxTextureSize() const override { + MOZ_CRASH("GFX: Call on compositor, not LayerManagerComposite"); + } + + void GetBackendName(nsAString& name) override { + MOZ_CRASH("GFX: Shouldn't be called for composited layer manager"); + } + + virtual void ForcePresent() = 0; + virtual void AddInvalidRegion(const nsIntRegion& aRegion) = 0; + + virtual void NotifyShadowTreeTransaction() {} + virtual void BeginTransactionWithDrawTarget(gfx::DrawTarget* aTarget, + const gfx::IntRect& aRect) = 0; + virtual Compositor* GetCompositor() const = 0; + virtual TextureSourceProvider* GetTextureSourceProvider() const = 0; + virtual void EndTransaction(const TimeStamp& aTimeStamp, + EndTransactionFlags aFlags = END_DEFAULT) = 0; + virtual void UpdateRenderBounds(const gfx::IntRect& aRect) {} + virtual void SetDiagnosticTypes(DiagnosticTypes aDiagnostics) {} + virtual void InvalidateAll() = 0; + + HostLayerManager* AsHostLayerManager() override { return this; } + virtual LayerManagerMLGPU* AsLayerManagerMLGPU() { return nullptr; } + + void ExtractImageCompositeNotifications( + nsTArray* aNotifications) { + aNotifications->AppendElements(std::move(mImageCompositeNotifications)); + } + + void AppendImageCompositeNotification( + const ImageCompositeNotificationInfo& aNotification) { + // Only send composite notifications when we're drawing to the screen, + // because that's what they mean. + // Also when we're not drawing to the screen, DidComposite will not be + // called to extract and send these notifications, so they might linger + // and contain stale ImageContainerParent pointers. + if (IsCompositingToScreen()) { + mImageCompositeNotifications.AppendElement(aNotification); + } + } + + /** + * LayerManagerComposite provides sophisticated debug overlays + * that can request a next frame. + */ + bool DebugOverlayWantsNextFrame() { return mDebugOverlayWantsNextFrame; } + void SetDebugOverlayWantsNextFrame(bool aVal) { + mDebugOverlayWantsNextFrame = aVal; + } + + /** + * Add an on frame warning. + * @param severity ranges from 0 to 1. It's used to compute the warning color. + */ + void VisualFrameWarning(float severity) { + mozilla::TimeStamp now = TimeStamp::Now(); + if (mWarnTime.IsNull() || severity > mWarningLevel || + mWarnTime + TimeDuration::FromMilliseconds(kVisualWarningDuration) < + now) { + mWarnTime = now; + mWarningLevel = severity; + } + } + + void SetPaintTime(const TimeDuration& aPaintTime) { + mLastPaintTime = aPaintTime; + } + + virtual bool AlwaysScheduleComposite() const { return false; } + virtual bool IsCompositingToScreen() const { return false; } + + void RecordPaintTimes(const PaintTiming& aTiming); + void RecordUpdateTime(float aValue); + + CompositionOpportunityId GetCompositionOpportunityId() const { + return mCompositionOpportunityId; + } + + TimeStamp GetCompositionTime() const { return mCompositionTime; } + void SetCompositionTime(TimeStamp aTimeStamp) { + mCompositionTime = aTimeStamp; + if (!mCompositionTime.IsNull() && !mCompositeUntilTime.IsNull() && + mCompositionTime >= mCompositeUntilTime) { + mCompositeUntilTime = TimeStamp(); + } + } + + void CompositeUntil(TimeStamp aTimeStamp) { + if (mCompositeUntilTime.IsNull() || mCompositeUntilTime < aTimeStamp) { + mCompositeUntilTime = aTimeStamp; + } + } + TimeStamp GetCompositeUntilTime() const { return mCompositeUntilTime; } + + // We maintaining a global mapping from ID to CompositorBridgeParent for + // async compositables. + uint64_t GetCompositorBridgeID() const { return mCompositorBridgeID; } + void SetCompositorBridgeID(uint64_t aID) { + MOZ_ASSERT(mCompositorBridgeID == 0, + "The compositor ID must be set only once."); + mCompositorBridgeID = aID; + } + + void SetCompositionRecorder(UniquePtr aRecorder) { + mCompositionRecorder = std::move(aRecorder); + } + + /** + * Write the frames collected by the |CompositionRecorder| to disk. + * + * If there is not currently a |CompositionRecorder|, this is a no-op. + */ + void WriteCollectedFrames(); + + Maybe GetCollectedFrames(); + + protected: + bool mDebugOverlayWantsNextFrame; + nsTArray mImageCompositeNotifications; + // Testing property. If hardware composer is supported, this will return + // true if the last frame was deemed 'too complicated' to be rendered. + float mWarningLevel; + mozilla::TimeStamp mWarnTime; + UniquePtr mDiagnostics; + uint64_t mCompositorBridgeID; + + TimeDuration mLastPaintTime; + TimeStamp mRenderStartTime; + UniquePtr mCompositionRecorder = nullptr; + + // Render time for the current composition. + TimeStamp mCompositionTime; + + // CompositionOpportunityId of the current composition. + CompositionOpportunityId mCompositionOpportunityId; + + // When nonnull, during rendering, some compositable indicated that it will + // change its rendering at this time. In order not to miss it, we composite + // on every vsync until this time occurs (this is the latest such time). + TimeStamp mCompositeUntilTime; +#if defined(MOZ_WIDGET_ANDROID) + public: + // Used by UiCompositorControllerParent to set itself as the target for the + // contents of the frame buffer after a composite. + // Implemented in LayerManagerComposite + virtual void RequestScreenPixels(UiCompositorControllerParent* aController) {} +#endif // defined(MOZ_WIDGET_ANDROID) +}; + +// A layer manager implementation that uses the Compositor API +// to render layers. +class LayerManagerComposite final : public HostLayerManager { + typedef mozilla::gfx::DrawTarget DrawTarget; + typedef mozilla::gfx::IntSize IntSize; + typedef mozilla::gfx::SurfaceFormat SurfaceFormat; + + public: + explicit LayerManagerComposite(Compositor* aCompositor); + virtual ~LayerManagerComposite(); + + void Destroy() override; + + /** + * Sets the clipping region for this layer manager. This is important on + * windows because using OGL we no longer have GDI's native clipping. Therefor + * widget must tell us what part of the screen is being invalidated, + * and we should clip to this. + * + * \param aClippingRegion Region to clip to. Setting an empty region + * will disable clipping. + */ + void SetClippingRegion(const nsIntRegion& aClippingRegion) { + mClippingRegion = aClippingRegion; + } + + /** + * LayerManager implementation. + */ + LayerManagerComposite* AsLayerManagerComposite() override { return this; } + + void UpdateRenderBounds(const gfx::IntRect& aRect) override; + + bool BeginTransaction(const nsCString& aURL) override; + void BeginTransactionWithDrawTarget(gfx::DrawTarget* aTarget, + const gfx::IntRect& aRect) override; + void EndTransaction(const TimeStamp& aTimeStamp, + EndTransactionFlags aFlags = END_DEFAULT) override; + virtual void EndTransaction( + DrawPaintedLayerCallback aCallback, void* aCallbackData, + EndTransactionFlags aFlags = END_DEFAULT) override { + MOZ_CRASH("GFX: Use EndTransaction(aTimeStamp)"); + } + + void SetRoot(Layer* aLayer) override; + + // XXX[nrc]: never called, we should move this logic to ClientLayerManager + // (bug 946926). + bool CanUseCanvasLayerForSize(const gfx::IntSize& aSize) override; + + void ClearCachedResources(Layer* aSubtree = nullptr) override; + + already_AddRefed CreatePaintedLayer() override; + already_AddRefed CreateContainerLayer() override; + already_AddRefed CreateImageLayer() override; + already_AddRefed CreateColorLayer() override; + already_AddRefed CreateCanvasLayer() override; + already_AddRefed CreateRefLayer() override; + + bool AreComponentAlphaLayersEnabled() override; + + already_AddRefed CreateOptimalMaskDrawTarget( + const IntSize& aSize) override; + + const char* Name() const override { return ""; } + bool IsCompositingToScreen() const override; + + bool AlwaysScheduleComposite() const override; + + /** + * Post-processes layers before composition. This performs the following: + * + * - Applies occlusion culling. This restricts the shadow visible region of + * layers that are covered with opaque content. |aOpaqueRegion| is the + * region already known to be covered with opaque content, in the + * post-transform coordinate space of aLayer. + * + * - Recomputes visible regions to account for async transforms. + * Each layer accumulates into |aVisibleRegion| its post-transform + * (including async transforms) visible region. + * + * - aRenderTargetClip is the exact clip required for aLayer, in the + * coordinates of the nearest render target (the same as + * GetEffectiveTransform). + * + * - aClipFromAncestors is the approximate combined clip from all + * ancestors, in the coordinate space of our parent, but maybe be an + * overestimate in the presence of complex transforms. + */ + void PostProcessLayers(nsIntRegion& aOpaqueRegion); + void PostProcessLayers(Layer* aLayer, nsIntRegion& aOpaqueRegion, + LayerIntRegion& aVisibleRegion, + const Maybe& aRenderTargetClip, + const Maybe& aClipFromAncestors, + bool aCanContributeOpaque); + + /** + * RAII helper class to add a mask effect with the compositable from + * aMaskLayer to the EffectChain aEffect and notify the compositable when we + * are done. + */ + class AutoAddMaskEffect { + public: + AutoAddMaskEffect(Layer* aMaskLayer, EffectChain& aEffect); + ~AutoAddMaskEffect(); + + bool Failed() const { return mFailed; } + + private: + CompositableHost* mCompositable; + bool mFailed; + }; + + /** + * returns true if PlatformAllocBuffer will return a buffer that supports + * direct texturing + */ + static bool SupportsDirectTexturing(); + + static void PlatformSyncBeforeReplyUpdate(); + + void AddInvalidRegion(const nsIntRegion& aRegion) override { + mInvalidRegion.Or(mInvalidRegion, aRegion); + } + + Compositor* GetCompositor() const override { return mCompositor; } + TextureSourceProvider* GetTextureSourceProvider() const override { + return mCompositor; + } + + void NotifyShadowTreeTransaction() override; + + TextRenderer* GetTextRenderer() { return mTextRenderer; } + + void UnusedApzTransformWarning() { mUnusedApzTransformWarning = true; } + void DisabledApzWarning() { mDisabledApzWarning = true; } + + bool AsyncPanZoomEnabled() const override; + + public: + TextureFactoryIdentifier GetTextureFactoryIdentifier() override { + return mCompositor->GetTextureFactoryIdentifier(); + } + LayersBackend GetBackendType() override { + return mCompositor ? mCompositor->GetBackendType() + : LayersBackend::LAYERS_NONE; + } + void SetDiagnosticTypes(DiagnosticTypes aDiagnostics) override { + mCompositor->SetDiagnosticTypes(aDiagnostics); + } + + void InvalidateAll() override { + AddInvalidRegion(nsIntRegion(mRenderBounds)); + } + + void ForcePresent() override { mCompositor->ForcePresent(); } + + private: + /** Region we're clipping our current drawing to. */ + nsIntRegion mClippingRegion; + gfx::IntRect mRenderBounds; + + /** Current root layer. */ + LayerComposite* RootLayer() const; + + /** + * Update the invalid region and render it. + */ + void UpdateAndRender(); + + /** + * Render the current layer tree to the active target. + * Returns true if the current invalid region can be cleared, false if + * rendering was canceled. + */ + bool Render(const nsIntRegion& aInvalidRegion, + const nsIntRegion& aOpaqueRegion); +#if defined(MOZ_WIDGET_ANDROID) + void RenderToPresentationSurface(); + // Shifts the content down so the toolbar does not cover it. + // Returns the Y shift of the content in screen pixels + ScreenCoord GetContentShiftForToolbar(); + // Renders the static snapshot after the content has been rendered. + void RenderToolbar(); + // Used by robocop tests to get a snapshot of the frame buffer. + void HandlePixelsTarget(); +#endif + + /** + * We need to know our invalid region before we're ready to render. + */ + void InvalidateDebugOverlay(nsIntRegion& aInvalidRegion, + const gfx::IntRect& aBounds); + + /** + * Render debug overlays such as the FPS/FrameCounter above the frame. + */ + void RenderDebugOverlay(const gfx::IntRect& aBounds); + + void DrawBorder(const gfx::IntRect& aOuter, int32_t aBorderWidth, + const gfx::DeviceColor& aColor, + const gfx::Matrix4x4& aTransform); + void DrawTranslationWarningOverlay(const gfx::IntRect& aBounds); + + void UpdateDebugOverlayNativeLayers(); + + RefPtr PushGroupForLayerEffects(); + void PopGroupForLayerEffects(RefPtr aPreviousTarget, + gfx::IntRect aClipRect, bool aGrayscaleEffect, + bool aInvertEffect, float aContrastEffect); + + /** + * Create or recycle native layers to cover aRegion or aRect. + * This method takes existing layers from the front of aLayersToRecycle (or + * creates new layers if no layers are left to recycle) and appends them to + * the end of mNativeLayers. The "take from front, add to back" approach keeps + * the layer to rect assignment stable between frames. + * Updates the rect and opaqueness on the layers. For layers that moved or + * resized, *aWindowInvalidRegion is updated to include the area impacted by + * the move. + * Any layers left in aLayersToRecycle are not needed and can be disposed of. + */ + void PlaceNativeLayers(const gfx::IntRegion& aRegion, bool aOpaque, + std::deque>* aLayersToRecycle, + gfx::IntRegion* aWindowInvalidRegion); + void PlaceNativeLayer(const gfx::IntRect& aRect, bool aOpaque, + std::deque>* aLayersToRecycle, + gfx::IntRegion* aWindowInvalidRegion); + + bool mUnusedApzTransformWarning; + bool mDisabledApzWarning; + RefPtr mCompositor; + UniquePtr mClonedLayerTreeProperties; + + /** + * Context target, nullptr when drawing directly to our swap chain. + */ + RefPtr mTarget; + gfx::IntRect mTargetBounds; + + nsIntRegion mInvalidRegion; + + bool mInTransaction; + bool mIsCompositorReady; + + RefPtr mTwoPassTmpTarget; + ScreenshotGrabber mProfilerScreenshotGrabber; + RefPtr mTextRenderer; + RefPtr mNativeLayerRoot; + RefPtr mSurfacePoolHandle; + std::deque> mNativeLayers; + RefPtr mGPUStatsLayer; + RefPtr mUnusedTransformWarningLayer; + RefPtr mDisabledApzWarningLayer; + +#ifdef USE_SKIA + /** + * Render paint and composite times above the frame. + */ + void DrawPaintTimes(Compositor* aCompositor); + RefPtr mPaintCounter; +#endif +#if defined(MOZ_WIDGET_ANDROID) + public: + virtual void RequestScreenPixels( + UiCompositorControllerParent* aController) override { + mScreenPixelsTarget = aController; + } + + private: + UiCompositorControllerParent* mScreenPixelsTarget; +#endif // defined(MOZ_WIDGET_ANDROID) +}; + +/** + * Compositor layers are for use with OMTC on the compositor thread only. There + * must be corresponding Client layers on the content thread. For composite + * layers, the layer manager only maintains the layer tree. + */ +class HostLayer { + public: + explicit HostLayer(HostLayerManager* aManager) + : mCompositorManager(aManager), + mShadowOpacity(1.0), + mShadowTransformSetByAnimation(false), + mShadowOpacitySetByAnimation(false) {} + + virtual void SetLayerManager(HostLayerManager* aManager) { + mCompositorManager = aManager; + } + HostLayerManager* GetLayerManager() const { return mCompositorManager; } + + virtual ~HostLayer() = default; + + virtual LayerComposite* GetFirstChildComposite() { return nullptr; } + + virtual Layer* GetLayer() = 0; + + virtual LayerMLGPU* AsLayerMLGPU() { return nullptr; } + + virtual bool SetCompositableHost(CompositableHost*) { + // We must handle this gracefully, see bug 967824 + NS_WARNING( + "called SetCompositableHost for a layer type not accepting a " + "compositable"); + return false; + } + virtual CompositableHost* GetCompositableHost() = 0; + + /** + * The following methods are + * + * CONSTRUCTION PHASE ONLY + * + * They are analogous to the Layer interface. + */ + void SetShadowVisibleRegion(const LayerIntRegion& aRegion) { + mShadowVisibleRegion = aRegion; + } + void SetShadowVisibleRegion(LayerIntRegion&& aRegion) { + mShadowVisibleRegion = std::move(aRegion); + } + + void SetShadowOpacity(float aOpacity) { mShadowOpacity = aOpacity; } + void SetShadowOpacitySetByAnimation(bool aSetByAnimation) { + mShadowOpacitySetByAnimation = aSetByAnimation; + } + + void SetShadowClipRect(const Maybe& aRect) { + mShadowClipRect = aRect; + } + + void SetShadowBaseTransform(const gfx::Matrix4x4& aMatrix) { + mShadowTransform = aMatrix; + } + void SetShadowTransformSetByAnimation(bool aSetByAnimation) { + mShadowTransformSetByAnimation = aSetByAnimation; + } + + // These getters can be used anytime. + float GetShadowOpacity() { return mShadowOpacity; } + const Maybe& GetShadowClipRect() { + return mShadowClipRect; + } + virtual const LayerIntRegion& GetShadowVisibleRegion() { + return mShadowVisibleRegion; + } + const gfx::Matrix4x4& GetShadowBaseTransform() { return mShadowTransform; } + gfx::Matrix4x4 GetShadowTransform(); + bool GetShadowTransformSetByAnimation() { + return mShadowTransformSetByAnimation; + } + bool GetShadowOpacitySetByAnimation() { return mShadowOpacitySetByAnimation; } + + void RecomputeShadowVisibleRegionFromChildren(); + + protected: + HostLayerManager* mCompositorManager; + + gfx::Matrix4x4 mShadowTransform; + LayerIntRegion mShadowVisibleRegion; + Maybe mShadowClipRect; + float mShadowOpacity; + bool mShadowTransformSetByAnimation; + bool mShadowOpacitySetByAnimation; +}; + +/** + * Composite layers are for use with OMTC on the compositor thread only. There + * must be corresponding Client layers on the content thread. For composite + * layers, the layer manager only maintains the layer tree, all rendering is + * done by a Compositor (see Compositor.h). As such, composite layers are + * platform-independent and can be used on any platform for which there is a + * Compositor implementation. + * + * The composite layer tree reflects exactly the basic layer tree. To + * composite to screen, the layer manager walks the layer tree calling render + * methods which in turn call into their CompositableHosts' Composite methods. + * These call Compositor::DrawQuad to do the rendering. + * + * Mostly, layers are updated during the layers transaction. This is done from + * CompositableClient to CompositableHost without interacting with the layer. + * + * A reference to the Compositor is stored in LayerManagerComposite. + */ +class LayerComposite : public HostLayer { + public: + explicit LayerComposite(LayerManagerComposite* aManager); + + virtual ~LayerComposite(); + + void SetLayerManager(HostLayerManager* aManager) override; + + LayerComposite* GetFirstChildComposite() override { return nullptr; } + + /* Do NOT call this from the generic LayerComposite destructor. Only from the + * concrete class destructor + */ + virtual void Destroy(); + virtual void Cleanup() {} + + /** + * Perform a first pass over the layer tree to render all of the intermediate + * surfaces that we can. This allows us to avoid framebuffer switches in the + * middle of our render which is inefficient especially on mobile GPUs. This + * must be called before RenderLayer. + */ + virtual void Prepare(const RenderTargetIntRect& aClipRect) {} + + // TODO: This should also take RenderTargetIntRect like Prepare. + virtual void RenderLayer(const gfx::IntRect& aClipRect, + const Maybe& aGeometry) = 0; + + bool SetCompositableHost(CompositableHost*) override { + // We must handle this gracefully, see bug 967824 + NS_WARNING( + "called SetCompositableHost for a layer type not accepting a " + "compositable"); + return false; + } + + virtual void CleanupResources() = 0; + + virtual void DestroyFrontBuffer() {} + + void AddBlendModeEffect(EffectChain& aEffectChain); + + virtual void GenEffectChain(EffectChain& aEffect) {} + + void SetLayerComposited(bool value) { mLayerComposited = value; } + + void SetClearRect(const gfx::IntRect& aRect) { mClearRect = aRect; } + + bool HasLayerBeenComposited() { return mLayerComposited; } + gfx::IntRect GetClearRect() { return mClearRect; } + + // Returns false if the layer is attached to an older compositor. + bool HasStaleCompositor() const; + + /** + * Return the part of the visible region that has been fully rendered. + * While progressive drawing is in progress this region will be + * a subset of the shadow visible region. + */ + virtual nsIntRegion GetFullyRenderedRegion(); + + protected: + LayerManagerComposite* mCompositeManager; + + RefPtr mCompositor; + bool mDestroyed; + bool mLayerComposited; + gfx::IntRect mClearRect; +}; + +class WindowLMC : public profiler_screenshots::Window { + public: + explicit WindowLMC(Compositor* aCompositor) : mCompositor(aCompositor) {} + + already_AddRefed GetWindowContents( + const gfx::IntSize& aWindowSize) override; + already_AddRefed CreateDownscaleTarget( + const gfx::IntSize& aSize) override; + already_AddRefed + CreateAsyncReadbackBuffer(const gfx::IntSize& aSize) override; + + protected: + Compositor* mCompositor; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_LayerManagerComposite_H */ diff --git a/gfx/layers/composite/LayerManagerCompositeUtils.h b/gfx/layers/composite/LayerManagerCompositeUtils.h new file mode 100644 index 0000000000..d349cf99fc --- /dev/null +++ b/gfx/layers/composite/LayerManagerCompositeUtils.h @@ -0,0 +1,160 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_LayerManagerCompositeUtils_H +#define GFX_LayerManagerCompositeUtils_H + +#include // for size_t +#include "Layers.h" // for Layer +#include "Units.h" // for LayerIntRegion +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/gfx/BaseRect.h" // for operator- +#include "mozilla/gfx/Matrix.h" // for Matrix4x4 +#include "mozilla/gfx/Rect.h" // for IntRect, Rect, RoundedOut, IntRectTyped +#include "mozilla/layers/Compositor.h" // for Compositor, INIT_MODE_CLEAR +#include "mozilla/layers/Effects.h" // for EffectChain, EffectRenderTarget +#include "mozilla/layers/LayerManagerComposite.h" // for LayerManagerComposite::AutoAddMaskEffect, LayerComposite, LayerManagerComposite +#include "mozilla/layers/TextureHost.h" // for CompositingRenderTarget + +namespace mozilla { +namespace layers { + +// Render aLayer using aCompositor and apply all mask layers of aLayer: The +// layer's own mask layer (aLayer->GetMaskLayer()), and any ancestor mask +// layers. +// If more than one mask layer needs to be applied, we use intermediate surfaces +// (CompositingRenderTargets) for rendering, applying one mask layer at a time. +// Callers need to provide a callback function aRenderCallback that does the +// actual rendering of the source. It needs to have the following form: +// void (EffectChain& effectChain, const Rect& clipRect) +// aRenderCallback is called exactly once, inside this function, unless aLayer's +// visible region is completely clipped out (in that case, aRenderCallback won't +// be called at all). +// This function calls aLayer->AsHostLayer()->AddBlendModeEffect for the +// final rendering pass. +// +// (This function should really live in LayerManagerComposite.cpp, but we +// need to use templates for passing lambdas until bug 1164522 is resolved.) +template +void RenderWithAllMasks(Layer* aLayer, Compositor* aCompositor, + const gfx::IntRect& aClipRect, + RenderCallbackType aRenderCallback) { + Layer* firstMask = nullptr; + size_t maskLayerCount = 0; + size_t nextAncestorMaskLayer = 0; + + size_t ancestorMaskLayerCount = aLayer->GetAncestorMaskLayerCount(); + if (Layer* ownMask = aLayer->GetMaskLayer()) { + firstMask = ownMask; + maskLayerCount = ancestorMaskLayerCount + 1; + nextAncestorMaskLayer = 0; + } else if (ancestorMaskLayerCount > 0) { + firstMask = aLayer->GetAncestorMaskLayerAt(0); + maskLayerCount = ancestorMaskLayerCount; + nextAncestorMaskLayer = 1; + } else { + // no mask layers at all + } + + if (maskLayerCount <= 1) { + // This is the common case. Render in one pass and return. + EffectChain effectChain(aLayer); + LayerManagerComposite::AutoAddMaskEffect autoMaskEffect(firstMask, + effectChain); + static_cast(aLayer->AsHostLayer()) + ->AddBlendModeEffect(effectChain); + aRenderCallback(effectChain, aClipRect); + return; + } + + // We have multiple mask layers. + // We split our list of mask layers into three parts: + // (1) The first mask + // (2) The list of intermediate masks (every mask except first and last) + // (3) The final mask. + // Part (2) can be empty. + // For parts (1) and (2) we need to allocate intermediate surfaces to render + // into. The final mask gets rendered into the original render target. + + // Calculate the size of the intermediate surfaces. + gfx::Rect visibleRect( + aLayer->GetLocalVisibleRegion().GetBounds().ToUnknownRect()); + gfx::Matrix4x4 transform = aLayer->GetEffectiveTransform(); + // TODO: Use RenderTargetIntRect and TransformBy here + gfx::IntRect surfaceRect = RoundedOut( + transform.TransformAndClipBounds(visibleRect, gfx::Rect(aClipRect))); + if (surfaceRect.IsEmpty()) { + return; + } + + RefPtr originalTarget = + aCompositor->GetCurrentRenderTarget(); + + RefPtr firstTarget = + aCompositor->CreateRenderTarget(surfaceRect, INIT_MODE_CLEAR); + if (!firstTarget) { + return; + } + + // Render the source while applying the first mask. + aCompositor->SetRenderTarget(firstTarget); + { + EffectChain firstEffectChain(aLayer); + LayerManagerComposite::AutoAddMaskEffect firstMaskEffect(firstMask, + firstEffectChain); + aRenderCallback(firstEffectChain, aClipRect - surfaceRect.TopLeft()); + // firstTarget now contains the transformed source with the first mask and + // opacity already applied. + } + + // Apply the intermediate masks. + gfx::IntRect intermediateClip(surfaceRect - surfaceRect.TopLeft()); + RefPtr previousTarget = firstTarget; + for (size_t i = nextAncestorMaskLayer; i < ancestorMaskLayerCount - 1; i++) { + Layer* intermediateMask = aLayer->GetAncestorMaskLayerAt(i); + RefPtr intermediateTarget = + aCompositor->CreateRenderTarget(surfaceRect, INIT_MODE_CLEAR); + if (!intermediateTarget) { + break; + } + aCompositor->SetRenderTarget(intermediateTarget); + EffectChain intermediateEffectChain(aLayer); + LayerManagerComposite::AutoAddMaskEffect intermediateMaskEffect( + intermediateMask, intermediateEffectChain); + if (intermediateMaskEffect.Failed()) { + continue; + } + intermediateEffectChain.mPrimaryEffect = + new EffectRenderTarget(previousTarget); + aCompositor->DrawQuad(gfx::Rect(surfaceRect), intermediateClip, + intermediateEffectChain, 1.0, gfx::Matrix4x4()); + previousTarget = intermediateTarget; + } + + aCompositor->SetRenderTarget(originalTarget); + + // Apply the final mask, rendering into originalTarget. + EffectChain finalEffectChain(aLayer); + finalEffectChain.mPrimaryEffect = new EffectRenderTarget(previousTarget); + Layer* finalMask = aLayer->GetAncestorMaskLayerAt(ancestorMaskLayerCount - 1); + + // The blend mode needs to be applied in this final step, because this is + // where we're blending with the actual background (which is in + // originalTarget). + static_cast(aLayer->AsHostLayer()) + ->AddBlendModeEffect(finalEffectChain); + LayerManagerComposite::AutoAddMaskEffect autoMaskEffect(finalMask, + finalEffectChain); + if (!autoMaskEffect.Failed()) { + aCompositor->DrawQuad(gfx::Rect(surfaceRect), aClipRect, finalEffectChain, + 1.0, gfx::Matrix4x4()); + } +} + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_LayerManagerCompositeUtils_H */ diff --git a/gfx/layers/composite/PaintCounter.cpp b/gfx/layers/composite/PaintCounter.cpp new file mode 100644 index 0000000000..fbf1552116 --- /dev/null +++ b/gfx/layers/composite/PaintCounter.cpp @@ -0,0 +1,79 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/gfx/Point.h" // for IntSize, Point +#include "mozilla/gfx/Rect.h" // for Rect +#include "mozilla/gfx/Types.h" // for Color, SurfaceFormat +#include "mozilla/layers/Compositor.h" // for Compositor +#include "mozilla/layers/CompositorTypes.h" +#include "mozilla/layers/Effects.h" // for Effect, EffectChain, etc +#include "mozilla/TimeStamp.h" // for TimeStamp, TimeDuration +#include "mozilla/Sprintf.h" + +#include "mozilla/gfx/HelpersSkia.h" +#include "skia/include/core/SkFont.h" +#include "PaintCounter.h" + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +// Positioned below the chrome UI +IntRect PaintCounter::mRect = IntRect(0, 175, 300, 60); + +PaintCounter::PaintCounter() { + mFormat = SurfaceFormat::B8G8R8A8; + mSurface = Factory::CreateDataSourceSurface(mRect.Size(), mFormat); + mMap.emplace(mSurface, DataSourceSurface::READ_WRITE); + mStride = mMap->GetStride(); + + mCanvas = SkCanvas::MakeRasterDirect(MakeSkiaImageInfo(mRect.Size(), mFormat), + mMap->GetData(), mStride); + mCanvas->clear(SK_ColorWHITE); +} + +PaintCounter::~PaintCounter() { + mSurface = nullptr; + mTextureSource = nullptr; + mTexturedEffect = nullptr; +} + +void PaintCounter::Draw(Compositor* aCompositor, TimeDuration aPaintTime, + TimeDuration aCompositeTime) { + char buffer[48]; + SprintfLiteral(buffer, "P: %.2f C: %.2f", aPaintTime.ToMilliseconds(), + aCompositeTime.ToMilliseconds()); + + SkPaint paint; + paint.setColor(SkColorSetRGB(0, 255, 0)); + paint.setAntiAlias(true); + + SkFont font(SkTypeface::MakeDefault(), 32); + + mCanvas->clear(SK_ColorTRANSPARENT); + mCanvas->drawString(buffer, 10, 30, font, paint); + mCanvas->flush(); + + if (!mTextureSource) { + mTextureSource = aCompositor->CreateDataTextureSource(); + mTexturedEffect = CreateTexturedEffect(mFormat, mTextureSource, + SamplingFilter::POINT, true); + mTexturedEffect->mTextureCoords = Rect(0, 0, 1.0f, 1.0f); + } + + mTextureSource->Update(mSurface); + + EffectChain effectChain; + effectChain.mPrimaryEffect = mTexturedEffect; + + gfx::Matrix4x4 identity; + Rect rect(mRect.X(), mRect.Y(), mRect.Width(), mRect.Height()); + aCompositor->DrawQuad(rect, mRect, effectChain, 1.0, identity); +} + +} // end namespace layers +} // end namespace mozilla diff --git a/gfx/layers/composite/PaintCounter.h b/gfx/layers/composite/PaintCounter.h new file mode 100644 index 0000000000..084330c998 --- /dev/null +++ b/gfx/layers/composite/PaintCounter.h @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_PaintCounter_h_ +#define mozilla_layers_PaintCounter_h_ + +#include // for std::map +#include "mozilla/Maybe.h" +#include "mozilla/RefPtr.h" // for already_AddRefed, RefCounted +#include "mozilla/TimeStamp.h" // for TimeStamp, TimeDuration +#include "skia/include/core/SkCanvas.h" + +namespace mozilla { +namespace layers { + +class Compositor; + +using namespace mozilla::gfx; +using namespace mozilla::gl; + +// Keeps track and paints how long a full invalidation paint takes to rasterize +// and composite. +class PaintCounter { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PaintCounter) + + PaintCounter(); + void Draw(Compositor* aCompositor, TimeDuration aPaintTime, + TimeDuration aCompositeTime); + static IntRect GetPaintRect() { return PaintCounter::mRect; } + + private: + virtual ~PaintCounter(); + + SurfaceFormat mFormat; + std::unique_ptr mCanvas; + IntSize mSize; + int mStride; + + RefPtr mSurface; + RefPtr mTextureSource; + RefPtr mTexturedEffect; + Maybe mMap; + static IntRect mRect; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_opengl_PaintCounter_h_ diff --git a/gfx/layers/composite/PaintedLayerComposite.cpp b/gfx/layers/composite/PaintedLayerComposite.cpp new file mode 100644 index 0000000000..fe8028d12f --- /dev/null +++ b/gfx/layers/composite/PaintedLayerComposite.cpp @@ -0,0 +1,169 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PaintedLayerComposite.h" +#include "CompositableHost.h" // for TiledLayerProperties, etc +#include "FrameMetrics.h" // for FrameMetrics +#include "Units.h" // for CSSRect, LayerPixel, etc +#include "gfxEnv.h" // for gfxEnv +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/gfx/Matrix.h" // for Matrix4x4 +#include "mozilla/gfx/Point.h" // for Point +#include "mozilla/gfx/Polygon.h" // for Polygon +#include "mozilla/gfx/Rect.h" // for RoundedToInt, Rect +#include "mozilla/gfx/Types.h" // for SamplingFilter::LINEAR +#include "mozilla/layers/Compositor.h" // for Compositor +#include "mozilla/layers/ContentHost.h" // for ContentHost +#include "mozilla/layers/Effects.h" // for EffectChain +#include "mozilla/layers/LayerManagerCompositeUtils.h" +#include "mozilla/mozalloc.h" // for operator delete +#include "nsAString.h" +#include "mozilla/RefPtr.h" // for nsRefPtr +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "nsMathUtils.h" // for NS_lround +#include "nsString.h" // for nsAutoCString +#include "TextRenderer.h" +#include "GeckoProfiler.h" + +namespace mozilla { +namespace layers { + +PaintedLayerComposite::PaintedLayerComposite(LayerManagerComposite* aManager) + : PaintedLayer(aManager, nullptr), + LayerComposite(aManager), + mBuffer(nullptr) { + MOZ_COUNT_CTOR(PaintedLayerComposite); + mImplData = static_cast(this); +} + +PaintedLayerComposite::~PaintedLayerComposite() { + MOZ_COUNT_DTOR(PaintedLayerComposite); + CleanupResources(); +} + +bool PaintedLayerComposite::SetCompositableHost(CompositableHost* aHost) { + switch (aHost->GetType()) { + case CompositableType::CONTENT_TILED: + case CompositableType::CONTENT_SINGLE: + case CompositableType::CONTENT_DOUBLE: { + ContentHost* newBuffer = static_cast(aHost); + if (mBuffer && newBuffer != mBuffer) { + mBuffer->Detach(this); + } + mBuffer = newBuffer; + return true; + } + default: + return false; + } +} + +void PaintedLayerComposite::Disconnect() { Destroy(); } + +void PaintedLayerComposite::Destroy() { + if (!mDestroyed) { + CleanupResources(); + mDestroyed = true; + } +} + +Layer* PaintedLayerComposite::GetLayer() { return this; } + +void PaintedLayerComposite::SetLayerManager(HostLayerManager* aManager) { + LayerComposite::SetLayerManager(aManager); + mManager = aManager; + if (mBuffer && mCompositor) { + mBuffer->SetTextureSourceProvider(mCompositor); + } +} + +void PaintedLayerComposite::RenderLayer(const gfx::IntRect& aClipRect, + const Maybe& aGeometry) { + if (!mBuffer || !mBuffer->IsAttached()) { + return; + } + AUTO_PROFILER_LABEL("PaintedLayerComposite::RenderLayer", GRAPHICS); + + Compositor* compositor = mCompositeManager->GetCompositor(); + + MOZ_ASSERT(mBuffer->GetTextureSourceProvider() == compositor && + mBuffer->GetLayer() == this, + "buffer is corrupted"); + + const nsIntRegion visibleRegion = GetLocalVisibleRegion().ToUnknownRegion(); + +#ifdef MOZ_DUMP_PAINTING + if (gfxEnv::DumpCompositorTextures()) { + RefPtr surf = mBuffer->GetAsSurface(); + if (surf) { + WriteSnapshotToDumpFile(this, surf); + } + } +#endif + + RenderWithAllMasks( + this, compositor, aClipRect, + [&](EffectChain& effectChain, const gfx::IntRect& clipRect) { + mBuffer->SetPaintWillResample(MayResample()); + + mBuffer->Composite(compositor, this, effectChain, GetEffectiveOpacity(), + GetEffectiveTransform(), GetSamplingFilter(), + clipRect, &visibleRegion, aGeometry); + }); + + mBuffer->BumpFlashCounter(); + + compositor->MakeCurrent(); +} + +CompositableHost* PaintedLayerComposite::GetCompositableHost() { + if (mBuffer && mBuffer->IsAttached()) { + return mBuffer.get(); + } + + return nullptr; +} + +void PaintedLayerComposite::CleanupResources() { + if (mBuffer) { + mBuffer->Detach(this); + } + mBuffer = nullptr; +} + +bool PaintedLayerComposite::IsOpaque() { + if (!mBuffer || !mBuffer->IsAttached()) { + return false; + } + return PaintedLayer::IsOpaque(); +} + +void PaintedLayerComposite::GenEffectChain(EffectChain& aEffect) { + aEffect.mLayerRef = this; + aEffect.mPrimaryEffect = mBuffer->GenEffect(GetSamplingFilter()); +} + +void PaintedLayerComposite::PrintInfo(std::stringstream& aStream, + const char* aPrefix) { + PaintedLayer::PrintInfo(aStream, aPrefix); + if (mBuffer && mBuffer->IsAttached()) { + aStream << "\n"; + nsAutoCString pfx(aPrefix); + pfx += " "; + mBuffer->PrintInfo(aStream, pfx.get()); + } +} + +const gfx::TiledIntRegion& PaintedLayerComposite::GetInvalidRegion() { + if (mBuffer) { + nsIntRegion region = mInvalidRegion.GetRegion(); + mBuffer->AddAnimationInvalidation(region); + } + return mInvalidRegion; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/PaintedLayerComposite.h b/gfx/layers/composite/PaintedLayerComposite.h new file mode 100644 index 0000000000..c2c22eb433 --- /dev/null +++ b/gfx/layers/composite/PaintedLayerComposite.h @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_PaintedLayerComposite_H +#define GFX_PaintedLayerComposite_H + +#include "Layers.h" // for Layer (ptr only), etc +#include "mozilla/gfx/Rect.h" +#include "mozilla/Attributes.h" // for override +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/layers/LayerManagerComposite.h" // for LayerComposite, etc +#include "mozilla/layers/LayersTypes.h" // for LayerRenderState, etc +#include "nsRegion.h" // for nsIntRegion +#include "nscore.h" // for nsACString + +namespace mozilla { +namespace layers { + +/** + * PaintedLayers use ContentHosts for their compsositable host. + * By using different ContentHosts, PaintedLayerComposite support tiled and + * non-tiled PaintedLayers and single or double buffering. + */ + +class CompositableHost; +class ContentHost; + +class PaintedLayerComposite : public PaintedLayer, public LayerComposite { + public: + explicit PaintedLayerComposite(LayerManagerComposite* aManager); + + protected: + virtual ~PaintedLayerComposite(); + + public: + void Disconnect() override; + + CompositableHost* GetCompositableHost() override; + + void Destroy() override; + + Layer* GetLayer() override; + + void SetLayerManager(HostLayerManager* aManager) override; + + void RenderLayer(const gfx::IntRect& aClipRect, + const Maybe& aGeometry) override; + + void CleanupResources() override; + + bool IsOpaque() override; + + void GenEffectChain(EffectChain& aEffect) override; + + bool SetCompositableHost(CompositableHost* aHost) override; + + HostLayer* AsHostLayer() override { return this; } + + void InvalidateRegion(const nsIntRegion& aRegion) override { + MOZ_CRASH("PaintedLayerComposites can't fill invalidated regions"); + } + + const gfx::TiledIntRegion& GetInvalidRegion() override; + + MOZ_LAYER_DECL_NAME("PaintedLayerComposite", TYPE_PAINTED) + + protected: + virtual void PrintInfo(std::stringstream& aStream, + const char* aPrefix) override; + + private: + gfx::SamplingFilter GetSamplingFilter() { + return gfx::SamplingFilter::LINEAR; + } + + private: + RefPtr mBuffer; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_PaintedLayerComposite_H */ diff --git a/gfx/layers/composite/TextRenderer.cpp b/gfx/layers/composite/TextRenderer.cpp new file mode 100644 index 0000000000..3bd1a7d9b3 --- /dev/null +++ b/gfx/layers/composite/TextRenderer.cpp @@ -0,0 +1,245 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TextRenderer.h" +#include "FontData.h" +#include "ConsolasFontData.h" +#include "png.h" +#include "mozilla/Base64.h" +#include "mozilla/layers/Compositor.h" +#include "mozilla/layers/TextureHost.h" +#include "mozilla/layers/Effects.h" + +namespace mozilla { +namespace layers { + +using namespace gfx; + +const Float sBackgroundOpacity = 0.8f; +const SurfaceFormat sTextureFormat = SurfaceFormat::B8G8R8A8; + +static void PNGAPI info_callback(png_structp png_ptr, png_infop info_ptr) { + png_read_update_info(png_ptr, info_ptr); +} + +static void PNGAPI row_callback(png_structp png_ptr, png_bytep new_row, + png_uint_32 row_num, int pass) { + MOZ_ASSERT(sTextureFormat == SurfaceFormat::B8G8R8A8); + + TextRenderer::FontCache* cache = + static_cast(png_get_progressive_ptr(png_ptr)); + + uint32_t* dst = + (uint32_t*)(cache->mMap.mData + cache->mMap.mStride * row_num); + + for (uint32_t x = 0; x < cache->mInfo->mTextureWidth; x++) { + // We blend to a transparent white background, this will make text readable + // even if it's on a dark background. Without hurting our ability to + // interact with the content behind the text. + Float alphaValue = Float(0xFF - new_row[x]) / 255.0f; + Float baseValue = sBackgroundOpacity * (1.0f - alphaValue); + // FIXME(aosmond): PNGs may have color profiles. Should we be checking for + // that and performing color management? + sRGBColor pixelColor(baseValue, baseValue, baseValue, + baseValue + alphaValue); + dst[x] = pixelColor.ToABGR(); + } +} + +TextRenderer::~TextRenderer() = default; + +TextRenderer::FontCache::~FontCache() { mGlyphBitmaps->Unmap(); } + +void TextRenderer::RenderText(Compositor* aCompositor, const std::string& aText, + const IntPoint& aOrigin, + const Matrix4x4& aTransform, uint32_t aTextSize, + uint32_t aTargetPixelWidth, FontType aFontType) { + const FontBitmapInfo* info = GetFontInfo(aFontType); + + // For now we only have a bitmap font with a 24px cell size, so we just + // scale it up if the user wants larger text. + Float scaleFactor = Float(aTextSize) / Float(info->mCellHeight); + aTargetPixelWidth /= scaleFactor; + + RefPtr src = + RenderText(aCompositor, aText, aTargetPixelWidth, aFontType); + if (!src) { + return; + } + + RefPtr effect = new EffectRGB(src, true, SamplingFilter::LINEAR); + EffectChain chain; + chain.mPrimaryEffect = effect; + + Matrix4x4 transform = aTransform; + transform.PreScale(scaleFactor, scaleFactor, 1.0f); + + IntRect drawRect(aOrigin, src->GetSize()); + IntRect clip(-10000, -10000, 20000, 20000); + aCompositor->DrawQuad(Rect(drawRect), clip, chain, 1.0f, transform); +} + +IntSize TextRenderer::ComputeSurfaceSize(const std::string& aText, + uint32_t aTargetPixelWidth, + FontType aFontType) { + if (!EnsureInitialized(aFontType)) { + return IntSize(); + } + + FontCache* cache = mFonts[aFontType].get(); + const FontBitmapInfo* info = cache->mInfo; + + uint32_t numLines = 1; + uint32_t maxWidth = 0; + uint32_t lineWidth = 0; + // Calculate the size of the surface needed to draw all the glyphs. + for (uint32_t i = 0; i < aText.length(); i++) { + // Insert a line break if we go past the TargetPixelWidth. + // XXX - this has the downside of overrunning the intended width, causing + // things at the edge of a window to be cut off. + if (aText[i] == '\n' || + (aText[i] == ' ' && lineWidth > aTargetPixelWidth)) { + numLines++; + lineWidth = 0; + continue; + } + + lineWidth += info->GetGlyphWidth(aText[i]); + maxWidth = std::max(lineWidth, maxWidth); + } + + return IntSize(maxWidth, numLines * info->mCellHeight); +} + +RefPtr TextRenderer::RenderText(TextureSourceProvider* aProvider, + const std::string& aText, + uint32_t aTargetPixelWidth, + FontType aFontType) { + if (!EnsureInitialized(aFontType)) { + return nullptr; + } + + IntSize size = ComputeSurfaceSize(aText, aTargetPixelWidth, aFontType); + + // Create a DrawTarget to draw our glyphs to. + RefPtr dt = + Factory::CreateDrawTarget(BackendType::SKIA, size, sTextureFormat); + + RenderTextToDrawTarget(dt, aText, aTargetPixelWidth, aFontType); + RefPtr surf = dt->Snapshot(); + RefPtr dataSurf = surf->GetDataSurface(); + RefPtr src = aProvider->CreateDataTextureSource(); + + if (!src->Update(dataSurf)) { + // Upload failed. + return nullptr; + } + + return src; +} + +void TextRenderer::RenderTextToDrawTarget(DrawTarget* aDrawTarget, + const std::string& aText, + uint32_t aTargetPixelWidth, + FontType aFontType) { + if (!EnsureInitialized(aFontType)) { + return; + } + + // Initialize the DrawTarget to transparent white. + // FIXME(aosmond): Should the background be color managed if we color manage + // the glyphs or is this part of a mask? + IntSize size = aDrawTarget->GetSize(); + aDrawTarget->FillRect( + Rect(0, 0, size.width, size.height), + ColorPattern(DeviceColor(1.0, 1.0, 1.0, sBackgroundOpacity)), + DrawOptions(1.0, CompositionOp::OP_SOURCE)); + + IntPoint currentPos; + + FontCache* cache = mFonts[aFontType].get(); + const FontBitmapInfo* info = cache->mInfo; + + const unsigned int kGlyphsPerLine = info->mTextureWidth / info->mCellWidth; + + // Copy our glyphs onto the DrawTarget. + for (uint32_t i = 0; i < aText.length(); i++) { + if (aText[i] == '\n' || + (aText[i] == ' ' && currentPos.x > int32_t(aTargetPixelWidth))) { + currentPos.y += info->mCellHeight; + currentPos.x = 0; + continue; + } + + uint32_t index = aText[i] - info->mFirstChar; + uint32_t cellIndexY = index / kGlyphsPerLine; + uint32_t cellIndexX = index - (cellIndexY * kGlyphsPerLine); + uint32_t glyphWidth = info->GetGlyphWidth(aText[i]); + IntRect srcRect(cellIndexX * info->mCellWidth, + cellIndexY * info->mCellHeight, glyphWidth, + info->mCellHeight); + + aDrawTarget->CopySurface(cache->mGlyphBitmaps, srcRect, currentPos); + + currentPos.x += glyphWidth; + } +} + +/* static */ const FontBitmapInfo* TextRenderer::GetFontInfo(FontType aType) { + switch (aType) { + case FontType::Default: + return &sDefaultCompositorFont; + case FontType::FixedWidth: + return &sFixedWidthCompositorFont; + default: + MOZ_ASSERT_UNREACHABLE("unknown font type"); + return nullptr; + } +} + +bool TextRenderer::EnsureInitialized(FontType aType) { + if (mFonts[aType]) { + return true; + } + + const FontBitmapInfo* info = GetFontInfo(aType); + + IntSize size(info->mTextureWidth, info->mTextureHeight); + RefPtr surface = + Factory::CreateDataSourceSurface(size, sTextureFormat); + if (NS_WARN_IF(!surface)) { + return false; + } + + DataSourceSurface::MappedSurface map; + if (NS_WARN_IF(!surface->Map(DataSourceSurface::MapType::READ_WRITE, &map))) { + return false; + } + + UniquePtr cache = MakeUnique(); + cache->mGlyphBitmaps = surface; + cache->mMap = map; + cache->mInfo = info; + + png_structp png_ptr = NULL; + png_ptr = + png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + + png_set_progressive_read_fn(png_ptr, cache.get(), info_callback, row_callback, + nullptr); + png_infop info_ptr = NULL; + info_ptr = png_create_info_struct(png_ptr); + + png_process_data(png_ptr, info_ptr, (uint8_t*)info->mPNG, info->mPNGLength); + + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + + mFonts[aType] = std::move(cache); + return true; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/TextRenderer.h b/gfx/layers/composite/TextRenderer.h new file mode 100644 index 0000000000..e88fc1d8f5 --- /dev/null +++ b/gfx/layers/composite/TextRenderer.h @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_TextRenderer_H +#define GFX_TextRenderer_H + +#include "mozilla/EnumeratedArray.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/UniquePtr.h" +#include "nsISupportsImpl.h" +#include + +namespace mozilla { +namespace layers { + +class Compositor; +class TextureSource; +class TextureSourceProvider; +struct FontBitmapInfo; + +class TextRenderer final { + ~TextRenderer(); + + public: + NS_INLINE_DECL_REFCOUNTING(TextRenderer) + + enum class FontType { Default, FixedWidth, NumTypes }; + + TextRenderer() = default; + + RefPtr RenderText(TextureSourceProvider* aProvider, + const std::string& aText, + uint32_t aTargetPixelWidth, + FontType aFontType); + + void RenderText(Compositor* aCompositor, const std::string& aText, + const gfx::IntPoint& aOrigin, + const gfx::Matrix4x4& aTransform, uint32_t aTextSize, + uint32_t aTargetPixelWidth, + FontType aFontType = FontType::Default); + + gfx::IntSize ComputeSurfaceSize(const std::string& aText, + uint32_t aTargetPixelWidth, + FontType aFontType = FontType::Default); + + void RenderTextToDrawTarget(gfx::DrawTarget* aDrawTarget, + const std::string& aText, + uint32_t aTargetPixelWidth, + FontType aFontType = FontType::Default); + + struct FontCache { + ~FontCache(); + RefPtr mGlyphBitmaps; + gfx::DataSourceSurface::MappedSurface mMap; + const FontBitmapInfo* mInfo; + }; + + protected: + // Note that this may still fail to set mGlyphBitmaps to a valid value + // if the underlying CreateDataSourceSurface fails for some reason. + bool EnsureInitialized(FontType aType); + + static const FontBitmapInfo* GetFontInfo(FontType aType); + + private: + EnumeratedArray> mFonts; +}; + +struct FontBitmapInfo { + Maybe mGlyphWidth; + Maybe mGlyphWidths; + unsigned int mTextureWidth; + unsigned int mTextureHeight; + unsigned int mCellWidth; + unsigned int mCellHeight; + unsigned int mFirstChar; + const unsigned char* mPNG; + size_t mPNGLength; + + unsigned int GetGlyphWidth(char aGlyph) const { + if (mGlyphWidth) { + return mGlyphWidth.value(); + } + MOZ_ASSERT(unsigned(aGlyph) >= mFirstChar); + return mGlyphWidths.value()[unsigned(aGlyph) - mFirstChar]; + } +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/composite/TextureHost.cpp b/gfx/layers/composite/TextureHost.cpp new file mode 100644 index 0000000000..92ced1ee89 --- /dev/null +++ b/gfx/layers/composite/TextureHost.cpp @@ -0,0 +1,1371 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TextureHost.h" + +#include "CompositableHost.h" // for CompositableHost +#include "LayerScope.h" +#include "mozilla/gfx/2D.h" // for DataSourceSurface, Factory +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/ipc/Shmem.h" // for Shmem +#include "mozilla/layers/AsyncImagePipelineManager.h" +#include "mozilla/layers/BufferTexture.h" +#include "mozilla/layers/CompositableTransactionParent.h" // for CompositableParentManager +#include "mozilla/layers/CompositorBridgeParent.h" +#include "mozilla/layers/Compositor.h" // for Compositor +#include "mozilla/layers/ISurfaceAllocator.h" // for ISurfaceAllocator +#include "mozilla/layers/ImageBridgeParent.h" // for ImageBridgeParent +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor, etc +#include "mozilla/layers/TextureHostBasic.h" +#include "mozilla/layers/TextureHostOGL.h" // for TextureHostOGL +#include "mozilla/layers/ImageDataSerializer.h" +#include "mozilla/layers/TextureClient.h" +#ifdef XP_DARWIN +# include "mozilla/layers/TextureSync.h" +#endif +#include "mozilla/layers/GPUVideoTextureHost.h" +#include "mozilla/layers/WebRenderTextureHost.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/webrender/RenderBufferTextureHost.h" +#include "mozilla/webrender/RenderBufferTextureHostSWGL.h" +#include "mozilla/webrender/RenderExternalTextureHost.h" +#include "mozilla/webrender/RenderThread.h" +#include "mozilla/webrender/WebRenderAPI.h" +#include "nsAString.h" +#include "mozilla/RefPtr.h" // for nsRefPtr +#include "nsPrintfCString.h" // for nsPrintfCString +#include "mozilla/layers/PTextureParent.h" +#include "mozilla/Unused.h" +#include +#include "../opengl/CompositorOGL.h" + +#include "gfxUtils.h" +#include "IPDLActor.h" + +#ifdef MOZ_ENABLE_D3D10_LAYER +# include "../d3d11/CompositorD3D11.h" +#endif + +#ifdef MOZ_X11 +# include "mozilla/layers/X11TextureHost.h" +#endif + +#ifdef XP_MACOSX +# include "../opengl/MacIOSurfaceTextureHostOGL.h" +#endif + +#ifdef XP_WIN +# include "mozilla/layers/TextureD3D11.h" +# include "mozilla/layers/TextureDIB.h" +#endif + +#if 0 +# define RECYCLE_LOG(...) printf_stderr(__VA_ARGS__) +#else +# define RECYCLE_LOG(...) \ + do { \ + } while (0) +#endif + +namespace mozilla { +namespace layers { + +/** + * TextureParent is the host-side IPDL glue between TextureClient and + * TextureHost. It is an IPDL actor just like LayerParent, CompositableParent, + * etc. + */ +class TextureParent : public ParentActor { + public: + TextureParent(HostIPCAllocator* aAllocator, uint64_t aSerial, + const wr::MaybeExternalImageId& aExternalImageId); + + virtual ~TextureParent(); + + bool Init(const SurfaceDescriptor& aSharedData, + const ReadLockDescriptor& aReadLock, + const LayersBackend& aLayersBackend, const TextureFlags& aFlags); + + void NotifyNotUsed(uint64_t aTransactionId); + + mozilla::ipc::IPCResult RecvRecycleTexture( + const TextureFlags& aTextureFlags) final; + + TextureHost* GetTextureHost() { return mTextureHost; } + + void Destroy() override; + + uint64_t GetSerial() const { return mSerial; } + + HostIPCAllocator* mSurfaceAllocator; + RefPtr mTextureHost; + // mSerial is unique in TextureClient's process. + const uint64_t mSerial; + wr::MaybeExternalImageId mExternalImageId; +}; + +static bool WrapWithWebRenderTextureHost(ISurfaceAllocator* aDeallocator, + LayersBackend aBackend, + TextureFlags aFlags) { + if ((aFlags & TextureFlags::SNAPSHOT) || + (aBackend != LayersBackend::LAYERS_WR) || + (!aDeallocator->UsesImageBridge() && + !aDeallocator->AsCompositorBridgeParentBase())) { + return false; + } + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +PTextureParent* TextureHost::CreateIPDLActor( + HostIPCAllocator* aAllocator, const SurfaceDescriptor& aSharedData, + const ReadLockDescriptor& aReadLock, LayersBackend aLayersBackend, + TextureFlags aFlags, uint64_t aSerial, + const wr::MaybeExternalImageId& aExternalImageId) { + TextureParent* actor = + new TextureParent(aAllocator, aSerial, aExternalImageId); + if (!actor->Init(aSharedData, aReadLock, aLayersBackend, aFlags)) { + actor->ActorDestroy(ipc::IProtocol::ActorDestroyReason::FailedConstructor); + delete actor; + return nullptr; + } + return actor; +} + +// static +bool TextureHost::DestroyIPDLActor(PTextureParent* actor) { + delete actor; + return true; +} + +// static +bool TextureHost::SendDeleteIPDLActor(PTextureParent* actor) { + return PTextureParent::Send__delete__(actor); +} + +// static +TextureHost* TextureHost::AsTextureHost(PTextureParent* actor) { + if (!actor) { + return nullptr; + } + return static_cast(actor)->mTextureHost; +} + +// static +uint64_t TextureHost::GetTextureSerial(PTextureParent* actor) { + if (!actor) { + return UINT64_MAX; + } + return static_cast(actor)->mSerial; +} + +PTextureParent* TextureHost::GetIPDLActor() { return mActor; } + +void TextureHost::SetLastFwdTransactionId(uint64_t aTransactionId) { + MOZ_ASSERT(mFwdTransactionId <= aTransactionId); + mFwdTransactionId = aTransactionId; +} + +already_AddRefed CreateDummyBufferTextureHost( + mozilla::layers::LayersBackend aBackend, + mozilla::layers::TextureFlags aFlags) { + // Ensure that the host will delete the memory. + aFlags &= ~TextureFlags::DEALLOCATE_CLIENT; + UniquePtr textureData(BufferTextureData::Create( + gfx::IntSize(1, 1), gfx::SurfaceFormat::B8G8R8A8, gfx::BackendType::SKIA, + aBackend, aFlags, TextureAllocationFlags::ALLOC_DEFAULT, nullptr)); + SurfaceDescriptor surfDesc; + textureData->Serialize(surfDesc); + const SurfaceDescriptorBuffer& bufferDesc = + surfDesc.get_SurfaceDescriptorBuffer(); + const MemoryOrShmem& data = bufferDesc.data(); + RefPtr host = + new MemoryTextureHost(reinterpret_cast(data.get_uintptr_t()), + bufferDesc.desc(), aFlags); + return host.forget(); +} + +already_AddRefed TextureHost::Create( + const SurfaceDescriptor& aDesc, const ReadLockDescriptor& aReadLock, + ISurfaceAllocator* aDeallocator, LayersBackend aBackend, + TextureFlags aFlags, wr::MaybeExternalImageId& aExternalImageId) { + RefPtr result; + + switch (aDesc.type()) { + case SurfaceDescriptor::TSurfaceDescriptorBuffer: + case SurfaceDescriptor::TSurfaceDescriptorDIB: + case SurfaceDescriptor::TSurfaceDescriptorFileMapping: + case SurfaceDescriptor::TSurfaceDescriptorGPUVideo: + result = CreateBackendIndependentTextureHost(aDesc, aDeallocator, + aBackend, aFlags); + break; + + case SurfaceDescriptor::TEGLImageDescriptor: + case SurfaceDescriptor::TSurfaceTextureDescriptor: + case SurfaceDescriptor::TSurfaceDescriptorAndroidHardwareBuffer: + case SurfaceDescriptor::TSurfaceDescriptorSharedGLTexture: + case SurfaceDescriptor::TSurfaceDescriptorDMABuf: + result = CreateTextureHostOGL(aDesc, aDeallocator, aBackend, aFlags); + break; + + case SurfaceDescriptor::TSurfaceDescriptorMacIOSurface: + if (aBackend == LayersBackend::LAYERS_OPENGL || + aBackend == LayersBackend::LAYERS_WR) { + result = CreateTextureHostOGL(aDesc, aDeallocator, aBackend, aFlags); + break; + } else { + result = CreateTextureHostBasic(aDesc, aDeallocator, aBackend, aFlags); + break; + } + +#ifdef MOZ_X11 + case SurfaceDescriptor::TSurfaceDescriptorX11: { + if (!aDeallocator->IsSameProcess()) { + NS_ERROR( + "A client process is trying to peek at our address space using a " + "X11Texture!"); + return nullptr; + } + + const SurfaceDescriptorX11& desc = aDesc.get_SurfaceDescriptorX11(); + result = MakeAndAddRef(aFlags, desc); + break; + } +#endif + +#ifdef XP_WIN + case SurfaceDescriptor::TSurfaceDescriptorD3D10: + case SurfaceDescriptor::TSurfaceDescriptorDXGIYCbCr: + result = CreateTextureHostD3D11(aDesc, aDeallocator, aBackend, aFlags); + break; +#endif + case SurfaceDescriptor::TSurfaceDescriptorRecorded: { + const SurfaceDescriptorRecorded& desc = + aDesc.get_SurfaceDescriptorRecorded(); + UniquePtr realDesc = + aDeallocator->AsCompositorBridgeParentBase() + ->LookupSurfaceDescriptorForClientTexture(desc.textureId()); + if (!realDesc) { + gfxCriticalNote << "Failed to get descriptor for recorded texture."; + // Create a dummy to prevent any crashes due to missing IPDL actors. + result = CreateDummyBufferTextureHost(aBackend, aFlags); + break; + } + + result = TextureHost::Create(*realDesc, aReadLock, aDeallocator, aBackend, + aFlags, aExternalImageId); + return result.forget(); + } + default: + MOZ_CRASH("GFX: Unsupported Surface type host"); + } + + if (!result) { + gfxCriticalNote << "TextureHost creation failure type=" << aDesc.type(); + } + + if (result && WrapWithWebRenderTextureHost(aDeallocator, aBackend, aFlags)) { + MOZ_ASSERT(aExternalImageId.isSome()); + result = + new WebRenderTextureHost(aDesc, aFlags, result, aExternalImageId.ref()); + } + + if (result) { + result->DeserializeReadLock(aReadLock, aDeallocator); + } + + return result.forget(); +} + +already_AddRefed CreateBackendIndependentTextureHost( + const SurfaceDescriptor& aDesc, ISurfaceAllocator* aDeallocator, + LayersBackend aBackend, TextureFlags aFlags) { + RefPtr result; + switch (aDesc.type()) { + case SurfaceDescriptor::TSurfaceDescriptorBuffer: { + const SurfaceDescriptorBuffer& bufferDesc = + aDesc.get_SurfaceDescriptorBuffer(); + const MemoryOrShmem& data = bufferDesc.data(); + switch (data.type()) { + case MemoryOrShmem::TShmem: { + const ipc::Shmem& shmem = data.get_Shmem(); + const BufferDescriptor& desc = bufferDesc.desc(); + if (!shmem.IsReadable()) { + // We failed to map the shmem so we can't verify its size. This + // should not be a fatal error, so just create the texture with + // nothing backing it. + result = new ShmemTextureHost(shmem, desc, aDeallocator, aFlags); + break; + } + + size_t bufSize = shmem.Size(); + size_t reqSize = SIZE_MAX; + switch (desc.type()) { + case BufferDescriptor::TYCbCrDescriptor: { + const YCbCrDescriptor& ycbcr = desc.get_YCbCrDescriptor(); + reqSize = ImageDataSerializer::ComputeYCbCrBufferSize( + ycbcr.ySize(), ycbcr.yStride(), ycbcr.cbCrSize(), + ycbcr.cbCrStride(), ycbcr.yOffset(), ycbcr.cbOffset(), + ycbcr.crOffset()); + break; + } + case BufferDescriptor::TRGBDescriptor: { + const RGBDescriptor& rgb = desc.get_RGBDescriptor(); + reqSize = ImageDataSerializer::ComputeRGBBufferSize(rgb.size(), + rgb.format()); + break; + } + default: + gfxCriticalError() + << "Bad buffer host descriptor " << (int)desc.type(); + MOZ_CRASH("GFX: Bad descriptor"); + } + + if (reqSize == 0 || bufSize < reqSize) { + NS_ERROR( + "A client process gave a shmem too small to fit for its " + "descriptor!"); + return nullptr; + } + + result = new ShmemTextureHost(shmem, desc, aDeallocator, aFlags); + break; + } + case MemoryOrShmem::Tuintptr_t: { + if (!aDeallocator->IsSameProcess()) { + NS_ERROR( + "A client process is trying to peek at our address space using " + "a MemoryTexture!"); + return nullptr; + } + + result = new MemoryTextureHost( + reinterpret_cast(data.get_uintptr_t()), + bufferDesc.desc(), aFlags); + break; + } + default: + gfxCriticalError() + << "Failed texture host for backend " << (int)data.type(); + MOZ_CRASH("GFX: No texture host for backend"); + } + break; + } + case SurfaceDescriptor::TSurfaceDescriptorGPUVideo: { + if (aDesc.get_SurfaceDescriptorGPUVideo().type() == + SurfaceDescriptorGPUVideo::TSurfaceDescriptorPlugin) { + MOZ_ASSERT(aDeallocator && aDeallocator->UsesImageBridge()); + auto ibpBase = static_cast(aDeallocator); + result = + ibpBase->LookupTextureHost(aDesc.get_SurfaceDescriptorGPUVideo()); + if (!result) { + return nullptr; + } + MOZ_ASSERT(aFlags == result->GetFlags()); + break; + } + + MOZ_ASSERT(aDesc.get_SurfaceDescriptorGPUVideo().type() == + SurfaceDescriptorGPUVideo::TSurfaceDescriptorRemoteDecoder); + result = GPUVideoTextureHost::CreateFromDescriptor( + aFlags, aDesc.get_SurfaceDescriptorGPUVideo()); + break; + } +#ifdef XP_WIN + case SurfaceDescriptor::TSurfaceDescriptorDIB: { + if (!aDeallocator->IsSameProcess()) { + NS_ERROR( + "A client process is trying to peek at our address space using a " + "DIBTexture!"); + return nullptr; + } + + result = new DIBTextureHost(aFlags, aDesc); + break; + } + case SurfaceDescriptor::TSurfaceDescriptorFileMapping: { + result = new TextureHostFileMapping(aFlags, aDesc); + break; + } +#endif + default: { + NS_WARNING("No backend independent TextureHost for this descriptor type"); + } + } + return result.forget(); +} + +TextureHost::TextureHost(TextureFlags aFlags) + : AtomicRefCountedWithFinalize("TextureHost"), + mActor(nullptr), + mFlags(aFlags), + mCompositableCount(0), + mFwdTransactionId(0), + mReadLocked(false) {} + +TextureHost::~TextureHost() { + if (mReadLocked) { + // If we still have a ReadLock, unlock it. At this point we don't care about + // the texture client being written into on the other side since it should + // be destroyed by now. But we will hit assertions if we don't ReadUnlock + // before destroying the lock itself. + ReadUnlock(); + MaybeNotifyUnlocked(); + } +} + +void TextureHost::Finalize() { + MaybeDestroyRenderTexture(); + + if (!(GetFlags() & TextureFlags::DEALLOCATE_CLIENT)) { + DeallocateSharedData(); + DeallocateDeviceData(); + } +} + +void TextureHost::UnbindTextureSource() { + if (mReadLocked) { + // This TextureHost is not used anymore. Since most compositor backends are + // working asynchronously under the hood a compositor could still be using + // this texture, so it is generally best to wait until the end of the next + // composition before calling ReadUnlock. We ask the compositor to take care + // of that for us. + if (mProvider) { + mProvider->UnlockAfterComposition(this); + } else { + // GetCompositor returned null which means no compositor can be using this + // texture. We can ReadUnlock right away. + ReadUnlock(); + MaybeNotifyUnlocked(); + } + } +} + +void TextureHost::RecycleTexture(TextureFlags aFlags) { + MOZ_ASSERT(GetFlags() & TextureFlags::RECYCLE); + MOZ_ASSERT(aFlags & TextureFlags::RECYCLE); + mFlags = aFlags; +} + +void TextureHost::NotifyNotUsed() { + if (!mActor) { + return; + } + + // Do not need to call NotifyNotUsed() if TextureHost does not have + // TextureFlags::RECYCLE flag nor TextureFlags::WAIT_HOST_USAGE_END flag. + if (!(GetFlags() & TextureFlags::RECYCLE) && + !(GetFlags() & TextureFlags::WAIT_HOST_USAGE_END)) { + return; + } + + // The following cases do not need to defer NotifyNotUsed until next + // Composite. + // - TextureHost does not have Compositor. + // - Compositor is BasicCompositor. + // - TextureHost has intermediate buffer. + // end of buffer usage. + if (!mProvider || HasIntermediateBuffer() || + !mProvider->NotifyNotUsedAfterComposition(this)) { + static_cast(mActor)->NotifyNotUsed(mFwdTransactionId); + return; + } +} + +void TextureHost::CallNotifyNotUsed() { + if (!mActor) { + return; + } + static_cast(mActor)->NotifyNotUsed(mFwdTransactionId); +} + +void TextureHost::MaybeDestroyRenderTexture() { + if (mExternalImageId.isNothing()) { + // RenderTextureHost was not created + return; + } + // When TextureHost created RenderTextureHost, delete it here. + TextureHost::DestroyRenderTexture(mExternalImageId.ref()); +} + +void TextureHost::DestroyRenderTexture( + const wr::ExternalImageId& aExternalImageId) { + wr::RenderThread::Get()->UnregisterExternalImage( + wr::AsUint64(aExternalImageId)); +} + +void TextureHost::EnsureRenderTexture( + const wr::MaybeExternalImageId& aExternalImageId) { + if (aExternalImageId.isNothing()) { + // TextureHost is wrapped by GPUVideoTextureHost. + if (mExternalImageId.isSome()) { + // RenderTextureHost was already created. + return; + } + mExternalImageId = + Some(AsyncImagePipelineManager::GetNextExternalImageId()); + } else { + // TextureHost is wrapped by WebRenderTextureHost. + if (aExternalImageId == mExternalImageId) { + // The texture has already been created. + return; + } + MOZ_ASSERT(mExternalImageId.isNothing()); + mExternalImageId = aExternalImageId; + } + CreateRenderTexture(mExternalImageId.ref()); +} + +void TextureHost::PrintInfo(std::stringstream& aStream, const char* aPrefix) { + aStream << aPrefix; + aStream << nsPrintfCString("%s (0x%p)", Name(), this).get(); + // Note: the TextureHost needs to be locked before it is safe to call + // GetSize() and GetFormat() on it. + if (Lock()) { + aStream << " [size=" << GetSize() << "]" + << " [format=" << GetFormat() << "]"; + Unlock(); + } + aStream << " [flags=" << mFlags << "]"; +#ifdef MOZ_DUMP_PAINTING + if (StaticPrefs::layers_dump_texture()) { + nsAutoCString pfx(aPrefix); + pfx += " "; + + aStream << "\n" << pfx.get() << "Surface: "; + RefPtr dSurf = GetAsSurface(); + if (dSurf) { + aStream << gfxUtils::GetAsLZ4Base64Str(dSurf).get(); + } + } +#endif +} + +void TextureHost::Updated(const nsIntRegion* aRegion) { + LayerScope::ContentChanged(this); + UpdatedInternal(aRegion); +} + +TextureSource::TextureSource() : mCompositableCount(0) {} + +TextureSource::~TextureSource() = default; +BufferTextureHost::BufferTextureHost(const BufferDescriptor& aDesc, + TextureFlags aFlags) + : TextureHost(aFlags), + mUpdateSerial(1), + mLocked(false), + mNeedsFullUpdate(false) { + mDescriptor = aDesc; + switch (mDescriptor.type()) { + case BufferDescriptor::TYCbCrDescriptor: { + const YCbCrDescriptor& ycbcr = mDescriptor.get_YCbCrDescriptor(); + mSize = ycbcr.display().Size(); + mFormat = gfx::SurfaceFormat::YUV; + mHasIntermediateBuffer = ycbcr.hasIntermediateBuffer(); + break; + } + case BufferDescriptor::TRGBDescriptor: { + const RGBDescriptor& rgb = mDescriptor.get_RGBDescriptor(); + mSize = rgb.size(); + mFormat = rgb.format(); + mHasIntermediateBuffer = rgb.hasIntermediateBuffer(); + break; + } + default: + gfxCriticalError() << "Bad buffer host descriptor " + << (int)mDescriptor.type(); + MOZ_CRASH("GFX: Bad descriptor"); + } + if (aFlags & TextureFlags::COMPONENT_ALPHA) { + // One texture of a component alpha texture pair will start out all white. + // This hack allows us to easily make sure that white will be uploaded. + // See bug 1138934 + mNeedsFullUpdate = true; + } + +#ifdef XP_MACOSX + const int kMinSize = 1024; + const int kMaxSize = 4096; + mUseExternalTextures = + kMaxSize >= mSize.width && mSize.width >= kMinSize && + kMaxSize >= mSize.height && mSize.height >= kMinSize && + StaticPrefs::gfx_webrender_enable_client_storage_AtStartup(); +#else + mUseExternalTextures = false; +#endif +} + +BufferTextureHost::~BufferTextureHost() = default; + +void BufferTextureHost::UpdatedInternal(const nsIntRegion* aRegion) { + ++mUpdateSerial; + // If the last frame wasn't uploaded yet, and we -don't- have a partial + // update, we still need to update the full surface. + if (aRegion && !mNeedsFullUpdate) { + mMaybeUpdatedRegion.OrWith(*aRegion); + } else { + mNeedsFullUpdate = true; + } + if (GetFlags() & TextureFlags::IMMEDIATE_UPLOAD) { + DebugOnly result = + MaybeUpload(!mNeedsFullUpdate ? &mMaybeUpdatedRegion : nullptr); + NS_WARNING_ASSERTION(result, "Failed to upload a texture"); + } +} + +void BufferTextureHost::SetTextureSourceProvider( + TextureSourceProvider* aProvider) { + if (mProvider == aProvider) { + return; + } + if (mFirstSource && mFirstSource->IsOwnedBy(this)) { + mFirstSource->SetOwner(nullptr); + } + if (mFirstSource) { + mFirstSource = nullptr; + mNeedsFullUpdate = true; + } + mProvider = aProvider; +} + +void BufferTextureHost::DeallocateDeviceData() { + if (mFirstSource && mFirstSource->NumCompositableRefs() > 0) { + // WrappingTextureSourceYCbCrBasic wraps YUV format BufferTextureHost. + // When BufferTextureHost is destroyed, data of + // WrappingTextureSourceYCbCrBasic becomes invalid. + if (mFirstSource->AsWrappingTextureSourceYCbCrBasic() && + mFirstSource->IsOwnedBy(this)) { + mFirstSource->SetOwner(nullptr); + mFirstSource->DeallocateDeviceData(); + } + return; + } + + if (!mFirstSource || !mFirstSource->IsOwnedBy(this)) { + mFirstSource = nullptr; + return; + } + + mFirstSource->SetOwner(nullptr); + + RefPtr it = mFirstSource; + while (it) { + it->DeallocateDeviceData(); + it = it->GetNextSibling(); + } +} + +bool BufferTextureHost::Lock() { + MOZ_ASSERT(!mLocked); + if (!UploadIfNeeded()) { + return false; + } + mLocked = !!mFirstSource; + return mLocked; +} + +void BufferTextureHost::Unlock() { + MOZ_ASSERT(mLocked); + mLocked = false; +} + +void BufferTextureHost::CreateRenderTexture( + const wr::ExternalImageId& aExternalImageId) { + RefPtr texture; + + if (gfx::gfxVars::UseSoftwareWebRender()) { + texture = + new wr::RenderBufferTextureHostSWGL(GetBuffer(), GetBufferDescriptor()); + } else if (UseExternalTextures()) { + texture = + new wr::RenderExternalTextureHost(GetBuffer(), GetBufferDescriptor()); + } else { + texture = + new wr::RenderBufferTextureHost(GetBuffer(), GetBufferDescriptor()); + } + + wr::RenderThread::Get()->RegisterExternalImage(wr::AsUint64(aExternalImageId), + texture.forget()); +} + +uint32_t BufferTextureHost::NumSubTextures() { + if (GetFormat() == gfx::SurfaceFormat::YUV) { + return 3; + } + + return 1; +} + +void BufferTextureHost::PushResourceUpdates( + wr::TransactionBuilder& aResources, ResourceUpdateOp aOp, + const Range& aImageKeys, const wr::ExternalImageId& aExtID) { + auto method = aOp == TextureHost::ADD_IMAGE + ? &wr::TransactionBuilder::AddExternalImage + : &wr::TransactionBuilder::UpdateExternalImage; + + auto imageType = UseExternalTextures() || gfx::gfxVars::UseSoftwareWebRender() + ? wr::ExternalImageType::TextureHandle( + wr::ImageBufferKind::TextureRect) + : wr::ExternalImageType::Buffer(); + + if (GetFormat() != gfx::SurfaceFormat::YUV) { + MOZ_ASSERT(aImageKeys.length() == 1); + + wr::ImageDescriptor descriptor( + GetSize(), + ImageDataSerializer::ComputeRGBStride(GetFormat(), GetSize().width), + GetFormat()); + (aResources.*method)(aImageKeys[0], descriptor, aExtID, imageType, 0); + } else { + MOZ_ASSERT(aImageKeys.length() == 3); + + const layers::YCbCrDescriptor& desc = mDescriptor.get_YCbCrDescriptor(); + wr::ImageDescriptor yDescriptor( + desc.ySize(), desc.yStride(), + SurfaceFormatForColorDepth(desc.colorDepth())); + wr::ImageDescriptor cbcrDescriptor( + desc.cbCrSize(), desc.cbCrStride(), + SurfaceFormatForColorDepth(desc.colorDepth())); + (aResources.*method)(aImageKeys[0], yDescriptor, aExtID, imageType, 0); + (aResources.*method)(aImageKeys[1], cbcrDescriptor, aExtID, imageType, 1); + (aResources.*method)(aImageKeys[2], cbcrDescriptor, aExtID, imageType, 2); + } +} + +void BufferTextureHost::PushDisplayItems(wr::DisplayListBuilder& aBuilder, + const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, + wr::ImageRendering aFilter, + const Range& aImageKeys, + PushDisplayItemFlagSet aFlags) { + // SWGL should always try to bypass shaders and composite directly. + bool preferCompositorSurface = + aFlags.contains(PushDisplayItemFlag::PREFER_COMPOSITOR_SURFACE); + bool useExternalSurface = + aFlags.contains(PushDisplayItemFlag::SUPPORTS_EXTERNAL_BUFFER_TEXTURES); + if (GetFormat() != gfx::SurfaceFormat::YUV) { + MOZ_ASSERT(aImageKeys.length() == 1); + aBuilder.PushImage(aBounds, aClip, true, aFilter, aImageKeys[0], + !(mFlags & TextureFlags::NON_PREMULTIPLIED), + wr::ColorF{1.0f, 1.0f, 1.0f, 1.0f}, + preferCompositorSurface, useExternalSurface); + } else { + MOZ_ASSERT(aImageKeys.length() == 3); + const YCbCrDescriptor& desc = mDescriptor.get_YCbCrDescriptor(); + aBuilder.PushYCbCrPlanarImage( + aBounds, aClip, true, aImageKeys[0], aImageKeys[1], aImageKeys[2], + wr::ToWrColorDepth(desc.colorDepth()), + wr::ToWrYuvColorSpace(desc.yUVColorSpace()), + wr::ToWrColorRange(desc.colorRange()), aFilter, preferCompositorSurface, + useExternalSurface); + } +} + +void TextureHost::DeserializeReadLock(const ReadLockDescriptor& aDesc, + ISurfaceAllocator* aAllocator) { + if (mReadLock) { + return; + } + + mReadLock = TextureReadLock::Deserialize(aDesc, aAllocator); +} + +void TextureHost::SetReadLocked() { + if (!mReadLock) { + return; + } + // If mReadLocked is true it means we haven't read unlocked yet and the + // content side should not have been able to write into this texture and read + // lock again! + MOZ_ASSERT(!mReadLocked); + mReadLocked = true; + if (mProvider) { + mProvider->MaybeUnlockBeforeNextComposition(this); + } +} + +void TextureHost::ReadUnlock() { + if (mReadLock && mReadLocked) { + mReadLock->ReadUnlock(); + mReadLocked = false; + } +} + +bool TextureHost::NeedsYFlip() const { + return bool(mFlags & TextureFlags::ORIGIN_BOTTOM_LEFT); +} + +bool BufferTextureHost::EnsureWrappingTextureSource() { + MOZ_ASSERT(!mHasIntermediateBuffer); + + if (mFirstSource && mFirstSource->IsOwnedBy(this)) { + return true; + } + // We don't own it, apparently. + if (mFirstSource) { + mNeedsFullUpdate = true; + mFirstSource = nullptr; + } + + if (!mProvider) { + return false; + } + + if (mFormat == gfx::SurfaceFormat::YUV) { + mFirstSource = mProvider->CreateDataTextureSourceAroundYCbCr(this); + } else { + uint8_t* data = GetBuffer(); + if (!data) { + return false; + } + RefPtr surf = + gfx::Factory::CreateWrappingDataSourceSurface( + data, ImageDataSerializer::ComputeRGBStride(mFormat, mSize.width), + mSize, mFormat); + if (!surf) { + return false; + } + mFirstSource = mProvider->CreateDataTextureSourceAround(surf); + } + + if (!mFirstSource) { + // BasicCompositor::CreateDataTextureSourceAround never returns null + // and we don't expect to take this branch if we are using another backend. + // Returning false is fine but if we get into this situation it probably + // means something fishy is going on, like a texture being used with + // several compositor backends. + NS_WARNING("Failed to use a BufferTextureHost without intermediate buffer"); + return false; + } + + mFirstSource->SetUpdateSerial(mUpdateSerial); + mFirstSource->SetOwner(this); + + return true; +} + +static bool IsCompatibleTextureSource(TextureSource* aTexture, + const BufferDescriptor& aDescriptor, + TextureSourceProvider* aProvider) { + if (!aProvider) { + return false; + } + + switch (aDescriptor.type()) { + case BufferDescriptor::TYCbCrDescriptor: { + const YCbCrDescriptor& ycbcr = aDescriptor.get_YCbCrDescriptor(); + + if (!aProvider->SupportsEffect(EffectTypes::YCBCR)) { + return aTexture->GetFormat() == gfx::SurfaceFormat::B8G8R8X8 && + aTexture->GetSize() == ycbcr.ySize(); + } + + if (aTexture->GetFormat() != gfx::SurfaceFormat::A8 || + aTexture->GetSize() != ycbcr.ySize()) { + return false; + } + + auto cbTexture = aTexture->GetSubSource(1); + if (!cbTexture || cbTexture->GetFormat() != gfx::SurfaceFormat::A8 || + cbTexture->GetSize() != ycbcr.cbCrSize()) { + return false; + } + + auto crTexture = aTexture->GetSubSource(2); + if (!crTexture || crTexture->GetFormat() != gfx::SurfaceFormat::A8 || + crTexture->GetSize() != ycbcr.cbCrSize()) { + return false; + } + + return true; + } + case BufferDescriptor::TRGBDescriptor: { + const RGBDescriptor& rgb = aDescriptor.get_RGBDescriptor(); + return aTexture->GetFormat() == rgb.format() && + aTexture->GetSize() == rgb.size(); + } + default: { + return false; + } + } +} + +void BufferTextureHost::PrepareTextureSource( + CompositableTextureSourceRef& aTexture) { + // Reuse WrappingTextureSourceYCbCrBasic to reduce memory consumption. + if (mFormat == gfx::SurfaceFormat::YUV && !mHasIntermediateBuffer && + aTexture.get() && aTexture->AsWrappingTextureSourceYCbCrBasic() && + aTexture->NumCompositableRefs() <= 1 && + aTexture->GetSize() == GetSize()) { + aTexture->AsSourceBasic()->SetBufferTextureHost(this); + aTexture->AsDataTextureSource()->SetOwner(this); + mFirstSource = aTexture->AsDataTextureSource(); + mNeedsFullUpdate = true; + } + + if (!mHasIntermediateBuffer) { + EnsureWrappingTextureSource(); + } + + if (mFirstSource && mFirstSource->IsOwnedBy(this)) { + // We are already attached to a TextureSource, nothing to do except tell + // the compositable to use it. + aTexture = mFirstSource.get(); + return; + } + + // We don't own it, apparently. + if (mFirstSource) { + mNeedsFullUpdate = true; + mFirstSource = nullptr; + } + + DataTextureSource* texture = + aTexture.get() ? aTexture->AsDataTextureSource() : nullptr; + + bool compatibleFormats = + texture && IsCompatibleTextureSource(texture, mDescriptor, mProvider); + + bool shouldCreateTexture = !compatibleFormats || + texture->NumCompositableRefs() > 1 || + texture->HasOwner(); + + if (!shouldCreateTexture) { + mFirstSource = texture; + mFirstSource->SetOwner(this); + mNeedsFullUpdate = true; + + // It's possible that texture belonged to a different compositor, + // so make sure we update it (and all of its siblings) to the + // current one. + RefPtr it = mFirstSource; + while (it) { + it->SetTextureSourceProvider(mProvider); + it = it->GetNextSibling(); + } + } +} + +bool BufferTextureHost::BindTextureSource( + CompositableTextureSourceRef& aTexture) { + MOZ_ASSERT(mLocked); + MOZ_ASSERT(mFirstSource); + aTexture = mFirstSource; + return !!aTexture; +} + +bool BufferTextureHost::AcquireTextureSource( + CompositableTextureSourceRef& aTexture) { + if (!UploadIfNeeded()) { + return false; + } + aTexture = mFirstSource; + return !!mFirstSource; +} + +void BufferTextureHost::ReadUnlock() { + if (mFirstSource) { + mFirstSource->Sync(true); + } + + TextureHost::ReadUnlock(); +} + +void BufferTextureHost::MaybeNotifyUnlocked() { +#ifdef XP_DARWIN + auto actor = GetIPDLActor(); + if (actor) { + AutoTArray serials; + serials.AppendElement(TextureHost::GetTextureSerial(actor)); + TextureSync::SetTexturesUnlocked(actor->OtherPid(), serials); + } +#endif +} + +void BufferTextureHost::UnbindTextureSource() { + if (mFirstSource && mFirstSource->IsOwnedBy(this)) { + mFirstSource->Unbind(); + } + + // This texture is not used by any layer anymore. + // If the texture doesn't have an intermediate buffer, it means we are + // compositing synchronously on the CPU, so we don't need to wait until + // the end of the next composition to ReadUnlock (which other textures do + // by default). + // If the texture has an intermediate buffer we don't care either because + // texture uploads are also performed synchronously for BufferTextureHost. + ReadUnlock(); + MaybeNotifyUnlocked(); +} + +gfx::SurfaceFormat BufferTextureHost::GetFormat() const { + // mFormat is the format of the data that we share with the content process. + // GetFormat, on the other hand, expects the format that we present to the + // Compositor (it is used to choose the effect type). + // if the compositor does not support YCbCr effects, we give it a RGBX texture + // instead (see BufferTextureHost::Upload) + if (mFormat == gfx::SurfaceFormat::YUV && mProvider && + !mProvider->SupportsEffect(EffectTypes::YCBCR)) { + return gfx::SurfaceFormat::R8G8B8X8; + } + return mFormat; +} + +gfx::YUVColorSpace BufferTextureHost::GetYUVColorSpace() const { + if (mFormat == gfx::SurfaceFormat::YUV) { + const YCbCrDescriptor& desc = mDescriptor.get_YCbCrDescriptor(); + return desc.yUVColorSpace(); + } + return gfx::YUVColorSpace::UNKNOWN; +} + +gfx::ColorDepth BufferTextureHost::GetColorDepth() const { + if (mFormat == gfx::SurfaceFormat::YUV) { + const YCbCrDescriptor& desc = mDescriptor.get_YCbCrDescriptor(); + return desc.colorDepth(); + } + return gfx::ColorDepth::COLOR_8; +} + +gfx::ColorRange BufferTextureHost::GetColorRange() const { + if (mFormat == gfx::SurfaceFormat::YUV) { + const YCbCrDescriptor& desc = mDescriptor.get_YCbCrDescriptor(); + return desc.colorRange(); + } + return TextureHost::GetColorRange(); +} + +bool BufferTextureHost::UploadIfNeeded() { + return MaybeUpload(!mNeedsFullUpdate ? &mMaybeUpdatedRegion : nullptr); +} + +bool BufferTextureHost::MaybeUpload(nsIntRegion* aRegion) { + auto serial = mFirstSource ? mFirstSource->GetUpdateSerial() : 0; + + if (serial == mUpdateSerial) { + return true; + } + + if (serial == 0) { + // 0 means the source has no valid content + aRegion = nullptr; + } + + if (!Upload(aRegion)) { + return false; + } + + if (mHasIntermediateBuffer) { + // We just did the texture upload, the content side can now freely write + // into the shared buffer. + ReadUnlock(); + MaybeNotifyUnlocked(); + } + + // We no longer have an invalid region. + mNeedsFullUpdate = false; + mMaybeUpdatedRegion.SetEmpty(); + + // If upload returns true we know mFirstSource is not null + mFirstSource->SetUpdateSerial(mUpdateSerial); + return true; +} + +bool BufferTextureHost::Upload(nsIntRegion* aRegion) { + uint8_t* buf = GetBuffer(); + if (!buf) { + // We don't have a buffer; a possible cause is that the IPDL actor + // is already dead. This inevitably happens as IPDL actors can die + // at any time, so we want to silently return in this case. + // another possible cause is that IPDL failed to map the shmem when + // deserializing it. + return false; + } + if (!mProvider) { + // This can happen if we send textures to a compositable that isn't yet + // attached to a layer. + return false; + } + if (!mHasIntermediateBuffer && EnsureWrappingTextureSource()) { + if (!mFirstSource || !mFirstSource->IsDirectMap()) { + return true; + } + } + + if (mFormat == gfx::SurfaceFormat::UNKNOWN) { + NS_WARNING("BufferTextureHost: unsupported format!"); + return false; + } else if (mFormat == gfx::SurfaceFormat::YUV) { + const YCbCrDescriptor& desc = mDescriptor.get_YCbCrDescriptor(); + + if (!mProvider->SupportsEffect(EffectTypes::YCBCR)) { + RefPtr surf = + ImageDataSerializer::DataSourceSurfaceFromYCbCrDescriptor( + buf, mDescriptor.get_YCbCrDescriptor()); + if (NS_WARN_IF(!surf)) { + return false; + } + if (!mFirstSource) { + mFirstSource = mProvider->CreateDataTextureSource( + mFlags | TextureFlags::RGB_FROM_YCBCR); + mFirstSource->SetOwner(this); + } + return mFirstSource->Update(surf, aRegion); + } + + RefPtr srcY; + RefPtr srcU; + RefPtr srcV; + if (!mFirstSource) { + // We don't support BigImages for YCbCr compositing. + srcY = mProvider->CreateDataTextureSource( + mFlags | TextureFlags::DISALLOW_BIGIMAGE); + srcU = mProvider->CreateDataTextureSource( + mFlags | TextureFlags::DISALLOW_BIGIMAGE); + srcV = mProvider->CreateDataTextureSource( + mFlags | TextureFlags::DISALLOW_BIGIMAGE); + mFirstSource = srcY; + mFirstSource->SetOwner(this); + srcY->SetNextSibling(srcU); + srcU->SetNextSibling(srcV); + } else { + // mFormat never changes so if this was created as a YCbCr host and + // already contains a source it should already have 3 sources. + // BufferTextureHost only uses DataTextureSources so it is safe to assume + // all 3 sources are DataTextureSource. + MOZ_ASSERT(mFirstSource->GetNextSibling()); + MOZ_ASSERT(mFirstSource->GetNextSibling()->GetNextSibling()); + srcY = mFirstSource; + srcU = mFirstSource->GetNextSibling()->AsDataTextureSource(); + srcV = mFirstSource->GetNextSibling() + ->GetNextSibling() + ->AsDataTextureSource(); + } + + RefPtr tempY = + gfx::Factory::CreateWrappingDataSourceSurface( + ImageDataSerializer::GetYChannel(buf, desc), desc.yStride(), + desc.ySize(), SurfaceFormatForColorDepth(desc.colorDepth())); + RefPtr tempCb = + gfx::Factory::CreateWrappingDataSourceSurface( + ImageDataSerializer::GetCbChannel(buf, desc), desc.cbCrStride(), + desc.cbCrSize(), SurfaceFormatForColorDepth(desc.colorDepth())); + RefPtr tempCr = + gfx::Factory::CreateWrappingDataSourceSurface( + ImageDataSerializer::GetCrChannel(buf, desc), desc.cbCrStride(), + desc.cbCrSize(), SurfaceFormatForColorDepth(desc.colorDepth())); + // We don't support partial updates for Y U V textures + NS_ASSERTION(!aRegion, "Unsupported partial updates for YCbCr textures"); + if (!tempY || !tempCb || !tempCr || !srcY->Update(tempY) || + !srcU->Update(tempCb) || !srcV->Update(tempCr)) { + NS_WARNING("failed to update the DataTextureSource"); + return false; + } + } else { + // non-YCbCr case + nsIntRegion* regionToUpdate = aRegion; + if (!mFirstSource) { + mFirstSource = mProvider->CreateDataTextureSource(mFlags); + mFirstSource->SetOwner(this); + if (mFlags & TextureFlags::COMPONENT_ALPHA) { + // Update the full region the first time for component alpha textures. + regionToUpdate = nullptr; + } + } + + RefPtr surf = + gfx::Factory::CreateWrappingDataSourceSurface( + GetBuffer(), + ImageDataSerializer::ComputeRGBStride(mFormat, mSize.width), mSize, + mFormat); + if (!surf) { + return false; + } + + if (!mFirstSource->Update(surf.get(), regionToUpdate)) { + NS_WARNING("failed to update the DataTextureSource"); + return false; + } + } + MOZ_ASSERT(mFirstSource); + return true; +} + +already_AddRefed BufferTextureHost::GetAsSurface() { + RefPtr result; + if (mFormat == gfx::SurfaceFormat::UNKNOWN) { + NS_WARNING("BufferTextureHost: unsupported format!"); + return nullptr; + } else if (mFormat == gfx::SurfaceFormat::YUV) { + result = ImageDataSerializer::DataSourceSurfaceFromYCbCrDescriptor( + GetBuffer(), mDescriptor.get_YCbCrDescriptor()); + if (NS_WARN_IF(!result)) { + return nullptr; + } + } else { + result = gfx::Factory::CreateWrappingDataSourceSurface( + GetBuffer(), + ImageDataSerializer::GetRGBStride(mDescriptor.get_RGBDescriptor()), + mSize, mFormat); + } + return result.forget(); +} + +ShmemTextureHost::ShmemTextureHost(const ipc::Shmem& aShmem, + const BufferDescriptor& aDesc, + ISurfaceAllocator* aDeallocator, + TextureFlags aFlags) + : BufferTextureHost(aDesc, aFlags), mDeallocator(aDeallocator) { + if (aShmem.IsReadable()) { + mShmem = MakeUnique(aShmem); + } else { + // This can happen if we failed to map the shmem on this process, perhaps + // because it was big and we didn't have enough contiguous address space + // available, even though we did on the child process. + // As a result this texture will be in an invalid state and Lock will + // always fail. + + gfxCriticalNote << "Failed to create a valid ShmemTextureHost"; + } + + MOZ_COUNT_CTOR(ShmemTextureHost); +} + +ShmemTextureHost::~ShmemTextureHost() { + MOZ_ASSERT(!mShmem || (mFlags & TextureFlags::DEALLOCATE_CLIENT), + "Leaking our buffer"); + DeallocateDeviceData(); + MOZ_COUNT_DTOR(ShmemTextureHost); +} + +void ShmemTextureHost::DeallocateSharedData() { + if (mShmem) { + MOZ_ASSERT(mDeallocator, + "Shared memory would leak without a ISurfaceAllocator"); + mDeallocator->AsShmemAllocator()->DeallocShmem(*mShmem); + mShmem = nullptr; + } +} + +void ShmemTextureHost::ForgetSharedData() { + if (mShmem) { + mShmem = nullptr; + } +} + +void ShmemTextureHost::OnShutdown() { mShmem = nullptr; } + +uint8_t* ShmemTextureHost::GetBuffer() { + return mShmem ? mShmem->get() : nullptr; +} + +size_t ShmemTextureHost::GetBufferSize() { + return mShmem ? mShmem->Size() : 0; +} + +MemoryTextureHost::MemoryTextureHost(uint8_t* aBuffer, + const BufferDescriptor& aDesc, + TextureFlags aFlags) + : BufferTextureHost(aDesc, aFlags), mBuffer(aBuffer) { + MOZ_COUNT_CTOR(MemoryTextureHost); +} + +MemoryTextureHost::~MemoryTextureHost() { + MOZ_ASSERT(!mBuffer || (mFlags & TextureFlags::DEALLOCATE_CLIENT), + "Leaking our buffer"); + DeallocateDeviceData(); + MOZ_COUNT_DTOR(MemoryTextureHost); +} + +void MemoryTextureHost::DeallocateSharedData() { + if (mBuffer) { + GfxMemoryImageReporter::WillFree(mBuffer); + } + delete[] mBuffer; + mBuffer = nullptr; +} + +void MemoryTextureHost::ForgetSharedData() { mBuffer = nullptr; } + +uint8_t* MemoryTextureHost::GetBuffer() { return mBuffer; } + +size_t MemoryTextureHost::GetBufferSize() { + // MemoryTextureHost just trusts that the buffer size is large enough to read + // anything we need to. That's because MemoryTextureHost has to trust the + // buffer pointer anyway, so the security model here is just that + // MemoryTexture's are restricted to same-process clients. + return std::numeric_limits::max(); +} + +TextureParent::TextureParent(HostIPCAllocator* aSurfaceAllocator, + uint64_t aSerial, + const wr::MaybeExternalImageId& aExternalImageId) + : mSurfaceAllocator(aSurfaceAllocator), + mSerial(aSerial), + mExternalImageId(aExternalImageId) { + MOZ_COUNT_CTOR(TextureParent); +} + +TextureParent::~TextureParent() { MOZ_COUNT_DTOR(TextureParent); } + +void TextureParent::NotifyNotUsed(uint64_t aTransactionId) { + if (!mTextureHost) { + return; + } + mSurfaceAllocator->NotifyNotUsed(this, aTransactionId); +} + +bool TextureParent::Init(const SurfaceDescriptor& aSharedData, + const ReadLockDescriptor& aReadLock, + const LayersBackend& aBackend, + const TextureFlags& aFlags) { + mTextureHost = TextureHost::Create(aSharedData, aReadLock, mSurfaceAllocator, + aBackend, aFlags, mExternalImageId); + if (mTextureHost) { + mTextureHost->mActor = this; + } + + return !!mTextureHost; +} + +void TextureParent::Destroy() { + if (!mTextureHost) { + return; + } + + if (mTextureHost->mReadLocked) { + // ReadUnlock here to make sure the ReadLock's shmem does not outlive the + // protocol that created it. + mTextureHost->ReadUnlock(); + mTextureHost->MaybeNotifyUnlocked(); + } + + if (mTextureHost->GetFlags() & TextureFlags::DEALLOCATE_CLIENT) { + mTextureHost->ForgetSharedData(); + } + + mTextureHost->mActor = nullptr; + mTextureHost = nullptr; +} + +void TextureHost::ReceivedDestroy(PTextureParent* aActor) { + static_cast(aActor)->RecvDestroy(); +} + +mozilla::ipc::IPCResult TextureParent::RecvRecycleTexture( + const TextureFlags& aTextureFlags) { + if (!mTextureHost) { + return IPC_OK(); + } + mTextureHost->RecycleTexture(aTextureFlags); + return IPC_OK(); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/TextureHost.h b/gfx/layers/composite/TextureHost.h new file mode 100644 index 0000000000..aafbde1e0d --- /dev/null +++ b/gfx/layers/composite/TextureHost.h @@ -0,0 +1,1082 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_TEXTUREHOST_H +#define MOZILLA_GFX_TEXTUREHOST_H + +#include // for size_t +#include // for uint64_t, uint32_t, uint8_t +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/Attributes.h" // for override +#include "mozilla/RefPtr.h" // for RefPtr, already_AddRefed, etc +#include "mozilla/gfx/Logging.h" +#include "mozilla/gfx/Matrix.h" +#include "mozilla/gfx/Point.h" // for IntSize, IntPoint +#include "mozilla/gfx/Rect.h" +#include "mozilla/gfx/Types.h" // for SurfaceFormat, etc +#include "mozilla/ipc/FileDescriptor.h" +#include "mozilla/layers/CompositorTypes.h" // for TextureFlags, etc +#include "mozilla/layers/LayersTypes.h" // for LayerRenderState, etc +#include "mozilla/layers/LayersSurfaces.h" +#include "mozilla/mozalloc.h" // for operator delete +#include "mozilla/Range.h" +#include "mozilla/UniquePtr.h" // for UniquePtr +#include "mozilla/webrender/WebRenderTypes.h" +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsDebug.h" // for NS_WARNING +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "nsRect.h" +#include "nsRegion.h" // for nsIntRegion +#include "nsTraceRefcnt.h" // for MOZ_COUNT_CTOR, etc +#include "nscore.h" // for nsACString +#include "mozilla/layers/AtomicRefCountedWithFinalize.h" + +class MacIOSurface; +namespace mozilla { +namespace gfx { +class DataSourceSurface; +} + +namespace ipc { +class Shmem; +} // namespace ipc + +namespace wr { +class DisplayListBuilder; +class TransactionBuilder; +} // namespace wr + +namespace layers { + +class AndroidHardwareBuffer; +class AndroidHardwareBufferTextureHost; +class BufferDescriptor; +class BufferTextureHost; +class Compositor; +class CompositableParentManager; +class ReadLockDescriptor; +class CompositorBridgeParent; +class SurfaceDescriptor; +class HostIPCAllocator; +class ISurfaceAllocator; +class MacIOSurfaceTextureHostOGL; +class SurfaceTextureHost; +class TextureHostOGL; +class TextureReadLock; +class TextureSourceOGL; +class TextureSourceD3D11; +class TextureSourceBasic; +class TextureSourceProvider; +class DataTextureSource; +class PTextureParent; +class TextureParent; +class WebRenderTextureHost; +class WrappingTextureSourceYCbCrBasic; + +/** + * A view on a TextureHost where the texture is internally represented as tiles + * (contrast with a tiled buffer, where each texture is a tile). For iteration + * by the texture's buffer host. This is only useful when the underlying surface + * is too big to fit in one device texture, which forces us to split it in + * smaller parts. Tiled Compositable is a different thing. + */ +class BigImageIterator { + public: + virtual void BeginBigImageIteration() = 0; + virtual void EndBigImageIteration(){}; + virtual gfx::IntRect GetTileRect() = 0; + virtual size_t GetTileCount() = 0; + virtual bool NextTile() = 0; +}; + +/** + * TextureSource is the interface for texture objects that can be composited + * by a given compositor backend. Since the drawing APIs are different + * between backends, the TextureSource interface is split into different + * interfaces (TextureSourceOGL, etc.), and TextureSource mostly provide + * access to these interfaces. + * + * This class is used on the compositor side. + */ +class TextureSource : public RefCounted { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(TextureSource) + + TextureSource(); + + virtual ~TextureSource(); + + virtual const char* Name() const = 0; + + /** + * Should be overridden in order to deallocate the data that is associated + * with the rendering backend, such as GL textures. + */ + virtual void DeallocateDeviceData() {} + + /** + * Return the size of the texture in texels. + * If this is a tile iterator, GetSize must return the size of the current + * tile. + */ + virtual gfx::IntSize GetSize() const = 0; + + /** + * Return the pixel format of this texture + */ + virtual gfx::SurfaceFormat GetFormat() const { + return gfx::SurfaceFormat::UNKNOWN; + } + + /** + * Cast to a TextureSource for for each backend.. + */ + virtual TextureSourceOGL* AsSourceOGL() { + gfxCriticalNote << "Failed to cast " << Name() + << " into a TextureSourceOGL"; + return nullptr; + } + virtual TextureSourceD3D11* AsSourceD3D11() { return nullptr; } + virtual TextureSourceBasic* AsSourceBasic() { return nullptr; } + /** + * Cast to a DataTextureSurce. + */ + virtual DataTextureSource* AsDataTextureSource() { return nullptr; } + virtual WrappingTextureSourceYCbCrBasic* AsWrappingTextureSourceYCbCrBasic() { + return nullptr; + } + + /** + * Overload this if the TextureSource supports big textures that don't fit in + * one device texture and must be tiled internally. + */ + virtual BigImageIterator* AsBigImageIterator() { return nullptr; } + + virtual void SetTextureSourceProvider(TextureSourceProvider* aProvider) {} + + virtual void Unbind() {} + + void SetNextSibling(TextureSource* aTexture) { mNextSibling = aTexture; } + + TextureSource* GetNextSibling() const { return mNextSibling; } + + /** + * In some rare cases we currently need to consider a group of textures as one + * TextureSource, that can be split in sub-TextureSources. + */ + TextureSource* GetSubSource(int index) { + switch (index) { + case 0: + return this; + case 1: + return GetNextSibling(); + case 2: + return GetNextSibling() ? GetNextSibling()->GetNextSibling() : nullptr; + } + return nullptr; + } + + void AddCompositableRef() { ++mCompositableCount; } + + void ReleaseCompositableRef() { + --mCompositableCount; + MOZ_ASSERT(mCompositableCount >= 0); + } + + // When iterating as a BigImage, this creates temporary TextureSources + // wrapping individual tiles. + virtual RefPtr ExtractCurrentTile() { + NS_WARNING("Implementation does not expose tile sources"); + return nullptr; + } + + int NumCompositableRefs() const { return mCompositableCount; } + + // Some texture sources could wrap the cpu buffer to gpu directly. Then, + // we could get better performance of texture uploading. + virtual bool IsDirectMap() { return false; } + // The direct-map cpu buffer should be alive when gpu uses it. And it + // should not be updated while gpu reads it. This Sync() function + // implements this synchronized behavior by allowing us to check if + // the GPU is done with the texture, and block on it if aBlocking is + // true. + virtual bool Sync(bool aBlocking) { return true; } + + protected: + RefPtr mNextSibling; + int mCompositableCount; +}; + +/// Equivalent of a RefPtr, that calls AddCompositableRef and +/// ReleaseCompositableRef in addition to the usual AddRef and Release. +/// +/// The semantoics of these CompositableTextureRefs are important because they +/// are used both as a synchronization/safety mechanism, and as an optimization +/// mechanism. They are also tricky and subtle because we use them in a very +/// implicit way (assigning to a CompositableTextureRef is less visible than +/// explicitly calling a method or whatnot). +/// It is Therefore important to be careful about the way we use this tool. +/// +/// CompositableTextureRef is a mechanism that lets us count how many +/// compositables are using a given texture (for TextureSource and TextureHost). +/// We use it to run specific code when a texture is not used anymore, and also +/// we trigger fast paths on some operations when we can see that the texture's +/// CompositableTextureRef counter is equal to 1 (the texture is not shared +/// between compositables). +/// This means that it is important to observe the following rules: +/// * CompositableHosts that receive UseTexture and similar messages *must* +/// store all of the TextureHosts they receive in CompositableTextureRef slots +/// for as long as they may be using them. +/// * CompositableHosts must store each texture in a *single* +/// CompositableTextureRef slot to ensure that the counter properly reflects how +/// many compositables are using the texture. If a compositable needs to hold +/// two references to a given texture (for example to have a pointer to the +/// current texture in a list of textures that may be used), it can hold its +/// extra references with RefPtr or whichever pointer type makes sense. +template +class CompositableTextureRef { + public: + CompositableTextureRef() = default; + + explicit CompositableTextureRef(const CompositableTextureRef& aOther) { + *this = aOther; + } + + explicit CompositableTextureRef(T* aOther) { *this = aOther; } + + ~CompositableTextureRef() { + if (mRef) { + mRef->ReleaseCompositableRef(); + } + } + + CompositableTextureRef& operator=(const CompositableTextureRef& aOther) { + if (aOther.get()) { + aOther->AddCompositableRef(); + } + if (mRef) { + mRef->ReleaseCompositableRef(); + } + mRef = aOther.get(); + return *this; + } + + CompositableTextureRef& operator=(T* aOther) { + if (aOther) { + aOther->AddCompositableRef(); + } + if (mRef) { + mRef->ReleaseCompositableRef(); + } + mRef = aOther; + return *this; + } + + T* get() const { return mRef; } + operator T*() const { return mRef; } + T* operator->() const { return mRef; } + T& operator*() const { return *mRef; } + + private: + RefPtr mRef; +}; + +typedef CompositableTextureRef CompositableTextureSourceRef; +typedef CompositableTextureRef CompositableTextureHostRef; + +/** + * Interface for TextureSources that can be updated from a DataSourceSurface. + * + * All backend should implement at least one DataTextureSource. + */ +class DataTextureSource : public TextureSource { + public: + DataTextureSource() : mOwner(0), mUpdateSerial(0) {} + + const char* Name() const override { return "DataTextureSource"; } + + DataTextureSource* AsDataTextureSource() override { return this; } + + /** + * Upload a (portion of) surface to the TextureSource. + * + * The DataTextureSource doesn't own aSurface, although it owns and manage + * the device texture it uploads to internally. + */ + virtual bool Update(gfx::DataSourceSurface* aSurface, + nsIntRegion* aDestRegion = nullptr, + gfx::IntPoint* aSrcOffset = nullptr) = 0; + + /** + * A facility to avoid reuploading when it is not necessary. + * The caller of Update can use GetUpdateSerial to see if the number has + * changed since last update, and call SetUpdateSerial after each successful + * update. The caller is responsible for managing the update serial except + * when the texture data is deallocated in which case the TextureSource should + * always reset the update serial to zero. + */ + uint32_t GetUpdateSerial() const { return mUpdateSerial; } + void SetUpdateSerial(uint32_t aValue) { mUpdateSerial = aValue; } + + // By default at least set the update serial to zero. + // overloaded versions should do that too. + void DeallocateDeviceData() override { SetUpdateSerial(0); } + +#ifdef DEBUG + /** + * Provide read access to the data as a DataSourceSurface. + * + * This is expected to be very slow and should be used for mostly debugging. + * XXX - implement everywhere and make it pure virtual. + */ + virtual already_AddRefed ReadBack() { + return nullptr; + }; +#endif + + void SetOwner(TextureHost* aOwner) { + auto newOwner = (uintptr_t)aOwner; + if (newOwner != mOwner) { + mOwner = newOwner; + SetUpdateSerial(0); + } + } + + bool IsOwnedBy(TextureHost* aOwner) const { + return mOwner == (uintptr_t)aOwner; + } + + bool HasOwner() const { return !IsOwnedBy(nullptr); } + + private: + // We store mOwner as an integer rather than as a pointer to make it clear + // it is not intended to be dereferenced. + uintptr_t mOwner; + uint32_t mUpdateSerial; +}; + +/** + * TextureHost is a thin abstraction over texture data that need to be shared + * between the content process and the compositor process. It is the + * compositor-side half of a TextureClient/TextureHost pair. A corresponding + * TextureClient lives on the content-side. + * + * TextureHost only knows how to deserialize or synchronize generic image data + * (SurfaceDescriptor) and provide access to one or more TextureSource objects + * (these provide the necessary APIs for compositor backends to composite the + * image). + * + * A TextureHost implementation corresponds to one SurfaceDescriptor type, as + * opposed to TextureSource that corresponds to device textures. + * This means that for YCbCr planes, even though they are represented as + * 3 textures internally (3 TextureSources), we use 1 TextureHost and not 3, + * because the 3 planes are stored in the same buffer of shared memory, before + * they are uploaded separately. + * + * There is always one and only one TextureHost per TextureClient, and the + * TextureClient/Host pair only owns one buffer of image data through its + * lifetime. This means that the lifetime of the underlying shared data + * matches the lifetime of the TextureClient/Host pair. It also means + * TextureClient/Host do not implement double buffering, which is the + * reponsibility of the compositable (which would use two Texture pairs). + * + * The Lock/Unlock mecanism here mirrors Lock/Unlock in TextureClient. + * + */ +class TextureHost : public AtomicRefCountedWithFinalize { + /** + * Called once, just before the destructor. + * + * Here goes the shut-down code that uses virtual methods. + * Must only be called by Release(). + */ + void Finalize(); + + friend class AtomicRefCountedWithFinalize; + + public: + explicit TextureHost(TextureFlags aFlags); + + protected: + virtual ~TextureHost(); + + public: + /** + * Factory method. + */ + static already_AddRefed Create( + const SurfaceDescriptor& aDesc, const ReadLockDescriptor& aReadLock, + ISurfaceAllocator* aDeallocator, LayersBackend aBackend, + TextureFlags aFlags, wr::MaybeExternalImageId& aExternalImageId); + + /** + * Lock the texture host for compositing. + */ + virtual bool Lock() { return true; } + /** + * Unlock the texture host after compositing. Lock() and Unlock() should be + * called in pair. + */ + virtual void Unlock() {} + + /** + * Lock the texture host for compositing without using compositor. + */ + virtual bool LockWithoutCompositor() { return true; } + /** + * Similar to Unlock(), but it should be called with LockWithoutCompositor(). + */ + virtual void UnlockWithoutCompositor() {} + + /** + * Note that the texture host format can be different from its corresponding + * texture source's. For example a ShmemTextureHost can have the ycbcr + * format and produce 3 "alpha" textures sources. + */ + virtual gfx::SurfaceFormat GetFormat() const = 0; + /** + * Return the format used for reading the texture. + * Apple's YCBCR_422 is R8G8B8X8. + */ + virtual gfx::SurfaceFormat GetReadFormat() const { return GetFormat(); } + + virtual gfx::YUVColorSpace GetYUVColorSpace() const { + return gfx::YUVColorSpace::UNKNOWN; + } + + /** + * Return the color depth of the image. Used with YUV textures. + */ + virtual gfx::ColorDepth GetColorDepth() const { + return gfx::ColorDepth::COLOR_8; + } + + /** + * Return true if using full range values (0-255 if 8 bits YUV). Used with YUV + * textures. + */ + virtual gfx::ColorRange GetColorRange() const { + return gfx::ColorRange::LIMITED; + } + + /** + * Called during the transaction. The TextureSource may or may not be + * composited. + * + * Note that this is called outside of lock/unlock. + */ + virtual void PrepareTextureSource(CompositableTextureSourceRef& aTexture) {} + + /** + * Called at composition time, just before compositing the TextureSource + * composited. + * + * Note that this is called only withing lock/unlock. + */ + virtual bool BindTextureSource(CompositableTextureSourceRef& aTexture) = 0; + + /** + * Called when preparing the rendering pipeline for advanced-layers. This is + * a lockless version of BindTextureSource. + */ + virtual bool AcquireTextureSource(CompositableTextureSourceRef& aTexture) { + return false; + } + + /** + * Called when another TextureHost will take over. + */ + virtual void UnbindTextureSource(); + + virtual bool IsValid() { return true; } + + /** + * Is called before compositing if the shared data has changed since last + * composition. + * This method should be overload in cases like when we need to do a texture + * upload for example. + * + * @param aRegion The region that has been changed, if nil, it means that the + * entire surface should be updated. + */ + void Updated(const nsIntRegion* aRegion = nullptr); + + /** + * Sets this TextureHost's compositor. A TextureHost can change compositor + * on certain occasions, in particular if it belongs to an async Compositable. + * aCompositor can be null, in which case the TextureHost must cleanup all + * of its device textures. + * + * Setting mProvider from this callback implicitly causes the texture to + * be locked for an extra frame after being detached from a compositable. + */ + virtual void SetTextureSourceProvider(TextureSourceProvider* aProvider) {} + + /** + * Should be overridden in order to deallocate the data that is associated + * with the rendering backend, such as GL textures. + */ + virtual void DeallocateDeviceData() {} + + /** + * Should be overridden in order to deallocate the data that is shared with + * the content side, such as shared memory. + */ + virtual void DeallocateSharedData() {} + + /** + * Should be overridden in order to force the TextureHost to drop all + * references to it's shared data. + * + * This is important to ensure the correctness of the deallocation protocol. + */ + virtual void ForgetSharedData() {} + + virtual gfx::IntSize GetSize() const = 0; + + /** + * Should be overridden if TextureHost supports crop rect. + */ + virtual void SetCropRect(nsIntRect aCropRect) {} + + /** + * Debug facility. + * XXX - cool kids use Moz2D. See bug 882113. + */ + virtual already_AddRefed GetAsSurface() = 0; + + /** + * XXX - Flags should only be set at creation time, this will be removed. + */ + void SetFlags(TextureFlags aFlags) { mFlags = aFlags; } + + /** + * XXX - Flags should only be set at creation time, this will be removed. + */ + void AddFlag(TextureFlags aFlag) { mFlags |= aFlag; } + + TextureFlags GetFlags() { return mFlags; } + + /** + * Allocate and deallocate a TextureParent actor. + * + * TextureParent< is an implementation detail of TextureHost that is not + * exposed to the rest of the code base. CreateIPDLActor and DestroyIPDLActor + * are for use with the managing IPDL protocols only (so that they can + * implement AllocPTextureParent and DeallocPTextureParent). + */ + static PTextureParent* CreateIPDLActor( + HostIPCAllocator* aAllocator, const SurfaceDescriptor& aSharedData, + const ReadLockDescriptor& aDescriptor, LayersBackend aLayersBackend, + TextureFlags aFlags, uint64_t aSerial, + const wr::MaybeExternalImageId& aExternalImageId); + static bool DestroyIPDLActor(PTextureParent* actor); + + /** + * Destroy the TextureChild/Parent pair. + */ + static bool SendDeleteIPDLActor(PTextureParent* actor); + + static void ReceivedDestroy(PTextureParent* actor); + + /** + * Get the TextureHost corresponding to the actor passed in parameter. + */ + static TextureHost* AsTextureHost(PTextureParent* actor); + + static uint64_t GetTextureSerial(PTextureParent* actor); + + /** + * Return a pointer to the IPDLActor. + * + * This is to be used with IPDL messages only. Do not store the returned + * pointer. + */ + PTextureParent* GetIPDLActor(); + + // If a texture host holds a reference to shmem, it should override this + // method to forget about the shmem _without_ releasing it. + virtual void OnShutdown() {} + + // Forget buffer actor. Used only for hacky fix for bug 966446. + virtual void ForgetBufferActor() {} + + virtual const char* Name() { return "TextureHost"; } + virtual void PrintInfo(std::stringstream& aStream, const char* aPrefix); + + /** + * Indicates whether the TextureHost implementation is backed by an + * in-memory buffer. The consequence of this is that locking the + * TextureHost does not contend with locking the texture on the client side. + */ + virtual bool HasIntermediateBuffer() const { return false; } + + /** + * Returns true if the TextureHost can be released before the rendering is + * completed, otherwise returns false. + */ + virtual bool NeedsDeferredDeletion() const { + return !HasIntermediateBuffer(); + } + + void AddCompositableRef() { + ++mCompositableCount; + if (mCompositableCount == 1) { + PrepareForUse(); + } + } + + void ReleaseCompositableRef() { + --mCompositableCount; + MOZ_ASSERT(mCompositableCount >= 0); + if (mCompositableCount == 0) { + UnbindTextureSource(); + // Send mFwdTransactionId to client side if necessary. + NotifyNotUsed(); + } + } + + int NumCompositableRefs() const { return mCompositableCount; } + + void SetLastFwdTransactionId(uint64_t aTransactionId); + + void DeserializeReadLock(const ReadLockDescriptor& aDesc, + ISurfaceAllocator* aAllocator); + void SetReadLocked(); + + TextureReadLock* GetReadLock() { return mReadLock; } + + virtual BufferTextureHost* AsBufferTextureHost() { return nullptr; } + virtual MacIOSurfaceTextureHostOGL* AsMacIOSurfaceTextureHost() { + return nullptr; + } + virtual WebRenderTextureHost* AsWebRenderTextureHost() { return nullptr; } + virtual SurfaceTextureHost* AsSurfaceTextureHost() { return nullptr; } + virtual AndroidHardwareBufferTextureHost* + AsAndroidHardwareBufferTextureHost() { + return nullptr; + } + + // Create the corresponding RenderTextureHost type of this texture, and + // register the RenderTextureHost into render thread. + virtual void CreateRenderTexture( + const wr::ExternalImageId& aExternalImageId) { + MOZ_RELEASE_ASSERT( + false, + "No CreateRenderTexture() implementation for this TextureHost type."); + } + + void EnsureRenderTexture(const wr::MaybeExternalImageId& aExternalImageId); + + // Destroy RenderTextureHost when it was created by the TextureHost. + // It is called in TextureHost::Finalize(). + virtual void MaybeDestroyRenderTexture(); + + static void DestroyRenderTexture(const wr::ExternalImageId& aExternalImageId); + + /// Returns the number of actual textures that will be used to render this. + /// For example in a lot of YUV cases it will be 3 + virtual uint32_t NumSubTextures() { return 1; } + + enum ResourceUpdateOp { + ADD_IMAGE, + UPDATE_IMAGE, + }; + + // Add all necessary TextureHost informations to the resource update queue. + virtual void PushResourceUpdates(wr::TransactionBuilder& aResources, + ResourceUpdateOp aOp, + const Range& aImageKeys, + const wr::ExternalImageId& aExtID) { + MOZ_ASSERT_UNREACHABLE("Unimplemented"); + } + + enum class PushDisplayItemFlag { + // Passed if the caller wants these display items to be promoted + // to compositor surfaces if possible. + PREFER_COMPOSITOR_SURFACE, + + // Passed in the RenderCompositor supports BufferTextureHosts + // being used directly as external compositor surfaces. + SUPPORTS_EXTERNAL_BUFFER_TEXTURES, + }; + using PushDisplayItemFlagSet = EnumSet; + + // Put all necessary WR commands into DisplayListBuilder for this textureHost + // rendering. + virtual void PushDisplayItems(wr::DisplayListBuilder& aBuilder, + const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, + wr::ImageRendering aFilter, + const Range& aKeys, + PushDisplayItemFlagSet aFlags) { + MOZ_ASSERT_UNREACHABLE( + "No PushDisplayItems() implementation for this TextureHost type."); + } + + /** + * Some API's can use the cross-process IOSurface directly, such as OpenVR + */ + virtual MacIOSurface* GetMacIOSurface() { return nullptr; } + + virtual bool IsDirectMap() { return false; } + + virtual bool NeedsYFlip() const; + + TextureSourceProvider* GetProvider() const { return mProvider; } + + virtual void SetAcquireFence(mozilla::ipc::FileDescriptor&& aFenceFd) {} + + virtual void SetReleaseFence(mozilla::ipc::FileDescriptor&& aFenceFd) {} + + virtual mozilla::ipc::FileDescriptor GetAndResetReleaseFence() { + return mozilla::ipc::FileDescriptor(); + } + + virtual AndroidHardwareBuffer* GetAndroidHardwareBuffer() const { + return nullptr; + } + + virtual bool SupportsExternalCompositing() { return false; } + + protected: + virtual void ReadUnlock(); + + void RecycleTexture(TextureFlags aFlags); + + virtual void MaybeNotifyUnlocked() {} + + virtual void UpdatedInternal(const nsIntRegion* Region) {} + + /** + * Called when mCompositableCount becomes from 0 to 1. + */ + virtual void PrepareForUse() {} + + /** + * Called when mCompositableCount becomes 0. + */ + virtual void NotifyNotUsed(); + + // for Compositor. + void CallNotifyNotUsed(); + + PTextureParent* mActor; + RefPtr mProvider; + RefPtr mReadLock; + TextureFlags mFlags; + int mCompositableCount; + uint64_t mFwdTransactionId; + bool mReadLocked; + wr::MaybeExternalImageId mExternalImageId; + + friend class Compositor; + friend class TextureParent; + friend class TiledLayerBufferComposite; + friend class TextureSourceProvider; + friend class GPUVideoTextureHost; + friend class WebRenderTextureHost; +}; + +/** + * TextureHost that wraps a random access buffer such as a Shmem or some raw + * memory. + * + * This TextureHost is backend-independent and the backend-specific bits are + * in the TextureSource. + * This class must be inherited to implement GetBuffer and DeallocSharedData + * (see ShmemTextureHost and MemoryTextureHost) + * + * Uploads happen when Lock is called. + * + * BufferTextureHost supports YCbCr and flavours of RGBA images (RGBX, A, etc.). + */ +class BufferTextureHost : public TextureHost { + public: + BufferTextureHost(const BufferDescriptor& aDescriptor, TextureFlags aFlags); + + virtual ~BufferTextureHost(); + + virtual uint8_t* GetBuffer() = 0; + + virtual size_t GetBufferSize() = 0; + + bool Lock() override; + + void Unlock() override; + + void PrepareTextureSource(CompositableTextureSourceRef& aTexture) override; + + bool BindTextureSource(CompositableTextureSourceRef& aTexture) override; + bool AcquireTextureSource(CompositableTextureSourceRef& aTexture) override; + + void UnbindTextureSource() override; + + void DeallocateDeviceData() override; + + void SetTextureSourceProvider(TextureSourceProvider* aProvider) override; + + /** + * Return the format that is exposed to the compositor when calling + * BindTextureSource. + * + * If the shared format is YCbCr and the compositor does not support it, + * GetFormat will be RGB32 (even though mFormat is SurfaceFormat::YUV). + */ + gfx::SurfaceFormat GetFormat() const override; + + gfx::YUVColorSpace GetYUVColorSpace() const override; + + gfx::ColorDepth GetColorDepth() const override; + + gfx::ColorRange GetColorRange() const override; + + gfx::IntSize GetSize() const override { return mSize; } + + already_AddRefed GetAsSurface() override; + + bool HasIntermediateBuffer() const override { return mHasIntermediateBuffer; } + + bool NeedsDeferredDeletion() const override { + return TextureHost::NeedsDeferredDeletion() || UseExternalTextures(); + } + + BufferTextureHost* AsBufferTextureHost() override { return this; } + + const BufferDescriptor& GetBufferDescriptor() const { return mDescriptor; } + + void CreateRenderTexture( + const wr::ExternalImageId& aExternalImageId) override; + + uint32_t NumSubTextures() override; + + void PushResourceUpdates(wr::TransactionBuilder& aResources, + ResourceUpdateOp aOp, + const Range& aImageKeys, + const wr::ExternalImageId& aExtID) override; + + void PushDisplayItems(wr::DisplayListBuilder& aBuilder, + const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, wr::ImageRendering aFilter, + const Range& aImageKeys, + PushDisplayItemFlagSet aFlags) override; + + void ReadUnlock() override; + bool IsDirectMap() override { + return mFirstSource && mFirstSource->IsDirectMap(); + }; + + bool CanUnlock() { return !mFirstSource || mFirstSource->Sync(false); } + void DisableExternalTextures() { mUseExternalTextures = false; } + + protected: + bool UseExternalTextures() const { return mUseExternalTextures; } + bool Upload(nsIntRegion* aRegion = nullptr); + bool UploadIfNeeded(); + bool MaybeUpload(nsIntRegion* aRegion); + bool EnsureWrappingTextureSource(); + + void UpdatedInternal(const nsIntRegion* aRegion = nullptr) override; + void MaybeNotifyUnlocked() override; + + BufferDescriptor mDescriptor; + RefPtr mCompositor; + RefPtr mFirstSource; + nsIntRegion mMaybeUpdatedRegion; + gfx::IntSize mSize; + gfx::SurfaceFormat mFormat; + uint32_t mUpdateSerial; + bool mLocked; + bool mNeedsFullUpdate; + bool mHasIntermediateBuffer; + bool mUseExternalTextures; + + class DataTextureSourceYCbCrBasic; +}; + +/** + * TextureHost that wraps shared memory. + * the corresponding texture on the client side is ShmemTextureClient. + * This TextureHost is backend-independent. + */ +class ShmemTextureHost : public BufferTextureHost { + public: + ShmemTextureHost(const mozilla::ipc::Shmem& aShmem, + const BufferDescriptor& aDesc, + ISurfaceAllocator* aDeallocator, TextureFlags aFlags); + + protected: + ~ShmemTextureHost(); + + public: + void DeallocateSharedData() override; + + void ForgetSharedData() override; + + uint8_t* GetBuffer() override; + + size_t GetBufferSize() override; + + const char* Name() override { return "ShmemTextureHost"; } + + void OnShutdown() override; + + protected: + UniquePtr mShmem; + RefPtr mDeallocator; +}; + +/** + * TextureHost that wraps raw memory. + * The corresponding texture on the client side is MemoryTextureClient. + * Can obviously not be used in a cross process setup. + * This TextureHost is backend-independent. + */ +class MemoryTextureHost : public BufferTextureHost { + public: + MemoryTextureHost(uint8_t* aBuffer, const BufferDescriptor& aDesc, + TextureFlags aFlags); + + protected: + ~MemoryTextureHost(); + + public: + void DeallocateSharedData() override; + + void ForgetSharedData() override; + + uint8_t* GetBuffer() override; + + size_t GetBufferSize() override; + + const char* Name() override { return "MemoryTextureHost"; } + + protected: + uint8_t* mBuffer; +}; + +class MOZ_STACK_CLASS AutoLockTextureHost { + public: + explicit AutoLockTextureHost(TextureHost* aTexture) : mTexture(aTexture) { + mLocked = mTexture ? mTexture->Lock() : false; + } + + ~AutoLockTextureHost() { + if (mTexture && mLocked) { + mTexture->Unlock(); + } + } + + bool Failed() { return mTexture && !mLocked; } + + private: + RefPtr mTexture; + bool mLocked; +}; + +class MOZ_STACK_CLASS AutoLockTextureHostWithoutCompositor { + public: + explicit AutoLockTextureHostWithoutCompositor(TextureHost* aTexture) + : mTexture(aTexture) { + mLocked = mTexture ? mTexture->LockWithoutCompositor() : false; + } + + ~AutoLockTextureHostWithoutCompositor() { + if (mTexture && mLocked) { + mTexture->UnlockWithoutCompositor(); + } + } + + bool Failed() { return mTexture && !mLocked; } + + private: + RefPtr mTexture; + bool mLocked; +}; + +/** + * This can be used as an offscreen rendering target by the compositor, and + * subsequently can be used as a source by the compositor. + */ +class CompositingRenderTarget : public TextureSource { + public: + explicit CompositingRenderTarget(const gfx::IntPoint& aOrigin) + : mClearOnBind(false), + mOrigin(aOrigin), + mZNear(0), + mZFar(0), + mHasComplexProjection(false), + mEnableDepthBuffer(false) {} + virtual ~CompositingRenderTarget() = default; + + const char* Name() const override { return "CompositingRenderTarget"; } + +#ifdef MOZ_DUMP_PAINTING + virtual already_AddRefed Dump( + Compositor* aCompositor) { + return nullptr; + } +#endif + + /** + * Perform a clear when recycling a non opaque surface. + * The clear is deferred to when the render target is bound. + */ + void ClearOnBind() { mClearOnBind = true; } + + const gfx::IntPoint& GetOrigin() const { return mOrigin; } + gfx::IntRect GetRect() { return gfx::IntRect(GetOrigin(), GetSize()); } + + /** + * If a Projection matrix is set, then it is used for rendering to + * this render target instead of generating one. If no explicit + * projection is set, Compositors are expected to generate an + * orthogonal maaping that maps 0..1 to the full size of the render + * target. + */ + bool HasComplexProjection() const { return mHasComplexProjection; } + void ClearProjection() { mHasComplexProjection = false; } + void SetProjection(const gfx::Matrix4x4& aNewMatrix, bool aEnableDepthBuffer, + float aZNear, float aZFar) { + mProjectionMatrix = aNewMatrix; + mEnableDepthBuffer = aEnableDepthBuffer; + mZNear = aZNear; + mZFar = aZFar; + mHasComplexProjection = true; + } + void GetProjection(gfx::Matrix4x4& aMatrix, bool& aEnableDepth, float& aZNear, + float& aZFar) { + MOZ_ASSERT(mHasComplexProjection); + aMatrix = mProjectionMatrix; + aEnableDepth = mEnableDepthBuffer; + aZNear = mZNear; + aZFar = mZFar; + } + + protected: + bool mClearOnBind; + + private: + gfx::IntPoint mOrigin; + + gfx::Matrix4x4 mProjectionMatrix; + float mZNear, mZFar; + bool mHasComplexProjection; + bool mEnableDepthBuffer; +}; + +/** + * Creates a TextureHost that can be used with any of the existing backends + * Not all SurfaceDescriptor types are supported + */ +already_AddRefed CreateBackendIndependentTextureHost( + const SurfaceDescriptor& aDesc, ISurfaceAllocator* aDeallocator, + LayersBackend aBackend, TextureFlags aFlags); + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/composite/TiledContentHost.cpp b/gfx/layers/composite/TiledContentHost.cpp new file mode 100644 index 0000000000..02f7c3b846 --- /dev/null +++ b/gfx/layers/composite/TiledContentHost.cpp @@ -0,0 +1,658 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TiledContentHost.h" +#include "PaintedLayerComposite.h" // for PaintedLayerComposite +#include "mozilla/gfx/BaseSize.h" // for BaseSize +#include "mozilla/gfx/Matrix.h" // for Matrix4x4 +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/layers/Compositor.h" // for Compositor +// clang-format off +//#include "mozilla/layers/CompositorBridgeParent.h" // for CompositorBridgeParent +// clang-format on +#include "mozilla/layers/Effects.h" // for TexturedEffect, Effect, etc +#include "mozilla/layers/LayerMetricsWrapper.h" // for LayerMetricsWrapper +#include "mozilla/layers/PTextureParent.h" +#include "mozilla/layers/TextureHostOGL.h" // for TextureHostOGL +#ifdef XP_DARWIN +# include "mozilla/layers/TextureSync.h" // for TextureSync +#endif +#include "nsAString.h" +#include "nsDebug.h" // for NS_WARNING +#include "nsPoint.h" // for IntPoint +#include "nsPrintfCString.h" // for nsPrintfCString +#include "nsRect.h" // for IntRect +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/layers/TextureClient.h" + +namespace mozilla { +using namespace gfx; +namespace layers { + +class Layer; + +float TileHost::GetFadeInOpacity(float aOpacity) { + TimeStamp now = TimeStamp::Now(); + if (!StaticPrefs::layers_tiles_fade_in_enabled() || mFadeStart.IsNull() || + now < mFadeStart) { + return aOpacity; + } + + float duration = StaticPrefs::layers_tiles_fade_in_duration_ms(); + float elapsed = (now - mFadeStart).ToMilliseconds(); + if (elapsed > duration) { + mFadeStart = TimeStamp(); + return aOpacity; + } + return aOpacity * (elapsed / duration); +} + +RefPtr TileHost::AcquireTextureSource() const { + if (!mTextureHost || !mTextureHost->AcquireTextureSource(mTextureSource)) { + return nullptr; + } + return mTextureSource.get(); +} + +RefPtr TileHost::AcquireTextureSourceOnWhite() const { + if (!mTextureHostOnWhite || + !mTextureHostOnWhite->AcquireTextureSource(mTextureSourceOnWhite)) { + return nullptr; + } + return mTextureSourceOnWhite.get(); +} + +TiledLayerBufferComposite::TiledLayerBufferComposite() : mFrameResolution() {} + +TiledLayerBufferComposite::~TiledLayerBufferComposite() { Clear(); } + +void TiledLayerBufferComposite::SetTextureSourceProvider( + TextureSourceProvider* aProvider) { + MOZ_ASSERT(aProvider); + for (TileHost& tile : mRetainedTiles) { + if (tile.IsPlaceholderTile()) continue; + tile.mTextureHost->SetTextureSourceProvider(aProvider); + if (tile.mTextureHostOnWhite) { + tile.mTextureHostOnWhite->SetTextureSourceProvider(aProvider); + } + } +} + +void TiledLayerBufferComposite::AddAnimationInvalidation(nsIntRegion& aRegion) { + // We need to invalidate rects where we have a tile that is in the + // process of fading in. + for (size_t i = 0; i < mRetainedTiles.Length(); i++) { + if (!mRetainedTiles[i].mFadeStart.IsNull()) { + TileCoordIntPoint coord = mTiles.TileCoord(i); + IntPoint offset = GetTileOffset(coord); + nsIntRegion tileRegion = IntRect(offset, GetScaledTileSize()); + aRegion.OrWith(tileRegion); + } + } +} + +TiledContentHost::TiledContentHost(const TextureInfo& aTextureInfo) + : ContentHost(aTextureInfo), + mTiledBuffer(TiledLayerBufferComposite()), + mLowPrecisionTiledBuffer(TiledLayerBufferComposite()) { + MOZ_COUNT_CTOR(TiledContentHost); +} + +TiledContentHost::~TiledContentHost() { MOZ_COUNT_DTOR(TiledContentHost); } + +already_AddRefed TiledContentHost::GenEffect( + const gfx::SamplingFilter aSamplingFilter) { + MOZ_ASSERT(mTiledBuffer.GetTileCount() == 1 && + mLowPrecisionTiledBuffer.GetTileCount() == 0); + MOZ_ASSERT(mTiledBuffer.GetTile(0).mTextureHost); + + TileHost& tile = mTiledBuffer.GetTile(0); + if (!tile.mTextureHost->BindTextureSource(tile.mTextureSource)) { + return nullptr; + } + + return CreateTexturedEffect(tile.mTextureSource, nullptr, aSamplingFilter, + true); +} + +void TiledContentHost::Attach(Layer* aLayer, TextureSourceProvider* aProvider, + AttachFlags aFlags /* = NO_FLAGS */) { + CompositableHost::Attach(aLayer, aProvider, aFlags); +} + +void TiledContentHost::Detach(Layer* aLayer, + AttachFlags aFlags /* = NO_FLAGS */) { + if (!mKeepAttached || aLayer == mLayer || aFlags & FORCE_DETACH) { + // Clear the TiledLayerBuffers, which will take care of releasing the + // copy-on-write locks. + mTiledBuffer.Clear(); + mLowPrecisionTiledBuffer.Clear(); + } + CompositableHost::Detach(aLayer, aFlags); +} + +bool TiledContentHost::UseTiledLayerBuffer( + ISurfaceAllocator* aAllocator, + const SurfaceDescriptorTiles& aTiledDescriptor) { + HostLayerManager* lm = GetLayerManager(); + if (!lm) { + return false; + } + + if (aTiledDescriptor.resolution() < 1) { + if (!mLowPrecisionTiledBuffer.UseTiles(aTiledDescriptor, lm, aAllocator)) { + return false; + } + } else { + if (!mTiledBuffer.UseTiles(aTiledDescriptor, lm, aAllocator)) { + return false; + } + } + return true; +} + +static void UseTileTexture(CompositableTextureHostRef& aTexture, + CompositableTextureSourceRef& aTextureSource, + const IntRect& aUpdateRect, + TextureSourceProvider* aProvider) { + MOZ_ASSERT(aTexture); + if (!aTexture) { + return; + } + + if (aProvider) { + aTexture->SetTextureSourceProvider(aProvider); + } + + if (!aUpdateRect.IsEmpty()) { + // For !HasIntermediateBuffer() textures, this is likely a no-op. + nsIntRegion region = aUpdateRect; + aTexture->Updated(®ion); + } + + aTexture->PrepareTextureSource(aTextureSource); +} + +class TextureSourceRecycler { + public: + explicit TextureSourceRecycler(nsTArray&& aTileSet) + : mTiles(std::move(aTileSet)), mFirstPossibility(0) {} + + // Attempts to recycle a texture source that is already bound to the + // texture host for aTile. + void RecycleTextureSourceForTile(TileHost& aTile) { + for (size_t i = mFirstPossibility; i < mTiles.Length(); i++) { + // Skip over existing tiles without a retained texture source + // and make sure we don't iterate them in the future. + if (!mTiles[i].mTextureSource) { + if (i == mFirstPossibility) { + mFirstPossibility++; + } + continue; + } + + // If this tile matches, then copy across the retained texture source (if + // any). + if (aTile.mTextureHost == mTiles[i].mTextureHost) { + aTile.mTextureSource = std::move(mTiles[i].mTextureSource); + if (aTile.mTextureHostOnWhite) { + aTile.mTextureSourceOnWhite = + std::move(mTiles[i].mTextureSourceOnWhite); + } + break; + } + } + } + + // Attempts to recycle any texture source to avoid needing to allocate + // a new one. + void RecycleTextureSource(TileHost& aTile) { + for (size_t i = mFirstPossibility; i < mTiles.Length(); i++) { + if (!mTiles[i].mTextureSource) { + if (i == mFirstPossibility) { + mFirstPossibility++; + } + continue; + } + + if (mTiles[i].mTextureSource && mTiles[i].mTextureHost->GetFormat() == + aTile.mTextureHost->GetFormat()) { + aTile.mTextureSource = std::move(mTiles[i].mTextureSource); + if (aTile.mTextureHostOnWhite) { + aTile.mTextureSourceOnWhite = + std::move(mTiles[i].mTextureSourceOnWhite); + } + break; + } + } + } + + void RecycleTileFading(TileHost& aTile) { + for (size_t i = 0; i < mTiles.Length(); i++) { + if (mTiles[i].mTextureHost == aTile.mTextureHost) { + aTile.mFadeStart = mTiles[i].mFadeStart; + } + } + } + + protected: + nsTArray mTiles; + size_t mFirstPossibility; +}; + +bool TiledLayerBufferComposite::UseTiles(const SurfaceDescriptorTiles& aTiles, + HostLayerManager* aLayerManager, + ISurfaceAllocator* aAllocator) { + if (mResolution != aTiles.resolution() || aTiles.tileSize() != mTileSize) { + Clear(); + } + MOZ_ASSERT(aAllocator); + MOZ_ASSERT(aLayerManager); + if (!aAllocator || !aLayerManager) { + return false; + } + + if (aTiles.resolution() == 0 || IsNaN(aTiles.resolution())) { + // There are divisions by mResolution so this protects the compositor + // process against malicious content processes and fuzzing. + return false; + } + + TilesPlacement newTiles(aTiles.firstTileX(), aTiles.firstTileY(), + aTiles.retainedWidth(), aTiles.retainedHeight()); + + const nsTArray& tileDescriptors = aTiles.tiles(); + + TextureSourceRecycler oldRetainedTiles(std::move(mRetainedTiles)); + mRetainedTiles.SetLength(tileDescriptors.Length()); + + AutoTArray lockedTextureSerials; + base::ProcessId lockedTexturePid = 0; + + // Step 1, deserialize the incoming set of tiles into mRetainedTiles, and + // attempt to recycle the TextureSource for any repeated tiles. + // + // Since we don't have any retained 'tile' object, we have to search for + // instances of the same TextureHost in the old tile set. The cost of binding + // a TextureHost to a TextureSource for gralloc (binding EGLImage to GL + // texture) can be really high, so we avoid this whenever possible. + for (size_t i = 0; i < tileDescriptors.Length(); i++) { + const TileDescriptor& tileDesc = tileDescriptors[i]; + + TileHost& tile = mRetainedTiles[i]; + + if (tileDesc.type() != TileDescriptor::TTexturedTileDescriptor) { + NS_WARNING_ASSERTION( + tileDesc.type() == TileDescriptor::TPlaceholderTileDescriptor, + "Unrecognised tile descriptor type"); + continue; + } + + const TexturedTileDescriptor& texturedDesc = + tileDesc.get_TexturedTileDescriptor(); + + tile.mTextureHost = + TextureHost::AsTextureHost(texturedDesc.textureParent()); + if (texturedDesc.readLocked()) { + tile.mTextureHost->SetReadLocked(); + auto actor = tile.mTextureHost->GetIPDLActor(); + if (actor && tile.mTextureHost->IsDirectMap()) { + lockedTextureSerials.AppendElement( + TextureHost::GetTextureSerial(actor)); + + if (lockedTexturePid) { + MOZ_ASSERT(lockedTexturePid == actor->OtherPid()); + } + lockedTexturePid = actor->OtherPid(); + } + } + + if (texturedDesc.textureOnWhiteParent().isSome()) { + tile.mTextureHostOnWhite = + TextureHost::AsTextureHost(texturedDesc.textureOnWhiteParent().ref()); + if (texturedDesc.readLockedOnWhite()) { + tile.mTextureHostOnWhite->SetReadLocked(); + auto actor = tile.mTextureHostOnWhite->GetIPDLActor(); + if (actor && tile.mTextureHostOnWhite->IsDirectMap()) { + lockedTextureSerials.AppendElement( + TextureHost::GetTextureSerial(actor)); + } + } + } + + tile.mTileCoord = newTiles.TileCoord(i); + + // If this same tile texture existed in the old tile set then this will move + // the texture source into our new tile. + oldRetainedTiles.RecycleTextureSourceForTile(tile); + + // If this tile is in the process of fading, we need to keep that going + oldRetainedTiles.RecycleTileFading(tile); + + if (aTiles.isProgressive() && texturedDesc.wasPlaceholder()) { + // This is a progressive paint, and the tile used to be a placeholder. + // We need to begin fading it in (if enabled via + // layers.tiles.fade-in.enabled) + tile.mFadeStart = TimeStamp::Now(); + + aLayerManager->CompositeUntil( + tile.mFadeStart + + TimeDuration::FromMilliseconds( + StaticPrefs::layers_tiles_fade_in_duration_ms())); + } + } + +#ifdef XP_DARWIN + if (lockedTextureSerials.Length() > 0) { + TextureSync::SetTexturesLocked(lockedTexturePid, lockedTextureSerials); + } +#endif + + // Step 2, attempt to recycle unused texture sources from the old tile set + // into new tiles. + // + // For gralloc, binding a new TextureHost to the existing TextureSource is the + // fastest way to ensure that any implicit locking on the old gralloc image is + // released. + for (TileHost& tile : mRetainedTiles) { + if (!tile.mTextureHost || tile.mTextureSource) { + continue; + } + oldRetainedTiles.RecycleTextureSource(tile); + } + + // Step 3, handle the texture uploads, texture source binding and release the + // copy-on-write locks for textures with an internal buffer. + for (size_t i = 0; i < mRetainedTiles.Length(); i++) { + TileHost& tile = mRetainedTiles[i]; + if (!tile.mTextureHost) { + continue; + } + + const TileDescriptor& tileDesc = tileDescriptors[i]; + const TexturedTileDescriptor& texturedDesc = + tileDesc.get_TexturedTileDescriptor(); + + UseTileTexture(tile.mTextureHost, tile.mTextureSource, + texturedDesc.updateRect(), + aLayerManager->GetTextureSourceProvider()); + + if (tile.mTextureHostOnWhite) { + UseTileTexture(tile.mTextureHostOnWhite, tile.mTextureSourceOnWhite, + texturedDesc.updateRect(), + aLayerManager->GetTextureSourceProvider()); + } + } + + mTiles = newTiles; + mTileSize = aTiles.tileSize(); + mTileOrigin = aTiles.tileOrigin(); + mValidRegion = aTiles.validRegion(); + mResolution = aTiles.resolution(); + mFrameResolution = CSSToParentLayerScale2D(aTiles.frameXResolution(), + aTiles.frameYResolution()); + + return true; +} + +void TiledLayerBufferComposite::Clear() { + mRetainedTiles.Clear(); + mTiles.mFirst = TileCoordIntPoint(); + mTiles.mSize = TileCoordIntSize(); + mValidRegion = nsIntRegion(); + mResolution = 1.0; +} + +void TiledContentHost::Composite( + Compositor* aCompositor, LayerComposite* aLayer, EffectChain& aEffectChain, + float aOpacity, const gfx::Matrix4x4& aTransform, + const gfx::SamplingFilter aSamplingFilter, const gfx::IntRect& aClipRect, + const nsIntRegion* aVisibleRegion /* = nullptr */, + const Maybe& aGeometry) { + // Reduce the opacity of the low-precision buffer to make it a + // little more subtle and less jarring. In particular, text + // rendered at low-resolution and scaled tends to look pretty + // heavy and this helps mitigate that. When we reduce the opacity + // we also make sure to draw the background color behind the + // reduced-opacity tile so that content underneath doesn't show + // through. + // However, in cases where the background is transparent, or the layer + // already has some opacity, we want to skip this behaviour. Otherwise + // we end up changing the expected overall transparency of the content, + // and it just looks wrong. + DeviceColor backgroundColor; + if (aOpacity == 1.0f && StaticPrefs::layers_low_precision_opacity() < 1.0f) { + // Background colors are only stored on scrollable layers. Grab + // the one from the nearest scrollable ancestor layer. + for (LayerMetricsWrapper ancestor(GetLayer(), + LayerMetricsWrapper::StartAt::BOTTOM); + ancestor; ancestor = ancestor.GetParent()) { + if (ancestor.Metrics().IsScrollable()) { + backgroundColor = ancestor.Metadata().GetBackgroundColor(); + break; + } + } + } + float lowPrecisionOpacityReduction = + (aOpacity == 1.0f && backgroundColor.a == 1.0f) + ? StaticPrefs::layers_low_precision_opacity() + : 1.0f; + + nsIntRegion tmpRegion; + const nsIntRegion* renderRegion = aVisibleRegion; +#ifndef MOZ_IGNORE_PAINT_WILL_RESAMPLE + if (PaintWillResample()) { + // If we're resampling, then the texture image will contain exactly the + // entire visible region's bounds, and we should draw it all in one quad + // to avoid unexpected aliasing. + tmpRegion = aVisibleRegion->GetBounds(); + renderRegion = &tmpRegion; + } +#endif + + // Render the low and high precision buffers. + RenderLayerBuffer( + mLowPrecisionTiledBuffer, aCompositor, + lowPrecisionOpacityReduction < 1.0f ? &backgroundColor : nullptr, + aEffectChain, lowPrecisionOpacityReduction * aOpacity, aSamplingFilter, + aClipRect, *renderRegion, aTransform, aGeometry); + + RenderLayerBuffer(mTiledBuffer, aCompositor, nullptr, aEffectChain, aOpacity, + aSamplingFilter, aClipRect, *renderRegion, aTransform, + aGeometry); +} + +void TiledContentHost::RenderTile( + TileHost& aTile, Compositor* aCompositor, EffectChain& aEffectChain, + float aOpacity, const gfx::Matrix4x4& aTransform, + const gfx::SamplingFilter aSamplingFilter, const gfx::IntRect& aClipRect, + const nsIntRegion& aScreenRegion, const IntPoint& aTextureOffset, + const IntSize& aTextureBounds, const gfx::Rect& aVisibleRect, + const Maybe& aGeometry) { + MOZ_ASSERT(!aTile.IsPlaceholderTile()); + + AutoLockTextureHost autoLock(aTile.mTextureHost); + AutoLockTextureHost autoLockOnWhite(aTile.mTextureHostOnWhite); + if (autoLock.Failed() || autoLockOnWhite.Failed()) { + NS_WARNING("Failed to lock tile"); + return; + } + + if (!aTile.mTextureHost->BindTextureSource(aTile.mTextureSource)) { + return; + } + + if (aTile.mTextureHostOnWhite && + !aTile.mTextureHostOnWhite->BindTextureSource( + aTile.mTextureSourceOnWhite)) { + return; + } + + RefPtr effect = CreateTexturedEffect( + aTile.mTextureSource, aTile.mTextureSourceOnWhite, aSamplingFilter, true); + if (!effect) { + return; + } + + float opacity = aTile.GetFadeInOpacity(aOpacity); + aEffectChain.mPrimaryEffect = effect; + + for (auto iter = aScreenRegion.RectIter(); !iter.Done(); iter.Next()) { + const IntRect& rect = iter.Get(); + Rect graphicsRect(rect.X(), rect.Y(), rect.Width(), rect.Height()); + Rect textureRect(rect.X() - aTextureOffset.x, rect.Y() - aTextureOffset.y, + rect.Width(), rect.Height()); + + effect->mTextureCoords.SetRect( + textureRect.X() / aTextureBounds.width, + textureRect.Y() / aTextureBounds.height, + textureRect.Width() / aTextureBounds.width, + textureRect.Height() / aTextureBounds.height); + + aCompositor->DrawGeometry(graphicsRect, aClipRect, aEffectChain, opacity, + aTransform, aVisibleRect, aGeometry); + } + + DiagnosticFlags flags = DiagnosticFlags::CONTENT | DiagnosticFlags::TILE; + if (aTile.mTextureHostOnWhite) { + flags |= DiagnosticFlags::COMPONENT_ALPHA; + } + aCompositor->DrawDiagnostics(flags, aScreenRegion, aClipRect, aTransform, + mFlashCounter); +} + +void TiledContentHost::RenderLayerBuffer( + TiledLayerBufferComposite& aLayerBuffer, Compositor* aCompositor, + const DeviceColor* aBackgroundColor, EffectChain& aEffectChain, + float aOpacity, const gfx::SamplingFilter aSamplingFilter, + const gfx::IntRect& aClipRect, nsIntRegion aVisibleRegion, + gfx::Matrix4x4 aTransform, const Maybe& aGeometry) { + float resolution = aLayerBuffer.GetResolution(); + gfx::Size layerScale(1, 1); + + // We assume that the current frame resolution is the one used in our high + // precision layer buffer. Compensate for a changing frame resolution when + // rendering the low precision buffer. + if (aLayerBuffer.GetFrameResolution() != mTiledBuffer.GetFrameResolution()) { + const CSSToParentLayerScale2D& layerResolution = + aLayerBuffer.GetFrameResolution(); + const CSSToParentLayerScale2D& localResolution = + mTiledBuffer.GetFrameResolution(); + layerScale.width = layerResolution.xScale / localResolution.xScale; + layerScale.height = layerResolution.yScale / localResolution.yScale; + aVisibleRegion.ScaleRoundOut(layerScale.width, layerScale.height); + } + + // Make sure we don't render at low resolution where we have valid high + // resolution content, to avoid overdraw and artifacts with semi-transparent + // layers. + nsIntRegion maskRegion; + if (resolution != mTiledBuffer.GetResolution()) { + maskRegion = mTiledBuffer.GetValidRegion(); + // XXX This should be ScaleRoundIn, but there is no such function on + // nsIntRegion. + maskRegion.ScaleRoundOut(layerScale.width, layerScale.height); + } + + // Make sure the resolution and difference in frame resolution are accounted + // for in the layer transform. + aTransform.PreScale(1 / (resolution * layerScale.width), + 1 / (resolution * layerScale.height), 1); + + DiagnosticFlags componentAlphaDiagnostic = DiagnosticFlags::NO_DIAGNOSTIC; + + nsIntRegion compositeRegion = aLayerBuffer.GetValidRegion(); + compositeRegion.AndWith(aVisibleRegion); + compositeRegion.SubOut(maskRegion); + + IntRect visibleRect = aVisibleRegion.GetBounds(); + + if (compositeRegion.IsEmpty()) { + return; + } + + if (aBackgroundColor) { + nsIntRegion backgroundRegion = compositeRegion; + backgroundRegion.ScaleRoundOut(resolution, resolution); + EffectChain effect; + effect.mPrimaryEffect = new EffectSolidColor(*aBackgroundColor); + for (auto iter = backgroundRegion.RectIter(); !iter.Done(); iter.Next()) { + const IntRect& rect = iter.Get(); + Rect graphicsRect(rect.X(), rect.Y(), rect.Width(), rect.Height()); + aCompositor->DrawGeometry(graphicsRect, aClipRect, effect, 1.0, + aTransform, aGeometry); + } + } + + for (size_t i = 0; i < aLayerBuffer.GetTileCount(); ++i) { + TileHost& tile = aLayerBuffer.GetTile(i); + if (tile.IsPlaceholderTile()) { + continue; + } + + TileCoordIntPoint tileCoord = aLayerBuffer.GetPlacement().TileCoord(i); + // A sanity check that catches a lot of mistakes. + MOZ_ASSERT(tileCoord.x == tile.mTileCoord.x && + tileCoord.y == tile.mTileCoord.y); + + IntPoint tileOffset = aLayerBuffer.GetTileOffset(tileCoord); + nsIntRegion tileDrawRegion = + IntRect(tileOffset, aLayerBuffer.GetScaledTileSize()); + tileDrawRegion.AndWith(compositeRegion); + + if (tileDrawRegion.IsEmpty()) { + continue; + } + + tileDrawRegion.ScaleRoundOut(resolution, resolution); + RenderTile(tile, aCompositor, aEffectChain, aOpacity, aTransform, + aSamplingFilter, aClipRect, tileDrawRegion, + tileOffset * resolution, aLayerBuffer.GetTileSize(), + gfx::Rect(visibleRect.X(), visibleRect.Y(), visibleRect.Width(), + visibleRect.Height()), + aGeometry); + + if (tile.mTextureHostOnWhite) { + componentAlphaDiagnostic = DiagnosticFlags::COMPONENT_ALPHA; + } + } + + gfx::Rect rect(visibleRect.X(), visibleRect.Y(), visibleRect.Width(), + visibleRect.Height()); + aCompositor->DrawDiagnostics( + DiagnosticFlags::CONTENT | componentAlphaDiagnostic, rect, aClipRect, + aTransform, mFlashCounter); +} + +void TiledContentHost::PrintInfo(std::stringstream& aStream, + const char* aPrefix) { + aStream << aPrefix; + aStream << nsPrintfCString("TiledContentHost (0x%p)", this).get(); + +#if defined(MOZ_DUMP_PAINTING) + if (StaticPrefs::layers_dump_texture()) { + nsAutoCString pfx(aPrefix); + pfx += " "; + + Dump(aStream, pfx.get(), false); + } +#endif +} + +void TiledContentHost::Dump(std::stringstream& aStream, const char* aPrefix, + bool aDumpHtml) { + mTiledBuffer.Dump( + aStream, aPrefix, aDumpHtml, + TextureDumpMode:: + DoNotCompress /* compression not supported on host side */); +} + +void TiledContentHost::AddAnimationInvalidation(nsIntRegion& aRegion) { + return mTiledBuffer.AddAnimationInvalidation(aRegion); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/composite/TiledContentHost.h b/gfx/layers/composite/TiledContentHost.h new file mode 100644 index 0000000000..fa754bff30 --- /dev/null +++ b/gfx/layers/composite/TiledContentHost.h @@ -0,0 +1,263 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_TILEDCONTENTHOST_H +#define GFX_TILEDCONTENTHOST_H + +#include // for uint16_t +#include // for FILE +#include // for swap +#include "ContentHost.h" // for ContentHost +#include "TiledLayerBuffer.h" // for TiledLayerBuffer, etc +#include "CompositableHost.h" +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/Attributes.h" // for override +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/gfx/MatrixFwd.h" // for Matrix4x4 +#include "mozilla/gfx/Point.h" // for Point +#include "mozilla/gfx/Rect.h" // for Rect +#include "mozilla/gfx/Types.h" // for SamplingFilter +#include "mozilla/layers/CompositorTypes.h" // for TextureInfo, etc +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor +#include "mozilla/layers/LayersTypes.h" // for LayerRenderState, etc +#include "mozilla/layers/TextureHost.h" // for TextureHost +#include "mozilla/layers/TextureClient.h" +#include "mozilla/mozalloc.h" // for operator delete +#include "nsRegion.h" // for nsIntRegion +#include "nscore.h" // for nsACString + +namespace mozilla { + +namespace layers { + +class Compositor; +class ISurfaceAllocator; +class Layer; +class ThebesBufferData; +class TextureReadLock; +struct EffectChain; + +class TileHost { + public: + // Constructs a placeholder TileHost. See the comments above + // TiledLayerBuffer for more information on what this is used for; + // essentially, this is a sentinel used to represent an invalid or blank + // tile. + TileHost() = default; + + // Constructs a TileHost from a TextureReadLock and TextureHost. + TileHost(TextureReadLock* aSharedLock, TextureHost* aTextureHost, + TextureHost* aTextureHostOnWhite, TextureSource* aSource, + TextureSource* aSourceOnWhite) + : mTextureHost(aTextureHost), + mTextureHostOnWhite(aTextureHostOnWhite), + mTextureSource(aSource), + mTextureSourceOnWhite(aSourceOnWhite) {} + + TileHost(const TileHost& o) { + mTextureHost = o.mTextureHost; + mTextureHostOnWhite = o.mTextureHostOnWhite; + mTextureSource = o.mTextureSource; + mTextureSourceOnWhite = o.mTextureSourceOnWhite; + mTileCoord = o.mTileCoord; + } + TileHost& operator=(const TileHost& o) { + if (this == &o) { + return *this; + } + mTextureHost = o.mTextureHost; + mTextureHostOnWhite = o.mTextureHostOnWhite; + mTextureSource = o.mTextureSource; + mTextureSourceOnWhite = o.mTextureSourceOnWhite; + mTileCoord = o.mTileCoord; + return *this; + } + + bool operator==(const TileHost& o) const { + return mTextureHost == o.mTextureHost; + } + bool operator!=(const TileHost& o) const { + return mTextureHost != o.mTextureHost; + } + + bool IsPlaceholderTile() const { return mTextureHost == nullptr; } + + void Dump(std::stringstream& aStream) { + aStream << "TileHost(...)"; // fill in as needed + } + + void DumpTexture(std::stringstream& aStream, + TextureDumpMode /* aCompress, ignored for host tiles */) { + // TODO We should combine the OnWhite/OnBlack here an just output a single + // image. + CompositableHost::DumpTextureHost(aStream, mTextureHost); + } + + RefPtr AcquireTextureSource() const; + RefPtr AcquireTextureSourceOnWhite() const; + + /** + * This does a linear tween of the passed opacity (which is assumed + * to be between 0.0 and 1.0). The duration of the fade is controlled + * by the 'layers.tiles.fade-in.duration-ms' preference. It is enabled + * via 'layers.tiles.fade-in.enabled' + */ + float GetFadeInOpacity(float aOpacity); + + CompositableTextureHostRef mTextureHost; + CompositableTextureHostRef mTextureHostOnWhite; + mutable CompositableTextureSourceRef mTextureSource; + mutable CompositableTextureSourceRef mTextureSourceOnWhite; + // This is not strictly necessary but makes debugging whole lot easier. + TileCoordIntPoint mTileCoord; + TimeStamp mFadeStart; +}; + +class TiledLayerBufferComposite + : public TiledLayerBuffer { + friend class TiledLayerBuffer; + + public: + TiledLayerBufferComposite(); + ~TiledLayerBufferComposite(); + + bool UseTiles(const SurfaceDescriptorTiles& aTileDescriptors, + HostLayerManager* aLayerManager, ISurfaceAllocator* aAllocator); + + void Clear(); + + TileHost GetPlaceholderTile() const { return TileHost(); } + + // Stores the absolute resolution of the containing frame, calculated + // by the sum of the resolutions of all parent layers' FrameMetrics. + const CSSToParentLayerScale2D& GetFrameResolution() { + return mFrameResolution; + } + + void SetTextureSourceProvider(TextureSourceProvider* aProvider); + + void AddAnimationInvalidation(nsIntRegion& aRegion); + + protected: + CSSToParentLayerScale2D mFrameResolution; +}; + +/** + * ContentHost for tiled PaintedLayers. Since tiled layers are special snow + * flakes, we have a unique update process. All the textures that back the + * tiles are added in the usual way, but Updated is called on the host side + * in response to a message that describes the transaction for every tile. + * Composition happens in the normal way. + * + * TiledContentHost has a TiledLayerBufferComposite which keeps hold of the + * tiles. Each tile has a reference to a texture host. During the layers + * transaction, we receive a list of descriptors for the client-side tile buffer + * tiles (UseTiledLayerBuffer). If we receive two transactions before a + * composition, we immediately unlock and discard the unused buffer. + * + * When the content host is composited, we first validate the TiledLayerBuffer + * (Upload), which calls Updated on each tile's texture host to make sure the + * texture data has been uploaded. For single-buffered tiles, we unlock at this + * point, for double-buffered tiles we unlock and discard the last composited + * buffer after compositing a new one. Rendering takes us to RenderTile which + * is similar to Composite for non-tiled ContentHosts. + */ +class TiledContentHost : public ContentHost { + public: + explicit TiledContentHost(const TextureInfo& aTextureInfo); + + protected: + virtual ~TiledContentHost(); + + public: + // Generate effect for layerscope when using hwc. + virtual already_AddRefed GenEffect( + const gfx::SamplingFilter aSamplingFilter) override; + + virtual bool UpdateThebes(const ThebesBufferData& aData, + const nsIntRegion& aUpdated, + const nsIntRegion& aOldValidRegionBack) override { + NS_ERROR("N/A for tiled layers"); + return false; + } + + const nsIntRegion& GetValidLowPrecisionRegion() const { + return mLowPrecisionTiledBuffer.GetValidRegion(); + } + + const nsIntRegion& GetValidRegion() const { + return mTiledBuffer.GetValidRegion(); + } + + void SetTextureSourceProvider(TextureSourceProvider* aProvider) override { + CompositableHost::SetTextureSourceProvider(aProvider); + mTiledBuffer.SetTextureSourceProvider(aProvider); + mLowPrecisionTiledBuffer.SetTextureSourceProvider(aProvider); + } + + bool UseTiledLayerBuffer(ISurfaceAllocator* aAllocator, + const SurfaceDescriptorTiles& aTiledDescriptor); + + void Composite(Compositor* aCompositor, LayerComposite* aLayer, + EffectChain& aEffectChain, float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::SamplingFilter aSamplingFilter, + const gfx::IntRect& aClipRect, + const nsIntRegion* aVisibleRegion = nullptr, + const Maybe& aGeometry = Nothing()) override; + + CompositableType GetType() override { + return CompositableType::CONTENT_TILED; + } + + TiledContentHost* AsTiledContentHost() override { return this; } + + void Attach(Layer* aLayer, TextureSourceProvider* aProvider, + AttachFlags aFlags = NO_FLAGS) override; + + void Detach(Layer* aLayer = nullptr, AttachFlags aFlags = NO_FLAGS) override; + + void Dump(std::stringstream& aStream, const char* aPrefix = "", + bool aDumpHtml = false) override; + + void PrintInfo(std::stringstream& aStream, const char* aPrefix) override; + + void AddAnimationInvalidation(nsIntRegion& aRegion) override; + + TiledLayerBufferComposite& GetLowResBuffer() { + return mLowPrecisionTiledBuffer; + } + TiledLayerBufferComposite& GetHighResBuffer() { return mTiledBuffer; } + + private: + void RenderLayerBuffer(TiledLayerBufferComposite& aLayerBuffer, + Compositor* aCompositor, + const gfx::DeviceColor* aBackgroundColor, + EffectChain& aEffectChain, float aOpacity, + const gfx::SamplingFilter aSamplingFilter, + const gfx::IntRect& aClipRect, nsIntRegion aMaskRegion, + gfx::Matrix4x4 aTransform, + const Maybe& aGeometry); + + // Renders a single given tile. + void RenderTile( + TileHost& aTile, Compositor* aCompositor, EffectChain& aEffectChain, + float aOpacity, const gfx::Matrix4x4& aTransform, + const gfx::SamplingFilter aSamplingFilter, const gfx::IntRect& aClipRect, + const nsIntRegion& aScreenRegion, const gfx::IntPoint& aTextureOffset, + const gfx::IntSize& aTextureBounds, const gfx::Rect& aVisibleRect, + const Maybe& aGeometry); + + void EnsureTileStore() {} + + TiledLayerBufferComposite mTiledBuffer; + TiledLayerBufferComposite mLowPrecisionTiledBuffer; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/composite/X11TextureHost.cpp b/gfx/layers/composite/X11TextureHost.cpp new file mode 100644 index 0000000000..5bbb7a82c8 --- /dev/null +++ b/gfx/layers/composite/X11TextureHost.cpp @@ -0,0 +1,103 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "X11TextureHost.h" + +#include "mozilla/layers/BasicCompositor.h" +#include "mozilla/layers/CompositorOGL.h" +#include "mozilla/layers/X11TextureSourceBasic.h" +#include "mozilla/layers/X11TextureSourceOGL.h" +#include "gfx2DGlue.h" +#include "gfxPlatform.h" +#include "gfxXlibSurface.h" + +namespace mozilla::layers { + +using namespace mozilla::gfx; + +X11TextureHost::X11TextureHost(TextureFlags aFlags, + const SurfaceDescriptorX11& aDescriptor) + : TextureHost(aFlags) { + mSurface = aDescriptor.OpenForeign(); + + if (mSurface && !(aFlags & TextureFlags::DEALLOCATE_CLIENT)) { + mSurface->TakePixmap(); + } +} + +bool X11TextureHost::Lock() { + if (!mCompositor || !mSurface) { + return false; + } + + if (!mTextureSource) { + switch (mCompositor->GetBackendType()) { + case LayersBackend::LAYERS_BASIC: + mTextureSource = new X11TextureSourceBasic( + mCompositor->AsBasicCompositor(), mSurface); + break; + case LayersBackend::LAYERS_OPENGL: + mTextureSource = + new X11TextureSourceOGL(mCompositor->AsCompositorOGL(), mSurface); + break; + default: + return false; + } + } + + return true; +} + +void X11TextureHost::SetTextureSourceProvider( + TextureSourceProvider* aProvider) { + mProvider = aProvider; + if (mProvider) { + mCompositor = mProvider->AsCompositor(); + } else { + mCompositor = nullptr; + } + if (mTextureSource) { + mTextureSource->SetTextureSourceProvider(aProvider); + } +} + +SurfaceFormat X11TextureHost::GetFormat() const { + if (!mSurface) { + return SurfaceFormat::UNKNOWN; + } + gfxContentType type = mSurface->GetContentType(); + if (mCompositor->GetBackendType() == LayersBackend::LAYERS_OPENGL) { + return X11TextureSourceOGL::ContentTypeToSurfaceFormat(type); + } + return X11TextureSourceBasic::ContentTypeToSurfaceFormat(type); +} + +IntSize X11TextureHost::GetSize() const { + if (!mSurface) { + return IntSize(); + } + return mSurface->GetSize(); +} + +already_AddRefed X11TextureHost::GetAsSurface() { + if (!mTextureSource || !mTextureSource->AsSourceBasic()) { + return nullptr; + } + RefPtr tempDT = + gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(GetSize(), + GetFormat()); + if (!tempDT) { + return nullptr; + } + RefPtr surf = + mTextureSource->AsSourceBasic()->GetSurface(tempDT); + if (!surf) { + return nullptr; + } + return surf->GetDataSurface(); +} + +} // namespace mozilla::layers diff --git a/gfx/layers/composite/X11TextureHost.h b/gfx/layers/composite/X11TextureHost.h new file mode 100644 index 0000000000..568e136ff1 --- /dev/null +++ b/gfx/layers/composite/X11TextureHost.h @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_X11TEXTUREHOST__H +#define MOZILLA_GFX_X11TEXTUREHOST__H + +#include "mozilla/layers/TextureHost.h" +#include "mozilla/layers/LayersSurfaces.h" +#include "mozilla/gfx/Types.h" + +#include "gfxXlibSurface.h" + +namespace mozilla { +namespace layers { + +class X11TextureSource : public TextureSource { + public: + // Called when the underlying X surface has been changed. + // Useful for determining whether to rebind a GLXPixmap to a texture. + virtual void Updated() = 0; + + const char* Name() const override { return "X11TextureSource"; } +}; + +// TextureHost for Xlib-backed TextureSources. +class X11TextureHost : public TextureHost { + public: + X11TextureHost(TextureFlags aFlags, const SurfaceDescriptorX11& aDescriptor); + + void SetTextureSourceProvider(TextureSourceProvider* aProvider) override; + + bool Lock() override; + + gfx::SurfaceFormat GetFormat() const override; + + gfx::IntSize GetSize() const override; + + bool BindTextureSource(CompositableTextureSourceRef& aTexture) override { + aTexture = mTextureSource; + return !!aTexture; + } + + already_AddRefed GetAsSurface() override; + +#ifdef MOZ_LAYERS_HAVE_LOG + const char* Name() override { return "X11TextureHost"; } +#endif + + protected: + void UpdatedInternal(const nsIntRegion*) override { + if (mTextureSource) mTextureSource->Updated(); + } + + RefPtr mCompositor; + RefPtr mTextureSource; + RefPtr mSurface; +}; + +} // namespace layers +} // namespace mozilla + +#endif // MOZILLA_GFX_X11TEXTUREHOST__H diff --git a/gfx/layers/d3d11/BlendShaderConstants.h b/gfx/layers/d3d11/BlendShaderConstants.h new file mode 100644 index 0000000000..84b2c68a4a --- /dev/null +++ b/gfx/layers/d3d11/BlendShaderConstants.h @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_LAYERS_D3D11_BLENDSHADERCONSTANTS_H_ +#define MOZILLA_GFX_LAYERS_D3D11_BLENDSHADERCONSTANTS_H_ + +// These constants are shared between CompositorD3D11 and the blend pixel +// shader. +#define PS_LAYER_RGB 0 +#define PS_LAYER_RGBA 1 +#define PS_LAYER_YCBCR 2 +#define PS_LAYER_COLOR 3 +#define PS_LAYER_NV12 4 + +// These must be in the same order as the Mask enum. +#define PS_MASK_NONE 0 +#define PS_MASK 1 + +// These must be in the same order as CompositionOp. +#define PS_BLEND_MULTIPLY 0 +#define PS_BLEND_SCREEN 1 +#define PS_BLEND_OVERLAY 2 +#define PS_BLEND_DARKEN 3 +#define PS_BLEND_LIGHTEN 4 +#define PS_BLEND_COLOR_DODGE 5 +#define PS_BLEND_COLOR_BURN 6 +#define PS_BLEND_HARD_LIGHT 7 +#define PS_BLEND_SOFT_LIGHT 8 +#define PS_BLEND_DIFFERENCE 9 +#define PS_BLEND_EXCLUSION 10 +#define PS_BLEND_HUE 11 +#define PS_BLEND_SATURATION 12 +#define PS_BLEND_COLOR 13 +#define PS_BLEND_LUMINOSITY 14 + +#if defined(__cplusplus) +namespace mozilla { +namespace layers { + +static inline int BlendOpToShaderConstant(gfx::CompositionOp aOp) { + return int(aOp) - int(gfx::CompositionOp::OP_MULTIPLY); +} + +} // namespace layers +} // namespace mozilla + +// Sanity checks. +namespace { +static inline void BlendShaderConstantAsserts() { + static_assert(PS_MASK_NONE == int(mozilla::layers::MaskType::MaskNone), + "shader constant is out of sync"); + static_assert(PS_MASK == int(mozilla::layers::MaskType::Mask), + "shader constant is out of sync"); + static_assert(int(mozilla::gfx::CompositionOp::OP_LUMINOSITY) - + int(mozilla::gfx::CompositionOp::OP_MULTIPLY) == + 14, + "shader constants are out of sync"); +} +} // anonymous namespace +#endif + +#endif // MOZILLA_GFX_LAYERS_D3D11_BLENDSHADERCONSTANTS_H_ diff --git a/gfx/layers/d3d11/BlendingHelpers.hlslh b/gfx/layers/d3d11/BlendingHelpers.hlslh new file mode 100644 index 0000000000..57d27b23b2 --- /dev/null +++ b/gfx/layers/d3d11/BlendingHelpers.hlslh @@ -0,0 +1,184 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Helper functions. +float hardlight(float dest, float src) { + if (src <= 0.5) { + return dest * (2.0 * src); + } else { + // Note: we substitute (2*src-1) into the screen formula below. + return 2.0 * dest + 2.0 * src - 1.0 - 2.0 * dest * src; + } +} + +float dodge(float dest, float src) { + if (dest == 0.0) { + return 0.0; + } else if (src == 1.0) { + return 1.0; + } else { + return min(1.0, dest / (1.0 - src)); + } +} + +float burn(float dest, float src) { + if (dest == 1.0) { + return 1.0; + } else if (src == 0.0) { + return 0.0; + } else { + return 1.0 - min(1.0, (1.0 - dest) / src); + } +} + +float darken(float dest) { + if (dest <= 0.25) { + return ((16.0 * dest - 12.0) * dest + 4.0) * dest; + } else { + return sqrt(dest); + } +} + +float softlight(float dest, float src) { + if (src <= 0.5) { + return dest - (1.0 - 2.0 * src) * dest * (1.0 - dest); + } else { + return dest + (2.0 * src - 1.0) * (darken(dest) - dest); + } +} + +float Lum(float3 c) { + return dot(float3(0.3, 0.59, 0.11), c); +} + +float3 ClipColor(float3 c) { + float L = Lum(c); + float n = min(min(c.r, c.g), c.b); + float x = max(max(c.r, c.g), c.b); + if (n < 0.0) { + c = L + (((c - L) * L) / (L - n)); + } + if (x > 1.0) { + c = L + (((c - L) * (1.0 - L)) / (x - L)); + } + return c; +} + +float3 SetLum(float3 c, float L) { + float d = L - Lum(c); + return ClipColor(float3( + c.r + d, + c.g + d, + c.b + d)); +} + +float Sat(float3 c) { + return max(max(c.r, c.g), c.b) - min(min(c.r, c.g), c.b); +} + +// To use this helper, re-arrange rgb such that r=min, g=mid, and b=max. +float3 SetSatInner(float3 c, float s) { + if (c.b > c.r) { + c.g = (((c.g - c.r) * s) / (c.b - c.r)); + c.b = s; + } else { + c.gb = float2(0.0, 0.0); + } + return float3(0.0, c.g, c.b); +} + +float3 SetSat(float3 c, float s) { + if (c.r <= c.g) { + if (c.g <= c.b) { + c.rgb = SetSatInner(c.rgb, s); + } else if (c.r <= c.b) { + c.rbg = SetSatInner(c.rbg, s); + } else { + c.brg = SetSatInner(c.brg, s); + } + } else if (c.r <= c.b) { + c.grb = SetSatInner(c.grb, s); + } else if (c.g <= c.b) { + c.gbr = SetSatInner(c.gbr, s); + } else { + c.bgr = SetSatInner(c.bgr, s); + } + return c; +} + +float3 BlendMultiply(float3 dest, float3 src) { + return dest * src; +} + +float3 BlendScreen(float3 dest, float3 src) { + return dest + src - (dest * src); +} + +float3 BlendOverlay(float3 dest, float3 src) { + return float3( + hardlight(src.r, dest.r), + hardlight(src.g, dest.g), + hardlight(src.b, dest.b)); +} + +float3 BlendDarken(float3 dest, float3 src) { + return min(dest, src); +} + +float3 BlendLighten(float3 dest, float3 src) { + return max(dest, src); +} + +float3 BlendColorDodge(float3 dest, float3 src) { + return float3( + dodge(dest.r, src.r), + dodge(dest.g, src.g), + dodge(dest.b, src.b)); +} + +float3 BlendColorBurn(float3 dest, float3 src) { + return float3( + burn(dest.r, src.r), + burn(dest.g, src.g), + burn(dest.b, src.b)); +} + +float3 BlendHardLight(float3 dest, float3 src) { + return float3( + hardlight(dest.r, src.r), + hardlight(dest.g, src.g), + hardlight(dest.b, src.b)); +} + +float3 BlendSoftLight(float3 dest, float3 src) { + return float3( + softlight(dest.r, src.r), + softlight(dest.g, src.g), + softlight(dest.b, src.b)); +} + +float3 BlendDifference(float3 dest, float3 src) { + return abs(dest - src); +} + +float3 BlendExclusion(float3 dest, float3 src) { + return dest + src - 2.0 * dest * src; +} + +float3 BlendHue(float3 dest, float3 src) { + return SetLum(SetSat(src, Sat(dest)), Lum(dest)); +} + +float3 BlendSaturation(float3 dest, float3 src) { + return SetLum(SetSat(dest, Sat(src)), Lum(dest)); +} + +float3 BlendColor(float3 dest, float3 src) { + return SetLum(src, Lum(dest)); +} + +float3 BlendLuminosity(float3 dest, float3 src) { + return SetLum(dest, Lum(src)); +} diff --git a/gfx/layers/d3d11/CompositorD3D11.cpp b/gfx/layers/d3d11/CompositorD3D11.cpp new file mode 100644 index 0000000000..69b7617112 --- /dev/null +++ b/gfx/layers/d3d11/CompositorD3D11.cpp @@ -0,0 +1,1794 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "CompositorD3D11.h" + +#include "TextureD3D11.h" + +#include "gfxWindowsPlatform.h" +#include "nsIWidget.h" +#include "Layers.h" +#include "mozilla/gfx/D3D11Checks.h" +#include "mozilla/gfx/DeviceManagerDx.h" +#include "mozilla/gfx/GPUParent.h" +#include "mozilla/gfx/Swizzle.h" +#include "mozilla/layers/ImageHost.h" +#include "mozilla/layers/ContentHost.h" +#include "mozilla/layers/Diagnostics.h" +#include "mozilla/layers/DiagnosticsD3D11.h" +#include "mozilla/layers/Effects.h" +#include "mozilla/layers/HelpersD3D11.h" +#include "nsWindowsHelpers.h" +#include "gfxConfig.h" +#include "gfxCrashReporterUtils.h" +#include "gfxUtils.h" +#include "mozilla/gfx/StackArray.h" +#include "mozilla/widget/WinCompositorWidget.h" + +#include "mozilla/EnumeratedArray.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/Telemetry.h" +#include "BlendShaderConstants.h" + +#include "D3D11ShareHandleImage.h" +#include "DeviceAttachmentsD3D11.h" + +#include // For IsWindows8OrGreater +#include + +namespace mozilla { + +using namespace gfx; + +namespace layers { + +bool CanUsePartialPresents(ID3D11Device* aDevice); + +const FLOAT sBlendFactor[] = {0, 0, 0, 0}; + +class AsyncReadbackBufferD3D11 final : public AsyncReadbackBuffer { + public: + AsyncReadbackBufferD3D11(ID3D11DeviceContext* aContext, + ID3D11Texture2D* aTexture, const IntSize& aSize); + + bool MapAndCopyInto(DataSourceSurface* aSurface, + const IntSize& aReadSize) const override; + + ID3D11Texture2D* GetTexture() { return mTexture; } + + private: + RefPtr mContext; + RefPtr mTexture; +}; + +AsyncReadbackBufferD3D11::AsyncReadbackBufferD3D11( + ID3D11DeviceContext* aContext, ID3D11Texture2D* aTexture, + const IntSize& aSize) + : AsyncReadbackBuffer(aSize), mContext(aContext), mTexture(aTexture) {} + +bool AsyncReadbackBufferD3D11::MapAndCopyInto(DataSourceSurface* aSurface, + const IntSize& aReadSize) const { + D3D11_MAPPED_SUBRESOURCE map; + HRESULT hr = mContext->Map(mTexture, 0, D3D11_MAP_READ, 0, &map); + + if (FAILED(hr)) { + return false; + } + + RefPtr sourceSurface = + Factory::CreateWrappingDataSourceSurface(static_cast(map.pData), + map.RowPitch, mSize, + SurfaceFormat::B8G8R8A8); + + bool result; + { + DataSourceSurface::ScopedMap sourceMap(sourceSurface, + DataSourceSurface::READ); + DataSourceSurface::ScopedMap destMap(aSurface, DataSourceSurface::WRITE); + + result = SwizzleData(sourceMap.GetData(), sourceMap.GetStride(), + SurfaceFormat::B8G8R8A8, destMap.GetData(), + destMap.GetStride(), aSurface->GetFormat(), aReadSize); + } + + mContext->Unmap(mTexture, 0); + + return result; +} + +CompositorD3D11::CompositorD3D11(CompositorBridgeParent* aParent, + widget::CompositorWidget* aWidget) + : Compositor(aWidget, aParent), + mWindowRTCopy(nullptr), + mAttachments(nullptr), + mHwnd(nullptr), + mDisableSequenceForNextFrame(false), + mAllowPartialPresents(false), + mIsDoubleBuffered(false), + mVerifyBuffersFailed(false), + mUseMutexOnPresent(false), + mUseForSoftwareWebRender(false) { + mUseMutexOnPresent = StaticPrefs::gfx_use_mutex_on_present_AtStartup(); +} + +CompositorD3D11::~CompositorD3D11() {} + +template +void CompositorD3D11::SetVertexBuffer(ID3D11Buffer* aBuffer) { + UINT size = sizeof(VertexType); + UINT offset = 0; + mContext->IASetVertexBuffers(0, 1, &aBuffer, &size, &offset); +} + +bool CompositorD3D11::SupportsLayerGeometry() const { + return StaticPrefs::layers_geometry_d3d11_enabled(); +} + +bool CompositorD3D11::UpdateDynamicVertexBuffer( + const nsTArray& aTriangles) { + HRESULT hr; + + // Resize the dynamic vertex buffer if needed. + if (!mAttachments->EnsureTriangleBuffer(aTriangles.Length())) { + return false; + } + + D3D11_MAPPED_SUBRESOURCE resource{}; + hr = mContext->Map(mAttachments->mDynamicVertexBuffer, 0, + D3D11_MAP_WRITE_DISCARD, 0, &resource); + + if (Failed(hr, "map dynamic vertex buffer")) { + return false; + } + + const nsTArray vertices = + TexturedTrianglesToVertexArray(aTriangles); + + memcpy(resource.pData, vertices.Elements(), + vertices.Length() * sizeof(TexturedVertex)); + + mContext->Unmap(mAttachments->mDynamicVertexBuffer, 0); + + return true; +} + +bool CompositorD3D11::Initialize(nsCString* const out_failureReason) { + ScopedGfxFeatureReporter reporter("D3D11 Layers"); + + HRESULT hr; + + DeviceManagerDx::Get()->GetCompositorDevices(&mDevice, &mAttachments); + if (!mDevice) { + gfxCriticalNote << "[D3D11] failed to get compositor device."; + *out_failureReason = "FEATURE_FAILURE_D3D11_NO_DEVICE"; + return false; + } + if (!mAttachments || !mAttachments->IsValid()) { + gfxCriticalNote << "[D3D11] failed to get compositor device attachments"; + *out_failureReason = mAttachments ? mAttachments->GetFailureId() + : "FEATURE_FAILURE_NO_ATTACHMENTS"_ns; + return false; + } + + mDevice->GetImmediateContext(getter_AddRefs(mContext)); + if (!mContext) { + gfxCriticalNote << "[D3D11] failed to get immediate context"; + *out_failureReason = "FEATURE_FAILURE_D3D11_CONTEXT"; + return false; + } + + mDiagnostics = MakeUnique(mDevice, mContext); + mFeatureLevel = mDevice->GetFeatureLevel(); + + mHwnd = mWidget->AsWindows()->GetHwnd(); + + memset(&mVSConstants, 0, sizeof(VertexShaderConstants)); + + RefPtr dxgiDevice; + RefPtr dxgiAdapter; + + mDevice->QueryInterface(dxgiDevice.StartAssignment()); + dxgiDevice->GetAdapter(getter_AddRefs(dxgiAdapter)); + + { + RefPtr dxgiFactory; + dxgiAdapter->GetParent(IID_PPV_ARGS(dxgiFactory.StartAssignment())); + + RefPtr dxgiFactory2; + hr = dxgiFactory->QueryInterface( + (IDXGIFactory2**)getter_AddRefs(dxgiFactory2)); + +#if (_WIN32_WINDOWS_MAXVER >= 0x0A00) + if (gfxVars::UseDoubleBufferingWithCompositor() && SUCCEEDED(hr) && + dxgiFactory2) { + // DXGI_SCALING_NONE is not available on Windows 7 with Platform Update. + // This looks awful for things like the awesome bar and browser window + // resizing so we don't use a flip buffer chain here. When using + // EFFECT_SEQUENTIAL it looks like windows doesn't stretch the surface + // when resizing. We chose not to run this before Windows 10 because it + // appears sometimes this breaks our ability to test ASAP compositing. + RefPtr swapChain; + + DXGI_SWAP_CHAIN_DESC1 swapDesc; + ::ZeroMemory(&swapDesc, sizeof(swapDesc)); + swapDesc.Width = 0; + swapDesc.Height = 0; + swapDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + swapDesc.SampleDesc.Count = 1; + swapDesc.SampleDesc.Quality = 0; + swapDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + swapDesc.BufferCount = 2; + swapDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; + swapDesc.Scaling = DXGI_SCALING_NONE; + mIsDoubleBuffered = true; + swapDesc.Flags = 0; + + /** + * Create a swap chain, this swap chain will contain the backbuffer for + * the window we draw to. The front buffer is the full screen front + * buffer. + */ + hr = dxgiFactory2->CreateSwapChainForHwnd(mDevice, mHwnd, &swapDesc, + nullptr, nullptr, + getter_AddRefs(swapChain)); + if (SUCCEEDED(hr)) { + DXGI_RGBA color = {1.0f, 1.0f, 1.0f, 1.0f}; + swapChain->SetBackgroundColor(&color); + + mSwapChain = swapChain; + } else if (mWidget->AsWindows()->GetCompositorHwnd()) { + // Destroy compositor window. + mWidget->AsWindows()->DestroyCompositorWindow(); + mHwnd = mWidget->AsWindows()->GetHwnd(); + } + } + + // In some configurations double buffering may have failed with an + // ACCESS_DENIED error. + if (!mSwapChain) +#endif + { + if (mWidget->AsWindows()->GetCompositorHwnd()) { + // Destroy compositor window. + mWidget->AsWindows()->DestroyCompositorWindow(); + mHwnd = mWidget->AsWindows()->GetHwnd(); + } + + DXGI_SWAP_CHAIN_DESC swapDesc; + ::ZeroMemory(&swapDesc, sizeof(swapDesc)); + swapDesc.BufferDesc.Width = 0; + swapDesc.BufferDesc.Height = 0; + swapDesc.BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + swapDesc.BufferDesc.RefreshRate.Numerator = 60; + swapDesc.BufferDesc.RefreshRate.Denominator = 1; + swapDesc.SampleDesc.Count = 1; + swapDesc.SampleDesc.Quality = 0; + swapDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + swapDesc.BufferCount = 1; + swapDesc.OutputWindow = mHwnd; + swapDesc.Windowed = TRUE; + swapDesc.Flags = 0; + swapDesc.SwapEffect = DXGI_SWAP_EFFECT_SEQUENTIAL; + + /** + * Create a swap chain, this swap chain will contain the backbuffer for + * the window we draw to. The front buffer is the full screen front + * buffer. + */ + hr = dxgiFactory->CreateSwapChain(dxgiDevice, &swapDesc, + getter_AddRefs(mSwapChain)); + if (Failed(hr, "create swap chain")) { + *out_failureReason = "FEATURE_FAILURE_D3D11_SWAP_CHAIN"; + return false; + } + } + + // We need this because we don't want DXGI to respond to Alt+Enter. + dxgiFactory->MakeWindowAssociation(mHwnd, DXGI_MWA_NO_WINDOW_CHANGES); + } + + if (!mWidget->InitCompositor(this)) { + *out_failureReason = "FEATURE_FAILURE_D3D11_INIT_COMPOSITOR"; + return false; + } + + mAllowPartialPresents = CanUsePartialPresents(mDevice); + + reporter.SetSuccessful(); + return true; +} + +bool CanUsePartialPresents(ID3D11Device* aDevice) { + if (StaticPrefs::gfx_partialpresent_force() > 0) { + return true; + } + if (StaticPrefs::gfx_partialpresent_force() < 0) { + return false; + } + if (DeviceManagerDx::Get()->IsWARP()) { + return true; + } + + DXGI_ADAPTER_DESC desc; + if (!D3D11Checks::GetDxgiDesc(aDevice, &desc)) { + return false; + } + + // We have to disable partial presents on NVIDIA (bug 1189940). + if (desc.VendorId == 0x10de) { + return false; + } + + return true; +} + +already_AddRefed CompositorD3D11::CreateDataTextureSource( + TextureFlags aFlags) { + RefPtr result = + new DataTextureSourceD3D11(gfx::SurfaceFormat::UNKNOWN, this, aFlags); + return result.forget(); +} + +TextureFactoryIdentifier CompositorD3D11::GetTextureFactoryIdentifier() { + TextureFactoryIdentifier ident; + ident.mMaxTextureSize = GetMaxTextureSize(); + ident.mParentProcessType = XRE_GetProcessType(); + ident.mParentBackend = LayersBackend::LAYERS_D3D11; + if (mWidget) { + ident.mUseCompositorWnd = !!mWidget->AsWindows()->GetCompositorHwnd(); + } + if (mAttachments->mSyncObject) { + ident.mSyncHandle = mAttachments->mSyncObject->GetSyncHandle(); + } + return ident; +} + +bool CompositorD3D11::CanUseCanvasLayerForSize(const gfx::IntSize& aSize) { + int32_t maxTextureSize = GetMaxTextureSize(); + + if (aSize.width > maxTextureSize || aSize.height > maxTextureSize) { + return false; + } + + return true; +} + +int32_t CompositorD3D11::GetMaxTextureSize() const { + return GetMaxTextureSizeForFeatureLevel(mFeatureLevel); +} + +already_AddRefed CompositorD3D11::CreateRenderTarget( + const gfx::IntRect& aRect, SurfaceInitMode aInit) { + MOZ_ASSERT(!aRect.IsZeroArea()); + + if (aRect.IsZeroArea()) { + return nullptr; + } + + CD3D11_TEXTURE2D_DESC desc( + DXGI_FORMAT_B8G8R8A8_UNORM, aRect.width, aRect.height, 1, 1, + D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET); + + RefPtr texture; + HRESULT hr = + mDevice->CreateTexture2D(&desc, nullptr, getter_AddRefs(texture)); + if (FAILED(hr) || !texture) { + gfxCriticalNote << "Failed in CreateRenderTarget " << hexa(hr); + return nullptr; + } + + RefPtr rt = + new CompositingRenderTargetD3D11(texture, aRect.TopLeft()); + rt->SetSize(IntSize(aRect.Width(), aRect.Height())); + + if (aInit == INIT_MODE_CLEAR) { + FLOAT clear[] = {0, 0, 0, 0}; + mContext->ClearRenderTargetView(rt->mRTView, clear); + } + + return rt.forget(); +} + +RefPtr CompositorD3D11::CreateTexture( + const gfx::IntRect& aRect, const CompositingRenderTarget* aSource, + const gfx::IntPoint& aSourcePoint) { + MOZ_ASSERT(!aRect.IsZeroArea()); + + if (aRect.IsZeroArea()) { + return nullptr; + } + + CD3D11_TEXTURE2D_DESC desc( + DXGI_FORMAT_B8G8R8A8_UNORM, aRect.Width(), aRect.Height(), 1, 1, + D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET); + + RefPtr texture; + HRESULT hr = + mDevice->CreateTexture2D(&desc, nullptr, getter_AddRefs(texture)); + + if (FAILED(hr) || !texture) { + gfxCriticalNote << "Failed in CreateRenderTargetFromSource " << hexa(hr); + HandleError(hr); + return nullptr; + } + + if (aSource) { + const CompositingRenderTargetD3D11* sourceD3D11 = + static_cast(aSource); + + const IntSize& srcSize = sourceD3D11->GetSize(); + MOZ_ASSERT(srcSize.width >= 0 && srcSize.height >= 0, + "render targets should have nonnegative sizes"); + + IntRect srcRect(IntPoint(), srcSize); + IntRect copyRect(aSourcePoint, aRect.Size()); + if (!srcRect.Contains(copyRect)) { + NS_WARNING("Could not copy the whole copy rect from the render target"); + } + + copyRect = copyRect.Intersect(srcRect); + + if (!copyRect.IsEmpty()) { + D3D11_BOX copyBox; + copyBox.front = 0; + copyBox.back = 1; + copyBox.left = copyRect.X(); + copyBox.top = copyRect.Y(); + copyBox.right = copyRect.XMost(); + copyBox.bottom = copyRect.YMost(); + + mContext->CopySubresourceRegion( + texture, 0, 0, 0, 0, sourceD3D11->GetD3D11Texture(), 0, ©Box); + } + } + + return texture; +} + +already_AddRefed +CompositorD3D11::CreateRenderTargetFromSource( + const gfx::IntRect& aRect, const CompositingRenderTarget* aSource, + const gfx::IntPoint& aSourcePoint) { + RefPtr texture = CreateTexture(aRect, aSource, aSourcePoint); + if (!texture) { + return nullptr; + } + + RefPtr rt = + new CompositingRenderTargetD3D11(texture, aRect.TopLeft()); + rt->SetSize(aRect.Size()); + + return rt.forget(); +} + +bool CompositorD3D11::ShouldAllowFrameRecording() const { +#ifdef MOZ_GECKO_PROFILER + return mAllowFrameRecording || + profiler_feature_active(ProfilerFeature::Screenshots); +#else + return mAllowFrameRecording; +#endif +} + +already_AddRefed +CompositorD3D11::GetWindowRenderTarget() const { + if (!ShouldAllowFrameRecording()) { + return nullptr; + } + + if (!mDefaultRT) { + return nullptr; + } + + const IntSize size = mDefaultRT->GetSize(); + + RefPtr rtTexture; + + if (!mWindowRTCopy || mWindowRTCopy->GetSize() != size) { + /* + * The compositor screenshots infrastructure is going to scale down the + * render target returned by this method. However, mDefaultRT does not + * contain a texture created wth the D3D11_BIND_SHADER_RESOURCE flag, so if + * we were to simply return mDefaultRT then scaling would fail. + */ + CD3D11_TEXTURE2D_DESC desc(DXGI_FORMAT_B8G8R8A8_UNORM, size.width, + size.height, 1, 1, D3D11_BIND_SHADER_RESOURCE); + + HRESULT hr = + mDevice->CreateTexture2D(&desc, nullptr, getter_AddRefs(rtTexture)); + if (FAILED(hr)) { + return nullptr; + } + + mWindowRTCopy = MakeRefPtr( + rtTexture, IntPoint(0, 0), DXGI_FORMAT_B8G8R8A8_UNORM); + mWindowRTCopy->SetSize(size); + } else { + rtTexture = mWindowRTCopy->GetD3D11Texture(); + } + + const RefPtr sourceTexture = mDefaultRT->GetD3D11Texture(); + mContext->CopyResource(rtTexture, sourceTexture); + + return RefPtr( + static_cast(mWindowRTCopy)) + .forget(); +} + +bool CompositorD3D11::ReadbackRenderTarget(CompositingRenderTarget* aSource, + AsyncReadbackBuffer* aDest) { + RefPtr srcTexture = + static_cast(aSource); + RefPtr destBuffer = + static_cast(aDest); + + mContext->CopyResource(destBuffer->GetTexture(), + srcTexture->GetD3D11Texture()); + + return true; +} + +already_AddRefed +CompositorD3D11::CreateAsyncReadbackBuffer(const gfx::IntSize& aSize) { + RefPtr texture; + + CD3D11_TEXTURE2D_DESC desc(DXGI_FORMAT_B8G8R8A8_UNORM, aSize.width, + aSize.height, 1, 1, 0, D3D11_USAGE_STAGING, + D3D11_CPU_ACCESS_READ); + + HRESULT hr = + mDevice->CreateTexture2D(&desc, nullptr, getter_AddRefs(texture)); + + if (FAILED(hr)) { + HandleError(hr); + return nullptr; + } + + return MakeAndAddRef(mContext, texture, aSize); +} + +bool CompositorD3D11::BlitRenderTarget(CompositingRenderTarget* aSource, + const gfx::IntSize& aSourceSize, + const gfx::IntSize& aDestSize) { + RefPtr source = + static_cast(aSource); + + RefPtr texturedEffect = CreateTexturedEffect( + SurfaceFormat::B8G8R8A8, source, SamplingFilter::LINEAR, true); + texturedEffect->mTextureCoords = + Rect(0, 0, Float(aSourceSize.width) / Float(source->GetSize().width), + Float(aSourceSize.height) / Float(source->GetSize().height)); + + EffectChain effect; + effect.mPrimaryEffect = texturedEffect; + + const Float scaleX = Float(aDestSize.width) / Float(aSourceSize.width); + const Float scaleY = Float(aDestSize.height) / (aSourceSize.height); + const Matrix4x4 transform = Matrix4x4::Scaling(scaleX, scaleY, 1.0f); + + const Rect sourceRect(0, 0, aSourceSize.width, aSourceSize.height); + + DrawQuad(sourceRect, IntRect(0, 0, aDestSize.width, aDestSize.height), effect, + 1.0f, transform, sourceRect); + + return true; +} + +bool CompositorD3D11::CopyBackdrop(const gfx::IntRect& aRect, + RefPtr* aOutTexture, + RefPtr* aOutView) { + RefPtr texture = + CreateTexture(aRect, mCurrentRT, aRect.TopLeft()); + if (!texture) { + return false; + } + + CD3D11_SHADER_RESOURCE_VIEW_DESC desc(D3D11_SRV_DIMENSION_TEXTURE2D, + DXGI_FORMAT_B8G8R8A8_UNORM); + + RefPtr srv; + HRESULT hr = + mDevice->CreateShaderResourceView(texture, &desc, getter_AddRefs(srv)); + if (FAILED(hr) || !srv) { + return false; + } + + *aOutTexture = texture.forget(); + *aOutView = srv.forget(); + return true; +} + +void CompositorD3D11::SetRenderTarget(CompositingRenderTarget* aRenderTarget) { + MOZ_ASSERT(aRenderTarget); + CompositingRenderTargetD3D11* newRT = + static_cast(aRenderTarget); + if (mCurrentRT != newRT) { + mCurrentRT = newRT; + mCurrentRT->BindRenderTarget(mContext); + } + + if (newRT->HasComplexProjection()) { + gfx::Matrix4x4 projection; + bool depthEnable; + float zNear, zFar; + newRT->GetProjection(projection, depthEnable, zNear, zFar); + PrepareViewport(newRT->GetSize(), projection, zNear, zFar); + } else { + PrepareViewport(newRT->GetSize()); + } +} + +ID3D11PixelShader* CompositorD3D11::GetPSForEffect(Effect* aEffect, + const bool aUseBlendShader, + const MaskType aMaskType) { + if (aUseBlendShader) { + return mAttachments->mBlendShader[MaskType::MaskNone]; + } + + switch (aEffect->mType) { + case EffectTypes::SOLID_COLOR: + return mAttachments->mSolidColorShader[aMaskType]; + case EffectTypes::RENDER_TARGET: + return mAttachments->mRGBAShader[aMaskType]; + case EffectTypes::RGB: { + SurfaceFormat format = + static_cast(aEffect)->mTexture->GetFormat(); + return (format == SurfaceFormat::B8G8R8A8 || + format == SurfaceFormat::R8G8B8A8) + ? mAttachments->mRGBAShader[aMaskType] + : mAttachments->mRGBShader[aMaskType]; + } + case EffectTypes::NV12: + return mAttachments->mNV12Shader[aMaskType]; + case EffectTypes::YCBCR: + return mAttachments->mYCbCrShader[aMaskType]; + case EffectTypes::COMPONENT_ALPHA: + return mAttachments->mComponentAlphaShader[aMaskType]; + default: + NS_WARNING("No shader to load"); + return nullptr; + } +} + +void CompositorD3D11::ClearRect(const gfx::Rect& aRect) { + if (aRect.IsEmpty()) { + return; + } + + mContext->OMSetBlendState(mAttachments->mDisabledBlendState, sBlendFactor, + 0xFFFFFFFF); + + Matrix4x4 identity; + memcpy(&mVSConstants.layerTransform, &identity._11, 64); + + mVSConstants.layerQuad = aRect; + mVSConstants.renderTargetOffset[0] = 0; + mVSConstants.renderTargetOffset[1] = 0; + mPSConstants.layerOpacity[0] = 1.0f; + + D3D11_RECT scissor; + scissor.left = aRect.X(); + scissor.right = aRect.XMost(); + scissor.top = aRect.Y(); + scissor.bottom = aRect.YMost(); + mContext->RSSetScissorRects(1, &scissor); + mContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); + mContext->VSSetShader(mAttachments->mVSQuadShader[MaskType::MaskNone], + nullptr, 0); + + mContext->PSSetShader(mAttachments->mSolidColorShader[MaskType::MaskNone], + nullptr, 0); + mPSConstants.layerColor[0] = 0; + mPSConstants.layerColor[1] = 0; + mPSConstants.layerColor[2] = 0; + mPSConstants.layerColor[3] = 0; + + if (!UpdateConstantBuffers()) { + NS_WARNING("Failed to update shader constant buffers"); + return; + } + + mContext->Draw(4, 0); + + // Restore the default blend state. + mContext->OMSetBlendState(mAttachments->mPremulBlendState, sBlendFactor, + 0xFFFFFFFF); +} + +static inline bool EffectHasPremultipliedAlpha(Effect* aEffect) { + if (aEffect->mType == EffectTypes::RGB) { + return static_cast(aEffect)->mPremultiplied; + } + return true; +} + +static inline int EffectToBlendLayerType(Effect* aEffect) { + switch (aEffect->mType) { + case EffectTypes::SOLID_COLOR: + return PS_LAYER_COLOR; + case EffectTypes::RGB: { + gfx::SurfaceFormat format = + static_cast(aEffect)->mTexture->GetFormat(); + return (format == gfx::SurfaceFormat::B8G8R8A8 || + format == gfx::SurfaceFormat::R8G8B8A8) + ? PS_LAYER_RGBA + : PS_LAYER_RGB; + } + case EffectTypes::RENDER_TARGET: + return PS_LAYER_RGBA; + case EffectTypes::YCBCR: + return PS_LAYER_YCBCR; + case EffectTypes::NV12: + return PS_LAYER_NV12; + default: + MOZ_ASSERT_UNREACHABLE("blending not supported for this layer type"); + return 0; + } +} + +void CompositorD3D11::DrawQuad(const gfx::Rect& aRect, + const gfx::IntRect& aClipRect, + const EffectChain& aEffectChain, + gfx::Float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::Rect& aVisibleRect) { + DrawGeometry(aRect, aRect, aClipRect, aEffectChain, aOpacity, aTransform, + aVisibleRect); +} + +void CompositorD3D11::DrawTriangles( + const nsTArray& aTriangles, const gfx::Rect& aRect, + const gfx::IntRect& aClipRect, const EffectChain& aEffectChain, + gfx::Float aOpacity, const gfx::Matrix4x4& aTransform, + const gfx::Rect& aVisibleRect) { + DrawGeometry(aTriangles, aRect, aClipRect, aEffectChain, aOpacity, aTransform, + aVisibleRect); +} + +void CompositorD3D11::PrepareDynamicVertexBuffer() { + mContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + mContext->IASetInputLayout(mAttachments->mDynamicInputLayout); + SetVertexBuffer(mAttachments->mDynamicVertexBuffer); +} + +void CompositorD3D11::PrepareStaticVertexBuffer() { + mContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); + mContext->IASetInputLayout(mAttachments->mInputLayout); + SetVertexBuffer(mAttachments->mVertexBuffer); +} + +void CompositorD3D11::Draw(const nsTArray& aTriangles, + const gfx::Rect*) { + if (!UpdateConstantBuffers()) { + NS_WARNING("Failed to update shader constant buffers"); + return; + } + + PrepareDynamicVertexBuffer(); + + if (!UpdateDynamicVertexBuffer(aTriangles)) { + NS_WARNING("Failed to update shader dynamic buffers"); + return; + } + + mContext->Draw(3 * aTriangles.Length(), 0); + + PrepareStaticVertexBuffer(); +} + +void CompositorD3D11::Draw(const gfx::Rect& aRect, + const gfx::Rect* aTexCoords) { + Rect layerRects[4] = {aRect}; + Rect textureRects[4] = {}; + size_t rects = 1; + + if (aTexCoords) { + rects = DecomposeIntoNoRepeatRects(aRect, *aTexCoords, &layerRects, + &textureRects); + } + + for (size_t i = 0; i < rects; i++) { + mVSConstants.layerQuad = layerRects[i]; + mVSConstants.textureCoords = textureRects[i]; + + if (!UpdateConstantBuffers()) { + NS_WARNING("Failed to update shader constant buffers"); + break; + } + + mContext->Draw(4, 0); + } +} + +ID3D11VertexShader* CompositorD3D11::GetVSForGeometry( + const nsTArray& aTriangles, + const bool aUseBlendShaders, const MaskType aMaskType) { + return aUseBlendShaders ? mAttachments->mVSDynamicBlendShader[aMaskType] + : mAttachments->mVSDynamicShader[aMaskType]; +} + +ID3D11VertexShader* CompositorD3D11::GetVSForGeometry( + const gfx::Rect& aRect, const bool aUseBlendShaders, + const MaskType aMaskType) { + return aUseBlendShaders ? mAttachments->mVSQuadBlendShader[aMaskType] + : mAttachments->mVSQuadShader[aMaskType]; +} + +template +void CompositorD3D11::DrawGeometry(const Geometry& aGeometry, + const gfx::Rect& aRect, + const gfx::IntRect& aClipRect, + const EffectChain& aEffectChain, + gfx::Float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::Rect& aVisibleRect) { + if (mCurrentClip.IsEmpty()) { + return; + } + + MOZ_ASSERT(mCurrentRT, "No render target"); + + memcpy(&mVSConstants.layerTransform, &aTransform._11, 64); + IntPoint origin = mCurrentRT->GetOrigin(); + mVSConstants.renderTargetOffset[0] = origin.x; + mVSConstants.renderTargetOffset[1] = origin.y; + + mPSConstants.layerOpacity[0] = aOpacity; + + bool restoreBlendMode = false; + + MaskType maskType = MaskType::MaskNone; + + if (aEffectChain.mSecondaryEffects[EffectTypes::MASK]) { + maskType = MaskType::Mask; + + EffectMask* maskEffect = static_cast( + aEffectChain.mSecondaryEffects[EffectTypes::MASK].get()); + TextureSourceD3D11* source = maskEffect->mMaskTexture->AsSourceD3D11(); + + if (!source) { + NS_WARNING("Missing texture source!"); + return; + } + + ID3D11ShaderResourceView* srView = source->GetShaderResourceView(); + mContext->PSSetShaderResources(TexSlot::Mask, 1, &srView); + + const gfx::Matrix4x4& maskTransform = maskEffect->mMaskTransform; + NS_ASSERTION(maskTransform.Is2D(), + "How did we end up with a 3D transform here?!"); + Rect bounds = Rect(Point(), Size(maskEffect->mSize)); + bounds = maskTransform.As2D().TransformBounds(bounds); + + Matrix4x4 transform; + transform._11 = 1.0f / bounds.Width(); + transform._22 = 1.0f / bounds.Height(); + transform._41 = float(-bounds.X()) / bounds.Width(); + transform._42 = float(-bounds.Y()) / bounds.Height(); + memcpy(mVSConstants.maskTransform, &transform._11, 64); + } + + D3D11_RECT scissor; + + IntRect clipRect(aClipRect.X(), aClipRect.Y(), aClipRect.Width(), + aClipRect.Height()); + if (mCurrentRT == mDefaultRT) { + clipRect = clipRect.Intersect(mCurrentClip); + } + + if (clipRect.IsEmpty()) { + return; + } + + scissor.left = clipRect.X(); + scissor.right = clipRect.XMost(); + scissor.top = clipRect.Y(); + scissor.bottom = clipRect.YMost(); + + bool useBlendShaders = false; + RefPtr mixBlendBackdrop; + gfx::CompositionOp blendMode = gfx::CompositionOp::OP_OVER; + if (aEffectChain.mSecondaryEffects[EffectTypes::BLEND_MODE]) { + EffectBlendMode* blendEffect = static_cast( + aEffectChain.mSecondaryEffects[EffectTypes::BLEND_MODE].get()); + blendMode = blendEffect->mBlendMode; + + // If the blend operation needs to read from the backdrop, copy the + // current render target into a new texture and bind it now. + if (BlendOpIsMixBlendMode(blendMode)) { + gfx::Matrix4x4 backdropTransform; + gfx::IntRect rect = ComputeBackdropCopyRect(aRect, aClipRect, aTransform, + &backdropTransform); + + RefPtr srv; + if (CopyBackdrop(rect, &mixBlendBackdrop, &srv) && + mAttachments->InitBlendShaders()) { + useBlendShaders = true; + + ID3D11ShaderResourceView* srView = srv.get(); + mContext->PSSetShaderResources(TexSlot::Backdrop, 1, &srView); + + memcpy(&mVSConstants.backdropTransform, &backdropTransform._11, 64); + + mPSConstants.blendConfig[0] = + EffectToBlendLayerType(aEffectChain.mPrimaryEffect); + mPSConstants.blendConfig[1] = int(maskType); + mPSConstants.blendConfig[2] = BlendOpToShaderConstant(blendMode); + mPSConstants.blendConfig[3] = + EffectHasPremultipliedAlpha(aEffectChain.mPrimaryEffect); + } + } + } + + mContext->RSSetScissorRects(1, &scissor); + + RefPtr vertexShader = + GetVSForGeometry(aGeometry, useBlendShaders, maskType); + + RefPtr pixelShader = + GetPSForEffect(aEffectChain.mPrimaryEffect, useBlendShaders, maskType); + + mContext->VSSetShader(vertexShader, nullptr, 0); + mContext->PSSetShader(pixelShader, nullptr, 0); + + const Rect* pTexCoordRect = nullptr; + + switch (aEffectChain.mPrimaryEffect->mType) { + case EffectTypes::SOLID_COLOR: { + DeviceColor color = + static_cast(aEffectChain.mPrimaryEffect.get()) + ->mColor; + mPSConstants.layerColor[0] = color.r * color.a * aOpacity; + mPSConstants.layerColor[1] = color.g * color.a * aOpacity; + mPSConstants.layerColor[2] = color.b * color.a * aOpacity; + mPSConstants.layerColor[3] = color.a * aOpacity; + } break; + case EffectTypes::RGB: + case EffectTypes::RENDER_TARGET: { + TexturedEffect* texturedEffect = + static_cast(aEffectChain.mPrimaryEffect.get()); + + pTexCoordRect = &texturedEffect->mTextureCoords; + + TextureSourceD3D11* source = texturedEffect->mTexture->AsSourceD3D11(); + + if (!source) { + NS_WARNING("Missing texture source!"); + return; + } + + ID3D11ShaderResourceView* srView = source->GetShaderResourceView(); + mContext->PSSetShaderResources(TexSlot::RGB, 1, &srView); + + if (!texturedEffect->mPremultiplied) { + mContext->OMSetBlendState(mAttachments->mNonPremulBlendState, + sBlendFactor, 0xFFFFFFFF); + restoreBlendMode = true; + } + + SetSamplerForSamplingFilter(texturedEffect->mSamplingFilter); + } break; + case EffectTypes::NV12: { + EffectNV12* effectNV12 = + static_cast(aEffectChain.mPrimaryEffect.get()); + + pTexCoordRect = &effectNV12->mTextureCoords; + + TextureSourceD3D11* source = effectNV12->mTexture->AsSourceD3D11(); + if (!source) { + NS_WARNING("Missing texture source!"); + return; + } + + RefPtr texture = source->GetD3D11Texture(); + if (!texture) { + NS_WARNING("No texture found in texture source!"); + } + + D3D11_TEXTURE2D_DESC sourceDesc; + texture->GetDesc(&sourceDesc); + MOZ_DIAGNOSTIC_ASSERT(sourceDesc.Format == DXGI_FORMAT_NV12 || + sourceDesc.Format == DXGI_FORMAT_P010 || + sourceDesc.Format == DXGI_FORMAT_P016); + + // Might want to cache these for efficiency. + RefPtr srViewY; + RefPtr srViewCbCr; + D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc = + CD3D11_SHADER_RESOURCE_VIEW_DESC(D3D11_SRV_DIMENSION_TEXTURE2D, + sourceDesc.Format == DXGI_FORMAT_NV12 + ? DXGI_FORMAT_R8_UNORM + : DXGI_FORMAT_R16_UNORM); + mDevice->CreateShaderResourceView(texture, &srvDesc, + getter_AddRefs(srViewY)); + srvDesc.Format = sourceDesc.Format == DXGI_FORMAT_NV12 + ? DXGI_FORMAT_R8G8_UNORM + : DXGI_FORMAT_R16G16_UNORM; + mDevice->CreateShaderResourceView(texture, &srvDesc, + getter_AddRefs(srViewCbCr)); + + ID3D11ShaderResourceView* views[] = {srViewY, srViewCbCr}; + mContext->PSSetShaderResources(TexSlot::Y, 2, views); + + const float* yuvToRgb = + gfxUtils::YuvToRgbMatrix4x3RowMajor(effectNV12->mYUVColorSpace); + memcpy(&mPSConstants.yuvColorMatrix, yuvToRgb, + sizeof(mPSConstants.yuvColorMatrix)); + mPSConstants.vCoefficient[0] = + RescalingFactorForColorDepth(effectNV12->mColorDepth); + + SetSamplerForSamplingFilter(effectNV12->mSamplingFilter); + } break; + case EffectTypes::YCBCR: { + EffectYCbCr* ycbcrEffect = + static_cast(aEffectChain.mPrimaryEffect.get()); + + SetSamplerForSamplingFilter(SamplingFilter::LINEAR); + + pTexCoordRect = &ycbcrEffect->mTextureCoords; + + const int Y = 0, Cb = 1, Cr = 2; + TextureSource* source = ycbcrEffect->mTexture; + + if (!source) { + NS_WARNING("No texture to composite"); + return; + } + + if (!source->GetSubSource(Y) || !source->GetSubSource(Cb) || + !source->GetSubSource(Cr)) { + // This can happen if we failed to upload the textures, most likely + // because of unsupported dimensions (we don't tile YCbCr textures). + return; + } + + const float* yuvToRgb = + gfxUtils::YuvToRgbMatrix4x3RowMajor(ycbcrEffect->mYUVColorSpace); + memcpy(&mPSConstants.yuvColorMatrix, yuvToRgb, + sizeof(mPSConstants.yuvColorMatrix)); + + // Adjust range according to the bit depth. + mPSConstants.vCoefficient[0] = + RescalingFactorForColorDepth(ycbcrEffect->mColorDepth); + + TextureSourceD3D11* sourceY = source->GetSubSource(Y)->AsSourceD3D11(); + TextureSourceD3D11* sourceCb = source->GetSubSource(Cb)->AsSourceD3D11(); + TextureSourceD3D11* sourceCr = source->GetSubSource(Cr)->AsSourceD3D11(); + + ID3D11ShaderResourceView* srViews[3] = { + sourceY->GetShaderResourceView(), sourceCb->GetShaderResourceView(), + sourceCr->GetShaderResourceView()}; + mContext->PSSetShaderResources(TexSlot::Y, 3, srViews); + } break; + case EffectTypes::COMPONENT_ALPHA: { + MOZ_ASSERT(LayerManager::LayersComponentAlphaEnabled()); + MOZ_ASSERT(mAttachments->mComponentBlendState); + EffectComponentAlpha* effectComponentAlpha = + static_cast(aEffectChain.mPrimaryEffect.get()); + + TextureSourceD3D11* sourceOnWhite = + effectComponentAlpha->mOnWhite->AsSourceD3D11(); + TextureSourceD3D11* sourceOnBlack = + effectComponentAlpha->mOnBlack->AsSourceD3D11(); + + if (!sourceOnWhite || !sourceOnBlack) { + NS_WARNING("Missing texture source(s)!"); + return; + } + + SetSamplerForSamplingFilter(effectComponentAlpha->mSamplingFilter); + + pTexCoordRect = &effectComponentAlpha->mTextureCoords; + + ID3D11ShaderResourceView* srViews[2] = { + sourceOnBlack->GetShaderResourceView(), + sourceOnWhite->GetShaderResourceView()}; + mContext->PSSetShaderResources(TexSlot::RGB, 1, &srViews[0]); + mContext->PSSetShaderResources(TexSlot::RGBWhite, 1, &srViews[1]); + + mContext->OMSetBlendState(mAttachments->mComponentBlendState, + sBlendFactor, 0xFFFFFFFF); + restoreBlendMode = true; + } break; + default: + NS_WARNING("Unknown shader type"); + return; + } + + Draw(aGeometry, pTexCoordRect); + + if (restoreBlendMode) { + mContext->OMSetBlendState(mAttachments->mPremulBlendState, sBlendFactor, + 0xFFFFFFFF); + } +} + +Maybe CompositorD3D11::BeginFrameForWindow( + const nsIntRegion& aInvalidRegion, const Maybe& aClipRect, + const IntRect& aRenderBounds, const nsIntRegion& aOpaqueRegion) { + MOZ_RELEASE_ASSERT(!mTarget, "mTarget not cleared properly"); + return BeginFrame(aInvalidRegion, aClipRect, aRenderBounds, aOpaqueRegion); +} + +Maybe CompositorD3D11::BeginFrameForTarget( + const nsIntRegion& aInvalidRegion, const Maybe& aClipRect, + const IntRect& aRenderBounds, const nsIntRegion& aOpaqueRegion, + DrawTarget* aTarget, const IntRect& aTargetBounds) { + MOZ_RELEASE_ASSERT(!mTarget, "mTarget not cleared properly"); + mTarget = aTarget; // Will be cleared in EndFrame(). + mTargetBounds = aTargetBounds; + Maybe result = + BeginFrame(aInvalidRegion, aClipRect, aRenderBounds, aOpaqueRegion); + if (!result) { + // Composition has been aborted. Reset mTarget. + mTarget = nullptr; + } + return result; +} + +void CompositorD3D11::BeginFrameForNativeLayers() { + MOZ_CRASH("Native layers are not implemented on Windows."); +} + +Maybe CompositorD3D11::BeginRenderingToNativeLayer( + const nsIntRegion& aInvalidRegion, const Maybe& aClipRect, + const nsIntRegion& aOpaqueRegion, NativeLayer* aNativeLayer) { + MOZ_CRASH("Native layers are not implemented on Windows."); +} + +void CompositorD3D11::EndRenderingToNativeLayer() { + MOZ_CRASH("Native layers are not implemented on Windows."); +} + +Maybe CompositorD3D11::BeginFrame(const nsIntRegion& aInvalidRegion, + const Maybe& aClipRect, + const IntRect& aRenderBounds, + const nsIntRegion& aOpaqueRegion) { + // Don't composite if we are minimised. Other than for the sake of efficency, + // this is important because resizing our buffers when mimised will fail and + // cause a crash when we're restored. + NS_ASSERTION(mHwnd, "Couldn't find an HWND when initialising?"); + if (mWidget->IsHidden()) { + // We are not going to render, and not going to call EndFrame so we have to + // read-unlock our textures to prevent them from accumulating. + ReadUnlockTextures(); + return Nothing(); + } + + if (mDevice->GetDeviceRemovedReason() != S_OK) { + ReadUnlockTextures(); + + if (!mAttachments->IsDeviceReset()) { + gfxCriticalNote << "GFX: D3D11 skip BeginFrame with device-removed."; + + // If we are in the GPU process then the main process doesn't + // know that a device reset has happened and needs to be informed. + // + // When CompositorD3D11 is used for Software WebRender, it does not need + // to notify device reset. The device reset is notified by WebRender. + if (XRE_IsGPUProcess() && !mUseForSoftwareWebRender) { + GPUParent::GetSingleton()->NotifyDeviceReset(); + } + mAttachments->SetDeviceReset(); + } + return Nothing(); + } + + LayoutDeviceIntSize oldSize = mSize; + + EnsureSize(); + + IntRect rect = IntRect(IntPoint(0, 0), mSize.ToUnknownSize()); + // Sometimes the invalid region is larger than we want to draw. + nsIntRegion invalidRegionSafe; + + if (mSize != oldSize) { + invalidRegionSafe = rect; + } else { + invalidRegionSafe.And(aInvalidRegion, rect); + } + + IntRect invalidRect = invalidRegionSafe.GetBounds(); + + IntRect clipRect = invalidRect; + if (aClipRect) { + clipRect.IntersectRect(clipRect, *aClipRect); + } + + if (clipRect.IsEmpty()) { + CancelFrame(); + return Nothing(); + } + + PrepareStaticVertexBuffer(); + + mBackBufferInvalid.Or(mBackBufferInvalid, invalidRegionSafe); + if (mIsDoubleBuffered) { + mFrontBufferInvalid.Or(mFrontBufferInvalid, invalidRegionSafe); + } + + // We have to call UpdateRenderTarget after we've determined the invalid regi + // Failed to create a render target or the view. + if (!UpdateRenderTarget() || !mDefaultRT || !mDefaultRT->mRTView || + mSize.width <= 0 || mSize.height <= 0) { + ReadUnlockTextures(); + return Nothing(); + } + + mCurrentClip = mBackBufferInvalid.GetBounds(); + + mContext->RSSetState(mAttachments->mRasterizerState); + + SetRenderTarget(mDefaultRT); + + IntRegion regionToClear(mCurrentClip); + regionToClear.Sub(regionToClear, aOpaqueRegion); + + ClearRect(Rect(regionToClear.GetBounds())); + + mContext->OMSetBlendState(mAttachments->mPremulBlendState, sBlendFactor, + 0xFFFFFFFF); + + if (mAttachments->mSyncObject) { + if (!mAttachments->mSyncObject->Synchronize()) { + // It's timeout here. Since the timeout is related to the driver-removed, + // skip this frame. + return Nothing(); + } + } + + if (StaticPrefs::layers_acceleration_draw_fps()) { + uint32_t pixelsPerFrame = 0; + for (auto iter = mBackBufferInvalid.RectIter(); !iter.Done(); iter.Next()) { + pixelsPerFrame += iter.Get().Width() * iter.Get().Height(); + } + + mDiagnostics->Start(pixelsPerFrame); + } + + return Some(rect); +} + +void CompositorD3D11::NormalDrawingDone() { mDiagnostics->End(); } + +void CompositorD3D11::EndFrame() { +#ifdef MOZ_GECKO_PROFILER + if (!profiler_feature_active(ProfilerFeature::Screenshots) && mWindowRTCopy) { + mWindowRTCopy = nullptr; + } +#endif // MOZ_GECKO_PROFILER + + if (!mDefaultRT) { + Compositor::EndFrame(); + mTarget = nullptr; + return; + } + + if (XRE_IsParentProcess() && mDevice->GetDeviceRemovedReason() != S_OK) { + gfxCriticalNote << "GFX: D3D11 skip EndFrame with device-removed."; + Compositor::EndFrame(); + mTarget = nullptr; + mCurrentRT = nullptr; + return; + } + + LayoutDeviceIntSize oldSize = mSize; + EnsureSize(); + if (mSize.width <= 0 || mSize.height <= 0) { + Compositor::EndFrame(); + mTarget = nullptr; + return; + } + + RefPtr query; + if (mRecycledQuery) { + query = mRecycledQuery.forget(); + } else { + CD3D11_QUERY_DESC desc(D3D11_QUERY_EVENT); + mDevice->CreateQuery(&desc, getter_AddRefs(query)); + } + if (query) { + mContext->End(query); + } + + if (oldSize == mSize) { + Present(); + if (StaticPrefs::gfx_compositor_clearstate()) { + mContext->ClearState(); + } + } else { + mDiagnostics->Cancel(); + } + + // Block until the previous frame's work has been completed. + if (mQuery) { + BOOL result; + WaitForFrameGPUQuery(mDevice, mContext, mQuery, &result); + // Store the query for recycling + mRecycledQuery = mQuery; + } + // Store the query for this frame so we can flush it next time. + mQuery = query; + + Compositor::EndFrame(); + mTarget = nullptr; + mCurrentRT = nullptr; +} + +void CompositorD3D11::GetFrameStats(GPUStats* aStats) { + mDiagnostics->Query(aStats); +} + +void CompositorD3D11::Present() { + UINT presentInterval = 0; + + bool isWARP = DeviceManagerDx::Get()->IsWARP(); + if (isWARP) { + // When we're using WARP we cannot present immediately as it causes us + // to tear when rendering. When not using WARP it appears the DWM takes + // care of tearing for us. + presentInterval = 1; + } + + // This must be called before present so our back buffer has the validated + // window content. + if (mTarget) { + PaintToTarget(); + } + + RefPtr chain; + HRESULT hr = + mSwapChain->QueryInterface((IDXGISwapChain1**)getter_AddRefs(chain)); + + RefPtr mutex; + if (mUseMutexOnPresent && mAttachments->mSyncObject) { + SyncObjectD3D11Host* d3dSyncObj = + (SyncObjectD3D11Host*)mAttachments->mSyncObject.get(); + mutex = d3dSyncObj->GetKeyedMutex(); + MOZ_ASSERT(mutex); + } + + if (SUCCEEDED(hr) && mAllowPartialPresents) { + DXGI_PRESENT_PARAMETERS params; + PodZero(¶ms); + params.DirtyRectsCount = mBackBufferInvalid.GetNumRects(); + StackArray rects(params.DirtyRectsCount); + + uint32_t i = 0; + for (auto iter = mBackBufferInvalid.RectIter(); !iter.Done(); iter.Next()) { + const IntRect& r = iter.Get(); + rects[i].left = r.X(); + rects[i].top = r.Y(); + rects[i].bottom = r.YMost(); + rects[i].right = r.XMost(); + i++; + } + + params.pDirtyRects = params.DirtyRectsCount ? rects.data() : nullptr; + + if (mutex) { + hr = mutex->AcquireSync(0, 2000); + NS_ENSURE_TRUE_VOID(SUCCEEDED(hr)); + } + + chain->Present1( + presentInterval, + mDisableSequenceForNextFrame ? DXGI_PRESENT_DO_NOT_SEQUENCE : 0, + ¶ms); + + if (mutex) { + mutex->ReleaseSync(0); + } + } else { + if (mutex) { + hr = mutex->AcquireSync(0, 2000); + NS_ENSURE_TRUE_VOID(SUCCEEDED(hr)); + } + + hr = mSwapChain->Present( + 0, mDisableSequenceForNextFrame ? DXGI_PRESENT_DO_NOT_SEQUENCE : 0); + + if (mutex) { + mutex->ReleaseSync(0); + } + + if (FAILED(hr)) { + gfxCriticalNote << "D3D11 swap chain preset failed " << hexa(hr); + HandleError(hr); + } + } + + if (mIsDoubleBuffered) { + mBackBufferInvalid = mFrontBufferInvalid; + mFrontBufferInvalid.SetEmpty(); + } else { + mBackBufferInvalid.SetEmpty(); + } + + mDisableSequenceForNextFrame = false; +} + +void CompositorD3D11::CancelFrame(bool aNeedFlush) { + ReadUnlockTextures(); + // Flush the context, otherwise the driver might hold some resources alive + // until the next flush or present. + if (aNeedFlush) { + mContext->Flush(); + } +} + +void CompositorD3D11::PrepareViewport(const gfx::IntSize& aSize) { + // This view matrix translates coordinates from 0..width and 0..height to + // -1..1 on the X axis, and -1..1 on the Y axis (flips the Y coordinate) + Matrix viewMatrix = Matrix::Translation(-1.0, 1.0); + viewMatrix.PreScale(2.0f / float(aSize.width), 2.0f / float(aSize.height)); + viewMatrix.PreScale(1.0f, -1.0f); + + Matrix4x4 projection = Matrix4x4::From2D(viewMatrix); + projection._33 = 0.0f; + + PrepareViewport(aSize, projection, 0.0f, 1.0f); +} + +void CompositorD3D11::ForcePresent() { + LayoutDeviceIntSize size = mWidget->GetClientSize(); + + DXGI_SWAP_CHAIN_DESC desc; + mSwapChain->GetDesc(&desc); + + if (desc.BufferDesc.Width == size.width && + desc.BufferDesc.Height == size.height && size == mBufferSize) { + mSwapChain->Present(0, 0); + if (mIsDoubleBuffered) { + // Make sure we present what was the front buffer before that we know is + // completely valid. This non v-synced present should be pretty much + // 'free' for a flip chain. + mSwapChain->Present(0, 0); + } + } +} + +void CompositorD3D11::PrepareViewport(const gfx::IntSize& aSize, + const gfx::Matrix4x4& aProjection, + float aZNear, float aZFar) { + D3D11_VIEWPORT viewport; + viewport.MaxDepth = aZFar; + viewport.MinDepth = aZNear; + viewport.Width = aSize.width; + viewport.Height = aSize.height; + viewport.TopLeftX = 0; + viewport.TopLeftY = 0; + + mContext->RSSetViewports(1, &viewport); + + memcpy(&mVSConstants.projection, &aProjection._11, + sizeof(mVSConstants.projection)); +} + +void CompositorD3D11::EnsureSize() { mSize = mWidget->GetClientSize(); } + +bool CompositorD3D11::VerifyBufferSize() { + mWidget->AsWindows()->UpdateCompositorWndSizeIfNecessary(); + + DXGI_SWAP_CHAIN_DESC swapDesc; + HRESULT hr; + + hr = mSwapChain->GetDesc(&swapDesc); + if (FAILED(hr)) { + gfxCriticalError() << "Failed to get the description " << hexa(hr) << ", " + << mSize << ", " << (int)mVerifyBuffersFailed; + HandleError(hr); + return false; + } + + if (((swapDesc.BufferDesc.Width == mSize.width && + swapDesc.BufferDesc.Height == mSize.height) || + mSize.width <= 0 || mSize.height <= 0) && + !mVerifyBuffersFailed) { + return true; + } + + ID3D11RenderTargetView* view = nullptr; + mContext->OMSetRenderTargets(1, &view, nullptr); + + if (mDefaultRT) { + RefPtr rtView = mDefaultRT->mRTView; + RefPtr srView = mDefaultRT->mSRV; + + // Make sure the texture, which belongs to the swapchain, is destroyed + // before resizing the swapchain. + if (mCurrentRT == mDefaultRT) { + mCurrentRT = nullptr; + } + + MOZ_ASSERT(mDefaultRT->hasOneRef()); + mDefaultRT = nullptr; + + RefPtr resource; + rtView->GetResource(getter_AddRefs(resource)); + + ULONG newRefCnt = rtView.forget().take()->Release(); + + if (newRefCnt > 0) { + gfxCriticalError() << "mRTView not destroyed on final release! RefCnt: " + << newRefCnt; + } + + if (srView) { + newRefCnt = srView.forget().take()->Release(); + + if (newRefCnt > 0) { + gfxCriticalError() << "mSRV not destroyed on final release! RefCnt: " + << newRefCnt; + } + } + + newRefCnt = resource.forget().take()->Release(); + + if (newRefCnt > 0) { + gfxCriticalError() + << "Unexpecting lingering references to backbuffer! RefCnt: " + << newRefCnt; + } + } + + hr = mSwapChain->ResizeBuffers(0, mSize.width, mSize.height, + DXGI_FORMAT_B8G8R8A8_UNORM, 0); + + mVerifyBuffersFailed = FAILED(hr); + if (mVerifyBuffersFailed) { + gfxCriticalNote << "D3D11 swap resize buffers failed " << hexa(hr) << " on " + << mSize; + HandleError(hr); + mBufferSize = LayoutDeviceIntSize(); + } else { + mBufferSize = mSize; + } + + mBackBufferInvalid = mFrontBufferInvalid = + IntRect(0, 0, mSize.width, mSize.height); + + return !mVerifyBuffersFailed; +} + +bool CompositorD3D11::UpdateRenderTarget() { + HRESULT hr; + + RefPtr backBuf; + + if (!VerifyBufferSize()) { + gfxCriticalNote << "Failed VerifyBufferSize in UpdateRenderTarget " + << mSize; + return false; + } + + if (mSize.width <= 0 || mSize.height <= 0) { + gfxCriticalNote << "Invalid size in UpdateRenderTarget " << mSize << ", " + << (int)mVerifyBuffersFailed; + return false; + } + + hr = mSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), + (void**)backBuf.StartAssignment()); + if (hr == DXGI_ERROR_INVALID_CALL) { + // This happens on some GPUs/drivers when there's a TDR. + if (mDevice->GetDeviceRemovedReason() != S_OK) { + gfxCriticalError() << "GetBuffer returned invalid call! " << mSize << ", " + << (int)mVerifyBuffersFailed; + return false; + } + } + + if (FAILED(hr)) { + gfxCriticalNote << "Failed in UpdateRenderTarget " << hexa(hr) << ", " + << mSize << ", " << (int)mVerifyBuffersFailed; + HandleError(hr); + return false; + } + + IntRegion validFront; + validFront.Sub(mBackBufferInvalid, mFrontBufferInvalid); + + if (mIsDoubleBuffered && !validFront.IsEmpty()) { + RefPtr frontBuf; + hr = mSwapChain->GetBuffer(1, __uuidof(ID3D11Texture2D), + (void**)frontBuf.StartAssignment()); + + if (SUCCEEDED(hr)) { + for (auto iter = validFront.RectIter(); !iter.Done(); iter.Next()) { + const IntRect& rect = iter.Get(); + + D3D11_BOX box; + box.back = 1; + box.front = 0; + box.left = rect.X(); + box.right = rect.XMost(); + box.top = rect.Y(); + box.bottom = rect.YMost(); + mContext->CopySubresourceRegion(backBuf, 0, rect.X(), rect.Y(), 0, + frontBuf, 0, &box); + } + mBackBufferInvalid = mFrontBufferInvalid; + } + } + + mDefaultRT = new CompositingRenderTargetD3D11(backBuf, IntPoint(0, 0)); + mDefaultRT->SetSize(mSize.ToUnknownSize()); + + return true; +} + +bool CompositorD3D11::UpdateConstantBuffers() { + HRESULT hr; + D3D11_MAPPED_SUBRESOURCE resource; + resource.pData = nullptr; + + hr = mContext->Map(mAttachments->mVSConstantBuffer, 0, + D3D11_MAP_WRITE_DISCARD, 0, &resource); + if (FAILED(hr) || !resource.pData) { + gfxCriticalError() << "Failed to map VSConstantBuffer. Result: " << hexa(hr) + << ", " << (int)mVerifyBuffersFailed; + HandleError(hr); + return false; + } + *(VertexShaderConstants*)resource.pData = mVSConstants; + mContext->Unmap(mAttachments->mVSConstantBuffer, 0); + resource.pData = nullptr; + + hr = mContext->Map(mAttachments->mPSConstantBuffer, 0, + D3D11_MAP_WRITE_DISCARD, 0, &resource); + if (FAILED(hr) || !resource.pData) { + gfxCriticalError() << "Failed to map PSConstantBuffer. Result: " << hexa(hr) + << ", " << (int)mVerifyBuffersFailed; + HandleError(hr); + return false; + } + *(PixelShaderConstants*)resource.pData = mPSConstants; + mContext->Unmap(mAttachments->mPSConstantBuffer, 0); + + ID3D11Buffer* buffer = mAttachments->mVSConstantBuffer; + + mContext->VSSetConstantBuffers(0, 1, &buffer); + + buffer = mAttachments->mPSConstantBuffer; + mContext->PSSetConstantBuffers(0, 1, &buffer); + return true; +} + +void CompositorD3D11::SetSamplerForSamplingFilter( + SamplingFilter aSamplingFilter) { + ID3D11SamplerState* sampler; + switch (aSamplingFilter) { + case SamplingFilter::POINT: + sampler = mAttachments->mPointSamplerState; + break; + case SamplingFilter::LINEAR: + default: + sampler = mAttachments->mLinearSamplerState; + break; + } + + mContext->PSSetSamplers(0, 1, &sampler); +} + +void CompositorD3D11::PaintToTarget() { + RefPtr backBuf; + HRESULT hr; + + hr = mSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), + (void**)backBuf.StartAssignment()); + if (FAILED(hr)) { + gfxCriticalErrorOnce(gfxCriticalError::DefaultOptions(false)) + << "Failed in PaintToTarget 1"; + HandleError(hr); + return; + } + + D3D11_TEXTURE2D_DESC bbDesc; + backBuf->GetDesc(&bbDesc); + + CD3D11_TEXTURE2D_DESC softDesc(bbDesc.Format, bbDesc.Width, bbDesc.Height); + softDesc.MipLevels = 1; + softDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + softDesc.Usage = D3D11_USAGE_STAGING; + softDesc.BindFlags = 0; + + RefPtr readTexture; + + hr = + mDevice->CreateTexture2D(&softDesc, nullptr, getter_AddRefs(readTexture)); + if (FAILED(hr)) { + gfxCriticalErrorOnce(gfxCriticalError::DefaultOptions(false)) + << "Failed in PaintToTarget 2"; + HandleError(hr); + return; + } + mContext->CopyResource(readTexture, backBuf); + + D3D11_MAPPED_SUBRESOURCE map; + hr = mContext->Map(readTexture, 0, D3D11_MAP_READ, 0, &map); + if (FAILED(hr)) { + gfxCriticalErrorOnce(gfxCriticalError::DefaultOptions(false)) + << "Failed in PaintToTarget 3"; + HandleError(hr); + return; + } + RefPtr sourceSurface = + Factory::CreateWrappingDataSourceSurface( + (uint8_t*)map.pData, map.RowPitch, + IntSize(bbDesc.Width, bbDesc.Height), SurfaceFormat::B8G8R8A8); + mTarget->CopySurface(sourceSurface, + IntRect(0, 0, bbDesc.Width, bbDesc.Height), + IntPoint(-mTargetBounds.X(), -mTargetBounds.Y())); + + mTarget->Flush(); + mContext->Unmap(readTexture, 0); +} + +bool CompositorD3D11::Failed(HRESULT hr, const char* aContext) { + if (SUCCEEDED(hr)) return false; + + gfxCriticalNote << "[D3D11] " << aContext << " failed: " << hexa(hr) << ", " + << (int)mVerifyBuffersFailed; + return true; +} + +SyncObjectHost* CompositorD3D11::GetSyncObject() { + if (mAttachments) { + return mAttachments->mSyncObject; + } + return nullptr; +} + +void CompositorD3D11::HandleError(HRESULT hr, Severity aSeverity) { + if (SUCCEEDED(hr)) { + return; + } + + if (aSeverity == Critical) { + MOZ_CRASH("GFX: Unrecoverable D3D11 error"); + } + + if (mDevice && DeviceManagerDx::Get()->GetCompositorDevice() != mDevice) { + gfxCriticalNote << "Out of sync D3D11 devices in HandleError, " + << (int)mVerifyBuffersFailed; + } + + HRESULT hrOnReset = S_OK; + bool deviceRemoved = hr == DXGI_ERROR_DEVICE_REMOVED; + + if (deviceRemoved && mDevice) { + hrOnReset = mDevice->GetDeviceRemovedReason(); + } else if (hr == DXGI_ERROR_INVALID_CALL && mDevice) { + hrOnReset = mDevice->GetDeviceRemovedReason(); + if (hrOnReset != S_OK) { + deviceRemoved = true; + } + } + + // Device reset may not be an error on our side, but can mess things up so + // it's useful to see it in the reports. + gfxCriticalError(CriticalLog::DefaultOptions(!deviceRemoved)) + << (deviceRemoved ? "[CompositorD3D11] device removed with error code: " + : "[CompositorD3D11] error code: ") + << hexa(hr) << ", " << hexa(hrOnReset) << ", " + << (int)mVerifyBuffersFailed; + + // Crash if we are making invalid calls outside of device removal + if (hr == DXGI_ERROR_INVALID_CALL) { + gfxDevCrash(deviceRemoved ? LogReason::D3D11InvalidCallDeviceRemoved + : LogReason::D3D11InvalidCall) + << "Invalid D3D11 api call"; + } + + if (aSeverity == Recoverable) { + NS_WARNING("Encountered a recoverable D3D11 error"); + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/d3d11/CompositorD3D11.h b/gfx/layers/d3d11/CompositorD3D11.h new file mode 100644 index 0000000000..c1a0c81c23 --- /dev/null +++ b/gfx/layers/d3d11/CompositorD3D11.h @@ -0,0 +1,303 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_COMPOSITORD3D11_H +#define MOZILLA_GFX_COMPOSITORD3D11_H + +#include "mozilla/gfx/2D.h" +#include "gfx2DGlue.h" +#include "mozilla/layers/Compositor.h" +#include "TextureD3D11.h" +#include +#include +#include "ShaderDefinitionsD3D11.h" + +class nsWidget; + +namespace mozilla { +namespace layers { + +#define LOGD3D11(param) + +class DeviceAttachmentsD3D11; +class DiagnosticsD3D11; + +class CompositorD3D11 : public Compositor { + public: + CompositorD3D11(CompositorBridgeParent* aParent, + widget::CompositorWidget* aWidget); + virtual ~CompositorD3D11(); + + CompositorD3D11* AsCompositorD3D11() override { return this; } + + bool Initialize(nsCString* const out_failureReason) override; + + TextureFactoryIdentifier GetTextureFactoryIdentifier() override; + + already_AddRefed CreateDataTextureSource( + TextureFlags aFlags = TextureFlags::NO_FLAGS) override; + + bool CanUseCanvasLayerForSize(const gfx::IntSize& aSize) override; + int32_t GetMaxTextureSize() const final; + + void MakeCurrent(MakeCurrentFlags aFlags = 0) override {} + + already_AddRefed CreateRenderTarget( + const gfx::IntRect& aRect, SurfaceInitMode aInit) override; + + already_AddRefed CreateRenderTargetFromSource( + const gfx::IntRect& aRect, const CompositingRenderTarget* aSource, + const gfx::IntPoint& aSourcePoint) override; + + void SetRenderTarget(CompositingRenderTarget* aSurface) override; + already_AddRefed GetCurrentRenderTarget() + const override { + return do_AddRef(mCurrentRT); + } + already_AddRefed GetWindowRenderTarget() + const override; + + bool ReadbackRenderTarget(CompositingRenderTarget* aSource, + AsyncReadbackBuffer* aDest) override; + already_AddRefed CreateAsyncReadbackBuffer( + const gfx::IntSize& aSize) override; + + bool BlitRenderTarget(CompositingRenderTarget* aSource, + const gfx::IntSize& aSourceSize, + const gfx::IntSize& aDestSize) override; + + void SetDestinationSurfaceSize(const gfx::IntSize& aSize) override {} + + void ClearRect(const gfx::Rect& aRect) override; + + void DrawQuad(const gfx::Rect& aRect, const gfx::IntRect& aClipRect, + const EffectChain& aEffectChain, gfx::Float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::Rect& aVisibleRect) override; + + /** + * Start a new frame. + */ + Maybe BeginFrameForWindow( + const nsIntRegion& aInvalidRegion, const Maybe& aClipRect, + const gfx::IntRect& aRenderBounds, + const nsIntRegion& aOpaqueRegion) override; + + Maybe BeginFrameForTarget( + const nsIntRegion& aInvalidRegion, const Maybe& aClipRect, + const gfx::IntRect& aRenderBounds, const nsIntRegion& aOpaqueRegion, + gfx::DrawTarget* aTarget, const gfx::IntRect& aTargetBounds) override; + + void BeginFrameForNativeLayers() override; + + Maybe BeginRenderingToNativeLayer( + const nsIntRegion& aInvalidRegion, const Maybe& aClipRect, + const nsIntRegion& aOpaqueRegion, NativeLayer* aNativeLayer) override; + + void EndRenderingToNativeLayer() override; + + void NormalDrawingDone() override; + + /** + * Flush the current frame to the screen. + */ + void EndFrame() override; + + void CancelFrame(bool aNeedFlush = true) override; + + /** + * Setup the viewport and projection matrix for rendering + * to a window of the given dimensions. + */ + virtual void PrepareViewport(const gfx::IntSize& aSize); + virtual void PrepareViewport(const gfx::IntSize& aSize, + const gfx::Matrix4x4& aProjection, float aZNear, + float aZFar); + + bool SupportsPartialTextureUpdate() override { return true; } + + bool SupportsLayerGeometry() const override; + +#ifdef MOZ_DUMP_PAINTING + const char* Name() const override { return "Direct3D 11"; } +#endif + + LayersBackend GetBackendType() const override { + return LayersBackend::LAYERS_D3D11; + } + + virtual void ForcePresent(); + + // For TextureSourceProvider. + ID3D11Device* GetD3D11Device() const override { return mDevice; } + + ID3D11Device* GetDevice() { return mDevice; } + + ID3D11DeviceContext* GetDC() { return mContext; } + + virtual void RequestAllowFrameRecording(bool aWillRecord) override { + mAllowFrameRecording = aWillRecord; + } + + void Readback(gfx::DrawTarget* aDrawTarget) { + mTarget = aDrawTarget; + mTargetBounds = gfx::IntRect(); + PaintToTarget(); + mTarget = nullptr; + } + + SyncObjectHost* GetSyncObject(); + + void UseForSoftwareWebRender() { mUseForSoftwareWebRender = true; } + + private: + enum Severity { + Recoverable, + DebugAssert, + Critical, + }; + + void HandleError(HRESULT hr, Severity aSeverity = DebugAssert); + + // Same as Failed(), except the severity is critical (with no abort) and + // a string prefix must be provided. + bool Failed(HRESULT hr, const char* aContext); + + // ensure mSize is up to date with respect to mWidget + void EnsureSize(); + bool VerifyBufferSize(); + bool UpdateRenderTarget(); + bool UpdateConstantBuffers(); + void SetSamplerForSamplingFilter(gfx::SamplingFilter aSamplingFilter); + + ID3D11PixelShader* GetPSForEffect(Effect* aEffect, const bool aUseBlendShader, + const MaskType aMaskType); + Maybe BeginFrame(const nsIntRegion& aInvalidRegion, + const Maybe& aClipRect, + const gfx::IntRect& aRenderBounds, + const nsIntRegion& aOpaqueRegion); + void PaintToTarget(); + RefPtr CreateTexture(const gfx::IntRect& aRect, + const CompositingRenderTarget* aSource, + const gfx::IntPoint& aSourcePoint); + bool CopyBackdrop(const gfx::IntRect& aRect, + RefPtr* aOutTexture, + RefPtr* aOutView); + + void DrawTriangles(const nsTArray& aTriangles, + const gfx::Rect& aRect, const gfx::IntRect& aClipRect, + const EffectChain& aEffectChain, gfx::Float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::Rect& aVisibleRect) override; + + template + void DrawGeometry(const Geometry& aGeometry, const gfx::Rect& aRect, + const gfx::IntRect& aClipRect, + const EffectChain& aEffectChain, gfx::Float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::Rect& aVisibleRect); + + bool UpdateDynamicVertexBuffer( + const nsTArray& aTriangles); + + void PrepareDynamicVertexBuffer(); + void PrepareStaticVertexBuffer(); + + // Overloads for rendering both rects and triangles with same rendering path + void Draw(const nsTArray& aGeometry, + const gfx::Rect* aTexCoords); + + void Draw(const gfx::Rect& aGeometry, const gfx::Rect* aTexCoords); + + void GetFrameStats(GPUStats* aStats) override; + + void Present(); + + ID3D11VertexShader* GetVSForGeometry( + const nsTArray& aTriangles, + const bool aUseBlendShader, const MaskType aMaskType); + + ID3D11VertexShader* GetVSForGeometry(const gfx::Rect& aRect, + const bool aUseBlendShader, + const MaskType aMaskType); + + template + void SetVertexBuffer(ID3D11Buffer* aBuffer); + + /** + * Whether or not the recorder should be recording frames. + * + * When this returns true, the CompositorD3D11 will allocate and return window + * render targets from |GetWindowRenderTarget|, which otherwise returns + * nullptr. + * + * This will be true when either we are recording a profile with screenshots + * enabled or the |LayerManagerComposite| has requested us to record frames + * for the |CompositionRecorder|. + */ + bool ShouldAllowFrameRecording() const; + + // The DrawTarget from BeginFrameForTarget, which EndFrame needs to copy the + // window contents into. + // Only non-null between BeginFrameForTarget and EndFrame. + RefPtr mTarget; + gfx::IntRect mTargetBounds; + + RefPtr mContext; + RefPtr mDevice; + RefPtr mSwapChain; + RefPtr mDefaultRT; + RefPtr mCurrentRT; + mutable RefPtr mWindowRTCopy; + + RefPtr mQuery; + RefPtr mRecycledQuery; + + RefPtr mAttachments; + UniquePtr mDiagnostics; + + LayoutDeviceIntSize mSize; + + // The size that we passed to ResizeBuffers to set + // the swapchain buffer size. + LayoutDeviceIntSize mBufferSize; + + HWND mHwnd; + + D3D_FEATURE_LEVEL mFeatureLevel; + + VertexShaderConstants mVSConstants; + PixelShaderConstants mPSConstants; + bool mDisableSequenceForNextFrame; + bool mAllowPartialPresents; + bool mIsDoubleBuffered; + + gfx::IntRegion mFrontBufferInvalid; + gfx::IntRegion mBackBufferInvalid; + // This is the clip rect applied to the default DrawTarget (i.e. the window) + gfx::IntRect mCurrentClip; + + bool mVerifyBuffersFailed; + bool mUseMutexOnPresent; + bool mAllowFrameRecording; + + bool mUseForSoftwareWebRender; +}; + +namespace TexSlot { +static const int RGB = 0; +static const int Y = 1; +static const int Cb = 2; +static const int Cr = 3; +static const int RGBWhite = 4; +static const int Mask = 5; +static const int Backdrop = 6; +} // namespace TexSlot + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/d3d11/CompositorD3D11.hlsl b/gfx/layers/d3d11/CompositorD3D11.hlsl new file mode 100644 index 0000000000..c740662548 --- /dev/null +++ b/gfx/layers/d3d11/CompositorD3D11.hlsl @@ -0,0 +1,503 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "BlendingHelpers.hlslh" +#include "BlendShaderConstants.h" + +typedef float4 rect; + +float4x4 mLayerTransform : register(vs, c0); +float4x4 mProjection : register(vs, c4); +float4 vRenderTargetOffset : register(vs, c8); +rect vTextureCoords : register(vs, c9); +rect vLayerQuad : register(vs, c10); +float4x4 mMaskTransform : register(vs, c11); +float4x4 mBackdropTransform : register(vs, c15); + +float4 fLayerColor : register(ps, c0); +float fLayerOpacity : register(ps, c1); + +// x = layer type +// y = mask type +// z = blend op +// w = is premultiplied +uint4 iBlendConfig : register(ps, c2); + +float fCoefficient : register(ps, c3); + +row_major float3x3 mYuvColorMatrix : register(ps, c4); + +sampler sSampler : register(ps, s0); + +// The mix-blend mega shader uses all variables, so we have to make sure they +// are assigned fixed slots. +Texture2D tRGB : register(ps, t0); +Texture2D tY : register(ps, t1); +Texture2D tCb : register(ps, t2); +Texture2D tCr : register(ps, t3); +Texture2D tRGBWhite : register(ps, t4); +Texture2D tMask : register(ps, t5); +Texture2D tBackdrop : register(ps, t6); + +struct VS_INPUT { + float2 vPosition : POSITION; +}; + +struct VS_TEX_INPUT { + float2 vPosition : POSITION; + float2 vTexCoords : TEXCOORD0; +}; + +struct VS_OUTPUT { + float4 vPosition : SV_Position; + float2 vTexCoords : TEXCOORD0; +}; + +struct VS_MASK_OUTPUT { + float4 vPosition : SV_Position; + float2 vTexCoords : TEXCOORD0; + float3 vMaskCoords : TEXCOORD1; +}; + +// Combined struct for the mix-blend compatible vertex shaders. +struct VS_BLEND_OUTPUT { + float4 vPosition : SV_Position; + float2 vTexCoords : TEXCOORD0; + float3 vMaskCoords : TEXCOORD1; + float2 vBackdropCoords : TEXCOORD2; +}; + +struct PS_OUTPUT { + float4 vSrc; + float4 vAlpha; +}; + +float2 TexCoords(const float2 aPosition) +{ + float2 result; + const float2 size = vTextureCoords.zw; + result.x = vTextureCoords.x + aPosition.x * size.x; + result.y = vTextureCoords.y + aPosition.y * size.y; + + return result; +} + +SamplerState LayerTextureSamplerLinear +{ + Filter = MIN_MAG_MIP_LINEAR; + AddressU = Clamp; + AddressV = Clamp; +}; + +float4 TransformedPosition(float2 aInPosition) +{ + // the current vertex's position on the quad + // [x,y,0,1] is mandated by the CSS Transforms spec as the point value to transform + float4 position = float4(0, 0, 0, 1); + + // We use 4 component floats to uniquely describe a rectangle, by the structure + // of x, y, width, height. This allows us to easily generate the 4 corners + // of any rectangle from the 4 corners of the 0,0-1,1 quad that we use as the + // stream source for our LayerQuad vertex shader. We do this by doing: + // Xout = x + Xin * width + // Yout = y + Yin * height + float2 size = vLayerQuad.zw; + position.x = vLayerQuad.x + aInPosition.x * size.x; + position.y = vLayerQuad.y + aInPosition.y * size.y; + + position = mul(mLayerTransform, position); + + return position; +} + +float4 VertexPosition(float4 aTransformedPosition) +{ + float4 result; + result.w = aTransformedPosition.w; + result.xyz = aTransformedPosition.xyz / aTransformedPosition.w; + result -= vRenderTargetOffset; + result.xyz *= result.w; + + result = mul(mProjection, result); + + return result; +} + +float2 BackdropPosition(float4 aPosition) +{ + // Move the position from clip space (-1,1) into 0..1 space. + float2 pos; + pos.x = (aPosition.x + 1.0) / 2.0; + pos.y = 1.0 - (aPosition.y + 1.0) / 2.0; + + return mul(mBackdropTransform, float4(pos.xy, 0, 1.0)).xy; +} + +VS_OUTPUT LayerQuadVS(const VS_INPUT aVertex) +{ + VS_OUTPUT outp; + float4 position = TransformedPosition(aVertex.vPosition); + + outp.vPosition = VertexPosition(position); + outp.vTexCoords = TexCoords(aVertex.vPosition.xy); + + return outp; +} + +float3 MaskCoords(float4 aPosition) +{ + // We use the w coord to do non-perspective correct interpolation: + // the quad might be transformed in 3D, in which case it will have some + // perspective. The graphics card will do perspective-correct interpolation + // of the texture, but our mask is already transformed and so we require + // linear interpolation. Therefore, we must correct the interpolation + // ourselves, we do this by multiplying all coords by w here, and dividing by + // w in the pixel shader (post-interpolation), we pass w in outp.vMaskCoords.z. + // See http://en.wikipedia.org/wiki/Texture_mapping#Perspective_correctness + return float3(mul(mMaskTransform, (aPosition / aPosition.w)).xy, 1.0) * aPosition.w; +} + +VS_MASK_OUTPUT LayerQuadMaskVS(const VS_INPUT aVertex) +{ + float4 position = TransformedPosition(aVertex.vPosition); + + VS_MASK_OUTPUT outp; + outp.vPosition = VertexPosition(position); + outp.vMaskCoords = MaskCoords(position); + outp.vTexCoords = TexCoords(aVertex.vPosition.xy); + return outp; +} + +VS_OUTPUT LayerDynamicVS(const VS_TEX_INPUT aVertex) +{ + VS_OUTPUT outp; + + float4 position = float4(aVertex.vPosition, 0, 1); + position = mul(mLayerTransform, position); + outp.vPosition = VertexPosition(position); + + outp.vTexCoords = aVertex.vTexCoords; + + return outp; +} + +VS_MASK_OUTPUT LayerDynamicMaskVS(const VS_TEX_INPUT aVertex) +{ + VS_MASK_OUTPUT outp; + + float4 position = float4(aVertex.vPosition, 0, 1); + position = mul(mLayerTransform, position); + outp.vPosition = VertexPosition(position); + + // calculate the position on the mask texture + outp.vMaskCoords = MaskCoords(position); + outp.vTexCoords = aVertex.vTexCoords; + return outp; +} + +float4 RGBAShaderMask(const VS_MASK_OUTPUT aVertex) : SV_Target +{ + float2 maskCoords = aVertex.vMaskCoords.xy / aVertex.vMaskCoords.z; + float mask = tMask.Sample(sSampler, maskCoords).r; + return tRGB.Sample(sSampler, aVertex.vTexCoords) * fLayerOpacity * mask; +} + +float4 RGBShaderMask(const VS_MASK_OUTPUT aVertex) : SV_Target +{ + float4 result; + result = tRGB.Sample(sSampler, aVertex.vTexCoords) * fLayerOpacity; + result.a = fLayerOpacity; + + float2 maskCoords = aVertex.vMaskCoords.xy / aVertex.vMaskCoords.z; + float mask = tMask.Sample(sSampler, maskCoords).r; + return result * mask; +} + +/* From Rec601: +[R] [1.1643835616438356, 0.0, 1.5960267857142858] [ Y - 16] +[G] = [1.1643835616438358, -0.3917622900949137, -0.8129676472377708] x [Cb - 128] +[B] [1.1643835616438356, 2.017232142857143, 8.862867620416422e-17] [Cr - 128] + +For [0,1] instead of [0,255], and to 5 places: +[R] [1.16438, 0.00000, 1.59603] [ Y - 0.06275] +[G] = [1.16438, -0.39176, -0.81297] x [Cb - 0.50196] +[B] [1.16438, 2.01723, 0.00000] [Cr - 0.50196] + +From Rec709: +[R] [1.1643835616438356, 4.2781193979771426e-17, 1.7927410714285714] [ Y - 16] +[G] = [1.1643835616438358, -0.21324861427372963, -0.532909328559444] x [Cb - 128] +[B] [1.1643835616438356, 2.1124017857142854, 0.0] [Cr - 128] + +For [0,1] instead of [0,255], and to 5 places: +[R] [1.16438, 0.00000, 1.79274] [ Y - 0.06275] +[G] = [1.16438, -0.21325, -0.53291] x [Cb - 0.50196] +[B] [1.16438, 2.11240, 0.00000] [Cr - 0.50196] +*/ +float4 CalculateYCbCrColor(const float2 aTexCoords) +{ + float3 yuv = float3( + tY.Sample(sSampler, aTexCoords).r, + tCb.Sample(sSampler, aTexCoords).r, + tCr.Sample(sSampler, aTexCoords).r); + yuv = yuv * fCoefficient - float3(0.06275, 0.50196, 0.50196); + + return float4(mul(mYuvColorMatrix, yuv), 1.0); +} + +float4 CalculateNV12Color(const float2 aTexCoords) +{ + float3 yuv = float3( + tY.Sample(sSampler, aTexCoords).r, + tCb.Sample(sSampler, aTexCoords).r, + tCb.Sample(sSampler, aTexCoords).g); + yuv = yuv * fCoefficient - float3(0.06275, 0.50196, 0.50196); + + return float4(mul(mYuvColorMatrix, yuv), 1.0); +} + +float4 YCbCrShaderMask(const VS_MASK_OUTPUT aVertex) : SV_Target +{ + float2 maskCoords = aVertex.vMaskCoords.xy / aVertex.vMaskCoords.z; + float mask = tMask.Sample(sSampler, maskCoords).r; + + return CalculateYCbCrColor(aVertex.vTexCoords) * fLayerOpacity * mask; +} + +float4 NV12ShaderMask(const VS_MASK_OUTPUT aVertex) : SV_Target +{ + float2 maskCoords = aVertex.vMaskCoords.xy / aVertex.vMaskCoords.z; + float mask = tMask.Sample(sSampler, maskCoords).r; + + return CalculateNV12Color(aVertex.vTexCoords) * fLayerOpacity * mask; +} + +PS_OUTPUT ComponentAlphaShaderMask(const VS_MASK_OUTPUT aVertex) : SV_Target +{ + PS_OUTPUT result; + + result.vSrc = tRGB.Sample(sSampler, aVertex.vTexCoords); + result.vAlpha = 1.0 - tRGBWhite.Sample(sSampler, aVertex.vTexCoords) + result.vSrc; + result.vSrc.a = result.vAlpha.g; + + float2 maskCoords = aVertex.vMaskCoords.xy / aVertex.vMaskCoords.z; + float mask = tMask.Sample(sSampler, maskCoords).r; + result.vSrc *= fLayerOpacity * mask; + result.vAlpha *= fLayerOpacity * mask; + + return result; +} + +float4 SolidColorShaderMask(const VS_MASK_OUTPUT aVertex) : SV_Target +{ + float2 maskCoords = aVertex.vMaskCoords.xy / aVertex.vMaskCoords.z; + float mask = tMask.Sample(sSampler, maskCoords).r; + return fLayerColor * mask; +} + +/* + * Un-masked versions + ************************************************************* + */ +float4 RGBAShader(const VS_OUTPUT aVertex) : SV_Target +{ + return tRGB.Sample(sSampler, aVertex.vTexCoords) * fLayerOpacity; +} + +float4 RGBShader(const VS_OUTPUT aVertex) : SV_Target +{ + float4 result; + result = tRGB.Sample(sSampler, aVertex.vTexCoords) * fLayerOpacity; + result.a = fLayerOpacity; + return result; +} + +float4 YCbCrShader(const VS_OUTPUT aVertex) : SV_Target +{ + return CalculateYCbCrColor(aVertex.vTexCoords) * fLayerOpacity; +} + +float4 NV12Shader(const VS_OUTPUT aVertex) : SV_Target +{ + return CalculateNV12Color(aVertex.vTexCoords) * fLayerOpacity; +} + +PS_OUTPUT ComponentAlphaShader(const VS_OUTPUT aVertex) : SV_Target +{ + PS_OUTPUT result; + + result.vSrc = tRGB.Sample(sSampler, aVertex.vTexCoords); + result.vAlpha = 1.0 - tRGBWhite.Sample(sSampler, aVertex.vTexCoords) + result.vSrc; + result.vSrc.a = result.vAlpha.g; + result.vSrc *= fLayerOpacity; + result.vAlpha *= fLayerOpacity; + return result; +} + +float4 SolidColorShader(const VS_OUTPUT aVertex) : SV_Target +{ + return fLayerColor; +} + +// Mix-blend compatible vertex shaders. +VS_BLEND_OUTPUT LayerQuadBlendVS(const VS_INPUT aVertex) +{ + VS_OUTPUT v = LayerQuadVS(aVertex); + + VS_BLEND_OUTPUT o; + o.vPosition = v.vPosition; + o.vTexCoords = v.vTexCoords; + o.vMaskCoords = float3(0, 0, 0); + o.vBackdropCoords = BackdropPosition(v.vPosition); + return o; +} + +VS_BLEND_OUTPUT LayerQuadBlendMaskVS(const VS_INPUT aVertex) +{ + VS_MASK_OUTPUT v = LayerQuadMaskVS(aVertex); + + VS_BLEND_OUTPUT o; + o.vPosition = v.vPosition; + o.vTexCoords = v.vTexCoords; + o.vMaskCoords = v.vMaskCoords; + o.vBackdropCoords = BackdropPosition(v.vPosition); + return o; +} + +VS_BLEND_OUTPUT LayerDynamicBlendVS(const VS_TEX_INPUT aVertex) +{ + VS_OUTPUT v = LayerDynamicVS(aVertex); + + VS_BLEND_OUTPUT o; + o.vPosition = v.vPosition; + o.vTexCoords = v.vTexCoords; + o.vMaskCoords = float3(0, 0, 0); + o.vBackdropCoords = BackdropPosition(v.vPosition); + return o; +} + +VS_BLEND_OUTPUT LayerDynamicBlendMaskVS(const VS_TEX_INPUT aVertex) +{ + VS_MASK_OUTPUT v = LayerDynamicMaskVS(aVertex); + + VS_BLEND_OUTPUT o; + o.vPosition = v.vPosition; + o.vTexCoords = v.vTexCoords; + o.vMaskCoords = v.vMaskCoords; + o.vBackdropCoords = BackdropPosition(v.vPosition); + return o; +} + +// The layer type and mask type are specified as constants. We use these to +// call the correct pixel shader to determine the source color for blending. +// Unfortunately this also requires some boilerplate to convert VS_BLEND_OUTPUT +// to a compatible pixel shader input. +float4 ComputeBlendSourceColor(const VS_BLEND_OUTPUT aVertex) +{ + if (iBlendConfig.y == PS_MASK_NONE) { + VS_OUTPUT tmp; + tmp.vPosition = aVertex.vPosition; + tmp.vTexCoords = aVertex.vTexCoords; + if (iBlendConfig.x == PS_LAYER_RGB) { + return RGBShader(tmp); + } else if (iBlendConfig.x == PS_LAYER_RGBA) { + return RGBAShader(tmp); + } else if (iBlendConfig.x == PS_LAYER_YCBCR) { + return YCbCrShader(tmp); + } else if (iBlendConfig.x == PS_LAYER_NV12) { + return NV12Shader(tmp); + } else { + return SolidColorShader(tmp); + } + } else if (iBlendConfig.y == PS_MASK) { + VS_MASK_OUTPUT tmp; + tmp.vPosition = aVertex.vPosition; + tmp.vTexCoords = aVertex.vTexCoords; + tmp.vMaskCoords = aVertex.vMaskCoords; + + if (iBlendConfig.x == PS_LAYER_RGB) { + return RGBShaderMask(tmp); + } else if (iBlendConfig.x == PS_LAYER_RGBA) { + return RGBAShaderMask(tmp); + } else if (iBlendConfig.x == PS_LAYER_YCBCR) { + return YCbCrShaderMask(tmp); + } else if (iBlendConfig.x == PS_LAYER_NV12) { + return NV12ShaderMask(tmp); + } else { + return SolidColorShaderMask(tmp); + } + } else { + return float4(0.0, 0.0, 0.0, 1.0); + } +} + +float3 ChooseBlendFunc(float3 dest, float3 src) +{ + [flatten] switch (iBlendConfig.z) { + case PS_BLEND_MULTIPLY: + return BlendMultiply(dest, src); + case PS_BLEND_SCREEN: + return BlendScreen(dest, src); + case PS_BLEND_OVERLAY: + return BlendOverlay(dest, src); + case PS_BLEND_DARKEN: + return BlendDarken(dest, src); + case PS_BLEND_LIGHTEN: + return BlendLighten(dest, src); + case PS_BLEND_COLOR_DODGE: + return BlendColorDodge(dest, src); + case PS_BLEND_COLOR_BURN: + return BlendColorBurn(dest, src); + case PS_BLEND_HARD_LIGHT: + return BlendHardLight(dest, src); + case PS_BLEND_SOFT_LIGHT: + return BlendSoftLight(dest, src); + case PS_BLEND_DIFFERENCE: + return BlendDifference(dest, src); + case PS_BLEND_EXCLUSION: + return BlendExclusion(dest, src); + case PS_BLEND_HUE: + return BlendHue(dest, src); + case PS_BLEND_SATURATION: + return BlendSaturation(dest, src); + case PS_BLEND_COLOR: + return BlendColor(dest, src); + case PS_BLEND_LUMINOSITY: + return BlendLuminosity(dest, src); + default: + return float3(0, 0, 0); + } +} + +float4 BlendShader(const VS_BLEND_OUTPUT aVertex) : SV_Target +{ + float4 backdrop = tBackdrop.Sample(sSampler, aVertex.vBackdropCoords.xy); + float4 source = ComputeBlendSourceColor(aVertex); + + // Shortcut when the backdrop or source alpha is 0, otherwise we may leak + // infinity into the blend function and return incorrect results. + if (backdrop.a == 0.0) { + return source; + } + if (source.a == 0.0) { + return float4(0, 0, 0, 0); + } + + // The spec assumes there is no premultiplied alpha. The backdrop is always + // premultiplied, so undo the premultiply. If the source is premultiplied we + // must fix that as well. + backdrop.rgb /= backdrop.a; + if (iBlendConfig.w) { + source.rgb /= source.a; + } + + float4 result; + result.rgb = ChooseBlendFunc(backdrop.rgb, source.rgb); + result.a = source.a; + + // Factor backdrop alpha, then premultiply for the final OP_OVER. + result.rgb = (1.0 - backdrop.a) * source.rgb + backdrop.a * result.rgb; + result.rgb *= result.a; + return result; +} diff --git a/gfx/layers/d3d11/DeviceAttachmentsD3D11.cpp b/gfx/layers/d3d11/DeviceAttachmentsD3D11.cpp new file mode 100644 index 0000000000..5cca7b111f --- /dev/null +++ b/gfx/layers/d3d11/DeviceAttachmentsD3D11.cpp @@ -0,0 +1,342 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DeviceAttachmentsD3D11.h" +#include "mozilla/Telemetry.h" +#include "mozilla/layers/Compositor.h" +#include "CompositorD3D11Shaders.h" +#include "Layers.h" +#include "ShaderDefinitionsD3D11.h" + +namespace mozilla { +namespace layers { + +using namespace gfx; + +static const size_t kInitialMaximumTriangles = 64; + +DeviceAttachmentsD3D11::DeviceAttachmentsD3D11(ID3D11Device* device) + : mMaximumTriangles(kInitialMaximumTriangles), + mDevice(device), + mContinueInit(true), + mInitialized(false), + mDeviceReset(false) {} + +DeviceAttachmentsD3D11::~DeviceAttachmentsD3D11() {} + +/* static */ +RefPtr DeviceAttachmentsD3D11::Create( + ID3D11Device* aDevice) { + // We don't return null even if the attachments object even if it fails to + // initialize, so the compositor can grab the failure ID. + RefPtr attachments = + new DeviceAttachmentsD3D11(aDevice); + attachments->Initialize(); + return attachments.forget(); +} + +bool DeviceAttachmentsD3D11::Initialize() { + D3D11_INPUT_ELEMENT_DESC layout[] = { + {"POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, + D3D11_INPUT_PER_VERTEX_DATA, 0}, + }; + + HRESULT hr; + hr = mDevice->CreateInputLayout( + layout, sizeof(layout) / sizeof(D3D11_INPUT_ELEMENT_DESC), LayerQuadVS, + sizeof(LayerQuadVS), getter_AddRefs(mInputLayout)); + + if (Failed(hr, "CreateInputLayout")) { + mInitFailureId = "FEATURE_FAILURE_D3D11_INPUT_LAYOUT"; + return false; + } + + Vertex vertices[] = {{{0.0, 0.0}}, {{1.0, 0.0}}, {{0.0, 1.0}}, {{1.0, 1.0}}}; + CD3D11_BUFFER_DESC bufferDesc(sizeof(vertices), D3D11_BIND_VERTEX_BUFFER); + D3D11_SUBRESOURCE_DATA data; + data.pSysMem = (void*)vertices; + + hr = mDevice->CreateBuffer(&bufferDesc, &data, getter_AddRefs(mVertexBuffer)); + if (Failed(hr, "create vertex buffer")) { + mInitFailureId = "FEATURE_FAILURE_D3D11_VERTEX_BUFFER"; + return false; + } + + // Create a second input layout for layers with dynamic geometry. + D3D11_INPUT_ELEMENT_DESC dynamicLayout[] = { + {"POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, + D3D11_INPUT_PER_VERTEX_DATA, 0}, + {"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 8, + D3D11_INPUT_PER_VERTEX_DATA, 0}, + }; + + hr = mDevice->CreateInputLayout( + dynamicLayout, sizeof(dynamicLayout) / sizeof(D3D11_INPUT_ELEMENT_DESC), + LayerDynamicVS, sizeof(LayerDynamicVS), + getter_AddRefs(mDynamicInputLayout)); + + if (Failed(hr, "CreateInputLayout")) { + mInitFailureId = "FEATURE_FAILURE_D3D11_INPUT_LAYOUT"; + return false; + } + + // Allocate memory for the dynamic vertex buffer. + bufferDesc = CD3D11_BUFFER_DESC( + sizeof(TexturedVertex) * mMaximumTriangles * 3, D3D11_BIND_VERTEX_BUFFER, + D3D11_USAGE_DYNAMIC, D3D11_CPU_ACCESS_WRITE); + + hr = mDevice->CreateBuffer(&bufferDesc, nullptr, + getter_AddRefs(mDynamicVertexBuffer)); + if (Failed(hr, "create dynamic vertex buffer")) { + mInitFailureId = "FEATURE_FAILURE_D3D11_VERTEX_BUFFER"; + return false; + } + + if (!CreateShaders()) { + mInitFailureId = "FEATURE_FAILURE_D3D11_CREATE_SHADERS"; + return false; + } + + CD3D11_BUFFER_DESC cBufferDesc(sizeof(VertexShaderConstants), + D3D11_BIND_CONSTANT_BUFFER, + D3D11_USAGE_DYNAMIC, D3D11_CPU_ACCESS_WRITE); + + hr = mDevice->CreateBuffer(&cBufferDesc, nullptr, + getter_AddRefs(mVSConstantBuffer)); + if (Failed(hr, "create vs buffer")) { + mInitFailureId = "FEATURE_FAILURE_D3D11_VS_BUFFER"; + return false; + } + + cBufferDesc.ByteWidth = sizeof(PixelShaderConstants); + hr = mDevice->CreateBuffer(&cBufferDesc, nullptr, + getter_AddRefs(mPSConstantBuffer)); + if (Failed(hr, "create ps buffer")) { + mInitFailureId = "FEATURE_FAILURE_D3D11_PS_BUFFER"; + return false; + } + + CD3D11_RASTERIZER_DESC rastDesc(D3D11_DEFAULT); + rastDesc.CullMode = D3D11_CULL_NONE; + rastDesc.ScissorEnable = TRUE; + + hr = mDevice->CreateRasterizerState(&rastDesc, + getter_AddRefs(mRasterizerState)); + if (Failed(hr, "create rasterizer")) { + mInitFailureId = "FEATURE_FAILURE_D3D11_RASTERIZER"; + return false; + } + + CD3D11_SAMPLER_DESC samplerDesc(D3D11_DEFAULT); + hr = mDevice->CreateSamplerState(&samplerDesc, + getter_AddRefs(mLinearSamplerState)); + if (Failed(hr, "create linear sampler")) { + mInitFailureId = "FEATURE_FAILURE_D3D11_LINEAR_SAMPLER"; + return false; + } + + samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; + hr = mDevice->CreateSamplerState(&samplerDesc, + getter_AddRefs(mPointSamplerState)); + if (Failed(hr, "create point sampler")) { + mInitFailureId = "FEATURE_FAILURE_D3D11_POINT_SAMPLER"; + return false; + } + + CD3D11_BLEND_DESC blendDesc(D3D11_DEFAULT); + D3D11_RENDER_TARGET_BLEND_DESC rtBlendPremul = {TRUE, + D3D11_BLEND_ONE, + D3D11_BLEND_INV_SRC_ALPHA, + D3D11_BLEND_OP_ADD, + D3D11_BLEND_ONE, + D3D11_BLEND_INV_SRC_ALPHA, + D3D11_BLEND_OP_ADD, + D3D11_COLOR_WRITE_ENABLE_ALL}; + blendDesc.RenderTarget[0] = rtBlendPremul; + hr = mDevice->CreateBlendState(&blendDesc, getter_AddRefs(mPremulBlendState)); + if (Failed(hr, "create pm blender")) { + mInitFailureId = "FEATURE_FAILURE_D3D11_PM_BLENDER"; + return false; + } + + D3D11_RENDER_TARGET_BLEND_DESC rtBlendNonPremul = { + TRUE, + D3D11_BLEND_SRC_ALPHA, + D3D11_BLEND_INV_SRC_ALPHA, + D3D11_BLEND_OP_ADD, + D3D11_BLEND_ONE, + D3D11_BLEND_INV_SRC_ALPHA, + D3D11_BLEND_OP_ADD, + D3D11_COLOR_WRITE_ENABLE_ALL}; + blendDesc.RenderTarget[0] = rtBlendNonPremul; + hr = mDevice->CreateBlendState(&blendDesc, + getter_AddRefs(mNonPremulBlendState)); + if (Failed(hr, "create npm blender")) { + mInitFailureId = "FEATURE_FAILURE_D3D11_NPM_BLENDER"; + return false; + } + + if (LayerManager::LayersComponentAlphaEnabled()) { + D3D11_RENDER_TARGET_BLEND_DESC rtBlendComponent = { + TRUE, + D3D11_BLEND_ONE, + D3D11_BLEND_INV_SRC1_COLOR, + D3D11_BLEND_OP_ADD, + D3D11_BLEND_ONE, + D3D11_BLEND_INV_SRC_ALPHA, + D3D11_BLEND_OP_ADD, + D3D11_COLOR_WRITE_ENABLE_ALL}; + blendDesc.RenderTarget[0] = rtBlendComponent; + hr = mDevice->CreateBlendState(&blendDesc, + getter_AddRefs(mComponentBlendState)); + if (Failed(hr, "create component blender")) { + mInitFailureId = "FEATURE_FAILURE_D3D11_COMP_BLENDER"; + return false; + } + } + + D3D11_RENDER_TARGET_BLEND_DESC rtBlendDisabled = { + FALSE, + D3D11_BLEND_SRC_ALPHA, + D3D11_BLEND_INV_SRC_ALPHA, + D3D11_BLEND_OP_ADD, + D3D11_BLEND_ONE, + D3D11_BLEND_INV_SRC_ALPHA, + D3D11_BLEND_OP_ADD, + D3D11_COLOR_WRITE_ENABLE_ALL}; + blendDesc.RenderTarget[0] = rtBlendDisabled; + hr = mDevice->CreateBlendState(&blendDesc, + getter_AddRefs(mDisabledBlendState)); + if (Failed(hr, "create null blender")) { + mInitFailureId = "FEATURE_FAILURE_D3D11_NULL_BLENDER"; + return false; + } + + if (!InitSyncObject()) { + mInitFailureId = "FEATURE_FAILURE_D3D11_OBJ_SYNC"; + return false; + } + + mInitialized = true; + return true; +} + +bool DeviceAttachmentsD3D11::InitSyncObject() { + // Sync object is not supported on WARP. + if (DeviceManagerDx::Get()->IsWARP()) { + return true; + } + + MOZ_ASSERT(!mSyncObject); + MOZ_ASSERT(mDevice); + + mSyncObject = SyncObjectHost::CreateSyncObjectHost(mDevice); + MOZ_ASSERT(mSyncObject); + + return mSyncObject->Init(); +} + +bool DeviceAttachmentsD3D11::InitBlendShaders() { + if (!mVSQuadBlendShader[MaskType::MaskNone]) { + InitVertexShader(sLayerQuadBlendVS, mVSQuadBlendShader, MaskType::MaskNone); + InitVertexShader(sLayerQuadBlendMaskVS, mVSQuadBlendShader, MaskType::Mask); + } + + if (!mVSDynamicBlendShader[MaskType::MaskNone]) { + InitVertexShader(sLayerDynamicBlendVS, mVSDynamicBlendShader, + MaskType::MaskNone); + InitVertexShader(sLayerDynamicBlendMaskVS, mVSDynamicBlendShader, + MaskType::Mask); + } + + if (!mBlendShader[MaskType::MaskNone]) { + InitPixelShader(sBlendShader, mBlendShader, MaskType::MaskNone); + } + return mContinueInit; +} + +bool DeviceAttachmentsD3D11::CreateShaders() { + InitVertexShader(sLayerQuadVS, mVSQuadShader, MaskType::MaskNone); + InitVertexShader(sLayerQuadMaskVS, mVSQuadShader, MaskType::Mask); + + InitVertexShader(sLayerDynamicVS, mVSDynamicShader, MaskType::MaskNone); + InitVertexShader(sLayerDynamicMaskVS, mVSDynamicShader, MaskType::Mask); + + InitPixelShader(sSolidColorShader, mSolidColorShader, MaskType::MaskNone); + InitPixelShader(sSolidColorShaderMask, mSolidColorShader, MaskType::Mask); + InitPixelShader(sRGBShader, mRGBShader, MaskType::MaskNone); + InitPixelShader(sRGBShaderMask, mRGBShader, MaskType::Mask); + InitPixelShader(sRGBAShader, mRGBAShader, MaskType::MaskNone); + InitPixelShader(sRGBAShaderMask, mRGBAShader, MaskType::Mask); + InitPixelShader(sYCbCrShader, mYCbCrShader, MaskType::MaskNone); + InitPixelShader(sYCbCrShaderMask, mYCbCrShader, MaskType::Mask); + InitPixelShader(sNV12Shader, mNV12Shader, MaskType::MaskNone); + InitPixelShader(sNV12ShaderMask, mNV12Shader, MaskType::Mask); + if (LayerManager::LayersComponentAlphaEnabled()) { + InitPixelShader(sComponentAlphaShader, mComponentAlphaShader, + MaskType::MaskNone); + InitPixelShader(sComponentAlphaShaderMask, mComponentAlphaShader, + MaskType::Mask); + } + + return mContinueInit; +} + +void DeviceAttachmentsD3D11::InitVertexShader(const ShaderBytes& aShader, + ID3D11VertexShader** aOut) { + if (!mContinueInit) { + return; + } + if (Failed(mDevice->CreateVertexShader(aShader.mData, aShader.mLength, + nullptr, aOut), + "create vs")) { + mContinueInit = false; + } +} + +void DeviceAttachmentsD3D11::InitPixelShader(const ShaderBytes& aShader, + ID3D11PixelShader** aOut) { + if (!mContinueInit) { + return; + } + if (Failed(mDevice->CreatePixelShader(aShader.mData, aShader.mLength, nullptr, + aOut), + "create ps")) { + mContinueInit = false; + } +} + +bool DeviceAttachmentsD3D11::Failed(HRESULT hr, const char* aContext) { + if (SUCCEEDED(hr)) { + return false; + } + + gfxCriticalNote << "[D3D11] " << aContext << " failed: " << hexa(hr); + return true; +} + +bool DeviceAttachmentsD3D11::EnsureTriangleBuffer(size_t aNumTriangles) { + if (aNumTriangles > mMaximumTriangles) { + CD3D11_BUFFER_DESC bufferDesc(sizeof(TexturedVertex) * aNumTriangles * 3, + D3D11_BIND_VERTEX_BUFFER, D3D11_USAGE_DYNAMIC, + D3D11_CPU_ACCESS_WRITE); + + HRESULT hr = mDevice->CreateBuffer(&bufferDesc, nullptr, + getter_AddRefs(mDynamicVertexBuffer)); + + if (Failed(hr, "resize dynamic vertex buffer")) { + return false; + } + + mMaximumTriangles = aNumTriangles; + } + + MOZ_ASSERT(mMaximumTriangles >= aNumTriangles); + return true; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/d3d11/DeviceAttachmentsD3D11.h b/gfx/layers/d3d11/DeviceAttachmentsD3D11.h new file mode 100644 index 0000000000..2d655ceb2f --- /dev/null +++ b/gfx/layers/d3d11/DeviceAttachmentsD3D11.h @@ -0,0 +1,116 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_layers_d3d11_DeviceAttachmentsD3D11_h +#define mozilla_gfx_layers_d3d11_DeviceAttachmentsD3D11_h + +#include "mozilla/EnumeratedArray.h" +#include "mozilla/RefPtr.h" +#include "mozilla/gfx/DeviceManagerDx.h" +#include "mozilla/layers/CompositorTypes.h" +#include "mozilla/layers/SyncObject.h" +#include +#include + +namespace mozilla { +namespace layers { + +struct ShaderBytes; + +class DeviceAttachmentsD3D11 final { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DeviceAttachmentsD3D11); + + public: + static RefPtr Create(ID3D11Device* aDevice); + + bool InitBlendShaders(); + bool EnsureTriangleBuffer(size_t aNumTriangles); + + bool IsValid() const { return mInitialized; } + const nsCString& GetFailureId() const { + MOZ_ASSERT(!IsValid()); + return mInitFailureId; + } + + typedef EnumeratedArray> + VertexShaderArray; + typedef EnumeratedArray> + PixelShaderArray; + + RefPtr mInputLayout; + RefPtr mDynamicInputLayout; + + RefPtr mVertexBuffer; + RefPtr mDynamicVertexBuffer; + + VertexShaderArray mVSQuadShader; + VertexShaderArray mVSQuadBlendShader; + + VertexShaderArray mVSDynamicShader; + VertexShaderArray mVSDynamicBlendShader; + + PixelShaderArray mSolidColorShader; + PixelShaderArray mRGBAShader; + PixelShaderArray mRGBShader; + PixelShaderArray mYCbCrShader; + PixelShaderArray mNV12Shader; + PixelShaderArray mComponentAlphaShader; + PixelShaderArray mBlendShader; + RefPtr mPSConstantBuffer; + RefPtr mVSConstantBuffer; + RefPtr mRasterizerState; + RefPtr mLinearSamplerState; + RefPtr mPointSamplerState; + + RefPtr mPremulBlendState; + RefPtr mNonPremulBlendState; + RefPtr mComponentBlendState; + RefPtr mDisabledBlendState; + + RefPtr mSyncObject; + + void SetDeviceReset() { mDeviceReset = true; } + bool IsDeviceReset() const { return mDeviceReset; } + + private: + explicit DeviceAttachmentsD3D11(ID3D11Device* device); + ~DeviceAttachmentsD3D11(); + + bool Initialize(); + bool CreateShaders(); + bool InitSyncObject(); + + void InitVertexShader(const ShaderBytes& aShader, VertexShaderArray& aArray, + MaskType aMaskType) { + InitVertexShader(aShader, getter_AddRefs(aArray[aMaskType])); + } + void InitPixelShader(const ShaderBytes& aShader, PixelShaderArray& aArray, + MaskType aMaskType) { + InitPixelShader(aShader, getter_AddRefs(aArray[aMaskType])); + } + + void InitVertexShader(const ShaderBytes& aShader, ID3D11VertexShader** aOut); + void InitPixelShader(const ShaderBytes& aShader, ID3D11PixelShader** aOut); + + bool Failed(HRESULT hr, const char* aContext); + + private: + size_t mMaximumTriangles; + + // Only used during initialization. + RefPtr mDevice; + bool mContinueInit; + bool mInitialized; + bool mDeviceReset; + nsCString mInitFailureId; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_gfx_layers_d3d11_DeviceAttachmentsD3D11_h diff --git a/gfx/layers/d3d11/DiagnosticsD3D11.cpp b/gfx/layers/d3d11/DiagnosticsD3D11.cpp new file mode 100644 index 0000000000..6d37280036 --- /dev/null +++ b/gfx/layers/d3d11/DiagnosticsD3D11.cpp @@ -0,0 +1,83 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DiagnosticsD3D11.h" +#include "mozilla/layers/Diagnostics.h" +#include "mozilla/layers/HelpersD3D11.h" + +namespace mozilla { +namespace layers { + +DiagnosticsD3D11::DiagnosticsD3D11(ID3D11Device* aDevice, + ID3D11DeviceContext* aContext) + : mDevice(aDevice), mContext(aContext) {} + +void DiagnosticsD3D11::Start(uint32_t aPixelsPerFrame) { + mPrevFrame = mCurrentFrame; + mCurrentFrame = FrameQueries(); + + CD3D11_QUERY_DESC desc(D3D11_QUERY_PIPELINE_STATISTICS); + mDevice->CreateQuery(&desc, getter_AddRefs(mCurrentFrame.stats)); + if (mCurrentFrame.stats) { + mContext->Begin(mCurrentFrame.stats); + } + mCurrentFrame.pixelsPerFrame = aPixelsPerFrame; + + desc = CD3D11_QUERY_DESC(D3D11_QUERY_TIMESTAMP_DISJOINT); + mDevice->CreateQuery(&desc, getter_AddRefs(mCurrentFrame.timing)); + if (mCurrentFrame.timing) { + mContext->Begin(mCurrentFrame.timing); + } + + desc = CD3D11_QUERY_DESC(D3D11_QUERY_TIMESTAMP); + mDevice->CreateQuery(&desc, getter_AddRefs(mCurrentFrame.frameBegin)); + if (mCurrentFrame.frameBegin) { + mContext->End(mCurrentFrame.frameBegin); + } +} + +void DiagnosticsD3D11::End() { + if (mCurrentFrame.stats) { + mContext->End(mCurrentFrame.stats); + } + if (mCurrentFrame.frameBegin) { + CD3D11_QUERY_DESC desc(D3D11_QUERY_TIMESTAMP); + mDevice->CreateQuery(&desc, getter_AddRefs(mCurrentFrame.frameEnd)); + if (mCurrentFrame.frameEnd) { + mContext->End(mCurrentFrame.frameEnd); + } + } + if (mCurrentFrame.timing) { + mContext->End(mCurrentFrame.timing); + } +} + +void DiagnosticsD3D11::Cancel() { mCurrentFrame = FrameQueries(); } + +void DiagnosticsD3D11::Query(GPUStats* aStats) { + // Collect pixel shader stats. + if (mPrevFrame.stats) { + D3D11_QUERY_DATA_PIPELINE_STATISTICS stats; + if (WaitForGPUQuery(mDevice, mContext, mPrevFrame.stats, &stats)) { + aStats->mInvalidPixels = mPrevFrame.pixelsPerFrame; + aStats->mPixelsFilled = uint32_t(stats.PSInvocations); + } + } + if (mPrevFrame.timing) { + UINT64 begin, end; + D3D11_QUERY_DATA_TIMESTAMP_DISJOINT timing; + if (WaitForGPUQuery(mDevice, mContext, mPrevFrame.timing, &timing) && + !timing.Disjoint && + WaitForGPUQuery(mDevice, mContext, mPrevFrame.frameBegin, &begin) && + WaitForGPUQuery(mDevice, mContext, mPrevFrame.frameEnd, &end)) { + float timeMs = float(end - begin) / float(timing.Frequency) * 1000.0f; + aStats->mDrawTime = Some(timeMs); + } + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/d3d11/DiagnosticsD3D11.h b/gfx/layers/d3d11/DiagnosticsD3D11.h new file mode 100644 index 0000000000..c4e2226a62 --- /dev/null +++ b/gfx/layers/d3d11/DiagnosticsD3D11.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_layers_d3d11_DiagnosticsD3D11_h +#define mozilla_gfx_layers_d3d11_DiagnosticsD3D11_h + +#include +#include "mozilla/RefPtr.h" +#include + +namespace mozilla { +namespace layers { + +struct GPUStats; + +class DiagnosticsD3D11 { + public: + DiagnosticsD3D11(ID3D11Device* aDevice, ID3D11DeviceContext* aContext); + + void Start(uint32_t aPixelsPerFrame); + void End(); + void Cancel(); + + void Query(GPUStats* aStats); + + private: + RefPtr mDevice; + RefPtr mContext; + + // When using the diagnostic overlay, we double-buffer some queries for + // frame statistics. + struct FrameQueries { + FrameQueries() : pixelsPerFrame(0) {} + + RefPtr stats; + RefPtr timing; + RefPtr frameBegin; + RefPtr frameEnd; + uint32_t pixelsPerFrame; + }; + FrameQueries mPrevFrame; + FrameQueries mCurrentFrame; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_gfx_layers_d3d11_DiagnosticsD3D11_h diff --git a/gfx/layers/d3d11/HelpersD3D11.h b/gfx/layers/d3d11/HelpersD3D11.h new file mode 100644 index 0000000000..337212aadc --- /dev/null +++ b/gfx/layers/d3d11/HelpersD3D11.h @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_layers_d3d11_HelpersD3D11_h +#define mozilla_gfx_layers_d3d11_HelpersD3D11_h + +#include +#include "mozilla/Telemetry.h" +#include "mozilla/TimeStamp.h" + +namespace mozilla { +namespace layers { + +template +static inline bool WaitForGPUQuery(ID3D11Device* aDevice, + ID3D11DeviceContext* aContext, + ID3D11Query* aQuery, T* aOut) { + TimeStamp start = TimeStamp::Now(); + while (aContext->GetData(aQuery, aOut, sizeof(*aOut), 0) != S_OK) { + if (aDevice->GetDeviceRemovedReason() != S_OK) { + return false; + } + if (TimeStamp::Now() - start > TimeDuration::FromSeconds(2)) { + return false; + } + Sleep(0); + } + return true; +} + +static inline bool WaitForFrameGPUQuery(ID3D11Device* aDevice, + ID3D11DeviceContext* aContext, + ID3D11Query* aQuery, BOOL* aOut) { + TimeStamp start = TimeStamp::Now(); + bool success = true; + while (aContext->GetData(aQuery, aOut, sizeof(*aOut), 0) != S_OK) { + if (aDevice->GetDeviceRemovedReason() != S_OK) { + return false; + } + if (TimeStamp::Now() - start > TimeDuration::FromSeconds(2)) { + success = false; + break; + } + Sleep(0); + } + Telemetry::AccumulateTimeDelta(Telemetry::GPU_WAIT_TIME_MS, start); + return success; +} + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_gfx_layers_d3d11_HelpersD3D11_h diff --git a/gfx/layers/d3d11/MLGDeviceD3D11.cpp b/gfx/layers/d3d11/MLGDeviceD3D11.cpp new file mode 100644 index 0000000000..219662ead1 --- /dev/null +++ b/gfx/layers/d3d11/MLGDeviceD3D11.cpp @@ -0,0 +1,2034 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MLGDeviceD3D11.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Telemetry.h" +#include "mozilla/WindowsVersion.h" +#include "mozilla/gfx/GPUParent.h" +#include "mozilla/gfx/StackArray.h" +#include "mozilla/layers/DiagnosticsD3D11.h" +#include "mozilla/layers/HelpersD3D11.h" +#include "mozilla/layers/LayerMLGPU.h" +#include "mozilla/layers/MemoryReportingMLGPU.h" +#include "mozilla/layers/ShaderDefinitionsMLGPU.h" +#include "mozilla/layers/UtilityMLGPU.h" +#include "mozilla/widget/CompositorWidget.h" +#include "mozilla/widget/WinCompositorWidget.h" +#include "MLGShaders.h" +#include "TextureD3D11.h" +#include "gfxConfig.h" +#include "mozilla/StaticPrefs_layers.h" +#include "FxROutputHandler.h" + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; +using namespace mozilla::widget; +using namespace mozilla::layers::mlg; + +// Defined in CompositorD3D11.cpp. +bool CanUsePartialPresents(ID3D11Device* aDevice); + +static D3D11_BOX RectToBox(const gfx::IntRect& aRect); + +MLGRenderTargetD3D11::MLGRenderTargetD3D11(const gfx::IntSize& aSize, + MLGRenderTargetFlags aFlags) + : MLGRenderTarget(aFlags), mSize(aSize) {} + +MLGRenderTargetD3D11::~MLGRenderTargetD3D11() { + if (mDepthBuffer) { + sRenderTargetUsage -= mSize.width * mSize.height * 1; + } + ForgetTexture(); +} + +bool MLGRenderTargetD3D11::Initialize(ID3D11Device* aDevice) { + D3D11_TEXTURE2D_DESC desc; + ::ZeroMemory(&desc, sizeof(desc)); + desc.Width = mSize.width; + desc.Height = mSize.height; + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.SampleDesc.Count = 1; + desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE; + + RefPtr texture; + HRESULT hr = + aDevice->CreateTexture2D(&desc, nullptr, getter_AddRefs(texture)); + if (FAILED(hr) || !texture) { + gfxCriticalNote << "Failed to create render target texture: " << hexa(hr); + return false; + } + + return Initialize(aDevice, texture); +} + +bool MLGRenderTargetD3D11::Initialize(ID3D11Device* aDevice, + ID3D11Texture2D* aTexture) { + if (!UpdateTexture(aTexture)) { + return false; + } + if ((mFlags & MLGRenderTargetFlags::ZBuffer) && !CreateDepthBuffer(aDevice)) { + return false; + } + return true; +} + +bool MLGRenderTargetD3D11::UpdateTexture(ID3D11Texture2D* aTexture) { + // Save the view first, in case we can re-use it. + RefPtr view = mRTView.forget(); + + ForgetTexture(); + + if (!aTexture) { + return true; + } + +#ifdef DEBUG + D3D11_TEXTURE2D_DESC desc; + aTexture->GetDesc(&desc); + MOZ_ASSERT(desc.Width == mSize.width && desc.Height == mSize.height); +#endif + + RefPtr device; + aTexture->GetDevice(getter_AddRefs(device)); + + if (view) { + // Check that the view matches the backing texture. + RefPtr resource; + view->GetResource(getter_AddRefs(resource)); + if (resource != aTexture) { + view = nullptr; + } + } + + // If we couldn't re-use a view from before, make one now. + if (!view) { + HRESULT hr = + device->CreateRenderTargetView(aTexture, nullptr, getter_AddRefs(view)); + if (FAILED(hr) || !view) { + gfxCriticalNote << "Failed to create render target view: " << hexa(hr); + return false; + } + } + + mTexture = aTexture; + mRTView = view.forget(); + sRenderTargetUsage += mSize.width * mSize.height * 4; + return true; +} + +void MLGRenderTargetD3D11::ForgetTexture() { + if (mTexture) { + sRenderTargetUsage -= mSize.width * mSize.height * 4; + mTexture = nullptr; + } + mRTView = nullptr; + mTextureSource = nullptr; +} + +bool MLGRenderTargetD3D11::CreateDepthBuffer(ID3D11Device* aDevice) { + D3D11_TEXTURE2D_DESC desc; + ::ZeroMemory(&desc, sizeof(desc)); + desc.Width = mSize.width; + desc.Height = mSize.height; + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.Format = DXGI_FORMAT_D32_FLOAT; + desc.SampleDesc.Count = 1; + desc.SampleDesc.Quality = 0; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.BindFlags = D3D11_BIND_DEPTH_STENCIL; + + RefPtr buffer; + HRESULT hr = aDevice->CreateTexture2D(&desc, nullptr, getter_AddRefs(buffer)); + if (FAILED(hr) || !buffer) { + gfxCriticalNote << "Could not create depth-stencil buffer: " << hexa(hr); + return false; + } + + D3D11_DEPTH_STENCIL_VIEW_DESC viewDesc; + ::ZeroMemory(&viewDesc, sizeof(viewDesc)); + viewDesc.Format = DXGI_FORMAT_D32_FLOAT; + viewDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D; + + RefPtr dsv; + hr = aDevice->CreateDepthStencilView(buffer, &viewDesc, getter_AddRefs(dsv)); + if (FAILED(hr) || !dsv) { + gfxCriticalNote << "Could not create depth-stencil view: " << hexa(hr); + return false; + } + + mDepthBuffer = buffer; + mDepthStencilView = dsv; + sRenderTargetUsage += mSize.width * mSize.height * 1; + return true; +} + +ID3D11DepthStencilView* MLGRenderTargetD3D11::GetDSV() { + return mDepthStencilView; +} + +ID3D11RenderTargetView* MLGRenderTargetD3D11::GetRenderTargetView() { + return mRTView; +} + +IntSize MLGRenderTargetD3D11::GetSize() const { return mSize; } + +MLGTexture* MLGRenderTargetD3D11::GetTexture() { + if (!mTextureSource) { + mTextureSource = new MLGTextureD3D11(mTexture); + } + return mTextureSource; +} + +MLGSwapChainD3D11::MLGSwapChainD3D11(MLGDeviceD3D11* aParent, + ID3D11Device* aDevice) + : mParent(aParent), + mDevice(aDevice), + mWidget(nullptr), + mCanUsePartialPresents(CanUsePartialPresents(aDevice)) {} + +MLGSwapChainD3D11::~MLGSwapChainD3D11() {} + +void MLGSwapChainD3D11::Destroy() { + if (mRT == mParent->GetRenderTarget()) { + mParent->SetRenderTarget(nullptr); + } + mWidget = nullptr; + mRT = nullptr; + mSwapChain = nullptr; + mSwapChain1 = nullptr; +} + +RefPtr MLGSwapChainD3D11::Create(MLGDeviceD3D11* aParent, + ID3D11Device* aDevice, + CompositorWidget* aWidget) { + RefPtr swapChain = new MLGSwapChainD3D11(aParent, aDevice); + if (!swapChain->Initialize(aWidget)) { + return nullptr; + } + return swapChain.forget(); +} + +bool MLGSwapChainD3D11::Initialize(CompositorWidget* aWidget) { + HWND hwnd = aWidget->AsWindows()->GetHwnd(); + + RefPtr dxgiDevice; + mDevice->QueryInterface(dxgiDevice.StartAssignment()); + + RefPtr dxgiFactory; + { + RefPtr adapter; + dxgiDevice->GetAdapter(getter_AddRefs(adapter)); + + adapter->GetParent(IID_PPV_ARGS(dxgiFactory.StartAssignment())); + } + + RefPtr dxgiFactory2; + if (gfxVars::UseDoubleBufferingWithCompositor() && + SUCCEEDED(dxgiFactory->QueryInterface(dxgiFactory2.StartAssignment())) && + dxgiFactory2 && XRE_IsGPUProcess()) { + // DXGI_SCALING_NONE is not available on Windows 7 with the Platform Update: + // This looks awful for things like the awesome bar and browser window + // resizing, so we don't use a flip buffer chain here. (Note when using + // EFFECT_SEQUENTIAL Windows doesn't stretch the surface when resizing). + // + // We choose not to run this on platforms earlier than Windows 10 because + // it appears sometimes this breaks our ability to test ASAP compositing, + // which breaks Talos. + // + // When the GPU process is disabled we don't have a compositor window which + // can lead to issues with Window re-use so we don't use this. + DXGI_SWAP_CHAIN_DESC1 desc; + ::ZeroMemory(&desc, sizeof(desc)); + desc.Width = 0; + desc.Height = 0; + desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + desc.SampleDesc.Count = 1; + desc.SampleDesc.Quality = 0; + desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + desc.BufferCount = 2; + desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; + desc.Scaling = DXGI_SCALING_NONE; + desc.Flags = 0; + + HRESULT hr = dxgiFactory2->CreateSwapChainForHwnd( + mDevice, hwnd, &desc, nullptr, nullptr, getter_AddRefs(mSwapChain1)); + if (SUCCEEDED(hr) && mSwapChain1) { + DXGI_RGBA color = {1.0f, 1.0f, 1.0f, 1.0f}; + mSwapChain1->SetBackgroundColor(&color); + mSwapChain = mSwapChain1; + mIsDoubleBuffered = true; + } else if (aWidget->AsWindows()->GetCompositorHwnd()) { + // Destroy compositor window. + aWidget->AsWindows()->DestroyCompositorWindow(); + hwnd = aWidget->AsWindows()->GetHwnd(); + } + } + + if (!mSwapChain) { + DXGI_SWAP_CHAIN_DESC swapDesc; + ::ZeroMemory(&swapDesc, sizeof(swapDesc)); + swapDesc.BufferDesc.Width = 0; + swapDesc.BufferDesc.Height = 0; + swapDesc.BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + swapDesc.BufferDesc.RefreshRate.Numerator = 60; + swapDesc.BufferDesc.RefreshRate.Denominator = 1; + swapDesc.SampleDesc.Count = 1; + swapDesc.SampleDesc.Quality = 0; + swapDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + swapDesc.BufferCount = 1; + swapDesc.OutputWindow = hwnd; + swapDesc.Windowed = TRUE; + swapDesc.Flags = 0; + swapDesc.SwapEffect = DXGI_SWAP_EFFECT_SEQUENTIAL; + + HRESULT hr = dxgiFactory->CreateSwapChain(dxgiDevice, &swapDesc, + getter_AddRefs(mSwapChain)); + if (FAILED(hr)) { + gfxCriticalNote << "Could not create swap chain: " << hexa(hr); + return false; + } + + // Try to get an IDXGISwapChain1 if we can, for partial presents. + mSwapChain->QueryInterface(mSwapChain1.StartAssignment()); + } + + // We need this because we don't want DXGI to respond to Alt+Enter. + dxgiFactory->MakeWindowAssociation(hwnd, DXGI_MWA_NO_WINDOW_CHANGES); + mWidget = aWidget; + return true; +} + +RefPtr MLGSwapChainD3D11::AcquireBackBuffer() { + RefPtr texture; + HRESULT hr = mSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), + getter_AddRefs(texture)); + if (hr == DXGI_ERROR_INVALID_CALL && + mDevice->GetDeviceRemovedReason() != S_OK) { + // This can happen on some drivers when there's a TDR. + mParent->HandleDeviceReset("SwapChain::GetBuffer"); + return nullptr; + } + if (FAILED(hr)) { + gfxCriticalNote << "Failed to acquire swap chain's backbuffer: " + << hexa(hr); + return nullptr; + } + + if (!mRT) { + MLGRenderTargetFlags flags = MLGRenderTargetFlags::Default; + if (StaticPrefs::layers_mlgpu_enable_depth_buffer_AtStartup()) { + flags |= MLGRenderTargetFlags::ZBuffer; + } + + mRT = new MLGRenderTargetD3D11(mSize, flags); + if (!mRT->Initialize(mDevice, nullptr)) { + return nullptr; + } + } + + if (!mRT->UpdateTexture(texture)) { + return nullptr; + } + + if (mIsDoubleBuffered) { + UpdateBackBufferContents(texture); + } + return mRT; +} + +void MLGSwapChainD3D11::UpdateBackBufferContents(ID3D11Texture2D* aBack) { + MOZ_ASSERT(mIsDoubleBuffered); + + // The front region contains the newly invalid region for this frame. The + // back region contains that, plus the region that was only drawn into the + // back buffer on the previous frame. Thus by subtracting the two, we can + // find the region that needs to be copied from the front buffer to the + // back. We do this so we don't have to re-render those pixels. + nsIntRegion frontValid; + frontValid.Sub(mBackBufferInvalid, mFrontBufferInvalid); + if (frontValid.IsEmpty()) { + return; + } + + RefPtr front; + HRESULT hr = mSwapChain->GetBuffer(1, __uuidof(ID3D11Texture2D), + getter_AddRefs(front)); + if (FAILED(hr) || !front) { + return; + } + + RefPtr context; + mDevice->GetImmediateContext(getter_AddRefs(context)); + + for (auto iter = frontValid.RectIter(); !iter.Done(); iter.Next()) { + const IntRect& rect = iter.Get(); + D3D11_BOX box = RectToBox(rect); + context->CopySubresourceRegion(aBack, 0, rect.X(), rect.Y(), 0, front, 0, + &box); + } + + // The back and front buffers are now in sync. + mBackBufferInvalid = mFrontBufferInvalid; + MOZ_ASSERT(!mBackBufferInvalid.IsEmpty()); +} + +bool MLGSwapChainD3D11::ResizeBuffers(const IntSize& aSize) { + // We have to clear all references to the old backbuffer before resizing. + mRT = nullptr; + + // Clear the size before re-allocating. If allocation fails we want to try + // again, because we had to sacrifice our original backbuffer to try + // resizing. + mSize = IntSize(0, 0); + + HRESULT hr = mSwapChain->ResizeBuffers(0, aSize.width, aSize.height, + DXGI_FORMAT_B8G8R8A8_UNORM, 0); + if (hr == DXGI_ERROR_DEVICE_REMOVED) { + mParent->HandleDeviceReset("ResizeBuffers"); + return false; + } + if (FAILED(hr)) { + gfxCriticalNote << "Failed to resize swap chain buffers: " << hexa(hr); + return false; + } + + mSize = aSize; + mBackBufferInvalid = IntRect(IntPoint(0, 0), mSize); + mFrontBufferInvalid = IntRect(IntPoint(0, 0), mSize); + return true; +} + +IntSize MLGSwapChainD3D11::GetSize() const { return mSize; } + +void MLGSwapChainD3D11::Present() { + MOZ_ASSERT(!mBackBufferInvalid.IsEmpty()); + MOZ_ASSERT(mBackBufferInvalid.GetNumRects() > 0); + + // See bug 1260611 comment #28 for why we do this. + mParent->InsertPresentWaitQuery(); + + if (mWidget->AsWindows()->HasFxrOutputHandler()) { + // There is a Firefox Reality handler for this swapchain. Update this + // window's contents to the VR window. + FxROutputHandler* fxrHandler = mWidget->AsWindows()->GetFxrOutputHandler(); + if (fxrHandler->TryInitialize(mSwapChain, mDevice)) { + RefPtr context; + mDevice->GetImmediateContext(getter_AddRefs(context)); + fxrHandler->UpdateOutput(context); + } + } + + HRESULT hr; + if (mCanUsePartialPresents && mSwapChain1) { + StackArray rects(mBackBufferInvalid.GetNumRects()); + size_t i = 0; + for (auto iter = mBackBufferInvalid.RectIter(); !iter.Done(); iter.Next()) { + const IntRect& rect = iter.Get(); + rects[i].left = rect.X(); + rects[i].top = rect.Y(); + rects[i].bottom = rect.YMost(); + rects[i].right = rect.XMost(); + i++; + } + + DXGI_PRESENT_PARAMETERS params; + PodZero(¶ms); + params.DirtyRectsCount = mBackBufferInvalid.GetNumRects(); + params.pDirtyRects = rects.data(); + hr = mSwapChain1->Present1(0, 0, ¶ms); + } else { + hr = mSwapChain->Present(0, 0); + } + + if (hr == DXGI_ERROR_DEVICE_REMOVED) { + mParent->HandleDeviceReset("Present"); + } + + if (FAILED(hr)) { + gfxCriticalNote << "D3D11 swap chain failed to present: " << hexa(hr); + } + + if (mIsDoubleBuffered) { + // Both the front and back buffer invalid regions are in sync, but now the + // presented buffer (the front buffer) is clean, so we clear its invalid + // region. The back buffer that will be used next frame however is now + // dirty. + MOZ_ASSERT(mFrontBufferInvalid.GetBounds() == + mBackBufferInvalid.GetBounds()); + mFrontBufferInvalid.SetEmpty(); + } else { + mBackBufferInvalid.SetEmpty(); + } + mLastPresentSize = mSize; + + // Note: this waits on the query we inserted in the previous frame, + // not the one we just inserted now. Example: + // Insert query #1 + // Present #1 + // (first frame, no wait) + // Insert query #2 + // Present #2 + // Wait for query #1. + // Insert query #3 + // Present #3 + // Wait for query #2. + // + // This ensures we're done reading textures before swapping buffers. + mParent->WaitForPreviousPresentQuery(); +} + +void MLGSwapChainD3D11::ForcePresent() { + DXGI_SWAP_CHAIN_DESC desc; + mSwapChain->GetDesc(&desc); + + LayoutDeviceIntSize size = mWidget->GetClientSize(); + + if (desc.BufferDesc.Width != size.width || + desc.BufferDesc.Height != size.height) { + return; + } + + mSwapChain->Present(0, 0); + if (mIsDoubleBuffered) { + // Make sure we present the old front buffer since we know it is completely + // valid. This non-vsynced present should be pretty much 'free' for a flip + // chain. + mSwapChain->Present(0, 0); + } + + mLastPresentSize = mSize; +} + +void MLGSwapChainD3D11::CopyBackbuffer(gfx::DrawTarget* aTarget, + const gfx::IntRect& aBounds) { + RefPtr context; + mDevice->GetImmediateContext(getter_AddRefs(context)); + + RefPtr backbuffer; + HRESULT hr = mSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), + (void**)backbuffer.StartAssignment()); + if (FAILED(hr)) { + gfxWarning() << "Failed to acquire swapchain backbuffer: " << hexa(hr); + return; + } + + D3D11_TEXTURE2D_DESC bbDesc; + backbuffer->GetDesc(&bbDesc); + + CD3D11_TEXTURE2D_DESC tempDesc(bbDesc.Format, bbDesc.Width, bbDesc.Height); + tempDesc.MipLevels = 1; + tempDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + tempDesc.Usage = D3D11_USAGE_STAGING; + tempDesc.BindFlags = 0; + + RefPtr temp; + hr = mDevice->CreateTexture2D(&tempDesc, nullptr, getter_AddRefs(temp)); + if (FAILED(hr)) { + gfxWarning() << "Failed to create a temporary texture for PresentAndCopy: " + << hexa(hr); + return; + } + + context->CopyResource(temp, backbuffer); + + D3D11_MAPPED_SUBRESOURCE map; + hr = context->Map(temp, 0, D3D11_MAP_READ, 0, &map); + if (FAILED(hr)) { + gfxWarning() << "Failed to map temporary texture for PresentAndCopy: " + << hexa(hr); + return; + } + + RefPtr source = Factory::CreateWrappingDataSourceSurface( + (uint8_t*)map.pData, map.RowPitch, IntSize(bbDesc.Width, bbDesc.Height), + SurfaceFormat::B8G8R8A8); + + aTarget->CopySurface(source, IntRect(0, 0, bbDesc.Width, bbDesc.Height), + IntPoint(-aBounds.X(), -aBounds.Y())); + aTarget->Flush(); + + context->Unmap(temp, 0); +} + +RefPtr MLGBufferD3D11::Create(ID3D11Device* aDevice, + MLGBufferType aType, + uint32_t aSize, MLGUsage aUsage, + const void* aInitialData) { + D3D11_BUFFER_DESC desc; + desc.ByteWidth = aSize; + desc.MiscFlags = 0; + desc.StructureByteStride = 0; + + switch (aUsage) { + case MLGUsage::Dynamic: + desc.Usage = D3D11_USAGE_DYNAMIC; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; + break; + case MLGUsage::Immutable: + desc.Usage = D3D11_USAGE_IMMUTABLE; + desc.CPUAccessFlags = 0; + break; + default: + MOZ_ASSERT_UNREACHABLE("Unknown buffer usage type"); + return nullptr; + } + + switch (aType) { + case MLGBufferType::Vertex: + desc.BindFlags = D3D11_BIND_VERTEX_BUFFER; + break; + case MLGBufferType::Constant: + desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; + break; + default: + MOZ_ASSERT_UNREACHABLE("Unknown buffer type"); + return nullptr; + } + + D3D11_SUBRESOURCE_DATA data; + data.pSysMem = aInitialData; + data.SysMemPitch = aSize; + data.SysMemSlicePitch = 0; + + RefPtr buffer; + HRESULT hr = aDevice->CreateBuffer(&desc, aInitialData ? &data : nullptr, + getter_AddRefs(buffer)); + if (FAILED(hr) || !buffer) { + gfxCriticalError() << "Failed to create ID3D11Buffer."; + return nullptr; + } + + return new MLGBufferD3D11(buffer, aType, aSize); +} + +MLGBufferD3D11::MLGBufferD3D11(ID3D11Buffer* aBuffer, MLGBufferType aType, + size_t aSize) + : mBuffer(aBuffer), mType(aType), mSize(aSize) { + switch (mType) { + case MLGBufferType::Vertex: + mlg::sVertexBufferUsage += mSize; + break; + case MLGBufferType::Constant: + mlg::sConstantBufferUsage += mSize; + break; + } +} + +MLGBufferD3D11::~MLGBufferD3D11() { + switch (mType) { + case MLGBufferType::Vertex: + mlg::sVertexBufferUsage -= mSize; + break; + case MLGBufferType::Constant: + mlg::sConstantBufferUsage -= mSize; + break; + } +} + +MLGTextureD3D11::MLGTextureD3D11(ID3D11Texture2D* aTexture) + : mTexture(aTexture) { + D3D11_TEXTURE2D_DESC desc; + aTexture->GetDesc(&desc); + + mSize.width = desc.Width; + mSize.height = desc.Height; +} + +/* static */ +RefPtr MLGTextureD3D11::Create(ID3D11Device* aDevice, + const gfx::IntSize& aSize, + gfx::SurfaceFormat aFormat, + MLGUsage aUsage, + MLGTextureFlags aFlags) { + D3D11_TEXTURE2D_DESC desc; + ::ZeroMemory(&desc, sizeof(desc)); + desc.Width = aSize.width; + desc.Height = aSize.height; + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.SampleDesc.Count = 1; + + switch (aFormat) { + case SurfaceFormat::B8G8R8A8: + desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + break; + default: + MOZ_ASSERT_UNREACHABLE("Unsupported surface format"); + return nullptr; + } + + switch (aUsage) { + case MLGUsage::Immutable: + desc.Usage = D3D11_USAGE_IMMUTABLE; + break; + case MLGUsage::Default: + desc.Usage = D3D11_USAGE_DEFAULT; + break; + case MLGUsage::Dynamic: + desc.Usage = D3D11_USAGE_DYNAMIC; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; + break; + case MLGUsage::Staging: + desc.Usage = D3D11_USAGE_STAGING; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + break; + default: + MOZ_ASSERT_UNREACHABLE("Unsupported usage type"); + break; + } + + if (aFlags & MLGTextureFlags::ShaderResource) { + desc.BindFlags |= D3D11_BIND_SHADER_RESOURCE; + } + if (aFlags & MLGTextureFlags::RenderTarget) { + desc.BindFlags |= D3D11_BIND_RENDER_TARGET; + } + + RefPtr texture; + HRESULT hr = + aDevice->CreateTexture2D(&desc, nullptr, getter_AddRefs(texture)); + if (FAILED(hr) || !texture) { + gfxCriticalNote << "Failed to create 2D texture: " << hexa(hr); + return nullptr; + } + + ReportTextureMemoryUsage(texture, aSize.width * aSize.height * 4); + + return new MLGTextureD3D11(texture); +} + +ID3D11ShaderResourceView* MLGTextureD3D11::GetShaderResourceView() { + if (!mView) { + RefPtr device; + mTexture->GetDevice(getter_AddRefs(device)); + + HRESULT hr = device->CreateShaderResourceView(mTexture, nullptr, + getter_AddRefs(mView)); + if (FAILED(hr) || !mView) { + gfxWarning() << "Could not create shader resource view: " << hexa(hr); + return nullptr; + } + } + return mView; +} + +MLGDeviceD3D11::MLGDeviceD3D11(ID3D11Device* aDevice) + : mDevice(aDevice), mScissored(false) {} + +MLGDeviceD3D11::~MLGDeviceD3D11() { + // Caller should have unlocked all textures after presenting. + MOZ_ASSERT(mLockedTextures.IsEmpty()); + MOZ_ASSERT(mLockAttemptedTextures.IsEmpty()); +} + +bool MLGDeviceD3D11::Initialize() { + if (!mDevice) { + return Fail("FEATURE_FAILURE_NO_DEVICE"); + } + + if (mDevice->GetFeatureLevel() < D3D_FEATURE_LEVEL_10_0) { + return Fail("FEATURE_FAILURE_NEED_LEVEL_10_0"); + } + + mDevice->GetImmediateContext(getter_AddRefs(mCtx)); + if (!mCtx) { + return Fail("FEATURE_FAILURE_NO_CONTEXT"); + } + + mCtx->QueryInterface((ID3D11DeviceContext1**)getter_AddRefs(mCtx1)); + + if (mCtx1) { + // Windows 7 can have Direct3D 11.1 if the platform update is installed, + // but according to some NVIDIA presentations it is known to be buggy. + // It's not clear whether that only refers to command list emulation, + // or whether it just has performance penalties. To be safe we only use + // it on Windows 8 or higher. + // + // https://docs.microsoft.com/en-us/windows-hardware/drivers/display/directx-feature-improvements-in-windows-8#buffers + D3D11_FEATURE_DATA_D3D11_OPTIONS options; + HRESULT hr = mDevice->CheckFeatureSupport(D3D11_FEATURE_D3D11_OPTIONS, + &options, sizeof(options)); + if (SUCCEEDED(hr)) { + if (IsWin8OrLater()) { + mCanUseConstantBufferOffsetBinding = + (options.ConstantBufferOffsetting != FALSE); + } else { + gfxConfig::EnableFallback(Fallback::NO_CONSTANT_BUFFER_OFFSETTING, + "Unsupported by driver"); + } + mCanUseClearView = (options.ClearView != FALSE); + } else { + gfxCriticalNote << "Failed to query D3D11.1 feature support: " + << hexa(hr); + } + } + + // Get capabilities. + switch (mDevice->GetFeatureLevel()) { + case D3D_FEATURE_LEVEL_11_1: + case D3D_FEATURE_LEVEL_11_0: + mMaxConstantBufferBindSize = D3D11_REQ_CONSTANT_BUFFER_ELEMENT_COUNT * 16; + break; + case D3D_FEATURE_LEVEL_10_1: + case D3D_FEATURE_LEVEL_10_0: + mMaxConstantBufferBindSize = D3D10_REQ_CONSTANT_BUFFER_ELEMENT_COUNT * 16; + break; + default: + MOZ_ASSERT_UNREACHABLE("Unknown feature level"); + } + + mDiagnostics = MakeUnique(mDevice, mCtx); + + { + struct Vertex2D { + float x; + float y; + }; + Vertex2D vertices[] = {{0, 0}, {1.0f, 0}, {0, 1.0f}, {1.0f, 1.0f}}; + mUnitQuadVB = CreateBuffer(MLGBufferType::Vertex, sizeof(Vertex2D) * 4, + MLGUsage::Immutable, &vertices); + if (!mUnitQuadVB) { + return Fail("FEATURE_FAILURE_UNIT_QUAD_BUFFER"); + } + } + + { + struct Vertex3D { + float x; + float y; + float z; + }; + Vertex3D vertices[3] = { + {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}; + mUnitTriangleVB = CreateBuffer(MLGBufferType::Vertex, sizeof(Vertex3D) * 3, + MLGUsage::Immutable, &vertices); + if (!mUnitTriangleVB) { + return Fail("FEATURE_FAILURE_UNIT_TRIANGLE_BUFFER"); + } + } + + // Define pixel shaders. +#define LAZY_PS(cxxName, enumName) \ + mLazyPixelShaders[PixelShaderID::enumName] = &s##cxxName; + LAZY_PS(TexturedVertexRGB, TexturedVertexRGB); + LAZY_PS(TexturedVertexRGBA, TexturedVertexRGBA); + LAZY_PS(TexturedQuadRGB, TexturedQuadRGB); + LAZY_PS(TexturedQuadRGBA, TexturedQuadRGBA); + LAZY_PS(ColoredQuadPS, ColoredQuad); + LAZY_PS(ColoredVertexPS, ColoredVertex); + LAZY_PS(ComponentAlphaQuadPS, ComponentAlphaQuad); + LAZY_PS(ComponentAlphaVertexPS, ComponentAlphaVertex); + LAZY_PS(TexturedVertexIMC4, TexturedVertexIMC4); + LAZY_PS(TexturedVertexIdentityIMC4, TexturedVertexIdentityIMC4); + LAZY_PS(TexturedVertexNV12, TexturedVertexNV12); + LAZY_PS(TexturedQuadIMC4, TexturedQuadIMC4); + LAZY_PS(TexturedQuadIdentityIMC4, TexturedQuadIdentityIMC4); + LAZY_PS(TexturedQuadNV12, TexturedQuadNV12); + LAZY_PS(BlendMultiplyPS, BlendMultiply); + LAZY_PS(BlendScreenPS, BlendScreen); + LAZY_PS(BlendOverlayPS, BlendOverlay); + LAZY_PS(BlendDarkenPS, BlendDarken); + LAZY_PS(BlendLightenPS, BlendLighten); + LAZY_PS(BlendColorDodgePS, BlendColorDodge); + LAZY_PS(BlendColorBurnPS, BlendColorBurn); + LAZY_PS(BlendHardLightPS, BlendHardLight); + LAZY_PS(BlendSoftLightPS, BlendSoftLight); + LAZY_PS(BlendDifferencePS, BlendDifference); + LAZY_PS(BlendExclusionPS, BlendExclusion); + LAZY_PS(BlendHuePS, BlendHue); + LAZY_PS(BlendSaturationPS, BlendSaturation); + LAZY_PS(BlendColorPS, BlendColor); + LAZY_PS(BlendLuminosityPS, BlendLuminosity); + LAZY_PS(ClearPS, Clear); + LAZY_PS(MaskCombinerPS, MaskCombiner); + LAZY_PS(DiagnosticTextPS, DiagnosticText); +#undef LAZY_PS + + // Define vertex shaders. +#define LAZY_VS(cxxName, enumName) \ + mLazyVertexShaders[VertexShaderID::enumName] = &s##cxxName; + LAZY_VS(TexturedQuadVS, TexturedQuad); + LAZY_VS(TexturedVertexVS, TexturedVertex); + LAZY_VS(BlendVertexVS, BlendVertex); + LAZY_VS(ColoredQuadVS, ColoredQuad); + LAZY_VS(ColoredVertexVS, ColoredVertex); + LAZY_VS(ClearVS, Clear); + LAZY_VS(MaskCombinerVS, MaskCombiner); + LAZY_VS(DiagnosticTextVS, DiagnosticText); +#undef LAZY_VS + + // Force critical shaders to initialize early. + if (!InitPixelShader(PixelShaderID::TexturedQuadRGB) || + !InitPixelShader(PixelShaderID::TexturedQuadRGBA) || + !InitPixelShader(PixelShaderID::ColoredQuad) || + !InitPixelShader(PixelShaderID::ComponentAlphaQuad) || + !InitPixelShader(PixelShaderID::Clear) || + !InitVertexShader(VertexShaderID::TexturedQuad) || + !InitVertexShader(VertexShaderID::ColoredQuad) || + !InitVertexShader(VertexShaderID::Clear)) { + return Fail("FEATURE_FAILURE_CRITICAL_SHADER_FAILURE"); + } + + // Common unit quad layout: vPos, vRect, vLayerIndex, vDepth +#define BASE_UNIT_QUAD_LAYOUT \ + {"POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, \ + 0}, \ + {"TEXCOORD", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, \ + 1, 0, D3D11_INPUT_PER_INSTANCE_DATA, \ + 1}, \ + {"TEXCOORD", \ + 1, \ + DXGI_FORMAT_R32_UINT, \ + 1, \ + D3D11_APPEND_ALIGNED_ELEMENT, \ + D3D11_INPUT_PER_INSTANCE_DATA, \ + 1}, \ + { \ + "TEXCOORD", 2, DXGI_FORMAT_R32_SINT, 1, D3D11_APPEND_ALIGNED_ELEMENT, \ + D3D11_INPUT_PER_INSTANCE_DATA, 1 \ + } + + // Common unit triangle layout: vUnitPos, vPos1-3, vLayerIndex, vDepth +#define BASE_UNIT_TRIANGLE_LAYOUT \ + {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, \ + 0, 0, D3D11_INPUT_PER_VERTEX_DATA, \ + 0}, \ + {"POSITION", 1, DXGI_FORMAT_R32G32_FLOAT, \ + 1, 0, D3D11_INPUT_PER_INSTANCE_DATA, \ + 1}, \ + {"POSITION", \ + 2, \ + DXGI_FORMAT_R32G32_FLOAT, \ + 1, \ + D3D11_APPEND_ALIGNED_ELEMENT, \ + D3D11_INPUT_PER_INSTANCE_DATA, \ + 1}, \ + {"POSITION", \ + 3, \ + DXGI_FORMAT_R32G32_FLOAT, \ + 1, \ + D3D11_APPEND_ALIGNED_ELEMENT, \ + D3D11_INPUT_PER_INSTANCE_DATA, \ + 1}, \ + {"TEXCOORD", \ + 0, \ + DXGI_FORMAT_R32_UINT, \ + 1, \ + D3D11_APPEND_ALIGNED_ELEMENT, \ + D3D11_INPUT_PER_INSTANCE_DATA, \ + 1}, \ + {"TEXCOORD", \ + 1, \ + DXGI_FORMAT_R32_SINT, \ + 1, \ + D3D11_APPEND_ALIGNED_ELEMENT, \ + D3D11_INPUT_PER_INSTANCE_DATA, \ + 1} + + // Initialize input layouts. + { + D3D11_INPUT_ELEMENT_DESC inputDesc[] = { + BASE_UNIT_QUAD_LAYOUT, + // vTexRect + {"TEXCOORD", 3, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, + D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1}}; + if (!InitInputLayout(inputDesc, MOZ_ARRAY_LENGTH(inputDesc), + sTexturedQuadVS, VertexShaderID::TexturedQuad)) { + return Fail("FEATURE_FAILURE_UNIT_QUAD_TEXTURED_LAYOUT"); + } + } + { + D3D11_INPUT_ELEMENT_DESC inputDesc[] = { + BASE_UNIT_QUAD_LAYOUT, + // vColor + {"TEXCOORD", 3, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, + D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1}}; + if (!InitInputLayout(inputDesc, MOZ_ARRAY_LENGTH(inputDesc), sColoredQuadVS, + VertexShaderID::ColoredQuad)) { + return Fail("FEATURE_FAILURE_UNIT_QUAD_COLORED_LAYOUT"); + } + } + { + D3D11_INPUT_ELEMENT_DESC inputDesc[] = { + BASE_UNIT_TRIANGLE_LAYOUT, + // vTexCoord1, vTexCoord2, vTexCoord3 + {"TEXCOORD", 2, DXGI_FORMAT_R32G32_FLOAT, 1, + D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1}, + {"TEXCOORD", 3, DXGI_FORMAT_R32G32_FLOAT, 1, + D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1}, + {"TEXCOORD", 4, DXGI_FORMAT_R32G32_FLOAT, 1, + D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1}}; + if (!InitInputLayout(inputDesc, MOZ_ARRAY_LENGTH(inputDesc), + sTexturedVertexVS, VertexShaderID::TexturedVertex)) { + return Fail("FEATURE_FAILURE_TEXTURED_INPUT_LAYOUT"); + } + // Propagate the input layout to other vertex shaders that use the same. + mInputLayouts[VertexShaderID::BlendVertex] = + mInputLayouts[VertexShaderID::TexturedVertex]; + } + { + D3D11_INPUT_ELEMENT_DESC inputDesc[] = { + BASE_UNIT_TRIANGLE_LAYOUT, + {"TEXCOORD", 2, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, + D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1}}; + if (!InitInputLayout(inputDesc, MOZ_ARRAY_LENGTH(inputDesc), + sColoredVertexVS, VertexShaderID::ColoredVertex)) { + return Fail("FEATURE_FAILURE_COLORED_INPUT_LAYOUT"); + } + } + +#undef BASE_UNIT_QUAD_LAYOUT +#undef BASE_UNIT_TRIANGLE_LAYOUT + + // Ancillary shaders that are not used for batching. + { + D3D11_INPUT_ELEMENT_DESC inputDesc[] = { + // vPos + {"POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, + D3D11_INPUT_PER_VERTEX_DATA, 0}, + // vRect + {"TEXCOORD", 0, DXGI_FORMAT_R32G32B32A32_SINT, 1, 0, + D3D11_INPUT_PER_INSTANCE_DATA, 1}, + // vDepth + {"TEXCOORD", 1, DXGI_FORMAT_R32_SINT, 1, D3D11_APPEND_ALIGNED_ELEMENT, + D3D11_INPUT_PER_INSTANCE_DATA, 1}, + }; + if (!InitInputLayout(inputDesc, MOZ_ARRAY_LENGTH(inputDesc), sClearVS, + VertexShaderID::Clear)) { + return Fail("FEATURE_FAILURE_CLEAR_INPUT_LAYOUT"); + } + } + { + D3D11_INPUT_ELEMENT_DESC inputDesc[] = { + // vPos + {"POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, + D3D11_INPUT_PER_VERTEX_DATA, 0}, + // vTexCoords + {"POSITION", 1, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, + D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1}}; + if (!InitInputLayout(inputDesc, MOZ_ARRAY_LENGTH(inputDesc), + sMaskCombinerVS, VertexShaderID::MaskCombiner)) { + return Fail("FEATURE_FAILURE_MASK_COMBINER_INPUT_LAYOUT"); + } + } + { + D3D11_INPUT_ELEMENT_DESC inputDesc[] = { + // vPos + {"POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, + D3D11_INPUT_PER_VERTEX_DATA, 0}, + // vRect + {"TEXCOORD", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 0, + D3D11_INPUT_PER_INSTANCE_DATA, 1}, + // vTexCoords + {"TEXCOORD", 1, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, + D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1}, + }; + if (!InitInputLayout(inputDesc, MOZ_ARRAY_LENGTH(inputDesc), + sDiagnosticTextVS, VertexShaderID::DiagnosticText)) { + return Fail("FEATURE_FAILURE_DIAGNOSTIC_INPUT_LAYOUT"); + } + } + + if (!InitRasterizerStates() || !InitDepthStencilState() || + !InitBlendStates() || !InitSamplerStates() || !InitSyncObject()) { + return false; + } + + mCtx->RSSetState(mRasterizerStateNoScissor); + + return MLGDevice::Initialize(); +} + +bool MLGDeviceD3D11::InitPixelShader(PixelShaderID aShaderID) { + const ShaderBytes* code = mLazyPixelShaders[aShaderID]; + HRESULT hr = + mDevice->CreatePixelShader(code->mData, code->mLength, nullptr, + getter_AddRefs(mPixelShaders[aShaderID])); + if (FAILED(hr)) { + gfxCriticalNote << "Could not create pixel shader " + << hexa(unsigned(aShaderID)) << ": " << hexa(hr); + return false; + } + return true; +} + +bool MLGDeviceD3D11::InitRasterizerStates() { + { + CD3D11_RASTERIZER_DESC desc = CD3D11_RASTERIZER_DESC(CD3D11_DEFAULT()); + desc.CullMode = D3D11_CULL_NONE; + desc.FillMode = D3D11_FILL_SOLID; + desc.ScissorEnable = TRUE; + HRESULT hr = mDevice->CreateRasterizerState( + &desc, getter_AddRefs(mRasterizerStateScissor)); + if (FAILED(hr)) { + return Fail("FEATURE_FAILURE_SCISSOR_RASTERIZER", + "Could not create scissor rasterizer (%x)", hr); + } + } + { + CD3D11_RASTERIZER_DESC desc = CD3D11_RASTERIZER_DESC(CD3D11_DEFAULT()); + desc.CullMode = D3D11_CULL_NONE; + desc.FillMode = D3D11_FILL_SOLID; + HRESULT hr = mDevice->CreateRasterizerState( + &desc, getter_AddRefs(mRasterizerStateNoScissor)); + if (FAILED(hr)) { + return Fail("FEATURE_FAILURE_DEFAULT_RASTERIZER", + "Could not create default rasterizer (%x)", hr); + } + } + return true; +} + +bool MLGDeviceD3D11::InitSamplerStates() { + { + CD3D11_SAMPLER_DESC desc = CD3D11_SAMPLER_DESC(CD3D11_DEFAULT()); + HRESULT hr = mDevice->CreateSamplerState( + &desc, getter_AddRefs(mSamplerStates[SamplerMode::LinearClamp])); + if (FAILED(hr)) { + return Fail("FEATURE_FAILURE_LINEAR_CLAMP_SAMPLER", + "Could not create linear clamp sampler (%x)", hr); + } + } + { + CD3D11_SAMPLER_DESC desc = CD3D11_SAMPLER_DESC(CD3D11_DEFAULT()); + desc.AddressU = D3D11_TEXTURE_ADDRESS_BORDER; + desc.AddressV = D3D11_TEXTURE_ADDRESS_BORDER; + desc.AddressW = D3D11_TEXTURE_ADDRESS_BORDER; + memset(desc.BorderColor, 0, sizeof(desc.BorderColor)); + HRESULT hr = mDevice->CreateSamplerState( + &desc, getter_AddRefs(mSamplerStates[SamplerMode::LinearClampToZero])); + if (FAILED(hr)) { + return Fail("FEATURE_FAILURE_LINEAR_CLAMP_ZERO_SAMPLER", + "Could not create linear clamp to zero sampler (%x)", hr); + } + } + { + CD3D11_SAMPLER_DESC desc = CD3D11_SAMPLER_DESC(CD3D11_DEFAULT()); + desc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP; + desc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP; + desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP; + HRESULT hr = mDevice->CreateSamplerState( + &desc, getter_AddRefs(mSamplerStates[SamplerMode::LinearRepeat])); + if (FAILED(hr)) { + return Fail("FEATURE_FAILURE_LINEAR_CLAMP_ZERO_SAMPLER", + "Could not create linear clamp to zero sampler (%x)", hr); + } + } + + { + CD3D11_SAMPLER_DESC desc = CD3D11_SAMPLER_DESC(CD3D11_DEFAULT()); + desc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; + HRESULT hr = mDevice->CreateSamplerState( + &desc, getter_AddRefs(mSamplerStates[SamplerMode::Point])); + if (FAILED(hr)) { + return Fail("FEATURE_FAILURE_POINT_SAMPLER", + "Could not create point sampler (%x)", hr); + } + } + return true; +} + +bool MLGDeviceD3D11::InitBlendStates() { + { + CD3D11_BLEND_DESC desc = CD3D11_BLEND_DESC(CD3D11_DEFAULT()); + HRESULT hr = mDevice->CreateBlendState( + &desc, getter_AddRefs(mBlendStates[MLGBlendState::Copy])); + if (FAILED(hr)) { + return Fail("FEATURE_FAILURE_COPY_BLEND_STATE", + "Could not create copy blend state (%x)", hr); + } + } + + { + CD3D11_BLEND_DESC desc = CD3D11_BLEND_DESC(CD3D11_DEFAULT()); + desc.RenderTarget[0].BlendEnable = TRUE; + desc.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE; + desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; + desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; + desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; + desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA; + desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; + HRESULT hr = mDevice->CreateBlendState( + &desc, getter_AddRefs(mBlendStates[MLGBlendState::Over])); + if (FAILED(hr)) { + return Fail("FEATURE_FAILURE_OVER_BLEND_STATE", + "Could not create over blend state (%x)", hr); + } + } + + { + CD3D11_BLEND_DESC desc = CD3D11_BLEND_DESC(CD3D11_DEFAULT()); + desc.RenderTarget[0].BlendEnable = TRUE; + desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA; + desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; + desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; + desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; + desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA; + desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; + HRESULT hr = mDevice->CreateBlendState( + &desc, getter_AddRefs(mBlendStates[MLGBlendState::OverAndPremultiply])); + if (FAILED(hr)) { + return Fail("FEATURE_FAILURE_OVER_BLEND_STATE", + "Could not create over blend state (%x)", hr); + } + } + + { + CD3D11_BLEND_DESC desc = CD3D11_BLEND_DESC(CD3D11_DEFAULT()); + desc.RenderTarget[0].BlendEnable = TRUE; + desc.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE; + desc.RenderTarget[0].DestBlend = D3D11_BLEND_ONE; + desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_MIN; + desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; + desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ONE; + desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_MIN; + HRESULT hr = mDevice->CreateBlendState( + &desc, getter_AddRefs(mBlendStates[MLGBlendState::Min])); + if (FAILED(hr)) { + return Fail("FEATURE_FAILURE_MIN_BLEND_STATE", + "Could not create min blend state (%x)", hr); + } + } + + { + CD3D11_BLEND_DESC desc = CD3D11_BLEND_DESC(CD3D11_DEFAULT()); + desc.RenderTarget[0].BlendEnable = TRUE; + desc.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE; + desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC1_COLOR; + desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; + desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; + desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA; + desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; + HRESULT hr = mDevice->CreateBlendState( + &desc, getter_AddRefs(mBlendStates[MLGBlendState::ComponentAlpha])); + if (FAILED(hr)) { + return Fail("FEATURE_FAILURE_COMPONENT_ALPHA_BLEND_STATE", + "Could not create component alpha blend state (%x)", hr); + } + } + return true; +} + +bool MLGDeviceD3D11::InitDepthStencilState() { + D3D11_DEPTH_STENCIL_DESC desc = CD3D11_DEPTH_STENCIL_DESC(D3D11_DEFAULT); + + HRESULT hr = mDevice->CreateDepthStencilState( + &desc, getter_AddRefs(mDepthStencilStates[MLGDepthTestMode::Write])); + if (FAILED(hr)) { + return Fail("FEATURE_FAILURE_WRITE_DEPTH_STATE", + "Could not create write depth stencil state (%x)", hr); + } + + desc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ZERO; + hr = mDevice->CreateDepthStencilState( + &desc, getter_AddRefs(mDepthStencilStates[MLGDepthTestMode::ReadOnly])); + if (FAILED(hr)) { + return Fail("FEATURE_FAILURE_READ_DEPTH_STATE", + "Could not create read depth stencil state (%x)", hr); + } + + desc.DepthEnable = FALSE; + hr = mDevice->CreateDepthStencilState( + &desc, getter_AddRefs(mDepthStencilStates[MLGDepthTestMode::Disabled])); + if (FAILED(hr)) { + return Fail("FEATURE_FAILURE_DISABLED_DEPTH_STATE", + "Could not create disabled depth stencil state (%x)", hr); + } + + desc = CD3D11_DEPTH_STENCIL_DESC(D3D11_DEFAULT); + desc.DepthFunc = D3D11_COMPARISON_ALWAYS; + hr = mDevice->CreateDepthStencilState( + &desc, + getter_AddRefs(mDepthStencilStates[MLGDepthTestMode::AlwaysWrite])); + if (FAILED(hr)) { + return Fail("FEATURE_FAILURE_WRITE_DEPTH_STATE", + "Could not create always-write depth stencil state (%x)", hr); + } + + return true; +} + +bool MLGDeviceD3D11::InitVertexShader(VertexShaderID aShaderID) { + const ShaderBytes* code = mLazyVertexShaders[aShaderID]; + HRESULT hr = + mDevice->CreateVertexShader(code->mData, code->mLength, nullptr, + getter_AddRefs(mVertexShaders[aShaderID])); + if (FAILED(hr)) { + gfxCriticalNote << "Could not create vertex shader " + << hexa(unsigned(aShaderID)) << ": " << hexa(hr); + return false; + } + return true; +} + +bool MLGDeviceD3D11::InitInputLayout(D3D11_INPUT_ELEMENT_DESC* aDesc, + size_t aNumElements, + const ShaderBytes& aCode, + VertexShaderID aShaderID) { + HRESULT hr = mDevice->CreateInputLayout( + aDesc, aNumElements, aCode.mData, aCode.mLength, + getter_AddRefs(mInputLayouts[aShaderID])); + if (FAILED(hr)) { + gfxCriticalNote << "Could not create input layout for shader " + << hexa(unsigned(aShaderID)) << ": " << hexa(hr); + return false; + } + return true; +} + +TextureFactoryIdentifier MLGDeviceD3D11::GetTextureFactoryIdentifier( + widget::CompositorWidget* aWidget) const { + TextureFactoryIdentifier ident(GetLayersBackend(), XRE_GetProcessType(), + GetMaxTextureSize()); + if (aWidget) { + ident.mUseCompositorWnd = !!aWidget->AsWindows()->GetCompositorHwnd(); + } + if (mSyncObject) { + ident.mSyncHandle = mSyncObject->GetSyncHandle(); + } + + return ident; +} + +inline uint32_t GetMaxTextureSizeForFeatureLevel1( + D3D_FEATURE_LEVEL aFeatureLevel) { + int32_t maxTextureSize; + switch (aFeatureLevel) { + case D3D_FEATURE_LEVEL_11_1: + case D3D_FEATURE_LEVEL_11_0: + maxTextureSize = D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION; + break; + case D3D_FEATURE_LEVEL_10_1: + case D3D_FEATURE_LEVEL_10_0: + maxTextureSize = D3D10_REQ_TEXTURE2D_U_OR_V_DIMENSION; + break; + case D3D_FEATURE_LEVEL_9_3: + maxTextureSize = D3D_FL9_3_REQ_TEXTURE2D_U_OR_V_DIMENSION; + break; + default: + maxTextureSize = D3D_FL9_1_REQ_TEXTURE2D_U_OR_V_DIMENSION; + } + return maxTextureSize; +} + +LayersBackend MLGDeviceD3D11::GetLayersBackend() const { + return LayersBackend::LAYERS_D3D11; +} + +int32_t MLGDeviceD3D11::GetMaxTextureSize() const { + return GetMaxTextureSizeForFeatureLevel1(mDevice->GetFeatureLevel()); +} + +RefPtr MLGDeviceD3D11::CreateSwapChainForWidget( + widget::CompositorWidget* aWidget) { + return MLGSwapChainD3D11::Create(this, mDevice, aWidget); +} + +RefPtr MLGDeviceD3D11::CreateDataTextureSource( + TextureFlags aFlags) { + return new DataTextureSourceD3D11(mDevice, gfx::SurfaceFormat::UNKNOWN, + aFlags); +} + +static inline D3D11_MAP ToD3D11Map(MLGMapType aType) { + switch (aType) { + case MLGMapType::READ: + return D3D11_MAP_READ; + case MLGMapType::READ_WRITE: + return D3D11_MAP_READ_WRITE; + case MLGMapType::WRITE: + return D3D11_MAP_WRITE; + case MLGMapType::WRITE_DISCARD: + return D3D11_MAP_WRITE_DISCARD; + } + return D3D11_MAP_WRITE; +} + +bool MLGDeviceD3D11::Map(MLGResource* aResource, MLGMapType aType, + MLGMappedResource* aMap) { + ID3D11Resource* resource = aResource->AsResourceD3D11()->GetResource(); + MOZ_ASSERT(resource); + + D3D11_MAPPED_SUBRESOURCE map; + HRESULT hr = mCtx->Map(resource, 0, ToD3D11Map(aType), 0, &map); + + if (FAILED(hr)) { + gfxWarning() << "Could not map MLG resource: " << hexa(hr); + return false; + } + + aMap->mData = reinterpret_cast(map.pData); + aMap->mStride = map.RowPitch; + return true; +} + +void MLGDeviceD3D11::Unmap(MLGResource* aResource) { + ID3D11Resource* resource = aResource->AsResourceD3D11()->GetResource(); + mCtx->Unmap(resource, 0); +} + +void MLGDeviceD3D11::UpdatePartialResource(MLGResource* aResource, + const gfx::IntRect* aRect, + void* aData, uint32_t aStride) { + D3D11_BOX box; + if (aRect) { + box = RectToBox(*aRect); + } + + ID3D11Resource* resource = aResource->AsResourceD3D11()->GetResource(); + mCtx->UpdateSubresource(resource, 0, aRect ? &box : nullptr, aData, aStride, + 0); +} + +void MLGDeviceD3D11::SetRenderTarget(MLGRenderTarget* aRT) { + ID3D11RenderTargetView* rtv = nullptr; + ID3D11DepthStencilView* dsv = nullptr; + + if (aRT) { + MLGRenderTargetD3D11* rt = aRT->AsD3D11(); + rtv = rt->GetRenderTargetView(); + dsv = rt->GetDSV(); + } + + mCtx->OMSetRenderTargets(1, &rtv, dsv); + mCurrentRT = aRT; +} + +MLGRenderTarget* MLGDeviceD3D11::GetRenderTarget() { return mCurrentRT; } + +void MLGDeviceD3D11::SetViewport(const gfx::IntRect& aViewport) { + D3D11_VIEWPORT vp; + vp.MaxDepth = 1.0f; + vp.MinDepth = 0.0f; + vp.TopLeftX = aViewport.X(); + vp.TopLeftY = aViewport.Y(); + vp.Width = aViewport.Width(); + vp.Height = aViewport.Height(); + mCtx->RSSetViewports(1, &vp); +} + +static inline D3D11_RECT ToD3D11Rect(const gfx::IntRect& aRect) { + D3D11_RECT rect; + rect.left = aRect.X(); + rect.top = aRect.Y(); + rect.right = aRect.XMost(); + rect.bottom = aRect.YMost(); + return rect; +} + +void MLGDeviceD3D11::SetScissorRect(const Maybe& aScissorRect) { + if (!aScissorRect) { + if (mScissored) { + mCtx->RSSetState(mRasterizerStateNoScissor); + mScissored = false; + } + return; + } + D3D11_RECT rect = ToD3D11Rect(aScissorRect.value()); + mCtx->RSSetScissorRects(1, &rect); + if (!mScissored) { + mScissored = true; + mCtx->RSSetState(mRasterizerStateScissor); + } +} + +void MLGDeviceD3D11::SetVertexShader(VertexShaderID aShader) { + if (!mVertexShaders[aShader]) { + InitVertexShader(aShader); + MOZ_ASSERT(mInputLayouts[aShader]); + } + SetVertexShader(mVertexShaders[aShader]); + SetInputLayout(mInputLayouts[aShader]); +} + +void MLGDeviceD3D11::SetInputLayout(ID3D11InputLayout* aLayout) { + if (mCurrentInputLayout == aLayout) { + return; + } + mCtx->IASetInputLayout(aLayout); + mCurrentInputLayout = aLayout; +} + +void MLGDeviceD3D11::SetVertexShader(ID3D11VertexShader* aShader) { + if (mCurrentVertexShader == aShader) { + return; + } + mCtx->VSSetShader(aShader, nullptr, 0); + mCurrentVertexShader = aShader; +} + +void MLGDeviceD3D11::SetPixelShader(PixelShaderID aShader) { + if (!mPixelShaders[aShader]) { + InitPixelShader(aShader); + } + if (mCurrentPixelShader != mPixelShaders[aShader]) { + mCtx->PSSetShader(mPixelShaders[aShader], nullptr, 0); + mCurrentPixelShader = mPixelShaders[aShader]; + } +} + +void MLGDeviceD3D11::SetSamplerMode(uint32_t aIndex, SamplerMode aMode) { + ID3D11SamplerState* sampler = mSamplerStates[aMode]; + mCtx->PSSetSamplers(aIndex, 1, &sampler); +} + +void MLGDeviceD3D11::SetBlendState(MLGBlendState aState) { + if (mCurrentBlendState != mBlendStates[aState]) { + FLOAT blendFactor[4] = {0, 0, 0, 0}; + mCtx->OMSetBlendState(mBlendStates[aState], blendFactor, 0xFFFFFFFF); + mCurrentBlendState = mBlendStates[aState]; + } +} + +void MLGDeviceD3D11::SetVertexBuffer(uint32_t aSlot, MLGBuffer* aBuffer, + uint32_t aStride, uint32_t aOffset) { + ID3D11Buffer* buffer = aBuffer ? aBuffer->AsD3D11()->GetBuffer() : nullptr; + mCtx->IASetVertexBuffers(aSlot, 1, &buffer, &aStride, &aOffset); +} + +void MLGDeviceD3D11::SetVSConstantBuffer(uint32_t aSlot, MLGBuffer* aBuffer) { + MOZ_ASSERT(aSlot < kMaxVertexShaderConstantBuffers); + + ID3D11Buffer* buffer = aBuffer ? aBuffer->AsD3D11()->GetBuffer() : nullptr; + mCtx->VSSetConstantBuffers(aSlot, 1, &buffer); +} + +void MLGDeviceD3D11::SetPSConstantBuffer(uint32_t aSlot, MLGBuffer* aBuffer) { + MOZ_ASSERT(aSlot < kMaxPixelShaderConstantBuffers); + + ID3D11Buffer* buffer = aBuffer ? aBuffer->AsD3D11()->GetBuffer() : nullptr; + mCtx->PSSetConstantBuffers(aSlot, 1, &buffer); +} + +void MLGDeviceD3D11::SetVSConstantBuffer(uint32_t aSlot, MLGBuffer* aBuffer, + uint32_t aFirstConstant, + uint32_t aNumConstants) { + MOZ_ASSERT(aSlot < kMaxVertexShaderConstantBuffers); + MOZ_ASSERT(mCanUseConstantBufferOffsetBinding); + MOZ_ASSERT(mCtx1); + MOZ_ASSERT(aFirstConstant % 16 == 0); + + ID3D11Buffer* buffer = aBuffer ? aBuffer->AsD3D11()->GetBuffer() : nullptr; + mCtx1->VSSetConstantBuffers1(aSlot, 1, &buffer, &aFirstConstant, + &aNumConstants); +} + +void MLGDeviceD3D11::SetPSConstantBuffer(uint32_t aSlot, MLGBuffer* aBuffer, + uint32_t aFirstConstant, + uint32_t aNumConstants) { + MOZ_ASSERT(aSlot < kMaxPixelShaderConstantBuffers); + MOZ_ASSERT(mCanUseConstantBufferOffsetBinding); + MOZ_ASSERT(mCtx1); + MOZ_ASSERT(aFirstConstant % 16 == 0); + + ID3D11Buffer* buffer = aBuffer ? aBuffer->AsD3D11()->GetBuffer() : nullptr; + mCtx1->PSSetConstantBuffers1(aSlot, 1, &buffer, &aFirstConstant, + &aNumConstants); +} + +void MLGDeviceD3D11::SetPrimitiveTopology(MLGPrimitiveTopology aTopology) { + D3D11_PRIMITIVE_TOPOLOGY topology; + switch (aTopology) { + case MLGPrimitiveTopology::TriangleStrip: + topology = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP; + break; + case MLGPrimitiveTopology::TriangleList: + topology = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST; + break; + case MLGPrimitiveTopology::UnitQuad: + SetVertexBuffer(0, mUnitQuadVB, sizeof(float) * 2, 0); + topology = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP; + break; + case MLGPrimitiveTopology::UnitTriangle: + SetVertexBuffer(0, mUnitTriangleVB, sizeof(float) * 3, 0); + topology = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST; + break; + default: + MOZ_ASSERT_UNREACHABLE("Unknown topology"); + topology = D3D11_PRIMITIVE_TOPOLOGY_UNDEFINED; + break; + } + + mCtx->IASetPrimitiveTopology(topology); +} + +RefPtr MLGDeviceD3D11::CreateBuffer(MLGBufferType aType, + uint32_t aSize, MLGUsage aUsage, + const void* aInitialData) { + return MLGBufferD3D11::Create(mDevice, aType, aSize, aUsage, aInitialData); +} + +RefPtr MLGDeviceD3D11::CreateRenderTarget( + const gfx::IntSize& aSize, MLGRenderTargetFlags aFlags) { + RefPtr rt = new MLGRenderTargetD3D11(aSize, aFlags); + if (!rt->Initialize(mDevice)) { + return nullptr; + } + return rt; +} + +void MLGDeviceD3D11::Clear(MLGRenderTarget* aRT, + const gfx::DeviceColor& aColor) { + MLGRenderTargetD3D11* rt = aRT->AsD3D11(); + FLOAT rgba[4] = {aColor.r, aColor.g, aColor.b, aColor.a}; + mCtx->ClearRenderTargetView(rt->GetRenderTargetView(), rgba); + if (ID3D11DepthStencilView* dsv = rt->GetDSV()) { + mCtx->ClearDepthStencilView(dsv, D3D11_CLEAR_DEPTH, 1.0, 0); + } +} + +void MLGDeviceD3D11::ClearDepthBuffer(MLGRenderTarget* aRT) { + MLGRenderTargetD3D11* rt = aRT->AsD3D11(); + if (ID3D11DepthStencilView* dsv = rt->GetDSV()) { + mCtx->ClearDepthStencilView(dsv, D3D11_CLEAR_DEPTH, 1.0, 0); + } +} + +void MLGDeviceD3D11::ClearView(MLGRenderTarget* aRT, const DeviceColor& aColor, + const IntRect* aRects, size_t aNumRects) { + MOZ_ASSERT(mCanUseClearView); + MOZ_ASSERT(mCtx1); + + MLGRenderTargetD3D11* rt = aRT->AsD3D11(); + FLOAT rgba[4] = {aColor.r, aColor.g, aColor.b, aColor.a}; + + StackArray rects(aNumRects); + for (size_t i = 0; i < aNumRects; i++) { + rects[i] = ToD3D11Rect(aRects[i]); + } + + // Batch ClearView calls since too many will crash NVIDIA drivers. + size_t remaining = aNumRects; + size_t cursor = 0; + while (remaining > 0) { + size_t amount = std::min(remaining, kMaxClearViewRects); + mCtx1->ClearView(rt->GetRenderTargetView(), rgba, rects.data() + cursor, + amount); + + remaining -= amount; + cursor += amount; + } +} + +void MLGDeviceD3D11::Draw(uint32_t aVertexCount, uint32_t aOffset) { + mCtx->Draw(aVertexCount, aOffset); +} + +void MLGDeviceD3D11::DrawInstanced(uint32_t aVertexCountPerInstance, + uint32_t aInstanceCount, + uint32_t aVertexOffset, + uint32_t aInstanceOffset) { + mCtx->DrawInstanced(aVertexCountPerInstance, aInstanceCount, aVertexOffset, + aInstanceOffset); +} + +void MLGDeviceD3D11::SetPSTextures(uint32_t aSlot, uint32_t aNumTextures, + TextureSource* const* aTextures) { + // TextureSource guarantees that the ID3D11ShaderResourceView will be cached, + // so we don't hold a RefPtr here. + StackArray views(aNumTextures); + + for (size_t i = 0; i < aNumTextures; i++) { + views[i] = ResolveTextureSourceForShader(aTextures[i]); + } + + mCtx->PSSetShaderResources(aSlot, aNumTextures, views.data()); +} + +ID3D11ShaderResourceView* MLGDeviceD3D11::ResolveTextureSourceForShader( + TextureSource* aTexture) { + if (!aTexture) { + return nullptr; + } + + if (TextureSourceD3D11* source = aTexture->AsSourceD3D11()) { + ID3D11Texture2D* texture = source->GetD3D11Texture(); + if (!texture) { + gfxWarning() << "No D3D11 texture present in SetPSTextures"; + return nullptr; + } + + MaybeLockTexture(texture); + return source->GetShaderResourceView(); + } + + gfxWarning() << "Unknown texture type in SetPSTextures"; + return nullptr; +} + +void MLGDeviceD3D11::SetPSTexture(uint32_t aSlot, MLGTexture* aTexture) { + RefPtr view; + if (aTexture) { + MLGTextureD3D11* texture = aTexture->AsD3D11(); + view = texture->GetShaderResourceView(); + } + + ID3D11ShaderResourceView* viewPtr = view.get(); + mCtx->PSSetShaderResources(aSlot, 1, &viewPtr); +} + +void MLGDeviceD3D11::MaybeLockTexture(ID3D11Texture2D* aTexture) { + RefPtr mutex; + HRESULT hr = aTexture->QueryInterface(__uuidof(IDXGIKeyedMutex), + (void**)getter_AddRefs(mutex)); + if (FAILED(hr) || !mutex) { + return; + } + + hr = mutex->AcquireSync(0, 10000); + + if (hr == WAIT_TIMEOUT) { + gfxDevCrash(LogReason::D3DLockTimeout) << "D3D lock mutex timeout"; + mLockAttemptedTextures.PutEntry(mutex); + } else if (hr == WAIT_ABANDONED) { + gfxCriticalNote << "GFX: D3D11 lock mutex abandoned"; + mLockAttemptedTextures.PutEntry(mutex); + } else if (FAILED(hr)) { + gfxCriticalNote << "D3D11 lock mutex failed: " << hexa(hr); + mLockAttemptedTextures.PutEntry(mutex); + } else { + mLockedTextures.PutEntry(mutex); + } +} + +void MLGDeviceD3D11::SetPSTexturesNV12(uint32_t aSlot, + TextureSource* aTexture) { + MOZ_ASSERT(aTexture->GetFormat() == SurfaceFormat::NV12 || + aTexture->GetFormat() == SurfaceFormat::P010 || + aTexture->GetFormat() == SurfaceFormat::P016); + + TextureSourceD3D11* source = aTexture->AsSourceD3D11(); + if (!source) { + gfxWarning() << "Unknown texture type in SetPSCompoundTexture"; + return; + } + + ID3D11Texture2D* texture = source->GetD3D11Texture(); + if (!texture) { + gfxWarning() << "TextureSourceD3D11 does not have an ID3D11Texture"; + return; + } + + MaybeLockTexture(texture); + + const bool isNV12 = aTexture->GetFormat() == SurfaceFormat::NV12; + + RefPtr views[2]; + D3D11_SHADER_RESOURCE_VIEW_DESC desc = CD3D11_SHADER_RESOURCE_VIEW_DESC( + D3D11_SRV_DIMENSION_TEXTURE2D, + isNV12 ? DXGI_FORMAT_R8_UNORM : DXGI_FORMAT_R16_UNORM); + + HRESULT hr = mDevice->CreateShaderResourceView(texture, &desc, + getter_AddRefs(views[0])); + if (FAILED(hr) || !views[0]) { + gfxWarning() << "Could not bind an SRV for Y plane of NV12 texture: " + << hexa(hr); + return; + } + + desc.Format = isNV12 ? DXGI_FORMAT_R8G8_UNORM : DXGI_FORMAT_R16G16_UNORM; + hr = mDevice->CreateShaderResourceView(texture, &desc, + getter_AddRefs(views[1])); + if (FAILED(hr) || !views[1]) { + gfxWarning() << "Could not bind an SRV for CbCr plane of NV12 texture: " + << hexa(hr); + return; + } + + ID3D11ShaderResourceView* bind[2] = {views[0], views[1]}; + mCtx->PSSetShaderResources(aSlot, 2, bind); +} + +bool MLGDeviceD3D11::InitSyncObject() { + MOZ_ASSERT(!mSyncObject); + MOZ_ASSERT(mDevice); + + mSyncObject = SyncObjectHost::CreateSyncObjectHost(mDevice); + MOZ_ASSERT(mSyncObject); + + return mSyncObject->Init(); +} + +void MLGDeviceD3D11::StartDiagnostics(uint32_t aInvalidPixels) { + mDiagnostics->Start(aInvalidPixels); +} + +void MLGDeviceD3D11::EndDiagnostics() { mDiagnostics->End(); } + +void MLGDeviceD3D11::GetDiagnostics(GPUStats* aStats) { + mDiagnostics->Query(aStats); +} + +bool MLGDeviceD3D11::Synchronize() { + MOZ_ASSERT(mSyncObject); + + if (mSyncObject) { + if (!mSyncObject->Synchronize()) { + // It's timeout or other error. Handle the device-reset here. + HandleDeviceReset("SyncObject"); + return false; + } + } + + return true; +} + +void MLGDeviceD3D11::UnlockAllTextures() { + for (auto iter = mLockedTextures.Iter(); !iter.Done(); iter.Next()) { + RefPtr mutex = iter.Get()->GetKey(); + mutex->ReleaseSync(0); + } + mLockedTextures.Clear(); + mLockAttemptedTextures.Clear(); +} + +void MLGDeviceD3D11::SetDepthTestMode(MLGDepthTestMode aMode) { + mCtx->OMSetDepthStencilState(mDepthStencilStates[aMode], 0xffffffff); +} + +void MLGDeviceD3D11::InsertPresentWaitQuery() { + CD3D11_QUERY_DESC desc(D3D11_QUERY_EVENT); + HRESULT hr = + mDevice->CreateQuery(&desc, getter_AddRefs(mNextWaitForPresentQuery)); + if (FAILED(hr) || !mNextWaitForPresentQuery) { + gfxWarning() << "Could not create D3D11_QUERY_EVENT: " << hexa(hr); + return; + } + + mCtx->End(mNextWaitForPresentQuery); +} + +void MLGDeviceD3D11::WaitForPreviousPresentQuery() { + if (mWaitForPresentQuery) { + BOOL result; + WaitForFrameGPUQuery(mDevice, mCtx, mWaitForPresentQuery, &result); + } + mWaitForPresentQuery = mNextWaitForPresentQuery.forget(); +} + +void MLGDeviceD3D11::Flush() { mCtx->Flush(); } + +void MLGDeviceD3D11::EndFrame() { + // On our Windows 8 x64 machines, we have observed a driver bug related to + // XXSetConstantBuffers1. It appears binding the same buffer to multiple + // slots, and potentially leaving slots bound for many frames (as can + // happen if we bind a high slot, like for blending), can consistently + // cause shaders to read wrong values much later. It is possible there is + // a driver bug related to aliasing and partial binding. + // + // Configuration: GeForce GT 610 (0x104a), Driver 9.18.13.3523, 3-4-2014, + // on Windows 8 x64. + // + // To alleviate this we unbind all buffers at the end of the frame. + static ID3D11Buffer* nullBuffers[6] = { + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + }; + MOZ_ASSERT(MOZ_ARRAY_LENGTH(nullBuffers) >= kMaxVertexShaderConstantBuffers); + MOZ_ASSERT(MOZ_ARRAY_LENGTH(nullBuffers) >= kMaxPixelShaderConstantBuffers); + + mCtx->VSSetConstantBuffers(0, kMaxVertexShaderConstantBuffers, nullBuffers); + mCtx->VSSetConstantBuffers(0, kMaxPixelShaderConstantBuffers, nullBuffers); + + MLGDevice::EndFrame(); +} + +void MLGDeviceD3D11::HandleDeviceReset(const char* aWhere) { + if (!IsValid()) { + return; + } + + Fail("FEATURE_FAILURE_DEVICE_RESET"_ns, nullptr); + + gfxCriticalNote << "GFX: D3D11 detected a device reset in " << aWhere; + if (XRE_IsGPUProcess()) { + GPUParent::GetSingleton()->NotifyDeviceReset(); + } + + UnmapSharedBuffers(); + mIsValid = false; +} + +RefPtr MLGDeviceD3D11::CreateTexture(const gfx::IntSize& aSize, + gfx::SurfaceFormat aFormat, + MLGUsage aUsage, + MLGTextureFlags aFlags) { + return MLGTextureD3D11::Create(mDevice, aSize, aFormat, aUsage, aFlags); +} + +RefPtr MLGDeviceD3D11::CreateTexture(TextureSource* aSource) { + TextureSourceD3D11* source = aSource->AsSourceD3D11(); + if (!source) { + gfxWarning() << "Attempted to wrap a non-D3D11 texture"; + return nullptr; + } + if (!source->GetD3D11Texture()) { + return nullptr; + } + return new MLGTextureD3D11(source->GetD3D11Texture()); +} + +void MLGDeviceD3D11::CopyTexture(MLGTexture* aDest, + const gfx::IntPoint& aTarget, + MLGTexture* aSource, + const gfx::IntRect& aRect) { + MLGTextureD3D11* dest = aDest->AsD3D11(); + MLGTextureD3D11* source = aSource->AsD3D11(); + + // We check both the source and destination copy regions, because + // CopySubresourceRegion is documented as causing a device reset if + // the operation is out-of-bounds. And it's not lying. + IntRect sourceBounds(IntPoint(0, 0), aSource->GetSize()); + if (!sourceBounds.Contains(aRect)) { + gfxWarning() << "Attempt to read out-of-bounds in CopySubresourceRegion: " + << sourceBounds << ", " << aRect; + return; + } + + IntRect destBounds(IntPoint(0, 0), aDest->GetSize()); + if (!destBounds.Contains(IntRect(aTarget, aRect.Size()))) { + gfxWarning() << "Attempt to write out-of-bounds in CopySubresourceRegion: " + << destBounds << ", " << aTarget << ", " << aRect.Size(); + return; + } + + D3D11_BOX box = RectToBox(aRect); + mCtx->CopySubresourceRegion(dest->GetTexture(), 0, aTarget.x, aTarget.y, 0, + source->GetTexture(), 0, &box); +} + +bool MLGDeviceD3D11::VerifyConstantBufferOffsetting() { + RefPtr vs; + HRESULT hr = mDevice->CreateVertexShader(sTestConstantBuffersVS.mData, + sTestConstantBuffersVS.mLength, + nullptr, getter_AddRefs(vs)); + if (FAILED(hr)) { + gfxCriticalNote << "Failed creating vertex shader for buffer test: " + << hexa(hr); + return false; + } + + D3D11_INPUT_ELEMENT_DESC inputDesc[] = {{"POSITION", 0, + DXGI_FORMAT_R32G32_FLOAT, 0, 0, + D3D11_INPUT_PER_VERTEX_DATA, 0}}; + + RefPtr layout; + hr = mDevice->CreateInputLayout( + inputDesc, sizeof(inputDesc) / sizeof(inputDesc[0]), + sTestConstantBuffersVS.mData, sTestConstantBuffersVS.mLength, + getter_AddRefs(layout)); + if (FAILED(hr)) { + gfxCriticalNote << "Failed creating input layout for buffer test: " + << hexa(hr); + return false; + } + + RefPtr rt = + CreateRenderTarget(IntSize(2, 2), MLGRenderTargetFlags::Default); + if (!rt) { + return false; + } + + static const size_t kConstantSize = 4 * sizeof(float); + static const size_t kMinConstants = 16; + static const size_t kNumBindings = 3; + + RefPtr buffer = CreateBuffer( + MLGBufferType::Constant, kConstantSize * kMinConstants * kNumBindings, + MLGUsage::Dynamic, nullptr); + if (!buffer) { + return false; + } + + // Populate the buffer. The shader will pick R from buffer 1, G from buffer + // 2, and B from buffer 3. + { + MLGMappedResource map; + if (!Map(buffer, MLGMapType::WRITE_DISCARD, &map)) { + return false; + } + + *reinterpret_cast(map.mData) = + DeviceColor(1.0f, 0.2f, 0.3f, 1.0f); + *reinterpret_cast(map.mData + kConstantSize * kMinConstants) = + DeviceColor(0.4f, 0.0f, 0.5f, 1.0f); + *reinterpret_cast(map.mData + + (kConstantSize * kMinConstants) * 2) = + DeviceColor(0.6f, 0.7f, 1.0f, 1.0f); + + Unmap(buffer); + } + + Clear(rt, DeviceColor(0.0f, 0.0f, 0.0f, 1.0f)); + SetRenderTarget(rt); + SetViewport(IntRect(0, 0, 2, 2)); + SetScissorRect(Nothing()); + SetBlendState(MLGBlendState::Over); + + SetTopology(MLGPrimitiveTopology::UnitQuad); + SetInputLayout(layout); + SetVertexShader(vs); + SetPixelShader(PixelShaderID::ColoredQuad); + + ID3D11Buffer* buffers[3] = {buffer->AsD3D11()->GetBuffer(), + buffer->AsD3D11()->GetBuffer(), + buffer->AsD3D11()->GetBuffer()}; + UINT offsets[3] = {0 * kMinConstants, 1 * kMinConstants, 2 * kMinConstants}; + UINT counts[3] = {kMinConstants, kMinConstants, kMinConstants}; + + mCtx1->VSSetConstantBuffers1(0, 3, buffers, offsets, counts); + mCtx->Draw(4, 0); + + // Kill bindings to resources. + SetRenderTarget(nullptr); + + ID3D11Buffer* nulls[3] = {nullptr, nullptr, nullptr}; + mCtx->VSSetConstantBuffers(0, 3, nulls); + + RefPtr copy = + CreateTexture(IntSize(2, 2), SurfaceFormat::B8G8R8A8, MLGUsage::Staging, + MLGTextureFlags::None); + if (!copy) { + return false; + } + + CopyTexture(copy, IntPoint(0, 0), rt->GetTexture(), IntRect(0, 0, 2, 2)); + + uint8_t r, g, b, a; + { + MLGMappedResource map; + if (!Map(copy, MLGMapType::READ, &map)) { + return false; + } + r = map.mData[0]; + g = map.mData[1]; + b = map.mData[2]; + a = map.mData[3]; + Unmap(copy); + } + + return r == 255 && g == 0 && b == 255 && a == 255; +} + +static D3D11_BOX RectToBox(const gfx::IntRect& aRect) { + D3D11_BOX box; + box.front = 0; + box.back = 1; + box.left = aRect.X(); + box.top = aRect.Y(); + box.right = aRect.XMost(); + box.bottom = aRect.YMost(); + return box; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/d3d11/MLGDeviceD3D11.h b/gfx/layers/d3d11/MLGDeviceD3D11.h new file mode 100644 index 0000000000..2a6aa8ffc7 --- /dev/null +++ b/gfx/layers/d3d11/MLGDeviceD3D11.h @@ -0,0 +1,326 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_layers_d3d11_MLGDeviceD3D11_h +#define mozilla_gfx_layers_d3d11_MLGDeviceD3D11_h + +#include + +#include "mozilla/layers/MLGDevice.h" +#include "mozilla/layers/SyncObject.h" +#include "mozilla/EnumeratedArray.h" +#include "nsTHashtable.h" +#include "nsPrintfCString.h" + +namespace mozilla { +namespace layers { + +struct GPUStats; +struct ShaderBytes; +class DiagnosticsD3D11; + +class MLGRenderTargetD3D11 final : public MLGRenderTarget { + public: + MLGRenderTargetD3D11(const gfx::IntSize& aSize, MLGRenderTargetFlags aFlags); + + // Create with a new texture. + bool Initialize(ID3D11Device* aDevice); + + // Do not create a texture - use the given one provided, which may be null. + // The depth buffer is still initialized. + bool Initialize(ID3D11Device* aDevice, ID3D11Texture2D* aTexture); + + gfx::IntSize GetSize() const override; + MLGRenderTargetD3D11* AsD3D11() override { return this; } + MLGTexture* GetTexture() override; + + // This is exposed only for MLGSwapChainD3D11. + bool UpdateTexture(ID3D11Texture2D* aTexture); + + ID3D11DepthStencilView* GetDSV(); + ID3D11RenderTargetView* GetRenderTargetView(); + + private: + bool CreateDepthBuffer(ID3D11Device* aDevice); + void ForgetTexture(); + + private: + virtual ~MLGRenderTargetD3D11(); + + private: + RefPtr mTexture; + RefPtr mRTView; + RefPtr mDepthBuffer; + RefPtr mDepthStencilView; + RefPtr mTextureSource; + gfx::IntSize mSize; +}; + +class MLGSwapChainD3D11 final : public MLGSwapChain { + public: + static RefPtr Create(MLGDeviceD3D11* aParent, + ID3D11Device* aDevice, + widget::CompositorWidget* aWidget); + + RefPtr AcquireBackBuffer() override; + gfx::IntSize GetSize() const override; + bool ResizeBuffers(const gfx::IntSize& aSize) override; + void CopyBackbuffer(gfx::DrawTarget* aTarget, + const gfx::IntRect& aBounds) override; + void Present() override; + void ForcePresent() override; + void Destroy() override; + + private: + MLGSwapChainD3D11(MLGDeviceD3D11* aParent, ID3D11Device* aDevice); + virtual ~MLGSwapChainD3D11(); + + bool Initialize(widget::CompositorWidget* aWidget); + void UpdateBackBufferContents(ID3D11Texture2D* aBack); + + private: + RefPtr mParent; + RefPtr mDevice; + RefPtr mSwapChain; + RefPtr mSwapChain1; + RefPtr mRT; + widget::CompositorWidget* mWidget; + gfx::IntSize mSize; + bool mCanUsePartialPresents; +}; + +class MLGResourceD3D11 { + public: + virtual ID3D11Resource* GetResource() const = 0; +}; + +class MLGBufferD3D11 final : public MLGBuffer, public MLGResourceD3D11 { + public: + static RefPtr Create(ID3D11Device* aDevice, + MLGBufferType aType, uint32_t aSize, + MLGUsage aUsage, + const void* aInitialData); + + MLGBufferD3D11* AsD3D11() override { return this; } + ID3D11Resource* GetResource() const override { return mBuffer; } + ID3D11Buffer* GetBuffer() const { return mBuffer; } + MLGResourceD3D11* AsResourceD3D11() override { return this; } + size_t GetSize() const override { return mSize; } + + protected: + MLGBufferD3D11(ID3D11Buffer* aBuffer, MLGBufferType aType, size_t aSize); + virtual ~MLGBufferD3D11(); + + private: + RefPtr mBuffer; + MLGBufferType mType; + size_t mSize; +}; + +class MLGTextureD3D11 final : public MLGTexture, public MLGResourceD3D11 { + public: + explicit MLGTextureD3D11(ID3D11Texture2D* aTexture); + + static RefPtr Create(ID3D11Device* aDevice, + const gfx::IntSize& aSize, + gfx::SurfaceFormat aFormat, + MLGUsage aUsage, + MLGTextureFlags aFlags); + + MLGTextureD3D11* AsD3D11() override { return this; } + MLGResourceD3D11* AsResourceD3D11() override { return this; } + ID3D11Texture2D* GetTexture() const { return mTexture; } + ID3D11Resource* GetResource() const override { return mTexture; } + ID3D11ShaderResourceView* GetShaderResourceView(); + + private: + RefPtr mTexture; + RefPtr mView; +}; + +class MLGDeviceD3D11 final : public MLGDevice { + public: + explicit MLGDeviceD3D11(ID3D11Device* aDevice); + virtual ~MLGDeviceD3D11(); + + bool Initialize() override; + + void StartDiagnostics(uint32_t aInvalidPixels) override; + void EndDiagnostics() override; + void GetDiagnostics(GPUStats* aStats) override; + + MLGDeviceD3D11* AsD3D11() override { return this; } + TextureFactoryIdentifier GetTextureFactoryIdentifier( + widget::CompositorWidget* aWidget) const override; + + RefPtr CreateSwapChainForWidget( + widget::CompositorWidget* aWidget) override; + + int32_t GetMaxTextureSize() const override; + LayersBackend GetLayersBackend() const override; + + void EndFrame() override; + + bool Map(MLGResource* aResource, MLGMapType aType, + MLGMappedResource* aMap) override; + void Unmap(MLGResource* aResource) override; + void UpdatePartialResource(MLGResource* aResource, const gfx::IntRect* aRect, + void* aData, uint32_t aStride) override; + void CopyTexture(MLGTexture* aDest, const gfx::IntPoint& aTarget, + MLGTexture* aSource, const gfx::IntRect& aRect) override; + + RefPtr CreateDataTextureSource( + TextureFlags aFlags) override; + + void SetRenderTarget(MLGRenderTarget* aRT) override; + MLGRenderTarget* GetRenderTarget() override; + void SetViewport(const gfx::IntRect& aViewport) override; + void SetScissorRect(const Maybe& aScissorRect) override; + void SetVertexShader(VertexShaderID aVertexShader) override; + void SetPixelShader(PixelShaderID aPixelShader) override; + void SetSamplerMode(uint32_t aIndex, SamplerMode aSamplerMode) override; + void SetBlendState(MLGBlendState aBlendState) override; + void SetVertexBuffer(uint32_t aSlot, MLGBuffer* aBuffer, uint32_t aStride, + uint32_t aOffset) override; + void SetPSTextures(uint32_t aSlot, uint32_t aNumTextures, + TextureSource* const* aTextures) override; + void SetPSTexture(uint32_t aSlot, MLGTexture* aTexture) override; + void SetPSTexturesNV12(uint32_t aSlot, TextureSource* aTexture) override; + void SetPrimitiveTopology(MLGPrimitiveTopology aTopology) override; + void SetDepthTestMode(MLGDepthTestMode aMode) override; + + void SetVSConstantBuffer(uint32_t aSlot, MLGBuffer* aBuffer) override; + void SetPSConstantBuffer(uint32_t aSlot, MLGBuffer* aBuffer) override; + void SetVSConstantBuffer(uint32_t aSlot, MLGBuffer* aBuffer, + uint32_t aFirstConstant, + uint32_t aNumConstants) override; + void SetPSConstantBuffer(uint32_t aSlot, MLGBuffer* aBuffer, + uint32_t aFirstConstant, + uint32_t aNumConstants) override; + + RefPtr CreateBuffer(MLGBufferType aType, uint32_t aSize, + MLGUsage aUsage, + const void* aInitialData) override; + + RefPtr CreateRenderTarget( + const gfx::IntSize& aSize, MLGRenderTargetFlags aFlags) override; + + RefPtr CreateTexture(const gfx::IntSize& aSize, + gfx::SurfaceFormat aFormat, MLGUsage aUsage, + MLGTextureFlags aFlags) override; + + RefPtr CreateTexture(TextureSource* aSource) override; + + void Clear(MLGRenderTarget* aRT, const gfx::DeviceColor& aColor) override; + void ClearDepthBuffer(MLGRenderTarget* aRT) override; + void ClearView(MLGRenderTarget* aRT, const gfx::DeviceColor& aColor, + const gfx::IntRect* aRects, size_t aNumRects) override; + void Draw(uint32_t aVertexCount, uint32_t aOffset) override; + void DrawInstanced(uint32_t aVertexCountPerInstance, uint32_t aInstanceCount, + uint32_t aVertexOffset, uint32_t aInstanceOffset) override; + void Flush() override; + + // This is exposed for TextureSourceProvider. + ID3D11Device* GetD3D11Device() const { return mDevice; } + + bool Synchronize() override; + void UnlockAllTextures() override; + + void InsertPresentWaitQuery(); + void WaitForPreviousPresentQuery(); + void HandleDeviceReset(const char* aWhere); + + private: + bool InitSyncObject(); + + void MaybeLockTexture(ID3D11Texture2D* aTexture); + + bool InitPixelShader(PixelShaderID aShaderID); + bool InitVertexShader(VertexShaderID aShaderID); + bool InitInputLayout(D3D11_INPUT_ELEMENT_DESC* aDesc, size_t aNumElements, + const ShaderBytes& aCode, VertexShaderID aShaderID); + bool InitRasterizerStates(); + bool InitSamplerStates(); + bool InitBlendStates(); + bool InitDepthStencilState(); + bool VerifyConstantBufferOffsetting() override; + + void SetInputLayout(ID3D11InputLayout* aLayout); + void SetVertexShader(ID3D11VertexShader* aShader); + + // Resolve a TextureSource to an ID3D11ShaderResourceView, locking the + // texture if needed. The lock is released at the end of the frame. + ID3D11ShaderResourceView* ResolveTextureSourceForShader( + TextureSource* aSource); + + private: + RefPtr mDevice; + RefPtr mCtx; + RefPtr mCtx1; + UniquePtr mDiagnostics; + + typedef EnumeratedArray> + PixelShaderArray; + typedef EnumeratedArray> + VertexShaderArray; + typedef EnumeratedArray> + InputLayoutArray; + typedef EnumeratedArray> + SamplerStateArray; + typedef EnumeratedArray> + BlendStateArray; + typedef EnumeratedArray> + DepthStencilStateArray; + + PixelShaderArray mPixelShaders; + VertexShaderArray mVertexShaders; + InputLayoutArray mInputLayouts; + SamplerStateArray mSamplerStates; + BlendStateArray mBlendStates; + DepthStencilStateArray mDepthStencilStates; + RefPtr mRasterizerStateNoScissor; + RefPtr mRasterizerStateScissor; + + RefPtr mSyncObject; + + RefPtr mUnitQuadVB; + RefPtr mUnitTriangleVB; + RefPtr mCurrentVertexShader; + RefPtr mCurrentInputLayout; + RefPtr mCurrentPixelShader; + RefPtr mCurrentBlendState; + + RefPtr mWaitForPresentQuery; + RefPtr mNextWaitForPresentQuery; + + nsTHashtable> mLockedTextures; + nsTHashtable> mLockAttemptedTextures; + + typedef EnumeratedArray + LazyPixelShaderArray; + LazyPixelShaderArray mLazyPixelShaders; + + typedef EnumeratedArray + LazyVertexShaderArray; + LazyVertexShaderArray mLazyVertexShaders; + + bool mScissored; +}; + +} // namespace layers +} // namespace mozilla + +struct ShaderBytes; + +#endif // mozilla_gfx_layers_d3d11_MLGDeviceD3D11_h diff --git a/gfx/layers/d3d11/ReadbackManagerD3D11.cpp b/gfx/layers/d3d11/ReadbackManagerD3D11.cpp new file mode 100644 index 0000000000..bfe7c12af6 --- /dev/null +++ b/gfx/layers/d3d11/ReadbackManagerD3D11.cpp @@ -0,0 +1,153 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ReadbackManagerD3D11.h" +#include "ReadbackProcessor.h" +#include "ReadbackLayer.h" +#include "mozilla/layers/TextureClient.h" +#include "mozilla/gfx/2D.h" + +#include "nsIThread.h" +#include "nsThreadUtils.h" + +namespace mozilla { + +using namespace gfx; + +namespace layers { + +// Structure that contains the information required to execute a readback task, +// the only member accessed off the main thread here is mReadbackTexture. Since +// mSink may be released only on the main thread this object should always be +// destroyed on the main thread! +struct ReadbackTask { + // The texture that we copied the contents of the paintedlayer to. + RefPtr mReadbackTexture; + // The sink that we're trying to read back to. + RefPtr mSink; +}; + +// This class is created and dispatched from the Readback thread but it must be +// destroyed by the main thread. +class ReadbackResultWriterD3D11 final : public nsIRunnable { + ~ReadbackResultWriterD3D11() {} + NS_DECL_THREADSAFE_ISUPPORTS + public: + explicit ReadbackResultWriterD3D11(UniquePtr&& aTask) + : mTask(std::move(aTask)) {} + + NS_IMETHOD Run() override { + D3D10_TEXTURE2D_DESC desc; + mTask->mReadbackTexture->GetDesc(&desc); + + D3D10_MAPPED_TEXTURE2D mappedTex; + // Unless there is an error this map should succeed immediately, as we've + // recently mapped (and unmapped) this copied data on our task thread. + HRESULT hr = mTask->mReadbackTexture->Map(0, D3D10_MAP_READ, 0, &mappedTex); + + if (FAILED(hr)) { + mTask->mSink->ProcessReadback(nullptr); + return NS_OK; + } + + { + RefPtr surf = Factory::CreateWrappingDataSourceSurface( + (uint8_t*)mappedTex.pData, mappedTex.RowPitch, + IntSize(desc.Width, desc.Height), SurfaceFormat::B8G8R8A8); + + mTask->mSink->ProcessReadback(surf); + + MOZ_ASSERT(surf->hasOneRef()); + } + + mTask->mReadbackTexture->Unmap(0); + + return NS_OK; + } + + private: + UniquePtr mTask; +}; + +NS_IMPL_ISUPPORTS(ReadbackResultWriterD3D11, nsIRunnable) + +DWORD WINAPI ReadbackManagerD3D11::StartTaskThread(void* aManager) { + static_cast(aManager)->ProcessTasks(); + + return 0; +} + +ReadbackManagerD3D11::ReadbackManagerD3D11() : mRefCnt(0) { + ::InitializeCriticalSection(&mTaskMutex); + mShutdownEvent = ::CreateEventA(nullptr, FALSE, FALSE, nullptr); + mTaskSemaphore = ::CreateSemaphoreA(nullptr, 0, 1000000, nullptr); + mTaskThread = ::CreateThread(nullptr, 0, StartTaskThread, this, 0, 0); +} + +ReadbackManagerD3D11::~ReadbackManagerD3D11() { + ::SetEvent(mShutdownEvent); + + // This shouldn't take longer than 5 seconds, if it does we're going to choose + // to leak the thread and its synchronisation in favor of crashing or freezing + DWORD result = ::WaitForSingleObject(mTaskThread, 5000); + if (result != WAIT_TIMEOUT) { + ::DeleteCriticalSection(&mTaskMutex); + ::CloseHandle(mShutdownEvent); + ::CloseHandle(mTaskSemaphore); + ::CloseHandle(mTaskThread); + } else { + MOZ_CRASH("ReadbackManager: Task thread did not shutdown in 5 seconds."); + } +} + +void ReadbackManagerD3D11::PostTask(ID3D10Texture2D* aTexture, + TextureReadbackSink* aSink) { + auto task = MakeUnique(); + task->mReadbackTexture = aTexture; + task->mSink = aSink; + + ::EnterCriticalSection(&mTaskMutex); + mPendingReadbackTasks.AppendElement(std::move(task)); + ::LeaveCriticalSection(&mTaskMutex); + + ::ReleaseSemaphore(mTaskSemaphore, 1, nullptr); +} + +void ReadbackManagerD3D11::ProcessTasks() { + HANDLE handles[] = {mTaskSemaphore, mShutdownEvent}; + + while (true) { + DWORD result = ::WaitForMultipleObjects(2, handles, FALSE, INFINITE); + if (result != WAIT_OBJECT_0) { + return; + } + + ::EnterCriticalSection(&mTaskMutex); + if (mPendingReadbackTasks.Length() == 0) { + MOZ_CRASH("Trying to read from an empty array, bad bad bad"); + } + UniquePtr nextReadbackTask = + std::move(mPendingReadbackTasks[0]); + mPendingReadbackTasks.RemoveElementAt(0); + ::LeaveCriticalSection(&mTaskMutex); + + // We want to block here until the texture contents are available, the + // easiest thing is to simply map and unmap. + D3D10_MAPPED_TEXTURE2D mappedTex; + nextReadbackTask->mReadbackTexture->Map(0, D3D10_MAP_READ, 0, &mappedTex); + nextReadbackTask->mReadbackTexture->Unmap(0); + + // We can only send the update to the sink on the main thread, so post an + // event there to do so. Ownership of the task is passed from + // mPendingReadbackTasks to ReadbackResultWriter here. + nsCOMPtr thread = do_GetMainThread(); + thread->Dispatch(new ReadbackResultWriterD3D11(std::move(nextReadbackTask)), + nsIEventTarget::DISPATCH_NORMAL); + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/d3d11/ReadbackManagerD3D11.h b/gfx/layers/d3d11/ReadbackManagerD3D11.h new file mode 100644 index 0000000000..00f5dfd467 --- /dev/null +++ b/gfx/layers/d3d11/ReadbackManagerD3D11.h @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_READBACKMANAGERD3D11_H +#define GFX_READBACKMANAGERD3D11_H + +#include +#include + +#include "mozilla/UniquePtr.h" +#include "nsTArray.h" + +namespace mozilla { +namespace layers { + +class TextureReadbackSink; +struct ReadbackTask; + +class ReadbackManagerD3D11 final { + NS_INLINE_DECL_REFCOUNTING(ReadbackManagerD3D11) + public: + ReadbackManagerD3D11(); + + /** + * Tell the readback manager to post a readback task. + * + * @param aTexture D3D10_USAGE_STAGING texture that will contain the data that + * was readback. + * @param aSink TextureReadbackSink that the resulting DataSourceSurface + * should be dispatched to. + */ + void PostTask(ID3D10Texture2D* aTexture, TextureReadbackSink* aSink); + + private: + ~ReadbackManagerD3D11(); + + static DWORD WINAPI StartTaskThread(void* aManager); + + void ProcessTasks(); + + // The invariant maintained by |mTaskSemaphore| is that the readback thread + // will awaken from WaitForMultipleObjects() at least once per readback + // task enqueued by the main thread. Since the readback thread processes + // exactly one task per wakeup (with one exception), no tasks are lost. The + // exception is when the readback thread is shut down, which orphans the + // remaining tasks, on purpose. + HANDLE mTaskSemaphore; + // Event signaled when the task thread should shutdown + HANDLE mShutdownEvent; + // Handle to the task thread + HANDLE mTaskThread; + + // FiFo list of readback tasks that are to be executed. Access is synchronized + // by mTaskMutex. + CRITICAL_SECTION mTaskMutex; + nsTArray> mPendingReadbackTasks; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_READBACKMANAGERD3D11_H */ diff --git a/gfx/layers/d3d11/ShaderDefinitionsD3D11.h b/gfx/layers/d3d11/ShaderDefinitionsD3D11.h new file mode 100644 index 0000000000..1e15cf7987 --- /dev/null +++ b/gfx/layers/d3d11/ShaderDefinitionsD3D11.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_layers_d3d11_ShaderDefinitionsD3D11_h +#define mozilla_gfx_layers_d3d11_ShaderDefinitionsD3D11_h + +#include "mozilla/gfx/Rect.h" + +namespace mozilla { +namespace layers { + +struct VertexShaderConstants { + float layerTransform[4][4]; + float projection[4][4]; + float renderTargetOffset[4]; + gfx::Rect textureCoords; + gfx::Rect layerQuad; + float maskTransform[4][4]; + float backdropTransform[4][4]; +}; + +struct PixelShaderConstants { + float layerColor[4]; + float layerOpacity[4]; + int blendConfig[4]; + float vCoefficient[4]; + float yuvColorMatrix[3][4]; +}; + +struct Vertex { + float position[2]; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_gfx_layers_d3d11_ShaderDefinitionsD3D11_h diff --git a/gfx/layers/d3d11/TextureD3D11.cpp b/gfx/layers/d3d11/TextureD3D11.cpp new file mode 100644 index 0000000000..7acfd61544 --- /dev/null +++ b/gfx/layers/d3d11/TextureD3D11.cpp @@ -0,0 +1,1867 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TextureD3D11.h" + +#include "CompositorD3D11.h" +#include "Effects.h" +#include "MainThreadUtils.h" +#include "PaintThread.h" +#include "ReadbackManagerD3D11.h" +#include "gfx2DGlue.h" +#include "gfxContext.h" +#include "gfxWindowsPlatform.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/Telemetry.h" +#include "mozilla/gfx/DataSurfaceHelpers.h" +#include "mozilla/gfx/DeviceManagerDx.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/layers/CompositorBridgeChild.h" +#include "mozilla/webrender/RenderD3D11TextureHost.h" +#include "mozilla/webrender/RenderThread.h" +#include "mozilla/webrender/WebRenderAPI.h" + +namespace mozilla { + +using namespace gfx; + +namespace layers { + +static const GUID sD3D11TextureUsage = { + 0xd89275b0, + 0x6c7d, + 0x4038, + {0xb5, 0xfa, 0x4d, 0x87, 0x16, 0xd5, 0xcc, 0x4e}}; + +/* This class gets its lifetime tied to a D3D texture + * and increments memory usage on construction and decrements + * on destruction */ +class TextureMemoryMeasurer final : public IUnknown { + public: + explicit TextureMemoryMeasurer(size_t aMemoryUsed) { + mMemoryUsed = aMemoryUsed; + gfxWindowsPlatform::sD3D11SharedTextures += mMemoryUsed; + mRefCnt = 0; + } + STDMETHODIMP_(ULONG) AddRef() { + mRefCnt++; + return mRefCnt; + } + STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject) { + IUnknown* punk = nullptr; + if (riid == IID_IUnknown) { + punk = this; + } + *ppvObject = punk; + if (punk) { + punk->AddRef(); + return S_OK; + } else { + return E_NOINTERFACE; + } + } + + STDMETHODIMP_(ULONG) Release() { + int refCnt = --mRefCnt; + if (refCnt == 0) { + gfxWindowsPlatform::sD3D11SharedTextures -= mMemoryUsed; + delete this; + } + return refCnt; + } + + private: + int mRefCnt; + int mMemoryUsed; + + ~TextureMemoryMeasurer() = default; +}; + +static DXGI_FORMAT SurfaceFormatToDXGIFormat(gfx::SurfaceFormat aFormat) { + switch (aFormat) { + case SurfaceFormat::B8G8R8A8: + return DXGI_FORMAT_B8G8R8A8_UNORM; + case SurfaceFormat::B8G8R8X8: + return DXGI_FORMAT_B8G8R8A8_UNORM; + case SurfaceFormat::R8G8B8A8: + return DXGI_FORMAT_R8G8B8A8_UNORM; + case SurfaceFormat::R8G8B8X8: + return DXGI_FORMAT_R8G8B8A8_UNORM; + case SurfaceFormat::A8: + return DXGI_FORMAT_R8_UNORM; + case SurfaceFormat::A16: + return DXGI_FORMAT_R16_UNORM; + default: + MOZ_ASSERT(false, "unsupported format"); + return DXGI_FORMAT_UNKNOWN; + } +} + +void ReportTextureMemoryUsage(ID3D11Texture2D* aTexture, size_t aBytes) { + aTexture->SetPrivateDataInterface(sD3D11TextureUsage, + new TextureMemoryMeasurer(aBytes)); +} + +static uint32_t GetRequiredTilesD3D11(uint32_t aSize, uint32_t aMaxSize) { + uint32_t requiredTiles = aSize / aMaxSize; + if (aSize % aMaxSize) { + requiredTiles++; + } + return requiredTiles; +} + +static IntRect GetTileRectD3D11(uint32_t aID, IntSize aSize, + uint32_t aMaxSize) { + uint32_t horizontalTiles = GetRequiredTilesD3D11(aSize.width, aMaxSize); + uint32_t verticalTiles = GetRequiredTilesD3D11(aSize.height, aMaxSize); + + uint32_t verticalTile = aID / horizontalTiles; + uint32_t horizontalTile = aID % horizontalTiles; + + return IntRect( + horizontalTile * aMaxSize, verticalTile * aMaxSize, + horizontalTile < (horizontalTiles - 1) ? aMaxSize + : aSize.width % aMaxSize, + verticalTile < (verticalTiles - 1) ? aMaxSize : aSize.height % aMaxSize); +} + +AutoTextureLock::AutoTextureLock(IDXGIKeyedMutex* aMutex, HRESULT& aResult, + uint32_t aTimeout) { + mMutex = aMutex; + if (mMutex) { + mResult = mMutex->AcquireSync(0, aTimeout); + aResult = mResult; + } else { + aResult = E_INVALIDARG; + } +} + +AutoTextureLock::~AutoTextureLock() { + if (mMutex && !FAILED(mResult) && mResult != WAIT_TIMEOUT && + mResult != WAIT_ABANDONED) { + mMutex->ReleaseSync(0); + } +} + +ID3D11ShaderResourceView* TextureSourceD3D11::GetShaderResourceView() { + MOZ_ASSERT(mTexture == GetD3D11Texture(), + "You need to override GetShaderResourceView if you're overriding " + "GetD3D11Texture!"); + + if (!mSRV && mTexture) { + RefPtr device; + mTexture->GetDevice(getter_AddRefs(device)); + + // see comment in CompositingRenderTargetD3D11 constructor + CD3D11_SHADER_RESOURCE_VIEW_DESC srvDesc(D3D11_SRV_DIMENSION_TEXTURE2D, + mFormatOverride); + D3D11_SHADER_RESOURCE_VIEW_DESC* desc = + mFormatOverride == DXGI_FORMAT_UNKNOWN ? nullptr : &srvDesc; + + HRESULT hr = + device->CreateShaderResourceView(mTexture, desc, getter_AddRefs(mSRV)); + if (FAILED(hr)) { + gfxCriticalNote << "[D3D11] TextureSourceD3D11:GetShaderResourceView " + "CreateSRV failure " + << gfx::hexa(hr); + return nullptr; + } + } + return mSRV; +} + +DataTextureSourceD3D11::DataTextureSourceD3D11(ID3D11Device* aDevice, + SurfaceFormat aFormat, + TextureFlags aFlags) + : mDevice(aDevice), + mFormat(aFormat), + mFlags(aFlags), + mCurrentTile(0), + mIsTiled(false), + mIterating(false), + mAllowTextureUploads(true) {} + +DataTextureSourceD3D11::DataTextureSourceD3D11(ID3D11Device* aDevice, + SurfaceFormat aFormat, + ID3D11Texture2D* aTexture) + : mDevice(aDevice), + mFormat(aFormat), + mFlags(TextureFlags::NO_FLAGS), + mCurrentTile(0), + mIsTiled(false), + mIterating(false), + mAllowTextureUploads(false) { + mTexture = aTexture; + D3D11_TEXTURE2D_DESC desc; + aTexture->GetDesc(&desc); + + mSize = IntSize(desc.Width, desc.Height); +} + +DataTextureSourceD3D11::DataTextureSourceD3D11(gfx::SurfaceFormat aFormat, + TextureSourceProvider* aProvider, + ID3D11Texture2D* aTexture) + : DataTextureSourceD3D11(aProvider->GetD3D11Device(), aFormat, aTexture) {} + +DataTextureSourceD3D11::DataTextureSourceD3D11(gfx::SurfaceFormat aFormat, + TextureSourceProvider* aProvider, + TextureFlags aFlags) + : DataTextureSourceD3D11(aProvider->GetD3D11Device(), aFormat, aFlags) {} + +DataTextureSourceD3D11::~DataTextureSourceD3D11() {} + +template // ID3D10Texture2D or ID3D11Texture2D +static bool LockD3DTexture(T* aTexture) { + MOZ_ASSERT(aTexture); + RefPtr mutex; + aTexture->QueryInterface((IDXGIKeyedMutex**)getter_AddRefs(mutex)); + // Textures created by the DXVA decoders don't have a mutex for + // synchronization + if (mutex) { + HRESULT hr = mutex->AcquireSync(0, 10000); + if (hr == WAIT_TIMEOUT) { + gfxDevCrash(LogReason::D3DLockTimeout) << "D3D lock mutex timeout"; + } else if (hr == WAIT_ABANDONED) { + gfxCriticalNote << "GFX: D3D11 lock mutex abandoned"; + } + + if (FAILED(hr)) { + NS_WARNING("Failed to lock the texture"); + return false; + } + } + return true; +} + +template +static bool HasKeyedMutex(T* aTexture) { + RefPtr mutex; + aTexture->QueryInterface((IDXGIKeyedMutex**)getter_AddRefs(mutex)); + return !!mutex; +} + +template // ID3D10Texture2D or ID3D11Texture2D +static void UnlockD3DTexture(T* aTexture) { + MOZ_ASSERT(aTexture); + RefPtr mutex; + aTexture->QueryInterface((IDXGIKeyedMutex**)getter_AddRefs(mutex)); + if (mutex) { + HRESULT hr = mutex->ReleaseSync(0); + if (FAILED(hr)) { + NS_WARNING("Failed to unlock the texture"); + } + } +} + +D3D11TextureData::D3D11TextureData(ID3D11Texture2D* aTexture, + gfx::IntSize aSize, + gfx::SurfaceFormat aFormat, + TextureAllocationFlags aFlags) + : mSize(aSize), + mFormat(aFormat), + mNeedsClear(aFlags & ALLOC_CLEAR_BUFFER), + mNeedsClearWhite(aFlags & ALLOC_CLEAR_BUFFER_WHITE), + mIsForOutOfBandContent(aFlags & ALLOC_FOR_OUT_OF_BAND_CONTENT), + mTexture(aTexture), + mAllocationFlags(aFlags) { + MOZ_ASSERT(aTexture); + mHasSynchronization = HasKeyedMutex(aTexture); +} + +static void DestroyDrawTarget(RefPtr& aDT, + RefPtr& aTexture) { + // An Azure DrawTarget needs to be locked when it gets nullptr'ed as this is + // when it calls EndDraw. This EndDraw should not execute anything so it + // shouldn't -really- need the lock but the debug layer chokes on this. + LockD3DTexture(aTexture.get()); + aDT = nullptr; + UnlockD3DTexture(aTexture.get()); + aTexture = nullptr; +} + +D3D11TextureData::~D3D11TextureData() { + if (mDrawTarget) { + DestroyDrawTarget(mDrawTarget, mTexture); + } +} + +bool D3D11TextureData::Lock(OpenMode aMode) { + if (!LockD3DTexture(mTexture.get())) { + return false; + } + + if (NS_IsMainThread() && !mIsForOutOfBandContent) { + if (!PrepareDrawTargetInLock(aMode)) { + Unlock(); + return false; + } + } + + return true; +} + +bool D3D11TextureData::PrepareDrawTargetInLock(OpenMode aMode) { + // Make sure that successful write-lock means we will have a DrawTarget to + // write into. + if (!mDrawTarget && + (aMode & OpenMode::OPEN_WRITE || mNeedsClear || mNeedsClearWhite)) { + mDrawTarget = BorrowDrawTarget(); + if (!mDrawTarget) { + return false; + } + } + + // Reset transform + mDrawTarget->SetTransform(Matrix()); + + if (mNeedsClear) { + mDrawTarget->ClearRect(Rect(0, 0, mSize.width, mSize.height)); + mNeedsClear = false; + } + if (mNeedsClearWhite) { + mDrawTarget->FillRect(Rect(0, 0, mSize.width, mSize.height), + ColorPattern(DeviceColor(1.0, 1.0, 1.0, 1.0))); + mNeedsClearWhite = false; + } + + return true; +} + +void D3D11TextureData::Unlock() { UnlockD3DTexture(mTexture.get()); } + +void D3D11TextureData::FillInfo(TextureData::Info& aInfo) const { + aInfo.size = mSize; + aInfo.format = mFormat; + aInfo.supportsMoz2D = true; + aInfo.hasIntermediateBuffer = false; + aInfo.hasSynchronization = mHasSynchronization; +} + +void D3D11TextureData::SyncWithObject(RefPtr aSyncObject) { + if (!aSyncObject || mHasSynchronization) { + // When we have per texture synchronization we sync using the keyed mutex. + return; + } + + MOZ_ASSERT(aSyncObject->GetSyncType() == SyncObjectClient::SyncType::D3D11); + SyncObjectD3D11Client* sync = + static_cast(aSyncObject.get()); + sync->RegisterTexture(mTexture); +} + +bool D3D11TextureData::SerializeSpecific( + SurfaceDescriptorD3D10* const aOutDesc) { + RefPtr resource; + GetDXGIResource((IDXGIResource**)getter_AddRefs(resource)); + if (!resource) { + return false; + } + HANDLE sharedHandle; + HRESULT hr = resource->GetSharedHandle(&sharedHandle); + if (FAILED(hr)) { + LOGD3D11("Error getting shared handle for texture."); + return false; + } + + *aOutDesc = SurfaceDescriptorD3D10((WindowsHandle)sharedHandle, mFormat, + mSize, mYUVColorSpace, mColorRange); + return true; +} + +bool D3D11TextureData::Serialize(SurfaceDescriptor& aOutDescriptor) { + SurfaceDescriptorD3D10 desc; + if (!SerializeSpecific(&desc)) return false; + + aOutDescriptor = std::move(desc); + return true; +} + +void D3D11TextureData::GetSubDescriptor( + RemoteDecoderVideoSubDescriptor* const aOutDesc) { + SurfaceDescriptorD3D10 ret; + if (!SerializeSpecific(&ret)) return; + + *aOutDesc = std::move(ret); +} + +D3D11TextureData* D3D11TextureData::Create(IntSize aSize, SurfaceFormat aFormat, + TextureAllocationFlags aFlags, + ID3D11Device* aDevice) { + return Create(aSize, aFormat, nullptr, aFlags, aDevice); +} + +D3D11TextureData* D3D11TextureData::Create(SourceSurface* aSurface, + TextureAllocationFlags aFlags, + ID3D11Device* aDevice) { + return Create(aSurface->GetSize(), aSurface->GetFormat(), aSurface, aFlags, + aDevice); +} + +D3D11TextureData* D3D11TextureData::Create(IntSize aSize, SurfaceFormat aFormat, + SourceSurface* aSurface, + TextureAllocationFlags aFlags, + ID3D11Device* aDevice) { + if (aFormat == SurfaceFormat::A8) { + // Currently we don't support A8 surfaces. Fallback. + return nullptr; + } + + // Just grab any device. We never use the immediate context, so the devices + // are fine to use from any thread. + RefPtr device = aDevice; + if (!device) { + device = DeviceManagerDx::Get()->GetContentDevice(); + if (!device) { + return nullptr; + } + } + + CD3D11_TEXTURE2D_DESC newDesc( + DXGI_FORMAT_B8G8R8A8_UNORM, aSize.width, aSize.height, 1, 1, + D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE); + + if (aFormat == SurfaceFormat::NV12) { + newDesc.Format = DXGI_FORMAT_NV12; + } else if (aFormat == SurfaceFormat::P010) { + newDesc.Format = DXGI_FORMAT_P010; + } else if (aFormat == SurfaceFormat::P016) { + newDesc.Format = DXGI_FORMAT_P016; + } + + newDesc.MiscFlags = D3D11_RESOURCE_MISC_SHARED; + if (!NS_IsMainThread() || !!(aFlags & ALLOC_FOR_OUT_OF_BAND_CONTENT)) { + // On the main thread we use the syncobject to handle synchronization. + if (!(aFlags & ALLOC_MANUAL_SYNCHRONIZATION)) { + newDesc.MiscFlags = D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX; + } + } + + if (aSurface && newDesc.MiscFlags == D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX && + !DeviceManagerDx::Get()->CanInitializeKeyedMutexTextures()) { + return nullptr; + } + + D3D11_SUBRESOURCE_DATA uploadData; + D3D11_SUBRESOURCE_DATA* uploadDataPtr = nullptr; + RefPtr srcSurf; + DataSourceSurface::MappedSurface sourceMap; + + if (aSurface) { + srcSurf = aSurface->GetDataSurface(); + + if (!srcSurf) { + gfxCriticalError() + << "Failed to GetDataSurface in D3D11TextureData::Create"; + return nullptr; + } + + if (!srcSurf->Map(DataSourceSurface::READ, &sourceMap)) { + gfxCriticalError() + << "Failed to map source surface for D3D11TextureData::Create"; + return nullptr; + } + } + + if (srcSurf && !DeviceManagerDx::Get()->HasCrashyInitData()) { + uploadData.pSysMem = sourceMap.mData; + uploadData.SysMemPitch = sourceMap.mStride; + uploadData.SysMemSlicePitch = 0; // unused + + uploadDataPtr = &uploadData; + } + + // See bug 1397040 + RefPtr mt; + device->QueryInterface((ID3D10Multithread**)getter_AddRefs(mt)); + + RefPtr texture11; + + { + D3D11MTAutoEnter lock(mt.forget()); + + HRESULT hr = device->CreateTexture2D(&newDesc, uploadDataPtr, + getter_AddRefs(texture11)); + + if (FAILED(hr) || !texture11) { + gfxCriticalNote << "[D3D11] 2 CreateTexture2D failure Size: " << aSize + << "texture11: " << texture11 + << " Code: " << gfx::hexa(hr); + return nullptr; + } + + if (srcSurf && DeviceManagerDx::Get()->HasCrashyInitData()) { + D3D11_BOX box; + box.front = box.top = box.left = 0; + box.back = 1; + box.right = aSize.width; + box.bottom = aSize.height; + RefPtr ctx; + device->GetImmediateContext(getter_AddRefs(ctx)); + ctx->UpdateSubresource(texture11, 0, &box, sourceMap.mData, + sourceMap.mStride, 0); + } + } + + if (srcSurf) { + srcSurf->Unmap(); + } + + // If we created the texture with a keyed mutex, then we expect all operations + // on it to be synchronized using it. If we did an initial upload using + // aSurface then bizarely this isn't covered, so we insert a manual + // lock/unlock pair to force this. + if (aSurface && newDesc.MiscFlags == D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX) { + if (!LockD3DTexture(texture11.get())) { + return nullptr; + } + UnlockD3DTexture(texture11.get()); + } + texture11->SetPrivateDataInterface( + sD3D11TextureUsage, + new TextureMemoryMeasurer(newDesc.Width * newDesc.Height * 4)); + return new D3D11TextureData(texture11, aSize, aFormat, aFlags); +} + +void D3D11TextureData::Deallocate(LayersIPCChannel* aAllocator) { + mDrawTarget = nullptr; + mTexture = nullptr; +} + +TextureData* D3D11TextureData::CreateSimilar( + LayersIPCChannel* aAllocator, LayersBackend aLayersBackend, + TextureFlags aFlags, TextureAllocationFlags aAllocFlags) const { + return D3D11TextureData::Create(mSize, mFormat, aAllocFlags); +} + +void D3D11TextureData::GetDXGIResource(IDXGIResource** aOutResource) { + mTexture->QueryInterface(aOutResource); +} + +TextureFlags D3D11TextureData::GetTextureFlags() const { + TextureFlags flags = TextureFlags::NO_FLAGS; + // With WebRender, resource open happens asynchronously on RenderThread. + // During opening the resource on host side, TextureClient needs to be alive. + // With WAIT_HOST_USAGE_END, keep TextureClient alive during host side usage. + if (gfx::gfxVars::UseWebRender()) { + flags |= TextureFlags::WAIT_HOST_USAGE_END; + } + return flags; +} + +DXGIYCbCrTextureData* DXGIYCbCrTextureData::Create( + IDirect3DTexture9* aTextureY, IDirect3DTexture9* aTextureCb, + IDirect3DTexture9* aTextureCr, HANDLE aHandleY, HANDLE aHandleCb, + HANDLE aHandleCr, const gfx::IntSize& aSize, const gfx::IntSize& aSizeY, + const gfx::IntSize& aSizeCbCr, gfx::ColorDepth aColorDepth, + YUVColorSpace aYUVColorSpace, gfx::ColorRange aColorRange) { + if (!aHandleY || !aHandleCb || !aHandleCr || !aTextureY || !aTextureCb || + !aTextureCr) { + return nullptr; + } + + DXGIYCbCrTextureData* texture = new DXGIYCbCrTextureData(); + texture->mHandles[0] = aHandleY; + texture->mHandles[1] = aHandleCb; + texture->mHandles[2] = aHandleCr; + texture->mD3D9Textures[0] = aTextureY; + texture->mD3D9Textures[1] = aTextureCb; + texture->mD3D9Textures[2] = aTextureCr; + texture->mSize = aSize; + texture->mSizeY = aSizeY; + texture->mSizeCbCr = aSizeCbCr; + texture->mColorDepth = aColorDepth; + texture->mYUVColorSpace = aYUVColorSpace; + texture->mColorRange = aColorRange; + + return texture; +} + +DXGIYCbCrTextureData* DXGIYCbCrTextureData::Create( + ID3D11Texture2D* aTextureY, ID3D11Texture2D* aTextureCb, + ID3D11Texture2D* aTextureCr, const gfx::IntSize& aSize, + const gfx::IntSize& aSizeY, const gfx::IntSize& aSizeCbCr, + gfx::ColorDepth aColorDepth, YUVColorSpace aYUVColorSpace, + gfx::ColorRange aColorRange) { + if (!aTextureY || !aTextureCb || !aTextureCr) { + return nullptr; + } + + aTextureY->SetPrivateDataInterface( + sD3D11TextureUsage, + new TextureMemoryMeasurer(aSizeY.width * aSizeY.height)); + aTextureCb->SetPrivateDataInterface( + sD3D11TextureUsage, + new TextureMemoryMeasurer(aSizeCbCr.width * aSizeCbCr.height)); + aTextureCr->SetPrivateDataInterface( + sD3D11TextureUsage, + new TextureMemoryMeasurer(aSizeCbCr.width * aSizeCbCr.height)); + + RefPtr resource; + + aTextureY->QueryInterface((IDXGIResource**)getter_AddRefs(resource)); + + HANDLE handleY; + HRESULT hr = resource->GetSharedHandle(&handleY); + if (FAILED(hr)) { + return nullptr; + } + + aTextureCb->QueryInterface((IDXGIResource**)getter_AddRefs(resource)); + + HANDLE handleCb; + hr = resource->GetSharedHandle(&handleCb); + if (FAILED(hr)) { + return nullptr; + } + + aTextureCr->QueryInterface((IDXGIResource**)getter_AddRefs(resource)); + HANDLE handleCr; + hr = resource->GetSharedHandle(&handleCr); + if (FAILED(hr)) { + return nullptr; + } + + DXGIYCbCrTextureData* texture = new DXGIYCbCrTextureData(); + texture->mHandles[0] = handleY; + texture->mHandles[1] = handleCb; + texture->mHandles[2] = handleCr; + texture->mD3D11Textures[0] = aTextureY; + texture->mD3D11Textures[1] = aTextureCb; + texture->mD3D11Textures[2] = aTextureCr; + texture->mSize = aSize; + texture->mSizeY = aSizeY; + texture->mSizeCbCr = aSizeCbCr; + texture->mColorDepth = aColorDepth; + texture->mYUVColorSpace = aYUVColorSpace; + texture->mColorRange = aColorRange; + + return texture; +} + +void DXGIYCbCrTextureData::FillInfo(TextureData::Info& aInfo) const { + aInfo.size = mSize; + aInfo.format = gfx::SurfaceFormat::YUV; + aInfo.supportsMoz2D = false; + aInfo.hasIntermediateBuffer = false; + aInfo.hasSynchronization = false; +} + +void DXGIYCbCrTextureData::SerializeSpecific( + SurfaceDescriptorDXGIYCbCr* const aOutDesc) { + *aOutDesc = SurfaceDescriptorDXGIYCbCr( + (WindowsHandle)mHandles[0], (WindowsHandle)mHandles[1], + (WindowsHandle)mHandles[2], mSize, mSizeY, mSizeCbCr, mColorDepth, + mYUVColorSpace, mColorRange); +} + +bool DXGIYCbCrTextureData::Serialize(SurfaceDescriptor& aOutDescriptor) { + SurfaceDescriptorDXGIYCbCr desc; + SerializeSpecific(&desc); + + aOutDescriptor = std::move(desc); + return true; +} + +void DXGIYCbCrTextureData::GetSubDescriptor( + RemoteDecoderVideoSubDescriptor* const aOutDesc) { + SurfaceDescriptorDXGIYCbCr desc; + SerializeSpecific(&desc); + + *aOutDesc = std::move(desc); +} + +void DXGIYCbCrTextureData::Deallocate(LayersIPCChannel*) { + mD3D9Textures[0] = nullptr; + mD3D9Textures[1] = nullptr; + mD3D9Textures[2] = nullptr; + mD3D11Textures[0] = nullptr; + mD3D11Textures[1] = nullptr; + mD3D11Textures[2] = nullptr; +} + +TextureFlags DXGIYCbCrTextureData::GetTextureFlags() const { + TextureFlags flags = TextureFlags::DEALLOCATE_MAIN_THREAD; + // With WebRender, resource open happens asynchronously on RenderThread. + // During opening the resource on host side, TextureClient needs to be alive. + // With WAIT_HOST_USAGE_END, keep TextureClient alive during host side usage. + if (gfx::gfxVars::UseWebRender()) { + flags |= TextureFlags::WAIT_HOST_USAGE_END; + } + return flags; +} + +already_AddRefed CreateTextureHostD3D11( + const SurfaceDescriptor& aDesc, ISurfaceAllocator* aDeallocator, + LayersBackend aBackend, TextureFlags aFlags) { + RefPtr result; + switch (aDesc.type()) { + case SurfaceDescriptor::TSurfaceDescriptorD3D10: { + result = + new DXGITextureHostD3D11(aFlags, aDesc.get_SurfaceDescriptorD3D10()); + break; + } + case SurfaceDescriptor::TSurfaceDescriptorDXGIYCbCr: { + result = new DXGIYCbCrTextureHostD3D11( + aFlags, aDesc.get_SurfaceDescriptorDXGIYCbCr()); + break; + } + default: { + MOZ_ASSERT_UNREACHABLE("Unsupported SurfaceDescriptor type"); + } + } + return result.forget(); +} + +already_AddRefed D3D11TextureData::BorrowDrawTarget() { + MOZ_ASSERT(NS_IsMainThread() || PaintThread::IsOnPaintThread() || + NS_IsInCanvasThreadOrWorker()); + + if (!mDrawTarget && mTexture) { + // This may return a null DrawTarget + mDrawTarget = Factory::CreateDrawTargetForD3D11Texture(mTexture, mFormat); + if (!mDrawTarget) { + gfxCriticalNote << "Could not borrow DrawTarget (D3D11) " << (int)mFormat; + } + } + + RefPtr result = mDrawTarget; + return result.forget(); +} + +bool D3D11TextureData::UpdateFromSurface(gfx::SourceSurface* aSurface) { + // Supporting texture updates after creation requires an ID3D11DeviceContext + // and those aren't threadsafe. We'd need to either lock, or have a device for + // whatever thread this runs on and we're trying to avoid extra devices (bug + // 1284672). + MOZ_ASSERT(false, + "UpdateFromSurface not supported for D3D11! Use CreateFromSurface " + "instead"); + return false; +} + +DXGITextureHostD3D11::DXGITextureHostD3D11( + TextureFlags aFlags, const SurfaceDescriptorD3D10& aDescriptor) + : TextureHost(aFlags), + mSize(aDescriptor.size()), + mHandle(aDescriptor.handle()), + mFormat(aDescriptor.format()), + mYUVColorSpace(aDescriptor.yUVColorSpace()), + mColorRange(aDescriptor.colorRange()), + mIsLocked(false) {} + +bool DXGITextureHostD3D11::EnsureTexture() { + RefPtr device; + if (mTexture) { + mTexture->GetDevice(getter_AddRefs(device)); + if (device == DeviceManagerDx::Get()->GetCompositorDevice()) { + NS_WARNING("Incompatible texture."); + return true; + } + mTexture = nullptr; + } + + device = GetDevice(); + if (!device || device != DeviceManagerDx::Get()->GetCompositorDevice()) { + NS_WARNING("No device or incompatible device."); + return false; + } + + HRESULT hr = device->OpenSharedResource( + (HANDLE)mHandle, __uuidof(ID3D11Texture2D), + (void**)(ID3D11Texture2D**)getter_AddRefs(mTexture)); + if (FAILED(hr)) { + MOZ_ASSERT(false, "Failed to open shared texture"); + return false; + } + + D3D11_TEXTURE2D_DESC desc; + mTexture->GetDesc(&desc); + mSize = IntSize(desc.Width, desc.Height); + return true; +} + +RefPtr DXGITextureHostD3D11::GetDevice() { + if (mFlags & TextureFlags::INVALID_COMPOSITOR) { + return nullptr; + } + + if (mProvider) { + return mProvider->GetD3D11Device(); + } else { + return mDevice; + } +} + +void DXGITextureHostD3D11::SetTextureSourceProvider( + TextureSourceProvider* aProvider) { + if (!aProvider || !aProvider->GetD3D11Device()) { + mDevice = nullptr; + mProvider = nullptr; + mTextureSource = nullptr; + return; + } + + if (mDevice && (aProvider->GetD3D11Device() != mDevice)) { + if (mTextureSource) { + mTextureSource->Reset(); + } + mTextureSource = nullptr; + return; + } + + mProvider = aProvider; + mDevice = aProvider->GetD3D11Device(); + + if (mTextureSource) { + mTextureSource->SetTextureSourceProvider(aProvider); + } +} + +bool DXGITextureHostD3D11::Lock() { + if (!mProvider) { + // Make an early return here if we call SetTextureSourceProvider() with an + // incompatible compositor. This check tries to prevent the problem where we + // use that incompatible compositor to compose this texture. + return false; + } + + return LockInternal(); +} + +bool DXGITextureHostD3D11::LockWithoutCompositor() { + // Unlike the normal Lock() function, this function may be called when + // mProvider is nullptr such as during WebVR frame submission. So, there is + // no 'mProvider' checking here. + if (!mDevice) { + mDevice = DeviceManagerDx::Get()->GetCompositorDevice(); + } + return LockInternal(); +} + +void DXGITextureHostD3D11::Unlock() { UnlockInternal(); } + +void DXGITextureHostD3D11::UnlockWithoutCompositor() { UnlockInternal(); } + +bool DXGITextureHostD3D11::LockInternal() { + if (!GetDevice()) { + NS_WARNING("trying to lock a TextureHost without a D3D device"); + return false; + } + + if (!EnsureTextureSource()) { + return false; + } + + mIsLocked = LockD3DTexture(mTextureSource->GetD3D11Texture()); + + return mIsLocked; +} + +already_AddRefed DXGITextureHostD3D11::GetAsSurface() { + if (!gfxVars::UseWebRender()) { + return nullptr; + } + + switch (GetFormat()) { + case gfx::SurfaceFormat::R8G8B8X8: + case gfx::SurfaceFormat::R8G8B8A8: + case gfx::SurfaceFormat::B8G8R8A8: + case gfx::SurfaceFormat::B8G8R8X8: + break; + default: { + MOZ_ASSERT_UNREACHABLE("DXGITextureHostD3D11: unsupported format!"); + return nullptr; + } + } + + AutoLockTextureHostWithoutCompositor autoLock(this); + if (autoLock.Failed()) { + NS_WARNING("Failed to lock the D3DTexture"); + return nullptr; + } + + RefPtr device; + mTexture->GetDevice(getter_AddRefs(device)); + + D3D11_TEXTURE2D_DESC textureDesc = {0}; + mTexture->GetDesc(&textureDesc); + + RefPtr context; + device->GetImmediateContext(getter_AddRefs(context)); + + textureDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + textureDesc.Usage = D3D11_USAGE_STAGING; + textureDesc.BindFlags = 0; + textureDesc.MiscFlags = 0; + textureDesc.MipLevels = 1; + RefPtr cpuTexture; + HRESULT hr = device->CreateTexture2D(&textureDesc, nullptr, + getter_AddRefs(cpuTexture)); + if (FAILED(hr)) { + return nullptr; + } + + context->CopyResource(cpuTexture, mTexture); + + D3D11_MAPPED_SUBRESOURCE mappedSubresource; + hr = context->Map(cpuTexture, 0, D3D11_MAP_READ, 0, &mappedSubresource); + if (FAILED(hr)) { + return nullptr; + } + + RefPtr surf = gfx::CreateDataSourceSurfaceFromData( + IntSize(textureDesc.Width, textureDesc.Height), GetFormat(), + (uint8_t*)mappedSubresource.pData, mappedSubresource.RowPitch); + context->Unmap(cpuTexture, 0); + return surf.forget(); +} + +bool DXGITextureHostD3D11::EnsureTextureSource() { + if (mTextureSource) { + return true; + } + + if (!EnsureTexture()) { + DeviceManagerDx::Get()->ForceDeviceReset( + ForcedDeviceResetReason::OPENSHAREDHANDLE); + return false; + } + + if (mProvider) { + if (!mProvider->IsValid()) { + return false; + } + mTextureSource = new DataTextureSourceD3D11(mFormat, mProvider, mTexture); + } else { + mTextureSource = new DataTextureSourceD3D11(mDevice, mFormat, mTexture); + } + return true; +} + +void DXGITextureHostD3D11::UnlockInternal() { + UnlockD3DTexture(mTextureSource->GetD3D11Texture()); +} + +bool DXGITextureHostD3D11::BindTextureSource( + CompositableTextureSourceRef& aTexture) { + MOZ_ASSERT(mIsLocked); + // If Lock was successful we must have a valid TextureSource. + MOZ_ASSERT(mTextureSource); + return AcquireTextureSource(aTexture); +} + +bool DXGITextureHostD3D11::AcquireTextureSource( + CompositableTextureSourceRef& aTexture) { + if (!EnsureTextureSource()) { + return false; + } + aTexture = mTextureSource; + return true; +} + +void DXGITextureHostD3D11::CreateRenderTexture( + const wr::ExternalImageId& aExternalImageId) { + RefPtr texture = new wr::RenderDXGITextureHost( + mHandle, mFormat, mYUVColorSpace, mColorRange, mSize); + + wr::RenderThread::Get()->RegisterExternalImage(wr::AsUint64(aExternalImageId), + texture.forget()); +} + +uint32_t DXGITextureHostD3D11::NumSubTextures() { + switch (GetFormat()) { + case gfx::SurfaceFormat::R8G8B8X8: + case gfx::SurfaceFormat::R8G8B8A8: + case gfx::SurfaceFormat::B8G8R8A8: + case gfx::SurfaceFormat::B8G8R8X8: { + return 1; + } + case gfx::SurfaceFormat::NV12: + case gfx::SurfaceFormat::P010: + case gfx::SurfaceFormat::P016: { + return 2; + } + default: { + MOZ_ASSERT_UNREACHABLE("unexpected format"); + return 1; + } + } +} + +void DXGITextureHostD3D11::PushResourceUpdates( + wr::TransactionBuilder& aResources, ResourceUpdateOp aOp, + const Range& aImageKeys, const wr::ExternalImageId& aExtID) { + if (!gfx::gfxVars::UseWebRenderANGLE()) { + MOZ_ASSERT_UNREACHABLE("unexpected to be called without ANGLE"); + return; + } + + MOZ_ASSERT(mHandle); + auto method = aOp == TextureHost::ADD_IMAGE + ? &wr::TransactionBuilder::AddExternalImage + : &wr::TransactionBuilder::UpdateExternalImage; + switch (mFormat) { + case gfx::SurfaceFormat::R8G8B8X8: + case gfx::SurfaceFormat::R8G8B8A8: + case gfx::SurfaceFormat::B8G8R8A8: + case gfx::SurfaceFormat::B8G8R8X8: { + MOZ_ASSERT(aImageKeys.length() == 1); + + wr::ImageDescriptor descriptor(mSize, GetFormat()); + auto imageType = gfx::gfxVars::UseSoftwareWebRender() + ? wr::ExternalImageType::TextureHandle( + wr::ImageBufferKind::TextureRect) + : wr::ExternalImageType::TextureHandle( + wr::ImageBufferKind::TextureExternal); + (aResources.*method)(aImageKeys[0], descriptor, aExtID, imageType, 0); + break; + } + case gfx::SurfaceFormat::P010: + case gfx::SurfaceFormat::P016: + case gfx::SurfaceFormat::NV12: { + MOZ_ASSERT(aImageKeys.length() == 2); + MOZ_ASSERT(mSize.width % 2 == 0); + MOZ_ASSERT(mSize.height % 2 == 0); + + wr::ImageDescriptor descriptor0(mSize, mFormat == gfx::SurfaceFormat::NV12 + ? gfx::SurfaceFormat::A8 + : gfx::SurfaceFormat::A16); + wr::ImageDescriptor descriptor1(mSize / 2, + mFormat == gfx::SurfaceFormat::NV12 + ? gfx::SurfaceFormat::R8G8 + : gfx::SurfaceFormat::R16G16); + auto imageType = gfx::gfxVars::UseSoftwareWebRender() + ? wr::ExternalImageType::TextureHandle( + wr::ImageBufferKind::TextureRect) + : wr::ExternalImageType::TextureHandle( + wr::ImageBufferKind::TextureExternal); + (aResources.*method)(aImageKeys[0], descriptor0, aExtID, imageType, 0); + (aResources.*method)(aImageKeys[1], descriptor1, aExtID, imageType, 1); + break; + } + default: { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + } + } +} + +void DXGITextureHostD3D11::PushDisplayItems( + wr::DisplayListBuilder& aBuilder, const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, wr::ImageRendering aFilter, + const Range& aImageKeys, PushDisplayItemFlagSet aFlags) { + bool preferCompositorSurface = + aFlags.contains(PushDisplayItemFlag::PREFER_COMPOSITOR_SURFACE); + if (!gfx::gfxVars::UseWebRenderANGLE()) { + MOZ_ASSERT_UNREACHABLE("unexpected to be called without ANGLE"); + return; + } + + switch (GetFormat()) { + case gfx::SurfaceFormat::R8G8B8X8: + case gfx::SurfaceFormat::R8G8B8A8: + case gfx::SurfaceFormat::B8G8R8A8: + case gfx::SurfaceFormat::B8G8R8X8: { + MOZ_ASSERT(aImageKeys.length() == 1); + aBuilder.PushImage(aBounds, aClip, true, aFilter, aImageKeys[0], + !(mFlags & TextureFlags::NON_PREMULTIPLIED), + wr::ColorF{1.0f, 1.0f, 1.0f, 1.0f}, + preferCompositorSurface, + SupportsExternalCompositing()); + break; + } + case gfx::SurfaceFormat::P010: + case gfx::SurfaceFormat::P016: + case gfx::SurfaceFormat::NV12: { + MOZ_ASSERT(aImageKeys.length() == 2); + aBuilder.PushNV12Image( + aBounds, aClip, true, aImageKeys[0], aImageKeys[1], + GetFormat() == gfx::SurfaceFormat::NV12 ? wr::ColorDepth::Color8 + : wr::ColorDepth::Color16, + wr::ToWrYuvColorSpace(mYUVColorSpace), + wr::ToWrColorRange(mColorRange), aFilter, preferCompositorSurface, + SupportsExternalCompositing()); + break; + } + default: { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + } + } +} + +bool DXGITextureHostD3D11::SupportsExternalCompositing() { + if (gfx::gfxVars::UseSoftwareWebRender()) { + return true; + } + // XXX Add P010 and P016 support. + if (GetFormat() == gfx::SurfaceFormat::NV12 && + gfx::gfxVars::UseWebRenderDCompVideoOverlayWin()) { + return true; + } + return false; +} + +DXGIYCbCrTextureHostD3D11::DXGIYCbCrTextureHostD3D11( + TextureFlags aFlags, const SurfaceDescriptorDXGIYCbCr& aDescriptor) + : TextureHost(aFlags), + mSize(aDescriptor.size()), + mSizeY(aDescriptor.sizeY()), + mSizeCbCr(aDescriptor.sizeCbCr()), + mIsLocked(false), + mColorDepth(aDescriptor.colorDepth()), + mYUVColorSpace(aDescriptor.yUVColorSpace()), + mColorRange(aDescriptor.colorRange()) { + mHandles[0] = aDescriptor.handleY(); + mHandles[1] = aDescriptor.handleCb(); + mHandles[2] = aDescriptor.handleCr(); +} + +bool DXGIYCbCrTextureHostD3D11::EnsureTexture() { + RefPtr device; + if (mTextures[0]) { + mTextures[0]->GetDevice(getter_AddRefs(device)); + if (device == DeviceManagerDx::Get()->GetCompositorDevice()) { + NS_WARNING("Incompatible texture."); + return true; + } + mTextures[0] = nullptr; + mTextures[1] = nullptr; + mTextures[2] = nullptr; + } + + if (!GetDevice() || + GetDevice() != DeviceManagerDx::Get()->GetCompositorDevice()) { + NS_WARNING("No device or incompatible device."); + return false; + } + + device = GetDevice(); + RefPtr textures[3]; + + HRESULT hr = device->OpenSharedResource( + (HANDLE)mHandles[0], __uuidof(ID3D11Texture2D), + (void**)(ID3D11Texture2D**)getter_AddRefs(textures[0])); + if (FAILED(hr)) { + NS_WARNING("Failed to open shared texture for Y Plane"); + return false; + } + + hr = device->OpenSharedResource( + (HANDLE)mHandles[1], __uuidof(ID3D11Texture2D), + (void**)(ID3D11Texture2D**)getter_AddRefs(textures[1])); + if (FAILED(hr)) { + NS_WARNING("Failed to open shared texture for Cb Plane"); + return false; + } + + hr = device->OpenSharedResource( + (HANDLE)mHandles[2], __uuidof(ID3D11Texture2D), + (void**)(ID3D11Texture2D**)getter_AddRefs(textures[2])); + if (FAILED(hr)) { + NS_WARNING("Failed to open shared texture for Cr Plane"); + return false; + } + + mTextures[0] = textures[0].forget(); + mTextures[1] = textures[1].forget(); + mTextures[2] = textures[2].forget(); + + return true; +} + +RefPtr DXGIYCbCrTextureHostD3D11::GetDevice() { + if (mFlags & TextureFlags::INVALID_COMPOSITOR) { + return nullptr; + } + + return mProvider->GetD3D11Device(); +} + +void DXGIYCbCrTextureHostD3D11::SetTextureSourceProvider( + TextureSourceProvider* aProvider) { + if (!aProvider || !aProvider->GetD3D11Device()) { + mProvider = nullptr; + mTextureSources[0] = nullptr; + mTextureSources[1] = nullptr; + mTextureSources[2] = nullptr; + return; + } + + mProvider = aProvider; + + if (mTextureSources[0]) { + mTextureSources[0]->SetTextureSourceProvider(aProvider); + } +} + +bool DXGIYCbCrTextureHostD3D11::Lock() { + if (!EnsureTextureSource()) { + return false; + } + + mIsLocked = LockD3DTexture(mTextureSources[0]->GetD3D11Texture()) && + LockD3DTexture(mTextureSources[1]->GetD3D11Texture()) && + LockD3DTexture(mTextureSources[2]->GetD3D11Texture()); + + return mIsLocked; +} + +bool DXGIYCbCrTextureHostD3D11::EnsureTextureSource() { + if (!mProvider) { + NS_WARNING("no suitable compositor"); + return false; + } + + if (!GetDevice()) { + NS_WARNING("trying to lock a TextureHost without a D3D device"); + return false; + } + if (!mTextureSources[0]) { + if (!EnsureTexture()) { + return false; + } + + MOZ_ASSERT(mTextures[1] && mTextures[2]); + + mTextureSources[0] = + new DataTextureSourceD3D11(SurfaceFormat::A8, mProvider, mTextures[0]); + mTextureSources[1] = + new DataTextureSourceD3D11(SurfaceFormat::A8, mProvider, mTextures[1]); + mTextureSources[2] = + new DataTextureSourceD3D11(SurfaceFormat::A8, mProvider, mTextures[2]); + mTextureSources[0]->SetNextSibling(mTextureSources[1]); + mTextureSources[1]->SetNextSibling(mTextureSources[2]); + } + return true; +} + +void DXGIYCbCrTextureHostD3D11::Unlock() { + MOZ_ASSERT(mIsLocked); + UnlockD3DTexture(mTextureSources[0]->GetD3D11Texture()); + UnlockD3DTexture(mTextureSources[1]->GetD3D11Texture()); + UnlockD3DTexture(mTextureSources[2]->GetD3D11Texture()); + mIsLocked = false; +} + +bool DXGIYCbCrTextureHostD3D11::BindTextureSource( + CompositableTextureSourceRef& aTexture) { + MOZ_ASSERT(mIsLocked); + // If Lock was successful we must have a valid TextureSource. + MOZ_ASSERT(mTextureSources[0] && mTextureSources[1] && mTextureSources[2]); + aTexture = mTextureSources[0].get(); + return !!aTexture; +} + +void DXGIYCbCrTextureHostD3D11::CreateRenderTexture( + const wr::ExternalImageId& aExternalImageId) { + RefPtr texture = new wr::RenderDXGIYCbCrTextureHost( + mHandles, mYUVColorSpace, mColorDepth, mColorRange, mSizeY, mSizeCbCr); + + wr::RenderThread::Get()->RegisterExternalImage(wr::AsUint64(aExternalImageId), + texture.forget()); +} + +uint32_t DXGIYCbCrTextureHostD3D11::NumSubTextures() { + // ycbcr use 3 sub textures. + return 3; +} + +void DXGIYCbCrTextureHostD3D11::PushResourceUpdates( + wr::TransactionBuilder& aResources, ResourceUpdateOp aOp, + const Range& aImageKeys, const wr::ExternalImageId& aExtID) { + if (!gfx::gfxVars::UseWebRenderANGLE()) { + MOZ_ASSERT_UNREACHABLE("unexpected to be called without ANGLE"); + return; + } + + MOZ_ASSERT(mHandles[0] && mHandles[1] && mHandles[2]); + MOZ_ASSERT(aImageKeys.length() == 3); + // Assume the chroma planes are rounded up if the luma plane is odd sized. + MOZ_ASSERT((mSizeCbCr.width == mSizeY.width || + mSizeCbCr.width == (mSizeY.width + 1) >> 1) && + (mSizeCbCr.height == mSizeY.height || + mSizeCbCr.height == (mSizeY.height + 1) >> 1)); + + auto method = aOp == TextureHost::ADD_IMAGE + ? &wr::TransactionBuilder::AddExternalImage + : &wr::TransactionBuilder::UpdateExternalImage; + auto imageType = gfx::gfxVars::UseSoftwareWebRender() + ? wr::ExternalImageType::TextureHandle( + wr::ImageBufferKind::TextureRect) + : wr::ExternalImageType::TextureHandle( + wr::ImageBufferKind::TextureExternal); + + // y + wr::ImageDescriptor descriptor0(mSizeY, gfx::SurfaceFormat::A8); + // cb and cr + wr::ImageDescriptor descriptor1(mSizeCbCr, gfx::SurfaceFormat::A8); + (aResources.*method)(aImageKeys[0], descriptor0, aExtID, imageType, 0); + (aResources.*method)(aImageKeys[1], descriptor1, aExtID, imageType, 1); + (aResources.*method)(aImageKeys[2], descriptor1, aExtID, imageType, 2); +} + +void DXGIYCbCrTextureHostD3D11::PushDisplayItems( + wr::DisplayListBuilder& aBuilder, const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, wr::ImageRendering aFilter, + const Range& aImageKeys, PushDisplayItemFlagSet aFlags) { + if (!gfx::gfxVars::UseWebRenderANGLE()) { + MOZ_ASSERT_UNREACHABLE("unexpected to be called without ANGLE"); + return; + } + + MOZ_ASSERT(aImageKeys.length() == 3); + + aBuilder.PushYCbCrPlanarImage( + aBounds, aClip, true, aImageKeys[0], aImageKeys[1], aImageKeys[2], + wr::ToWrColorDepth(mColorDepth), wr::ToWrYuvColorSpace(mYUVColorSpace), + wr::ToWrColorRange(mColorRange), aFilter, + aFlags.contains(PushDisplayItemFlag::PREFER_COMPOSITOR_SURFACE), + SupportsExternalCompositing()); +} + +bool DXGIYCbCrTextureHostD3D11::SupportsExternalCompositing() { + if (gfx::gfxVars::UseSoftwareWebRender()) { + return true; + } + return false; +} + +bool DXGIYCbCrTextureHostD3D11::AcquireTextureSource( + CompositableTextureSourceRef& aTexture) { + if (!EnsureTextureSource()) { + return false; + } + aTexture = mTextureSources[0].get(); + return !!aTexture; +} + +bool DataTextureSourceD3D11::Update(DataSourceSurface* aSurface, + nsIntRegion* aDestRegion, + IntPoint* aSrcOffset) { + // Incremental update with a source offset is only used on Mac so it is not + // clear that we ever will need to support it for D3D. + MOZ_ASSERT(!aSrcOffset); + MOZ_ASSERT(aSurface); + + MOZ_ASSERT(mAllowTextureUploads); + if (!mAllowTextureUploads) { + return false; + } + + HRESULT hr; + + if (!mDevice) { + return false; + } + + uint32_t bpp = BytesPerPixel(aSurface->GetFormat()); + DXGI_FORMAT dxgiFormat = SurfaceFormatToDXGIFormat(aSurface->GetFormat()); + + mSize = aSurface->GetSize(); + mFormat = aSurface->GetFormat(); + + CD3D11_TEXTURE2D_DESC desc(dxgiFormat, mSize.width, mSize.height, 1, 1); + + int32_t maxSize = GetMaxTextureSizeFromDevice(mDevice); + if ((mSize.width <= maxSize && mSize.height <= maxSize) || + (mFlags & TextureFlags::DISALLOW_BIGIMAGE)) { + if (mTexture) { + D3D11_TEXTURE2D_DESC currentDesc; + mTexture->GetDesc(¤tDesc); + + // Make sure there's no size mismatch, if there is, recreate. + if (currentDesc.Width != mSize.width || + currentDesc.Height != mSize.height || + currentDesc.Format != dxgiFormat) { + mTexture = nullptr; + // Make sure we upload the whole surface. + aDestRegion = nullptr; + } + } + + nsIntRegion* regionToUpdate = aDestRegion; + if (!mTexture) { + hr = mDevice->CreateTexture2D(&desc, nullptr, getter_AddRefs(mTexture)); + mIsTiled = false; + if (FAILED(hr) || !mTexture) { + Reset(); + return false; + } + + if (mFlags & TextureFlags::COMPONENT_ALPHA) { + regionToUpdate = nullptr; + } + } + + DataSourceSurface::MappedSurface map; + if (!aSurface->Map(DataSourceSurface::MapType::READ, &map)) { + gfxCriticalError() << "Failed to map surface."; + Reset(); + return false; + } + + RefPtr context; + mDevice->GetImmediateContext(getter_AddRefs(context)); + + if (regionToUpdate) { + for (auto iter = regionToUpdate->RectIter(); !iter.Done(); iter.Next()) { + const IntRect& rect = iter.Get(); + D3D11_BOX box; + box.front = 0; + box.back = 1; + box.left = rect.X(); + box.top = rect.Y(); + box.right = rect.XMost(); + box.bottom = rect.YMost(); + + void* data = map.mData + map.mStride * rect.Y() + + BytesPerPixel(aSurface->GetFormat()) * rect.X(); + + context->UpdateSubresource(mTexture, 0, &box, data, map.mStride, + map.mStride * rect.Height()); + } + } else { + context->UpdateSubresource(mTexture, 0, nullptr, map.mData, map.mStride, + map.mStride * mSize.height); + } + + aSurface->Unmap(); + } else { + mIsTiled = true; + uint32_t tileCount = GetRequiredTilesD3D11(mSize.width, maxSize) * + GetRequiredTilesD3D11(mSize.height, maxSize); + + mTileTextures.resize(tileCount); + mTileSRVs.resize(tileCount); + mTexture = nullptr; + + DataSourceSurface::ScopedMap map(aSurface, DataSourceSurface::READ); + if (!map.IsMapped()) { + gfxCriticalError() << "Failed to map surface."; + Reset(); + return false; + } + + for (uint32_t i = 0; i < tileCount; i++) { + IntRect tileRect = GetTileRect(i); + + desc.Width = tileRect.Width(); + desc.Height = tileRect.Height(); + desc.Usage = D3D11_USAGE_IMMUTABLE; + + D3D11_SUBRESOURCE_DATA initData; + initData.pSysMem = + map.GetData() + tileRect.Y() * map.GetStride() + tileRect.X() * bpp; + initData.SysMemPitch = map.GetStride(); + + hr = mDevice->CreateTexture2D(&desc, &initData, + getter_AddRefs(mTileTextures[i])); + if (FAILED(hr) || !mTileTextures[i]) { + Reset(); + return false; + } + } + } + return true; +} + +ID3D11Texture2D* DataTextureSourceD3D11::GetD3D11Texture() const { + return mIterating ? mTileTextures[mCurrentTile] : mTexture; +} + +RefPtr DataTextureSourceD3D11::ExtractCurrentTile() { + MOZ_ASSERT(mIterating); + return new DataTextureSourceD3D11(mDevice, mFormat, + mTileTextures[mCurrentTile]); +} + +ID3D11ShaderResourceView* DataTextureSourceD3D11::GetShaderResourceView() { + if (mIterating) { + if (!mTileSRVs[mCurrentTile]) { + if (!mTileTextures[mCurrentTile]) { + return nullptr; + } + + RefPtr device; + mTileTextures[mCurrentTile]->GetDevice(getter_AddRefs(device)); + HRESULT hr = device->CreateShaderResourceView( + mTileTextures[mCurrentTile], nullptr, + getter_AddRefs(mTileSRVs[mCurrentTile])); + if (FAILED(hr)) { + gfxCriticalNote + << "[D3D11] DataTextureSourceD3D11:GetShaderResourceView CreateSRV " + "failure " + << gfx::hexa(hr); + return nullptr; + } + } + return mTileSRVs[mCurrentTile]; + } + + return TextureSourceD3D11::GetShaderResourceView(); +} + +void DataTextureSourceD3D11::Reset() { + mTexture = nullptr; + mTileSRVs.resize(0); + mTileTextures.resize(0); + mIsTiled = false; + mSize.width = 0; + mSize.height = 0; +} + +IntRect DataTextureSourceD3D11::GetTileRect(uint32_t aIndex) const { + return GetTileRectD3D11(aIndex, mSize, GetMaxTextureSizeFromDevice(mDevice)); +} + +IntRect DataTextureSourceD3D11::GetTileRect() { + IntRect rect = GetTileRect(mCurrentTile); + return IntRect(rect.X(), rect.Y(), rect.Width(), rect.Height()); +} + +CompositingRenderTargetD3D11::CompositingRenderTargetD3D11( + ID3D11Texture2D* aTexture, const gfx::IntPoint& aOrigin, + DXGI_FORMAT aFormatOverride) + : CompositingRenderTarget(aOrigin) { + MOZ_ASSERT(aTexture); + + mTexture = aTexture; + + RefPtr device; + mTexture->GetDevice(getter_AddRefs(device)); + + mFormatOverride = aFormatOverride; + + // If we happen to have a typeless underlying DXGI surface, we need to be + // explicit about the format here. (Such a surface could come from an external + // source, such as the Oculus compositor) + CD3D11_RENDER_TARGET_VIEW_DESC rtvDesc(D3D11_RTV_DIMENSION_TEXTURE2D, + mFormatOverride); + D3D11_RENDER_TARGET_VIEW_DESC* desc = + aFormatOverride == DXGI_FORMAT_UNKNOWN ? nullptr : &rtvDesc; + + HRESULT hr = + device->CreateRenderTargetView(mTexture, desc, getter_AddRefs(mRTView)); + + if (FAILED(hr)) { + LOGD3D11("Failed to create RenderTargetView."); + } +} + +void CompositingRenderTargetD3D11::BindRenderTarget( + ID3D11DeviceContext* aContext) { + if (mClearOnBind) { + FLOAT clear[] = {0, 0, 0, 0}; + aContext->ClearRenderTargetView(mRTView, clear); + mClearOnBind = false; + } + ID3D11RenderTargetView* view = mRTView; + aContext->OMSetRenderTargets(1, &view, nullptr); +} + +IntSize CompositingRenderTargetD3D11::GetSize() const { + return TextureSourceD3D11::GetSize(); +} + +static inline bool ShouldDevCrashOnSyncInitFailure() { + // Compositor shutdown does not wait for video decoding to finish, so it is + // possible for the compositor to destroy the SyncObject before video has a + // chance to initialize it. + if (!NS_IsMainThread()) { + return false; + } + + // Note: CompositorIsInGPUProcess is a main-thread-only function. + return !CompositorBridgeChild::CompositorIsInGPUProcess() && + !DeviceManagerDx::Get()->HasDeviceReset(); +} + +SyncObjectD3D11Host::SyncObjectD3D11Host(ID3D11Device* aDevice) + : mSyncHandle(0), mDevice(aDevice) { + MOZ_ASSERT(aDevice); +} + +bool SyncObjectD3D11Host::Init() { + CD3D11_TEXTURE2D_DESC desc( + DXGI_FORMAT_B8G8R8A8_UNORM, 1, 1, 1, 1, + D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET); + desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX; + + RefPtr texture; + HRESULT hr = + mDevice->CreateTexture2D(&desc, nullptr, getter_AddRefs(texture)); + if (FAILED(hr) || !texture) { + gfxWarning() << "Could not create a sync texture: " << gfx::hexa(hr); + return false; + } + + hr = texture->QueryInterface((IDXGIResource**)getter_AddRefs(mSyncTexture)); + if (FAILED(hr) || !mSyncTexture) { + gfxWarning() << "Could not QI sync texture: " << gfx::hexa(hr); + return false; + } + + hr = mSyncTexture->QueryInterface( + (IDXGIKeyedMutex**)getter_AddRefs(mKeyedMutex)); + if (FAILED(hr) || !mKeyedMutex) { + gfxWarning() << "Could not QI keyed-mutex: " << gfx::hexa(hr); + return false; + } + + hr = mSyncTexture->GetSharedHandle(&mSyncHandle); + if (FAILED(hr) || !mSyncHandle) { + NS_DispatchToMainThread(NS_NewRunnableFunction( + "layers::SyncObjectD3D11Renderer::Init", + []() -> void { Accumulate(Telemetry::D3D11_SYNC_HANDLE_FAILURE, 1); })); + gfxWarning() << "Could not get sync texture shared handle: " + << gfx::hexa(hr); + return false; + } + + return true; +} + +SyncHandle SyncObjectD3D11Host::GetSyncHandle() { return mSyncHandle; } + +bool SyncObjectD3D11Host::Synchronize(bool aFallible) { + HRESULT hr; + AutoTextureLock lock(mKeyedMutex, hr, 10000); + + if (hr == WAIT_TIMEOUT) { + hr = mDevice->GetDeviceRemovedReason(); + if (hr != S_OK) { + // Since the timeout is related to the driver-removed. Return false for + // error handling. + gfxCriticalNote << "GFX: D3D11 timeout with device-removed:" + << gfx::hexa(hr); + } else if (aFallible) { + gfxCriticalNote << "GFX: D3D11 timeout on the D3D11 sync lock."; + } else { + // There is no driver-removed event. Crash with this timeout. + MOZ_CRASH("GFX: D3D11 normal status timeout"); + } + + return false; + } + if (hr == WAIT_ABANDONED) { + gfxCriticalNote << "GFX: AL_D3D11 abandoned sync"; + } + + return true; +} + +SyncObjectD3D11Client::SyncObjectD3D11Client(SyncHandle aSyncHandle, + ID3D11Device* aDevice) + : mSyncLock("SyncObjectD3D11"), mSyncHandle(aSyncHandle), mDevice(aDevice) { + MOZ_ASSERT(aDevice); +} + +SyncObjectD3D11Client::SyncObjectD3D11Client(SyncHandle aSyncHandle) + : mSyncLock("SyncObjectD3D11"), mSyncHandle(aSyncHandle) {} + +bool SyncObjectD3D11Client::Init(ID3D11Device* aDevice, bool aFallible) { + if (mKeyedMutex) { + return true; + } + + HRESULT hr = aDevice->OpenSharedResource( + mSyncHandle, __uuidof(ID3D11Texture2D), + (void**)(ID3D11Texture2D**)getter_AddRefs(mSyncTexture)); + if (FAILED(hr) || !mSyncTexture) { + gfxCriticalNote << "Failed to OpenSharedResource for SyncObjectD3D11: " + << hexa(hr); + if (!aFallible && ShouldDevCrashOnSyncInitFailure()) { + gfxDevCrash(LogReason::D3D11FinalizeFrame) + << "Without device reset: " << hexa(hr); + } + return false; + } + + hr = mSyncTexture->QueryInterface(__uuidof(IDXGIKeyedMutex), + getter_AddRefs(mKeyedMutex)); + if (FAILED(hr) || !mKeyedMutex) { + // Leave both the critical error and MOZ_CRASH for now; the critical error + // lets us "save" the hr value. We will probably eventually replace this + // with gfxDevCrash. + if (!aFallible) { + gfxCriticalError() << "Failed to get KeyedMutex (2): " << hexa(hr); + MOZ_CRASH("GFX: Cannot get D3D11 KeyedMutex"); + } else { + gfxCriticalNote << "Failed to get KeyedMutex (3): " << hexa(hr); + } + return false; + } + + return true; +} + +void SyncObjectD3D11Client::RegisterTexture(ID3D11Texture2D* aTexture) { + mSyncedTextures.push_back(aTexture); +} + +bool SyncObjectD3D11Client::IsSyncObjectValid() { + MOZ_ASSERT(mDevice); + return true; +} + +// We have only 1 sync object. As a thing that somehow works, +// we copy each of the textures that need to be synced with the compositor +// into our sync object and only use a lock for this sync object. +// This way, we don't have to sync every texture we send to the compositor. +// We only have to do this once per transaction. +bool SyncObjectD3D11Client::Synchronize(bool aFallible) { + MOZ_ASSERT(mDevice); + // Since this can be called from either the Paint or Main thread. + // We don't want this to race since we initialize the sync texture here + // too. + MutexAutoLock syncLock(mSyncLock); + + if (!mSyncedTextures.size()) { + return true; + } + if (!Init(mDevice, aFallible)) { + return false; + } + + return SynchronizeInternal(mDevice, aFallible); +} + +bool SyncObjectD3D11Client::SynchronizeInternal(ID3D11Device* aDevice, + bool aFallible) { + mSyncLock.AssertCurrentThreadOwns(); + + HRESULT hr; + AutoTextureLock lock(mKeyedMutex, hr, 20000); + + if (hr == WAIT_TIMEOUT) { + if (DeviceManagerDx::Get()->HasDeviceReset()) { + gfxWarning() << "AcquireSync timed out because of device reset."; + return false; + } + if (aFallible) { + gfxWarning() << "Timeout on the D3D11 sync lock."; + } else { + gfxDevCrash(LogReason::D3D11SyncLock) + << "Timeout on the D3D11 sync lock."; + } + return false; + } + + D3D11_BOX box; + box.front = box.top = box.left = 0; + box.back = box.bottom = box.right = 1; + + RefPtr ctx; + aDevice->GetImmediateContext(getter_AddRefs(ctx)); + + for (auto iter = mSyncedTextures.begin(); iter != mSyncedTextures.end(); + iter++) { + ctx->CopySubresourceRegion(mSyncTexture, 0, 0, 0, 0, *iter, 0, &box); + } + + mSyncedTextures.clear(); + + return true; +} + +uint32_t GetMaxTextureSizeFromDevice(ID3D11Device* aDevice) { + return GetMaxTextureSizeForFeatureLevel(aDevice->GetFeatureLevel()); +} + +AutoLockD3D11Texture::AutoLockD3D11Texture(ID3D11Texture2D* aTexture) { + aTexture->QueryInterface((IDXGIKeyedMutex**)getter_AddRefs(mMutex)); + if (!mMutex) { + return; + } + HRESULT hr = mMutex->AcquireSync(0, 10000); + if (hr == WAIT_TIMEOUT) { + MOZ_CRASH("GFX: IMFYCbCrImage timeout"); + } + + if (FAILED(hr)) { + NS_WARNING("Failed to lock the texture"); + } +} + +AutoLockD3D11Texture::~AutoLockD3D11Texture() { + if (!mMutex) { + return; + } + HRESULT hr = mMutex->ReleaseSync(0); + if (FAILED(hr)) { + NS_WARNING("Failed to unlock the texture"); + } +} + +SyncObjectD3D11ClientContentDevice::SyncObjectD3D11ClientContentDevice( + SyncHandle aSyncHandle) + : SyncObjectD3D11Client(aSyncHandle) {} + +bool SyncObjectD3D11ClientContentDevice::Synchronize(bool aFallible) { + // Since this can be called from either the Paint or Main thread. + // We don't want this to race since we initialize the sync texture here + // too. + MutexAutoLock syncLock(mSyncLock); + + MOZ_ASSERT(mContentDevice); + + if (!mSyncedTextures.size()) { + return true; + } + + if (!Init(mContentDevice, aFallible)) { + return false; + } + + RefPtr dev; + mSyncTexture->GetDevice(getter_AddRefs(dev)); + + if (dev == DeviceManagerDx::Get()->GetContentDevice()) { + if (DeviceManagerDx::Get()->HasDeviceReset()) { + return false; + } + } + + if (dev != mContentDevice) { + gfxWarning() << "Attempt to sync texture from invalid device."; + return false; + } + + return SyncObjectD3D11Client::SynchronizeInternal(dev, aFallible); +} + +bool SyncObjectD3D11ClientContentDevice::IsSyncObjectValid() { + RefPtr dev; + // There is a case that devices are not initialized yet with WebRender. + if (gfxPlatform::GetPlatform()->DevicesInitialized()) { + dev = DeviceManagerDx::Get()->GetContentDevice(); + } + + // Update mDevice if the ContentDevice initialization is detected. + if (!mContentDevice && dev && NS_IsMainThread() && gfxVars::UseWebRender()) { + mContentDevice = dev; + } + + if (!dev || (NS_IsMainThread() && dev != mContentDevice)) { + return false; + } + return true; +} + +void SyncObjectD3D11ClientContentDevice::EnsureInitialized() { + if (mContentDevice) { + return; + } + + if (XRE_IsGPUProcess() || !gfxPlatform::GetPlatform()->DevicesInitialized()) { + return; + } + + mContentDevice = DeviceManagerDx::Get()->GetContentDevice(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/d3d11/TextureD3D11.h b/gfx/layers/d3d11/TextureD3D11.h new file mode 100644 index 0000000000..2a69015719 --- /dev/null +++ b/gfx/layers/d3d11/TextureD3D11.h @@ -0,0 +1,600 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_TEXTURED3D11_H +#define MOZILLA_GFX_TEXTURED3D11_H + +#include + +#include + +#include "d3d9.h" +#include "gfxWindowsPlatform.h" +#include "mozilla/GfxMessageUtils.h" +#include "mozilla/Maybe.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/layers/Compositor.h" +#include "mozilla/layers/SyncObject.h" +#include "mozilla/layers/TextureClient.h" +#include "mozilla/layers/TextureHost.h" + +namespace mozilla { +namespace gl { +class GLBlitHelper; +} + +namespace layers { + +already_AddRefed CreateTextureHostD3D11( + const SurfaceDescriptor& aDesc, ISurfaceAllocator* aDeallocator, + LayersBackend aBackend, TextureFlags aFlags); + +class MOZ_RAII AutoTextureLock final { + public: + AutoTextureLock(IDXGIKeyedMutex* aMutex, HRESULT& aResult, + uint32_t aTimeout = 0); + ~AutoTextureLock(); + + private: + RefPtr mMutex; + HRESULT mResult; +}; + +class CompositorD3D11; + +class D3D11TextureData final : public TextureData { + public: + // If aDevice is null, use one provided by gfxWindowsPlatform. + static D3D11TextureData* Create(gfx::IntSize aSize, + gfx::SurfaceFormat aFormat, + TextureAllocationFlags aAllocFlags, + ID3D11Device* aDevice = nullptr); + static D3D11TextureData* Create(gfx::SourceSurface* aSurface, + TextureAllocationFlags aAllocFlags, + ID3D11Device* aDevice = nullptr); + + virtual ~D3D11TextureData(); + + bool UpdateFromSurface(gfx::SourceSurface* aSurface) override; + + bool Lock(OpenMode aMode) override; + + void Unlock() override; + + already_AddRefed BorrowDrawTarget() override; + + TextureData* CreateSimilar(LayersIPCChannel* aAllocator, + LayersBackend aLayersBackend, TextureFlags aFlags, + TextureAllocationFlags aAllocFlags) const override; + + void SyncWithObject(RefPtr aSyncObject) override; + + ID3D11Texture2D* GetD3D11Texture() const { return mTexture; } + + void Deallocate(LayersIPCChannel* aAllocator) override; + + D3D11TextureData* AsD3D11TextureData() override { return this; } + + TextureAllocationFlags GetTextureAllocationFlags() const { + return mAllocationFlags; + } + + void FillInfo(TextureData::Info& aInfo) const override; + + bool Serialize(SurfaceDescriptor& aOutDescrptor) override; + void GetSubDescriptor(RemoteDecoderVideoSubDescriptor* aOutDesc) override; + + gfx::YUVColorSpace GetYUVColorSpace() const { return mYUVColorSpace; } + void SetYUVColorSpace(gfx::YUVColorSpace aColorSpace) { + mYUVColorSpace = aColorSpace; + } + gfx::ColorRange GetColorRange() const { return mColorRange; } + void SetColorRange(gfx::ColorRange aColorRange) { mColorRange = aColorRange; } + + gfx::IntSize GetSize() const { return mSize; } + gfx::SurfaceFormat GetSurfaceFormat() const { return mFormat; } + + TextureFlags GetTextureFlags() const override; + + private: + D3D11TextureData(ID3D11Texture2D* aTexture, gfx::IntSize aSize, + gfx::SurfaceFormat aFormat, TextureAllocationFlags aFlags); + + void GetDXGIResource(IDXGIResource** aOutResource); + + bool PrepareDrawTargetInLock(OpenMode aMode); + + friend class gl::GLBlitHelper; + bool SerializeSpecific(SurfaceDescriptorD3D10* aOutDesc); + + static D3D11TextureData* Create(gfx::IntSize aSize, + gfx::SurfaceFormat aFormat, + gfx::SourceSurface* aSurface, + TextureAllocationFlags aAllocFlags, + ID3D11Device* aDevice = nullptr); + + // Hold on to the DrawTarget because it is expensive to create one each + // ::Lock. + RefPtr mDrawTarget; + gfx::IntSize mSize; + gfx::SurfaceFormat mFormat; + gfx::YUVColorSpace mYUVColorSpace = gfx::YUVColorSpace::UNKNOWN; + gfx::ColorRange mColorRange = gfx::ColorRange::LIMITED; + bool mNeedsClear; + bool mNeedsClearWhite; + bool mHasSynchronization; + bool mIsForOutOfBandContent; + + RefPtr mTexture; + const TextureAllocationFlags mAllocationFlags; +}; + +class DXGIYCbCrTextureData : public TextureData { + friend class gl::GLBlitHelper; + + public: + static DXGIYCbCrTextureData* Create( + IDirect3DTexture9* aTextureY, IDirect3DTexture9* aTextureCb, + IDirect3DTexture9* aTextureCr, HANDLE aHandleY, HANDLE aHandleCb, + HANDLE aHandleCr, const gfx::IntSize& aSize, const gfx::IntSize& aSizeY, + const gfx::IntSize& aSizeCbCr, gfx::ColorDepth aColorDepth, + gfx::YUVColorSpace aYUVColorSpace, gfx::ColorRange aColorRange); + + static DXGIYCbCrTextureData* Create( + ID3D11Texture2D* aTextureCb, ID3D11Texture2D* aTextureY, + ID3D11Texture2D* aTextureCr, const gfx::IntSize& aSize, + const gfx::IntSize& aSizeY, const gfx::IntSize& aSizeCbCr, + gfx::ColorDepth aColorDepth, gfx::YUVColorSpace aYUVColorSpace, + gfx::ColorRange aColorRange); + + bool Lock(OpenMode) override { return true; } + + void Unlock() override {} + + void FillInfo(TextureData::Info& aInfo) const override; + + void SerializeSpecific(SurfaceDescriptorDXGIYCbCr* aOutDesc); + bool Serialize(SurfaceDescriptor& aOutDescriptor) override; + void GetSubDescriptor(RemoteDecoderVideoSubDescriptor* aOutDesc) override; + + already_AddRefed BorrowDrawTarget() override { + return nullptr; + } + + void Deallocate(LayersIPCChannel* aAllocator) override; + + bool UpdateFromSurface(gfx::SourceSurface*) override { return false; } + + TextureFlags GetTextureFlags() const override; + + DXGIYCbCrTextureData* AsDXGIYCbCrTextureData() override { return this; } + + gfx::IntSize GetYSize() const { return mSizeY; } + + gfx::IntSize GetCbCrSize() const { return mSizeCbCr; } + + gfx::ColorDepth GetColorDepth() const { return mColorDepth; } + gfx::YUVColorSpace GetYUVColorSpace() const { return mYUVColorSpace; } + gfx::ColorRange GetColorRange() const { return mColorRange; } + + ID3D11Texture2D* GetD3D11Texture(size_t index) { + return mD3D11Textures[index]; + } + + protected: + RefPtr mD3D11Textures[3]; + RefPtr mD3D9Textures[3]; + HANDLE mHandles[3]; + gfx::IntSize mSize; + gfx::IntSize mSizeY; + gfx::IntSize mSizeCbCr; + gfx::ColorDepth mColorDepth; + gfx::YUVColorSpace mYUVColorSpace; + gfx::ColorRange mColorRange; +}; + +/** + * TextureSource that provides with the necessary APIs to be composited by a + * CompositorD3D11. + */ +class TextureSourceD3D11 { + public: + TextureSourceD3D11() : mFormatOverride(DXGI_FORMAT_UNKNOWN) {} + virtual ~TextureSourceD3D11() = default; + + virtual ID3D11Texture2D* GetD3D11Texture() const { return mTexture; } + virtual ID3D11ShaderResourceView* GetShaderResourceView(); + + protected: + virtual gfx::IntSize GetSize() const { return mSize; } + + gfx::IntSize mSize; + RefPtr mTexture; + RefPtr mSRV; + DXGI_FORMAT mFormatOverride; +}; + +/** + * A TextureSource that implements the DataTextureSource interface. + * it can be used without a TextureHost and is able to upload texture data + * from a gfx::DataSourceSurface. + */ +class DataTextureSourceD3D11 : public DataTextureSource, + public TextureSourceD3D11, + public BigImageIterator { + public: + /// Constructor allowing the texture to perform texture uploads. + /// + /// The texture can be used as an actual DataTextureSource. + DataTextureSourceD3D11(ID3D11Device* aDevice, gfx::SurfaceFormat aFormat, + TextureFlags aFlags); + + /// Constructor for textures created around DXGI shared handles, disallowing + /// texture uploads. + /// + /// The texture CANNOT be used as a DataTextureSource. + DataTextureSourceD3D11(ID3D11Device* aDevice, gfx::SurfaceFormat aFormat, + ID3D11Texture2D* aTexture); + + DataTextureSourceD3D11(gfx::SurfaceFormat aFormat, + TextureSourceProvider* aProvider, + ID3D11Texture2D* aTexture); + DataTextureSourceD3D11(gfx::SurfaceFormat aFormat, + TextureSourceProvider* aProvider, TextureFlags aFlags); + + virtual ~DataTextureSourceD3D11(); + + const char* Name() const override { return "DataTextureSourceD3D11"; } + + // DataTextureSource + + bool Update(gfx::DataSourceSurface* aSurface, + nsIntRegion* aDestRegion = nullptr, + gfx::IntPoint* aSrcOffset = nullptr) override; + + // TextureSource + + TextureSourceD3D11* AsSourceD3D11() override { return this; } + + ID3D11Texture2D* GetD3D11Texture() const override; + + ID3D11ShaderResourceView* GetShaderResourceView() override; + + // Returns nullptr if this texture was created by a DXGI TextureHost. + DataTextureSource* AsDataTextureSource() override { + return mAllowTextureUploads ? this : nullptr; + } + + void DeallocateDeviceData() override { mTexture = nullptr; } + + gfx::IntSize GetSize() const override { return mSize; } + + gfx::SurfaceFormat GetFormat() const override { return mFormat; } + + // BigImageIterator + + BigImageIterator* AsBigImageIterator() override { + return mIsTiled ? this : nullptr; + } + + size_t GetTileCount() override { return mTileTextures.size(); } + + bool NextTile() override { return (++mCurrentTile < mTileTextures.size()); } + + gfx::IntRect GetTileRect() override; + + void EndBigImageIteration() override { mIterating = false; } + + void BeginBigImageIteration() override { + mIterating = true; + mCurrentTile = 0; + } + + RefPtr ExtractCurrentTile() override; + + void Reset(); + + protected: + gfx::IntRect GetTileRect(uint32_t aIndex) const; + + std::vector > mTileTextures; + std::vector > mTileSRVs; + RefPtr mDevice; + gfx::SurfaceFormat mFormat; + TextureFlags mFlags; + uint32_t mCurrentTile; + bool mIsTiled; + bool mIterating; + // Sadly, the code was originally organized so that this class is used both in + // the cases where we want to perform texture uploads through the + // DataTextureSource interface, and the cases where we wrap the texture around + // an existing DXGI handle in which case we should not use it as a + // DataTextureSource. This member differentiates the two scenarios. When it is + // false the texture "pretends" to not be a DataTextureSource. + bool mAllowTextureUploads; +}; + +/** + * A TextureHost for shared D3D11 textures. + */ +class DXGITextureHostD3D11 : public TextureHost { + public: + DXGITextureHostD3D11(TextureFlags aFlags, + const SurfaceDescriptorD3D10& aDescriptor); + + bool BindTextureSource(CompositableTextureSourceRef& aTexture) override; + bool AcquireTextureSource(CompositableTextureSourceRef& aTexture) override; + + void DeallocateDeviceData() override {} + + void SetTextureSourceProvider(TextureSourceProvider* aProvider) override; + + gfx::SurfaceFormat GetFormat() const override { return mFormat; } + + bool Lock() override; + void Unlock() override; + + bool LockWithoutCompositor() override; + void UnlockWithoutCompositor() override; + + gfx::IntSize GetSize() const override { return mSize; } + gfx::YUVColorSpace GetYUVColorSpace() const override { + return mYUVColorSpace; + } + gfx::ColorRange GetColorRange() const override { return mColorRange; } + + already_AddRefed GetAsSurface() override; + + void CreateRenderTexture( + const wr::ExternalImageId& aExternalImageId) override; + + uint32_t NumSubTextures() override; + + void PushResourceUpdates(wr::TransactionBuilder& aResources, + ResourceUpdateOp aOp, + const Range& aImageKeys, + const wr::ExternalImageId& aExtID) override; + + void PushDisplayItems(wr::DisplayListBuilder& aBuilder, + const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, wr::ImageRendering aFilter, + const Range& aImageKeys, + PushDisplayItemFlagSet aFlags) override; + + bool SupportsExternalCompositing() override; + + protected: + bool LockInternal(); + void UnlockInternal(); + + bool EnsureTextureSource(); + + RefPtr GetDevice(); + + bool EnsureTexture(); + + RefPtr mDevice; + RefPtr mTexture; + RefPtr mTextureSource; + gfx::IntSize mSize; + WindowsHandle mHandle; + gfx::SurfaceFormat mFormat; + const gfx::YUVColorSpace mYUVColorSpace; + const gfx::ColorRange mColorRange; + bool mIsLocked; +}; + +class DXGIYCbCrTextureHostD3D11 : public TextureHost { + public: + DXGIYCbCrTextureHostD3D11(TextureFlags aFlags, + const SurfaceDescriptorDXGIYCbCr& aDescriptor); + + bool BindTextureSource(CompositableTextureSourceRef& aTexture) override; + bool AcquireTextureSource(CompositableTextureSourceRef& aTexture) override; + + void DeallocateDeviceData() override {} + + void SetTextureSourceProvider(TextureSourceProvider* aProvider) override; + + gfx::SurfaceFormat GetFormat() const override { + return gfx::SurfaceFormat::YUV; + } + + gfx::ColorDepth GetColorDepth() const override { return mColorDepth; } + gfx::YUVColorSpace GetYUVColorSpace() const override { + return mYUVColorSpace; + } + gfx::ColorRange GetColorRange() const override { return mColorRange; } + + bool Lock() override; + + void Unlock() override; + + gfx::IntSize GetSize() const override { return mSize; } + + already_AddRefed GetAsSurface() override { + return nullptr; + } + + void CreateRenderTexture( + const wr::ExternalImageId& aExternalImageId) override; + + uint32_t NumSubTextures() override; + + void PushResourceUpdates(wr::TransactionBuilder& aResources, + ResourceUpdateOp aOp, + const Range& aImageKeys, + const wr::ExternalImageId& aExtID) override; + + void PushDisplayItems(wr::DisplayListBuilder& aBuilder, + const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, wr::ImageRendering aFilter, + const Range& aImageKeys, + PushDisplayItemFlagSet aFlags) override; + + bool SupportsExternalCompositing() override; + + private: + bool EnsureTextureSource(); + + protected: + RefPtr GetDevice(); + + bool EnsureTexture(); + + RefPtr mTextures[3]; + RefPtr mTextureSources[3]; + + gfx::IntSize mSize; + gfx::IntSize mSizeY; + gfx::IntSize mSizeCbCr; + WindowsHandle mHandles[3]; + bool mIsLocked; + gfx::ColorDepth mColorDepth; + gfx::YUVColorSpace mYUVColorSpace; + gfx::ColorRange mColorRange; +}; + +class CompositingRenderTargetD3D11 : public CompositingRenderTarget, + public TextureSourceD3D11 { + public: + CompositingRenderTargetD3D11( + ID3D11Texture2D* aTexture, const gfx::IntPoint& aOrigin, + DXGI_FORMAT aFormatOverride = DXGI_FORMAT_UNKNOWN); + + const char* Name() const override { return "CompositingRenderTargetD3D11"; } + + TextureSourceD3D11* AsSourceD3D11() override { return this; } + + void BindRenderTarget(ID3D11DeviceContext* aContext); + + gfx::IntSize GetSize() const override; + + void SetSize(const gfx::IntSize& aSize) { mSize = aSize; } + + private: + friend class CompositorD3D11; + RefPtr mRTView; +}; + +class SyncObjectD3D11Host : public SyncObjectHost { + public: + explicit SyncObjectD3D11Host(ID3D11Device* aDevice); + + bool Init() override; + + SyncHandle GetSyncHandle() override; + + bool Synchronize(bool aFallible) override; + + IDXGIKeyedMutex* GetKeyedMutex() { return mKeyedMutex.get(); }; + + private: + virtual ~SyncObjectD3D11Host() = default; + + SyncHandle mSyncHandle; + RefPtr mDevice; + RefPtr mSyncTexture; + RefPtr mKeyedMutex; +}; + +class SyncObjectD3D11Client : public SyncObjectClient { + public: + SyncObjectD3D11Client(SyncHandle aSyncHandle, ID3D11Device* aDevice); + + bool Synchronize(bool aFallible) override; + + bool IsSyncObjectValid() override; + + void EnsureInitialized() override {} + + SyncType GetSyncType() override { return SyncType::D3D11; } + + void RegisterTexture(ID3D11Texture2D* aTexture); + + protected: + explicit SyncObjectD3D11Client(SyncHandle aSyncHandle); + bool Init(ID3D11Device* aDevice, bool aFallible); + bool SynchronizeInternal(ID3D11Device* aDevice, bool aFallible); + Mutex mSyncLock; + RefPtr mSyncTexture; + std::vector mSyncedTextures; + + private: + const SyncHandle mSyncHandle; + RefPtr mKeyedMutex; + const RefPtr mDevice; +}; + +class SyncObjectD3D11ClientContentDevice : public SyncObjectD3D11Client { + public: + explicit SyncObjectD3D11ClientContentDevice(SyncHandle aSyncHandle); + + bool Synchronize(bool aFallible) override; + + bool IsSyncObjectValid() override; + + void EnsureInitialized() override; + + private: + RefPtr mContentDevice; +}; + +inline uint32_t GetMaxTextureSizeForFeatureLevel( + D3D_FEATURE_LEVEL aFeatureLevel) { + int32_t maxTextureSize; + switch (aFeatureLevel) { + case D3D_FEATURE_LEVEL_11_1: + case D3D_FEATURE_LEVEL_11_0: + maxTextureSize = D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION; + break; + case D3D_FEATURE_LEVEL_10_1: + case D3D_FEATURE_LEVEL_10_0: + maxTextureSize = D3D10_REQ_TEXTURE2D_U_OR_V_DIMENSION; + break; + case D3D_FEATURE_LEVEL_9_3: + maxTextureSize = D3D_FL9_3_REQ_TEXTURE2D_U_OR_V_DIMENSION; + break; + default: + maxTextureSize = D3D_FL9_1_REQ_TEXTURE2D_U_OR_V_DIMENSION; + } + return maxTextureSize; +} + +uint32_t GetMaxTextureSizeFromDevice(ID3D11Device* aDevice); +void ReportTextureMemoryUsage(ID3D11Texture2D* aTexture, size_t aBytes); + +class AutoLockD3D11Texture { + public: + explicit AutoLockD3D11Texture(ID3D11Texture2D* aTexture); + ~AutoLockD3D11Texture(); + + private: + RefPtr mMutex; +}; + +class D3D11MTAutoEnter { + public: + explicit D3D11MTAutoEnter(already_AddRefed aMT) + : mMT(aMT) { + if (mMT) { + mMT->Enter(); + } + } + ~D3D11MTAutoEnter() { + if (mMT) { + mMT->Leave(); + } + } + + private: + RefPtr mMT; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* MOZILLA_GFX_TEXTURED3D11_H */ diff --git a/gfx/layers/d3d11/genshaders.py b/gfx/layers/d3d11/genshaders.py new file mode 100644 index 0000000000..2108bfe693 --- /dev/null +++ b/gfx/layers/d3d11/genshaders.py @@ -0,0 +1,176 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +from __future__ import absolute_import +import argparse +import codecs +import locale +import os +import re +import subprocess +import sys +import tempfile +import yaml +import buildconfig + + +def shell_main(): + parser = argparse.ArgumentParser() + parser.add_argument("-o", "--output", type=str, required=True, help="Output file") + parser.add_argument("manifest", type=str, help="Manifest source file") + args = parser.parse_args() + + with open(args.output, "w") as out_file: + process_manifest(out_file, args.manifest) + + +def main(output_fp, input_filename): + return process_manifest(output_fp, input_filename) + + +HEADER = """// AUTOGENERATED - DO NOT EDIT +namespace mozilla { +namespace layers { + +struct ShaderBytes { const void* mData; size_t mLength; }; +""" +FOOTER = """ +} // namespace layers +} // namespace mozilla""" + + +def process_manifest(output_fp, manifest_filename): + with codecs.open(manifest_filename, "r", "UTF-8") as in_fp: + manifest = yaml.safe_load(in_fp) + shader_folder, _ = os.path.split(manifest_filename) + + output_fp.write(HEADER) + + deps = set() + for block in manifest: + if "type" not in block: + raise Exception("Expected 'type' key with shader mode") + if "file" not in block: + raise Exception("Expected 'file' key with shader file") + if "shaders" not in block: + raise Exception("Expected 'shaders' key with shader name list") + + shader_file = os.path.join(shader_folder, block["file"]) + deps.add(shader_file) + + shader_model = block["type"] + for shader_name in block["shaders"]: + new_deps = run_fxc( + shader_model=shader_model, + shader_file=shader_file, + shader_name=shader_name, + output_fp=output_fp, + ) + deps |= new_deps + + output_fp.write(FOOTER) + return deps + + +def run_fxc(shader_model, shader_file, shader_name, output_fp): + fxc_location = buildconfig.substs["FXC"] + + argv = [ + fxc_location, + "-nologo", + "-T{0}".format(shader_model), + os.path.relpath(shader_file), + "-E{0}".format(shader_name), + "-Vn{0}".format(shader_name), + "-Vi", + ] + if "WINNT" not in buildconfig.substs["HOST_OS_ARCH"]: + argv.insert(0, buildconfig.substs["WINE"]) + if shader_model.startswith("vs_"): + argv += ["-DVERTEX_SHADER"] + elif shader_model.startswith("ps_"): + argv += ["-DPIXEL_SHADER"] + + deps = None + with ScopedTempFilename() as temp_filename: + argv += ["-Fh{0}".format(os.path.relpath(temp_filename))] + + sys.stdout.write("{0}\n".format(" ".join(argv))) + sys.stdout.flush() + proc_stdout = subprocess.check_output(argv) + proc_stdout = decode_console_text(sys.stdout, proc_stdout) + deps = find_dependencies(proc_stdout) + assert "fxc2" in fxc_location or len(deps) > 0 + + with open(temp_filename, "r") as temp_fp: + output_fp.write(temp_fp.read()) + + output_fp.write("ShaderBytes s{0} = {{ {0}, sizeof({0}) }};\n".format(shader_name)) + return deps + + +def find_dependencies(fxc_output): + # Dependencies look like this: + # Resolved to [] + # + # Microsoft likes to change output strings based on the user's language, so + # instead of pattern matching on that string, we take everything in between + # brackets. We filter out potentially bogus strings later. + deps = set() + for line in fxc_output.split("\n"): + m = re.search(r"\[([^\]]+)\]", line) + if m is None: + continue + dep_path = m.group(1) + dep_path = os.path.normpath(dep_path) + # When run via Wine, FXC's output contains Windows paths on the Z drive. + # We want to normalize them back to unix paths for the build system. + if "WINNT" not in buildconfig.substs[ + "HOST_OS_ARCH" + ] and dep_path.lower().startswith("z:"): + dep_path = dep_path[2:].replace("\\", "/") + if os.path.isfile(dep_path): + deps.add(dep_path) + return deps + + +# Python reads the raw bytes from stdout, so we need to try our best to +# capture that as a valid Python string. + + +def decode_console_text(pipe, text): + try: + if pipe.encoding: + return text.decode(pipe.encoding, "replace") + except Exception: + pass + try: + return text.decode(locale.getpreferredencoding(), "replace") + except Exception: + return text.decode("utf8", "replace") + + +# Allocate a temporary file name and delete it when done. We need an extra +# wrapper for this since TemporaryNamedFile holds the file open. + + +class ScopedTempFilename(object): + def __init__(self): + self.name = None + + def __enter__(self): + with tempfile.NamedTemporaryFile(dir=os.getcwd(), delete=False) as tmp: + self.name = tmp.name + return self.name + + def __exit__(self, type, value, traceback): + if not self.name: + return + try: + os.unlink(self.name) + except Exception: + pass + + +if __name__ == "__main__": + shell_main() diff --git a/gfx/layers/d3d11/mlgshaders/blend-common.hlsl b/gfx/layers/d3d11/mlgshaders/blend-common.hlsl new file mode 100644 index 0000000000..1194fc0801 --- /dev/null +++ b/gfx/layers/d3d11/mlgshaders/blend-common.hlsl @@ -0,0 +1,15 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "common-vs.hlsl" + +struct VS_BLEND_OUTPUT +{ + float4 vPosition : SV_Position; + float2 vTexCoords : TEXCOORD0; + float2 vBackdropCoords : TEXCOORD1; + float2 vLocalPos : TEXCOORD2; + float3 vMaskCoords : TEXCOORD3; + nointerpolation float4 vClipRect : TEXCOORD4; +}; diff --git a/gfx/layers/d3d11/mlgshaders/blend-ps-generated.hlslh b/gfx/layers/d3d11/mlgshaders/blend-ps-generated.hlslh new file mode 100644 index 0000000000..655f3e0cb4 --- /dev/null +++ b/gfx/layers/d3d11/mlgshaders/blend-ps-generated.hlslh @@ -0,0 +1,540 @@ +float4 BlendMultiplyPS(const VS_BLEND_OUTPUT aInput) : SV_Target +{ + if (!RectContainsPoint(aInput.vClipRect, aInput.vPosition.xy)) { + discard; + } + + float4 backdrop = tBackdrop.Sample(sSampler, aInput.vBackdropCoords); + float4 source = simpleTex.Sample(sSampler, aInput.vTexCoords); + + source *= ReadMask(aInput.vMaskCoords); + + // Shortcut when the backdrop or source alpha is 0, otherwise we may leak + // infinity into the blend function and return incorrect results. + if (backdrop.a == 0.0) { + return source; + } + if (source.a == 0.0) { + return float4(0, 0, 0, 0); + } + + // The spec assumes there is no premultiplied alpha. The backdrop and + // source are both render targets and always premultiplied, so we undo + // that here. + backdrop.rgb /= backdrop.a; + source.rgb /= source.a; + + float4 result; + result.rgb = BlendMultiply(backdrop.rgb, source.rgb); + result.a = source.a; + + // Factor backdrop alpha, then premultiply for the final OP_OVER. + result.rgb = (1.0 - backdrop.a) * source.rgb + backdrop.a * result.rgb; + result.rgb *= result.a; + return result; +} + +float4 BlendScreenPS(const VS_BLEND_OUTPUT aInput) : SV_Target +{ + if (!RectContainsPoint(aInput.vClipRect, aInput.vPosition.xy)) { + discard; + } + + float4 backdrop = tBackdrop.Sample(sSampler, aInput.vBackdropCoords); + float4 source = simpleTex.Sample(sSampler, aInput.vTexCoords); + + source *= ReadMask(aInput.vMaskCoords); + + // Shortcut when the backdrop or source alpha is 0, otherwise we may leak + // infinity into the blend function and return incorrect results. + if (backdrop.a == 0.0) { + return source; + } + if (source.a == 0.0) { + return float4(0, 0, 0, 0); + } + + // The spec assumes there is no premultiplied alpha. The backdrop and + // source are both render targets and always premultiplied, so we undo + // that here. + backdrop.rgb /= backdrop.a; + source.rgb /= source.a; + + float4 result; + result.rgb = BlendScreen(backdrop.rgb, source.rgb); + result.a = source.a; + + // Factor backdrop alpha, then premultiply for the final OP_OVER. + result.rgb = (1.0 - backdrop.a) * source.rgb + backdrop.a * result.rgb; + result.rgb *= result.a; + return result; +} + +float4 BlendOverlayPS(const VS_BLEND_OUTPUT aInput) : SV_Target +{ + if (!RectContainsPoint(aInput.vClipRect, aInput.vPosition.xy)) { + discard; + } + + float4 backdrop = tBackdrop.Sample(sSampler, aInput.vBackdropCoords); + float4 source = simpleTex.Sample(sSampler, aInput.vTexCoords); + + source *= ReadMask(aInput.vMaskCoords); + + // Shortcut when the backdrop or source alpha is 0, otherwise we may leak + // infinity into the blend function and return incorrect results. + if (backdrop.a == 0.0) { + return source; + } + if (source.a == 0.0) { + return float4(0, 0, 0, 0); + } + + // The spec assumes there is no premultiplied alpha. The backdrop and + // source are both render targets and always premultiplied, so we undo + // that here. + backdrop.rgb /= backdrop.a; + source.rgb /= source.a; + + float4 result; + result.rgb = BlendOverlay(backdrop.rgb, source.rgb); + result.a = source.a; + + // Factor backdrop alpha, then premultiply for the final OP_OVER. + result.rgb = (1.0 - backdrop.a) * source.rgb + backdrop.a * result.rgb; + result.rgb *= result.a; + return result; +} + +float4 BlendDarkenPS(const VS_BLEND_OUTPUT aInput) : SV_Target +{ + if (!RectContainsPoint(aInput.vClipRect, aInput.vPosition.xy)) { + discard; + } + + float4 backdrop = tBackdrop.Sample(sSampler, aInput.vBackdropCoords); + float4 source = simpleTex.Sample(sSampler, aInput.vTexCoords); + + source *= ReadMask(aInput.vMaskCoords); + + // Shortcut when the backdrop or source alpha is 0, otherwise we may leak + // infinity into the blend function and return incorrect results. + if (backdrop.a == 0.0) { + return source; + } + if (source.a == 0.0) { + return float4(0, 0, 0, 0); + } + + // The spec assumes there is no premultiplied alpha. The backdrop and + // source are both render targets and always premultiplied, so we undo + // that here. + backdrop.rgb /= backdrop.a; + source.rgb /= source.a; + + float4 result; + result.rgb = BlendDarken(backdrop.rgb, source.rgb); + result.a = source.a; + + // Factor backdrop alpha, then premultiply for the final OP_OVER. + result.rgb = (1.0 - backdrop.a) * source.rgb + backdrop.a * result.rgb; + result.rgb *= result.a; + return result; +} + +float4 BlendLightenPS(const VS_BLEND_OUTPUT aInput) : SV_Target +{ + if (!RectContainsPoint(aInput.vClipRect, aInput.vPosition.xy)) { + discard; + } + + float4 backdrop = tBackdrop.Sample(sSampler, aInput.vBackdropCoords); + float4 source = simpleTex.Sample(sSampler, aInput.vTexCoords); + + source *= ReadMask(aInput.vMaskCoords); + + // Shortcut when the backdrop or source alpha is 0, otherwise we may leak + // infinity into the blend function and return incorrect results. + if (backdrop.a == 0.0) { + return source; + } + if (source.a == 0.0) { + return float4(0, 0, 0, 0); + } + + // The spec assumes there is no premultiplied alpha. The backdrop and + // source are both render targets and always premultiplied, so we undo + // that here. + backdrop.rgb /= backdrop.a; + source.rgb /= source.a; + + float4 result; + result.rgb = BlendLighten(backdrop.rgb, source.rgb); + result.a = source.a; + + // Factor backdrop alpha, then premultiply for the final OP_OVER. + result.rgb = (1.0 - backdrop.a) * source.rgb + backdrop.a * result.rgb; + result.rgb *= result.a; + return result; +} + +float4 BlendColorDodgePS(const VS_BLEND_OUTPUT aInput) : SV_Target +{ + if (!RectContainsPoint(aInput.vClipRect, aInput.vPosition.xy)) { + discard; + } + + float4 backdrop = tBackdrop.Sample(sSampler, aInput.vBackdropCoords); + float4 source = simpleTex.Sample(sSampler, aInput.vTexCoords); + + source *= ReadMask(aInput.vMaskCoords); + + // Shortcut when the backdrop or source alpha is 0, otherwise we may leak + // infinity into the blend function and return incorrect results. + if (backdrop.a == 0.0) { + return source; + } + if (source.a == 0.0) { + return float4(0, 0, 0, 0); + } + + // The spec assumes there is no premultiplied alpha. The backdrop and + // source are both render targets and always premultiplied, so we undo + // that here. + backdrop.rgb /= backdrop.a; + source.rgb /= source.a; + + float4 result; + result.rgb = BlendColorDodge(backdrop.rgb, source.rgb); + result.a = source.a; + + // Factor backdrop alpha, then premultiply for the final OP_OVER. + result.rgb = (1.0 - backdrop.a) * source.rgb + backdrop.a * result.rgb; + result.rgb *= result.a; + return result; +} + +float4 BlendColorBurnPS(const VS_BLEND_OUTPUT aInput) : SV_Target +{ + if (!RectContainsPoint(aInput.vClipRect, aInput.vPosition.xy)) { + discard; + } + + float4 backdrop = tBackdrop.Sample(sSampler, aInput.vBackdropCoords); + float4 source = simpleTex.Sample(sSampler, aInput.vTexCoords); + + source *= ReadMask(aInput.vMaskCoords); + + // Shortcut when the backdrop or source alpha is 0, otherwise we may leak + // infinity into the blend function and return incorrect results. + if (backdrop.a == 0.0) { + return source; + } + if (source.a == 0.0) { + return float4(0, 0, 0, 0); + } + + // The spec assumes there is no premultiplied alpha. The backdrop and + // source are both render targets and always premultiplied, so we undo + // that here. + backdrop.rgb /= backdrop.a; + source.rgb /= source.a; + + float4 result; + result.rgb = BlendColorBurn(backdrop.rgb, source.rgb); + result.a = source.a; + + // Factor backdrop alpha, then premultiply for the final OP_OVER. + result.rgb = (1.0 - backdrop.a) * source.rgb + backdrop.a * result.rgb; + result.rgb *= result.a; + return result; +} + +float4 BlendHardLightPS(const VS_BLEND_OUTPUT aInput) : SV_Target +{ + if (!RectContainsPoint(aInput.vClipRect, aInput.vPosition.xy)) { + discard; + } + + float4 backdrop = tBackdrop.Sample(sSampler, aInput.vBackdropCoords); + float4 source = simpleTex.Sample(sSampler, aInput.vTexCoords); + + source *= sOpacity; + + // Shortcut when the backdrop or source alpha is 0, otherwise we may leak + // infinity into the blend function and return incorrect results. + if (backdrop.a == 0.0) { + return source; + } + if (source.a == 0.0) { + return float4(0, 0, 0, 0); + } + + // The spec assumes there is no premultiplied alpha. The backdrop and + // source are both render targets and always premultiplied, so we undo + // that here. + backdrop.rgb /= backdrop.a; + source.rgb /= source.a; + + float4 result; + result.rgb = BlendHardLight(backdrop.rgb, source.rgb); + result.a = source.a; + + // Factor backdrop alpha, then premultiply for the final OP_OVER. + result.rgb = (1.0 - backdrop.a) * source.rgb + backdrop.a * result.rgb; + result.rgb *= result.a; + return result; +} + +float4 BlendSoftLightPS(const VS_BLEND_OUTPUT aInput) : SV_Target +{ + if (!RectContainsPoint(aInput.vClipRect, aInput.vPosition.xy)) { + discard; + } + + float4 backdrop = tBackdrop.Sample(sSampler, aInput.vBackdropCoords); + float4 source = simpleTex.Sample(sSampler, aInput.vTexCoords); + + source *= ReadMask(aInput.vMaskCoords); + + // Shortcut when the backdrop or source alpha is 0, otherwise we may leak + // infinity into the blend function and return incorrect results. + if (backdrop.a == 0.0) { + return source; + } + if (source.a == 0.0) { + return float4(0, 0, 0, 0); + } + + // The spec assumes there is no premultiplied alpha. The backdrop and + // source are both render targets and always premultiplied, so we undo + // that here. + backdrop.rgb /= backdrop.a; + source.rgb /= source.a; + + float4 result; + result.rgb = BlendSoftLight(backdrop.rgb, source.rgb); + result.a = source.a; + + // Factor backdrop alpha, then premultiply for the final OP_OVER. + result.rgb = (1.0 - backdrop.a) * source.rgb + backdrop.a * result.rgb; + result.rgb *= result.a; + return result; +} + +float4 BlendDifferencePS(const VS_BLEND_OUTPUT aInput) : SV_Target +{ + if (!RectContainsPoint(aInput.vClipRect, aInput.vPosition.xy)) { + discard; + } + + float4 backdrop = tBackdrop.Sample(sSampler, aInput.vBackdropCoords); + float4 source = simpleTex.Sample(sSampler, aInput.vTexCoords); + + source *= ReadMask(aInput.vMaskCoords); + + // Shortcut when the backdrop or source alpha is 0, otherwise we may leak + // infinity into the blend function and return incorrect results. + if (backdrop.a == 0.0) { + return source; + } + if (source.a == 0.0) { + return float4(0, 0, 0, 0); + } + + // The spec assumes there is no premultiplied alpha. The backdrop and + // source are both render targets and always premultiplied, so we undo + // that here. + backdrop.rgb /= backdrop.a; + source.rgb /= source.a; + + float4 result; + result.rgb = BlendDifference(backdrop.rgb, source.rgb); + result.a = source.a; + + // Factor backdrop alpha, then premultiply for the final OP_OVER. + result.rgb = (1.0 - backdrop.a) * source.rgb + backdrop.a * result.rgb; + result.rgb *= result.a; + return result; +} + +float4 BlendExclusionPS(const VS_BLEND_OUTPUT aInput) : SV_Target +{ + if (!RectContainsPoint(aInput.vClipRect, aInput.vPosition.xy)) { + discard; + } + + float4 backdrop = tBackdrop.Sample(sSampler, aInput.vBackdropCoords); + float4 source = simpleTex.Sample(sSampler, aInput.vTexCoords); + + source *= ReadMask(aInput.vMaskCoords); + + // Shortcut when the backdrop or source alpha is 0, otherwise we may leak + // infinity into the blend function and return incorrect results. + if (backdrop.a == 0.0) { + return source; + } + if (source.a == 0.0) { + return float4(0, 0, 0, 0); + } + + // The spec assumes there is no premultiplied alpha. The backdrop and + // source are both render targets and always premultiplied, so we undo + // that here. + backdrop.rgb /= backdrop.a; + source.rgb /= source.a; + + float4 result; + result.rgb = BlendExclusion(backdrop.rgb, source.rgb); + result.a = source.a; + + // Factor backdrop alpha, then premultiply for the final OP_OVER. + result.rgb = (1.0 - backdrop.a) * source.rgb + backdrop.a * result.rgb; + result.rgb *= result.a; + return result; +} + +float4 BlendHuePS(const VS_BLEND_OUTPUT aInput) : SV_Target +{ + if (!RectContainsPoint(aInput.vClipRect, aInput.vPosition.xy)) { + discard; + } + + float4 backdrop = tBackdrop.Sample(sSampler, aInput.vBackdropCoords); + float4 source = simpleTex.Sample(sSampler, aInput.vTexCoords); + + source *= ReadMask(aInput.vMaskCoords); + + // Shortcut when the backdrop or source alpha is 0, otherwise we may leak + // infinity into the blend function and return incorrect results. + if (backdrop.a == 0.0) { + return source; + } + if (source.a == 0.0) { + return float4(0, 0, 0, 0); + } + + // The spec assumes there is no premultiplied alpha. The backdrop and + // source are both render targets and always premultiplied, so we undo + // that here. + backdrop.rgb /= backdrop.a; + source.rgb /= source.a; + + float4 result; + result.rgb = BlendHue(backdrop.rgb, source.rgb); + result.a = source.a; + + // Factor backdrop alpha, then premultiply for the final OP_OVER. + result.rgb = (1.0 - backdrop.a) * source.rgb + backdrop.a * result.rgb; + result.rgb *= result.a; + return result; +} + +float4 BlendSaturationPS(const VS_BLEND_OUTPUT aInput) : SV_Target +{ + if (!RectContainsPoint(aInput.vClipRect, aInput.vPosition.xy)) { + discard; + } + + float4 backdrop = tBackdrop.Sample(sSampler, aInput.vBackdropCoords); + float4 source = simpleTex.Sample(sSampler, aInput.vTexCoords); + + source *= ReadMask(aInput.vMaskCoords); + + // Shortcut when the backdrop or source alpha is 0, otherwise we may leak + // infinity into the blend function and return incorrect results. + if (backdrop.a == 0.0) { + return source; + } + if (source.a == 0.0) { + return float4(0, 0, 0, 0); + } + + // The spec assumes there is no premultiplied alpha. The backdrop and + // source are both render targets and always premultiplied, so we undo + // that here. + backdrop.rgb /= backdrop.a; + source.rgb /= source.a; + + float4 result; + result.rgb = BlendSaturation(backdrop.rgb, source.rgb); + result.a = source.a; + + // Factor backdrop alpha, then premultiply for the final OP_OVER. + result.rgb = (1.0 - backdrop.a) * source.rgb + backdrop.a * result.rgb; + result.rgb *= result.a; + return result; +} + +float4 BlendColorPS(const VS_BLEND_OUTPUT aInput) : SV_Target +{ + if (!RectContainsPoint(aInput.vClipRect, aInput.vPosition.xy)) { + discard; + } + + float4 backdrop = tBackdrop.Sample(sSampler, aInput.vBackdropCoords); + float4 source = simpleTex.Sample(sSampler, aInput.vTexCoords); + + source *= ReadMask(aInput.vMaskCoords); + + // Shortcut when the backdrop or source alpha is 0, otherwise we may leak + // infinity into the blend function and return incorrect results. + if (backdrop.a == 0.0) { + return source; + } + if (source.a == 0.0) { + return float4(0, 0, 0, 0); + } + + // The spec assumes there is no premultiplied alpha. The backdrop and + // source are both render targets and always premultiplied, so we undo + // that here. + backdrop.rgb /= backdrop.a; + source.rgb /= source.a; + + float4 result; + result.rgb = BlendColor(backdrop.rgb, source.rgb); + result.a = source.a; + + // Factor backdrop alpha, then premultiply for the final OP_OVER. + result.rgb = (1.0 - backdrop.a) * source.rgb + backdrop.a * result.rgb; + result.rgb *= result.a; + return result; +} + +float4 BlendLuminosityPS(const VS_BLEND_OUTPUT aInput) : SV_Target +{ + if (!RectContainsPoint(aInput.vClipRect, aInput.vPosition.xy)) { + discard; + } + + float4 backdrop = tBackdrop.Sample(sSampler, aInput.vBackdropCoords); + float4 source = simpleTex.Sample(sSampler, aInput.vTexCoords); + + source *= ReadMask(aInput.vMaskCoords); + + // Shortcut when the backdrop or source alpha is 0, otherwise we may leak + // infinity into the blend function and return incorrect results. + if (backdrop.a == 0.0) { + return source; + } + if (source.a == 0.0) { + return float4(0, 0, 0, 0); + } + + // The spec assumes there is no premultiplied alpha. The backdrop and + // source are both render targets and always premultiplied, so we undo + // that here. + backdrop.rgb /= backdrop.a; + source.rgb /= source.a; + + float4 result; + result.rgb = BlendLuminosity(backdrop.rgb, source.rgb); + result.a = source.a; + + // Factor backdrop alpha, then premultiply for the final OP_OVER. + result.rgb = (1.0 - backdrop.a) * source.rgb + backdrop.a * result.rgb; + result.rgb *= result.a; + return result; +} + diff --git a/gfx/layers/d3d11/mlgshaders/blend-ps-generated.hlslh.tpl b/gfx/layers/d3d11/mlgshaders/blend-ps-generated.hlslh.tpl new file mode 100644 index 0000000000..a24577a09a --- /dev/null +++ b/gfx/layers/d3d11/mlgshaders/blend-ps-generated.hlslh.tpl @@ -0,0 +1,36 @@ +float4 Blend{BLEND_FUNC}PS(const VS_BLEND_OUTPUT aInput) : SV_Target +{ + if (!RectContainsPoint(aInput.vClipRect, aInput.vPosition.xy)) { + discard; + } + + float4 backdrop = tBackdrop.Sample(sSampler, aInput.vBackdropCoords); + float4 source = simpleTex.Sample(sSampler, aInput.vTexCoords); + + // Apply masks to the source texture, not the result. + source *= ReadMask(aInput.vMaskCoords); + + // Shortcut when the backdrop or source alpha is 0, otherwise we may leak + // infinity into the blend function and return incorrect results. + if (backdrop.a == 0.0) { + return source; + } + if (source.a == 0.0) { + return float4(0, 0, 0, 0); + } + + // The spec assumes there is no premultiplied alpha. The backdrop and + // source are both render targets and always premultiplied, so we undo + // that here. + backdrop.rgb /= backdrop.a; + source.rgb /= source.a; + + float4 result; + result.rgb = Blend{BLEND_FUNC}(backdrop.rgb, source.rgb); + result.a = source.a; + + // Factor backdrop alpha, then premultiply for the final OP_OVER. + result.rgb = (1.0 - backdrop.a) * source.rgb + backdrop.a * result.rgb; + result.rgb *= result.a; + return result; +} diff --git a/gfx/layers/d3d11/mlgshaders/blend-ps.hlsl b/gfx/layers/d3d11/mlgshaders/blend-ps.hlsl new file mode 100644 index 0000000000..094bfdfdb1 --- /dev/null +++ b/gfx/layers/d3d11/mlgshaders/blend-ps.hlsl @@ -0,0 +1,13 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "common.hlsl" +#include "common-ps.hlsl" +#include "blend-common.hlsl" +#include "../BlendingHelpers.hlslh" + +Texture2D simpleTex : register(ps, t0); +Texture2D tBackdrop : register(ps, t1); + +#include "blend-ps-generated.hlslh" diff --git a/gfx/layers/d3d11/mlgshaders/blend-vs.hlsl b/gfx/layers/d3d11/mlgshaders/blend-vs.hlsl new file mode 100644 index 0000000000..db873e34a2 --- /dev/null +++ b/gfx/layers/d3d11/mlgshaders/blend-vs.hlsl @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "common-vs.hlsl" +#include "blend-common.hlsl" +#include "textured-common.hlsl" + +cbuffer BlendConstants : register(b4) +{ + float4x4 mBackdropTransform; +} + +float2 BackdropPosition(float4 aPosition) +{ + // Move the position from clip space (-1,1) into 0..1 space. + float2 pos; + pos.x = (aPosition.x + 1.0) / 2.0; + pos.y = 1.0 - (aPosition.y + 1.0) / 2.0; + + return mul(mBackdropTransform, float4(pos.xy, 0, 1.0)).xy; +} + +VS_BLEND_OUTPUT BlendImpl(const VertexInfo aInfo, float2 aTexCoord) +{ + VS_BLEND_OUTPUT output; + output.vPosition = aInfo.worldPos; + output.vTexCoords = aTexCoord; + output.vBackdropCoords = BackdropPosition(output.vPosition); + output.vLocalPos = aInfo.screenPos; + output.vClipRect = aInfo.clipRect; + output.vMaskCoords = aInfo.maskCoords; + return output; +} + +VS_BLEND_OUTPUT BlendVertexVS(const VS_TEXTUREDVERTEX aVertex) +{ + float2 layerPos = UnitTriangleToPos( + aVertex.vUnitPos, + aVertex.vPos1, + aVertex.vPos2, + aVertex.vPos3); + + float2 texCoord = UnitTriangleToPos( + aVertex.vUnitPos, + aVertex.vTexCoord1, + aVertex.vTexCoord2, + aVertex.vTexCoord3); + + VertexInfo info = ComputePosition(layerPos, aVertex.vLayerId, aVertex.vDepth); + return BlendImpl(info, texCoord); +} diff --git a/gfx/layers/d3d11/mlgshaders/clear-common.hlsl b/gfx/layers/d3d11/mlgshaders/clear-common.hlsl new file mode 100644 index 0000000000..da69301d34 --- /dev/null +++ b/gfx/layers/d3d11/mlgshaders/clear-common.hlsl @@ -0,0 +1,9 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +struct VS_CLEAR_OUT +{ + float4 vPosition : SV_Position; +}; diff --git a/gfx/layers/d3d11/mlgshaders/clear-ps.hlsl b/gfx/layers/d3d11/mlgshaders/clear-ps.hlsl new file mode 100644 index 0000000000..7313585eed --- /dev/null +++ b/gfx/layers/d3d11/mlgshaders/clear-ps.hlsl @@ -0,0 +1,12 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "common-ps.hlsl" +#include "clear-common.hlsl" + +float4 ClearPS(const VS_CLEAR_OUT aVS) : SV_Target +{ + return float4(0.0, 0.0, 0.0, 0.0); +} + diff --git a/gfx/layers/d3d11/mlgshaders/clear-vs.hlsl b/gfx/layers/d3d11/mlgshaders/clear-vs.hlsl new file mode 100644 index 0000000000..5d95fcf611 --- /dev/null +++ b/gfx/layers/d3d11/mlgshaders/clear-vs.hlsl @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "common-vs.hlsl" +#include "clear-common.hlsl" + +struct VS_CLEAR_IN +{ + float2 vPos : POSITION; + int4 vRect : TEXCOORD0; +}; + +// Note: we use slot 2 so we don't have to rebind the layer slot (1) to run +// this shader. +cbuffer ClearConstants : register(b2) { + int sDepth : packoffset(c0.x); +}; + +VS_CLEAR_OUT ClearVS(const VS_CLEAR_IN aInput) +{ + float4 rect = float4(aInput.vRect.x, aInput.vRect.y, aInput.vRect.z, aInput.vRect.w); + float4 screenPos = float4(UnitQuadToRect(aInput.vPos, rect), 0, 1); + float4 worldPos = mul(WorldTransform, screenPos); + worldPos.z = ComputeDepth(worldPos, sDepth); + + VS_CLEAR_OUT output; + output.vPosition = worldPos; + return output; +} diff --git a/gfx/layers/d3d11/mlgshaders/color-common.hlsl b/gfx/layers/d3d11/mlgshaders/color-common.hlsl new file mode 100644 index 0000000000..1769cbed80 --- /dev/null +++ b/gfx/layers/d3d11/mlgshaders/color-common.hlsl @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +struct VS_COLOROUTPUT +{ + nointerpolation float4 vColor : COLOR0; + nointerpolation float4 vClipRect : TEXCOORD0; + float4 vPosition : SV_Position; + float2 vLocalPos : TEXCOORD1; + float3 vMaskCoords : TEXCOORD2; +}; + +struct VS_COLOROUTPUT_CLIPPED +{ + float4 vPosition : SV_Position; + nointerpolation float4 vColor : COLOR0; +}; diff --git a/gfx/layers/d3d11/mlgshaders/color-ps.hlsl b/gfx/layers/d3d11/mlgshaders/color-ps.hlsl new file mode 100644 index 0000000000..4bbd545594 --- /dev/null +++ b/gfx/layers/d3d11/mlgshaders/color-ps.hlsl @@ -0,0 +1,20 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "common-ps.hlsl" +#include "color-common.hlsl" + +float4 ColoredQuadPS(const VS_COLOROUTPUT_CLIPPED aVS) : SV_Target +{ + // Opacity is always 1.0, we premultiply it on the CPU. + return aVS.vColor; +} + +float4 ColoredVertexPS(const VS_COLOROUTPUT aVS) : SV_Target +{ + if (!RectContainsPoint(aVS.vClipRect, aVS.vPosition.xy)) { + return float4(0, 0, 0, 0); + } + return aVS.vColor * ReadMaskWithOpacity(aVS.vMaskCoords, 1.0); +} diff --git a/gfx/layers/d3d11/mlgshaders/color-vs.hlsl b/gfx/layers/d3d11/mlgshaders/color-vs.hlsl new file mode 100644 index 0000000000..31f5037daf --- /dev/null +++ b/gfx/layers/d3d11/mlgshaders/color-vs.hlsl @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "common-vs.hlsl" +#include "color-common.hlsl" + +struct VS_COLORQUAD +{ + float2 vPos : POSITION; + float4 vRect : TEXCOORD0; + uint vLayerId : TEXCOORD1; + int vDepth : TEXCOORD2; + float4 vColor : TEXCOORD3; +}; + +struct VS_COLORVERTEX +{ + float3 vUnitPos : POSITION0; + float2 vPos1 : POSITION1; + float2 vPos2 : POSITION2; + float2 vPos3 : POSITION3; + uint vLayerId : TEXCOORD0; + int vDepth : TEXCOORD1; + float4 vColor : TEXCOORD2; +}; + +VS_COLOROUTPUT ColorImpl(float4 aColor, const VertexInfo aInfo) +{ + VS_COLOROUTPUT output; + output.vPosition = aInfo.worldPos; + output.vLocalPos = aInfo.screenPos; + output.vColor = aColor; + output.vClipRect = aInfo.clipRect; + output.vMaskCoords = aInfo.maskCoords; + return output; +} + +VS_COLOROUTPUT_CLIPPED ColoredQuadVS(const VS_COLORQUAD aInput) +{ + float4 worldPos = ComputeClippedPosition( + aInput.vPos, + aInput.vRect, + aInput.vLayerId, + aInput.vDepth); + + VS_COLOROUTPUT_CLIPPED output; + output.vPosition = worldPos; + output.vColor = aInput.vColor; + return output; +} + +VS_COLOROUTPUT ColoredVertexVS(const VS_COLORVERTEX aInput) +{ + float2 layerPos = UnitTriangleToPos(aInput.vUnitPos, aInput.vPos1, aInput.vPos2, aInput.vPos3); + VertexInfo info = ComputePosition(layerPos, aInput.vLayerId, aInput.vDepth); + return ColorImpl(aInput.vColor, info); +} diff --git a/gfx/layers/d3d11/mlgshaders/common-ps.hlsl b/gfx/layers/d3d11/mlgshaders/common-ps.hlsl new file mode 100644 index 0000000000..624f87e2ce --- /dev/null +++ b/gfx/layers/d3d11/mlgshaders/common-ps.hlsl @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "common.hlsl" + +sampler sSampler : register(ps, s0); +sampler sMaskSampler : register(ps, s1); + +Texture2D tMaskTexture : register(ps, t4); + +cbuffer MaskInformation : register(b0) +{ + float sOpacity : packoffset(c0.x); + uint sHasMask : packoffset(c0.y); +}; + +float2 MaskCoordsToUV(float3 aMaskCoords) +{ + return aMaskCoords.xy / aMaskCoords.z; +} + +float ReadMaskWithOpacity(float3 aMaskCoords, float aOpacity) +{ + if (!sHasMask) { + return aOpacity; + } + + float2 uv = MaskCoordsToUV(aMaskCoords); + float r = tMaskTexture.Sample(sMaskSampler, uv).r; + return min(aOpacity, r); +} + +float ReadMask(float3 aMaskCoords) +{ + return ReadMaskWithOpacity(aMaskCoords, sOpacity); +} diff --git a/gfx/layers/d3d11/mlgshaders/common-vs.hlsl b/gfx/layers/d3d11/mlgshaders/common-vs.hlsl new file mode 100644 index 0000000000..6231b525a9 --- /dev/null +++ b/gfx/layers/d3d11/mlgshaders/common-vs.hlsl @@ -0,0 +1,167 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_layers_d3d11_mlgshaders_common_vs_hlsl +#define mozilla_gfx_layers_d3d11_mlgshaders_common_vs_hlsl + +#include "common.hlsl" + +cbuffer VSBufSimple : register(b0) +{ + float4x4 WorldTransform; + float2 RenderTargetOffset; + int SortIndexOffset; + uint DebugFrameNumber; +}; + +struct Layer { + float4x4 transform; + float4 clipRect; + uint4 info; +}; + +cbuffer Layers : register(b1) +{ + Layer sLayers[682]; +}; + +cbuffer MaskRects : register(b3) +{ + float4 sMaskRects[4096]; +}; + +struct VertexInfo { + float4 worldPos; + float2 screenPos; + float3 maskCoords; + float4 clipRect; +}; + +float3 ComputeMaskCoords(float4 aPosition, Layer aLayer) +{ + if (aLayer.info.x == 0) { + return float3(0.0, 0.0, 0.0); + } + + float4 maskRect = sMaskRects[aLayer.info.x - 1]; + + // See the perspective comment in CompositorD3D11.hlsl. + float4x4 transform = float4x4( + 1.0/maskRect.z, 0.0, 0.0, -maskRect.x/maskRect.z, + 0.0, 1.0/maskRect.w, 0.0, -maskRect.y/maskRect.w, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0); + + return float3(mul(transform, aPosition / aPosition.w).xy, 1.0) * aPosition.w; +} + +float2 UnitTriangleToPos(const float3 aVertex, + const float2 aPos1, + const float2 aPos2, + const float2 aPos3) +{ + return aVertex.x * aPos1 + + aVertex.y * aPos2 + + aVertex.z * aPos3; +} + +float2 UnitQuadToRect(const float2 aVertex, const float4 aRect) +{ + return float2(aRect.x + aVertex.x * aRect.z, aRect.y + aVertex.y * aRect.w); +} + +float ComputeDepth(float4 aPosition, float aSortIndex) +{ + // Note: this value should match ShaderDefinitionsMLGPU.h. + return ((aSortIndex + SortIndexOffset) / 1000000.0f) * aPosition.w; +} + +// Compute the world-space, screen-space, layer-space clip, and mask +// uv-coordinates given a layer-space vertex, id, and z-index. +VertexInfo ComputePosition(float2 aVertex, uint aLayerId, float aSortIndex) +{ + Layer layer = sLayers[aLayerId]; + + // Translate from unit vertex to layer quad vertex. + float4 clipRect = layer.clipRect; + + // Transform to screen coordinates. + float4x4 transform = layer.transform; + float4 layerPos = mul(transform, float4(aVertex, 0, 1)); + float4 position = layerPos; + position.xyz /= position.w; + position.xy -= RenderTargetOffset.xy; + position.xyz *= position.w; + + float4 worldPos = mul(WorldTransform, position); + + // Depth must be computed after the world transform, since we don't want + // 3d transforms clobbering the z-value. We assume a viewport culling + // everything outside of [0, 1). Note that when depth-testing, we do not + // use sorting indices < 1. + // + // Note that we have to normalize this value to w=1, since the GPU will + // divide all values by w internally. + worldPos.z = ComputeDepth(worldPos, aSortIndex); + + VertexInfo info; + info.screenPos = position.xy; + info.worldPos = worldPos; + info.maskCoords = ComputeMaskCoords(layerPos, layer); + info.clipRect = clipRect; + return info; +} + +// This function takes a unit quad position and a layer rectangle, and computes +// a clipped draw rect. It is only valid to use this function for layers with +// rectilinear transforms that do not have masks. +float4 ComputeClippedPosition(const float2 aVertex, + const float4 aRect, + uint aLayerId, + float aDepth) +{ + Layer layer = sLayers[aLayerId]; + + float4 position = float4(UnitQuadToRect(aVertex, aRect), 0, 1); + + float4x4 transform = layer.transform; + float4 clipRect = layer.clipRect; + + // Transform to screen coordinates. + // + // We clamp the draw rect to the clip. This lets us use faster shaders. + // For opaque shapes, it is necessary to do this anyway since we might + // otherwrite write transparent pixels in the pixel which would also be + // written to the depth buffer. We cannot use discard in the pixel shader + // as this would break early-z tests. + // + // Note that for some shaders, like textured shaders, it is not valid to + // change the draw rect like this without also clamping the texture + // coordinates. We take care to adjust for this in our batching code. + // + // We do not need to do this for 3D transforms since we always treat those + // as transparent (they are not written to the depth buffer). 3D items + // will always use the full clip+masking shader. + position = mul(transform, position); + position.xyz /= position.w; + position.xy -= RenderTargetOffset.xy; + position.xy = clamp(position.xy, clipRect.xy, clipRect.xy + clipRect.zw); + position.xyz *= position.w; + + float4 worldPos = mul(WorldTransform, position); + + // Depth must be computed after the world transform, since we don't want + // 3d transforms clobbering the z-value. We assume a viewport culling + // everything outside of [0, 1). Note that when depth-testing, we do not + // use sorting indices < 1. + // + // Note that we have to normalize this value to w=1, since the GPU will + // divide all values by w internally. + worldPos.z = ComputeDepth(worldPos, aDepth); + + return worldPos; +} + +#endif // mozilla_gfx_layers_d3d11_mlgshaders_common_vs_hlsl diff --git a/gfx/layers/d3d11/mlgshaders/common.hlsl b/gfx/layers/d3d11/mlgshaders/common.hlsl new file mode 100644 index 0000000000..8c51370d08 --- /dev/null +++ b/gfx/layers/d3d11/mlgshaders/common.hlsl @@ -0,0 +1,17 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_layers_d3d11_mlgshaders_common_hlsl +#define mozilla_gfx_layers_d3d11_mlgshaders_common_hlsl + +bool RectContainsPoint(float4 aRect, float2 aPoint) +{ + return aPoint.x >= aRect.x && + aPoint.y >= aRect.y && + aPoint.x <= (aRect.x + aRect.z) && + aPoint.y <= (aRect.y + aRect.w); +} + +#endif // mozilla_gfx_layers_d3d11_mlgshaders_common_hlsl diff --git a/gfx/layers/d3d11/mlgshaders/component-alpha-ps.hlsl b/gfx/layers/d3d11/mlgshaders/component-alpha-ps.hlsl new file mode 100644 index 0000000000..d1705dd1b9 --- /dev/null +++ b/gfx/layers/d3d11/mlgshaders/component-alpha-ps.hlsl @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "common.hlsl" +#include "common-ps.hlsl" +#include "textured-common.hlsl" + +Texture2D texOnBlack : register(ps, t0); +Texture2D texOnWhite : register(ps, t1); + +struct PS_OUTPUT { + float4 vSrc; + float4 vAlpha; +}; + +PS_OUTPUT ComponentAlphaQuadPS(const VS_SAMPLEOUTPUT_CLIPPED aInput) : SV_Target +{ + PS_OUTPUT result; + result.vSrc = texOnBlack.Sample(sSampler, aInput.vTexCoords); + result.vAlpha = 1.0 - texOnWhite.Sample(sSampler, aInput.vTexCoords) + result.vSrc; + result.vSrc.a = result.vAlpha.g; + result.vSrc *= sOpacity; + result.vAlpha *= sOpacity; + return result; +} + +PS_OUTPUT ComponentAlphaVertexPS(const VS_SAMPLEOUTPUT aInput) : SV_Target +{ + PS_OUTPUT result; + if (!RectContainsPoint(aInput.vClipRect, aInput.vPosition.xy)) { + result.vSrc = float4(0, 0, 0, 0); + result.vAlpha = float4(0, 0, 0, 0); + return result; + } + + float alpha = ReadMask(aInput.vMaskCoords); + + result.vSrc = texOnBlack.Sample(sSampler, aInput.vTexCoords); + result.vAlpha = 1.0 - texOnWhite.Sample(sSampler, aInput.vTexCoords) + result.vSrc; + result.vSrc.a = result.vAlpha.g; + result.vSrc *= alpha; + result.vAlpha *= alpha; + return result; +} diff --git a/gfx/layers/d3d11/mlgshaders/diagnostics-common.hlsl b/gfx/layers/d3d11/mlgshaders/diagnostics-common.hlsl new file mode 100644 index 0000000000..49ddcb3d99 --- /dev/null +++ b/gfx/layers/d3d11/mlgshaders/diagnostics-common.hlsl @@ -0,0 +1,10 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +struct VS_DIAGOUTPUT +{ + float4 vPosition : SV_Position; + float2 vTexCoord : TEXCOORD0; +}; diff --git a/gfx/layers/d3d11/mlgshaders/diagnostics-ps.hlsl b/gfx/layers/d3d11/mlgshaders/diagnostics-ps.hlsl new file mode 100644 index 0000000000..893e5e19b4 --- /dev/null +++ b/gfx/layers/d3d11/mlgshaders/diagnostics-ps.hlsl @@ -0,0 +1,13 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "common-ps.hlsl" +#include "diagnostics-common.hlsl" + +Texture2D sTexture: register(ps, t0); + +float4 DiagnosticTextPS(const VS_DIAGOUTPUT aInput) : SV_Target +{ + return sTexture.Sample(sSampler, aInput.vTexCoord); +} diff --git a/gfx/layers/d3d11/mlgshaders/diagnostics-vs.hlsl b/gfx/layers/d3d11/mlgshaders/diagnostics-vs.hlsl new file mode 100644 index 0000000000..e240b3c374 --- /dev/null +++ b/gfx/layers/d3d11/mlgshaders/diagnostics-vs.hlsl @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "common-vs.hlsl" +#include "textured-common.hlsl" +#include "diagnostics-common.hlsl" + +struct VS_DIAGINPUT +{ + float2 vPos : POSITION; + float4 vRect : TEXCOORD0; + float4 vTexCoords : TEXCOORD1; +}; + +VS_DIAGOUTPUT DiagnosticTextVS(const VS_DIAGINPUT aInput) +{ + float2 pos = UnitQuadToRect(aInput.vPos, aInput.vRect); + float2 texCoord = UnitQuadToRect(aInput.vPos, aInput.vTexCoords); + + VS_DIAGOUTPUT output; + output.vPosition = mul(WorldTransform, float4(pos, 0, 1)); + output.vTexCoord = texCoord; + return output; +} diff --git a/gfx/layers/d3d11/mlgshaders/mask-combiner-common.hlsl b/gfx/layers/d3d11/mlgshaders/mask-combiner-common.hlsl new file mode 100644 index 0000000000..a1201f7833 --- /dev/null +++ b/gfx/layers/d3d11/mlgshaders/mask-combiner-common.hlsl @@ -0,0 +1,10 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +struct VS_MASKOUTPUT +{ + float4 vPosition : SV_Position; + float2 vTexCoords : TEXCOORD0; +}; diff --git a/gfx/layers/d3d11/mlgshaders/mask-combiner-ps.hlsl b/gfx/layers/d3d11/mlgshaders/mask-combiner-ps.hlsl new file mode 100644 index 0000000000..35010aed6f --- /dev/null +++ b/gfx/layers/d3d11/mlgshaders/mask-combiner-ps.hlsl @@ -0,0 +1,16 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "common.hlsl" +#include "mask-combiner-common.hlsl" + +sampler sSampler; + +Texture2D tMaskTexture : register(ps, t0); + +float4 MaskCombinerPS(VS_MASKOUTPUT aInput) : SV_Target +{ + float4 value = tMaskTexture.Sample(sSampler, aInput.vTexCoords); + return float4(value.r, 0, 0, value.r); +} diff --git a/gfx/layers/d3d11/mlgshaders/mask-combiner-vs.hlsl b/gfx/layers/d3d11/mlgshaders/mask-combiner-vs.hlsl new file mode 100644 index 0000000000..7db1d5edca --- /dev/null +++ b/gfx/layers/d3d11/mlgshaders/mask-combiner-vs.hlsl @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "common-vs.hlsl" +#include "mask-combiner-common.hlsl" + +struct VS_MASKINPUT +{ + // Note, the input is + float2 vPos : POSITION; + float4 vTexCoords : POSITION1; +}; + +VS_MASKOUTPUT MaskCombinerVS(VS_MASKINPUT aInput) +{ + float4 position = float4( + aInput.vPos.x * 2.0f - 1.0f, + 1.0f - (aInput.vPos.y * 2.0f), + 0, 1); + + VS_MASKOUTPUT output; + output.vPosition = position; + output.vTexCoords = UnitQuadToRect(aInput.vPos, aInput.vTexCoords); + return output; +} diff --git a/gfx/layers/d3d11/mlgshaders/shaders.manifest b/gfx/layers/d3d11/mlgshaders/shaders.manifest new file mode 100644 index 0000000000..3c6b41a2c9 --- /dev/null +++ b/gfx/layers/d3d11/mlgshaders/shaders.manifest @@ -0,0 +1,100 @@ +- type: vs_4_0 + file: textured-vs.hlsl + shaders: + - TexturedQuadVS + - TexturedVertexVS + +- type: ps_4_0 + file: textured-ps.hlsl + shaders: + - TexturedVertexRGB + - TexturedVertexRGBA + - TexturedQuadRGB + - TexturedQuadRGBA + +- type: ps_4_0 + file: ycbcr-ps.hlsl + shaders: + - TexturedVertexIMC4 + - TexturedVertexNV12 + - TexturedQuadIMC4 + - TexturedQuadNV12 + - TexturedVertexIdentityIMC4 + - TexturedQuadIdentityIMC4 + +- type: vs_4_0 + file: color-vs.hlsl + shaders: + - ColoredQuadVS + - ColoredVertexVS + +- type: ps_4_0 + file: color-ps.hlsl + shaders: + - ColoredQuadPS + - ColoredVertexPS + +- type: ps_4_0 + file: component-alpha-ps.hlsl + shaders: + - ComponentAlphaQuadPS + - ComponentAlphaVertexPS + +- type: vs_4_0 + file: blend-vs.hlsl + shaders: + - BlendVertexVS + +- type: ps_4_0 + file: blend-ps.hlsl + shaders: + - BlendMultiplyPS + - BlendScreenPS + - BlendOverlayPS + - BlendDarkenPS + - BlendLightenPS + - BlendColorDodgePS + - BlendColorBurnPS + - BlendHardLightPS + - BlendSoftLightPS + - BlendDifferencePS + - BlendExclusionPS + - BlendHuePS + - BlendSaturationPS + - BlendColorPS + - BlendLuminosityPS + +- type: vs_4_0 + file: clear-vs.hlsl + shaders: + - ClearVS + +- type: ps_4_0 + file: clear-ps.hlsl + shaders: + - ClearPS + +- type: vs_4_0 + file: mask-combiner-vs.hlsl + shaders: + - MaskCombinerVS + +- type: ps_4_0 + file: mask-combiner-ps.hlsl + shaders: + - MaskCombinerPS + +- type: vs_4_0 + file: diagnostics-vs.hlsl + shaders: + - DiagnosticTextVS + +- type: ps_4_0 + file: diagnostics-ps.hlsl + shaders: + - DiagnosticTextPS + +- type: vs_4_0 + file: test-features-vs.hlsl + shaders: + - TestConstantBuffersVS diff --git a/gfx/layers/d3d11/mlgshaders/test-features-vs.hlsl b/gfx/layers/d3d11/mlgshaders/test-features-vs.hlsl new file mode 100644 index 0000000000..d22de5e3e4 --- /dev/null +++ b/gfx/layers/d3d11/mlgshaders/test-features-vs.hlsl @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "common-vs.hlsl" +#include "color-common.hlsl" + +struct VS_INPUT { + float2 vPos : POSITION; +}; + +cbuffer Buffer0 : register(b0) { + float4 aValue0; +}; +cbuffer Buffer1 : register(b1) { + float4 aValue1; +}; +cbuffer Buffer2 : register(b2) { + float4 aValue2; +}; + +VS_COLOROUTPUT_CLIPPED TestConstantBuffersVS(VS_INPUT aInput) +{ + // Draw to the entire viewport. + float2 pos = UnitQuadToRect(aInput.vPos, float4(-1, -1, 2, 2)); + + VS_COLOROUTPUT_CLIPPED output; + output.vPosition = float4(pos, 0, 1); + output.vColor = float4(aValue0.r, aValue1.g, aValue2.b, 1.0); + return output; +} diff --git a/gfx/layers/d3d11/mlgshaders/textured-common.hlsl b/gfx/layers/d3d11/mlgshaders/textured-common.hlsl new file mode 100644 index 0000000000..27c2b32198 --- /dev/null +++ b/gfx/layers/d3d11/mlgshaders/textured-common.hlsl @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Instanced version. +struct VS_TEXTUREDINPUT +{ + float2 vPos : POSITION; + float4 vRect : TEXCOORD0; + uint vLayerId : TEXCOORD1; + int vDepth : TEXCOORD2; + float4 vTexRect : TEXCOORD3; +}; + +// Non-instanced version. +struct VS_TEXTUREDVERTEX +{ + float3 vUnitPos : POSITION0; + float2 vPos1: POSITION1; + float2 vPos2: POSITION2; + float2 vPos3: POSITION3; + uint vLayerId : TEXCOORD0; + int vDepth : TEXCOORD1; + float2 vTexCoord1 : TEXCOORD2; + float2 vTexCoord2 : TEXCOORD3; + float2 vTexCoord3 : TEXCOORD4; +}; + +struct VS_SAMPLEOUTPUT +{ + float4 vPosition : SV_Position; + float2 vTexCoords : TEXCOORD0; + float2 vLocalPos : TEXCOORD1; + float3 vMaskCoords : TEXCOORD2; + nointerpolation float4 vClipRect : TEXCOORD3; +}; + +struct VS_SAMPLEOUTPUT_CLIPPED +{ + float4 vPosition : SV_Position; + float2 vTexCoords : TEXCOORD0; +}; diff --git a/gfx/layers/d3d11/mlgshaders/textured-ps.hlsl b/gfx/layers/d3d11/mlgshaders/textured-ps.hlsl new file mode 100644 index 0000000000..e00f6cbc44 --- /dev/null +++ b/gfx/layers/d3d11/mlgshaders/textured-ps.hlsl @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "common.hlsl" +#include "common-ps.hlsl" +#include "textured-common.hlsl" + +Texture2D simpleTex : register(ps, t0); + +float4 FixRGBOpacity(float4 color, float alpha) { + return float4(color.rgb * alpha, alpha); +} + +// Fast cases that don't require complex clipping. +float4 TexturedQuadRGBA(const VS_SAMPLEOUTPUT_CLIPPED aInput) : SV_Target +{ + return simpleTex.Sample(sSampler, aInput.vTexCoords) * sOpacity; +} +float4 TexturedQuadRGB(const VS_SAMPLEOUTPUT_CLIPPED aInput) : SV_Target +{ + return FixRGBOpacity(simpleTex.Sample(sSampler, aInput.vTexCoords), sOpacity); +} + +// PaintedLayer common case. +float4 TexturedVertexRGBA(const VS_SAMPLEOUTPUT aInput) : SV_Target +{ + if (!RectContainsPoint(aInput.vClipRect, aInput.vPosition.xy)) { + return float4(0, 0, 0, 0); + } + + float alpha = ReadMask(aInput.vMaskCoords); + return simpleTex.Sample(sSampler, aInput.vTexCoords) * alpha; +} + +// ImageLayers. +float4 TexturedVertexRGB(const VS_SAMPLEOUTPUT aInput) : SV_Target +{ + if (!RectContainsPoint(aInput.vClipRect, aInput.vPosition.xy)) { + return float4(0, 0, 0, 0); + } + + float alpha = ReadMask(aInput.vMaskCoords); + return FixRGBOpacity(simpleTex.Sample(sSampler, aInput.vTexCoords), alpha); +} diff --git a/gfx/layers/d3d11/mlgshaders/textured-vs.hlsl b/gfx/layers/d3d11/mlgshaders/textured-vs.hlsl new file mode 100644 index 0000000000..9475ec048d --- /dev/null +++ b/gfx/layers/d3d11/mlgshaders/textured-vs.hlsl @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "common-vs.hlsl" +#include "textured-common.hlsl" + +VS_SAMPLEOUTPUT TexturedQuadImpl(const VertexInfo aInfo, const float2 aTexCoord) +{ + VS_SAMPLEOUTPUT output; + output.vPosition = aInfo.worldPos; + output.vTexCoords = aTexCoord; + output.vLocalPos = aInfo.screenPos; + output.vClipRect = aInfo.clipRect; + output.vMaskCoords = aInfo.maskCoords; + return output; +} + +VS_SAMPLEOUTPUT_CLIPPED TexturedQuadVS(const VS_TEXTUREDINPUT aVertex) +{ + float4 worldPos = ComputeClippedPosition( + aVertex.vPos, + aVertex.vRect, + aVertex.vLayerId, + aVertex.vDepth); + + VS_SAMPLEOUTPUT_CLIPPED output; + output.vPosition = worldPos; + output.vTexCoords = UnitQuadToRect(aVertex.vPos, aVertex.vTexRect); + return output; +} + +VS_SAMPLEOUTPUT TexturedVertexVS(const VS_TEXTUREDVERTEX aVertex) +{ + float2 layerPos = UnitTriangleToPos( + aVertex.vUnitPos, + aVertex.vPos1, + aVertex.vPos2, + aVertex.vPos3); + + float2 texCoord = UnitTriangleToPos( + aVertex.vUnitPos, + aVertex.vTexCoord1, + aVertex.vTexCoord2, + aVertex.vTexCoord3); + + VertexInfo info = ComputePosition(layerPos, aVertex.vLayerId, aVertex.vDepth); + return TexturedQuadImpl(info, texCoord); +} diff --git a/gfx/layers/d3d11/mlgshaders/ycbcr-ps.hlsl b/gfx/layers/d3d11/mlgshaders/ycbcr-ps.hlsl new file mode 100644 index 0000000000..1a30e6cb5a --- /dev/null +++ b/gfx/layers/d3d11/mlgshaders/ycbcr-ps.hlsl @@ -0,0 +1,118 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "common-ps.hlsl" +#include "textured-common.hlsl" + +Texture2D tY : register(ps, t0); +Texture2D tCb : register(ps, t1); +Texture2D tCr : register(ps, t2); + +cbuffer YCbCrBuffer : register(b1) { + row_major float3x3 YuvColorMatrix; +}; + +cbuffer vCoefficientBuffer : register(b2) { + float vCoefficient; +} + +/* From Rec601: +[R] [1.1643835616438356, 0.0, 1.5960267857142858] [ Y - 16] +[G] = [1.1643835616438358, -0.3917622900949137, -0.8129676472377708] x [Cb - 128] +[B] [1.1643835616438356, 2.017232142857143, 8.862867620416422e-17] [Cr - 128] + +For [0,1] instead of [0,255], and to 5 places: +[R] [1.16438, 0.00000, 1.59603] [ Y - 0.06275] +[G] = [1.16438, -0.39176, -0.81297] x [Cb - 0.50196] +[B] [1.16438, 2.01723, 0.00000] [Cr - 0.50196] + +From Rec709: +[R] [1.1643835616438356, 4.2781193979771426e-17, 1.7927410714285714] [ Y - 16] +[G] = [1.1643835616438358, -0.21324861427372963, -0.532909328559444] x [Cb - 128] +[B] [1.1643835616438356, 2.1124017857142854, 0.0] [Cr - 128] + +For [0,1] instead of [0,255], and to 5 places: +[R] [1.16438, 0.00000, 1.79274] [ Y - 0.06275] +[G] = [1.16438, -0.21325, -0.53291] x [Cb - 0.50196] +[B] [1.16438, 2.11240, 0.00000] [Cr - 0.50196] +*/ +float4 CalculateYCbCrColor(float3 rgb) +{ + return float4( + mul(YuvColorMatrix, + float3( + rgb.r - 0.06275, + rgb.g - 0.50196, + rgb.b - 0.50196)), + 1.0); +} + +float4 CalculateIMC4Color(const float2 aTexCoords) +{ + float3 yuv = float3( + tY.Sample(sSampler, aTexCoords).r, + tCb.Sample(sSampler, aTexCoords).r, + tCr.Sample(sSampler, aTexCoords).r); + return CalculateYCbCrColor(yuv * vCoefficient); +} + +float4 CalculateNV12Color(const float2 aTexCoords) +{ + float y = tY.Sample(sSampler, aTexCoords).r; + float2 cbcr = tCb.Sample(sSampler, aTexCoords).rg; + return CalculateYCbCrColor(float3(y, cbcr) * vCoefficient); +} + +float4 TexturedQuadIMC4(const VS_SAMPLEOUTPUT_CLIPPED aInput) : SV_Target +{ + return CalculateIMC4Color(aInput.vTexCoords) * sOpacity; +} + +float4 TexturedQuadNV12(const VS_SAMPLEOUTPUT_CLIPPED aInput) : SV_Target +{ + return CalculateNV12Color(aInput.vTexCoords) * sOpacity; +} + +float4 TexturedVertexIMC4(const VS_SAMPLEOUTPUT aInput) : SV_Target +{ + if (!RectContainsPoint(aInput.vClipRect, aInput.vPosition.xy)) { + return float4(0, 0, 0, 0); + } + + float alpha = ReadMask(aInput.vMaskCoords); + return CalculateIMC4Color(aInput.vTexCoords) * alpha; +} + +float4 TexturedVertexNV12(const VS_SAMPLEOUTPUT aInput) : SV_Target +{ + if (!RectContainsPoint(aInput.vClipRect, aInput.vPosition.xy)) { + return float4(0, 0, 0, 0); + } + + float alpha = ReadMask(aInput.vMaskCoords); + return CalculateNV12Color(aInput.vTexCoords) * alpha; +} + +float4 TexturedQuadIdentityIMC4(const VS_SAMPLEOUTPUT_CLIPPED aInput) : SV_Target +{ + float3 rgb = float3( + tCr.Sample(sSampler, aInput.vTexCoords).r, + tY.Sample(sSampler, aInput.vTexCoords).r, + tCb.Sample(sSampler, aInput.vTexCoords).r); + return float4(rgb * vCoefficient, 1.0) * sOpacity; +} + +float4 TexturedVertexIdentityIMC4(const VS_SAMPLEOUTPUT aInput) : SV_Target +{ + if (!RectContainsPoint(aInput.vClipRect, aInput.vPosition.xy)) { + return float4(0, 0, 0, 0); + } + + float alpha = ReadMask(aInput.vMaskCoords); + float3 rgb = float3( + tCr.Sample(sSampler, aInput.vTexCoords).r, + tY.Sample(sSampler, aInput.vTexCoords).r, + tCb.Sample(sSampler, aInput.vTexCoords).r); + return float4(rgb * vCoefficient, 1.0) * alpha; +} diff --git a/gfx/layers/d3d11/shaders.manifest b/gfx/layers/d3d11/shaders.manifest new file mode 100644 index 0000000000..f1f43dd23a --- /dev/null +++ b/gfx/layers/d3d11/shaders.manifest @@ -0,0 +1,28 @@ +- type: vs_4_0_level_9_3 + file: CompositorD3D11.hlsl + shaders: + - LayerQuadVS + - LayerDynamicVS + - LayerQuadMaskVS + - LayerDynamicMaskVS + - LayerQuadBlendVS + - LayerQuadBlendMaskVS + - LayerDynamicBlendVS + - LayerDynamicBlendMaskVS + +- type: ps_4_0_level_9_3 + file: CompositorD3D11.hlsl + shaders: + - SolidColorShader + - RGBShader + - RGBAShader + - ComponentAlphaShader + - YCbCrShader + - NV12Shader + - SolidColorShaderMask + - RGBShaderMask + - RGBAShaderMask + - YCbCrShaderMask + - NV12ShaderMask + - ComponentAlphaShaderMask + - BlendShader diff --git a/gfx/layers/ipc/APZCTreeManagerChild.cpp b/gfx/layers/ipc/APZCTreeManagerChild.cpp new file mode 100644 index 0000000000..b9fc37efb9 --- /dev/null +++ b/gfx/layers/ipc/APZCTreeManagerChild.cpp @@ -0,0 +1,181 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/APZCTreeManagerChild.h" + +#include "InputData.h" // for InputData +#include "mozilla/dom/BrowserParent.h" // for BrowserParent +#include "mozilla/layers/APZCCallbackHelper.h" // for APZCCallbackHelper +#include "mozilla/layers/APZInputBridgeChild.h" // for APZInputBridgeChild +#include "mozilla/layers/GeckoContentController.h" // for GeckoContentController +#include "mozilla/layers/RemoteCompositorSession.h" // for RemoteCompositorSession + +namespace mozilla { +namespace layers { + +APZCTreeManagerChild::APZCTreeManagerChild() + : mCompositorSession(nullptr), mIPCOpen(false) {} + +APZCTreeManagerChild::~APZCTreeManagerChild() = default; + +void APZCTreeManagerChild::SetCompositorSession( + RemoteCompositorSession* aSession) { + // Exactly one of mCompositorSession and aSession must be null (i.e. either + // we're setting mCompositorSession or we're clearing it). + MOZ_ASSERT(!mCompositorSession ^ !aSession); + mCompositorSession = aSession; +} + +void APZCTreeManagerChild::SetInputBridge(APZInputBridgeChild* aInputBridge) { + // The input bridge only exists from the UI process to the GPU process. + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(!mInputBridge); + + mInputBridge = aInputBridge; +} + +void APZCTreeManagerChild::Destroy() { + MOZ_ASSERT(NS_IsMainThread()); + if (mInputBridge) { + mInputBridge->Destroy(); + mInputBridge = nullptr; + } +} + +void APZCTreeManagerChild::SetKeyboardMap(const KeyboardMap& aKeyboardMap) { + SendSetKeyboardMap(aKeyboardMap); +} + +void APZCTreeManagerChild::ZoomToRect(const ScrollableLayerGuid& aGuid, + const CSSRect& aRect, + const uint32_t aFlags) { + SendZoomToRect(aGuid, aRect, aFlags); +} + +void APZCTreeManagerChild::ContentReceivedInputBlock(uint64_t aInputBlockId, + bool aPreventDefault) { + SendContentReceivedInputBlock(aInputBlockId, aPreventDefault); +} + +void APZCTreeManagerChild::SetTargetAPZC( + uint64_t aInputBlockId, const nsTArray& aTargets) { + SendSetTargetAPZC(aInputBlockId, aTargets); +} + +void APZCTreeManagerChild::UpdateZoomConstraints( + const ScrollableLayerGuid& aGuid, + const Maybe& aConstraints) { + if (mIPCOpen) { + SendUpdateZoomConstraints(aGuid, aConstraints); + } +} + +void APZCTreeManagerChild::SetDPI(float aDpiValue) { SendSetDPI(aDpiValue); } + +void APZCTreeManagerChild::SetAllowedTouchBehavior( + uint64_t aInputBlockId, const nsTArray& aValues) { + SendSetAllowedTouchBehavior(aInputBlockId, aValues); +} + +void APZCTreeManagerChild::StartScrollbarDrag( + const ScrollableLayerGuid& aGuid, const AsyncDragMetrics& aDragMetrics) { + SendStartScrollbarDrag(aGuid, aDragMetrics); +} + +bool APZCTreeManagerChild::StartAutoscroll(const ScrollableLayerGuid& aGuid, + const ScreenPoint& aAnchorLocation) { + return SendStartAutoscroll(aGuid, aAnchorLocation); +} + +void APZCTreeManagerChild::StopAutoscroll(const ScrollableLayerGuid& aGuid) { + SendStopAutoscroll(aGuid); +} + +void APZCTreeManagerChild::SetLongTapEnabled(bool aTapGestureEnabled) { + SendSetLongTapEnabled(aTapGestureEnabled); +} + +APZInputBridge* APZCTreeManagerChild::InputBridge() { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(mInputBridge); + + return mInputBridge.get(); +} + +void APZCTreeManagerChild::AddInputBlockCallback( + uint64_t aInputBlockId, InputBlockCallback&& aCallback) { + MOZ_RELEASE_ASSERT(false, + "Remoting of input block callbacks is not implemented"); +} + +void APZCTreeManagerChild::AddIPDLReference() { + MOZ_ASSERT(mIPCOpen == false); + mIPCOpen = true; + AddRef(); +} + +void APZCTreeManagerChild::ReleaseIPDLReference() { + mIPCOpen = false; + Release(); +} + +void APZCTreeManagerChild::ActorDestroy(ActorDestroyReason aWhy) { + mIPCOpen = false; +} + +mozilla::ipc::IPCResult APZCTreeManagerChild::RecvHandleTap( + const TapType& aType, const LayoutDevicePoint& aPoint, + const Modifiers& aModifiers, const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId) { + MOZ_ASSERT(XRE_IsParentProcess()); + if (mCompositorSession && + mCompositorSession->RootLayerTreeId() == aGuid.mLayersId && + mCompositorSession->GetContentController()) { + RefPtr controller = + mCompositorSession->GetContentController(); + controller->HandleTap(aType, aPoint, aModifiers, aGuid, aInputBlockId); + return IPC_OK(); + } + dom::BrowserParent* tab = + dom::BrowserParent::GetBrowserParentFromLayersId(aGuid.mLayersId); + if (tab) { + tab->SendHandleTap(aType, aPoint, aModifiers, aGuid, aInputBlockId); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult APZCTreeManagerChild::RecvNotifyPinchGesture( + const PinchGestureType& aType, const ScrollableLayerGuid& aGuid, + const LayoutDevicePoint& aFocusPoint, const LayoutDeviceCoord& aSpanChange, + const Modifiers& aModifiers) { + // This will only get sent from the GPU process to the parent process, so + // this function should never get called in the content process. + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + + // We want to handle it in this process regardless of what the target guid + // of the pinch is. This may change in the future. + if (mCompositorSession && mCompositorSession->GetWidget()) { + APZCCallbackHelper::NotifyPinchGesture(aType, aFocusPoint, aSpanChange, + aModifiers, + mCompositorSession->GetWidget()); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult APZCTreeManagerChild::RecvCancelAutoscroll( + const ScrollableLayerGuid::ViewID& aScrollId) { + // This will only get sent from the GPU process to the parent process, so + // this function should never get called in the content process. + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + + APZCCallbackHelper::CancelAutoscroll(aScrollId); + return IPC_OK(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/APZCTreeManagerChild.h b/gfx/layers/ipc/APZCTreeManagerChild.h new file mode 100644 index 0000000000..dcba794bfa --- /dev/null +++ b/gfx/layers/ipc/APZCTreeManagerChild.h @@ -0,0 +1,99 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_APZCTreeManagerChild_h +#define mozilla_layers_APZCTreeManagerChild_h + +#include "mozilla/layers/APZInputBridge.h" +#include "mozilla/layers/IAPZCTreeManager.h" +#include "mozilla/layers/PAPZCTreeManagerChild.h" + +namespace mozilla { +namespace layers { + +class APZInputBridgeChild; +class RemoteCompositorSession; + +class APZCTreeManagerChild : public IAPZCTreeManager, + public PAPZCTreeManagerChild { + friend class PAPZCTreeManagerChild; + using TapType = GeckoContentController_TapType; + + public: + APZCTreeManagerChild(); + + void SetCompositorSession(RemoteCompositorSession* aSession); + void SetInputBridge(APZInputBridgeChild* aInputBridge); + void Destroy(); + + void SetKeyboardMap(const KeyboardMap& aKeyboardMap) override; + + void ZoomToRect(const ScrollableLayerGuid& aGuid, const CSSRect& aRect, + const uint32_t aFlags = DEFAULT_BEHAVIOR) override; + + void ContentReceivedInputBlock(uint64_t aInputBlockId, + bool aPreventDefault) override; + + void SetTargetAPZC(uint64_t aInputBlockId, + const nsTArray& aTargets) override; + + void UpdateZoomConstraints( + const ScrollableLayerGuid& aGuid, + const Maybe& aConstraints) override; + + void SetDPI(float aDpiValue) override; + + void SetAllowedTouchBehavior( + uint64_t aInputBlockId, + const nsTArray& aValues) override; + + void StartScrollbarDrag(const ScrollableLayerGuid& aGuid, + const AsyncDragMetrics& aDragMetrics) override; + + bool StartAutoscroll(const ScrollableLayerGuid& aGuid, + const ScreenPoint& aAnchorLocation) override; + + void StopAutoscroll(const ScrollableLayerGuid& aGuid) override; + + void SetLongTapEnabled(bool aTapGestureEnabled) override; + + APZInputBridge* InputBridge() override; + + void AddInputBlockCallback(uint64_t aInputBlockId, + InputBlockCallback&& aCallback) override; + + void AddIPDLReference(); + void ReleaseIPDLReference(); + void ActorDestroy(ActorDestroyReason aWhy) override; + + protected: + MOZ_CAN_RUN_SCRIPT_BOUNDARY + mozilla::ipc::IPCResult RecvHandleTap(const TapType& aType, + const LayoutDevicePoint& aPoint, + const Modifiers& aModifiers, + const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId); + + mozilla::ipc::IPCResult RecvNotifyPinchGesture( + const PinchGestureType& aType, const ScrollableLayerGuid& aGuid, + const LayoutDevicePoint& aFocusPoint, + const LayoutDeviceCoord& aSpanChange, const Modifiers& aModifiers); + + mozilla::ipc::IPCResult RecvCancelAutoscroll( + const ScrollableLayerGuid::ViewID& aScrollId); + + virtual ~APZCTreeManagerChild(); + + private: + MOZ_NON_OWNING_REF RemoteCompositorSession* mCompositorSession; + RefPtr mInputBridge; + bool mIPCOpen; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_APZCTreeManagerChild_h diff --git a/gfx/layers/ipc/APZCTreeManagerParent.cpp b/gfx/layers/ipc/APZCTreeManagerParent.cpp new file mode 100644 index 0000000000..1dcd3fc275 --- /dev/null +++ b/gfx/layers/ipc/APZCTreeManagerParent.cpp @@ -0,0 +1,185 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/APZCTreeManagerParent.h" + +#include "apz/src/APZCTreeManager.h" +#include "mozilla/layers/APZThreadUtils.h" +#include "mozilla/layers/APZUpdater.h" + +namespace mozilla { +namespace layers { + +APZCTreeManagerParent::APZCTreeManagerParent( + LayersId aLayersId, RefPtr aAPZCTreeManager, + RefPtr aAPZUpdater) + : mLayersId(aLayersId), + mTreeManager(std::move(aAPZCTreeManager)), + mUpdater(std::move(aAPZUpdater)) { + MOZ_ASSERT(mTreeManager != nullptr); + MOZ_ASSERT(mUpdater != nullptr); + MOZ_ASSERT(mUpdater->HasTreeManager(mTreeManager)); +} + +APZCTreeManagerParent::~APZCTreeManagerParent() = default; + +void APZCTreeManagerParent::ChildAdopted( + RefPtr aAPZCTreeManager, RefPtr aAPZUpdater) { + MOZ_ASSERT(aAPZCTreeManager != nullptr); + MOZ_ASSERT(aAPZUpdater != nullptr); + MOZ_ASSERT(aAPZUpdater->HasTreeManager(aAPZCTreeManager)); + mTreeManager = std::move(aAPZCTreeManager); + mUpdater = std::move(aAPZUpdater); +} + +mozilla::ipc::IPCResult APZCTreeManagerParent::RecvSetKeyboardMap( + const KeyboardMap& aKeyboardMap) { + mUpdater->RunOnControllerThread( + mLayersId, NewRunnableMethod( + "layers::IAPZCTreeManager::SetKeyboardMap", mTreeManager, + &IAPZCTreeManager::SetKeyboardMap, aKeyboardMap)); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult APZCTreeManagerParent::RecvZoomToRect( + const ScrollableLayerGuid& aGuid, const CSSRect& aRect, + const uint32_t& aFlags) { + if (!IsGuidValid(aGuid)) { + return IPC_FAIL_NO_REASON(this); + } + + mUpdater->RunOnControllerThread( + aGuid.mLayersId, + NewRunnableMethod( + "layers::IAPZCTreeManager::ZoomToRect", mTreeManager, + &IAPZCTreeManager::ZoomToRect, aGuid, aRect, aFlags)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult APZCTreeManagerParent::RecvContentReceivedInputBlock( + const uint64_t& aInputBlockId, const bool& aPreventDefault) { + mUpdater->RunOnControllerThread( + mLayersId, NewRunnableMethod( + "layers::IAPZCTreeManager::ContentReceivedInputBlock", + mTreeManager, &IAPZCTreeManager::ContentReceivedInputBlock, + aInputBlockId, aPreventDefault)); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult APZCTreeManagerParent::RecvSetTargetAPZC( + const uint64_t& aInputBlockId, nsTArray&& aTargets) { + mUpdater->RunOnControllerThread( + mLayersId, + NewRunnableMethod>>( + "layers::IAPZCTreeManager::SetTargetAPZC", mTreeManager, + &IAPZCTreeManager::SetTargetAPZC, aInputBlockId, + std::move(aTargets))); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult APZCTreeManagerParent::RecvUpdateZoomConstraints( + const ScrollableLayerGuid& aGuid, + const MaybeZoomConstraints& aConstraints) { + if (!IsGuidValid(aGuid)) { + return IPC_FAIL_NO_REASON(this); + } + + mTreeManager->UpdateZoomConstraints(aGuid, aConstraints); + return IPC_OK(); +} + +mozilla::ipc::IPCResult APZCTreeManagerParent::RecvSetDPI( + const float& aDpiValue) { + mUpdater->RunOnControllerThread( + mLayersId, + NewRunnableMethod("layers::IAPZCTreeManager::SetDPI", mTreeManager, + &IAPZCTreeManager::SetDPI, aDpiValue)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult APZCTreeManagerParent::RecvSetAllowedTouchBehavior( + const uint64_t& aInputBlockId, nsTArray&& aValues) { + mUpdater->RunOnControllerThread( + mLayersId, + NewRunnableMethod>>( + "layers::IAPZCTreeManager::SetAllowedTouchBehavior", mTreeManager, + &IAPZCTreeManager::SetAllowedTouchBehavior, aInputBlockId, + std::move(aValues))); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult APZCTreeManagerParent::RecvStartScrollbarDrag( + const ScrollableLayerGuid& aGuid, const AsyncDragMetrics& aDragMetrics) { + if (!IsGuidValid(aGuid)) { + return IPC_FAIL_NO_REASON(this); + } + + mUpdater->RunOnControllerThread( + aGuid.mLayersId, + NewRunnableMethod( + "layers::IAPZCTreeManager::StartScrollbarDrag", mTreeManager, + &IAPZCTreeManager::StartScrollbarDrag, aGuid, aDragMetrics)); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult APZCTreeManagerParent::RecvStartAutoscroll( + const ScrollableLayerGuid& aGuid, const ScreenPoint& aAnchorLocation) { + // Unlike RecvStartScrollbarDrag(), this message comes from the parent + // process (via nsBaseWidget::mAPZC) rather than from the child process + // (via BrowserChild::mApzcTreeManager), so there is no need to check the + // layers id against mLayersId (and in any case, it wouldn't match, because + // mLayersId stores the parent process's layers id, while nsBaseWidget is + // sending the child process's layers id). + + mUpdater->RunOnControllerThread( + mLayersId, + NewRunnableMethod( + "layers::IAPZCTreeManager::StartAutoscroll", mTreeManager, + &IAPZCTreeManager::StartAutoscroll, aGuid, aAnchorLocation)); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult APZCTreeManagerParent::RecvStopAutoscroll( + const ScrollableLayerGuid& aGuid) { + // See RecvStartAutoscroll() for why we don't check the layers id. + + mUpdater->RunOnControllerThread( + mLayersId, NewRunnableMethod( + "layers::IAPZCTreeManager::StopAutoscroll", mTreeManager, + &IAPZCTreeManager::StopAutoscroll, aGuid)); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult APZCTreeManagerParent::RecvSetLongTapEnabled( + const bool& aLongTapEnabled) { + mUpdater->RunOnControllerThread( + mLayersId, + NewRunnableMethod( + "layers::IAPZCTreeManager::SetLongTapEnabled", mTreeManager, + &IAPZCTreeManager::SetLongTapEnabled, aLongTapEnabled)); + + return IPC_OK(); +} + +bool APZCTreeManagerParent::IsGuidValid(const ScrollableLayerGuid& aGuid) { + if (aGuid.mLayersId != mLayersId) { + NS_ERROR("Unexpected layers id"); + return false; + } + return true; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/APZCTreeManagerParent.h b/gfx/layers/ipc/APZCTreeManagerParent.h new file mode 100644 index 0000000000..de54d4dbfd --- /dev/null +++ b/gfx/layers/ipc/APZCTreeManagerParent.h @@ -0,0 +1,78 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_APZCTreeManagerParent_h +#define mozilla_layers_APZCTreeManagerParent_h + +#include "mozilla/layers/PAPZCTreeManagerParent.h" + +namespace mozilla { +namespace layers { + +class APZCTreeManager; +class APZUpdater; + +class APZCTreeManagerParent : public PAPZCTreeManagerParent { + public: + APZCTreeManagerParent(LayersId aLayersId, + RefPtr aAPZCTreeManager, + RefPtr mAPZUpdater); + virtual ~APZCTreeManagerParent(); + + LayersId GetLayersId() const { return mLayersId; } + + /** + * Called when the layer tree that this protocol is connected to + * is adopted by another compositor, and we need to switch APZCTreeManagers. + */ + void ChildAdopted(RefPtr aAPZCTreeManager, + RefPtr aAPZUpdater); + + mozilla::ipc::IPCResult RecvSetKeyboardMap(const KeyboardMap& aKeyboardMap); + + mozilla::ipc::IPCResult RecvZoomToRect(const ScrollableLayerGuid& aGuid, + const CSSRect& aRect, + const uint32_t& aFlags); + + mozilla::ipc::IPCResult RecvContentReceivedInputBlock( + const uint64_t& aInputBlockId, const bool& aPreventDefault); + + mozilla::ipc::IPCResult RecvSetTargetAPZC( + const uint64_t& aInputBlockId, nsTArray&& aTargets); + + mozilla::ipc::IPCResult RecvUpdateZoomConstraints( + const ScrollableLayerGuid& aGuid, + const MaybeZoomConstraints& aConstraints); + + mozilla::ipc::IPCResult RecvSetDPI(const float& aDpiValue); + + mozilla::ipc::IPCResult RecvSetAllowedTouchBehavior( + const uint64_t& aInputBlockId, nsTArray&& aValues); + + mozilla::ipc::IPCResult RecvStartScrollbarDrag( + const ScrollableLayerGuid& aGuid, const AsyncDragMetrics& aDragMetrics); + + mozilla::ipc::IPCResult RecvStartAutoscroll( + const ScrollableLayerGuid& aGuid, const ScreenPoint& aAnchorLocation); + + mozilla::ipc::IPCResult RecvStopAutoscroll(const ScrollableLayerGuid& aGuid); + + mozilla::ipc::IPCResult RecvSetLongTapEnabled(const bool& aTapGestureEnabled); + + void ActorDestroy(ActorDestroyReason aWhy) override {} + + private: + bool IsGuidValid(const ScrollableLayerGuid& aGuid); + + LayersId mLayersId; + RefPtr mTreeManager; + RefPtr mUpdater; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_APZCTreeManagerParent_h diff --git a/gfx/layers/ipc/APZChild.cpp b/gfx/layers/ipc/APZChild.cpp new file mode 100644 index 0000000000..a1754e1613 --- /dev/null +++ b/gfx/layers/ipc/APZChild.cpp @@ -0,0 +1,105 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/APZChild.h" +#include "mozilla/layers/GeckoContentController.h" + +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/layers/APZCCallbackHelper.h" + +#include "InputData.h" // for InputData + +namespace mozilla { +namespace layers { + +APZChild::APZChild(RefPtr aController) + : mController(aController) { + MOZ_ASSERT(mController); +} + +APZChild::~APZChild() { + if (mController) { + mController->Destroy(); + mController = nullptr; + } +} + +mozilla::ipc::IPCResult APZChild::RecvLayerTransforms( + nsTArray&& aTransforms) { + mController->NotifyLayerTransforms(std::move(aTransforms)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult APZChild::RecvRequestContentRepaint( + const RepaintRequest& aRequest) { + MOZ_ASSERT(mController->IsRepaintThread()); + + mController->RequestContentRepaint(aRequest); + return IPC_OK(); +} + +mozilla::ipc::IPCResult APZChild::RecvUpdateOverscrollVelocity( + const ScrollableLayerGuid& aGuid, const float& aX, const float& aY, + const bool& aIsRootContent) { + mController->UpdateOverscrollVelocity(aGuid, aX, aY, aIsRootContent); + return IPC_OK(); +} + +mozilla::ipc::IPCResult APZChild::RecvUpdateOverscrollOffset( + const ScrollableLayerGuid& aGuid, const float& aX, const float& aY, + const bool& aIsRootContent) { + mController->UpdateOverscrollOffset(aGuid, aX, aY, aIsRootContent); + return IPC_OK(); +} + +mozilla::ipc::IPCResult APZChild::RecvNotifyMozMouseScrollEvent( + const ViewID& aScrollId, const nsString& aEvent) { + mController->NotifyMozMouseScrollEvent(aScrollId, aEvent); + return IPC_OK(); +} + +mozilla::ipc::IPCResult APZChild::RecvNotifyAPZStateChange( + const ScrollableLayerGuid& aGuid, const APZStateChange& aChange, + const int& aArg) { + mController->NotifyAPZStateChange(aGuid, aChange, aArg); + return IPC_OK(); +} + +mozilla::ipc::IPCResult APZChild::RecvNotifyFlushComplete() { + MOZ_ASSERT(mController->IsRepaintThread()); + + mController->NotifyFlushComplete(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult APZChild::RecvNotifyAsyncScrollbarDragInitiated( + const uint64_t& aDragBlockId, const ViewID& aScrollId, + const ScrollDirection& aDirection) { + mController->NotifyAsyncScrollbarDragInitiated(aDragBlockId, aScrollId, + aDirection); + return IPC_OK(); +} + +mozilla::ipc::IPCResult APZChild::RecvNotifyAsyncScrollbarDragRejected( + const ViewID& aScrollId) { + mController->NotifyAsyncScrollbarDragRejected(aScrollId); + return IPC_OK(); +} + +mozilla::ipc::IPCResult APZChild::RecvNotifyAsyncAutoscrollRejected( + const ViewID& aScrollId) { + mController->NotifyAsyncAutoscrollRejected(aScrollId); + return IPC_OK(); +} + +mozilla::ipc::IPCResult APZChild::RecvDestroy() { + // mController->Destroy will be called in the destructor + PAPZChild::Send__delete__(this); + return IPC_OK(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/APZChild.h b/gfx/layers/ipc/APZChild.h new file mode 100644 index 0000000000..a87fd7b7aa --- /dev/null +++ b/gfx/layers/ipc/APZChild.h @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_APZChild_h +#define mozilla_layers_APZChild_h + +#include "mozilla/layers/PAPZChild.h" + +namespace mozilla { +namespace layers { + +class GeckoContentController; + +/** + * APZChild implements PAPZChild and is used to remote a GeckoContentController + * that lives in a different process than where APZ lives. + */ +class APZChild final : public PAPZChild { + public: + using APZStateChange = GeckoContentController_APZStateChange; + + explicit APZChild(RefPtr aController); + virtual ~APZChild(); + + mozilla::ipc::IPCResult RecvLayerTransforms( + nsTArray&& aTransforms); + + mozilla::ipc::IPCResult RecvRequestContentRepaint( + const RepaintRequest& aRequest); + + mozilla::ipc::IPCResult RecvUpdateOverscrollVelocity( + const ScrollableLayerGuid& aGuid, const float& aX, const float& aY, + const bool& aIsRootContent); + + mozilla::ipc::IPCResult RecvUpdateOverscrollOffset( + const ScrollableLayerGuid& aGuid, const float& aX, const float& aY, + const bool& aIsRootContent); + + mozilla::ipc::IPCResult RecvNotifyMozMouseScrollEvent(const ViewID& aScrollId, + const nsString& aEvent); + + mozilla::ipc::IPCResult RecvNotifyAPZStateChange( + const ScrollableLayerGuid& aGuid, const APZStateChange& aChange, + const int& aArg); + + mozilla::ipc::IPCResult RecvNotifyFlushComplete(); + + mozilla::ipc::IPCResult RecvNotifyAsyncScrollbarDragInitiated( + const uint64_t& aDragBlockId, const ViewID& aScrollId, + const ScrollDirection& aDirection); + mozilla::ipc::IPCResult RecvNotifyAsyncScrollbarDragRejected( + const ViewID& aScrollId); + + mozilla::ipc::IPCResult RecvNotifyAsyncAutoscrollRejected( + const ViewID& aScrollId); + + mozilla::ipc::IPCResult RecvDestroy(); + + private: + RefPtr mController; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_APZChild_h diff --git a/gfx/layers/ipc/APZInputBridgeChild.cpp b/gfx/layers/ipc/APZInputBridgeChild.cpp new file mode 100644 index 0000000000..d1bb57a52c --- /dev/null +++ b/gfx/layers/ipc/APZInputBridgeChild.cpp @@ -0,0 +1,124 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/APZInputBridgeChild.h" + +#include "InputData.h" // for InputData, etc + +namespace mozilla { +namespace layers { + +APZInputBridgeChild::APZInputBridgeChild() : mDestroyed(false) { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); +} + +APZInputBridgeChild::~APZInputBridgeChild() = default; + +void APZInputBridgeChild::Destroy() { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + + if (mDestroyed) { + return; + } + + Send__delete__(this); + mDestroyed = true; +} + +void APZInputBridgeChild::ActorDestroy(ActorDestroyReason aWhy) { + mDestroyed = true; +} + +APZEventResult APZInputBridgeChild::ReceiveInputEvent(InputData& aEvent) { + APZEventResult res; + switch (aEvent.mInputType) { + case MULTITOUCH_INPUT: { + MultiTouchInput& event = aEvent.AsMultiTouchInput(); + MultiTouchInput processedEvent; + + SendReceiveMultiTouchInputEvent(event, &res, &processedEvent); + + event = processedEvent; + return res; + } + case MOUSE_INPUT: { + MouseInput& event = aEvent.AsMouseInput(); + MouseInput processedEvent; + + SendReceiveMouseInputEvent(event, &res, &processedEvent); + + event = processedEvent; + return res; + } + case PANGESTURE_INPUT: { + PanGestureInput& event = aEvent.AsPanGestureInput(); + PanGestureInput processedEvent; + + SendReceivePanGestureInputEvent(event, &res, &processedEvent); + + event = processedEvent; + return res; + } + case PINCHGESTURE_INPUT: { + PinchGestureInput& event = aEvent.AsPinchGestureInput(); + PinchGestureInput processedEvent; + + SendReceivePinchGestureInputEvent(event, &res, &processedEvent); + + event = processedEvent; + return res; + } + case TAPGESTURE_INPUT: { + TapGestureInput& event = aEvent.AsTapGestureInput(); + TapGestureInput processedEvent; + + SendReceiveTapGestureInputEvent(event, &res, &processedEvent); + + event = processedEvent; + return res; + } + case SCROLLWHEEL_INPUT: { + ScrollWheelInput& event = aEvent.AsScrollWheelInput(); + ScrollWheelInput processedEvent; + + SendReceiveScrollWheelInputEvent(event, &res, &processedEvent); + + event = processedEvent; + return res; + } + case KEYBOARD_INPUT: { + KeyboardInput& event = aEvent.AsKeyboardInput(); + KeyboardInput processedEvent; + + SendReceiveKeyboardInputEvent(event, &res, &processedEvent); + + event = processedEvent; + return res; + } + default: { + MOZ_ASSERT_UNREACHABLE("Invalid InputData type."); + res.mStatus = nsEventStatus_eConsumeNoDefault; + return res; + } + } +} + +void APZInputBridgeChild::ProcessUnhandledEvent( + LayoutDeviceIntPoint* aRefPoint, ScrollableLayerGuid* aOutTargetGuid, + uint64_t* aOutFocusSequenceNumber, LayersId* aOutLayersId) { + SendProcessUnhandledEvent(*aRefPoint, aRefPoint, aOutTargetGuid, + aOutFocusSequenceNumber, aOutLayersId); +} + +void APZInputBridgeChild::UpdateWheelTransaction(LayoutDeviceIntPoint aRefPoint, + EventMessage aEventMessage) { + SendUpdateWheelTransaction(aRefPoint, aEventMessage); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/APZInputBridgeChild.h b/gfx/layers/ipc/APZInputBridgeChild.h new file mode 100644 index 0000000000..88e1d28c0a --- /dev/null +++ b/gfx/layers/ipc/APZInputBridgeChild.h @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_APZInputBridgeChild_h +#define mozilla_layers_APZInputBridgeChild_h + +#include "mozilla/layers/APZInputBridge.h" +#include "mozilla/layers/PAPZInputBridgeChild.h" + +namespace mozilla { +namespace layers { + +class APZInputBridgeChild : public PAPZInputBridgeChild, public APZInputBridge { + NS_INLINE_DECL_REFCOUNTING(APZInputBridgeChild, final) + + public: + APZInputBridgeChild(); + void Destroy(); + + APZEventResult ReceiveInputEvent(InputData& aEvent) override; + + protected: + void ProcessUnhandledEvent(LayoutDeviceIntPoint* aRefPoint, + ScrollableLayerGuid* aOutTargetGuid, + uint64_t* aOutFocusSequenceNumber, + LayersId* aOutLayersId) override; + + void UpdateWheelTransaction(LayoutDeviceIntPoint aRefPoint, + EventMessage aEventMessage) override; + + void ActorDestroy(ActorDestroyReason aWhy) override; + virtual ~APZInputBridgeChild(); + + private: + bool mDestroyed; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_APZInputBridgeChild_h diff --git a/gfx/layers/ipc/APZInputBridgeParent.cpp b/gfx/layers/ipc/APZInputBridgeParent.cpp new file mode 100644 index 0000000000..23fcb1cf88 --- /dev/null +++ b/gfx/layers/ipc/APZInputBridgeParent.cpp @@ -0,0 +1,128 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/APZInputBridgeParent.h" + +#include "mozilla/layers/APZInputBridge.h" +#include "mozilla/layers/CompositorBridgeParent.h" +#include "mozilla/layers/IAPZCTreeManager.h" +#include "InputData.h" + +namespace mozilla { +namespace layers { + +APZInputBridgeParent::APZInputBridgeParent(const LayersId& aLayersId) { + MOZ_ASSERT(XRE_IsGPUProcess()); + MOZ_ASSERT(NS_IsMainThread()); + + mTreeManager = CompositorBridgeParent::GetAPZCTreeManager(aLayersId); + MOZ_ASSERT(mTreeManager); +} + +APZInputBridgeParent::~APZInputBridgeParent() = default; + +mozilla::ipc::IPCResult APZInputBridgeParent::RecvReceiveMultiTouchInputEvent( + const MultiTouchInput& aEvent, APZEventResult* aOutResult, + MultiTouchInput* aOutEvent) { + MultiTouchInput event = aEvent; + + *aOutResult = mTreeManager->InputBridge()->ReceiveInputEvent(event); + *aOutEvent = event; + + return IPC_OK(); +} + +mozilla::ipc::IPCResult APZInputBridgeParent::RecvReceiveMouseInputEvent( + const MouseInput& aEvent, APZEventResult* aOutResult, + MouseInput* aOutEvent) { + MouseInput event = aEvent; + + *aOutResult = mTreeManager->InputBridge()->ReceiveInputEvent(event); + *aOutEvent = event; + + return IPC_OK(); +} + +mozilla::ipc::IPCResult APZInputBridgeParent::RecvReceivePanGestureInputEvent( + const PanGestureInput& aEvent, APZEventResult* aOutResult, + PanGestureInput* aOutEvent) { + PanGestureInput event = aEvent; + + *aOutResult = mTreeManager->InputBridge()->ReceiveInputEvent(event); + *aOutEvent = event; + + return IPC_OK(); +} + +mozilla::ipc::IPCResult APZInputBridgeParent::RecvReceivePinchGestureInputEvent( + const PinchGestureInput& aEvent, APZEventResult* aOutResult, + PinchGestureInput* aOutEvent) { + PinchGestureInput event = aEvent; + + *aOutResult = mTreeManager->InputBridge()->ReceiveInputEvent(event); + *aOutEvent = event; + + return IPC_OK(); +} + +mozilla::ipc::IPCResult APZInputBridgeParent::RecvReceiveTapGestureInputEvent( + const TapGestureInput& aEvent, APZEventResult* aOutResult, + TapGestureInput* aOutEvent) { + TapGestureInput event = aEvent; + + *aOutResult = mTreeManager->InputBridge()->ReceiveInputEvent(event); + *aOutEvent = event; + + return IPC_OK(); +} + +mozilla::ipc::IPCResult APZInputBridgeParent::RecvReceiveScrollWheelInputEvent( + const ScrollWheelInput& aEvent, APZEventResult* aOutResult, + ScrollWheelInput* aOutEvent) { + ScrollWheelInput event = aEvent; + + *aOutResult = mTreeManager->InputBridge()->ReceiveInputEvent(event); + *aOutEvent = event; + + return IPC_OK(); +} + +mozilla::ipc::IPCResult APZInputBridgeParent::RecvReceiveKeyboardInputEvent( + const KeyboardInput& aEvent, APZEventResult* aOutResult, + KeyboardInput* aOutEvent) { + KeyboardInput event = aEvent; + + *aOutResult = mTreeManager->InputBridge()->ReceiveInputEvent(event); + *aOutEvent = event; + + return IPC_OK(); +} + +mozilla::ipc::IPCResult APZInputBridgeParent::RecvUpdateWheelTransaction( + const LayoutDeviceIntPoint& aRefPoint, const EventMessage& aEventMessage) { + mTreeManager->InputBridge()->UpdateWheelTransaction(aRefPoint, aEventMessage); + return IPC_OK(); +} + +mozilla::ipc::IPCResult APZInputBridgeParent::RecvProcessUnhandledEvent( + const LayoutDeviceIntPoint& aRefPoint, LayoutDeviceIntPoint* aOutRefPoint, + ScrollableLayerGuid* aOutTargetGuid, uint64_t* aOutFocusSequenceNumber, + LayersId* aOutLayersId) { + LayoutDeviceIntPoint refPoint = aRefPoint; + mTreeManager->InputBridge()->ProcessUnhandledEvent( + &refPoint, aOutTargetGuid, aOutFocusSequenceNumber, aOutLayersId); + *aOutRefPoint = refPoint; + + return IPC_OK(); +} + +void APZInputBridgeParent::ActorDestroy(ActorDestroyReason aWhy) { + // We shouldn't need it after this + mTreeManager = nullptr; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/APZInputBridgeParent.h b/gfx/layers/ipc/APZInputBridgeParent.h new file mode 100644 index 0000000000..94d917a80f --- /dev/null +++ b/gfx/layers/ipc/APZInputBridgeParent.h @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_APZInputBridgeParent_h +#define mozilla_layers_APZInputBridgeParent_h + +#include "mozilla/layers/PAPZInputBridgeParent.h" + +namespace mozilla { +namespace layers { + +class IAPZCTreeManager; + +class APZInputBridgeParent : public PAPZInputBridgeParent { + NS_INLINE_DECL_REFCOUNTING(APZInputBridgeParent, final) + + public: + explicit APZInputBridgeParent(const LayersId& aLayersId); + + mozilla::ipc::IPCResult RecvReceiveMultiTouchInputEvent( + const MultiTouchInput& aEvent, APZEventResult* aOutResult, + MultiTouchInput* aOutEvent); + + mozilla::ipc::IPCResult RecvReceiveMouseInputEvent(const MouseInput& aEvent, + APZEventResult* aOutResult, + MouseInput* aOutEvent); + + mozilla::ipc::IPCResult RecvReceivePanGestureInputEvent( + const PanGestureInput& aEvent, APZEventResult* aOutResult, + PanGestureInput* aOutEvent); + + mozilla::ipc::IPCResult RecvReceivePinchGestureInputEvent( + const PinchGestureInput& aEvent, APZEventResult* aOutResult, + PinchGestureInput* aOutEvent); + + mozilla::ipc::IPCResult RecvReceiveTapGestureInputEvent( + const TapGestureInput& aEvent, APZEventResult* aOutResult, + TapGestureInput* aOutEvent); + + mozilla::ipc::IPCResult RecvReceiveScrollWheelInputEvent( + const ScrollWheelInput& aEvent, APZEventResult* aOutResult, + ScrollWheelInput* aOutEvent); + + mozilla::ipc::IPCResult RecvReceiveKeyboardInputEvent( + const KeyboardInput& aEvent, APZEventResult* aOutResult, + KeyboardInput* aOutEvent); + + mozilla::ipc::IPCResult RecvUpdateWheelTransaction( + const LayoutDeviceIntPoint& aRefPoint, const EventMessage& aEventMessage); + + mozilla::ipc::IPCResult RecvProcessUnhandledEvent( + const LayoutDeviceIntPoint& aRefPoint, LayoutDeviceIntPoint* aOutRefPoint, + ScrollableLayerGuid* aOutTargetGuid, uint64_t* aOutFocusSequenceNumber, + LayersId* aOutLayersId); + + void ActorDestroy(ActorDestroyReason aWhy) override; + + protected: + virtual ~APZInputBridgeParent(); + + private: + RefPtr mTreeManager; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_APZInputBridgeParent_h diff --git a/gfx/layers/ipc/CanvasChild.cpp b/gfx/layers/ipc/CanvasChild.cpp new file mode 100644 index 0000000000..144d383d89 --- /dev/null +++ b/gfx/layers/ipc/CanvasChild.cpp @@ -0,0 +1,325 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "CanvasChild.h" + +#include "MainThreadUtils.h" +#include "mozilla/gfx/DrawTargetRecording.h" +#include "mozilla/gfx/Tools.h" +#include "mozilla/gfx/Rect.h" +#include "mozilla/gfx/Point.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/ProcessChild.h" +#include "mozilla/layers/CanvasDrawEventRecorder.h" +#include "nsIObserverService.h" +#include "RecordedCanvasEventImpl.h" + +namespace mozilla { +namespace layers { + +class RingBufferWriterServices final + : public CanvasEventRingBuffer::WriterServices { + public: + explicit RingBufferWriterServices(RefPtr aCanvasChild) + : mCanvasChild(std::move(aCanvasChild)) {} + + ~RingBufferWriterServices() final = default; + + bool ReaderClosed() final { + return !mCanvasChild->GetIPCChannel()->CanSend() || + ipc::ProcessChild::ExpectingShutdown(); + } + + void ResumeReader() final { mCanvasChild->ResumeTranslation(); } + + private: + RefPtr mCanvasChild; +}; + +class SourceSurfaceCanvasRecording final : public gfx::SourceSurface { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SourceSurfaceCanvasRecording, final) + + SourceSurfaceCanvasRecording( + const RefPtr& aRecordedSuface, + CanvasChild* aCanvasChild, + const RefPtr& aRecorder) + : mRecordedSurface(aRecordedSuface), + mCanvasChild(aCanvasChild), + mRecorder(aRecorder) { + mRecorder->RecordEvent(RecordedAddSurfaceAlias(this, aRecordedSuface)); + mRecorder->AddStoredObject(this); + } + + ~SourceSurfaceCanvasRecording() { + ReleaseOnMainThread(std::move(mRecorder), this, std::move(mRecordedSurface), + std::move(mCanvasChild)); + } + + gfx::SurfaceType GetType() const final { return mRecordedSurface->GetType(); } + + gfx::IntSize GetSize() const final { return mRecordedSurface->GetSize(); } + + gfx::SurfaceFormat GetFormat() const final { + return mRecordedSurface->GetFormat(); + } + + already_AddRefed GetDataSurface() final { + EnsureDataSurfaceOnMainThread(); + return do_AddRef(mDataSourceSurface); + } + + protected: + void GuaranteePersistance() final { EnsureDataSurfaceOnMainThread(); } + + private: + void EnsureDataSurfaceOnMainThread() { + // The data can only be retrieved on the main thread. + if (!mDataSourceSurface && NS_IsMainThread()) { + mDataSourceSurface = mCanvasChild->GetDataSurface(mRecordedSurface); + } + } + + // Used to ensure that clean-up that requires it is done on the main thread. + static void ReleaseOnMainThread(RefPtr aRecorder, + ReferencePtr aSurfaceAlias, + RefPtr aAliasedSurface, + RefPtr aCanvasChild) { + if (!NS_IsMainThread()) { + NS_DispatchToMainThread(NewRunnableFunction( + "SourceSurfaceCanvasRecording::ReleaseOnMainThread", + SourceSurfaceCanvasRecording::ReleaseOnMainThread, + std::move(aRecorder), aSurfaceAlias, std::move(aAliasedSurface), + std::move(aCanvasChild))); + return; + } + + aRecorder->RemoveStoredObject(aSurfaceAlias); + aRecorder->RecordEvent(RecordedRemoveSurfaceAlias(aSurfaceAlias)); + aAliasedSurface = nullptr; + aCanvasChild = nullptr; + aRecorder = nullptr; + } + + RefPtr mRecordedSurface; + RefPtr mCanvasChild; + RefPtr mRecorder; + RefPtr mDataSourceSurface; +}; + +CanvasChild::CanvasChild(Endpoint&& aEndpoint) { + aEndpoint.Bind(this); +} + +CanvasChild::~CanvasChild() = default; + +static void NotifyCanvasDeviceReset() { + nsCOMPtr obs = services::GetObserverService(); + if (obs) { + obs->NotifyObservers(nullptr, "canvas-device-reset", nullptr); + } +} + +ipc::IPCResult CanvasChild::RecvNotifyDeviceChanged() { + NotifyCanvasDeviceReset(); + mRecorder->RecordEvent(RecordedDeviceChangeAcknowledged()); + return IPC_OK(); +} + +/* static */ bool CanvasChild::mDeactivated = false; + +ipc::IPCResult CanvasChild::RecvDeactivate() { + mDeactivated = true; + NotifyCanvasDeviceReset(); + return IPC_OK(); +} + +void CanvasChild::EnsureRecorder(TextureType aTextureType) { + if (!mRecorder) { + MOZ_ASSERT(mTextureType == TextureType::Unknown); + mTextureType = aTextureType; + mRecorder = MakeAndAddRef(); + SharedMemoryBasic::Handle handle; + CrossProcessSemaphoreHandle readerSem; + CrossProcessSemaphoreHandle writerSem; + if (!mRecorder->Init(OtherPid(), &handle, &readerSem, &writerSem, + MakeUnique(this))) { + mRecorder = nullptr; + return; + } + + if (CanSend()) { + Unused << SendInitTranslator(mTextureType, handle, readerSem, writerSem); + } + } + + MOZ_RELEASE_ASSERT(mTextureType == aTextureType, + "We only support one remote TextureType currently."); +} + +void CanvasChild::ActorDestroy(ActorDestroyReason aWhy) { + // Explicitly drop our reference to the recorder, because it holds a reference + // to us via the ResumeTranslation callback. + mRecorder = nullptr; +} + +void CanvasChild::ResumeTranslation() { + if (CanSend()) { + SendResumeTranslation(); + } +} + +void CanvasChild::Destroy() { + if (CanSend()) { + Close(); + } +} + +void CanvasChild::OnTextureWriteLock() { + // We drop mRecorder in ActorDestroy to break the reference cycle. + if (!mRecorder) { + return; + } + + mHasOutstandingWriteLock = true; + mLastWriteLockCheckpoint = mRecorder->CreateCheckpoint(); +} + +void CanvasChild::OnTextureForwarded() { + // We drop mRecorder in ActorDestroy to break the reference cycle. + if (!mRecorder) { + return; + } + + if (mHasOutstandingWriteLock) { + mRecorder->RecordEvent(RecordedCanvasFlush()); + if (!mRecorder->WaitForCheckpoint(mLastWriteLockCheckpoint)) { + gfxWarning() << "Timed out waiting for last write lock to be processed."; + } + + mHasOutstandingWriteLock = false; + } +} + +void CanvasChild::EnsureBeginTransaction() { + // We drop mRecorder in ActorDestroy to break the reference cycle. + if (!mRecorder) { + return; + } + + if (!mIsInTransaction) { + mRecorder->RecordEvent(RecordedCanvasBeginTransaction()); + mIsInTransaction = true; + } +} + +void CanvasChild::EndTransaction() { + // We drop mRecorder in ActorDestroy to break the reference cycle. + if (!mRecorder) { + return; + } + + if (mIsInTransaction) { + mRecorder->RecordEvent(RecordedCanvasEndTransaction()); + mIsInTransaction = false; + mLastNonEmptyTransaction = TimeStamp::NowLoRes(); + } + + ++mTransactionsSinceGetDataSurface; +} + +bool CanvasChild::ShouldBeCleanedUp() const { + // Always return true if we've been deactivated. + if (Deactivated()) { + return true; + } + + // We can only be cleaned up if nothing else references our recorder. + if (mRecorder && !mRecorder->hasOneRef()) { + return false; + } + + static const TimeDuration kCleanUpCanvasThreshold = + TimeDuration::FromSeconds(10); + return TimeStamp::NowLoRes() - mLastNonEmptyTransaction > + kCleanUpCanvasThreshold; +} + +already_AddRefed CanvasChild::CreateDrawTarget( + gfx::IntSize aSize, gfx::SurfaceFormat aFormat) { + // We drop mRecorder in ActorDestroy to break the reference cycle. + if (!mRecorder) { + return nullptr; + } + + RefPtr dummyDt = gfx::Factory::CreateDrawTarget( + gfx::BackendType::SKIA, gfx::IntSize(1, 1), aFormat); + RefPtr dt = MakeAndAddRef( + mRecorder, dummyDt, gfx::IntRect(gfx::IntPoint(0, 0), aSize)); + return dt.forget(); +} + +void CanvasChild::RecordEvent(const gfx::RecordedEvent& aEvent) { + // We drop mRecorder in ActorDestroy to break the reference cycle. + if (!mRecorder) { + return; + } + + mRecorder->RecordEvent(aEvent); +} + +already_AddRefed CanvasChild::GetDataSurface( + const gfx::SourceSurface* aSurface) { + MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aSurface); + + // We drop mRecorder in ActorDestroy to break the reference cycle. + if (!mRecorder) { + return nullptr; + } + + mTransactionsSinceGetDataSurface = 0; + EnsureBeginTransaction(); + mRecorder->RecordEvent(RecordedPrepareDataForSurface(aSurface)); + uint32_t checkpoint = mRecorder->CreateCheckpoint(); + + gfx::IntSize ssSize = aSurface->GetSize(); + gfx::SurfaceFormat ssFormat = aSurface->GetFormat(); + size_t dataFormatWidth = ssSize.width * BytesPerPixel(ssFormat); + RefPtr dataSurface = + gfx::Factory::CreateDataSourceSurfaceWithStride(ssSize, ssFormat, + dataFormatWidth); + if (!dataSurface) { + gfxWarning() << "Failed to create DataSourceSurface."; + return nullptr; + } + gfx::DataSourceSurface::ScopedMap map(dataSurface, + gfx::DataSourceSurface::READ_WRITE); + char* dest = reinterpret_cast(map.GetData()); + if (!mRecorder->WaitForCheckpoint(checkpoint)) { + gfxWarning() << "Timed out preparing data for DataSourceSurface."; + return dataSurface.forget(); + } + + mRecorder->RecordEvent(RecordedGetDataForSurface(aSurface)); + mRecorder->ReturnRead(dest, ssSize.height * dataFormatWidth); + + return dataSurface.forget(); +} + +already_AddRefed CanvasChild::WrapSurface( + const RefPtr& aSurface) { + MOZ_ASSERT(aSurface); + // We drop mRecorder in ActorDestroy to break the reference cycle. + if (!mRecorder) { + return nullptr; + } + + return MakeAndAddRef(aSurface, this, mRecorder); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/CanvasChild.h b/gfx/layers/ipc/CanvasChild.h new file mode 100644 index 0000000000..68ac863b35 --- /dev/null +++ b/gfx/layers/ipc/CanvasChild.h @@ -0,0 +1,151 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_CanvasChild_h +#define mozilla_layers_CanvasChild_h + +#include "mozilla/gfx/RecordedEvent.h" +#include "mozilla/ipc/CrossProcessSemaphore.h" +#include "mozilla/layers/PCanvasChild.h" +#include "mozilla/layers/SourceSurfaceSharedData.h" +#include "nsRefPtrHashtable.h" +#include "nsTArray.h" + +namespace mozilla { + +namespace gfx { +class SourceSurface; +} + +namespace layers { +class CanvasDrawEventRecorder; + +class CanvasChild final : public PCanvasChild { + public: + NS_INLINE_DECL_REFCOUNTING(CanvasChild) + + explicit CanvasChild(Endpoint&& aEndpoint); + + /** + * @returns true if remote canvas has been deactivated due to failure. + */ + static bool Deactivated() { return mDeactivated; } + + ipc::IPCResult RecvNotifyDeviceChanged(); + + ipc::IPCResult RecvDeactivate(); + + /** + * Ensures that the DrawEventRecorder has been created. + * + * @params aTextureType the TextureType to create in the CanvasTranslator. + */ + void EnsureRecorder(TextureType aTextureType); + + /** + * Send a messsage to our CanvasParent to resume translation. + */ + void ResumeTranslation(); + + /** + * Clean up IPDL actor. + */ + void Destroy(); + + /** + * Called when a RecordedTextureData is write locked. + */ + void OnTextureWriteLock(); + + /** + * Called when a RecordedTextureData is forwarded to the compositor. + */ + void OnTextureForwarded(); + + /** + * @returns true if we should be caching data surfaces in the GPU process. + */ + bool ShouldCacheDataSurface() const { + return mTransactionsSinceGetDataSurface < kCacheDataSurfaceThreshold; + } + + /** + * Ensures that we have sent a begin transaction event, since the last + * end transaction. + */ + void EnsureBeginTransaction(); + + /** + * Send an end transaction event to indicate the end of events for this frame. + */ + void EndTransaction(); + + /** + * @returns true if the canvas IPC classes have not been used for some time + * and can be cleaned up. + */ + bool ShouldBeCleanedUp() const; + + /** + * Create a DrawTargetRecording for a canvas texture. + * @param aSize size for the DrawTarget + * @param aFormat SurfaceFormat for the DrawTarget + * @returns newly created DrawTargetRecording + */ + already_AddRefed CreateDrawTarget( + gfx::IntSize aSize, gfx::SurfaceFormat aFormat); + + /** + * Record an event for processing by the CanvasParent's CanvasTranslator. + * @param aEvent the event to record + */ + void RecordEvent(const gfx::RecordedEvent& aEvent); + + /** + * Wrap the given surface, so that we can provide a DataSourceSurface if + * required. + * @param aSurface the SourceSurface to wrap + * @returns a SourceSurface that can provide a DataSourceSurface if required + */ + already_AddRefed WrapSurface( + const RefPtr& aSurface); + + /** + * Get DataSourceSurface from the translated equivalent version of aSurface in + * the GPU process. + * @param aSurface the SourceSurface in this process for which we need a + * DataSourceSurface + * @returns a DataSourceSurface created from data for aSurface retrieve from + * GPU process + */ + already_AddRefed GetDataSurface( + const gfx::SourceSurface* aSurface); + + protected: + void ActorDestroy(ActorDestroyReason aWhy) final; + + private: + DISALLOW_COPY_AND_ASSIGN(CanvasChild); + + ~CanvasChild() final; + + static const uint32_t kCacheDataSurfaceThreshold = 10; + + static bool mDeactivated; + + RefPtr mRecorder; + TextureType mTextureType = TextureType::Unknown; + uint32_t mLastWriteLockCheckpoint = 0; + uint32_t mTransactionsSinceGetDataSurface = kCacheDataSurfaceThreshold; + TimeStamp mLastNonEmptyTransaction = TimeStamp::NowLoRes(); + bool mIsInTransaction = false; + bool mHasOutstandingWriteLock = false; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_CanvasChild_h diff --git a/gfx/layers/ipc/CanvasThread.cpp b/gfx/layers/ipc/CanvasThread.cpp new file mode 100644 index 0000000000..e91492c1ac --- /dev/null +++ b/gfx/layers/ipc/CanvasThread.cpp @@ -0,0 +1,138 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "CanvasThread.h" + +#include "mozilla/SharedThreadPool.h" +#include "nsThreadUtils.h" +#include "prsystem.h" + +bool NS_IsInCanvasThreadOrWorker() { + return mozilla::layers::CanvasThreadHolder::IsInCanvasThreadOrWorker(); +} + +namespace mozilla { +namespace layers { + +StaticDataMutex> + CanvasThreadHolder::sCanvasThreadHolder("sCanvasThreadHolder"); + +CanvasThreadHolder::CanvasThreadHolder( + already_AddRefed aCanvasThread, + already_AddRefed aCanvasWorkers) + : mCanvasThread(aCanvasThread), + mCanvasWorkers(aCanvasWorkers), + mCompositorThreadKeepAlive(CompositorThreadHolder::GetSingleton()) { + MOZ_ASSERT(NS_IsInCompositorThread()); + MOZ_ASSERT(mCanvasThread); + MOZ_ASSERT(mCanvasWorkers); +} + +CanvasThreadHolder::~CanvasThreadHolder() { + // Note we can't just use NS_IsInCompositorThread() here because + // sCompositorThreadHolder might have already gone. + MOZ_ASSERT( + mCompositorThreadKeepAlive->GetCompositorThread()->IsOnCurrentThread()); +} + +/* static */ +already_AddRefed CanvasThreadHolder::EnsureCanvasThread() { + MOZ_ASSERT(NS_IsInCompositorThread()); + + auto lockedCanvasThreadHolder = sCanvasThreadHolder.Lock(); + if (!lockedCanvasThreadHolder.ref()) { + nsCOMPtr canvasThread; + nsresult rv = + NS_CreateBackgroundTaskQueue("Canvas", getter_AddRefs(canvasThread)); + NS_ENSURE_SUCCESS(rv, nullptr); + + // Given that the canvas workers are receiving instructions from + // content processes, it probably doesn't make sense to have more than + // half the number of processors doing canvas drawing. We set the + // lower limit to 2, so that even on single processor systems, if + // there is more than one window with canvas drawing, the OS can + // manage the load between them. + uint32_t threadLimit = std::max(2, PR_GetNumberOfProcessors() / 2); + nsCOMPtr canvasWorkers = + SharedThreadPool::Get("CanvasWorkers"_ns, threadLimit); + if (!canvasWorkers) { + return nullptr; + } + + lockedCanvasThreadHolder.ref() = + new CanvasThreadHolder(canvasThread.forget(), canvasWorkers.forget()); + } + + return do_AddRef(lockedCanvasThreadHolder.ref()); +} + +/* static */ +void CanvasThreadHolder::ReleaseOnCompositorThread( + already_AddRefed aCanvasThreadHolder) { + RefPtr canvasThreadHolder = aCanvasThreadHolder; + auto lockedCanvasThreadHolder = sCanvasThreadHolder.Lock(); + lockedCanvasThreadHolder.ref() + ->mCompositorThreadKeepAlive->GetCompositorThread() + ->Dispatch(NS_NewRunnableFunction( + "CanvasThreadHolder::StaticRelease", + [canvasThreadHolder = std::move(canvasThreadHolder)]() mutable { + RefPtr threadHolder = + canvasThreadHolder.forget(); + threadHolder = nullptr; + + auto lockedCanvasThreadHolder = sCanvasThreadHolder.Lock(); + if (lockedCanvasThreadHolder.ref()->mRefCnt == 1) { + lockedCanvasThreadHolder.ref() = nullptr; + } + })); +} + +/* static */ +bool CanvasThreadHolder::IsInCanvasThread() { + auto lockedCanvasThreadHolder = sCanvasThreadHolder.Lock(); + return lockedCanvasThreadHolder.ref() && + lockedCanvasThreadHolder.ref()->mCanvasThread->IsOnCurrentThread(); +} + +/* static */ +bool CanvasThreadHolder::IsInCanvasWorker() { + auto lockedCanvasThreadHolder = sCanvasThreadHolder.Lock(); + return lockedCanvasThreadHolder.ref() && + lockedCanvasThreadHolder.ref()->mCanvasWorkers->IsOnCurrentThread(); +} + +/* static */ +bool CanvasThreadHolder::IsInCanvasThreadOrWorker() { + auto lockedCanvasThreadHolder = sCanvasThreadHolder.Lock(); + return lockedCanvasThreadHolder.ref() && + (lockedCanvasThreadHolder.ref()->mCanvasWorkers->IsOnCurrentThread() || + lockedCanvasThreadHolder.ref()->mCanvasThread->IsOnCurrentThread()); +} + +/* static */ +void CanvasThreadHolder::MaybeDispatchToCanvasThread( + already_AddRefed aRunnable) { + auto lockedCanvasThreadHolder = sCanvasThreadHolder.Lock(); + if (!lockedCanvasThreadHolder.ref()) { + // There is no canvas thread just release the runnable. + nsCOMPtr runnable = aRunnable; + return; + } + + lockedCanvasThreadHolder.ref()->mCanvasThread->Dispatch(std::move(aRunnable)); +} + +void CanvasThreadHolder::DispatchToCanvasThread( + already_AddRefed aRunnable) { + mCanvasThread->Dispatch(std::move(aRunnable)); +} + +already_AddRefed CanvasThreadHolder::CreateWorkerTaskQueue() { + return MakeAndAddRef(do_AddRef(mCanvasWorkers)); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/CanvasThread.h b/gfx/layers/ipc/CanvasThread.h new file mode 100644 index 0000000000..ebcd4bc8d5 --- /dev/null +++ b/gfx/layers/ipc/CanvasThread.h @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_CanvasThread_h +#define mozilla_layers_CanvasThread_h + +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/DataMutex.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/TaskQueue.h" +#include "nsIThreadPool.h" + +namespace mozilla { +namespace layers { + +/** + * The CanvasThreadHolder is used to manage the lifetime of the canvas + * thread (IPC) and workers and also provides methods for using them. + */ +class CanvasThreadHolder final { + // We only AddRef/Release on the Compositor thread. + NS_INLINE_DECL_REFCOUNTING(CanvasThreadHolder) + + public: + /** + * Ensures that the canvas thread and workers are created. + * This must be called on the compositor thread. + * @return a CanvasThreadHolder + */ + static already_AddRefed EnsureCanvasThread(); + + /** + * Used to release CanvasThreadHolder references, which must be released on + * the compositor thread. + * @param aCanvasThreadHolder the reference to release + */ + static void ReleaseOnCompositorThread( + already_AddRefed aCanvasThreadHolder); + + /** + * @return true if in the canvas thread + */ + static bool IsInCanvasThread(); + + /** + * @return true if in a canvas worker thread + */ + static bool IsInCanvasWorker(); + + /** + * @return true if in the canvas thread or worker + */ + static bool IsInCanvasThreadOrWorker(); + + /** + * Static method to dispatch a runnable to the canvas thread. If there is no + * canvas thread this will just release aRunnable. + * @param aRunnable the runnable to dispatch + */ + static void MaybeDispatchToCanvasThread( + already_AddRefed aRunnable); + + /** + * Dispatch a runnable to the canvas thread. + * @param aRunnable the runnable to dispatch + */ + void DispatchToCanvasThread(already_AddRefed aRunnable); + + /** + * Create a TaskQueue for the canvas worker threads. This must be shutdown + * before the reference to CanvasThreadHolder is dropped. + * @return a TaskQueue that dispatches to the canvas worker threads + */ + already_AddRefed CreateWorkerTaskQueue(); + + private: + static StaticDataMutex> sCanvasThreadHolder; + + CanvasThreadHolder(already_AddRefed aCanvasThread, + already_AddRefed aCanvasWorkers); + + ~CanvasThreadHolder(); + + nsCOMPtr mCanvasThread; + RefPtr mCanvasWorkers; + + // Hold a reference to prevent the compositor thread ending. + RefPtr mCompositorThreadKeepAlive; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_CanvasThread_h diff --git a/gfx/layers/ipc/CanvasTranslator.cpp b/gfx/layers/ipc/CanvasTranslator.cpp new file mode 100644 index 0000000000..065bfacfd4 --- /dev/null +++ b/gfx/layers/ipc/CanvasTranslator.cpp @@ -0,0 +1,534 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "CanvasTranslator.h" + +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/GPUParent.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/layers/TextureClient.h" +#include "mozilla/SyncRunnable.h" +#include "mozilla/Telemetry.h" +#include "nsTHashtable.h" +#include "RecordedCanvasEventImpl.h" + +#if defined(XP_WIN) +# include "mozilla/gfx/DeviceManagerDx.h" +# include "mozilla/layers/TextureD3D11.h" +#endif + +namespace mozilla { +namespace layers { + +// When in a transaction we wait for a short time because we're expecting more +// events from the content process. We don't want to wait for too long in case +// other content processes are waiting for events to process. +static const TimeDuration kReadEventTimeout = TimeDuration::FromMilliseconds(5); + +class RingBufferReaderServices final + : public CanvasEventRingBuffer::ReaderServices { + public: + explicit RingBufferReaderServices(RefPtr aCanvasTranslator) + : mCanvasTranslator(std::move(aCanvasTranslator)) {} + + ~RingBufferReaderServices() final = default; + + bool WriterClosed() final { + return !mCanvasTranslator->GetIPCChannel()->CanSend(); + } + + private: + RefPtr mCanvasTranslator; +}; + +TextureData* CanvasTranslator::CreateTextureData(TextureType aTextureType, + const gfx::IntSize& aSize, + gfx::SurfaceFormat aFormat) { + TextureData* textureData = nullptr; + switch (aTextureType) { +#ifdef XP_WIN + case TextureType::D3D11: { + textureData = + D3D11TextureData::Create(aSize, aFormat, ALLOC_CLEAR_BUFFER, mDevice); + break; + } +#endif + default: + MOZ_CRASH("Unsupported TextureType for CanvasTranslator."); + } + + return textureData; +} + +typedef nsTHashtable> CanvasTranslatorSet; + +static CanvasTranslatorSet& CanvasTranslators() { + MOZ_ASSERT(CanvasThreadHolder::IsInCanvasThread()); + static CanvasTranslatorSet* sCanvasTranslator = new CanvasTranslatorSet(); + return *sCanvasTranslator; +} + +static void EnsureAllClosed() { + for (auto iter = CanvasTranslators().Iter(); !iter.Done(); iter.Next()) { + iter.Get()->GetKey()->Close(); + } +} + +/* static */ void CanvasTranslator::Shutdown() { + // If the dispatch fails there is no canvas thread and so no translators. + CanvasThreadHolder::MaybeDispatchToCanvasThread(NewRunnableFunction( + "CanvasTranslator::EnsureAllClosed", &EnsureAllClosed)); +} + +/* static */ already_AddRefed CanvasTranslator::Create( + ipc::Endpoint&& aEndpoint) { + MOZ_ASSERT(NS_IsInCompositorThread()); + + RefPtr threadHolder = + CanvasThreadHolder::EnsureCanvasThread(); + RefPtr canvasTranslator = + new CanvasTranslator(do_AddRef(threadHolder)); + threadHolder->DispatchToCanvasThread( + NewRunnableMethod&&>( + "CanvasTranslator::Bind", canvasTranslator, &CanvasTranslator::Bind, + std::move(aEndpoint))); + return canvasTranslator.forget(); +} + +CanvasTranslator::CanvasTranslator( + already_AddRefed aCanvasThreadHolder) + : gfx::InlineTranslator(), mCanvasThreadHolder(aCanvasThreadHolder) { + // Track when remote canvas has been activated. + Telemetry::ScalarAdd(Telemetry::ScalarID::GFX_CANVAS_REMOTE_ACTIVATED, 1); +} + +CanvasTranslator::~CanvasTranslator() { + if (mReferenceTextureData) { + mReferenceTextureData->Unlock(); + } +} + +void CanvasTranslator::Bind(Endpoint&& aEndpoint) { + if (!aEndpoint.Bind(this)) { + return; + } + + CanvasTranslators().PutEntry(this); +} + +mozilla::ipc::IPCResult CanvasTranslator::RecvInitTranslator( + const TextureType& aTextureType, + const ipc::SharedMemoryBasic::Handle& aReadHandle, + const CrossProcessSemaphoreHandle& aReaderSem, + const CrossProcessSemaphoreHandle& aWriterSem) { + mTextureType = aTextureType; + + // We need to initialize the stream first, because it might be used to + // communicate other failures back to the writer. + mStream = MakeUnique(); + if (!mStream->InitReader(aReadHandle, aReaderSem, aWriterSem, + MakeUnique(this))) { + return IPC_FAIL(this, "Failed to initialize ring buffer reader."); + } + +#if defined(XP_WIN) + if (!CheckForFreshCanvasDevice(__LINE__)) { + gfxCriticalNote << "GFX: CanvasTranslator failed to get device"; + return IPC_OK(); + } +#endif + + mTranslationTaskQueue = mCanvasThreadHolder->CreateWorkerTaskQueue(); + return RecvResumeTranslation(); +} + +ipc::IPCResult CanvasTranslator::RecvResumeTranslation() { + if (mDeactivated) { + // The other side might have sent a resume message before we deactivated. + return IPC_OK(); + } + + MOZ_ALWAYS_SUCCEEDS(mTranslationTaskQueue->Dispatch( + NewRunnableMethod("CanvasTranslator::StartTranslation", this, + &CanvasTranslator::StartTranslation))); + + return IPC_OK(); +} + +void CanvasTranslator::StartTranslation() { + if (!TranslateRecording() && GetIPCChannel()->CanSend()) { + MOZ_ALWAYS_SUCCEEDS(mTranslationTaskQueue->Dispatch( + NewRunnableMethod("CanvasTranslator::StartTranslation", this, + &CanvasTranslator::StartTranslation))); + } + + // If the stream has been marked as bad and the Writer hasn't failed, + // deactivate remote canvas. + if (!mStream->good() && !mStream->WriterFailed()) { + Telemetry::ScalarAdd( + Telemetry::ScalarID::GFX_CANVAS_REMOTE_DEACTIVATED_BAD_STREAM, 1); + Deactivate(); + } +} + +void CanvasTranslator::ActorDestroy(ActorDestroyReason why) { + MOZ_ASSERT(CanvasThreadHolder::IsInCanvasThread()); + + if (!mTranslationTaskQueue) { + return FinishShutdown(); + } + + mTranslationTaskQueue->BeginShutdown()->Then( + GetCurrentSerialEventTarget(), __func__, this, + &CanvasTranslator::FinishShutdown, &CanvasTranslator::FinishShutdown); +} + +void CanvasTranslator::FinishShutdown() { + MOZ_ASSERT(CanvasThreadHolder::IsInCanvasThread()); + + // mTranslationTaskQueue has shutdown we can safely drop the ring buffer to + // break the cycle caused by RingBufferReaderServices. + mStream = nullptr; + + // CanvasTranslators has a MOZ_ASSERT(CanvasThreadHolder::IsInCanvasThread()) + // to ensure it is only called on the Canvas Thread. This takes a lock on + // CanvasThreadHolder::sCanvasThreadHolder, which is also locked in + // CanvasThreadHolder::StaticRelease on the compositor thread from + // ReleaseOnCompositorThread below. If that lock wins the race with the one in + // IsInCanvasThread and it is the last CanvasThreadHolder reference then it + // shuts down the canvas thread waiting for it to finish. However + // IsInCanvasThread is waiting for the lock on the canvas thread and we + // deadlock. So, we need to call CanvasTranslators before + // ReleaseOnCompositorThread. + CanvasTranslatorSet& canvasTranslators = CanvasTranslators(); + CanvasThreadHolder::ReleaseOnCompositorThread(mCanvasThreadHolder.forget()); + canvasTranslators.RemoveEntry(this); +} + +void CanvasTranslator::Deactivate() { + if (mDeactivated) { + return; + } + mDeactivated = true; + + // We need to tell the other side to deactivate. Make sure the stream is + // marked as bad so that the writing side won't wait for space to write. + mStream->SetIsBad(); + mCanvasThreadHolder->DispatchToCanvasThread( + NewRunnableMethod("CanvasTranslator::SendDeactivate", this, + &CanvasTranslator::SendDeactivate)); + + { + // Unlock all of our textures. + gfx::AutoSerializeWithMoz2D serializeWithMoz2D(GetBackendType()); + for (auto const& entry : mTextureDatas) { + entry.second->Unlock(); + } + } + + // Also notify anyone waiting for a surface descriptor. This must be done + // after mDeactivated is set to true. + mSurfaceDescriptorsMonitor.NotifyAll(); +} + +bool CanvasTranslator::TranslateRecording() { + MOZ_ASSERT(CanvasThreadHolder::IsInCanvasWorker()); + + int32_t eventType = mStream->ReadNextEvent(); + while (mStream->good()) { + bool success = RecordedEvent::DoWithEventFromStream( + *mStream, static_cast(eventType), + [&](RecordedEvent* recordedEvent) -> bool { + // Make sure that the whole event was read from the stream. + if (!mStream->good()) { + if (!GetIPCChannel()->CanSend()) { + // The other side has closed only warn about read failure. + gfxWarning() << "Failed to read event type: " + << recordedEvent->GetType(); + } else { + gfxCriticalNote << "Failed to read event type: " + << recordedEvent->GetType(); + } + return false; + } + + return recordedEvent->PlayEvent(this); + }); + + // Check the stream is good here or we will log the issue twice. + if (!mStream->good()) { + return true; + } + + if (!success && !HandleExtensionEvent(eventType)) { + if (mDeviceResetInProgress) { + // We've notified the recorder of a device change, so we are expecting + // failures. Log as a warning to prevent crash reporting being flooded. + gfxWarning() << "Failed to play canvas event type: " << eventType; + } else { + gfxCriticalNote << "Failed to play canvas event type: " << eventType; + } + if (!mStream->good()) { + return true; + } + } + + if (!mIsInTransaction) { + return mStream->StopIfEmpty(); + } + + if (!mStream->HasDataToRead()) { + // We're going to wait for the next event, so take the opportunity to + // flush the rendering. + Flush(); + if (!mStream->WaitForDataToRead(kReadEventTimeout, 0)) { + return true; + } + } + + eventType = mStream->ReadNextEvent(); + } + + return true; +} + +#define READ_AND_PLAY_CANVAS_EVENT_TYPE(_typeenum, _class) \ + case _typeenum: { \ + auto e = _class(*mStream); \ + if (!mStream->good()) { \ + if (!GetIPCChannel()->CanSend()) { \ + /* The other side has closed only warn about read failure. */ \ + gfxWarning() << "Failed to read event type: " << _typeenum; \ + } else { \ + gfxCriticalNote << "Failed to read event type: " << _typeenum; \ + } \ + return false; \ + } \ + return e.PlayCanvasEvent(this); \ + } + +bool CanvasTranslator::HandleExtensionEvent(int32_t aType) { + // This is where we handle extensions to the Moz2D Recording events to handle + // canvas specific things. + switch (aType) { + FOR_EACH_CANVAS_EVENT(READ_AND_PLAY_CANVAS_EVENT_TYPE) + default: + return false; + } +} + +void CanvasTranslator::BeginTransaction() { mIsInTransaction = true; } + +void CanvasTranslator::Flush() { +#if defined(XP_WIN) + // We can end up without a device, due to a reset and failure to re-create. + if (!mDevice) { + return; + } + + gfx::AutoSerializeWithMoz2D serializeWithMoz2D(GetBackendType()); + RefPtr deviceContext; + mDevice->GetImmediateContext(getter_AddRefs(deviceContext)); + deviceContext->Flush(); +#endif +} + +void CanvasTranslator::EndTransaction() { + Flush(); + // At the end of a transaction is a good time to check if a new canvas device + // has been created, even if a reset did not occur. + Unused << CheckForFreshCanvasDevice(__LINE__); + mIsInTransaction = false; +} + +void CanvasTranslator::DeviceChangeAcknowledged() { + mDeviceResetInProgress = false; +} + +bool CanvasTranslator::CreateReferenceTexture() { + if (mReferenceTextureData) { + mReferenceTextureData->Unlock(); + } + + mReferenceTextureData.reset(CreateTextureData( + mTextureType, gfx::IntSize(1, 1), gfx::SurfaceFormat::B8G8R8A8)); + if (!mReferenceTextureData) { + return false; + } + + mReferenceTextureData->Lock(OpenMode::OPEN_READ_WRITE); + mBaseDT = mReferenceTextureData->BorrowDrawTarget(); + if (mBaseDT) { + mBackendType = mBaseDT->GetBackendType(); + } + return true; +} + +bool CanvasTranslator::CheckForFreshCanvasDevice(int aLineNumber) { +#if defined(XP_WIN) + // If a new device has already been created, use that one. + RefPtr device = gfx::DeviceManagerDx::Get()->GetCanvasDevice(); + if (device && device != mDevice) { + if (mDevice) { + // We already had a device, notify child of change. + NotifyDeviceChanged(); + } + mDevice = device.forget(); + return CreateReferenceTexture(); + } + + if (mDevice) { + if (mDevice->GetDeviceRemovedReason() == S_OK) { + return false; + } + + gfxCriticalNote << "GFX: CanvasTranslator detected a device reset at " + << aLineNumber; + NotifyDeviceChanged(); + } + + RefPtr runnable = NS_NewRunnableFunction( + "CanvasTranslator NotifyDeviceReset", + []() { gfx::GPUParent::GetSingleton()->NotifyDeviceReset(); }); + + // It is safe to wait here because only the Compositor thread waits on us and + // the main thread doesn't wait on the compositor thread in the GPU process. + SyncRunnable::DispatchToThread(GetMainThreadEventTarget(), runnable, + /*aForceDispatch*/ true); + + mDevice = gfx::DeviceManagerDx::Get()->GetCanvasDevice(); + if (!mDevice) { + // We don't have a canvas device, we need to deactivate. + Telemetry::ScalarAdd( + Telemetry::ScalarID::GFX_CANVAS_REMOTE_DEACTIVATED_NO_DEVICE, 1); + Deactivate(); + return false; + } + + return CreateReferenceTexture(); +#else + return false; +#endif +} + +void CanvasTranslator::NotifyDeviceChanged() { + mDeviceResetInProgress = true; + mCanvasThreadHolder->DispatchToCanvasThread( + NewRunnableMethod("CanvasTranslator::SendNotifyDeviceChanged", this, + &CanvasTranslator::SendNotifyDeviceChanged)); +} + +void CanvasTranslator::AddSurfaceDescriptor(int64_t aTextureId, + TextureData* aTextureData) { + UniquePtr descriptor = MakeUnique(); + if (!aTextureData->Serialize(*descriptor)) { + MOZ_CRASH("Failed to serialize"); + } + + MonitorAutoLock lock(mSurfaceDescriptorsMonitor); + mSurfaceDescriptors[aTextureId] = std::move(descriptor); + mSurfaceDescriptorsMonitor.Notify(); +} + +already_AddRefed CanvasTranslator::CreateDrawTarget( + gfx::ReferencePtr aRefPtr, const gfx::IntSize& aSize, + gfx::SurfaceFormat aFormat) { + RefPtr dt; + do { + // It is important that AutoSerializeWithMoz2D is called within the loop + // and doesn't hold during calls to CheckForFreshCanvasDevice, because that + // might cause a deadlock with device reset code on the main thread. + gfx::AutoSerializeWithMoz2D serializeWithMoz2D(GetBackendType()); + TextureData* textureData = CreateTextureData(mTextureType, aSize, aFormat); + if (textureData) { + MOZ_DIAGNOSTIC_ASSERT(mNextTextureId >= 0, "No texture ID set"); + textureData->Lock(OpenMode::OPEN_READ_WRITE); + mTextureDatas[mNextTextureId] = UniquePtr(textureData); + AddSurfaceDescriptor(mNextTextureId, textureData); + dt = textureData->BorrowDrawTarget(); + } + } while (!dt && CheckForFreshCanvasDevice(__LINE__)); + AddDrawTarget(aRefPtr, dt); + mNextTextureId = -1; + + return dt.forget(); +} + +void CanvasTranslator::RemoveTexture(int64_t aTextureId) { + gfx::AutoSerializeWithMoz2D serializeWithMoz2D(GetBackendType()); + mTextureDatas.erase(aTextureId); + + // It is possible that the texture from the content process has never been + // forwarded to the GPU process, so make sure its descriptor is removed. + MonitorAutoLock lock(mSurfaceDescriptorsMonitor); + mSurfaceDescriptors.erase(aTextureId); +} + +TextureData* CanvasTranslator::LookupTextureData(int64_t aTextureId) { + TextureMap::const_iterator result = mTextureDatas.find(aTextureId); + if (result == mTextureDatas.end()) { + return nullptr; + } + return result->second.get(); +} + +UniquePtr CanvasTranslator::WaitForSurfaceDescriptor( + int64_t aTextureId) { + MonitorAutoLock lock(mSurfaceDescriptorsMonitor); + DescriptorMap::iterator result; + while ((result = mSurfaceDescriptors.find(aTextureId)) == + mSurfaceDescriptors.end()) { + // If remote canvas has been deactivated just return null. + if (mDeactivated) { + return nullptr; + } + + mSurfaceDescriptorsMonitor.Wait(); + } + + UniquePtr descriptor = std::move(result->second); + mSurfaceDescriptors.erase(aTextureId); + return descriptor; +} + +gfx::DataSourceSurface* CanvasTranslator::LookupDataSurface( + gfx::ReferencePtr aRefPtr) { + return mDataSurfaces.GetWeak(aRefPtr); +} + +void CanvasTranslator::AddDataSurface( + gfx::ReferencePtr aRefPtr, RefPtr&& aSurface) { + mDataSurfaces.Put(aRefPtr, std::move(aSurface)); +} + +void CanvasTranslator::RemoveDataSurface(gfx::ReferencePtr aRefPtr) { + mDataSurfaces.Remove(aRefPtr); +} + +void CanvasTranslator::SetPreparedMap( + gfx::ReferencePtr aSurface, + UniquePtr aMap) { + mMappedSurface = aSurface; + mPreparedMap = std::move(aMap); +} + +UniquePtr CanvasTranslator::GetPreparedMap( + gfx::ReferencePtr aSurface) { + if (!mPreparedMap) { + // We might fail to set the map during, for example, device resets. + return nullptr; + } + + MOZ_RELEASE_ASSERT(mMappedSurface == aSurface, + "aSurface must match previously stored surface."); + + mMappedSurface = nullptr; + return std::move(mPreparedMap); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/CanvasTranslator.h b/gfx/layers/ipc/CanvasTranslator.h new file mode 100644 index 0000000000..eb010bbeb7 --- /dev/null +++ b/gfx/layers/ipc/CanvasTranslator.h @@ -0,0 +1,284 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_CanvasTranslator_h +#define mozilla_layers_CanvasTranslator_h + +#include +#include + +#include "mozilla/gfx/InlineTranslator.h" +#include "mozilla/layers/CanvasDrawEventRecorder.h" +#include "mozilla/layers/CanvasThread.h" +#include "mozilla/layers/LayersSurfaces.h" +#include "mozilla/layers/PCanvasParent.h" +#include "mozilla/ipc/CrossProcessSemaphore.h" +#include "mozilla/Monitor.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { +namespace layers { + +class TextureData; + +class CanvasTranslator final : public gfx::InlineTranslator, + public PCanvasParent { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CanvasTranslator) + + friend class PProtocolParent; + + /** + * Create an uninitialized CanvasTranslator and bind it to the given endpoint + * on the CanvasPlaybackLoop. + * + * @param aEndpoint the endpoint to bind to + * @return the new CanvasTranslator + */ + static already_AddRefed Create( + Endpoint&& aEndpoint); + + /** + * Shutdown all of the CanvasTranslators. + */ + static void Shutdown(); + + /** + * Initialize the canvas translator for a particular TextureType and + * CanvasEventRingBuffer. + * + * @param aTextureType the TextureType the translator will create + * @param aReadHandle handle to the shared memory for the + * CanvasEventRingBuffer + * @param aReaderSem reading blocked semaphore for the CanvasEventRingBuffer + * @param aWriterSem writing blocked semaphore for the CanvasEventRingBuffer + */ + ipc::IPCResult RecvInitTranslator( + const TextureType& aTextureType, + const ipc::SharedMemoryBasic::Handle& aReadHandle, + const CrossProcessSemaphoreHandle& aReaderSem, + const CrossProcessSemaphoreHandle& aWriterSem); + + /** + * Used to tell the CanvasTranslator to start translating again after it has + * stopped due to a timeout waiting for events. + */ + ipc::IPCResult RecvResumeTranslation(); + + void ActorDestroy(ActorDestroyReason why) final; + + /** + * Translates events until no more are available or the end of a transaction + * If this returns false the caller of this is responsible for re-calling + * this function. + * + * @returns true if all events are processed and false otherwise. + */ + bool TranslateRecording(); + + /** + * Marks the beginning of rendering for a transaction. While in a transaction + * the translator will wait for a short time for events before returning. + * When not in a transaction the translator will only translate one event at a + * time. + */ + void BeginTransaction(); + + /** + * Marks the end of a transaction. + */ + void EndTransaction(); + + /** + * Flushes canvas drawing, for example to a device. + */ + void Flush(); + + /** + * Marks that device change processing in the writing process has finished. + */ + void DeviceChangeAcknowledged(); + + /** + * Used to send data back to the writer. This is done through the same shared + * memory so the writer must wait and read the response after it has submitted + * the event that uses this. + * + * @param aData the data to be written back to the writer + * @param aSize the number of chars to write + */ + void ReturnWrite(const char* aData, size_t aSize) { + mStream->ReturnWrite(aData, aSize); + } + + /** + * Set the texture ID that will be used as a lookup for the texture created by + * the next CreateDrawTarget. + */ + void SetNextTextureId(int64_t aNextTextureId) { + mNextTextureId = aNextTextureId; + } + + /** + * Used during playback of events to create DrawTargets. For the + * CanvasTranslator this means creating TextureDatas and getting the + * DrawTargets from those. + * + * @param aRefPtr the key to store the created DrawTarget against + * @param aSize the size of the DrawTarget + * @param aFormat the surface format for the DrawTarget + * @returns the new DrawTarget + */ + already_AddRefed CreateDrawTarget( + gfx::ReferencePtr aRefPtr, const gfx::IntSize& aSize, + gfx::SurfaceFormat aFormat) final; + + /** + * Get the TextureData associated with a TextureData from another process. + * + * @param aTextureId the key used to find the TextureData + * @returns the TextureData found + */ + TextureData* LookupTextureData(int64_t aTextureId); + + /** + * Waits for the SurfaceDescriptor associated with a TextureData from another + * process to be created and then returns it. + * + * @param aTextureId the key used to find the SurfaceDescriptor + * @returns the SurfaceDescriptor found + */ + UniquePtr WaitForSurfaceDescriptor(int64_t aTextureId); + + /** + * Removes the texture and other objects associated with a texture ID. + * + * @param aTextureId the texture ID to remove + */ + void RemoveTexture(int64_t aTextureId); + + /** + * Removes the SourceSurface and other objects associated with a SourceSurface + * from another process. + * + * @param aRefPtr the key to the objects to remove + */ + void RemoveSourceSurface(gfx::ReferencePtr aRefPtr) final { + RemoveDataSurface(aRefPtr); + InlineTranslator::RemoveSourceSurface(aRefPtr); + } + + /** + * Gets the cached DataSourceSurface, if it exists, associated with a + * SourceSurface from another process. + * + * @param aRefPtr the key used to find the DataSourceSurface + * @returns the DataSourceSurface or nullptr if not found + */ + gfx::DataSourceSurface* LookupDataSurface(gfx::ReferencePtr aRefPtr); + + /** + * Used to cache the DataSourceSurface from a SourceSurface associated with a + * SourceSurface from another process. This is to improve performance if we + * require the data for that SourceSurface. + * + * @param aRefPtr the key used to store the DataSourceSurface + * @param aSurface the DataSourceSurface to store + */ + void AddDataSurface(gfx::ReferencePtr aRefPtr, + RefPtr&& aSurface); + + /** + * Gets the cached DataSourceSurface, if it exists, associated with a + * SourceSurface from another process. + * + * @param aRefPtr the key used to find the DataSourceSurface + * @returns the DataSourceSurface or nullptr if not found + */ + void RemoveDataSurface(gfx::ReferencePtr aRefPtr); + + /** + * Sets a ScopedMap, to be used in a later event. + * + * @param aSurface the associated surface in the other process + * @param aMap the ScopedMap to store + */ + void SetPreparedMap(gfx::ReferencePtr aSurface, + UniquePtr aMap); + + /** + * Gets the ScopedMap stored using SetPreparedMap. + * + * @param aSurface must match the surface from the SetPreparedMap call + * @returns the ScopedMap if aSurface matches otherwise nullptr + */ + UniquePtr GetPreparedMap( + gfx::ReferencePtr aSurface); + + /** + * @return the BackendType being used for translation + */ + gfx::BackendType GetBackendType() { return mBackendType; } + + private: + explicit CanvasTranslator( + already_AddRefed aCanvasThreadHolder); + + ~CanvasTranslator(); + + void Bind(Endpoint&& aEndpoint); + + void StartTranslation(); + + void FinishShutdown(); + + void Deactivate(); + + TextureData* CreateTextureData(TextureType aTextureType, + const gfx::IntSize& aSize, + gfx::SurfaceFormat aFormat); + + void AddSurfaceDescriptor(int64_t aTextureId, TextureData* atextureData); + + bool HandleExtensionEvent(int32_t aType); + + bool CreateReferenceTexture(); + bool CheckForFreshCanvasDevice(int aLineNumber); + void NotifyDeviceChanged(); + + RefPtr mCanvasThreadHolder; + RefPtr mTranslationTaskQueue; +#if defined(XP_WIN) + RefPtr mDevice; +#endif + // We hold the ring buffer as a UniquePtr so we can drop it once + // mTranslationTaskQueue has shutdown to break a RefPtr cycle. + UniquePtr mStream; + TextureType mTextureType = TextureType::Unknown; + UniquePtr mReferenceTextureData; + // Sometimes during device reset our reference DrawTarget can be null, so we + // hold the BackendType separately. + gfx::BackendType mBackendType = gfx::BackendType::NONE; + typedef std::unordered_map> TextureMap; + TextureMap mTextureDatas; + int64_t mNextTextureId = -1; + nsRefPtrHashtable, gfx::DataSourceSurface> mDataSurfaces; + gfx::ReferencePtr mMappedSurface; + UniquePtr mPreparedMap; + typedef std::unordered_map> + DescriptorMap; + DescriptorMap mSurfaceDescriptors; + Monitor mSurfaceDescriptorsMonitor{ + "CanvasTranslator::mSurfaceDescriptorsMonitor"}; + Atomic mDeactivated{false}; + bool mIsInTransaction = false; + bool mDeviceResetInProgress = false; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_CanvasTranslator_h diff --git a/gfx/layers/ipc/CompositableForwarder.cpp b/gfx/layers/ipc/CompositableForwarder.cpp new file mode 100644 index 0000000000..369dff086a --- /dev/null +++ b/gfx/layers/ipc/CompositableForwarder.cpp @@ -0,0 +1,15 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "CompositableForwarder.h" + +#include "mozilla/layers/CompositableClient.h" +#include "mozilla/layers/TextureClient.h" + +namespace mozilla::layers { +CompositableForwarder::CompositableForwarder() = default; +CompositableForwarder::~CompositableForwarder() = default; +} // namespace mozilla::layers diff --git a/gfx/layers/ipc/CompositableForwarder.h b/gfx/layers/ipc/CompositableForwarder.h new file mode 100644 index 0000000000..412af3e859 --- /dev/null +++ b/gfx/layers/ipc/CompositableForwarder.h @@ -0,0 +1,125 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_LAYERS_COMPOSITABLEFORWARDER +#define MOZILLA_LAYERS_COMPOSITABLEFORWARDER + +#include // for int32_t, uint32_t, uint64_t +#include "mozilla/Assertions.h" // for AssertionConditionType, MOZ_ASSERT, MOZ_ASSERT_HELPER1 +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/TimeStamp.h" // for TimeStamp +#include "mozilla/layers/KnowsCompositor.h" // for KnowsCompositor +#include "nsRect.h" // for nsIntRect +#include "nsRegion.h" // for nsIntRegion +#include "nsTArray.h" // for nsTArray + +namespace mozilla { +namespace layers { +class CompositableClient; +class CompositableHandle; +class ImageContainer; +class PTextureChild; +class ShadowLayerForwarder; +class SurfaceDescriptorTiles; +class TextureClient; +class ThebesBufferData; + +/** + * A transaction is a set of changes that happenned on the content side, that + * should be sent to the compositor side. + * CompositableForwarder is an interface to manage a transaction of + * compositable objetcs. + * + * ShadowLayerForwarder is an example of a CompositableForwarder (that can + * additionally forward modifications of the Layer tree). + * ImageBridgeChild is another CompositableForwarder. + * + * CompositableForwarder implements KnowsCompositor for simplicity as all + * implementations of CompositableForwarder currently also implement + * KnowsCompositor. This dependency could be split if we add new use cases. + */ +class CompositableForwarder : public KnowsCompositor { + public: + CompositableForwarder(); + ~CompositableForwarder(); + + /** + * Setup the IPDL actor for aCompositable to be part of layers + * transactions. + */ + virtual void Connect(CompositableClient* aCompositable, + ImageContainer* aImageContainer = nullptr) = 0; + + /** + * Tell the CompositableHost on the compositor side what TiledLayerBuffer to + * use for the next composition. + */ + virtual void UseTiledLayerBuffer( + CompositableClient* aCompositable, + const SurfaceDescriptorTiles& aTiledDescriptor) = 0; + + /** + * Communicate to the compositor that aRegion in the texture identified by + * aCompositable and aIdentifier has been updated to aThebesBuffer. + */ + virtual void UpdateTextureRegion(CompositableClient* aCompositable, + const ThebesBufferData& aThebesBufferData, + const nsIntRegion& aUpdatedRegion) = 0; + + virtual void ReleaseCompositable(const CompositableHandle& aHandle) = 0; + virtual bool DestroyInTransaction(PTextureChild* aTexture) = 0; + + /** + * Tell the CompositableHost on the compositor side to remove the texture + * from the CompositableHost. + * This function does not delete the TextureHost corresponding to the + * TextureClient passed in parameter. + * When the TextureClient has TEXTURE_DEALLOCATE_CLIENT flag, + * the transaction becomes synchronous. + */ + virtual void RemoveTextureFromCompositable(CompositableClient* aCompositable, + TextureClient* aTexture) = 0; + + struct TimedTextureClient { + TimedTextureClient() + : mTextureClient(nullptr), mFrameID(0), mProducerID(0) {} + + TextureClient* mTextureClient; + TimeStamp mTimeStamp; + nsIntRect mPictureRect; + int32_t mFrameID; + int32_t mProducerID; + }; + /** + * Tell the CompositableHost on the compositor side what textures to use for + * the next composition. + */ + virtual void UseTextures(CompositableClient* aCompositable, + const nsTArray& aTextures) = 0; + virtual void UseComponentAlphaTextures(CompositableClient* aCompositable, + TextureClient* aClientOnBlack, + TextureClient* aClientOnWhite) = 0; + + virtual void UpdateFwdTransactionId() = 0; + virtual uint64_t GetFwdTransactionId() = 0; + + virtual bool InForwarderThread() = 0; + + void AssertInForwarderThread() { MOZ_ASSERT(InForwarderThread()); } + + static uint32_t GetMaxFileDescriptorsPerMessage(); + + virtual ShadowLayerForwarder* AsLayerForwarder() { return nullptr; } + + protected: + nsTArray> mTexturesToRemove; + nsTArray> mCompositableClientsToRemove; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/ipc/CompositableTransactionParent.cpp b/gfx/layers/ipc/CompositableTransactionParent.cpp new file mode 100644 index 0000000000..5ca2453c54 --- /dev/null +++ b/gfx/layers/ipc/CompositableTransactionParent.cpp @@ -0,0 +1,337 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "CompositableTransactionParent.h" +#include "CompositableHost.h" // for CompositableParent, etc +#include "CompositorBridgeParent.h" // for CompositorBridgeParent +#include "GLContext.h" // for GLContext +#include "Layers.h" // for Layer +#include "RenderTrace.h" // for RenderTraceInvalidateEnd, etc +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/layers/CompositorTypes.h" +#include "mozilla/layers/ContentHost.h" // for ContentHostBase +#include "mozilla/layers/ImageBridgeParent.h" // for ImageBridgeParent +#include "mozilla/layers/LayerManagerComposite.h" +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor +#include "mozilla/layers/LayersTypes.h" // for MOZ_LAYERS_LOG +#include "mozilla/layers/TextureHost.h" // for TextureHost +#include "mozilla/layers/TextureHostOGL.h" // for TextureHostOGL +#include "mozilla/layers/TiledContentHost.h" +#include "mozilla/layers/PaintedLayerComposite.h" +#include "mozilla/mozalloc.h" // for operator delete +#include "mozilla/Unused.h" +#include "nsDebug.h" // for NS_WARNING, NS_ASSERTION +#include "nsRegion.h" // for nsIntRegion + +#ifdef MOZ_WIDGET_ANDROID +# include "mozilla/layers/AndroidHardwareBuffer.h" +#endif + +namespace mozilla { +namespace layers { + +class ClientTiledLayerBuffer; +class Compositor; + +// This function can in some cases fail and return false without it being a bug. +// This can theoretically happen if the ImageBridge sends frames before +// we created the layer tree. Since we can't enforce that the layer +// tree is already created before ImageBridge operates, there isn't much +// we can do about it, but in practice it is very rare. +// Typically when a tab with a video is dragged from a window to another, +// there can be a short time when the video is still sending frames +// asynchonously while the layer tree is not reconstructed. It's not a +// big deal. +// Note that Layers transactions do not need to call this because they always +// schedule the composition, in LayerManagerComposite::EndTransaction. +static bool ScheduleComposition(CompositableHost* aCompositable) { + uint64_t id = aCompositable->GetCompositorBridgeID(); + if (!id) { + return false; + } + CompositorBridgeParent* cp = + CompositorBridgeParent::GetCompositorBridgeParent(id); + if (!cp) { + return false; + } + cp->ScheduleComposition(); + return true; +} + +bool CompositableParentManager::ReceiveCompositableUpdate( + const CompositableOperation& aEdit) { + // Ignore all operations on compositables created on stale compositors. We + // return true because the child is unable to handle errors. + RefPtr compositable = + FindCompositable(aEdit.compositable()); + if (!compositable) { + return false; + } + return ReceiveCompositableUpdate(aEdit.detail(), WrapNotNull(compositable)); +} + +bool CompositableParentManager::ReceiveCompositableUpdate( + const CompositableOperationDetail& aDetail, + NotNull aCompositable) { + if (TextureSourceProvider* provider = + aCompositable->GetTextureSourceProvider()) { + if (!provider->IsValid()) { + return false; + } + } + + switch (aDetail.type()) { + case CompositableOperationDetail::TOpPaintTextureRegion: { + MOZ_LAYERS_LOG(("[ParentSide] Paint PaintedLayer")); + + const OpPaintTextureRegion& op = aDetail.get_OpPaintTextureRegion(); + Layer* layer = aCompositable->GetLayer(); + if (!layer || layer->GetType() != Layer::TYPE_PAINTED) { + return false; + } + PaintedLayerComposite* thebes = + static_cast(layer); + + const ThebesBufferData& bufferData = op.bufferData(); + + RenderTraceInvalidateStart(thebes, "FF00FF", + op.updatedRegion().GetBounds()); + + if (!aCompositable->UpdateThebes(bufferData, op.updatedRegion(), + thebes->GetValidRegion())) { + return false; + } + + RenderTraceInvalidateEnd(thebes, "FF00FF"); + break; + } + case CompositableOperationDetail::TOpUseTiledLayerBuffer: { + MOZ_LAYERS_LOG(("[ParentSide] Paint TiledLayerBuffer")); + const OpUseTiledLayerBuffer& op = aDetail.get_OpUseTiledLayerBuffer(); + TiledContentHost* tiledHost = aCompositable->AsTiledContentHost(); + + NS_ASSERTION(tiledHost, "The compositable is not tiled"); + + const SurfaceDescriptorTiles& tileDesc = op.tileLayerDescriptor(); + + bool success = tiledHost->UseTiledLayerBuffer(this, tileDesc); + + const nsTArray& tileDescriptors = tileDesc.tiles(); + for (size_t i = 0; i < tileDescriptors.Length(); i++) { + const TileDescriptor& tileDesc = tileDescriptors[i]; + if (tileDesc.type() != TileDescriptor::TTexturedTileDescriptor) { + continue; + } + const TexturedTileDescriptor& texturedDesc = + tileDesc.get_TexturedTileDescriptor(); + RefPtr texture = + TextureHost::AsTextureHost(texturedDesc.textureParent()); + if (texture) { + texture->SetLastFwdTransactionId(mFwdTransactionId); + // Make sure that each texture was handled by the compositable + // because the recycling logic depends on it. + MOZ_ASSERT(texture->NumCompositableRefs() > 0); + } + if (texturedDesc.textureOnWhiteParent().isSome()) { + texture = TextureHost::AsTextureHost( + texturedDesc.textureOnWhiteParent().ref()); + if (texture) { + texture->SetLastFwdTransactionId(mFwdTransactionId); + // Make sure that each texture was handled by the compositable + // because the recycling logic depends on it. + MOZ_ASSERT(texture->NumCompositableRefs() > 0); + } + } + } + if (!success) { + return false; + } + break; + } + case CompositableOperationDetail::TOpRemoveTexture: { + const OpRemoveTexture& op = aDetail.get_OpRemoveTexture(); + + RefPtr tex = TextureHost::AsTextureHost(op.textureParent()); + + MOZ_ASSERT(tex.get()); + aCompositable->RemoveTextureHost(tex); + break; + } + case CompositableOperationDetail::TOpUseTexture: { + const OpUseTexture& op = aDetail.get_OpUseTexture(); + + AutoTArray textures; + for (auto& timedTexture : op.textures()) { + CompositableHost::TimedTexture* t = textures.AppendElement(); + t->mTexture = TextureHost::AsTextureHost(timedTexture.textureParent()); + MOZ_ASSERT(t->mTexture); + t->mTimeStamp = timedTexture.timeStamp(); + t->mPictureRect = timedTexture.picture(); + t->mFrameID = timedTexture.frameID(); + t->mProducerID = timedTexture.producerID(); + if (timedTexture.readLocked()) { + t->mTexture->SetReadLocked(); + } + } + if (textures.Length() > 0) { + aCompositable->UseTextureHost(textures); + + for (auto& timedTexture : op.textures()) { + RefPtr texture = + TextureHost::AsTextureHost(timedTexture.textureParent()); + if (texture) { + texture->SetLastFwdTransactionId(mFwdTransactionId); + // Make sure that each texture was handled by the compositable + // because the recycling logic depends on it. + MOZ_ASSERT(texture->NumCompositableRefs() > 0); + } + } + } + + if (UsesImageBridge() && aCompositable->GetLayer()) { + ScheduleComposition(aCompositable); + } + break; + } + case CompositableOperationDetail::TOpUseComponentAlphaTextures: { + const OpUseComponentAlphaTextures& op = + aDetail.get_OpUseComponentAlphaTextures(); + RefPtr texOnBlack = + TextureHost::AsTextureHost(op.textureOnBlackParent()); + RefPtr texOnWhite = + TextureHost::AsTextureHost(op.textureOnWhiteParent()); + if (op.readLockedBlack()) { + texOnBlack->SetReadLocked(); + } + if (op.readLockedWhite()) { + texOnWhite->SetReadLocked(); + } + + MOZ_ASSERT(texOnBlack && texOnWhite); + aCompositable->UseComponentAlphaTextures(texOnBlack, texOnWhite); + + if (texOnBlack) { + texOnBlack->SetLastFwdTransactionId(mFwdTransactionId); + // Make sure that each texture was handled by the compositable + // because the recycling logic depends on it. + MOZ_ASSERT(texOnBlack->NumCompositableRefs() > 0); + } + + if (texOnWhite) { + texOnWhite->SetLastFwdTransactionId(mFwdTransactionId); + // Make sure that each texture was handled by the compositable + // because the recycling logic depends on it. + MOZ_ASSERT(texOnWhite->NumCompositableRefs() > 0); + } + + if (UsesImageBridge()) { + ScheduleComposition(aCompositable); + } + break; + } + case CompositableOperationDetail::TOpDeliverAcquireFence: { + const OpDeliverAcquireFence& op = aDetail.get_OpDeliverAcquireFence(); + RefPtr tex = TextureHost::AsTextureHost(op.textureParent()); + MOZ_ASSERT(tex.get()); + MOZ_ASSERT(tex->AsAndroidHardwareBufferTextureHost()); + + auto fenceFd = op.fenceFd(); + tex->SetAcquireFence(std::move(fenceFd)); + break; + } + default: { + MOZ_ASSERT(false, "bad type"); + } + } + + return true; +} + +void CompositableParentManager::DestroyActor(const OpDestroy& aOp) { + switch (aOp.type()) { + case OpDestroy::TPTextureParent: { + auto actor = aOp.get_PTextureParent(); + TextureHost::ReceivedDestroy(actor); + break; + } + case OpDestroy::TCompositableHandle: { + ReleaseCompositable(aOp.get_CompositableHandle()); + break; + } + default: { + MOZ_ASSERT(false, "unsupported type"); + } + } +} + +RefPtr CompositableParentManager::AddCompositable( + const CompositableHandle& aHandle, const TextureInfo& aInfo, + bool aUseWebRender) { + if (mCompositables.find(aHandle.Value()) != mCompositables.end()) { + NS_ERROR("Client should not allocate duplicate handles"); + return nullptr; + } + if (!aHandle) { + NS_ERROR("Client should not allocate 0 as a handle"); + return nullptr; + } + + RefPtr host = + CompositableHost::Create(aInfo, aUseWebRender); + if (!host) { + return nullptr; + } + + mCompositables[aHandle.Value()] = host; + return host; +} + +RefPtr CompositableParentManager::FindCompositable( + const CompositableHandle& aHandle, bool aAllowDisablingWebRender) { + auto iter = mCompositables.find(aHandle.Value()); + if (iter == mCompositables.end()) { + return nullptr; + } + + RefPtr host = iter->second; + if (!aAllowDisablingWebRender) { + return host; + } + + if (!host->AsWebRenderImageHost() || !host->GetAsyncRef()) { + return host; + } + + // Try to replace WebRenderImageHost of ImageBridge to ImageHost. + RefPtr newHost = CompositableHost::Create( + host->GetTextureInfo(), /* aUseWebRender */ false); + if (!newHost || !newHost->AsImageHost()) { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + return host; + } + + newHost->SetAsyncRef(host->GetAsyncRef()); + mCompositables[aHandle.Value()] = newHost; + + return newHost; +} + +void CompositableParentManager::ReleaseCompositable( + const CompositableHandle& aHandle) { + auto iter = mCompositables.find(aHandle.Value()); + if (iter == mCompositables.end()) { + return; + } + + RefPtr host = iter->second; + mCompositables.erase(iter); + + host->Detach(nullptr, CompositableHost::FORCE_DETACH); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/CompositableTransactionParent.h b/gfx/layers/ipc/CompositableTransactionParent.h new file mode 100644 index 0000000000..8912f50bce --- /dev/null +++ b/gfx/layers/ipc/CompositableTransactionParent.h @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_LAYERS_COMPOSITABLETRANSACTIONPARENT_H +#define MOZILLA_LAYERS_COMPOSITABLETRANSACTIONPARENT_H + +#include // for vector +#include "mozilla/Attributes.h" // for override +#include "mozilla/NotNull.h" +#include "mozilla/layers/ISurfaceAllocator.h" // for ISurfaceAllocator +#include "mozilla/layers/LayersMessages.h" // for EditReply, etc +#include "mozilla/layers/TextureClient.h" +#include "CompositableHost.h" + +namespace mozilla { +namespace layers { + +// Since PCompositble has two potential manager protocols, we can't just call +// the Manager() method usually generated when there's one manager protocol, +// so both manager protocols implement this and we keep a reference to them +// through this interface. +class CompositableParentManager : public HostIPCAllocator { + public: + CompositableParentManager() = default; + + void DestroyActor(const OpDestroy& aOp); + + void UpdateFwdTransactionId(uint64_t aTransactionId) { + MOZ_ASSERT(mFwdTransactionId < aTransactionId); + mFwdTransactionId = aTransactionId; + } + + uint64_t GetFwdTransactionId() { return mFwdTransactionId; } + + RefPtr AddCompositable(const CompositableHandle& aHandle, + const TextureInfo& aInfo, + bool aUseWebRender); + RefPtr FindCompositable( + const CompositableHandle& aHandle, bool aAllowDisablingWebRender = false); + + protected: + /** + * Handle the IPDL messages that affect PCompositable actors. + */ + bool ReceiveCompositableUpdate(const CompositableOperation& aEdit); + bool ReceiveCompositableUpdate(const CompositableOperationDetail& aDetail, + NotNull aCompositable); + + void ReleaseCompositable(const CompositableHandle& aHandle); + + uint64_t mFwdTransactionId = 0; + + /** + * Mapping form IDs to CompositableHosts. + */ + std::map> mCompositables; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/ipc/CompositorBench.cpp b/gfx/layers/ipc/CompositorBench.cpp new file mode 100644 index 0000000000..0fb4d8012b --- /dev/null +++ b/gfx/layers/ipc/CompositorBench.cpp @@ -0,0 +1,352 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "CompositorBench.h" + +#ifdef MOZ_COMPOSITOR_BENCH +# include "mozilla/gfx/2D.h" +# include "mozilla/layers/Compositor.h" +# include "mozilla/layers/Effects.h" +# include "mozilla/ProfilerMarkers.h" +# include "mozilla/StaticPrefs_layers.h" +# include "mozilla/TimeStamp.h" +# include + +# define TEST_STEPS 1000 +# define DURATION_THRESHOLD 30 +# define THRESHOLD_ABORT_COUNT 5 + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +static float SimplePseudoRandom(int aStep, int aCount) { + srand(aStep * 1000 + aCount); + return static_cast(rand()) / static_cast(RAND_MAX); +} + +class BenchTest { + public: + BenchTest(const char* aTestName) : mTestName(aTestName) {} + + virtual ~BenchTest() = default; + + virtual void Setup(Compositor* aCompositor, size_t aStep) {} + virtual void Teardown(Compositor* aCompositor) {} + virtual void DrawFrame(Compositor* aCompositor, const gfx::Rect& aScreenRect, + size_t aStep) = 0; + + const char* ToString() { return mTestName; } + + private: + const char* mTestName; +}; + +static void DrawFrameTrivialQuad(Compositor* aCompositor, + const gfx::Rect& aScreenRect, size_t aStep, + const EffectChain& effects) { + for (size_t i = 0; i < aStep * 10; i++) { + const gfx::Rect& rect = gfx::Rect(i % (int)aScreenRect.width, + (int)(i / aScreenRect.height), 1, 1); + const gfx::Rect& clipRect = aScreenRect; + + float opacity = 1.f; + + gfx::Matrix transform2d; + + gfx::Matrix4x4 transform = gfx::Matrix4x4::From2D(transform2d); + + aCompositor->DrawQuad(rect, clipRect, effects, opacity, transform); + } +} + +static void DrawFrameStressQuad(Compositor* aCompositor, + const gfx::Rect& aScreenRect, size_t aStep, + const EffectChain& effects) { + for (size_t i = 0; i < aStep * 10; i++) { + const gfx::Rect& rect = + gfx::Rect(aScreenRect.width * SimplePseudoRandom(i, 0), + aScreenRect.height * SimplePseudoRandom(i, 1), + aScreenRect.width * SimplePseudoRandom(i, 2), + aScreenRect.height * SimplePseudoRandom(i, 3)); + const gfx::Rect& clipRect = aScreenRect; + + float opacity = 1.f; + + gfx::Matrix transform2d; + transform2d = transform2d.PreRotate(SimplePseudoRandom(i, 4) * 70.f); + + gfx::Matrix4x4 transform = gfx::Matrix4x4::From2D(transform2d); + + aCompositor->DrawQuad(rect, clipRect, effects, opacity, transform); + } +} + +class EffectSolidColorBench : public BenchTest { + public: + EffectSolidColorBench() + : BenchTest("EffectSolidColorBench (clear frame with EffectSolidColor)") { + } + + void DrawFrame(Compositor* aCompositor, const gfx::Rect& aScreenRect, + size_t aStep) { + float tmp; + float red = modff(aStep * 0.03f, &tmp); + EffectChain effects; + effects.mPrimaryEffect = + new EffectSolidColor(gfx::DeviceColor(red, 0.4f, 0.4f, 1.0f)); + + const gfx::Rect& rect = aScreenRect; + const gfx::Rect& clipRect = aScreenRect; + + float opacity = 1.f; + + gfx::Matrix4x4 transform; + aCompositor->DrawQuad(rect, clipRect, effects, opacity, transform); + } +}; + +class EffectSolidColorTrivialBench : public BenchTest { + public: + EffectSolidColorTrivialBench() + : BenchTest("EffectSolidColorTrivialBench (10s 1x1 EffectSolidColor)") {} + + void DrawFrame(Compositor* aCompositor, const gfx::Rect& aScreenRect, + size_t aStep) { + EffectChain effects; + effects.mPrimaryEffect = CreateEffect(aStep); + + DrawFrameTrivialQuad(aCompositor, aScreenRect, aStep, effects); + } + + already_AddRefed CreateEffect(size_t i) { + float tmp; + float red = modff(i * 0.03f, &tmp); + EffectChain effects; + return MakeAndAddRef( + gfx::DeviceColor(red, 0.4f, 0.4f, 1.0f)); + } +}; + +class EffectSolidColorStressBench : public BenchTest { + public: + EffectSolidColorStressBench() + : BenchTest( + "EffectSolidColorStressBench (10s various EffectSolidColor)") {} + + void DrawFrame(Compositor* aCompositor, const gfx::Rect& aScreenRect, + size_t aStep) { + EffectChain effects; + effects.mPrimaryEffect = CreateEffect(aStep); + + DrawFrameStressQuad(aCompositor, aScreenRect, aStep, effects); + } + + already_AddRefed CreateEffect(size_t i) { + float tmp; + float red = modff(i * 0.03f, &tmp); + EffectChain effects; + return MakeAndAddRef( + gfx::DeviceColor(red, 0.4f, 0.4f, 1.0f)); + } +}; + +class UploadBench : public BenchTest { + public: + UploadBench() : BenchTest("Upload Bench (10s 256x256 upload)") {} + + uint32_t* mBuf; + RefPtr mSurface; + RefPtr mTexture; + + virtual void Setup(Compositor* aCompositor, size_t aStep) { + int bytesPerPixel = 4; + int w = 256; + int h = 256; + mBuf = (uint32_t*)malloc(w * h * sizeof(uint32_t)); + + mSurface = Factory::CreateWrappingDataSourceSurface( + reinterpret_cast(mBuf), w * bytesPerPixel, IntSize(w, h), + SurfaceFormat::B8G8R8A8); + mTexture = aCompositor->CreateDataTextureSource(); + } + + virtual void Teardown(Compositor* aCompositor) { + mSurface = nullptr; + mTexture = nullptr; + free(mBuf); + } + + void DrawFrame(Compositor* aCompositor, const gfx::Rect& aScreenRect, + size_t aStep) { + for (size_t i = 0; i < aStep * 10; i++) { + mTexture->Update(mSurface); + } + } +}; + +class TrivialTexturedQuadBench : public BenchTest { + public: + TrivialTexturedQuadBench() + : BenchTest("Trvial Textured Quad (10s 256x256 quads)") {} + + uint32_t* mBuf; + RefPtr mSurface; + RefPtr mTexture; + + virtual void Setup(Compositor* aCompositor, size_t aStep) { + int bytesPerPixel = 4; + size_t w = 256; + size_t h = 256; + mBuf = (uint32_t*)malloc(w * h * sizeof(uint32_t)); + for (size_t i = 0; i < w * h; i++) { + mBuf[i] = 0xFF00008F; + } + + mSurface = Factory::CreateWrappingDataSourceSurface( + reinterpret_cast(mBuf), w * bytesPerPixel, IntSize(w, h), + SurfaceFormat::B8G8R8A8); + mTexture = aCompositor->CreateDataTextureSource(); + mTexture->Update(mSurface); + } + + void DrawFrame(Compositor* aCompositor, const gfx::Rect& aScreenRect, + size_t aStep) { + EffectChain effects; + effects.mPrimaryEffect = CreateEffect(aStep); + + DrawFrameTrivialQuad(aCompositor, aScreenRect, aStep, effects); + } + + virtual void Teardown(Compositor* aCompositor) { + mSurface = nullptr; + mTexture = nullptr; + free(mBuf); + } + + already_AddRefed CreateEffect(size_t i) { + return CreateTexturedEffect(SurfaceFormat::B8G8R8A8, mTexture, + SamplingFilter::POINT, true); + } +}; + +class StressTexturedQuadBench : public BenchTest { + public: + StressTexturedQuadBench() + : BenchTest("Stress Textured Quad (10s 256x256 quads)") {} + + uint32_t* mBuf; + RefPtr mSurface; + RefPtr mTexture; + + virtual void Setup(Compositor* aCompositor, size_t aStep) { + int bytesPerPixel = 4; + size_t w = 256; + size_t h = 256; + mBuf = (uint32_t*)malloc(w * h * sizeof(uint32_t)); + for (size_t i = 0; i < w * h; i++) { + mBuf[i] = 0xFF00008F; + } + + mSurface = Factory::CreateWrappingDataSourceSurface( + reinterpret_cast(mBuf), w * bytesPerPixel, IntSize(w, h), + SurfaceFormat::B8G8R8A8); + mTexture = aCompositor->CreateDataTextureSource(); + mTexture->Update(mSurface); + } + + void DrawFrame(Compositor* aCompositor, const gfx::Rect& aScreenRect, + size_t aStep) { + EffectChain effects; + effects.mPrimaryEffect = CreateEffect(aStep); + + DrawFrameStressQuad(aCompositor, aScreenRect, aStep, effects); + } + + virtual void Teardown(Compositor* aCompositor) { + mSurface = nullptr; + mTexture = nullptr; + free(mBuf); + } + + virtual already_AddRefed CreateEffect(size_t i) { + return CreateTexturedEffect(SurfaceFormat::B8G8R8A8, mTexture, + SamplingFilter::POINT, true); + } +}; + +static void RunCompositorBench(Compositor* aCompositor, + const gfx::Rect& aScreenRect) { + std::vector tests; + + tests.push_back(new EffectSolidColorBench()); + tests.push_back(new UploadBench()); + tests.push_back(new EffectSolidColorTrivialBench()); + tests.push_back(new EffectSolidColorStressBench()); + tests.push_back(new TrivialTexturedQuadBench()); + tests.push_back(new StressTexturedQuadBench()); + + for (size_t i = 0; i < tests.size(); i++) { + BenchTest* test = tests[i]; + std::vector results; + int testsOverThreshold = 0; + PROFILER_MARKER_UNTYPED( + ProfilerString8View::WrapNullTerminatedString(test->ToString()), + GRAPHICS); + for (size_t j = 0; j < TEST_STEPS; j++) { + test->Setup(aCompositor, j); + + TimeStamp start = TimeStamp::Now(); + IntRect screenRect(aScreenRect.x, aScreenRect.y, aScreenRect.width, + aScreenRect.height); + aCompositor->BeginFrame(IntRect(screenRect.x, screenRect.y, + screenRect.width, screenRect.height), + nullptr, aScreenRect, nullptr, nullptr); + + test->DrawFrame(aCompositor, aScreenRect, j); + + aCompositor->EndFrame(); + results.push_back(TimeStamp::Now() - start); + + if (results[j].ToMilliseconds() > DURATION_THRESHOLD) { + testsOverThreshold++; + if (testsOverThreshold == THRESHOLD_ABORT_COUNT) { + test->Teardown(aCompositor); + break; + } + } else { + testsOverThreshold = 0; + } + test->Teardown(aCompositor); + } + + printf_stderr("%s\n", test->ToString()); + printf_stderr("Run step, Time (ms)\n"); + for (size_t j = 0; j < results.size(); j++) { + printf_stderr("%i,%f\n", j, (float)results[j].ToMilliseconds()); + } + printf_stderr("\n", test->ToString()); + } + + for (size_t i = 0; i < tests.size(); i++) { + delete tests[i]; + } +} + +void CompositorBench(Compositor* aCompositor, const gfx::IntRect& aScreenRect) { + static bool sRanBenchmark = false; + bool wantBenchmark = StaticPrefs::layers_bench_enabled(); + if (wantBenchmark && !sRanBenchmark) { + RunCompositorBench(aCompositor, aScreenRect); + } + sRanBenchmark = wantBenchmark; +} + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/ipc/CompositorBench.h b/gfx/layers/ipc/CompositorBench.h new file mode 100644 index 0000000000..4a3c5874e9 --- /dev/null +++ b/gfx/layers/ipc/CompositorBench.h @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_CompositorBench_h +#define mozilla_layers_CompositorBench_h + +#include "mozilla/gfx/Rect.h" // for Rect + +namespace mozilla { +namespace layers { + +class Compositor; + +// Uncomment this line to rebuild with compositor bench. +// #define MOZ_COMPOSITOR_BENCH + +#ifdef MOZ_COMPOSITOR_BENCH +void CompositorBench(Compositor* aCompositor, const gfx::IntRect& aScreenRect); +#else +static inline void CompositorBench(Compositor* aCompositor, + const gfx::IntRect& aScreenRect) {} +#endif + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/ipc/CompositorBridgeChild.cpp b/gfx/layers/ipc/CompositorBridgeChild.cpp new file mode 100644 index 0000000000..442989a1af --- /dev/null +++ b/gfx/layers/ipc/CompositorBridgeChild.cpp @@ -0,0 +1,1310 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/CompositorBridgeChild.h" +#include "mozilla/layers/CompositorBridgeParent.h" +#include "mozilla/layers/CompositorThread.h" +#include // for size_t +#include "ClientLayerManager.h" // for ClientLayerManager +#include "base/task.h" // for NewRunnableMethod, etc +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/layers/CompositorManagerChild.h" +#include "mozilla/layers/ImageBridgeChild.h" +#include "mozilla/layers/APZChild.h" +#include "mozilla/layers/IAPZCTreeManager.h" +#include "mozilla/layers/APZCTreeManagerChild.h" +#include "mozilla/layers/CanvasChild.h" +#include "mozilla/layers/LayerTransactionChild.h" +#include "mozilla/layers/PaintThread.h" +#include "mozilla/layers/PLayerTransactionChild.h" +#include "mozilla/layers/PTextureChild.h" +#include "mozilla/layers/TextureClient.h" // for TextureClient +#include "mozilla/layers/TextureClientPool.h" // for TextureClientPool +#include "mozilla/layers/WebRenderBridgeChild.h" +#include "mozilla/layers/SyncObject.h" // for SyncObjectClient +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/gfx/GPUProcessManager.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/webgpu/WebGPUChild.h" +#include "mozilla/mozalloc.h" // for operator new, etc +#include "mozilla/Telemetry.h" +#include "gfxConfig.h" +#include "nsDebug.h" // for NS_WARNING +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "nsTArray.h" // for nsTArray, nsTArray_Impl +#include "FrameLayerBuilder.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/Unused.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "nsThreadUtils.h" +#if defined(XP_WIN) +# include "WinUtils.h" +#endif +#include "mozilla/widget/CompositorWidget.h" +#ifdef MOZ_WIDGET_SUPPORTS_OOP_COMPOSITING +# include "mozilla/widget/CompositorWidgetChild.h" +#endif +#include "VsyncSource.h" + +#ifdef MOZ_WIDGET_ANDROID +# include "mozilla/layers/AndroidHardwareBuffer.h" +#endif + +using mozilla::Unused; +using mozilla::gfx::GPUProcessManager; +using mozilla::layers::LayerTransactionChild; + +namespace mozilla { +namespace layers { + +static int sShmemCreationCounter = 0; + +static void ResetShmemCounter() { sShmemCreationCounter = 0; } + +static void ShmemAllocated(CompositorBridgeChild* aProtocol) { + sShmemCreationCounter++; + if (sShmemCreationCounter > 256) { + aProtocol->SendSyncWithCompositor(); + ResetShmemCounter(); + MOZ_PERFORMANCE_WARNING( + "gfx", "The number of shmem allocations is too damn high!"); + } +} + +static StaticRefPtr sCompositorBridge; + +Atomic KnowsCompositor::sSerialCounter(0); + +CompositorBridgeChild::CompositorBridgeChild(CompositorManagerChild* aManager) + : mCompositorManager(aManager), + mIdNamespace(0), + mResourceId(0), + mCanSend(false), + mActorDestroyed(false), + mFwdTransactionId(0), + mThread(NS_GetCurrentThread()), + mProcessToken(0), + mSectionAllocator(nullptr), + mPaintLock("CompositorBridgeChild.mPaintLock"), + mTotalAsyncPaints(0), + mOutstandingAsyncPaints(0), + mOutstandingAsyncEndTransaction(false), + mIsDelayingForAsyncPaints(false), + mSlowFlushCount(0), + mTotalFlushCount(0) { + MOZ_ASSERT(NS_IsMainThread()); +} + +CompositorBridgeChild::~CompositorBridgeChild() { + if (mCanSend) { + gfxCriticalError() << "CompositorBridgeChild was not deinitialized"; + } +} + +bool CompositorBridgeChild::IsSameProcess() const { + return OtherPid() == base::GetCurrentProcId(); +} + +void CompositorBridgeChild::PrepareFinalDestroy() { + // Because of medium high priority DidComposite, we need to repost to + // medium high priority queue to ensure the actor is destroyed after possible + // pending DidComposite message. + nsCOMPtr runnable = + NewRunnableMethod("CompositorBridgeChild::AfterDestroy", this, + &CompositorBridgeChild::AfterDestroy); + NS_DispatchToCurrentThreadQueue(runnable.forget(), + EventQueuePriority::MediumHigh); +} + +void CompositorBridgeChild::AfterDestroy() { + // Note that we cannot rely upon mCanSend here because we already set that to + // false to prevent normal IPDL calls from being made after SendWillClose. + // The only time we should not issue Send__delete__ is if the actor is already + // destroyed, e.g. the compositor process crashed. + if (!mActorDestroyed) { + Send__delete__(this); + mActorDestroyed = true; + } + + if (mCanvasChild) { + mCanvasChild->Destroy(); + } + + if (sCompositorBridge == this) { + sCompositorBridge = nullptr; + } +} + +void CompositorBridgeChild::Destroy() { + // This must not be called from the destructor! + mTexturesWaitingNotifyNotUsed.clear(); + + // Destroying the layer manager may cause all sorts of things to happen, so + // let's make sure there is still a reference to keep this alive whatever + // happens. + RefPtr selfRef = this; + + for (size_t i = 0; i < mTexturePools.Length(); i++) { + mTexturePools[i]->Destroy(); + } + + if (mSectionAllocator) { + delete mSectionAllocator; + mSectionAllocator = nullptr; + } + + if (mLayerManager) { + mLayerManager->Destroy(); + mLayerManager = nullptr; + } + + // Flush async paints before we destroy texture data. + FlushAsyncPaints(); + + if (!mCanSend) { + // We may have already called destroy but still have lingering references + // or CompositorBridgeChild::ActorDestroy was called. Ensure that we do our + // post destroy clean up no matter what. It is safe to call multiple times. + NS_GetCurrentThread()->Dispatch( + NewRunnableMethod("CompositorBridgeChild::PrepareFinalDestroy", selfRef, + &CompositorBridgeChild::PrepareFinalDestroy)); + return; + } + + AutoTArray transactions; + ManagedPLayerTransactionChild(transactions); + for (int i = transactions.Length() - 1; i >= 0; --i) { + RefPtr layers = + static_cast(transactions[i]); + layers->Destroy(); + } + + AutoTArray wrBridges; + ManagedPWebRenderBridgeChild(wrBridges); + for (int i = wrBridges.Length() - 1; i >= 0; --i) { + RefPtr wrBridge = + static_cast(wrBridges[i]); + wrBridge->Destroy(/* aIsSync */ false); + } + + AutoTArray apzChildren; + ManagedPAPZChild(apzChildren); + for (PAPZChild* child : apzChildren) { + Unused << child->SendDestroy(); + } + + AutoTArray webGPUChildren; + ManagedPWebGPUChild(webGPUChildren); + for (PWebGPUChild* child : webGPUChildren) { + Unused << child->SendShutdown(); + } + + const ManagedContainer& textures = ManagedPTextureChild(); + for (auto iter = textures.ConstIter(); !iter.Done(); iter.Next()) { + RefPtr texture = + TextureClient::AsTextureClient(iter.Get()->GetKey()); + + if (texture) { + texture->Destroy(); + } + } + + // The WillClose message is synchronous, so we know that after it returns + // any messages sent by the above code will have been processed on the + // other side. + SendWillClose(); + mCanSend = false; + + // We no longer care about unexpected shutdowns, in the remote process case. + mProcessToken = 0; + + // The call just made to SendWillClose can result in IPC from the + // CompositorBridgeParent to the CompositorBridgeChild (e.g. caused by the + // destruction of shared memory). We need to ensure this gets processed by the + // CompositorBridgeChild before it gets destroyed. It suffices to ensure that + // events already in the thread get processed before the + // CompositorBridgeChild is destroyed, so we add a task to the thread to + // handle compositor destruction. + + // From now on we can't send any message message. + NS_GetCurrentThread()->Dispatch( + NewRunnableMethod("CompositorBridgeChild::PrepareFinalDestroy", selfRef, + &CompositorBridgeChild::PrepareFinalDestroy)); +} + +// static +void CompositorBridgeChild::ShutDown() { + if (sCompositorBridge) { + sCompositorBridge->Destroy(); + SpinEventLoopUntil([&]() { return !sCompositorBridge; }); + } +} + +bool CompositorBridgeChild::LookupCompositorFrameMetrics( + const ScrollableLayerGuid::ViewID aId, FrameMetrics& aFrame) { + SharedFrameMetricsData* data = mFrameMetricsTable.Get(aId); + if (data) { + data->CopyFrameMetrics(&aFrame); + return true; + } + return false; +} + +void CompositorBridgeChild::InitForContent(uint32_t aNamespace) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aNamespace); + + if (RefPtr old = sCompositorBridge.forget()) { + // Note that at this point, ActorDestroy may not have been called yet, + // meaning mCanSend is still true. In this case we will try to send a + // synchronous WillClose message to the parent, and will certainly get + // a false result and a MsgDropped processing error. This is okay. + old->Destroy(); + } + + mCanSend = true; + mIdNamespace = aNamespace; + + sCompositorBridge = this; +} + +void CompositorBridgeChild::InitForWidget(uint64_t aProcessToken, + LayerManager* aLayerManager, + uint32_t aNamespace) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aProcessToken); + MOZ_ASSERT(aLayerManager); + MOZ_ASSERT(aNamespace); + + mCanSend = true; + mProcessToken = aProcessToken; + mLayerManager = aLayerManager; + mIdNamespace = aNamespace; +} + +/*static*/ +CompositorBridgeChild* CompositorBridgeChild::Get() { + // This is only expected to be used in child processes. While the parent + // process does have CompositorBridgeChild instances, it has _multiple_ (one + // per window), and therefore there is no global singleton available. + MOZ_ASSERT(!XRE_IsParentProcess()); + return sCompositorBridge; +} + +// static +bool CompositorBridgeChild::ChildProcessHasCompositorBridge() { + return sCompositorBridge != nullptr; +} + +/* static */ +bool CompositorBridgeChild::CompositorIsInGPUProcess() { + MOZ_ASSERT(NS_IsMainThread()); + + if (XRE_IsParentProcess()) { + return !!GPUProcessManager::Get()->GetGPUChild(); + } + + MOZ_ASSERT(XRE_IsContentProcess()); + CompositorBridgeChild* bridge = CompositorBridgeChild::Get(); + if (!bridge) { + return false; + } + + return bridge->OtherPid() != dom::ContentChild::GetSingleton()->OtherPid(); +} + +PLayerTransactionChild* CompositorBridgeChild::AllocPLayerTransactionChild( + const nsTArray& aBackendHints, const LayersId& aId) { + LayerTransactionChild* c = new LayerTransactionChild(aId); + c->AddIPDLReference(); + + return c; +} + +bool CompositorBridgeChild::DeallocPLayerTransactionChild( + PLayerTransactionChild* actor) { + LayersId childId = static_cast(actor)->GetId(); + ClearSharedFrameMetricsData(childId); + static_cast(actor)->ReleaseIPDLReference(); + return true; +} + +mozilla::ipc::IPCResult CompositorBridgeChild::RecvInvalidateLayers( + const LayersId& aLayersId) { + if (mLayerManager) { + MOZ_ASSERT(!aLayersId.IsValid()); + FrameLayerBuilder::InvalidateAllLayers(mLayerManager); + } else if (aLayersId.IsValid()) { + if (dom::BrowserChild* child = dom::BrowserChild::GetFrom(aLayersId)) { + child->InvalidateLayers(); + } + } + return IPC_OK(); +} + +#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) +static void CalculatePluginClip( + const LayoutDeviceIntRect& aBounds, + const nsTArray& aPluginClipRects, + const LayoutDeviceIntPoint& aContentOffset, + const LayoutDeviceIntRegion& aParentLayerVisibleRegion, + nsTArray& aResult, LayoutDeviceIntRect& aVisibleBounds, + bool& aPluginIsVisible) { + aPluginIsVisible = true; + LayoutDeviceIntRegion contentVisibleRegion; + // aPluginClipRects (plugin widget origin) - contains *visible* rects + for (uint32_t idx = 0; idx < aPluginClipRects.Length(); idx++) { + LayoutDeviceIntRect rect = aPluginClipRects[idx]; + // shift to content origin + rect.MoveBy(aBounds.X(), aBounds.Y()); + // accumulate visible rects + contentVisibleRegion.OrWith(rect); + } + // apply layers clip (window origin) + LayoutDeviceIntRegion region = aParentLayerVisibleRegion; + region.MoveBy(-aContentOffset.x, -aContentOffset.y); + contentVisibleRegion.AndWith(region); + if (contentVisibleRegion.IsEmpty()) { + aPluginIsVisible = false; + return; + } + // shift to plugin widget origin + contentVisibleRegion.MoveBy(-aBounds.X(), -aBounds.Y()); + for (auto iter = contentVisibleRegion.RectIter(); !iter.Done(); iter.Next()) { + const LayoutDeviceIntRect& rect = iter.Get(); + aResult.AppendElement(rect); + aVisibleBounds.UnionRect(aVisibleBounds, rect); + } +} +#endif + +mozilla::ipc::IPCResult CompositorBridgeChild::RecvUpdatePluginConfigurations( + const LayoutDeviceIntPoint& aContentOffset, + const LayoutDeviceIntRegion& aParentLayerVisibleRegion, + nsTArray&& aPlugins) { +#if !defined(XP_WIN) && !defined(MOZ_WIDGET_GTK) + MOZ_ASSERT_UNREACHABLE( + "CompositorBridgeChild::RecvUpdatePluginConfigurations" + " calls unexpected on this platform."); + return IPC_FAIL_NO_REASON(this); +#else + // Now that we are on the main thread, update plugin widget config. + // This should happen a little before we paint to the screen assuming + // the main thread is running freely. + DebugOnly rv; + MOZ_ASSERT(NS_IsMainThread()); + + // Tracks visible plugins we update, so we can hide any plugins we don't. + nsTArray visiblePluginIds; + nsIWidget* parent = nullptr; + for (uint32_t pluginsIdx = 0; pluginsIdx < aPlugins.Length(); pluginsIdx++) { + nsIWidget* widget = nsIWidget::LookupRegisteredPluginWindow( + aPlugins[pluginsIdx].windowId()); + if (!widget) { + NS_WARNING("Unexpected, plugin id not found!"); + continue; + } + if (!parent) { + parent = widget->GetParent(); + } + bool isVisible = aPlugins[pluginsIdx].visible(); + if (widget && !widget->Destroyed()) { + LayoutDeviceIntRect bounds; + LayoutDeviceIntRect visibleBounds; + // If the plugin is visible update it's geometry. + if (isVisible) { + // Set bounds (content origin) + bounds = aPlugins[pluginsIdx].bounds(); + nsTArray rectsOut; + // This call may change the value of isVisible + CalculatePluginClip(bounds, aPlugins[pluginsIdx].clip(), aContentOffset, + aParentLayerVisibleRegion, rectsOut, visibleBounds, + isVisible); + // content clipping region (widget origin) + rv = widget->SetWindowClipRegion(rectsOut, false); + NS_ASSERTION(NS_SUCCEEDED(rv), "widget call failure"); + // This will trigger a browser window paint event for areas uncovered + // by a child window move, and will call invalidate on the plugin + // parent window which the browser owns. The latter gets picked up in + // our OnPaint handler and forwarded over to the plugin process async. + widget->Resize(aContentOffset.x + bounds.X(), + aContentOffset.y + bounds.Y(), bounds.Width(), + bounds.Height(), true); + } + + widget->Enable(isVisible); + + // visible state - updated after clipping, prior to invalidating + widget->Show(isVisible); + + // Handle invalidation, this can be costly, avoid if it is not needed. + if (isVisible) { + // invalidate region (widget origin) +# if defined(XP_WIN) + // Work around for flash's crummy sandbox. See bug 762948. This call + // digs down into the window hirearchy, invalidating regions on + // windows owned by other processes. + mozilla::widget::WinUtils::InvalidatePluginAsWorkaround(widget, + visibleBounds); +# else + widget->Invalidate(visibleBounds); +# endif + visiblePluginIds.AppendElement(aPlugins[pluginsIdx].windowId()); + } + } + } + // Any plugins we didn't update need to be hidden, as they are + // not associated with visible content. + nsIWidget::UpdateRegisteredPluginWindowVisibility((uintptr_t)parent, + visiblePluginIds); + if (!mCanSend) { + return IPC_OK(); + } + SendRemotePluginsReady(); + return IPC_OK(); +#endif // !defined(XP_WIN) && !defined(MOZ_WIDGET_GTK) +} + +#if defined(XP_WIN) +static void ScheduleSendAllPluginsCaptured(CompositorBridgeChild* aThis, + nsISerialEventTarget* aThread) { + aThread->Dispatch(NewNonOwningRunnableMethod( + "CompositorBridgeChild::SendAllPluginsCaptured", aThis, + &CompositorBridgeChild::SendAllPluginsCaptured)); +} +#endif + +mozilla::ipc::IPCResult CompositorBridgeChild::RecvCaptureAllPlugins( + const uintptr_t& aParentWidget) { +#if defined(XP_WIN) + MOZ_ASSERT(NS_IsMainThread()); + nsIWidget::CaptureRegisteredPlugins(aParentWidget); + + // Bounce the call to SendAllPluginsCaptured off the ImageBridgeChild thread, + // to make sure that the image updates on that thread have been processed. + ImageBridgeChild::GetSingleton()->GetThread()->Dispatch(NewRunnableFunction( + "ScheduleSendAllPluginsCapturedRunnable", &ScheduleSendAllPluginsCaptured, + this, NS_GetCurrentThread())); + return IPC_OK(); +#else + MOZ_ASSERT_UNREACHABLE( + "CompositorBridgeChild::RecvCaptureAllPlugins calls unexpected."); + return IPC_FAIL_NO_REASON(this); +#endif +} + +mozilla::ipc::IPCResult CompositorBridgeChild::RecvHideAllPlugins( + const uintptr_t& aParentWidget) { +#if !defined(XP_WIN) && !defined(MOZ_WIDGET_GTK) + MOZ_ASSERT_UNREACHABLE( + "CompositorBridgeChild::RecvHideAllPlugins calls " + "unexpected on this platform."); + return IPC_FAIL_NO_REASON(this); +#else + MOZ_ASSERT(NS_IsMainThread()); + nsTArray list; + nsIWidget::UpdateRegisteredPluginWindowVisibility(aParentWidget, list); + if (!mCanSend) { + return IPC_OK(); + } + SendRemotePluginsReady(); + return IPC_OK(); +#endif // !defined(XP_WIN) && !defined(MOZ_WIDGET_GTK) +} + +mozilla::ipc::IPCResult CompositorBridgeChild::RecvDidComposite( + const LayersId& aId, const TransactionId& aTransactionId, + const TimeStamp& aCompositeStart, const TimeStamp& aCompositeEnd) { + // Hold a reference to keep texture pools alive. See bug 1387799 + const auto texturePools = mTexturePools.Clone(); + + if (mLayerManager) { + MOZ_ASSERT(!aId.IsValid()); + MOZ_ASSERT(mLayerManager->GetBackendType() == + LayersBackend::LAYERS_CLIENT || + mLayerManager->GetBackendType() == LayersBackend::LAYERS_WR); + // Hold a reference to keep LayerManager alive. See Bug 1242668. + RefPtr m = mLayerManager; + m->DidComposite(aTransactionId, aCompositeStart, aCompositeEnd); + } else if (aId.IsValid()) { + RefPtr child = dom::BrowserChild::GetFrom(aId); + if (child) { + child->DidComposite(aTransactionId, aCompositeStart, aCompositeEnd); + } + } + + for (size_t i = 0; i < texturePools.Length(); i++) { + texturePools[i]->ReturnDeferredClients(); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorBridgeChild::RecvNotifyFrameStats( + nsTArray&& aFrameStats) { + gfxPlatform::GetPlatform()->NotifyFrameStats(std::move(aFrameStats)); + return IPC_OK(); +} + +void CompositorBridgeChild::ActorDestroy(ActorDestroyReason aWhy) { + if (aWhy == AbnormalShutdown) { + // If the parent side runs into a problem then the actor will be destroyed. + // There is nothing we can do in the child side, here sets mCanSend as + // false. + gfxCriticalNote << "Receive IPC close with reason=AbnormalShutdown"; + } + + { + // We take the lock to update these fields, since they are read from the + // paint thread. We don't need the lock to init them, since that happens + // on the main thread before the paint thread can ever grab a reference + // to the CompositorBridge object. + // + // Note that it is useful to take this lock for one other reason: It also + // tells us whether GetIPCChannel is safe to call. If we access the IPC + // channel within this lock, when mCanSend is true, then we know it has not + // been zapped by IPDL. + MonitorAutoLock lock(mPaintLock); + mCanSend = false; + mActorDestroyed = true; + } + + if (mProcessToken && XRE_IsParentProcess()) { + GPUProcessManager::Get()->NotifyRemoteActorDestroyed(mProcessToken); + } +} + +mozilla::ipc::IPCResult CompositorBridgeChild::RecvSharedCompositorFrameMetrics( + const mozilla::ipc::SharedMemoryBasic::Handle& metrics, + const CrossProcessMutexHandle& handle, const LayersId& aLayersId, + const uint32_t& aAPZCId) { + SharedFrameMetricsData* data = + new SharedFrameMetricsData(metrics, handle, aLayersId, aAPZCId); + mFrameMetricsTable.Put(data->GetViewID(), data); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +CompositorBridgeChild::RecvReleaseSharedCompositorFrameMetrics( + const ViewID& aId, const uint32_t& aAPZCId) { + if (auto entry = mFrameMetricsTable.Lookup(aId)) { + // The SharedFrameMetricsData may have been removed previously if + // a SharedFrameMetricsData with the same ViewID but later APZCId had + // been store and over wrote it. + if (entry.Data()->GetAPZCId() == aAPZCId) { + entry.Remove(); + } + } + return IPC_OK(); +} + +CompositorBridgeChild::SharedFrameMetricsData::SharedFrameMetricsData( + const ipc::SharedMemoryBasic::Handle& metrics, + const CrossProcessMutexHandle& handle, const LayersId& aLayersId, + const uint32_t& aAPZCId) + : mMutex(nullptr), mLayersId(aLayersId), mAPZCId(aAPZCId) { + mBuffer = new ipc::SharedMemoryBasic; + mBuffer->SetHandle(metrics, ipc::SharedMemory::RightsReadOnly); + mBuffer->Map(sizeof(FrameMetrics)); + mMutex = new CrossProcessMutex(handle); + MOZ_COUNT_CTOR(SharedFrameMetricsData); +} + +CompositorBridgeChild::SharedFrameMetricsData::~SharedFrameMetricsData() { + // When the hash table deletes the class, delete + // the shared memory and mutex. + delete mMutex; + mBuffer = nullptr; + MOZ_COUNT_DTOR(SharedFrameMetricsData); +} + +void CompositorBridgeChild::SharedFrameMetricsData::CopyFrameMetrics( + FrameMetrics* aFrame) { + const FrameMetrics* frame = + static_cast(mBuffer->memory()); + MOZ_ASSERT(frame); + mMutex->Lock(); + *aFrame = *frame; + mMutex->Unlock(); +} + +ScrollableLayerGuid::ViewID +CompositorBridgeChild::SharedFrameMetricsData::GetViewID() { + const FrameMetrics* frame = + static_cast(mBuffer->memory()); + MOZ_ASSERT(frame); + // Not locking to read of mScrollId since it should not change after being + // initially set. + return frame->GetScrollId(); +} + +LayersId CompositorBridgeChild::SharedFrameMetricsData::GetLayersId() const { + return mLayersId; +} + +uint32_t CompositorBridgeChild::SharedFrameMetricsData::GetAPZCId() { + return mAPZCId; +} + +mozilla::ipc::IPCResult CompositorBridgeChild::RecvRemotePaintIsReady() { + // Used on the content thread, this bounces the message to the + // BrowserParent (via the BrowserChild) if the notification was previously + // requested. XPCOM gives a soup of compiler errors when trying to + // do_QueryReference so I'm using static_cast<> + MOZ_LAYERS_LOG( + ("[RemoteGfx] CompositorBridgeChild received RemotePaintIsReady")); + RefPtr iBrowserChild(do_QueryReferent(mWeakBrowserChild)); + if (!iBrowserChild) { + MOZ_LAYERS_LOG( + ("[RemoteGfx] Note: BrowserChild was released before " + "RemotePaintIsReady. " + "MozAfterRemotePaint will not be sent to listener.")); + return IPC_OK(); + } + BrowserChild* browserChild = static_cast(iBrowserChild.get()); + MOZ_ASSERT(browserChild); + Unused << browserChild->SendRemotePaintIsReady(); + mWeakBrowserChild = nullptr; + return IPC_OK(); +} + +void CompositorBridgeChild::RequestNotifyAfterRemotePaint( + BrowserChild* aBrowserChild) { + MOZ_ASSERT(aBrowserChild, + "NULL BrowserChild not allowed in " + "CompositorBridgeChild::RequestNotifyAfterRemotePaint"); + mWeakBrowserChild = + do_GetWeakReference(static_cast(aBrowserChild)); + if (!mCanSend) { + return; + } + Unused << SendRequestNotifyAfterRemotePaint(); +} + +void CompositorBridgeChild::CancelNotifyAfterRemotePaint( + BrowserChild* aBrowserChild) { + RefPtr iBrowserChild(do_QueryReferent(mWeakBrowserChild)); + if (!iBrowserChild) { + return; + } + BrowserChild* browserChild = static_cast(iBrowserChild.get()); + if (browserChild == aBrowserChild) { + mWeakBrowserChild = nullptr; + } +} + +bool CompositorBridgeChild::SendWillClose() { + MOZ_RELEASE_ASSERT(mCanSend); + return PCompositorBridgeChild::SendWillClose(); +} + +bool CompositorBridgeChild::SendPause() { + if (!mCanSend) { + return false; + } + return PCompositorBridgeChild::SendPause(); +} + +bool CompositorBridgeChild::SendResume() { + if (!mCanSend) { + return false; + } + return PCompositorBridgeChild::SendResume(); +} + +bool CompositorBridgeChild::SendResumeAsync() { + if (!mCanSend) { + return false; + } + return PCompositorBridgeChild::SendResumeAsync(); +} + +bool CompositorBridgeChild::SendNotifyChildCreated( + const LayersId& id, CompositorOptions* aOptions) { + if (!mCanSend) { + return false; + } + return PCompositorBridgeChild::SendNotifyChildCreated(id, aOptions); +} + +bool CompositorBridgeChild::SendAdoptChild(const LayersId& id) { + if (!mCanSend) { + return false; + } + return PCompositorBridgeChild::SendAdoptChild(id); +} + +bool CompositorBridgeChild::SendMakeSnapshot( + const SurfaceDescriptor& inSnapshot, const gfx::IntRect& dirtyRect) { + if (!mCanSend) { + return false; + } + return PCompositorBridgeChild::SendMakeSnapshot(inSnapshot, dirtyRect); +} + +bool CompositorBridgeChild::SendFlushRendering() { + if (!mCanSend) { + return false; + } + return PCompositorBridgeChild::SendFlushRendering(); +} + +bool CompositorBridgeChild::SendStartFrameTimeRecording( + const int32_t& bufferSize, uint32_t* startIndex) { + if (!mCanSend) { + return false; + } + return PCompositorBridgeChild::SendStartFrameTimeRecording(bufferSize, + startIndex); +} + +bool CompositorBridgeChild::SendStopFrameTimeRecording( + const uint32_t& startIndex, nsTArray* intervals) { + if (!mCanSend) { + return false; + } + return PCompositorBridgeChild::SendStopFrameTimeRecording(startIndex, + intervals); +} + +bool CompositorBridgeChild::SendNotifyRegionInvalidated( + const nsIntRegion& region) { + if (!mCanSend) { + return false; + } + return PCompositorBridgeChild::SendNotifyRegionInvalidated(region); +} + +bool CompositorBridgeChild::SendRequestNotifyAfterRemotePaint() { + if (!mCanSend) { + return false; + } + return PCompositorBridgeChild::SendRequestNotifyAfterRemotePaint(); +} + +bool CompositorBridgeChild::SendAllPluginsCaptured() { + if (!mCanSend) { + return false; + } + return PCompositorBridgeChild::SendAllPluginsCaptured(); +} + +PTextureChild* CompositorBridgeChild::AllocPTextureChild( + const SurfaceDescriptor&, const ReadLockDescriptor&, const LayersBackend&, + const TextureFlags&, const LayersId&, const uint64_t& aSerial, + const wr::MaybeExternalImageId& aExternalImageId) { + return TextureClient::CreateIPDLActor(); +} + +bool CompositorBridgeChild::DeallocPTextureChild(PTextureChild* actor) { + return TextureClient::DestroyIPDLActor(actor); +} + +mozilla::ipc::IPCResult CompositorBridgeChild::RecvParentAsyncMessages( + nsTArray&& aMessages) { + for (AsyncParentMessageArray::index_type i = 0; i < aMessages.Length(); ++i) { + const AsyncParentMessageData& message = aMessages[i]; + + switch (message.type()) { + case AsyncParentMessageData::TOpNotifyNotUsed: { + const OpNotifyNotUsed& op = message.get_OpNotifyNotUsed(); + NotifyNotUsed(op.TextureId(), op.fwdTransactionId()); + break; + } + case AsyncParentMessageData::TOpDeliverReleaseFence: { + // Release fences are delivered via ImageBridge. + // Since some TextureClients are recycled without recycle callback. + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + break; + } + default: + NS_ERROR("unknown AsyncParentMessageData type"); + return IPC_FAIL_NO_REASON(this); + } + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorBridgeChild::RecvObserveLayersUpdate( + const LayersId& aLayersId, const LayersObserverEpoch& aEpoch, + const bool& aActive) { + // This message is sent via the window compositor, not the tab compositor - + // however it still has a layers id. + MOZ_ASSERT(aLayersId.IsValid()); + MOZ_ASSERT(XRE_IsParentProcess()); + + if (RefPtr tab = + dom::BrowserParent::GetBrowserParentFromLayersId(aLayersId)) { + tab->LayerTreeUpdate(aEpoch, aActive); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorBridgeChild::RecvCompositorOptionsChanged( + const LayersId& aLayersId, const CompositorOptions& aNewOptions) { + MOZ_ASSERT(aLayersId.IsValid()); + MOZ_ASSERT(XRE_IsParentProcess()); + + if (RefPtr tab = + dom::BrowserParent::GetBrowserParentFromLayersId(aLayersId)) { + Unused << tab->SendCompositorOptionsChanged(aNewOptions); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorBridgeChild::RecvNotifyJankedAnimations( + const LayersId& aLayersId, nsTArray&& aJankedAnimations) { + if (mLayerManager) { + MOZ_ASSERT(!aLayersId.IsValid()); + mLayerManager->UpdatePartialPrerenderedAnimations(aJankedAnimations); + } else if (aLayersId.IsValid()) { + RefPtr child = dom::BrowserChild::GetFrom(aLayersId); + if (child) { + child->NotifyJankedAnimations(aJankedAnimations); + } + } + + return IPC_OK(); +} + +void CompositorBridgeChild::HoldUntilCompositableRefReleasedIfNecessary( + TextureClient* aClient) { + if (!aClient) { + return; + } + +#ifdef MOZ_WIDGET_ANDROID + auto bufferId = aClient->GetInternalData()->GetBufferId(); + if (bufferId.isSome()) { + MOZ_ASSERT(aClient->GetFlags() & TextureFlags::WAIT_HOST_USAGE_END); + AndroidHardwareBufferManager::Get()->HoldUntilNotifyNotUsed( + bufferId.ref(), GetFwdTransactionId(), /* aUsesImageBridge */ false); + } +#endif + + bool waitNotifyNotUsed = + aClient->GetFlags() & TextureFlags::RECYCLE || + aClient->GetFlags() & TextureFlags::WAIT_HOST_USAGE_END; + if (!waitNotifyNotUsed) { + return; + } + + aClient->SetLastFwdTransactionId(GetFwdTransactionId()); + mTexturesWaitingNotifyNotUsed.emplace(aClient->GetSerial(), aClient); +} + +void CompositorBridgeChild::NotifyNotUsed(uint64_t aTextureId, + uint64_t aFwdTransactionId) { + auto it = mTexturesWaitingNotifyNotUsed.find(aTextureId); + if (it != mTexturesWaitingNotifyNotUsed.end()) { + if (aFwdTransactionId < it->second->GetLastFwdTransactionId()) { + // Released on host side, but client already requested newer use texture. + return; + } + mTexturesWaitingNotifyNotUsed.erase(it); + } +} + +void CompositorBridgeChild::CancelWaitForNotifyNotUsed(uint64_t aTextureId) { + mTexturesWaitingNotifyNotUsed.erase(aTextureId); +} + +TextureClientPool* CompositorBridgeChild::GetTexturePool( + KnowsCompositor* aAllocator, SurfaceFormat aFormat, TextureFlags aFlags) { + for (size_t i = 0; i < mTexturePools.Length(); i++) { + if (mTexturePools[i]->GetBackend() == + aAllocator->GetCompositorBackendType() && + mTexturePools[i]->GetMaxTextureSize() == + aAllocator->GetMaxTextureSize() && + mTexturePools[i]->GetFormat() == aFormat && + mTexturePools[i]->GetFlags() == aFlags) { + return mTexturePools[i]; + } + } + + mTexturePools.AppendElement(new TextureClientPool( + aAllocator, aFormat, gfx::gfxVars::TileSize(), aFlags, + StaticPrefs::layers_tile_pool_shrink_timeout_AtStartup(), + StaticPrefs::layers_tile_pool_clear_timeout_AtStartup(), + StaticPrefs::layers_tile_initial_pool_size_AtStartup(), + StaticPrefs::layers_tile_pool_unused_size_AtStartup(), this)); + + return mTexturePools.LastElement(); +} + +void CompositorBridgeChild::HandleMemoryPressure() { + for (size_t i = 0; i < mTexturePools.Length(); i++) { + mTexturePools[i]->Clear(); + } +} + +void CompositorBridgeChild::ClearTexturePool() { + for (size_t i = 0; i < mTexturePools.Length(); i++) { + mTexturePools[i]->Clear(); + } +} + +FixedSizeSmallShmemSectionAllocator* +CompositorBridgeChild::GetTileLockAllocator() { + if (!IPCOpen()) { + return nullptr; + } + + if (!mSectionAllocator) { + mSectionAllocator = new FixedSizeSmallShmemSectionAllocator(this); + } + return mSectionAllocator; +} + +PTextureChild* CompositorBridgeChild::CreateTexture( + const SurfaceDescriptor& aSharedData, const ReadLockDescriptor& aReadLock, + LayersBackend aLayersBackend, TextureFlags aFlags, uint64_t aSerial, + wr::MaybeExternalImageId& aExternalImageId, nsISerialEventTarget* aTarget) { + PTextureChild* textureChild = + AllocPTextureChild(aSharedData, aReadLock, aLayersBackend, aFlags, + LayersId{0} /* FIXME */, aSerial, aExternalImageId); + + // Do the DOM labeling. + if (aTarget) { + SetEventTargetForActor(textureChild, aTarget); + } + + return SendPTextureConstructor( + textureChild, aSharedData, aReadLock, aLayersBackend, aFlags, + LayersId{0} /* FIXME? */, aSerial, aExternalImageId); +} + +already_AddRefed CompositorBridgeChild::GetCanvasChild() { + MOZ_ASSERT(gfx::gfxVars::RemoteCanvasEnabled()); + + if (CanvasChild::Deactivated()) { + return nullptr; + } + + if (!mCanvasChild) { + ipc::Endpoint parentEndpoint; + ipc::Endpoint childEndpoint; + nsresult rv = PCanvas::CreateEndpoints(OtherPid(), base::GetCurrentProcId(), + &parentEndpoint, &childEndpoint); + if (NS_SUCCEEDED(rv)) { + Unused << SendInitPCanvasParent(std::move(parentEndpoint)); + mCanvasChild = new CanvasChild(std::move(childEndpoint)); + } + } + + return do_AddRef(mCanvasChild); +} + +void CompositorBridgeChild::EndCanvasTransaction() { + if (mCanvasChild) { + mCanvasChild->EndTransaction(); + if (mCanvasChild->ShouldBeCleanedUp()) { + mCanvasChild->Destroy(); + Unused << SendReleasePCanvasParent(); + mCanvasChild = nullptr; + } + } +} + +RefPtr CompositorBridgeChild::GetWebGPUChild() { + MOZ_ASSERT(gfx::gfxConfig::IsEnabled(gfx::Feature::WEBGPU)); + if (!mWebGPUChild) { + webgpu::PWebGPUChild* bridge = SendPWebGPUConstructor(); + mWebGPUChild = static_cast(bridge); + } + + return mWebGPUChild; +} + +bool CompositorBridgeChild::AllocUnsafeShmem( + size_t aSize, ipc::SharedMemory::SharedMemoryType aType, + ipc::Shmem* aShmem) { + ShmemAllocated(this); + return PCompositorBridgeChild::AllocUnsafeShmem(aSize, aType, aShmem); +} + +bool CompositorBridgeChild::AllocShmem( + size_t aSize, ipc::SharedMemory::SharedMemoryType aType, + ipc::Shmem* aShmem) { + ShmemAllocated(this); + return PCompositorBridgeChild::AllocShmem(aSize, aType, aShmem); +} + +bool CompositorBridgeChild::DeallocShmem(ipc::Shmem& aShmem) { + if (!mCanSend) { + return false; + } + return PCompositorBridgeChild::DeallocShmem(aShmem); +} + +widget::PCompositorWidgetChild* +CompositorBridgeChild::AllocPCompositorWidgetChild( + const CompositorWidgetInitData& aInitData) { + // We send the constructor manually. + MOZ_CRASH("Should not be called"); + return nullptr; +} + +bool CompositorBridgeChild::DeallocPCompositorWidgetChild( + PCompositorWidgetChild* aActor) { +#ifdef MOZ_WIDGET_SUPPORTS_OOP_COMPOSITING + delete aActor; + return true; +#else + return false; +#endif +} + +PAPZCTreeManagerChild* CompositorBridgeChild::AllocPAPZCTreeManagerChild( + const LayersId& aLayersId) { + APZCTreeManagerChild* child = new APZCTreeManagerChild(); + child->AddIPDLReference(); + + return child; +} + +PAPZChild* CompositorBridgeChild::AllocPAPZChild(const LayersId& aLayersId) { + // We send the constructor manually. + MOZ_CRASH("Should not be called"); + return nullptr; +} + +bool CompositorBridgeChild::DeallocPAPZChild(PAPZChild* aActor) { + delete aActor; + return true; +} + +bool CompositorBridgeChild::DeallocPAPZCTreeManagerChild( + PAPZCTreeManagerChild* aActor) { + APZCTreeManagerChild* child = static_cast(aActor); + child->ReleaseIPDLReference(); + return true; +} + +// - + +void CompositorBridgeChild::WillEndTransaction() { ResetShmemCounter(); } + +PWebRenderBridgeChild* CompositorBridgeChild::AllocPWebRenderBridgeChild( + const wr::PipelineId& aPipelineId, const LayoutDeviceIntSize&, + const WindowKind&) { + WebRenderBridgeChild* child = new WebRenderBridgeChild(aPipelineId); + child->AddIPDLReference(); + return child; +} + +bool CompositorBridgeChild::DeallocPWebRenderBridgeChild( + PWebRenderBridgeChild* aActor) { + WebRenderBridgeChild* child = static_cast(aActor); + ClearSharedFrameMetricsData(wr::AsLayersId(child->GetPipeline())); + child->ReleaseIPDLReference(); + return true; +} + +webgpu::PWebGPUChild* CompositorBridgeChild::AllocPWebGPUChild() { + webgpu::WebGPUChild* child = new webgpu::WebGPUChild(); + child->AddIPDLReference(); + return child; +} + +bool CompositorBridgeChild::DeallocPWebGPUChild(webgpu::PWebGPUChild* aActor) { + webgpu::WebGPUChild* child = static_cast(aActor); + child->ReleaseIPDLReference(); + return true; +} + +void CompositorBridgeChild::ClearSharedFrameMetricsData(LayersId aLayersId) { + for (auto iter = mFrameMetricsTable.Iter(); !iter.Done(); iter.Next()) { + auto data = iter.UserData(); + if (data->GetLayersId() == aLayersId) { + iter.Remove(); + } + } +} + +uint64_t CompositorBridgeChild::GetNextResourceId() { + ++mResourceId; + MOZ_RELEASE_ASSERT(mResourceId != UINT32_MAX); + + uint64_t id = mIdNamespace; + id = (id << 32) | mResourceId; + + return id; +} + +wr::MaybeExternalImageId CompositorBridgeChild::GetNextExternalImageId() { + return Some(wr::ToExternalImageId(GetNextResourceId())); +} + +wr::PipelineId CompositorBridgeChild::GetNextPipelineId() { + return wr::AsPipelineId(GetNextResourceId()); +} + +void CompositorBridgeChild::FlushAsyncPaints() { + MOZ_ASSERT(NS_IsMainThread()); + + Maybe start; + if (XRE_IsContentProcess() && gfx::gfxVars::UseOMTP()) { + start = Some(TimeStamp::Now()); + } + + { + MonitorAutoLock lock(mPaintLock); + while (mOutstandingAsyncPaints > 0 || mOutstandingAsyncEndTransaction) { + lock.Wait(); + } + + // It's now safe to free any TextureClients that were used during painting. + mTextureClientsForAsyncPaint.Clear(); + } + + if (start) { + float ms = (TimeStamp::Now() - start.value()).ToMilliseconds(); + + // Anything above 200us gets recorded. + if (ms >= 0.2) { + mSlowFlushCount++; + Telemetry::Accumulate(Telemetry::GFX_OMTP_PAINT_WAIT_TIME, int32_t(ms)); + } + mTotalFlushCount++; + + double ratio = double(mSlowFlushCount) / double(mTotalFlushCount); + Telemetry::ScalarSet(Telemetry::ScalarID::GFX_OMTP_PAINT_WAIT_RATIO, + uint32_t(ratio * 100 * 100)); + } +} + +void CompositorBridgeChild::NotifyBeginAsyncPaint(PaintTask* aTask) { + MOZ_ASSERT(NS_IsMainThread()); + + MonitorAutoLock lock(mPaintLock); + + if (mTotalAsyncPaints == 0) { + mAsyncTransactionBegin = TimeStamp::Now(); + } + mTotalAsyncPaints += 1; + + // We must not be waiting for paints or buffer copying to complete yet. This + // would imply we started a new paint without waiting for a previous one, + // which could lead to incorrect rendering or IPDL deadlocks. + MOZ_ASSERT(!mIsDelayingForAsyncPaints); + + mOutstandingAsyncPaints++; + + // Mark texture clients that they are being used for async painting, and + // make sure we hold them alive on the main thread. + for (auto& client : aTask->mClients) { + client->AddPaintThreadRef(); + mTextureClientsForAsyncPaint.AppendElement(client); + }; +} + +// Must only be called from the paint thread. Notifies the CompositorBridge +// that the paint thread has finished an asynchronous paint request. +bool CompositorBridgeChild::NotifyFinishedAsyncWorkerPaint(PaintTask* aTask) { + MOZ_ASSERT(PaintThread::Get()->IsOnPaintWorkerThread()); + + MonitorAutoLock lock(mPaintLock); + mOutstandingAsyncPaints--; + + for (auto& client : aTask->mClients) { + client->DropPaintThreadRef(); + }; + aTask->DropTextureClients(); + + // If the main thread has completed queuing work and this was the + // last paint, then it is time to end the layer transaction and sync + return mOutstandingAsyncEndTransaction && mOutstandingAsyncPaints == 0; +} + +bool CompositorBridgeChild::NotifyBeginAsyncEndLayerTransaction( + SyncObjectClient* aSyncObject) { + MOZ_ASSERT(NS_IsMainThread()); + MonitorAutoLock lock(mPaintLock); + + MOZ_ASSERT(!mOutstandingAsyncEndTransaction); + mOutstandingAsyncEndTransaction = true; + mOutstandingAsyncSyncObject = aSyncObject; + return mOutstandingAsyncPaints == 0; +} + +void CompositorBridgeChild::NotifyFinishedAsyncEndLayerTransaction() { + MOZ_ASSERT(PaintThread::Get()->IsOnPaintWorkerThread()); + + if (mOutstandingAsyncSyncObject) { + mOutstandingAsyncSyncObject->Synchronize(); + mOutstandingAsyncSyncObject = nullptr; + } + + MonitorAutoLock lock(mPaintLock); + + if (mTotalAsyncPaints > 0) { + float tenthMs = + (TimeStamp::Now() - mAsyncTransactionBegin).ToMilliseconds() * 10; + Telemetry::Accumulate(Telemetry::GFX_OMTP_PAINT_TASK_COUNT, + int32_t(mTotalAsyncPaints)); + Telemetry::Accumulate(Telemetry::GFX_OMTP_PAINT_TIME, int32_t(tenthMs)); + mTotalAsyncPaints = 0; + } + + // Since this should happen after ALL paints are done and + // at the end of a transaction, this should always be true. + MOZ_RELEASE_ASSERT(mOutstandingAsyncPaints == 0); + MOZ_ASSERT(mOutstandingAsyncEndTransaction); + + mOutstandingAsyncEndTransaction = false; + + // It's possible that we painted so fast that the main thread never reached + // the code that starts delaying messages. If so, mIsDelayingForAsyncPaints + // will be false, and we can safely return. + if (mIsDelayingForAsyncPaints) { + ResumeIPCAfterAsyncPaint(); + } + + // Notify the main thread in case it's blocking. We do this unconditionally + // to avoid deadlocking. + lock.Notify(); +} + +void CompositorBridgeChild::ResumeIPCAfterAsyncPaint() { + // Note: the caller is responsible for holding the lock. + mPaintLock.AssertCurrentThreadOwns(); + MOZ_ASSERT(PaintThread::Get()->IsOnPaintWorkerThread()); + MOZ_ASSERT(mOutstandingAsyncPaints == 0); + MOZ_ASSERT(!mOutstandingAsyncEndTransaction); + MOZ_ASSERT(mIsDelayingForAsyncPaints); + + mIsDelayingForAsyncPaints = false; + + // It's also possible that the channel has shut down already. + if (!mCanSend || mActorDestroyed) { + return; + } + + GetIPCChannel()->StopPostponingSends(); +} + +void CompositorBridgeChild::PostponeMessagesIfAsyncPainting() { + MOZ_ASSERT(NS_IsMainThread()); + + MonitorAutoLock lock(mPaintLock); + + MOZ_ASSERT(!mIsDelayingForAsyncPaints); + + // We need to wait for async paints and the async end transaction as + // it will do texture synchronization + if (mOutstandingAsyncPaints > 0 || mOutstandingAsyncEndTransaction) { + mIsDelayingForAsyncPaints = true; + GetIPCChannel()->BeginPostponingSends(); + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/CompositorBridgeChild.h b/gfx/layers/ipc/CompositorBridgeChild.h new file mode 100644 index 0000000000..ebfd0f10c0 --- /dev/null +++ b/gfx/layers/ipc/CompositorBridgeChild.h @@ -0,0 +1,427 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_CompositorBridgeChild_h +#define mozilla_layers_CompositorBridgeChild_h + +#include "base/basictypes.h" // for DISALLOW_EVIL_CONSTRUCTORS +#include "mozilla/Assertions.h" // for MOZ_ASSERT_HELPER2 +#include "mozilla/Attributes.h" // for override +#include "mozilla/Monitor.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/layers/PCompositorBridgeChild.h" +#include "mozilla/layers/TextureForwarder.h" // for TextureForwarder +#include "mozilla/layers/PaintThread.h" // for PaintThread +#include "mozilla/webrender/WebRenderTypes.h" +#include "nsClassHashtable.h" // for nsClassHashtable +#include "nsCOMPtr.h" // for nsCOMPtr +#include "nsHashKeys.h" // for nsUint64HashKey +#include "nsISupportsImpl.h" // for NS_INLINE_DECL_REFCOUNTING +#include "nsIWeakReferenceUtils.h" + +#include + +namespace mozilla { + +namespace dom { +class BrowserChild; +} // namespace dom + +namespace webgpu { +class PWebGPUChild; +class WebGPUChild; +} // namespace webgpu + +namespace widget { +class CompositorWidget; +} // namespace widget + +namespace layers { + +using mozilla::dom::BrowserChild; + +class IAPZCTreeManager; +class APZCTreeManagerChild; +class CanvasChild; +class ClientLayerManager; +class CompositorBridgeParent; +class CompositorManagerChild; +class CompositorOptions; +class LayerManager; +class TextureClient; +class TextureClientPool; +struct FrameMetrics; + +class CompositorBridgeChild final : public PCompositorBridgeChild, + public TextureForwarder { + typedef nsTArray AsyncParentMessageArray; + + friend class PCompositorBridgeChild; + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CompositorBridgeChild, override); + + explicit CompositorBridgeChild(CompositorManagerChild* aManager); + + /** + * Initialize the singleton compositor bridge for a content process. + */ + void InitForContent(uint32_t aNamespace); + + void InitForWidget(uint64_t aProcessToken, LayerManager* aLayerManager, + uint32_t aNamespace); + + void Destroy(); + + /** + * Lookup the FrameMetrics shared by the compositor process with the + * associated ScrollableLayerGuid::ViewID. The returned FrameMetrics is used + * in progressive paint calculations. + */ + bool LookupCompositorFrameMetrics(const ScrollableLayerGuid::ViewID aId, + FrameMetrics&); + + static CompositorBridgeChild* Get(); + + static bool ChildProcessHasCompositorBridge(); + + // Returns whether the compositor is in the GPU process (false if in the UI + // process). This may only be called on the main thread. + static bool CompositorIsInGPUProcess(); + + mozilla::ipc::IPCResult RecvDidComposite(const LayersId& aId, + const TransactionId& aTransactionId, + const TimeStamp& aCompositeStart, + const TimeStamp& aCompositeEnd); + + mozilla::ipc::IPCResult RecvNotifyFrameStats( + nsTArray&& aFrameStats); + + mozilla::ipc::IPCResult RecvInvalidateLayers(const LayersId& aLayersId); + + mozilla::ipc::IPCResult RecvUpdatePluginConfigurations( + const LayoutDeviceIntPoint& aContentOffset, + const LayoutDeviceIntRegion& aVisibleRegion, + nsTArray&& aPlugins); + + mozilla::ipc::IPCResult RecvCaptureAllPlugins(const uintptr_t& aParentWidget); + + mozilla::ipc::IPCResult RecvHideAllPlugins(const uintptr_t& aParentWidget); + + mozilla::ipc::IPCResult RecvNotifyJankedAnimations( + const LayersId& aLayersId, nsTArray&& aJankedAnimations); + + PTextureChild* AllocPTextureChild( + const SurfaceDescriptor& aSharedData, const ReadLockDescriptor& aReadLock, + const LayersBackend& aLayersBackend, const TextureFlags& aFlags, + const LayersId& aId, const uint64_t& aSerial, + const wr::MaybeExternalImageId& aExternalImageId); + + bool DeallocPTextureChild(PTextureChild* actor); + + mozilla::ipc::IPCResult RecvParentAsyncMessages( + nsTArray&& aMessages); + PTextureChild* CreateTexture(const SurfaceDescriptor& aSharedData, + const ReadLockDescriptor& aReadLock, + LayersBackend aLayersBackend, + TextureFlags aFlags, uint64_t aSerial, + wr::MaybeExternalImageId& aExternalImageId, + nsISerialEventTarget* aTarget) override; + + already_AddRefed GetCanvasChild() final; + + void EndCanvasTransaction(); + + RefPtr GetWebGPUChild(); + + /** + * Request that the parent tell us when graphics are ready on GPU. + * When we get that message, we bounce it to the BrowserParent via + * the BrowserChild + * @param browserChild The object to bounce the note to. Non-NULL. + */ + void RequestNotifyAfterRemotePaint(BrowserChild* aBrowserChild); + + void CancelNotifyAfterRemotePaint(BrowserChild* aBrowserChild); + + // Beware that these methods don't override their super-class equivalent + // (which are not virtual), they just overload them. All of these Send* + // methods just add a sanity check (that it is not too late send a message) + // and forward the call to the super-class's equivalent method. This means + // that it is correct to call directly the super-class methods, but you won't + // get the extra safety provided here. + bool SendWillClose(); + bool SendPause(); + bool SendResume(); + bool SendResumeAsync(); + bool SendNotifyChildCreated(const LayersId& id, CompositorOptions* aOptions); + bool SendAdoptChild(const LayersId& id); + bool SendMakeSnapshot(const SurfaceDescriptor& inSnapshot, + const gfx::IntRect& dirtyRect); + bool SendFlushRendering(); + bool SendGetTileSize(int32_t* tileWidth, int32_t* tileHeight); + bool SendStartFrameTimeRecording(const int32_t& bufferSize, + uint32_t* startIndex); + bool SendStopFrameTimeRecording(const uint32_t& startIndex, + nsTArray* intervals); + bool SendNotifyRegionInvalidated(const nsIntRegion& region); + bool SendRequestNotifyAfterRemotePaint(); + bool SendAllPluginsCaptured(); + bool IsSameProcess() const override; + + bool IPCOpen() const override { return mCanSend; } + + static void ShutDown(); + + void UpdateFwdTransactionId() { ++mFwdTransactionId; } + uint64_t GetFwdTransactionId() { return mFwdTransactionId; } + + /** + * Hold TextureClient ref until end of usage on host side if + * TextureFlags::RECYCLE is set. Host side's usage is checked via + * CompositableRef. + */ + void HoldUntilCompositableRefReleasedIfNecessary(TextureClient* aClient); + + /** + * Notify id of Texture When host side end its use. Transaction id is used to + * make sure if there is no newer usage. + */ + void NotifyNotUsed(uint64_t aTextureId, uint64_t aFwdTransactionId); + + void CancelWaitForNotifyNotUsed(uint64_t aTextureId) override; + + TextureClientPool* GetTexturePool(KnowsCompositor* aAllocator, + gfx::SurfaceFormat aFormat, + TextureFlags aFlags); + void ClearTexturePool(); + + FixedSizeSmallShmemSectionAllocator* GetTileLockAllocator() override; + + void HandleMemoryPressure(); + + nsISerialEventTarget* GetThread() const override { return mThread; } + + base::ProcessId GetParentPid() const override { return OtherPid(); } + + bool AllocUnsafeShmem(size_t aSize, + mozilla::ipc::SharedMemory::SharedMemoryType aShmType, + mozilla::ipc::Shmem* aShmem) override; + bool AllocShmem(size_t aSize, + mozilla::ipc::SharedMemory::SharedMemoryType aShmType, + mozilla::ipc::Shmem* aShmem) override; + bool DeallocShmem(mozilla::ipc::Shmem& aShmem) override; + + PCompositorWidgetChild* AllocPCompositorWidgetChild( + const CompositorWidgetInitData& aInitData); + bool DeallocPCompositorWidgetChild(PCompositorWidgetChild* aActor); + + PAPZCTreeManagerChild* AllocPAPZCTreeManagerChild(const LayersId& aLayersId); + bool DeallocPAPZCTreeManagerChild(PAPZCTreeManagerChild* aActor); + + PAPZChild* AllocPAPZChild(const LayersId& aLayersId); + bool DeallocPAPZChild(PAPZChild* aActor); + + void WillEndTransaction(); + + PWebRenderBridgeChild* AllocPWebRenderBridgeChild( + const wr::PipelineId& aPipelineId, const LayoutDeviceIntSize&, + const WindowKind&); + bool DeallocPWebRenderBridgeChild(PWebRenderBridgeChild* aActor); + + webgpu::PWebGPUChild* AllocPWebGPUChild(); + bool DeallocPWebGPUChild(webgpu::PWebGPUChild* aActor); + + wr::MaybeExternalImageId GetNextExternalImageId() override; + + wr::PipelineId GetNextPipelineId(); + + // Must only be called from the main thread. Ensures that any paints from + // previous frames have been flushed. The main thread blocks until the + // operation completes. + void FlushAsyncPaints(); + + // Must only be called from the main thread. Notifies the CompositorBridge + // that the paint thread is going to begin painting asynchronously. + void NotifyBeginAsyncPaint(PaintTask* aTask); + + // Must only be called from the paint thread. Notifies the CompositorBridge + // that the paint thread has finished an asynchronous paint request. + bool NotifyFinishedAsyncWorkerPaint(PaintTask* aTask); + + // Must only be called from the main thread. Notifies the CompositorBridge + // that all work has been submitted to the paint thread or paint worker + // threads, and returns whether all paints are completed. If this returns + // true, then an AsyncEndLayerTransaction must be queued, otherwise once + // NotifyFinishedAsyncWorkerPaint returns true, an AsyncEndLayerTransaction + // must be executed. + bool NotifyBeginAsyncEndLayerTransaction(SyncObjectClient* aSyncObject); + + // Must only be called from the paint thread. Notifies the CompositorBridge + // that the paint thread has finished all async paints and and may do the + // requested texture sync and resume sending messages. + void NotifyFinishedAsyncEndLayerTransaction(); + + // Must only be called from the main thread. Notifies the CompoistorBridge + // that a transaction is about to be sent, and if the paint thread is + // currently painting, to begin delaying IPC messages. + void PostponeMessagesIfAsyncPainting(); + + private: + // Private destructor, to discourage deletion outside of Release(): + virtual ~CompositorBridgeChild(); + + // Must only be called from the paint thread. If the main thread is delaying + // IPC messages, this forwards all such delayed IPC messages to the I/O thread + // and resumes IPC. + void ResumeIPCAfterAsyncPaint(); + + void PrepareFinalDestroy(); + void AfterDestroy(); + + PLayerTransactionChild* AllocPLayerTransactionChild( + const nsTArray& aBackendHints, const LayersId& aId); + + bool DeallocPLayerTransactionChild(PLayerTransactionChild* aChild); + + void ActorDestroy(ActorDestroyReason aWhy) override; + + mozilla::ipc::IPCResult RecvSharedCompositorFrameMetrics( + const mozilla::ipc::SharedMemoryBasic::Handle& metrics, + const CrossProcessMutexHandle& handle, const LayersId& aLayersId, + const uint32_t& aAPZCId); + + mozilla::ipc::IPCResult RecvReleaseSharedCompositorFrameMetrics( + const ViewID& aId, const uint32_t& aAPZCId); + + mozilla::ipc::IPCResult RecvRemotePaintIsReady(); + + mozilla::ipc::IPCResult RecvObserveLayersUpdate( + const LayersId& aLayersId, const LayersObserverEpoch& aEpoch, + const bool& aActive); + + mozilla::ipc::IPCResult RecvCompositorOptionsChanged( + const LayersId& aLayersId, const CompositorOptions& aNewOptions); + + uint64_t GetNextResourceId(); + + void ClearSharedFrameMetricsData(LayersId aLayersId); + + // Class used to store the shared FrameMetrics, mutex, and APZCId in a hash + // table + class SharedFrameMetricsData final { + public: + SharedFrameMetricsData( + const mozilla::ipc::SharedMemoryBasic::Handle& metrics, + const CrossProcessMutexHandle& handle, const LayersId& aLayersId, + const uint32_t& aAPZCId); + + ~SharedFrameMetricsData(); + + void CopyFrameMetrics(FrameMetrics* aFrame); + ScrollableLayerGuid::ViewID GetViewID(); + LayersId GetLayersId() const; + uint32_t GetAPZCId(); + + private: + // Pointer to the class that allows access to the shared memory that + // contains the shared FrameMetrics + RefPtr mBuffer; + CrossProcessMutex* mMutex; + LayersId mLayersId; + // Unique ID of the APZC that is sharing the FrameMetrics + uint32_t mAPZCId; + }; + + RefPtr mCompositorManager; + + RefPtr mLayerManager; + + uint32_t mIdNamespace; + uint32_t mResourceId; + + // When not multi-process, hold a reference to the CompositorBridgeParent to + // keep it alive. This reference should be null in multi-process. + RefPtr mCompositorBridgeParent; + + // The ViewID of the FrameMetrics is used as the key for this hash table. + // While this should be safe to use since the ViewID is unique + nsClassHashtable mFrameMetricsTable; + + // Weakly hold the BrowserChild that made a request to be alerted when + // the transaction has been received. + nsWeakPtr mWeakBrowserChild; // type is BrowserChild + + DISALLOW_EVIL_CONSTRUCTORS(CompositorBridgeChild); + + // True until the beginning of the two-step shutdown sequence of this actor. + bool mCanSend; + + // False until the actor is destroyed. + bool mActorDestroyed; + + /** + * Transaction id of ShadowLayerForwarder. + * It is incrementaed by UpdateFwdTransactionId() in each BeginTransaction() + * call. + */ + uint64_t mFwdTransactionId; + + /** + * Hold TextureClients refs until end of their usages on host side. + * It defer calling of TextureClient recycle callback. + */ + std::unordered_map> + mTexturesWaitingNotifyNotUsed; + + nsCOMPtr mThread; + + AutoTArray, 2> mTexturePools; + + uint64_t mProcessToken; + + FixedSizeSmallShmemSectionAllocator* mSectionAllocator; + + // TextureClients that must be kept alive during async painting. This + // is only accessed on the main thread. + nsTArray> mTextureClientsForAsyncPaint; + + // Off-Main-Thread Painting state. This covers access to the OMTP-related + // state below. + Monitor mPaintLock; + + // Contains the number of asynchronous paints that were queued since the + // beginning of the last async transaction, and the time stamp of when + // that was + size_t mTotalAsyncPaints; + TimeStamp mAsyncTransactionBegin; + + // Contains the number of outstanding asynchronous paints tied to a + // PLayerTransaction on this bridge. This is R/W on both the main and paint + // threads, and must be accessed within the paint lock. + size_t mOutstandingAsyncPaints; + + // Whether we are waiting for an async paint end transaction + bool mOutstandingAsyncEndTransaction; + RefPtr mOutstandingAsyncSyncObject; + + // True if this CompositorBridge is currently delaying its messages until the + // paint thread completes. This is R/W on both the main and paint threads, and + // must be accessed within the paint lock. + bool mIsDelayingForAsyncPaints; + + uintptr_t mSlowFlushCount; + uintptr_t mTotalFlushCount; + + RefPtr mCanvasChild; + + RefPtr mWebGPUChild; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_CompositorBrigedChild_h diff --git a/gfx/layers/ipc/CompositorBridgeParent.cpp b/gfx/layers/ipc/CompositorBridgeParent.cpp new file mode 100644 index 0000000000..130cf28d7b --- /dev/null +++ b/gfx/layers/ipc/CompositorBridgeParent.cpp @@ -0,0 +1,2990 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/CompositorBridgeParent.h" + +#include // for fprintf, stdout +#include // for uint64_t +#include // for _Rb_tree_iterator, etc +#include // for pair + +#include "apz/src/APZCTreeManager.h" // for APZCTreeManager +#include "LayerTransactionParent.h" // for LayerTransactionParent +#include "RenderTrace.h" // for RenderTraceLayers +#include "base/process.h" // for ProcessId +#include "gfxContext.h" // for gfxContext +#include "gfxPlatform.h" // for gfxPlatform +#include "TreeTraversal.h" // for ForEachNode +#ifdef MOZ_WIDGET_GTK +# include "gfxPlatformGtk.h" // for gfxPlatform +#endif +#include "mozilla/AutoRestore.h" // for AutoRestore +#include "mozilla/ClearOnShutdown.h" // for ClearOnShutdown +#include "mozilla/DebugOnly.h" // for DebugOnly +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/gfx/2D.h" // for DrawTarget +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/gfx/Rect.h" // for IntSize +#include "mozilla/gfx/gfxVars.h" // for gfxVars +#include "mozilla/ipc/Transport.h" // for Transport +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/gfx/GPUParent.h" +#include "mozilla/layers/APZCTreeManagerParent.h" // for APZCTreeManagerParent +#include "mozilla/layers/APZSampler.h" // for APZSampler +#include "mozilla/layers/APZThreadUtils.h" // for APZThreadUtils +#include "mozilla/layers/APZUpdater.h" // for APZUpdater +#include "mozilla/layers/AsyncCompositionManager.h" +#include "mozilla/layers/BasicCompositor.h" // for BasicCompositor +#include "mozilla/layers/CompositionRecorder.h" // for CompositionRecorder +#include "mozilla/layers/Compositor.h" // for Compositor +#include "mozilla/layers/CompositorAnimationStorage.h" // for CompositorAnimationStorage +#include "mozilla/layers/CompositorManagerParent.h" // for CompositorManagerParent +#include "mozilla/layers/CompositorOGL.h" // for CompositorOGL +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/layers/CompositorTypes.h" +#include "mozilla/layers/CompositorVsyncScheduler.h" +#include "mozilla/layers/ContentCompositorBridgeParent.h" +#include "mozilla/layers/FrameUniformityData.h" +#include "mozilla/layers/GeckoContentController.h" +#include "mozilla/layers/ImageBridgeParent.h" +#include "mozilla/layers/LayerManagerComposite.h" +#include "mozilla/layers/LayerManagerMLGPU.h" +#include "mozilla/layers/LayerTreeOwnerTracker.h" +#include "mozilla/layers/LayersTypes.h" +#include "mozilla/layers/OMTASampler.h" +#include "mozilla/layers/PLayerTransactionParent.h" +#include "mozilla/layers/RemoteContentController.h" +#include "mozilla/layers/UiCompositorControllerParent.h" +#include "mozilla/layers/WebRenderBridgeParent.h" +#include "mozilla/layers/AsyncImagePipelineManager.h" +#include "mozilla/webrender/WebRenderAPI.h" +#include "mozilla/webgpu/WebGPUParent.h" +#include "mozilla/webrender/RenderThread.h" +#include "mozilla/media/MediaSystemResourceService.h" // for MediaSystemResourceService +#include "mozilla/mozalloc.h" // for operator new, etc +#include "mozilla/PerfStats.h" +#include "mozilla/PodOperations.h" +#include "mozilla/Telemetry.h" +#ifdef MOZ_WIDGET_GTK +# include "basic/X11BasicCompositor.h" // for X11BasicCompositor +#endif +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsDebug.h" // for NS_ASSERTION, etc +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "nsIWidget.h" // for nsIWidget +#include "nsTArray.h" // for nsTArray +#include "nsThreadUtils.h" // for NS_IsMainThread +#ifdef XP_WIN +# include "mozilla/layers/CompositorD3D11.h" +# include "mozilla/widget/WinCompositorWidget.h" +# include "mozilla/WindowsVersion.h" +#endif +#include "GeckoProfiler.h" +#include "mozilla/ipc/ProtocolTypes.h" +#include "mozilla/Unused.h" +#include "mozilla/Hal.h" +#include "mozilla/HalTypes.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Telemetry.h" +#include "mozilla/VsyncDispatcher.h" +#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) +# include "VsyncSource.h" +#endif +#include "mozilla/widget/CompositorWidget.h" +#ifdef MOZ_WIDGET_SUPPORTS_OOP_COMPOSITING +# include "mozilla/widget/CompositorWidgetParent.h" +#endif +#ifdef XP_WIN +# include "mozilla/gfx/DeviceManagerDx.h" +#endif + +#include "LayerScope.h" + +namespace mozilla { + +namespace layers { + +using namespace mozilla::ipc; +using namespace mozilla::gfx; + +using base::ProcessId; + +using mozilla::Telemetry::LABELS_CONTENT_FRAME_TIME_REASON; + +/// Equivalent to asserting CompositorThreadHolder::IsInCompositorThread with +/// the addition that it doesn't assert if the compositor thread holder is +/// already gone during late shutdown. +static void AssertIsInCompositorThread() { + MOZ_RELEASE_ASSERT(!CompositorThread() || + CompositorThreadHolder::IsInCompositorThread()); +} + +CompositorBridgeParentBase::CompositorBridgeParentBase( + CompositorManagerParent* aManager) + : mCanSend(true), mCompositorManager(aManager) {} + +CompositorBridgeParentBase::~CompositorBridgeParentBase() = default; + +ProcessId CompositorBridgeParentBase::GetChildProcessId() { return OtherPid(); } + +void CompositorBridgeParentBase::NotifyNotUsed(PTextureParent* aTexture, + uint64_t aTransactionId) { + RefPtr texture = TextureHost::AsTextureHost(aTexture); + if (!texture) { + return; + } + +#ifdef MOZ_WIDGET_ANDROID + if (texture->GetAndroidHardwareBuffer()) { + MOZ_ASSERT(texture->GetFlags() & TextureFlags::RECYCLE); + ImageBridgeParent::NotifyBufferNotUsedOfCompositorBridge( + GetChildProcessId(), texture, aTransactionId); + } +#endif + + if (!(texture->GetFlags() & TextureFlags::RECYCLE) && + !(texture->GetFlags() & TextureFlags::WAIT_HOST_USAGE_END)) { + return; + } + + uint64_t textureId = TextureHost::GetTextureSerial(aTexture); + mPendingAsyncMessage.push_back(OpNotifyNotUsed(textureId, aTransactionId)); +} + +void CompositorBridgeParentBase::SendAsyncMessage( + const nsTArray& aMessage) { + Unused << SendParentAsyncMessages(aMessage); +} + +bool CompositorBridgeParentBase::AllocShmem( + size_t aSize, ipc::SharedMemory::SharedMemoryType aType, + ipc::Shmem* aShmem) { + return PCompositorBridgeParent::AllocShmem(aSize, aType, aShmem); +} + +bool CompositorBridgeParentBase::AllocUnsafeShmem( + size_t aSize, ipc::SharedMemory::SharedMemoryType aType, + ipc::Shmem* aShmem) { + return PCompositorBridgeParent::AllocUnsafeShmem(aSize, aType, aShmem); +} + +bool CompositorBridgeParentBase::DeallocShmem(ipc::Shmem& aShmem) { + return PCompositorBridgeParent::DeallocShmem(aShmem); +} + +base::ProcessId CompositorBridgeParentBase::RemotePid() { return OtherPid(); } + +bool CompositorBridgeParentBase::StartSharingMetrics( + ipc::SharedMemoryBasic::Handle aHandle, + CrossProcessMutexHandle aMutexHandle, LayersId aLayersId, + uint32_t aApzcId) { + if (!CompositorThreadHolder::IsInCompositorThread()) { + MOZ_ASSERT(CompositorThread()); + CompositorThread()->Dispatch( + NewRunnableMethod( + "layers::CompositorBridgeParent::StartSharingMetrics", this, + &CompositorBridgeParentBase::StartSharingMetrics, aHandle, + aMutexHandle, aLayersId, aApzcId)); + return true; + } + + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + if (!mCanSend) { + return false; + } + return PCompositorBridgeParent::SendSharedCompositorFrameMetrics( + aHandle, aMutexHandle, aLayersId, aApzcId); +} + +bool CompositorBridgeParentBase::StopSharingMetrics( + ScrollableLayerGuid::ViewID aScrollId, uint32_t aApzcId) { + if (!CompositorThreadHolder::IsInCompositorThread()) { + MOZ_ASSERT(CompositorThread()); + CompositorThread()->Dispatch( + NewRunnableMethod( + "layers::CompositorBridgeParent::StopSharingMetrics", this, + &CompositorBridgeParentBase::StopSharingMetrics, aScrollId, + aApzcId)); + return true; + } + + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + if (!mCanSend) { + return false; + } + return PCompositorBridgeParent::SendReleaseSharedCompositorFrameMetrics( + aScrollId, aApzcId); +} + +CompositorBridgeParent::LayerTreeState::LayerTreeState() + : mApzcTreeManagerParent(nullptr), + mParent(nullptr), + mLayerManager(nullptr), + mContentCompositorBridgeParent(nullptr), + mLayerTree(nullptr), + mUpdatedPluginDataAvailable(false) {} + +CompositorBridgeParent::LayerTreeState::~LayerTreeState() { + if (mController) { + mController->Destroy(); + } +} + +typedef std::map LayerTreeMap; +LayerTreeMap sIndirectLayerTrees; +StaticAutoPtr sIndirectLayerTreesLock; + +static void EnsureLayerTreeMapReady() { + MOZ_ASSERT(NS_IsMainThread()); + if (!sIndirectLayerTreesLock) { + sIndirectLayerTreesLock = new Monitor("IndirectLayerTree"); + mozilla::ClearOnShutdown(&sIndirectLayerTreesLock); + } +} + +template +inline void CompositorBridgeParent::ForEachIndirectLayerTree( + const Lambda& aCallback) { + sIndirectLayerTreesLock->AssertCurrentThreadOwns(); + for (auto it = sIndirectLayerTrees.begin(); it != sIndirectLayerTrees.end(); + it++) { + LayerTreeState* state = &it->second; + if (state->mParent == this) { + aCallback(state, it->first); + } + } +} + +/*static*/ template +inline void CompositorBridgeParent::ForEachWebRenderBridgeParent( + const Lambda& aCallback) { + sIndirectLayerTreesLock->AssertCurrentThreadOwns(); + for (auto& it : sIndirectLayerTrees) { + LayerTreeState* state = &it.second; + if (state->mWrBridge) { + aCallback(state->mWrBridge); + } + } +} + +/** + * A global map referencing each compositor by ID. + * + * This map is used by the ImageBridge protocol to trigger + * compositions without having to keep references to the + * compositor + */ +typedef std::map CompositorMap; +static StaticAutoPtr sCompositorMap; + +void CompositorBridgeParent::Setup() { + EnsureLayerTreeMapReady(); + + MOZ_ASSERT(!sCompositorMap); + sCompositorMap = new CompositorMap; +} + +void CompositorBridgeParent::FinishShutdown() { + MOZ_ASSERT(NS_IsMainThread()); + + if (sCompositorMap) { + MOZ_ASSERT(sCompositorMap->empty()); + sCompositorMap = nullptr; + } + + // TODO: this should be empty by now... + sIndirectLayerTrees.clear(); +} + +#ifdef COMPOSITOR_PERFORMANCE_WARNING +static int32_t CalculateCompositionFrameRate() { + // Used when layout.frame_rate is -1. Needs to be kept in sync with + // DEFAULT_FRAME_RATE in nsRefreshDriver.cpp. + // TODO: This should actually return the vsync rate. + const int32_t defaultFrameRate = 60; + int32_t compositionFrameRatePref = + StaticPrefs::layers_offmainthreadcomposition_frame_rate(); + if (compositionFrameRatePref < 0) { + // Use the same frame rate for composition as for layout. + int32_t layoutFrameRatePref = StaticPrefs::layout_frame_rate(); + if (layoutFrameRatePref < 0) { + // TODO: The main thread frame scheduling code consults the actual + // monitor refresh rate in this case. We should do the same. + return defaultFrameRate; + } + return layoutFrameRatePref; + } + return compositionFrameRatePref; +} +#endif + +CompositorBridgeParent::CompositorBridgeParent( + CompositorManagerParent* aManager, CSSToLayoutDeviceScale aScale, + const TimeDuration& aVsyncRate, const CompositorOptions& aOptions, + bool aUseExternalSurfaceSize, const gfx::IntSize& aSurfaceSize) + : CompositorBridgeParentBase(aManager), + mWidget(nullptr), + mScale(aScale), + mVsyncRate(aVsyncRate), + mPendingTransaction{0}, + mPaused(false), + mHaveCompositionRecorder(false), + mIsForcedFirstPaint(false), + mUseExternalSurfaceSize(aUseExternalSurfaceSize), + mEGLSurfaceSize(aSurfaceSize), + mOptions(aOptions), + mPauseCompositionMonitor("PauseCompositionMonitor"), + mResumeCompositionMonitor("ResumeCompositionMonitor"), + mCompositorBridgeID(0), + mRootLayerTreeID{0}, + mOverrideComposeReadiness(false), + mForceCompositionTask(nullptr), + mCompositorScheduler(nullptr), + mAnimationStorage(nullptr), + mPaintTime(TimeDuration::Forever()) +#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) + , + mLastPluginUpdateLayerTreeId{0}, + mDeferPluginWindows(false), + mPluginWindowsHidden(false) +#endif +{ +} + +void CompositorBridgeParent::InitSameProcess(widget::CompositorWidget* aWidget, + const LayersId& aLayerTreeId) { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + + mWidget = aWidget; + mRootLayerTreeID = aLayerTreeId; + + Initialize(); +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvInitialize( + const LayersId& aRootLayerTreeId) { + MOZ_ASSERT(XRE_IsGPUProcess()); + + mRootLayerTreeID = aRootLayerTreeId; +#ifdef XP_WIN + if (XRE_IsGPUProcess()) { + mWidget->AsWindows()->SetRootLayerTreeID(mRootLayerTreeID); + } +#endif + + Initialize(); + return IPC_OK(); +} + +void CompositorBridgeParent::Initialize() { + MOZ_ASSERT(CompositorThread(), + "The compositor thread must be Initialized before instanciating a " + "CompositorBridgeParent."); + + if (mOptions.UseAPZ()) { + MOZ_ASSERT(!mApzcTreeManager); + MOZ_ASSERT(!mApzSampler); + MOZ_ASSERT(!mApzUpdater); + mApzcTreeManager = + new APZCTreeManager(mRootLayerTreeID, mOptions.UseWebRender()); + mApzSampler = new APZSampler(mApzcTreeManager, mOptions.UseWebRender()); + mApzUpdater = new APZUpdater(mApzcTreeManager, mOptions.UseWebRender()); + } + + if (mOptions.UseWebRender()) { + CompositorAnimationStorage* animationStorage = GetAnimationStorage(); + mOMTASampler = new OMTASampler(animationStorage, mRootLayerTreeID); + } + + mPaused = mOptions.InitiallyPaused(); + + mCompositorBridgeID = 0; + // FIXME: This holds on the the fact that right now the only thing that + // can destroy this instance is initialized on the compositor thread after + // this task has been processed. + MOZ_ASSERT(CompositorThread()); + CompositorThread()->Dispatch(NewRunnableFunction( + "AddCompositorRunnable", &AddCompositor, this, &mCompositorBridgeID)); + + { // scope lock + MonitorAutoLock lock(*sIndirectLayerTreesLock); + sIndirectLayerTrees[mRootLayerTreeID].mParent = this; + } + + LayerScope::SetPixelScale(mScale.scale); + + if (!mOptions.UseWebRender()) { + mCompositorScheduler = new CompositorVsyncScheduler(this, mWidget); + } +} + +LayersId CompositorBridgeParent::RootLayerTreeId() { + MOZ_ASSERT(mRootLayerTreeID.IsValid()); + return mRootLayerTreeID; +} + +CompositorBridgeParent::~CompositorBridgeParent() { + nsTArray textures; + ManagedPTextureParent(textures); + // We expect all textures to be destroyed by now. + MOZ_DIAGNOSTIC_ASSERT(textures.Length() == 0); + for (unsigned int i = 0; i < textures.Length(); ++i) { + RefPtr tex = TextureHost::AsTextureHost(textures[i]); + tex->DeallocateDeviceData(); + } +} + +void CompositorBridgeParent::ForceIsFirstPaint() { + if (mWrBridge) { + mIsForcedFirstPaint = true; + } else { + mCompositionManager->ForceIsFirstPaint(); + } +} + +void CompositorBridgeParent::StopAndClearResources() { + if (mForceCompositionTask) { + mForceCompositionTask->Cancel(); + mForceCompositionTask = nullptr; + } + + mPaused = true; + + // We need to clear the APZ tree before we destroy the WebRender API below, + // because in the case of async scene building that will shut down the updater + // thread and we need to run the task before that happens. + MOZ_ASSERT((mApzSampler != nullptr) == (mApzcTreeManager != nullptr)); + MOZ_ASSERT((mApzUpdater != nullptr) == (mApzcTreeManager != nullptr)); + if (mApzUpdater) { + mApzSampler->Destroy(); + mApzSampler = nullptr; + mApzUpdater->ClearTree(mRootLayerTreeID); + mApzUpdater = nullptr; + mApzcTreeManager = nullptr; + } + + // Ensure that the layer manager is destroyed before CompositorBridgeChild. + if (mLayerManager) { + MonitorAutoLock lock(*sIndirectLayerTreesLock); + ForEachIndirectLayerTree([this](LayerTreeState* lts, LayersId) -> void { + mLayerManager->ClearCachedResources(lts->mRoot); + lts->mLayerManager = nullptr; + lts->mParent = nullptr; + }); + mLayerManager->Destroy(); + mLayerManager = nullptr; + mCompositionManager = nullptr; + } + + if (mWrBridge) { + // Ensure we are not holding the sIndirectLayerTreesLock when destroying + // the WebRenderBridgeParent instances because it may block on WR. + std::vector> indirectBridgeParents; + { // scope lock + MonitorAutoLock lock(*sIndirectLayerTreesLock); + ForEachIndirectLayerTree([&](LayerTreeState* lts, LayersId) -> void { + if (lts->mWrBridge) { + indirectBridgeParents.emplace_back(lts->mWrBridge.forget()); + } + lts->mParent = nullptr; + }); + } + for (const RefPtr& bridge : indirectBridgeParents) { + bridge->Destroy(); + } + indirectBridgeParents.clear(); + + RefPtr api = mWrBridge->GetWebRenderAPI(); + // Ensure we are not holding the sIndirectLayerTreesLock here because we + // are going to block on WR threads in order to shut it down properly. + mWrBridge->Destroy(); + mWrBridge = nullptr; + + if (api) { + // Make extra sure we are done cleaning WebRender up before continuing. + // After that we wont have a way to talk to a lot of the webrender parts. + api->FlushSceneBuilder(); + api = nullptr; + } + + if (mAsyncImageManager) { + mAsyncImageManager->Destroy(); + // WebRenderAPI should be already destructed + mAsyncImageManager = nullptr; + } + } + + if (mCompositor) { + mCompositor->Destroy(); + mCompositor = nullptr; + } + + // This must be destroyed now since it accesses the widget. + if (mCompositorScheduler) { + mCompositorScheduler->Destroy(); + mCompositorScheduler = nullptr; + } + + if (mOMTASampler) { + mOMTASampler->Destroy(); + mOMTASampler = nullptr; + } + + // After this point, it is no longer legal to access the widget. + mWidget = nullptr; + + // Clear mAnimationStorage here to ensure that the compositor thread + // still exists when we destroy it. + mAnimationStorage = nullptr; +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvWillClose() { + StopAndClearResources(); + // Once we get the WillClose message, the client side is going to go away + // soon and we can't be guaranteed that sending messages will work. + mCanSend = false; + return IPC_OK(); +} + +void CompositorBridgeParent::DeferredDestroy() { + MOZ_ASSERT(!NS_IsMainThread()); + mSelfRef = nullptr; +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvPause() { + PauseComposition(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvRequestFxrOutput() { +#ifdef XP_WIN + // Continue forwarding the request to the Widget + SwapChain + mWidget->AsWindows()->RequestFxrOutput(); +#endif + + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvResume() { + ResumeComposition(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvResumeAsync() { + ResumeComposition(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvMakeSnapshot( + const SurfaceDescriptor& aInSnapshot, const gfx::IntRect& aRect) { + RefPtr target = + GetDrawTargetForDescriptor(aInSnapshot, gfx::BackendType::CAIRO); + MOZ_ASSERT(target); + if (!target) { + // We kill the content process rather than have it continue with an invalid + // snapshot, that may be too harsh and we could decide to return some sort + // of error to the child process and let it deal with it... + return IPC_FAIL_NO_REASON(this); + } + ForceComposeToTarget(target, &aRect); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +CompositorBridgeParent::RecvWaitOnTransactionProcessed() { + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvFlushRendering() { + if (mWrBridge) { + mWrBridge->FlushRendering(); + return IPC_OK(); + } + + if (mCompositorScheduler->NeedsComposite()) { + CancelCurrentCompositeTask(); + ForceComposeToTarget(nullptr); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvFlushRenderingAsync() { + if (mWrBridge) { + mWrBridge->FlushRendering(false); + return IPC_OK(); + } + + return RecvFlushRendering(); +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvForcePresent() { + if (mWrBridge) { + mWrBridge->ScheduleForcedGenerateFrame(); + } + // During the shutdown sequence mLayerManager may be null + if (mLayerManager) { + mLayerManager->ForcePresent(); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvNotifyRegionInvalidated( + const nsIntRegion& aRegion) { + if (mLayerManager) { + mLayerManager->AddInvalidRegion(aRegion); + } + return IPC_OK(); +} + +void CompositorBridgeParent::Invalidate() { + if (mLayerManager) { + mLayerManager->InvalidateAll(); + } +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvStartFrameTimeRecording( + const int32_t& aBufferSize, uint32_t* aOutStartIndex) { + if (mLayerManager) { + *aOutStartIndex = mLayerManager->StartFrameTimeRecording(aBufferSize); + } else if (mWrBridge) { + *aOutStartIndex = mWrBridge->StartFrameTimeRecording(aBufferSize); + } else { + *aOutStartIndex = 0; + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvStopFrameTimeRecording( + const uint32_t& aStartIndex, nsTArray* intervals) { + if (mLayerManager) { + mLayerManager->StopFrameTimeRecording(aStartIndex, *intervals); + } else if (mWrBridge) { + mWrBridge->StopFrameTimeRecording(aStartIndex, *intervals); + } + return IPC_OK(); +} + +void CompositorBridgeParent::ActorDestroy(ActorDestroyReason why) { + mCanSend = false; + + StopAndClearResources(); + + RemoveCompositor(mCompositorBridgeID); + + mCompositionManager = nullptr; + + { // scope lock + MonitorAutoLock lock(*sIndirectLayerTreesLock); + sIndirectLayerTrees.erase(mRootLayerTreeID); + } + + // There are chances that the ref count reaches zero on the main thread + // shortly after this function returns while some ipdl code still needs to run + // on this thread. We must keep the compositor parent alive untill the code + // handling message reception is finished on this thread. + mSelfRef = this; + NS_GetCurrentThread()->Dispatch( + NewRunnableMethod("layers::CompositorBridgeParent::DeferredDestroy", this, + &CompositorBridgeParent::DeferredDestroy)); +} + +void CompositorBridgeParent::ScheduleRenderOnCompositorThread() { + MOZ_ASSERT(CompositorThread()); + CompositorThread()->Dispatch( + NewRunnableMethod("layers::CompositorBridgeParent::ScheduleComposition", + this, &CompositorBridgeParent::ScheduleComposition)); +} + +void CompositorBridgeParent::PauseComposition() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread(), + "PauseComposition() can only be called on the compositor thread"); + + MonitorAutoLock lock(mPauseCompositionMonitor); + + if (!mPaused) { + mPaused = true; + + TimeStamp now = TimeStamp::Now(); + if (mCompositor) { + mCompositor->Pause(); + DidComposite(VsyncId(), now, now); + } else if (mWrBridge) { + mWrBridge->Pause(); + NotifyPipelineRendered(mWrBridge->PipelineId(), + mWrBridge->GetCurrentEpoch(), VsyncId(), now, now, + now); + } + } + + // if anyone's waiting to make sure that composition really got paused, tell + // them + lock.NotifyAll(); +} + +void CompositorBridgeParent::ResumeComposition() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread(), + "ResumeComposition() can only be called on the compositor thread"); + + MonitorAutoLock lock(mResumeCompositionMonitor); + + bool resumed = + mOptions.UseWebRender() ? mWrBridge->Resume() : mCompositor->Resume(); + if (!resumed) { +#ifdef MOZ_WIDGET_ANDROID + // We can't get a surface. This could be because the activity changed + // between the time resume was scheduled and now. + __android_log_print( + ANDROID_LOG_INFO, "CompositorBridgeParent", + "Unable to renew compositor surface; remaining in paused state"); +#endif + lock.NotifyAll(); + return; + } + + mPaused = false; + + Invalidate(); + mCompositorScheduler->ForceComposeToTarget(nullptr, nullptr); + + // if anyone's waiting to make sure that composition really got resumed, tell + // them + lock.NotifyAll(); +} + +void CompositorBridgeParent::ForceComposition() { + // Cancel the orientation changed state to force composition + mForceCompositionTask = nullptr; + ScheduleRenderOnCompositorThread(); +} + +void CompositorBridgeParent::CancelCurrentCompositeTask() { + mCompositorScheduler->CancelCurrentCompositeTask(); +} + +void CompositorBridgeParent::SetEGLSurfaceRect(int x, int y, int width, + int height) { + NS_ASSERTION(mUseExternalSurfaceSize, + "Compositor created without UseExternalSurfaceSize provided"); + mEGLSurfaceSize.SizeTo(width, height); + if (mCompositor) { + mCompositor->SetDestinationSurfaceSize( + gfx::IntSize(mEGLSurfaceSize.width, mEGLSurfaceSize.height)); + if (mCompositor->AsCompositorOGL()) { + mCompositor->AsCompositorOGL()->SetSurfaceOrigin(ScreenIntPoint(x, y)); + } + } +} + +void CompositorBridgeParent::ResumeCompositionAndResize(int x, int y, int width, + int height) { + SetEGLSurfaceRect(x, y, width, height); + ResumeComposition(); +} + +void CompositorBridgeParent::UpdatePaintTime(LayerTransactionParent* aLayerTree, + const TimeDuration& aPaintTime) { + // We get a lot of paint timings for things with empty transactions. + if (!mLayerManager || aPaintTime.ToMilliseconds() < 1.0) { + return; + } + + mLayerManager->SetPaintTime(aPaintTime); +} + +void CompositorBridgeParent::RegisterPayloads( + LayerTransactionParent* aLayerTree, + const nsTArray& aPayload) { + // We get a lot of paint timings for things with empty transactions. + if (!mLayerManager) { + return; + } + + mLayerManager->RegisterPayloads(aPayload); +} + +void CompositorBridgeParent::NotifyShadowTreeTransaction( + LayersId aId, bool aIsFirstPaint, const FocusTarget& aFocusTarget, + bool aScheduleComposite, uint32_t aPaintSequenceNumber, + bool aIsRepeatTransaction, bool aHitTestUpdate) { + if (!aIsRepeatTransaction && mLayerManager && mLayerManager->GetRoot()) { + // Process plugin data here to give time for them to update before the next + // composition. + bool pluginsUpdatedFlag = true; + AutoResolveRefLayers resolve(mCompositionManager, this, nullptr, + &pluginsUpdatedFlag); + +#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) + // If plugins haven't been updated, stop waiting. + if (!pluginsUpdatedFlag) { + mWaitForPluginsUntil = TimeStamp(); + mHaveBlockedForPlugins = false; + } +#endif + + if (mApzUpdater) { + mApzUpdater->UpdateFocusState(mRootLayerTreeID, aId, aFocusTarget); + if (aHitTestUpdate) { + mApzUpdater->UpdateHitTestingTree( + mLayerManager->GetRoot(), aIsFirstPaint, aId, aPaintSequenceNumber); + } + } + + mLayerManager->NotifyShadowTreeTransaction(); + } + if (aScheduleComposite) { + ScheduleComposition(); + } +} + +void CompositorBridgeParent::ScheduleComposition() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + if (mPaused) { + return; + } + + if (mWrBridge) { + mWrBridge->ScheduleGenerateFrame(); + } else { + mCompositorScheduler->ScheduleComposition(); + } +} + +// Go down the composite layer tree, setting properties to match their +// content-side counterparts. +/* static */ +void CompositorBridgeParent::SetShadowProperties(Layer* aLayer) { + ForEachNode(aLayer, [](Layer* layer) { + if (Layer* maskLayer = layer->GetMaskLayer()) { + SetShadowProperties(maskLayer); + } + for (size_t i = 0; i < layer->GetAncestorMaskLayerCount(); i++) { + SetShadowProperties(layer->GetAncestorMaskLayerAt(i)); + } + + // FIXME: Bug 717688 -- Do these updates in + // LayerTransactionParent::RecvUpdate. + HostLayer* layerCompositor = layer->AsHostLayer(); + // Set the layerComposite's base transform to the layer's base transform. + const auto& animations = layer->GetPropertyAnimationGroups(); + // If there is any animation, the animation value will override + // non-animated value later, so we don't need to set the non-animated + // value here. + if (animations.IsEmpty()) { + layerCompositor->SetShadowBaseTransform(layer->GetBaseTransform()); + layerCompositor->SetShadowTransformSetByAnimation(false); + layerCompositor->SetShadowOpacity(layer->GetOpacity()); + layerCompositor->SetShadowOpacitySetByAnimation(false); + } + layerCompositor->SetShadowVisibleRegion(layer->GetVisibleRegion()); + layerCompositor->SetShadowClipRect(layer->GetClipRect()); + }); +} + +void CompositorBridgeParent::CompositeToTarget(VsyncId aId, DrawTarget* aTarget, + const gfx::IntRect* aRect) { + AUTO_PROFILER_TRACING_MARKER("Paint", "Composite", GRAPHICS); + AUTO_PROFILER_LABEL("CompositorBridgeParent::CompositeToTarget", GRAPHICS); + PerfStats::AutoMetricRecording autoRecording; + + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread(), + "Composite can only be called on the compositor thread"); + TimeStamp start = TimeStamp::Now(); + + if (!CanComposite()) { + TimeStamp end = TimeStamp::Now(); + DidComposite(aId, start, end); + return; + } + +#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) + if (!mWaitForPluginsUntil.IsNull() && mWaitForPluginsUntil > start) { + mHaveBlockedForPlugins = true; + ScheduleComposition(); + return; + } +#endif + + /* + * AutoResolveRefLayers handles two tasks related to Windows and Linux + * plugin window management: + * 1) calculating if we have remote content in the view. If we do not have + * remote content, all plugin windows for this CompositorBridgeParent (window) + * can be hidden since we do not support plugins in chrome when running + * under e10s. + * 2) Updating plugin position, size, and clip. We do this here while the + * remote layer tree is hooked up to to chrome layer tree. This is needed + * since plugin clipping can depend on chrome (for example, due to tab modal + * prompts). Updates in step 2 are applied via an async ipc message sent + * to the main thread. + */ + bool hasRemoteContent = false; + bool updatePluginsFlag = true; + AutoResolveRefLayers resolve(mCompositionManager, this, &hasRemoteContent, + &updatePluginsFlag); + +#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) + // We do not support plugins in local content. When switching tabs + // to local pages, hide every plugin associated with the window. + if (!hasRemoteContent && gfxVars::BrowserTabsRemoteAutostart() && + mCachedPluginData.Length()) { + Unused << SendHideAllPlugins(GetWidget()->GetWidgetKey()); + mCachedPluginData.Clear(); + } +#endif + + nsCString none; + if (aTarget) { + mLayerManager->BeginTransactionWithDrawTarget(aTarget, *aRect); + } else { + mLayerManager->BeginTransaction(none); + } + + SetShadowProperties(mLayerManager->GetRoot()); + + if (mForceCompositionTask && !mOverrideComposeReadiness) { + if (mCompositionManager->ReadyForCompose()) { + mForceCompositionTask->Cancel(); + mForceCompositionTask = nullptr; + } else { + return; + } + } + + mCompositionManager->ComputeRotation(); + + SampleTime time = mTestTime ? SampleTime::FromTest(*mTestTime) + : mCompositorScheduler->GetLastComposeTime(); + bool requestNextFrame = + mCompositionManager->TransformShadowTree(time, mVsyncRate); + + if (requestNextFrame) { + ScheduleComposition(); +#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) + // If we have visible windowed plugins then we need to wait for content (and + // then the plugins) to have been updated by the active animation. + if (!mPluginWindowsHidden && mCachedPluginData.Length()) { + mWaitForPluginsUntil = + mCompositorScheduler->GetLastComposeTime().Time() + (mVsyncRate * 2); + } +#endif + } + + RenderTraceLayers(mLayerManager->GetRoot(), "0000"); + + if (StaticPrefs::layers_dump_host_layers() || StaticPrefs::layers_dump()) { + printf_stderr("Painting --- compositing layer tree:\n"); + mLayerManager->Dump(/* aSorted = */ true); + } + mLayerManager->SetDebugOverlayWantsNextFrame(false); + mLayerManager->EndTransaction(time.Time()); + + if (!aTarget) { + TimeStamp end = TimeStamp::Now(); + DidComposite(aId, start, end); + } + + // We're not really taking advantage of the stored composite-again-time here. + // We might be able to skip the next few composites altogether. However, + // that's a bit complex to implement and we'll get most of the advantage + // by skipping compositing when we detect there's nothing invalid. This is why + // we do "composite until" rather than "composite again at". + // + // TODO(bug 1328602) Figure out what we should do here with the render thread. + if (!mLayerManager->GetCompositeUntilTime().IsNull() || + mLayerManager->DebugOverlayWantsNextFrame()) { + ScheduleComposition(); + } + +#ifdef COMPOSITOR_PERFORMANCE_WARNING + TimeDuration executionTime = + TimeStamp::Now() - mCompositorScheduler->GetLastComposeTime().Time(); + TimeDuration frameBudget = TimeDuration::FromMilliseconds(15); + int32_t frameRate = CalculateCompositionFrameRate(); + if (frameRate > 0) { + frameBudget = TimeDuration::FromSeconds(1.0 / frameRate); + } + if (executionTime > frameBudget) { + printf_stderr("Compositor: Composite execution took %4.1f ms\n", + executionTime.ToMilliseconds()); + } +#endif + + // 0 -> Full-tilt composite + if (StaticPrefs::layers_offmainthreadcomposition_frame_rate() == 0 || + mLayerManager->AlwaysScheduleComposite()) { + // Special full-tilt composite mode for performance testing + ScheduleComposition(); + } + + // TODO(bug 1328602) Need an equivalent that works with the rende thread. + mLayerManager->SetCompositionTime(TimeStamp()); + + mozilla::Telemetry::AccumulateTimeDelta(mozilla::Telemetry::COMPOSITE_TIME, + start); +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvRemotePluginsReady() { +#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) + mWaitForPluginsUntil = TimeStamp(); + if (mHaveBlockedForPlugins) { + mHaveBlockedForPlugins = false; + ForceComposeToTarget(nullptr); + } else { + ScheduleComposition(); + } + return IPC_OK(); +#else + MOZ_ASSERT_UNREACHABLE( + "CompositorBridgeParent::RecvRemotePluginsReady calls " + "unexpected on this platform."); + return IPC_FAIL_NO_REASON(this); +#endif +} + +void CompositorBridgeParent::ForceComposeToTarget(DrawTarget* aTarget, + const gfx::IntRect* aRect) { + AUTO_PROFILER_LABEL("CompositorBridgeParent::ForceComposeToTarget", GRAPHICS); + + AutoRestore override(mOverrideComposeReadiness); + mOverrideComposeReadiness = true; + mCompositorScheduler->ForceComposeToTarget(aTarget, aRect); +} + +PAPZCTreeManagerParent* CompositorBridgeParent::AllocPAPZCTreeManagerParent( + const LayersId& aLayersId) { + // This should only ever get called in the GPU process. + MOZ_ASSERT(XRE_IsGPUProcess()); + // We should only ever get this if APZ is enabled in this compositor. + MOZ_ASSERT(mOptions.UseAPZ()); + // The mApzcTreeManager and mApzUpdater should have been created via + // RecvInitialize() + MOZ_ASSERT(mApzcTreeManager); + MOZ_ASSERT(mApzUpdater); + // The main process should pass in 0 because we assume mRootLayerTreeID + MOZ_ASSERT(!aLayersId.IsValid()); + + MonitorAutoLock lock(*sIndirectLayerTreesLock); + CompositorBridgeParent::LayerTreeState& state = + sIndirectLayerTrees[mRootLayerTreeID]; + MOZ_ASSERT(state.mParent.get() == this); + MOZ_ASSERT(!state.mApzcTreeManagerParent); + state.mApzcTreeManagerParent = new APZCTreeManagerParent( + mRootLayerTreeID, mApzcTreeManager, mApzUpdater); + + return state.mApzcTreeManagerParent; +} + +bool CompositorBridgeParent::DeallocPAPZCTreeManagerParent( + PAPZCTreeManagerParent* aActor) { + delete aActor; + return true; +} + +void CompositorBridgeParent::AllocateAPZCTreeManagerParent( + const MonitorAutoLock& aProofOfLayerTreeStateLock, + const LayersId& aLayersId, LayerTreeState& aState) { + MOZ_ASSERT(aState.mParent == this); + MOZ_ASSERT(mApzcTreeManager); + MOZ_ASSERT(mApzUpdater); + MOZ_ASSERT(!aState.mApzcTreeManagerParent); + aState.mApzcTreeManagerParent = + new APZCTreeManagerParent(aLayersId, mApzcTreeManager, mApzUpdater); +} + +PAPZParent* CompositorBridgeParent::AllocPAPZParent(const LayersId& aLayersId) { + // This is the CompositorBridgeParent for a window, and so should only be + // creating a PAPZ instance if it lives in the GPU process. Instances that + // live in the UI process should going through SetControllerForLayerTree. + MOZ_RELEASE_ASSERT(XRE_IsGPUProcess()); + + // We should only ever get this if APZ is enabled on this compositor. + MOZ_RELEASE_ASSERT(mOptions.UseAPZ()); + + // The main process should pass in 0 because we assume mRootLayerTreeID + MOZ_RELEASE_ASSERT(!aLayersId.IsValid()); + + RemoteContentController* controller = new RemoteContentController(); + + // Increment the controller's refcount before we return it. This will keep the + // controller alive until it is released by IPDL in DeallocPAPZParent. + controller->AddRef(); + + MonitorAutoLock lock(*sIndirectLayerTreesLock); + CompositorBridgeParent::LayerTreeState& state = + sIndirectLayerTrees[mRootLayerTreeID]; + MOZ_RELEASE_ASSERT(!state.mController); + state.mController = controller; + + return controller; +} + +bool CompositorBridgeParent::DeallocPAPZParent(PAPZParent* aActor) { + RemoteContentController* controller = + static_cast(aActor); + controller->Release(); + return true; +} + +RefPtr CompositorBridgeParent::GetAPZSampler() const { + return mApzSampler; +} + +RefPtr CompositorBridgeParent::GetAPZUpdater() const { + return mApzUpdater; +} + +RefPtr CompositorBridgeParent::GetOMTASampler() const { + return mOMTASampler; +} + +CompositorBridgeParent* +CompositorBridgeParent::GetCompositorBridgeParentFromLayersId( + const LayersId& aLayersId) { + MonitorAutoLock lock(*sIndirectLayerTreesLock); + return sIndirectLayerTrees[aLayersId].mParent; +} + +/*static*/ +RefPtr +CompositorBridgeParent::GetCompositorBridgeParentFromWindowId( + const wr::WindowId& aWindowId) { + MonitorAutoLock lock(*sIndirectLayerTreesLock); + for (auto it = sIndirectLayerTrees.begin(); it != sIndirectLayerTrees.end(); + it++) { + LayerTreeState* state = &it->second; + if (!state->mWrBridge) { + continue; + } + // state->mWrBridge might be a root WebRenderBridgeParent or one of a + // content process, but in either case the state->mParent will be the same. + // So we don't need to distinguish between the two. + if (RefPtr api = state->mWrBridge->GetWebRenderAPI()) { + if (api->GetId() == aWindowId) { + return state->mParent; + } + } + } + return nullptr; +} + +bool CompositorBridgeParent::CanComposite() { + return mLayerManager && mLayerManager->GetRoot() && !mPaused; +} + +void CompositorBridgeParent::ScheduleRotationOnCompositorThread( + const TargetConfig& aTargetConfig, bool aIsFirstPaint) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + + if (!aIsFirstPaint && !mCompositionManager->IsFirstPaint() && + mCompositionManager->RequiresReorientation(aTargetConfig.orientation())) { + if (mForceCompositionTask != nullptr) { + mForceCompositionTask->Cancel(); + } + RefPtr task = NewCancelableRunnableMethod( + "layers::CompositorBridgeParent::ForceComposition", this, + &CompositorBridgeParent::ForceComposition); + mForceCompositionTask = task; + if (StaticPrefs::layers_orientation_sync_timeout() == 0) { + CompositorThread()->Dispatch(task.forget()); + } else { + CompositorThread()->DelayedDispatch( + task.forget(), StaticPrefs::layers_orientation_sync_timeout()); + } + } +} + +void CompositorBridgeParent::ShadowLayersUpdated( + LayerTransactionParent* aLayerTree, const TransactionInfo& aInfo, + bool aHitTestUpdate) { + const TargetConfig& targetConfig = aInfo.targetConfig(); + + ScheduleRotationOnCompositorThread(targetConfig, aInfo.isFirstPaint()); + + // Instruct the LayerManager to update its render bounds now. Since all the + // orientation change, dimension change would be done at the stage, update the + // size here is free of race condition. + mLayerManager->UpdateRenderBounds(targetConfig.naturalBounds()); + mLayerManager->SetRegionToClear(targetConfig.clearRegion()); + if (mLayerManager->GetCompositor()) { + mLayerManager->GetCompositor()->SetScreenRotation(targetConfig.rotation()); + } + + mCompositionManager->Updated(aInfo.isFirstPaint(), targetConfig); + Layer* root = aLayerTree->GetRoot(); + mLayerManager->SetRoot(root); + + if (mApzUpdater && !aInfo.isRepeatTransaction()) { + mApzUpdater->UpdateFocusState(mRootLayerTreeID, mRootLayerTreeID, + aInfo.focusTarget()); + + if (aHitTestUpdate) { + AutoResolveRefLayers resolve(mCompositionManager); + + mApzUpdater->UpdateHitTestingTree(root, aInfo.isFirstPaint(), + mRootLayerTreeID, + aInfo.paintSequenceNumber()); + } + } + + // The transaction ID might get reset to 1 if the page gets reloaded, see + // https://bugzilla.mozilla.org/show_bug.cgi?id=1145295#c41 + // Otherwise, it should be continually increasing. + MOZ_ASSERT(aInfo.id() == TransactionId{1} || + aInfo.id() > mPendingTransaction); + mPendingTransaction = aInfo.id(); + mRefreshStartTime = aInfo.refreshStart(); + mTxnStartTime = aInfo.transactionStart(); + mFwdTime = aInfo.fwdTime(); + RegisterPayloads(aLayerTree, aInfo.payload()); + + if (root) { + SetShadowProperties(root); + } + if (aInfo.scheduleComposite()) { + ScheduleComposition(); + if (mPaused) { + TimeStamp now = TimeStamp::Now(); + DidComposite(VsyncId(), now, now); + } + } + mLayerManager->NotifyShadowTreeTransaction(); +} + +void CompositorBridgeParent::ScheduleComposite( + LayerTransactionParent* aLayerTree) { + ScheduleComposition(); +} + +bool CompositorBridgeParent::SetTestSampleTime(const LayersId& aId, + const TimeStamp& aTime) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + + if (aTime.IsNull()) { + return false; + } + + mTestTime = Some(aTime); + if (mApzcTreeManager) { + mApzcTreeManager->SetTestSampleTime(mTestTime); + } + + if (mWrBridge) { + mWrBridge->FlushRendering(); + return true; + } + + bool testComposite = + mCompositionManager && mCompositorScheduler->NeedsComposite(); + + // Update but only if we were already scheduled to animate + if (testComposite) { + AutoResolveRefLayers resolve(mCompositionManager); + bool requestNextFrame = mCompositionManager->TransformShadowTree( + SampleTime::FromTest(aTime), mVsyncRate); + if (!requestNextFrame) { + CancelCurrentCompositeTask(); + // Pretend we composited in case someone is wating for this event. + TimeStamp now = TimeStamp::Now(); + DidComposite(VsyncId(), now, now); + } + } + + return true; +} + +void CompositorBridgeParent::LeaveTestMode(const LayersId& aId) { + mTestTime = Nothing(); + if (mApzcTreeManager) { + mApzcTreeManager->SetTestSampleTime(mTestTime); + } +} + +void CompositorBridgeParent::ApplyAsyncProperties( + LayerTransactionParent* aLayerTree, TransformsToSkip aSkip) { + // NOTE: This should only be used for testing. For example, when mTestTime is + // non-empty, or when called from test-only methods like + // LayerTransactionParent::RecvGetAnimationTransform. + + // Synchronously update the layer tree + if (aLayerTree->GetRoot()) { + AutoResolveRefLayers resolve(mCompositionManager); + SetShadowProperties(mLayerManager->GetRoot()); + + SampleTime time; + if (mTestTime) { + time = SampleTime::FromTest(*mTestTime); + } else { + time = mCompositorScheduler->GetLastComposeTime(); + } + bool requestNextFrame = + mCompositionManager->TransformShadowTree(time, mVsyncRate, aSkip); + if (!requestNextFrame) { + CancelCurrentCompositeTask(); + // Pretend we composited in case someone is waiting for this event. + TimeStamp now = TimeStamp::Now(); + DidComposite(VsyncId(), now, now); + } + } +} + +CompositorAnimationStorage* CompositorBridgeParent::GetAnimationStorage() { + if (!mAnimationStorage) { + mAnimationStorage = new CompositorAnimationStorage(this); + } + return mAnimationStorage; +} + +void CompositorBridgeParent::NotifyJankedAnimations( + const JankedAnimations& aJankedAnimations) { + MOZ_ASSERT(!aJankedAnimations.empty()); + + if (StaticPrefs::layout_animation_prerender_partial_jank()) { + return; + } + + for (const auto& entry : aJankedAnimations) { + const LayersId& layersId = entry.first; + const nsTArray& animations = entry.second; + if (layersId == mRootLayerTreeID) { + if (mLayerManager) { + Unused << SendNotifyJankedAnimations(LayersId{0}, animations); + } + // It unlikely happens multiple processes have janked animations at same + // time, so it should be fine with enumerating sIndirectLayerTrees every + // time. + } else if (const LayerTreeState* state = GetIndirectShadowTree(layersId)) { + if (ContentCompositorBridgeParent* cpcp = + state->mContentCompositorBridgeParent) { + Unused << cpcp->SendNotifyJankedAnimations(layersId, animations); + } + } + } +} + +void CompositorBridgeParent::SetTestAsyncScrollOffset( + const LayersId& aLayersId, const ScrollableLayerGuid::ViewID& aScrollId, + const CSSPoint& aPoint) { + if (mApzUpdater) { + MOZ_ASSERT(aLayersId.IsValid()); + mApzUpdater->SetTestAsyncScrollOffset(aLayersId, aScrollId, aPoint); + } +} + +void CompositorBridgeParent::SetTestAsyncZoom( + const LayersId& aLayersId, const ScrollableLayerGuid::ViewID& aScrollId, + const LayerToParentLayerScale& aZoom) { + if (mApzUpdater) { + MOZ_ASSERT(aLayersId.IsValid()); + mApzUpdater->SetTestAsyncZoom(aLayersId, aScrollId, aZoom); + } +} + +void CompositorBridgeParent::FlushApzRepaints(const LayersId& aLayersId) { + MOZ_ASSERT(mApzUpdater); + MOZ_ASSERT(aLayersId.IsValid()); + mApzUpdater->RunOnControllerThread( + aLayersId, NS_NewRunnableFunction( + "layers::CompositorBridgeParent::FlushApzRepaints", + [=]() { APZCTreeManager::FlushApzRepaints(aLayersId); })); +} + +void CompositorBridgeParent::GetAPZTestData(const LayersId& aLayersId, + APZTestData* aOutData) { + if (mApzUpdater) { + MOZ_ASSERT(aLayersId.IsValid()); + mApzUpdater->GetAPZTestData(aLayersId, aOutData); + } +} + +void CompositorBridgeParent::GetFrameUniformity(const LayersId& aLayersId, + FrameUniformityData* aOutData) { + if (mCompositionManager) { + mCompositionManager->GetFrameUniformity(aOutData); + } +} + +void CompositorBridgeParent::SetConfirmedTargetAPZC( + const LayersId& aLayersId, const uint64_t& aInputBlockId, + nsTArray&& aTargets) { + if (!mApzcTreeManager || !mApzUpdater) { + return; + } + // Need to specifically bind this since it's overloaded. + void (APZCTreeManager::*setTargetApzcFunc)( + uint64_t, const nsTArray&) = + &APZCTreeManager::SetTargetAPZC; + RefPtr task = + NewRunnableMethod>>( + "layers::CompositorBridgeParent::SetConfirmedTargetAPZC", + mApzcTreeManager.get(), setTargetApzcFunc, aInputBlockId, + std::move(aTargets)); + mApzUpdater->RunOnControllerThread(aLayersId, task.forget()); +} + +void CompositorBridgeParent::SetFixedLayerMargins(ScreenIntCoord aTop, + ScreenIntCoord aBottom) { + if (AsyncCompositionManager* manager = GetCompositionManager(nullptr)) { + manager->SetFixedLayerMargins(aTop, aBottom); + } + + if (mApzcTreeManager) { + mApzcTreeManager->SetFixedLayerMargins(aTop, aBottom); + } + + Invalidate(); + ScheduleComposition(); +} + +void CompositorBridgeParent::InitializeLayerManager( + const nsTArray& aBackendHints) { + NS_ASSERTION(!mLayerManager, "Already initialised mLayerManager"); + NS_ASSERTION(!mCompositor, "Already initialised mCompositor"); + + if (!InitializeAdvancedLayers(aBackendHints, nullptr)) { + mCompositor = NewCompositor(aBackendHints); + if (!mCompositor) { + return; + } +#ifdef XP_WIN + if (mCompositor->AsBasicCompositor() && XRE_IsGPUProcess()) { + // BasicCompositor does not use CompositorWindow, + // then if CompositorWindow exists, it needs to be destroyed. + mWidget->AsWindows()->DestroyCompositorWindow(); + } +#endif + mLayerManager = new LayerManagerComposite(mCompositor); + } + mLayerManager->SetCompositorBridgeID(mCompositorBridgeID); + + MonitorAutoLock lock(*sIndirectLayerTreesLock); + sIndirectLayerTrees[mRootLayerTreeID].mLayerManager = mLayerManager; +} + +bool CompositorBridgeParent::InitializeAdvancedLayers( + const nsTArray& aBackendHints, + TextureFactoryIdentifier* aOutIdentifier) { +#ifdef XP_WIN + if (!mOptions.UseAdvancedLayers()) { + return false; + } + + // Currently LayerManagerMLGPU hardcodes a D3D11 device, so we reject using + // AL if LAYERS_D3D11 isn't in the backend hints. + if (!aBackendHints.Contains(LayersBackend::LAYERS_D3D11)) { + return false; + } + + RefPtr manager = new LayerManagerMLGPU(mWidget); + if (!manager->Initialize()) { + return false; + } + + if (aOutIdentifier) { + *aOutIdentifier = manager->GetTextureFactoryIdentifier(); + } + mLayerManager = manager; + return true; +#else + return false; +#endif +} + +RefPtr CompositorBridgeParent::NewCompositor( + const nsTArray& aBackendHints) { + for (size_t i = 0; i < aBackendHints.Length(); ++i) { + RefPtr compositor; + if (aBackendHints[i] == LayersBackend::LAYERS_OPENGL) { + compositor = + new CompositorOGL(this, mWidget, mEGLSurfaceSize.width, + mEGLSurfaceSize.height, mUseExternalSurfaceSize); + } else if (aBackendHints[i] == LayersBackend::LAYERS_BASIC) { +#ifdef MOZ_WIDGET_GTK + if (gfxVars::UseXRender()) { + compositor = new X11BasicCompositor(this, mWidget); + } else +#endif + { + compositor = new BasicCompositor(this, mWidget); + } +#ifdef XP_WIN + } else if (aBackendHints[i] == LayersBackend::LAYERS_D3D11) { + compositor = new CompositorD3D11(this, mWidget); +#endif + } + nsCString failureReason; + + // Some software GPU emulation implementations will happily try to create + // unreasonably big surfaces and then fail in awful ways. + // Let's at least limit this to the default max texture size we use for + // content, anything larger than that will fail to render on the content + // side anyway. We can revisit this value and make it even tighter if need + // be. + const int max_fb_size = 32767; + const LayoutDeviceIntSize size = mWidget->GetClientSize(); + if (size.width > max_fb_size || size.height > max_fb_size) { + failureReason = "FEATURE_FAILURE_MAX_FRAMEBUFFER_SIZE"; + return nullptr; + } + + MOZ_ASSERT(!gfxVars::UseWebRender() || + aBackendHints[i] == LayersBackend::LAYERS_BASIC); + if (compositor && compositor->Initialize(&failureReason)) { + if (failureReason.IsEmpty()) { + failureReason = "SUCCESS"; + } + + // should only report success here + if (aBackendHints[i] == LayersBackend::LAYERS_OPENGL) { + Telemetry::Accumulate(Telemetry::OPENGL_COMPOSITING_FAILURE_ID, + failureReason); + } +#ifdef XP_WIN + else if (aBackendHints[i] == LayersBackend::LAYERS_D3D11) { + Telemetry::Accumulate(Telemetry::D3D11_COMPOSITING_FAILURE_ID, + failureReason); + } +#endif + + return compositor; + } + + // report any failure reasons here + if (aBackendHints[i] == LayersBackend::LAYERS_OPENGL) { + gfxCriticalNote << "[OPENGL] Failed to init compositor with reason: " + << failureReason.get(); + Telemetry::Accumulate(Telemetry::OPENGL_COMPOSITING_FAILURE_ID, + failureReason); + } +#ifdef XP_WIN + else if (aBackendHints[i] == LayersBackend::LAYERS_D3D11) { + gfxCriticalNote << "[D3D11] Failed to init compositor with reason: " + << failureReason.get(); + Telemetry::Accumulate(Telemetry::D3D11_COMPOSITING_FAILURE_ID, + failureReason); + } +#endif + } + + return nullptr; +} + +PLayerTransactionParent* CompositorBridgeParent::AllocPLayerTransactionParent( + const nsTArray& aBackendHints, const LayersId& aId) { + MOZ_ASSERT(!aId.IsValid()); + +#ifdef XP_WIN + // This is needed to avoid freezing the window on a device crash on double + // buffering, see bug 1549674. + if (gfxVars::UseDoubleBufferingWithCompositor() && XRE_IsGPUProcess() && + aBackendHints.Contains(LayersBackend::LAYERS_D3D11)) { + mWidget->AsWindows()->EnsureCompositorWindow(); + } +#endif + + InitializeLayerManager(aBackendHints); + + if (!mLayerManager) { + NS_WARNING("Failed to initialise Compositor"); + LayerTransactionParent* p = new LayerTransactionParent( + /* aManager */ nullptr, this, /* aAnimStorage */ nullptr, + mRootLayerTreeID, mVsyncRate); + p->AddIPDLReference(); + return p; + } + + mCompositionManager = new AsyncCompositionManager(this, mLayerManager); + + LayerTransactionParent* p = new LayerTransactionParent( + mLayerManager, this, GetAnimationStorage(), mRootLayerTreeID, mVsyncRate); + p->AddIPDLReference(); + return p; +} + +bool CompositorBridgeParent::DeallocPLayerTransactionParent( + PLayerTransactionParent* actor) { + static_cast(actor)->ReleaseIPDLReference(); + return true; +} + +CompositorBridgeParent* CompositorBridgeParent::GetCompositorBridgeParent( + uint64_t id) { + AssertIsInCompositorThread(); + CompositorMap::iterator it = sCompositorMap->find(id); + return it != sCompositorMap->end() ? it->second : nullptr; +} + +void CompositorBridgeParent::AddCompositor(CompositorBridgeParent* compositor, + uint64_t* outID) { + AssertIsInCompositorThread(); + + static uint64_t sNextID = 1; + + ++sNextID; + (*sCompositorMap)[sNextID] = compositor; + *outID = sNextID; +} + +CompositorBridgeParent* CompositorBridgeParent::RemoveCompositor(uint64_t id) { + AssertIsInCompositorThread(); + + CompositorMap::iterator it = sCompositorMap->find(id); + if (it == sCompositorMap->end()) { + return nullptr; + } + CompositorBridgeParent* retval = it->second; + sCompositorMap->erase(it); + return retval; +} + +void CompositorBridgeParent::NotifyVsync(const VsyncEvent& aVsync, + const LayersId& aLayersId) { + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_GPU); + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + + MonitorAutoLock lock(*sIndirectLayerTreesLock); + auto it = sIndirectLayerTrees.find(aLayersId); + if (it == sIndirectLayerTrees.end()) return; + + CompositorBridgeParent* cbp = it->second.mParent; + if (!cbp || !cbp->mWidget) return; + + RefPtr obs = cbp->mWidget->GetVsyncObserver(); + if (!obs) return; + + obs->NotifyVsync(aVsync); +} + +/* static */ +void CompositorBridgeParent::ScheduleForcedComposition( + const LayersId& aLayersId) { + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_GPU); + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + + MonitorAutoLock lock(*sIndirectLayerTreesLock); + auto it = sIndirectLayerTrees.find(aLayersId); + if (it == sIndirectLayerTrees.end()) { + return; + } + + CompositorBridgeParent* cbp = it->second.mParent; + if (!cbp || !cbp->mWidget) { + return; + } + + if (cbp->mWrBridge) { + cbp->mWrBridge->ScheduleForcedGenerateFrame(); + } else if (cbp->CanComposite()) { + cbp->mCompositorScheduler->ScheduleComposition(); + } +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvNotifyChildCreated( + const LayersId& child, CompositorOptions* aOptions) { + MonitorAutoLock lock(*sIndirectLayerTreesLock); + NotifyChildCreated(child); + *aOptions = mOptions; + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvNotifyChildRecreated( + const LayersId& aChild, CompositorOptions* aOptions) { + MonitorAutoLock lock(*sIndirectLayerTreesLock); + + if (sIndirectLayerTrees.find(aChild) != sIndirectLayerTrees.end()) { + NS_WARNING("Invalid to register the same layer tree twice"); + return IPC_FAIL_NO_REASON(this); + } + + NotifyChildCreated(aChild); + *aOptions = mOptions; + return IPC_OK(); +} + +void CompositorBridgeParent::NotifyChildCreated(LayersId aChild) { + sIndirectLayerTreesLock->AssertCurrentThreadOwns(); + sIndirectLayerTrees[aChild].mParent = this; + sIndirectLayerTrees[aChild].mLayerManager = mLayerManager; +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvMapAndNotifyChildCreated( + const LayersId& aChild, const base::ProcessId& aOwnerPid, + CompositorOptions* aOptions) { + // We only use this message when the remote compositor is in the GPU process. + // It is harmless to call it, though. + MOZ_ASSERT(XRE_IsGPUProcess()); + + LayerTreeOwnerTracker::Get()->Map(aChild, aOwnerPid); + + MonitorAutoLock lock(*sIndirectLayerTreesLock); + NotifyChildCreated(aChild); + *aOptions = mOptions; + return IPC_OK(); +} + +enum class CompositorOptionsChangeKind { + eSupported, + eBestEffort, + eUnsupported +}; + +static CompositorOptionsChangeKind ClassifyCompositorOptionsChange( + const CompositorOptions& aOld, const CompositorOptions& aNew) { + if (aOld == aNew) { + return CompositorOptionsChangeKind::eSupported; + } + if (aOld.UseAdvancedLayers() == aNew.UseAdvancedLayers() && + aOld.UseWebRender() == aNew.UseWebRender() && + aOld.InitiallyPaused() == aNew.InitiallyPaused()) { + // Only APZ enablement changed. + return CompositorOptionsChangeKind::eBestEffort; + } + return CompositorOptionsChangeKind::eUnsupported; +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvAdoptChild( + const LayersId& child) { + RefPtr oldApzUpdater; + APZCTreeManagerParent* parent; + bool scheduleComposition = false; + bool apzEnablementChanged = false; + RefPtr cpcp; + RefPtr childWrBridge; + + // Before adopting the child, save the old compositor's root content + // controller. We may need this to clear old layer transforms associated + // with the child. + // This is outside the lock because GetGeckoContentControllerForRoot() + // does its own locking. + RefPtr oldRootController = + GetGeckoContentControllerForRoot(child); + + { // scope lock + MonitorAutoLock lock(*sIndirectLayerTreesLock); + // If child is already belong to this CompositorBridgeParent, + // no need to handle adopting child. + if (sIndirectLayerTrees[child].mParent == this) { + return IPC_OK(); + } + + if (sIndirectLayerTrees[child].mParent) { + switch (ClassifyCompositorOptionsChange( + sIndirectLayerTrees[child].mParent->mOptions, mOptions)) { + case CompositorOptionsChangeKind::eUnsupported: { + MOZ_ASSERT(false, + "Moving tab between windows whose compositor options" + "differ in unsupported ways. Things may break in " + "unexpected ways"); + break; + } + case CompositorOptionsChangeKind::eBestEffort: { + NS_WARNING( + "Moving tab between windows with different APZ enablement. " + "This is supported on a best-effort basis, but some things may " + "break."); + apzEnablementChanged = true; + break; + } + case CompositorOptionsChangeKind::eSupported: { + // The common case, no action required. + break; + } + } + oldApzUpdater = sIndirectLayerTrees[child].mParent->mApzUpdater; + } + NotifyChildCreated(child); + if (sIndirectLayerTrees[child].mLayerTree) { + sIndirectLayerTrees[child].mLayerTree->SetLayerManager( + mLayerManager, GetAnimationStorage()); + // Trigger composition to handle a case that mLayerTree was not composited + // yet by previous CompositorBridgeParent, since nsRefreshDriver might + // wait composition complete. + scheduleComposition = true; + } + if (mWrBridge) { + childWrBridge = sIndirectLayerTrees[child].mWrBridge; + cpcp = sIndirectLayerTrees[child].mContentCompositorBridgeParent; + } + parent = sIndirectLayerTrees[child].mApzcTreeManagerParent; + } + + if (scheduleComposition) { + ScheduleComposition(); + } + + if (childWrBridge) { + MOZ_ASSERT(mWrBridge); + RefPtr api = mWrBridge->GetWebRenderAPI(); + api = api->Clone(); + wr::Epoch newEpoch = childWrBridge->UpdateWebRender( + mWrBridge->CompositorScheduler(), std::move(api), + mWrBridge->AsyncImageManager(), + mWrBridge->GetTextureFactoryIdentifier()); + // Pretend we composited, since parent CompositorBridgeParent was replaced. + TimeStamp now = TimeStamp::Now(); + NotifyPipelineRendered(childWrBridge->PipelineId(), newEpoch, VsyncId(), + now, now, now); + } + + if (oldApzUpdater) { + // If we are moving a child from an APZ-enabled window to an APZ-disabled + // window (which can happen if e.g. a WebExtension moves a tab into a + // popup window), try to handle it gracefully by clearing the old layer + // transforms associated with the child. (Since the new compositor is + // APZ-disabled, there will be nothing to update the transforms going + // forward.) + if (!mApzUpdater && oldRootController) { + // Tell the old APZCTreeManager not to send any more layer transforms + // for this layers ids. + oldApzUpdater->MarkAsDetached(child); + + // Clear the current transforms. + nsTArray clear; + clear.AppendElement(MatrixMessage(Nothing(), ScreenRect(), child)); + oldRootController->NotifyLayerTransforms(std::move(clear)); + } + } + if (mApzUpdater) { + if (parent) { + MOZ_ASSERT(mApzcTreeManager); + parent->ChildAdopted(mApzcTreeManager, mApzUpdater); + } + mApzUpdater->NotifyLayerTreeAdopted(child, oldApzUpdater); + } + if (apzEnablementChanged) { + Unused << SendCompositorOptionsChanged(child, mOptions); + } + return IPC_OK(); +} + +PWebRenderBridgeParent* CompositorBridgeParent::AllocPWebRenderBridgeParent( + const wr::PipelineId& aPipelineId, const LayoutDeviceIntSize& aSize, + const WindowKind& aWindowKind) { + MOZ_ASSERT(wr::AsLayersId(aPipelineId) == mRootLayerTreeID); + MOZ_ASSERT(!mWrBridge); + MOZ_ASSERT(!mCompositor); + MOZ_ASSERT(!mCompositorScheduler); + MOZ_ASSERT(mWidget); + +#ifdef XP_WIN + if (mWidget && (DeviceManagerDx::Get()->CanUseDComp() || + gfxVars::UseWebRenderFlipSequentialWin())) { + mWidget->AsWindows()->EnsureCompositorWindow(); + } +#endif + + RefPtr widget = mWidget; + wr::WrWindowId windowId = wr::NewWindowId(); + if (mApzUpdater) { + // If APZ is enabled, we need to register the APZ updater with the window id + // before the updater thread is created in WebRenderAPI::Create, so + // that the callback from the updater thread can find the right APZUpdater. + mApzUpdater->SetWebRenderWindowId(windowId); + } + if (mApzSampler) { + // Same as for mApzUpdater, but for the sampler thread. + mApzSampler->SetWebRenderWindowId(windowId); + } + if (mOMTASampler) { + // Same, but for the OMTA sampler. + mOMTASampler->SetWebRenderWindowId(windowId); + } + + nsCString error("FEATURE_FAILURE_WEBRENDER_INITIALIZE_UNSPECIFIED"); + RefPtr api = wr::WebRenderAPI::Create( + this, std::move(widget), windowId, aSize, aWindowKind, error); + if (!api) { + mWrBridge = + WebRenderBridgeParent::CreateDestroyed(aPipelineId, std::move(error)); + mWrBridge.get()->AddRef(); // IPDL reference + return mWrBridge; + } + + wr::TransactionBuilder txn; + txn.SetRootPipeline(aPipelineId); + api->SendTransaction(txn); + + bool useCompositorWnd = false; +#ifdef XP_WIN + // Headless mode uses HeadlessWidget. + if (mWidget->AsWindows()) { + useCompositorWnd = !!mWidget->AsWindows()->GetCompositorHwnd(); + } +#endif + mAsyncImageManager = + new AsyncImagePipelineManager(api->Clone(), useCompositorWnd); + RefPtr asyncMgr = mAsyncImageManager; + mWrBridge = new WebRenderBridgeParent(this, aPipelineId, mWidget, nullptr, + std::move(api), std::move(asyncMgr), + mVsyncRate); + mWrBridge.get()->AddRef(); // IPDL reference + + mCompositorScheduler = mWrBridge->CompositorScheduler(); + MOZ_ASSERT(mCompositorScheduler); + { // scope lock + MonitorAutoLock lock(*sIndirectLayerTreesLock); + MOZ_ASSERT(sIndirectLayerTrees[mRootLayerTreeID].mWrBridge == nullptr); + sIndirectLayerTrees[mRootLayerTreeID].mWrBridge = mWrBridge; + } + return mWrBridge; +} + +bool CompositorBridgeParent::DeallocPWebRenderBridgeParent( + PWebRenderBridgeParent* aActor) { + WebRenderBridgeParent* parent = static_cast(aActor); + { + MonitorAutoLock lock(*sIndirectLayerTreesLock); + auto it = sIndirectLayerTrees.find(wr::AsLayersId(parent->PipelineId())); + if (it != sIndirectLayerTrees.end()) { + it->second.mWrBridge = nullptr; + } + } + parent->Release(); // IPDL reference + return true; +} + +webgpu::PWebGPUParent* CompositorBridgeParent::AllocPWebGPUParent() { + MOZ_ASSERT(!mWebGPUBridge); + mWebGPUBridge = new webgpu::WebGPUParent(); + mWebGPUBridge.get()->AddRef(); // IPDL reference + return mWebGPUBridge; +} + +bool CompositorBridgeParent::DeallocPWebGPUParent( + webgpu::PWebGPUParent* aActor) { + webgpu::WebGPUParent* parent = static_cast(aActor); + MOZ_ASSERT(mWebGPUBridge == parent); + parent->Release(); // IPDL reference + mWebGPUBridge = nullptr; + return true; +} + +void CompositorBridgeParent::NotifyMemoryPressure() { + if (mWrBridge) { + RefPtr api = mWrBridge->GetWebRenderAPI(); + if (api) { + api->NotifyMemoryPressure(); + } + } +} + +void CompositorBridgeParent::AccumulateMemoryReport(wr::MemoryReport* aReport) { + if (mWrBridge) { + RefPtr api = mWrBridge->GetWebRenderAPI(); + if (api) { + api->AccumulateMemoryReport(aReport); + } + } +} + +/*static*/ +void CompositorBridgeParent::InitializeStatics() { + gfxVars::SetForceSubpixelAAWherePossibleListener(&UpdateQualitySettings); + gfxVars::SetWebRenderDebugFlagsListener(&UpdateDebugFlags); + gfxVars::SetUseWebRenderMultithreadingListener( + &UpdateWebRenderMultithreading); + gfxVars::SetWebRenderBatchingLookbackListener( + &UpdateWebRenderBatchingParameters); + gfxVars::SetWebRenderProfilerUIListener(&UpdateWebRenderProfilerUI); +} + +/*static*/ +void CompositorBridgeParent::UpdateQualitySettings() { + if (!CompositorThreadHolder::IsInCompositorThread()) { + if (CompositorThread()) { + CompositorThread()->Dispatch( + NewRunnableFunction("CompositorBridgeParent::UpdateQualitySettings", + &CompositorBridgeParent::UpdateQualitySettings)); + } + + // If there is no compositor thread, e.g. due to shutdown, then we can + // safefully just ignore this request. + return; + } + + MonitorAutoLock lock(*sIndirectLayerTreesLock); + ForEachWebRenderBridgeParent([&](WebRenderBridgeParent* wrBridge) -> void { + wrBridge->UpdateQualitySettings(); + }); +} + +/*static*/ +void CompositorBridgeParent::UpdateDebugFlags() { + if (!CompositorThreadHolder::IsInCompositorThread()) { + if (CompositorThread()) { + CompositorThread()->Dispatch( + NewRunnableFunction("CompositorBridgeParent::UpdateDebugFlags", + &CompositorBridgeParent::UpdateDebugFlags)); + } + + // If there is no compositor thread, e.g. due to shutdown, then we can + // safefully just ignore this request. + return; + } + + MonitorAutoLock lock(*sIndirectLayerTreesLock); + ForEachWebRenderBridgeParent([&](WebRenderBridgeParent* wrBridge) -> void { + wrBridge->UpdateDebugFlags(); + }); +} + +/*static*/ +void CompositorBridgeParent::UpdateWebRenderMultithreading() { + if (!CompositorThreadHolder::IsInCompositorThread()) { + if (CompositorThread()) { + CompositorThread()->Dispatch(NewRunnableFunction( + "CompositorBridgeParent::UpdateWebRenderMultithreading", + &CompositorBridgeParent::UpdateWebRenderMultithreading)); + } + + return; + } + + MonitorAutoLock lock(*sIndirectLayerTreesLock); + ForEachWebRenderBridgeParent([&](WebRenderBridgeParent* wrBridge) -> void { + wrBridge->UpdateMultithreading(); + }); +} + +/*static*/ +void CompositorBridgeParent::UpdateWebRenderBatchingParameters() { + if (!CompositorThreadHolder::IsInCompositorThread()) { + if (CompositorThread()) { + CompositorThread()->Dispatch(NewRunnableFunction( + "CompositorBridgeParent::UpdateWebRenderBatchingParameters", + &CompositorBridgeParent::UpdateWebRenderBatchingParameters)); + } + + return; + } + + MonitorAutoLock lock(*sIndirectLayerTreesLock); + ForEachWebRenderBridgeParent([&](WebRenderBridgeParent* wrBridge) -> void { + wrBridge->UpdateBatchingParameters(); + }); +} + +/*static*/ +void CompositorBridgeParent::UpdateWebRenderProfilerUI() { + if (!sIndirectLayerTreesLock) { + return; + } + MonitorAutoLock lock(*sIndirectLayerTreesLock); + ForEachWebRenderBridgeParent([&](WebRenderBridgeParent* wrBridge) -> void { + wrBridge->UpdateProfilerUI(); + }); +} + +RefPtr CompositorBridgeParent::GetWebRenderBridgeParent() + const { + return mWrBridge; +} + +Maybe CompositorBridgeParent::GetTestingTimeStamp() const { + return mTestTime; +} + +void EraseLayerState(LayersId aId) { + RefPtr apz; + + { // scope lock + MonitorAutoLock lock(*sIndirectLayerTreesLock); + auto iter = sIndirectLayerTrees.find(aId); + if (iter != sIndirectLayerTrees.end()) { + CompositorBridgeParent* parent = iter->second.mParent; + if (parent) { + apz = parent->GetAPZUpdater(); + } + sIndirectLayerTrees.erase(iter); + } + } + + if (apz) { + apz->NotifyLayerTreeRemoved(aId); + } +} + +/*static*/ +void CompositorBridgeParent::DeallocateLayerTreeId(LayersId aId) { + MOZ_ASSERT(NS_IsMainThread()); + // Here main thread notifies compositor to remove an element from + // sIndirectLayerTrees. This removed element might be queried soon. + // Checking the elements of sIndirectLayerTrees exist or not before using. + if (!CompositorThread()) { + gfxCriticalError() << "Attempting to post to an invalid Compositor Thread"; + return; + } + CompositorThread()->Dispatch( + NewRunnableFunction("EraseLayerStateRunnable", &EraseLayerState, aId)); +} + +static void UpdateControllerForLayersId(LayersId aLayersId, + GeckoContentController* aController) { + // Adopt ref given to us by SetControllerForLayerTree() + MonitorAutoLock lock(*sIndirectLayerTreesLock); + sIndirectLayerTrees[aLayersId].mController = + already_AddRefed(aController); +} + +ScopedLayerTreeRegistration::ScopedLayerTreeRegistration( + APZCTreeManager* aApzctm, LayersId aLayersId, Layer* aRoot, + GeckoContentController* aController) + : mLayersId(aLayersId) { + EnsureLayerTreeMapReady(); + MonitorAutoLock lock(*sIndirectLayerTreesLock); + sIndirectLayerTrees[aLayersId].mRoot = aRoot; + sIndirectLayerTrees[aLayersId].mController = aController; +} + +ScopedLayerTreeRegistration::~ScopedLayerTreeRegistration() { + MonitorAutoLock lock(*sIndirectLayerTreesLock); + sIndirectLayerTrees.erase(mLayersId); +} + +/*static*/ +void CompositorBridgeParent::SetControllerForLayerTree( + LayersId aLayersId, GeckoContentController* aController) { + // This ref is adopted by UpdateControllerForLayersId(). + aController->AddRef(); + CompositorThread()->Dispatch(NewRunnableFunction( + "UpdateControllerForLayersIdRunnable", &UpdateControllerForLayersId, + aLayersId, aController)); +} + +/*static*/ +already_AddRefed CompositorBridgeParent::GetAPZCTreeManager( + LayersId aLayersId) { + EnsureLayerTreeMapReady(); + MonitorAutoLock lock(*sIndirectLayerTreesLock); + LayerTreeMap::iterator cit = sIndirectLayerTrees.find(aLayersId); + if (sIndirectLayerTrees.end() == cit) { + return nullptr; + } + LayerTreeState* lts = &cit->second; + + RefPtr apzctm = + lts->mParent ? lts->mParent->mApzcTreeManager.get() : nullptr; + return apzctm.forget(); +} + +#if defined(MOZ_GECKO_PROFILER) +static void InsertVsyncProfilerMarker(TimeStamp aVsyncTimestamp) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + if (profiler_thread_is_being_profiled()) { + // Tracks when a vsync occurs according to the HardwareComposer. + struct VsyncMarker { + static constexpr mozilla::Span MarkerTypeName() { + return mozilla::MakeStringSpan("VsyncTimestamp"); + } + static void StreamJSONMarkerData( + baseprofiler::SpliceableJSONWriter& aWriter) {} + static MarkerSchema MarkerTypeDisplay() { + using MS = MarkerSchema; + MS schema{MS::Location::markerChart, MS::Location::markerTable}; + // Nothing outside the defaults. + return schema; + } + }; + profiler_add_marker("VsyncTimestamp", geckoprofiler::category::GRAPHICS, + MarkerTiming::InstantAt(aVsyncTimestamp), + VsyncMarker{}); + } +} +#endif + +/*static */ +void CompositorBridgeParent::PostInsertVsyncProfilerMarker( + TimeStamp aVsyncTimestamp) { +#if defined(MOZ_GECKO_PROFILER) + // Called in the vsync thread + if (profiler_is_active() && CompositorThreadHolder::IsActive()) { + CompositorThread()->Dispatch( + NewRunnableFunction("InsertVsyncProfilerMarkerRunnable", + InsertVsyncProfilerMarker, aVsyncTimestamp)); + } +#endif +} + +widget::PCompositorWidgetParent* +CompositorBridgeParent::AllocPCompositorWidgetParent( + const CompositorWidgetInitData& aInitData) { +#if defined(MOZ_WIDGET_SUPPORTS_OOP_COMPOSITING) + if (mWidget) { + // Should not create two widgets on the same compositor. + return nullptr; + } + + widget::CompositorWidgetParent* widget = + new widget::CompositorWidgetParent(aInitData, mOptions); + widget->AddRef(); + + // Sending the constructor acts as initialization as well. + mWidget = widget; + return widget; +#else + return nullptr; +#endif +} + +bool CompositorBridgeParent::DeallocPCompositorWidgetParent( + PCompositorWidgetParent* aActor) { +#if defined(MOZ_WIDGET_SUPPORTS_OOP_COMPOSITING) + static_cast(aActor)->Release(); + return true; +#else + return false; +#endif +} + +bool CompositorBridgeParent::IsPendingComposite() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + if (!mCompositor) { + return false; + } + return mCompositor->IsPendingComposite(); +} + +void CompositorBridgeParent::FinishPendingComposite() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + if (!mCompositor) { + return; + } + return mCompositor->FinishPendingComposite(); +} + +CompositorController* +CompositorBridgeParent::LayerTreeState::GetCompositorController() const { + return mParent; +} + +MetricsSharingController* +CompositorBridgeParent::LayerTreeState::CrossProcessSharingController() const { + return mContentCompositorBridgeParent; +} + +MetricsSharingController* +CompositorBridgeParent::LayerTreeState::InProcessSharingController() const { + return mParent; +} + +void CompositorBridgeParent::DidComposite(const VsyncId& aId, + TimeStamp& aCompositeStart, + TimeStamp& aCompositeEnd) { + if (mWrBridge) { + MOZ_ASSERT(false); // This should never get called for a WR compositor + } else { + NotifyDidComposite(mPendingTransaction, aId, aCompositeStart, + aCompositeEnd); +#if defined(ENABLE_FRAME_LATENCY_LOG) + if (mPendingTransaction.IsValid()) { + if (mRefreshStartTime) { + int32_t latencyMs = + lround((aCompositeEnd - mRefreshStartTime).ToMilliseconds()); + printf_stderr( + "From transaction start to end of generate frame latencyMs %d this " + "%p\n", + latencyMs, this); + } + if (mFwdTime) { + int32_t latencyMs = lround((aCompositeEnd - mFwdTime).ToMilliseconds()); + printf_stderr( + "From forwarding transaction to end of generate frame latencyMs %d " + "this %p\n", + latencyMs, this); + } + } + mRefreshStartTime = TimeStamp(); + mTxnStartTime = TimeStamp(); + mFwdTime = TimeStamp(); +#endif + mPendingTransaction = TransactionId{0}; + } +} + +void CompositorBridgeParent::NotifyDidSceneBuild( + RefPtr aInfo) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + if (mPaused) { + return; + } + + if (mWrBridge) { + mWrBridge->NotifyDidSceneBuild(aInfo); + } else { + mCompositorScheduler->ScheduleComposition(); + } +} + +void CompositorBridgeParent::NotifyDidRender(const VsyncId& aCompositeStartId, + TimeStamp& aCompositeStart, + TimeStamp& aRenderStart, + TimeStamp& aCompositeEnd, + wr::RendererStats* aStats) { + if (!mWrBridge) { + return; + } + + MOZ_RELEASE_ASSERT(mWrBridge->IsRootWebRenderBridgeParent()); + + RefPtr uiController = + UiCompositorControllerParent::GetFromRootLayerTreeId(mRootLayerTreeID); + + if (uiController && mIsForcedFirstPaint) { + uiController->NotifyFirstPaint(); + mIsForcedFirstPaint = false; + } + + nsTArray payload = + mWrBridge->TakePendingScrollPayload(aCompositeStartId); + if (!payload.IsEmpty()) { + RecordCompositionPayloadsPresented(aCompositeEnd, payload); + } + + nsTArray notifications; + mWrBridge->ExtractImageCompositeNotifications(¬ifications); + if (!notifications.IsEmpty()) { + Unused << ImageBridgeParent::NotifyImageComposites(notifications); + } +} + +void CompositorBridgeParent::NotifyPipelineRendered( + const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch, + const VsyncId& aCompositeStartId, TimeStamp& aCompositeStart, + TimeStamp& aRenderStart, TimeStamp& aCompositeEnd, + wr::RendererStats* aStats) { + if (!mWrBridge || !mAsyncImageManager) { + return; + } + + bool isRoot = mWrBridge->PipelineId() == aPipelineId; + RefPtr wrBridge = + isRoot ? mWrBridge + : RefPtr( + mAsyncImageManager->GetWrBridge(aPipelineId)); + if (!wrBridge) { + return; + } + + CompositorBridgeParentBase* compBridge = + isRoot ? this : wrBridge->GetCompositorBridge(); + if (!compBridge) { + return; + } + + MOZ_RELEASE_ASSERT(isRoot == wrBridge->IsRootWebRenderBridgeParent()); + + wrBridge->RemoveEpochDataPriorTo(aEpoch); + + nsTArray stats; + + RefPtr uiController = + UiCompositorControllerParent::GetFromRootLayerTreeId(mRootLayerTreeID); + + TransactionId transactionId = wrBridge->FlushTransactionIdsForEpoch( + aEpoch, aCompositeStartId, aCompositeStart, aRenderStart, aCompositeEnd, + uiController, aStats, &stats); + + LayersId layersId = isRoot ? LayersId{0} : wrBridge->GetLayersId(); + Unused << compBridge->SendDidComposite(layersId, transactionId, + aCompositeStart, aCompositeEnd); + + if (!stats.IsEmpty()) { + Unused << SendNotifyFrameStats(stats); + } +} + +RefPtr +CompositorBridgeParent::GetAsyncImagePipelineManager() const { + return mAsyncImageManager; +} + +void CompositorBridgeParent::NotifyDidComposite(TransactionId aTransactionId, + VsyncId aId, + TimeStamp& aCompositeStart, + TimeStamp& aCompositeEnd) { + MOZ_ASSERT(!mWrBridge, + "We should be going through NotifyDidRender and " + "NotifyPipelineRendered instead"); + + Unused << SendDidComposite(LayersId{0}, aTransactionId, aCompositeStart, + aCompositeEnd); + + if (mLayerManager) { + nsTArray notifications; + mLayerManager->ExtractImageCompositeNotifications(¬ifications); + if (!notifications.IsEmpty()) { + Unused << ImageBridgeParent::NotifyImageComposites(notifications); + } + } + + MonitorAutoLock lock(*sIndirectLayerTreesLock); + ForEachIndirectLayerTree([&](LayerTreeState* lts, + const LayersId& aLayersId) -> void { + if (lts->mContentCompositorBridgeParent && lts->mParent == this) { + ContentCompositorBridgeParent* cpcp = lts->mContentCompositorBridgeParent; + cpcp->DidCompositeLocked(aLayersId, aId, aCompositeStart, aCompositeEnd); + } + }); +} + +void CompositorBridgeParent::InvalidateRemoteLayers() { + MOZ_ASSERT(CompositorThread()->IsOnCurrentThread()); + + Unused << PCompositorBridgeParent::SendInvalidateLayers(LayersId{0}); + + MonitorAutoLock lock(*sIndirectLayerTreesLock); + ForEachIndirectLayerTree([](LayerTreeState* lts, + const LayersId& aLayersId) -> void { + if (lts->mContentCompositorBridgeParent) { + ContentCompositorBridgeParent* cpcp = lts->mContentCompositorBridgeParent; + Unused << cpcp->SendInvalidateLayers(aLayersId); + } + }); +} + +void UpdateIndirectTree(LayersId aId, Layer* aRoot, + const TargetConfig& aTargetConfig) { + MonitorAutoLock lock(*sIndirectLayerTreesLock); + sIndirectLayerTrees[aId].mRoot = aRoot; + sIndirectLayerTrees[aId].mTargetConfig = aTargetConfig; +} + +/* static */ CompositorBridgeParent::LayerTreeState* +CompositorBridgeParent::GetIndirectShadowTree(LayersId aId) { + // Only the compositor thread should use this method variant + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + + MonitorAutoLock lock(*sIndirectLayerTreesLock); + LayerTreeMap::iterator cit = sIndirectLayerTrees.find(aId); + if (sIndirectLayerTrees.end() == cit) { + return nullptr; + } + return &cit->second; +} + +/* static */ +bool CompositorBridgeParent::CallWithIndirectShadowTree( + LayersId aId, + const std::function& aFunc) { + if (!sIndirectLayerTreesLock) { + // Can hapen during shutdown + return false; + } + // Note that this does not make things universally threadsafe just because the + // sIndirectLayerTreesLock mutex is held. This is because the compositor + // thread can mutate the LayerTreeState outside the lock. It does however + // ensure that the *storage* for the LayerTreeState remains stable, since we + // should always hold the lock when adding/removing entries to the map. + MonitorAutoLock lock(*sIndirectLayerTreesLock); + LayerTreeMap::iterator cit = sIndirectLayerTrees.find(aId); + if (sIndirectLayerTrees.end() == cit) { + return false; + } + aFunc(cit->second); + return true; +} + +static CompositorBridgeParent::LayerTreeState* GetStateForRoot( + LayersId aContentLayersId, const MonitorAutoLock& aProofOfLock) { + CompositorBridgeParent::LayerTreeState* state = nullptr; + LayerTreeMap::iterator itr = sIndirectLayerTrees.find(aContentLayersId); + if (sIndirectLayerTrees.end() != itr) { + state = &itr->second; + } + + // |state| is the state for the content process, but we want the APZCTMParent + // for the parent process owning that content process. So we have to jump to + // the LayerTreeState for the root layer tree id for that layer tree, and use + // the mApzcTreeManagerParent from that. This should also work with nested + // content processes, because RootLayerTreeId() will bypass any intermediate + // processes' ids and go straight to the root. + if (state && state->mParent) { + LayersId rootLayersId = state->mParent->RootLayerTreeId(); + itr = sIndirectLayerTrees.find(rootLayersId); + state = (sIndirectLayerTrees.end() != itr) ? &itr->second : nullptr; + } + + return state; +} + +/* static */ +APZCTreeManagerParent* CompositorBridgeParent::GetApzcTreeManagerParentForRoot( + LayersId aContentLayersId) { + MonitorAutoLock lock(*sIndirectLayerTreesLock); + CompositorBridgeParent::LayerTreeState* state = + GetStateForRoot(aContentLayersId, lock); + return state ? state->mApzcTreeManagerParent : nullptr; +} + +/* static */ +GeckoContentController* +CompositorBridgeParent::GetGeckoContentControllerForRoot( + LayersId aContentLayersId) { + MonitorAutoLock lock(*sIndirectLayerTreesLock); + CompositorBridgeParent::LayerTreeState* state = + GetStateForRoot(aContentLayersId, lock); + return state ? state->mController.get() : nullptr; +} + +PTextureParent* CompositorBridgeParent::AllocPTextureParent( + const SurfaceDescriptor& aSharedData, const ReadLockDescriptor& aReadLock, + const LayersBackend& aLayersBackend, const TextureFlags& aFlags, + const LayersId& aId, const uint64_t& aSerial, + const wr::MaybeExternalImageId& aExternalImageId) { + return TextureHost::CreateIPDLActor(this, aSharedData, aReadLock, + aLayersBackend, aFlags, aSerial, + aExternalImageId); +} + +bool CompositorBridgeParent::DeallocPTextureParent(PTextureParent* actor) { + return TextureHost::DestroyIPDLActor(actor); +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvInitPCanvasParent( + Endpoint&& aEndpoint) { + MOZ_CRASH("PCanvasParent shouldn't be created via CompositorBridgeParent."); +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvReleasePCanvasParent() { + MOZ_CRASH("PCanvasParent shouldn't be released via CompositorBridgeParent."); +} + +bool CompositorBridgeParent::IsSameProcess() const { + return OtherPid() == base::GetCurrentProcId(); +} + +void CompositorBridgeParent::NotifyWebRenderDisableNativeCompositor() { + MOZ_ASSERT(CompositorThread()->IsOnCurrentThread()); + if (mWrBridge) { + mWrBridge->DisableNativeCompositor(); + } +} + +#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) +//#define PLUGINS_LOG(...) printf_stderr("CP [%s]: ", __FUNCTION__); +// printf_stderr(__VA_ARGS__); +// printf_stderr("\n"); +# define PLUGINS_LOG(...) + +bool CompositorBridgeParent::UpdatePluginWindowState(LayersId aId) { + MonitorAutoLock lock(*sIndirectLayerTreesLock); + CompositorBridgeParent::LayerTreeState& lts = sIndirectLayerTrees[aId]; + if (!lts.mParent) { + PLUGINS_LOG("[%" PRIu64 "] layer tree compositor parent pointer is null", + aId); + return false; + } + + // Check if this layer tree has received any shadow layer updates + if (!lts.mUpdatedPluginDataAvailable) { + PLUGINS_LOG("[%" PRIu64 "] no plugin data", aId); + return false; + } + + // pluginMetricsChanged tracks whether we need to send plugin update + // data to the main thread. If we do we'll have to block composition, + // which we want to avoid if at all possible. + bool pluginMetricsChanged = false; + + // Same layer tree checks + if (mLastPluginUpdateLayerTreeId == aId) { + // no plugin data and nothing has changed, bail. + if (!mCachedPluginData.Length() && !lts.mPluginData.Length()) { + PLUGINS_LOG("[%" PRIu64 "] no data, no changes", aId); + return false; + } + + if (mCachedPluginData.Length() == lts.mPluginData.Length()) { + // check for plugin data changes + for (uint32_t idx = 0; idx < lts.mPluginData.Length(); idx++) { + if (!(mCachedPluginData[idx] == lts.mPluginData[idx])) { + pluginMetricsChanged = true; + break; + } + } + } else { + // array lengths don't match, need to update + pluginMetricsChanged = true; + } + } else { + // exchanging layer trees, we need to update + pluginMetricsChanged = true; + } + + // Check if plugin windows are currently hidden due to scrolling + if (mDeferPluginWindows) { + PLUGINS_LOG("[%" PRIu64 "] suppressing", aId); + return false; + } + + // If the plugin windows were hidden but now are not, we need to force + // update the metrics to make sure they are visible again. + if (mPluginWindowsHidden) { + PLUGINS_LOG("[%" PRIu64 "] re-showing", aId); + mPluginWindowsHidden = false; + pluginMetricsChanged = true; + } + + if (!lts.mPluginData.Length()) { + // Don't hide plugins if the previous remote layer tree didn't contain any. + if (!mCachedPluginData.Length()) { + PLUGINS_LOG("[%" PRIu64 "] nothing to hide", aId); + return false; + } + + uintptr_t parentWidget = GetWidget()->GetWidgetKey(); + + // We will pass through here in cases where the previous shadow layer + // tree contained visible plugins and the new tree does not. All we need + // to do here is hide the plugins for the old tree, so don't waste time + // calculating clipping. + mPluginsLayerOffset = nsIntPoint(0, 0); + mPluginsLayerVisibleRegion.SetEmpty(); + Unused << lts.mParent->SendHideAllPlugins(parentWidget); + lts.mUpdatedPluginDataAvailable = false; + PLUGINS_LOG("[%" PRIu64 "] hide all", aId); + } else { + // Retrieve the offset and visible region of the layer that hosts + // the plugins, CompositorBridgeChild needs these in calculating proper + // plugin clipping. + LayerTransactionParent* layerTree = lts.mLayerTree; + Layer* contentRoot = layerTree->GetRoot(); + if (contentRoot) { + nsIntPoint offset; + nsIntRegion visibleRegion; + if (contentRoot->GetVisibleRegionRelativeToRootLayer(visibleRegion, + &offset)) { + // Check to see if these values have changed, if so we need to + // update plugin window position within the window. + if (!pluginMetricsChanged && + mPluginsLayerVisibleRegion == visibleRegion && + mPluginsLayerOffset == offset) { + PLUGINS_LOG("[%" PRIu64 "] no change", aId); + return false; + } + mPluginsLayerOffset = offset; + mPluginsLayerVisibleRegion = visibleRegion; + Unused << lts.mParent->SendUpdatePluginConfigurations( + LayoutDeviceIntPoint::FromUnknownPoint(offset), + LayoutDeviceIntRegion::FromUnknownRegion(visibleRegion), + lts.mPluginData); + lts.mUpdatedPluginDataAvailable = false; + PLUGINS_LOG("[%" PRIu64 "] updated", aId); + } else { + PLUGINS_LOG("[%" PRIu64 "] no visibility data", aId); + return false; + } + } else { + PLUGINS_LOG("[%" PRIu64 "] no content root", aId); + return false; + } + } + + mLastPluginUpdateLayerTreeId = aId; + mCachedPluginData = lts.mPluginData.Clone(); + return true; +} + +void CompositorBridgeParent::ScheduleShowAllPluginWindows() { + MOZ_ASSERT(CompositorThread()); + CompositorThread()->Dispatch( + NewRunnableMethod("layers::CompositorBridgeParent::ShowAllPluginWindows", + this, &CompositorBridgeParent::ShowAllPluginWindows)); +} + +void CompositorBridgeParent::ShowAllPluginWindows() { + MOZ_ASSERT(!NS_IsMainThread()); + mDeferPluginWindows = false; + ScheduleComposition(); +} + +void CompositorBridgeParent::ScheduleHideAllPluginWindows() { + MOZ_ASSERT(CompositorThread()); + CompositorThread()->Dispatch( + NewRunnableMethod("layers::CompositorBridgeParent::HideAllPluginWindows", + this, &CompositorBridgeParent::HideAllPluginWindows)); +} + +void CompositorBridgeParent::HideAllPluginWindows() { + MOZ_ASSERT(!NS_IsMainThread()); + // No plugins in the cache implies no plugins to manage + // in this content. + if (!mCachedPluginData.Length() || mDeferPluginWindows) { + return; + } + + uintptr_t parentWidget = GetWidget()->GetWidgetKey(); + + mDeferPluginWindows = true; + mPluginWindowsHidden = true; + +# if defined(XP_WIN) + // We will get an async reply that this has happened and then send hide. + mWaitForPluginsUntil = TimeStamp::Now() + mVsyncRate; + Unused << SendCaptureAllPlugins(parentWidget); +# else + Unused << SendHideAllPlugins(parentWidget); + ScheduleComposition(); +# endif +} +#endif // #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvAllPluginsCaptured() { +#if defined(XP_WIN) + mWaitForPluginsUntil = TimeStamp(); + mHaveBlockedForPlugins = false; + ForceComposeToTarget(nullptr); + Unused << SendHideAllPlugins(GetWidget()->GetWidgetKey()); + return IPC_OK(); +#else + MOZ_ASSERT_UNREACHABLE( + "CompositorBridgeParent::RecvAllPluginsCaptured calls unexpected."); + return IPC_FAIL_NO_REASON(this); +#endif +} + +int32_t RecordContentFrameTime( + const VsyncId& aTxnId, const TimeStamp& aVsyncStart, + const TimeStamp& aTxnStart, const VsyncId& aCompositeId, + const TimeStamp& aCompositeEnd, const TimeDuration& aFullPaintTime, + const TimeDuration& aVsyncRate, bool aContainsSVGGroup, + bool aRecordUploadStats, wr::RendererStats* aStats /* = nullptr */) { + double latencyMs = (aCompositeEnd - aTxnStart).ToMilliseconds(); + double latencyNorm = latencyMs / aVsyncRate.ToMilliseconds(); + int32_t fracLatencyNorm = lround(latencyNorm * 100.0); + +#ifdef MOZ_GECKO_PROFILER + if (profiler_can_accept_markers()) { + struct ContentFrameMarker { + static constexpr Span MarkerTypeName() { + return MakeStringSpan("CONTENT_FRAME_TIME"); + } + static void StreamJSONMarkerData( + baseprofiler::SpliceableJSONWriter& aWriter) {} + static MarkerSchema MarkerTypeDisplay() { + using MS = MarkerSchema; + MS schema{MS::Location::markerChart, MS::Location::markerTable}; + // Nothing outside the defaults. + return schema; + } + }; + + profiler_add_marker("CONTENT_FRAME_TIME", geckoprofiler::category::GRAPHICS, + MarkerTiming::Interval(aTxnStart, aCompositeEnd), + ContentFrameMarker{}); + } +#endif + + Telemetry::Accumulate(Telemetry::CONTENT_FRAME_TIME, fracLatencyNorm); + + if (!(aTxnId == VsyncId()) && aVsyncStart) { + latencyMs = (aCompositeEnd - aVsyncStart).ToMilliseconds(); + latencyNorm = latencyMs / aVsyncRate.ToMilliseconds(); + fracLatencyNorm = lround(latencyNorm * 100.0); + int32_t result = fracLatencyNorm; + Telemetry::Accumulate(Telemetry::CONTENT_FRAME_TIME_VSYNC, fracLatencyNorm); + + if (aContainsSVGGroup) { + Telemetry::Accumulate(Telemetry::CONTENT_FRAME_TIME_WITH_SVG, + fracLatencyNorm); + } + + // Record CONTENT_FRAME_TIME_REASON. + // + // Note that deseralizing a layers update (RecvUpdate) can delay the receipt + // of the composite vsync message + // (CompositorBridgeParent::CompositeToTarget), since they're using the same + // thread. This can mean that compositing might start significantly late, + // but this code will still detect it as having successfully started on the + // right vsync (which is somewhat correct). We'd now have reduced time left + // in the vsync interval to finish compositing, so the chances of a missed + // frame increases. This is effectively including the RecvUpdate work as + // part of the 'compositing' phase for this metric, but it isn't included in + // COMPOSITE_TIME, and *is* included in CONTENT_FULL_PAINT_TIME. + // + // Also of note is that when the root WebRenderBridgeParent decides to + // skip a composite (due to the Renderer being busy), that won't notify + // child WebRenderBridgeParents. That failure will show up as the + // composite starting late (since it did), but it's really a fault of a + // slow composite on the previous frame, not a slow + // CONTENT_FULL_PAINT_TIME. It would be nice to have a separate bucket for + // this category (scene was ready on the next vsync, but we chose not to + // composite), but I can't find a way to locate the right child + // WebRenderBridgeParents from the root. WebRender notifies us of the + // child pipelines contained within a render, after it finishes, but I + // can't see how to query what child pipeline would have been rendered, + // when we choose to not do it. + if (fracLatencyNorm < 200) { + // Success + Telemetry::AccumulateCategorical( + LABELS_CONTENT_FRAME_TIME_REASON::OnTime); + } else { + if (aCompositeId == VsyncId()) { + // aCompositeId is 0, possibly something got trigged from + // outside vsync? + Telemetry::AccumulateCategorical( + LABELS_CONTENT_FRAME_TIME_REASON::NoVsyncNoId); + } else if (aTxnId >= aCompositeId) { + // Vsync ids are nonsensical, maybe we're trying to catch up? + Telemetry::AccumulateCategorical( + LABELS_CONTENT_FRAME_TIME_REASON::NoVsync); + } else if (aCompositeId - aTxnId > 1) { + // Composite started late (and maybe took too long as well) + if (aFullPaintTime >= TimeDuration::FromMilliseconds(20)) { + Telemetry::AccumulateCategorical( + LABELS_CONTENT_FRAME_TIME_REASON::MissedCompositeLong); + } else if (aFullPaintTime >= TimeDuration::FromMilliseconds(10)) { + Telemetry::AccumulateCategorical( + LABELS_CONTENT_FRAME_TIME_REASON::MissedCompositeMid); + } else if (aFullPaintTime >= TimeDuration::FromMilliseconds(5)) { + Telemetry::AccumulateCategorical( + LABELS_CONTENT_FRAME_TIME_REASON::MissedCompositeLow); + } else { + Telemetry::AccumulateCategorical( + LABELS_CONTENT_FRAME_TIME_REASON::MissedComposite); + } + } else { + // Composite started on time, but must have taken too long. + Telemetry::AccumulateCategorical( + LABELS_CONTENT_FRAME_TIME_REASON::SlowComposite); + } + } + + if (aRecordUploadStats) { + if (aStats) { + latencyMs -= (double(aStats->resource_upload_time) / 1000000.0); + latencyNorm = latencyMs / aVsyncRate.ToMilliseconds(); + fracLatencyNorm = lround(latencyNorm * 100.0); + } + Telemetry::Accumulate( + Telemetry::CONTENT_FRAME_TIME_WITHOUT_RESOURCE_UPLOAD, + fracLatencyNorm); + + if (aStats) { + latencyMs -= (double(aStats->gpu_cache_upload_time) / 1000000.0); + latencyNorm = latencyMs / aVsyncRate.ToMilliseconds(); + fracLatencyNorm = lround(latencyNorm * 100.0); + } + Telemetry::Accumulate(Telemetry::CONTENT_FRAME_TIME_WITHOUT_UPLOAD, + fracLatencyNorm); + } + return result; + } + + return 0; +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvBeginRecording( + const TimeStamp& aRecordingStart, BeginRecordingResolver&& aResolve) { + if (mHaveCompositionRecorder) { + aResolve(false); + return IPC_OK(); + } + + if (mLayerManager) { + mLayerManager->SetCompositionRecorder( + MakeUnique(aRecordingStart)); + } else if (mWrBridge) { + mWrBridge->BeginRecording(aRecordingStart); + } + + mHaveCompositionRecorder = true; + aResolve(true); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvEndRecordingToDisk( + EndRecordingToDiskResolver&& aResolve) { + if (!mHaveCompositionRecorder) { + aResolve(false); + return IPC_OK(); + } + + if (mLayerManager) { + mLayerManager->WriteCollectedFrames(); + aResolve(true); + } else if (mWrBridge) { + mWrBridge->WriteCollectedFrames()->Then( + NS_GetCurrentThread(), __func__, + [resolve{aResolve}](const bool success) { resolve(success); }, + [resolve{aResolve}]() { resolve(false); }); + } else { + aResolve(false); + } + + mHaveCompositionRecorder = false; + + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorBridgeParent::RecvEndRecordingToMemory( + EndRecordingToMemoryResolver&& aResolve) { + if (!mHaveCompositionRecorder) { + aResolve(Nothing()); + return IPC_OK(); + } + + if (mLayerManager) { + Maybe frames = mLayerManager->GetCollectedFrames(); + if (frames) { + aResolve(WrapCollectedFrames(std::move(*frames))); + } else { + aResolve(Nothing()); + } + } else if (mWrBridge) { + RefPtr self = this; + mWrBridge->GetCollectedFrames()->Then( + NS_GetCurrentThread(), __func__, + [self, resolve{aResolve}](CollectedFrames&& frames) { + resolve(self->WrapCollectedFrames(std::move(frames))); + }, + [resolve{aResolve}]() { resolve(Nothing()); }); + } + + mHaveCompositionRecorder = false; + + return IPC_OK(); +} + +Maybe CompositorBridgeParent::WrapCollectedFrames( + CollectedFrames&& aFrames) { + CollectedFramesParams ipcFrames; + ipcFrames.recordingStart() = aFrames.mRecordingStart; + + size_t totalLength = 0; + for (const CollectedFrame& frame : aFrames.mFrames) { + totalLength += frame.mDataUri.Length(); + } + + Shmem shmem; + if (!AllocShmem(totalLength, SharedMemory::TYPE_BASIC, &shmem)) { + return Nothing(); + } + + { + char* raw = shmem.get(); + for (CollectedFrame& frame : aFrames.mFrames) { + size_t length = frame.mDataUri.Length(); + + PodCopy(raw, frame.mDataUri.get(), length); + raw += length; + + ipcFrames.frames().EmplaceBack(frame.mTimeOffset, length); + } + } + ipcFrames.buffer() = std::move(shmem); + + return Some(std::move(ipcFrames)); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/CompositorBridgeParent.h b/gfx/layers/ipc/CompositorBridgeParent.h new file mode 100644 index 0000000000..9d518749a2 --- /dev/null +++ b/gfx/layers/ipc/CompositorBridgeParent.h @@ -0,0 +1,904 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_CompositorBridgeParent_h +#define mozilla_layers_CompositorBridgeParent_h + +// Enable this pref to turn on compositor performance warning. +// This will print warnings if the compositor isn't meeting +// its responsiveness objectives: +// 1) Compose a frame within 15ms of receiving a ScheduleCompositeCall +// 2) Unless a frame was composited within the throttle threshold in +// which the deadline will be 15ms + throttle threshold +//#define COMPOSITOR_PERFORMANCE_WARNING + +#include // for uint64_t +#include +#include "mozilla/Assertions.h" // for MOZ_ASSERT_HELPER2 +#include "mozilla/Maybe.h" +#include "mozilla/Monitor.h" // for Monitor +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/TimeStamp.h" // for TimeStamp +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/ipc/SharedMemory.h" +#include "mozilla/layers/CompositorController.h" +#include "mozilla/layers/CompositorVsyncSchedulerOwner.h" +#include "mozilla/layers/ISurfaceAllocator.h" // for IShmemAllocator +#include "mozilla/layers/LayersTypes.h" +#include "mozilla/layers/MetricsSharingController.h" +#include "mozilla/layers/PCompositorBridgeParent.h" +#include "mozilla/webrender/WebRenderTypes.h" + +struct DxgiAdapterDesc; + +namespace mozilla { + +class CancelableRunnable; + +namespace dom { +class WebGLParent; +} // namespace dom + +namespace gfx { +class DrawTarget; +class GPUProcessManager; +class GPUParent; +} // namespace gfx + +namespace ipc { +class Shmem; +#ifdef FUZZING +class ProtocolFuzzerHelper; +#endif +} // namespace ipc + +namespace webgpu { +class PWebGPUParent; +class WebGPUParent; +} // namespace webgpu + +namespace widget { +class CompositorWidget; +} + +namespace wr { +class WebRenderPipelineInfo; +struct Epoch; +struct MemoryReport; +struct PipelineId; +struct RendererStats; +} // namespace wr + +namespace layers { + +class APZCTreeManager; +class APZCTreeManagerParent; +class APZSampler; +class APZTestData; +class APZUpdater; +class AsyncCompositionManager; +class AsyncImagePipelineManager; +class Compositor; +class CompositorAnimationStorage; +class CompositorBridgeParent; +class CompositorManagerParent; +class CompositorVsyncScheduler; +class FrameUniformityData; +class GeckoContentController; +class HostLayerManager; +class IAPZCTreeManager; +class Layer; +class LayerTransactionParent; +class OMTASampler; +class ContentCompositorBridgeParent; +class CompositorThreadHolder; +class InProcessCompositorSession; +class UiCompositorControllerParent; +class WebRenderBridgeParent; +struct CollectedFrames; + +struct ScopedLayerTreeRegistration { + ScopedLayerTreeRegistration(APZCTreeManager* aApzctm, LayersId aLayersId, + Layer* aRoot, + GeckoContentController* aController); + ~ScopedLayerTreeRegistration(); + + private: + LayersId mLayersId; +}; + +class CompositorBridgeParentBase : public PCompositorBridgeParent, + public HostIPCAllocator, + public mozilla::ipc::IShmemAllocator, + public MetricsSharingController { + friend class PCompositorBridgeParent; + + public: + explicit CompositorBridgeParentBase(CompositorManagerParent* aManager); + + virtual void ShadowLayersUpdated(LayerTransactionParent* aLayerTree, + const TransactionInfo& aInfo, + bool aHitTestUpdate) = 0; + + virtual AsyncCompositionManager* GetCompositionManager( + LayerTransactionParent* aLayerTree) { + return nullptr; + } + + virtual void NotifyClearCachedResources(LayerTransactionParent* aLayerTree) {} + + virtual void ScheduleComposite(LayerTransactionParent* aLayerTree) {} + virtual bool SetTestSampleTime(const LayersId& aId, const TimeStamp& aTime) { + return true; + } + virtual void LeaveTestMode(const LayersId& aId) {} + enum class TransformsToSkip : uint8_t { NoneOfThem = 0, APZ = 1 }; + virtual void ApplyAsyncProperties(LayerTransactionParent* aLayerTree, + TransformsToSkip aSkip) = 0; + virtual void SetTestAsyncScrollOffset( + const LayersId& aLayersId, const ScrollableLayerGuid::ViewID& aScrollId, + const CSSPoint& aPoint) = 0; + virtual void SetTestAsyncZoom(const LayersId& aLayersId, + const ScrollableLayerGuid::ViewID& aScrollId, + const LayerToParentLayerScale& aZoom) = 0; + virtual void FlushApzRepaints(const LayersId& aLayersId) = 0; + virtual void GetAPZTestData(const LayersId& aLayersId, + APZTestData* aOutData) {} + virtual void GetFrameUniformity(const LayersId& aLayersId, + FrameUniformityData* data) = 0; + virtual void SetConfirmedTargetAPZC( + const LayersId& aLayersId, const uint64_t& aInputBlockId, + nsTArray&& aTargets) = 0; + virtual void UpdatePaintTime(LayerTransactionParent* aLayerTree, + const TimeDuration& aPaintTime) {} + virtual void RegisterPayloads(LayerTransactionParent* aLayerTree, + const nsTArray& aPayload) {} + + IShmemAllocator* AsShmemAllocator() override { return this; } + + CompositorBridgeParentBase* AsCompositorBridgeParentBase() override { + return this; + } + + mozilla::ipc::IPCResult RecvSyncWithCompositor() { return IPC_OK(); } + + mozilla::ipc::IPCResult Recv__delete__() override { return IPC_OK(); } + + virtual void ObserveLayersUpdate(LayersId aLayersId, + LayersObserverEpoch aEpoch, + bool aActive) = 0; + + // HostIPCAllocator + base::ProcessId GetChildProcessId() override; + void NotifyNotUsed(PTextureParent* aTexture, + uint64_t aTransactionId) override; + void SendAsyncMessage( + const nsTArray& aMessage) override; + + // IShmemAllocator + bool AllocShmem(size_t aSize, + mozilla::ipc::SharedMemory::SharedMemoryType aType, + mozilla::ipc::Shmem* aShmem) override; + bool AllocUnsafeShmem(size_t aSize, + mozilla::ipc::SharedMemory::SharedMemoryType aType, + mozilla::ipc::Shmem* aShmem) override; + bool DeallocShmem(mozilla::ipc::Shmem& aShmem) override; + + // MetricsSharingController + NS_IMETHOD_(MozExternalRefCountType) AddRef() override { + return HostIPCAllocator::AddRef(); + } + NS_IMETHOD_(MozExternalRefCountType) Release() override { + return HostIPCAllocator::Release(); + } + base::ProcessId RemotePid() override; + bool StartSharingMetrics(mozilla::ipc::SharedMemoryBasic::Handle aHandle, + CrossProcessMutexHandle aMutexHandle, + LayersId aLayersId, uint32_t aApzcId) override; + bool StopSharingMetrics(ScrollableLayerGuid::ViewID aScrollId, + uint32_t aApzcId) override; + + virtual bool IsRemote() const { return false; } + + virtual UniquePtr LookupSurfaceDescriptorForClientTexture( + const int64_t aTextureId) { + MOZ_CRASH("Should only be called on ContentCompositorBridgeParent."); + } + + virtual void ForceComposeToTarget(gfx::DrawTarget* aTarget, + const gfx::IntRect* aRect = nullptr) { + MOZ_CRASH(); + } + + virtual void NotifyMemoryPressure() {} + virtual void AccumulateMemoryReport(wr::MemoryReport*) {} + + protected: + virtual ~CompositorBridgeParentBase(); + + virtual PAPZParent* AllocPAPZParent(const LayersId& layersId) = 0; + virtual bool DeallocPAPZParent(PAPZParent* aActor) = 0; + + virtual PAPZCTreeManagerParent* AllocPAPZCTreeManagerParent( + const LayersId& layersId) = 0; + virtual bool DeallocPAPZCTreeManagerParent( + PAPZCTreeManagerParent* aActor) = 0; + + virtual PLayerTransactionParent* AllocPLayerTransactionParent( + const nsTArray& layersBackendHints, + const LayersId& id) = 0; + virtual bool DeallocPLayerTransactionParent( + PLayerTransactionParent* aActor) = 0; + + virtual PTextureParent* AllocPTextureParent( + const SurfaceDescriptor& aSharedData, const ReadLockDescriptor& aReadLock, + const LayersBackend& aBackend, const TextureFlags& aTextureFlags, + const LayersId& id, const uint64_t& aSerial, + const MaybeExternalImageId& aExternalImageId) = 0; + virtual bool DeallocPTextureParent(PTextureParent* aActor) = 0; + + virtual PWebRenderBridgeParent* AllocPWebRenderBridgeParent( + const PipelineId& pipelineId, const LayoutDeviceIntSize& aSize, + const WindowKind& aWindowKind) = 0; + virtual bool DeallocPWebRenderBridgeParent( + PWebRenderBridgeParent* aActor) = 0; + + virtual webgpu::PWebGPUParent* AllocPWebGPUParent() = 0; + virtual bool DeallocPWebGPUParent(webgpu::PWebGPUParent* aActor) = 0; + + virtual PCompositorWidgetParent* AllocPCompositorWidgetParent( + const CompositorWidgetInitData& aInitData) = 0; + virtual bool DeallocPCompositorWidgetParent( + PCompositorWidgetParent* aActor) = 0; + + virtual mozilla::ipc::IPCResult RecvRemotePluginsReady() = 0; + virtual mozilla::ipc::IPCResult RecvAdoptChild(const LayersId& id) = 0; + virtual mozilla::ipc::IPCResult RecvFlushRenderingAsync() = 0; + virtual mozilla::ipc::IPCResult RecvForcePresent() = 0; + virtual mozilla::ipc::IPCResult RecvNotifyRegionInvalidated( + const nsIntRegion& region) = 0; + virtual mozilla::ipc::IPCResult RecvRequestNotifyAfterRemotePaint() = 0; + virtual mozilla::ipc::IPCResult RecvAllPluginsCaptured() = 0; + virtual mozilla::ipc::IPCResult RecvBeginRecording( + const TimeStamp& aRecordingStart, BeginRecordingResolver&& aResolve) = 0; + virtual mozilla::ipc::IPCResult RecvEndRecordingToDisk( + EndRecordingToDiskResolver&& aResolve) = 0; + virtual mozilla::ipc::IPCResult RecvEndRecordingToMemory( + EndRecordingToMemoryResolver&& aResolve) = 0; + virtual mozilla::ipc::IPCResult RecvInitialize( + const LayersId& rootLayerTreeId) = 0; + virtual mozilla::ipc::IPCResult RecvWillClose() = 0; + virtual mozilla::ipc::IPCResult RecvPause() = 0; + virtual mozilla::ipc::IPCResult RecvRequestFxrOutput() = 0; + virtual mozilla::ipc::IPCResult RecvResume() = 0; + virtual mozilla::ipc::IPCResult RecvResumeAsync() = 0; + virtual mozilla::ipc::IPCResult RecvNotifyChildCreated( + const LayersId& id, CompositorOptions* compositorOptions) = 0; + virtual mozilla::ipc::IPCResult RecvMapAndNotifyChildCreated( + const LayersId& id, const ProcessId& owner, + CompositorOptions* compositorOptions) = 0; + virtual mozilla::ipc::IPCResult RecvNotifyChildRecreated( + const LayersId& id, CompositorOptions* compositorOptions) = 0; + virtual mozilla::ipc::IPCResult RecvMakeSnapshot( + const SurfaceDescriptor& inSnapshot, const IntRect& dirtyRect) = 0; + virtual mozilla::ipc::IPCResult RecvFlushRendering() = 0; + virtual mozilla::ipc::IPCResult RecvWaitOnTransactionProcessed() = 0; + virtual mozilla::ipc::IPCResult RecvStartFrameTimeRecording( + const int32_t& bufferSize, uint32_t* startIndex) = 0; + virtual mozilla::ipc::IPCResult RecvStopFrameTimeRecording( + const uint32_t& startIndex, nsTArray* intervals) = 0; + virtual mozilla::ipc::IPCResult RecvCheckContentOnlyTDR( + const uint32_t& sequenceNum, bool* isContentOnlyTDR) = 0; + virtual mozilla::ipc::IPCResult RecvInitPCanvasParent( + Endpoint&& aEndpoint) = 0; + virtual mozilla::ipc::IPCResult RecvReleasePCanvasParent() = 0; + + virtual mozilla::ipc::IPCResult RecvSupportsAsyncDXGISurface(bool* value) { + return IPC_FAIL_NO_REASON(this); + } + virtual mozilla::ipc::IPCResult RecvPreferredDXGIAdapter( + DxgiAdapterDesc* desc) { + return IPC_FAIL_NO_REASON(this); + } + + virtual already_AddRefed AllocPWebGLParent() = 0; + + bool mCanSend; + + private: + RefPtr mCompositorManager; +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS( + CompositorBridgeParentBase::TransformsToSkip) + +class CompositorBridgeParent final : public CompositorBridgeParentBase, + public CompositorController, + public CompositorVsyncSchedulerOwner { + friend class CompositorThreadHolder; + friend class InProcessCompositorSession; + friend class gfx::GPUProcessManager; + friend class gfx::GPUParent; + friend class PCompositorBridgeParent; +#ifdef FUZZING + friend class mozilla::ipc::ProtocolFuzzerHelper; +#endif + + public: + NS_IMETHOD_(MozExternalRefCountType) AddRef() override { + return CompositorBridgeParentBase::AddRef(); + } + NS_IMETHOD_(MozExternalRefCountType) Release() override { + return CompositorBridgeParentBase::Release(); + } + + explicit CompositorBridgeParent(CompositorManagerParent* aManager, + CSSToLayoutDeviceScale aScale, + const TimeDuration& aVsyncRate, + const CompositorOptions& aOptions, + bool aUseExternalSurfaceSize, + const gfx::IntSize& aSurfaceSize); + + void InitSameProcess(widget::CompositorWidget* aWidget, + const LayersId& aLayerTreeId); + + mozilla::ipc::IPCResult RecvInitialize( + const LayersId& aRootLayerTreeId) override; + mozilla::ipc::IPCResult RecvWillClose() override; + mozilla::ipc::IPCResult RecvPause() override; + mozilla::ipc::IPCResult RecvRequestFxrOutput() override; + mozilla::ipc::IPCResult RecvResume() override; + mozilla::ipc::IPCResult RecvResumeAsync() override; + mozilla::ipc::IPCResult RecvNotifyChildCreated( + const LayersId& child, CompositorOptions* aOptions) override; + mozilla::ipc::IPCResult RecvMapAndNotifyChildCreated( + const LayersId& child, const base::ProcessId& pid, + CompositorOptions* aOptions) override; + mozilla::ipc::IPCResult RecvNotifyChildRecreated( + const LayersId& child, CompositorOptions* aOptions) override; + mozilla::ipc::IPCResult RecvAdoptChild(const LayersId& child) override; + mozilla::ipc::IPCResult RecvMakeSnapshot(const SurfaceDescriptor& aInSnapshot, + const gfx::IntRect& aRect) override; + mozilla::ipc::IPCResult RecvFlushRendering() override; + mozilla::ipc::IPCResult RecvFlushRenderingAsync() override; + mozilla::ipc::IPCResult RecvWaitOnTransactionProcessed() override; + mozilla::ipc::IPCResult RecvForcePresent() override; + + mozilla::ipc::IPCResult RecvNotifyRegionInvalidated( + const nsIntRegion& aRegion) override; + mozilla::ipc::IPCResult RecvStartFrameTimeRecording( + const int32_t& aBufferSize, uint32_t* aOutStartIndex) override; + mozilla::ipc::IPCResult RecvStopFrameTimeRecording( + const uint32_t& aStartIndex, nsTArray* intervals) override; + + mozilla::ipc::IPCResult RecvCheckContentOnlyTDR( + const uint32_t& sequenceNum, bool* isContentOnlyTDR) override { + return IPC_OK(); + } + + // Unused for chrome <-> compositor communication (which this class does). + // @see ContentCompositorBridgeParent::RecvRequestNotifyAfterRemotePaint + mozilla::ipc::IPCResult RecvRequestNotifyAfterRemotePaint() override { + return IPC_OK(); + }; + + mozilla::ipc::IPCResult RecvAllPluginsCaptured() override; + mozilla::ipc::IPCResult RecvBeginRecording( + const TimeStamp& aRecordingStart, + BeginRecordingResolver&& aResolve) override; + mozilla::ipc::IPCResult RecvEndRecordingToDisk( + EndRecordingToDiskResolver&& aResolve) override; + mozilla::ipc::IPCResult RecvEndRecordingToMemory( + EndRecordingToMemoryResolver&& aResolve) override; + + void NotifyMemoryPressure() override; + void AccumulateMemoryReport(wr::MemoryReport*) override; + + void ActorDestroy(ActorDestroyReason why) override; + + void ShadowLayersUpdated(LayerTransactionParent* aLayerTree, + const TransactionInfo& aInfo, + bool aHitTestUpdate) override; + void ScheduleComposite(LayerTransactionParent* aLayerTree) override; + bool SetTestSampleTime(const LayersId& aId, const TimeStamp& aTime) override; + void LeaveTestMode(const LayersId& aId) override; + void ApplyAsyncProperties(LayerTransactionParent* aLayerTree, + TransformsToSkip aSkip) override; + CompositorAnimationStorage* GetAnimationStorage(); + using JankedAnimations = + std::unordered_map, LayersId::HashFn>; + void NotifyJankedAnimations(const JankedAnimations& aJankedAnimations); + void SetTestAsyncScrollOffset(const LayersId& aLayersId, + const ScrollableLayerGuid::ViewID& aScrollId, + const CSSPoint& aPoint) override; + void SetTestAsyncZoom(const LayersId& aLayersId, + const ScrollableLayerGuid::ViewID& aScrollId, + const LayerToParentLayerScale& aZoom) override; + void FlushApzRepaints(const LayersId& aLayersId) override; + void GetAPZTestData(const LayersId& aLayersId, + APZTestData* aOutData) override; + void GetFrameUniformity(const LayersId& aLayersId, + FrameUniformityData* data) override; + void SetConfirmedTargetAPZC( + const LayersId& aLayersId, const uint64_t& aInputBlockId, + nsTArray&& aTargets) override; + AsyncCompositionManager* GetCompositionManager( + LayerTransactionParent* aLayerTree) override { + return mCompositionManager; + } + void SetFixedLayerMargins(ScreenIntCoord aTop, ScreenIntCoord aBottom); + + PTextureParent* AllocPTextureParent( + const SurfaceDescriptor& aSharedData, const ReadLockDescriptor& aReadLock, + const LayersBackend& aLayersBackend, const TextureFlags& aFlags, + const LayersId& aId, const uint64_t& aSerial, + const wr::MaybeExternalImageId& aExternalImageId) override; + bool DeallocPTextureParent(PTextureParent* actor) override; + + mozilla::ipc::IPCResult RecvInitPCanvasParent( + Endpoint&& aEndpoint) final; + + mozilla::ipc::IPCResult RecvReleasePCanvasParent() final; + + bool IsSameProcess() const override; + + void NotifyWebRenderDisableNativeCompositor(); + + void NotifyDidRender(const VsyncId& aCompositeStartId, + TimeStamp& aCompositeStart, TimeStamp& aRenderStart, + TimeStamp& aCompositeEnd, + wr::RendererStats* aStats = nullptr); + void NotifyPipelineRendered(const wr::PipelineId& aPipelineId, + const wr::Epoch& aEpoch, + const VsyncId& aCompositeStartId, + TimeStamp& aCompositeStart, + TimeStamp& aRenderStart, TimeStamp& aCompositeEnd, + wr::RendererStats* aStats = nullptr); + void NotifyDidSceneBuild(RefPtr aInfo); + RefPtr GetAsyncImagePipelineManager() const; + + PCompositorWidgetParent* AllocPCompositorWidgetParent( + const CompositorWidgetInitData& aInitData) override; + bool DeallocPCompositorWidgetParent(PCompositorWidgetParent* aActor) override; + + void ObserveLayersUpdate(LayersId aLayersId, LayersObserverEpoch aEpoch, + bool aActive) override {} + + /** + * This forces the is-first-paint flag to true. This is intended to + * be called by the widget code when it loses its viewport information + * (or for whatever reason wants to refresh the viewport information). + * The information refresh happens because the compositor will call + * SetFirstPaintViewport on the next frame of composition. + */ + void ForceIsFirstPaint(); + + static void SetShadowProperties(Layer* aLayer); + + void NotifyChildCreated(LayersId aChild); + + void AsyncRender(); + + // Can be called from any thread + void ScheduleRenderOnCompositorThread() override; + + void ScheduleComposition(); + + void NotifyShadowTreeTransaction(LayersId aId, bool aIsFirstPaint, + const FocusTarget& aFocusTarget, + bool aScheduleComposite, + uint32_t aPaintSequenceNumber, + bool aIsRepeatTransaction, + bool aHitTestUpdate); + + void UpdatePaintTime(LayerTransactionParent* aLayerTree, + const TimeDuration& aPaintTime) override; + void RegisterPayloads(LayerTransactionParent* aLayerTree, + const nsTArray& aPayload) override; + + /** + * Check rotation info and schedule a rendering task if needed. + * Only can be called from compositor thread. + */ + void ScheduleRotationOnCompositorThread(const TargetConfig& aTargetConfig, + bool aIsFirstPaint); + + static void ScheduleForcedComposition(const LayersId& aLayersId); + + /** + * Returns the unique layer tree identifier that corresponds to the root + * tree of this compositor. + */ + LayersId RootLayerTreeId(); + + /** + * Notify local and remote layer trees connected to this compositor that + * the compositor's local device is being reset. All layers must be + * invalidated to clear any cached TextureSources. + * + * This must be called on the compositor thread. + */ + void InvalidateRemoteLayers(); + + /** + * Initialize statics. + */ + static void InitializeStatics(); + + /** + * Returns a pointer to the CompositorBridgeParent corresponding to the given + * ID. + */ + static CompositorBridgeParent* GetCompositorBridgeParent(uint64_t id); + + /** + * Notify the compositor for the given layer tree that vsync has occurred. + */ + static void NotifyVsync(const VsyncEvent& aVsync, const LayersId& aLayersId); + + /** + * Set aController as the pan/zoom callback for the subtree referred + * to by aLayersId. + * + * Must run on content main thread. + */ + static void SetControllerForLayerTree(LayersId aLayersId, + GeckoContentController* aController); + + struct LayerTreeState { + LayerTreeState(); + ~LayerTreeState(); + RefPtr mRoot; + RefPtr mController; + APZCTreeManagerParent* mApzcTreeManagerParent; + RefPtr mParent; + HostLayerManager* mLayerManager; + RefPtr mWrBridge; + // Pointer to the ContentCompositorBridgeParent. Used by APZCs to share + // their FrameMetrics with the corresponding child process that holds + // the PCompositorBridgeChild + ContentCompositorBridgeParent* mContentCompositorBridgeParent; + TargetConfig mTargetConfig; + LayerTransactionParent* mLayerTree; + nsTArray mPluginData; + bool mUpdatedPluginDataAvailable; + + CompositorController* GetCompositorController() const; + MetricsSharingController* CrossProcessSharingController() const; + MetricsSharingController* InProcessSharingController() const; + RefPtr mUiControllerParent; + }; + + /** + * Lookup the indirect shadow tree for |aId| and return it if it + * exists. Otherwise null is returned. This must only be called on + * the compositor thread. + */ + static LayerTreeState* GetIndirectShadowTree(LayersId aId); + + /** + * Lookup the indirect shadow tree for |aId|, call the function object and + * return true if found. If not found, return false. + */ + static bool CallWithIndirectShadowTree( + LayersId aId, const std::function& aFunc); + + /** + * Given the layers id for a content process, get the APZCTreeManagerParent + * for the corresponding *root* layers id. That is, the APZCTreeManagerParent, + * if one is found, will always be connected to the parent process rather + * than a content process. Note that unless the compositor process is + * separated this is expected to return null, because if the compositor is + * living in the gecko parent process then there is no APZCTreeManagerParent + * for the parent process. + */ + static APZCTreeManagerParent* GetApzcTreeManagerParentForRoot( + LayersId aContentLayersId); + /** + * Same as the GetApzcTreeManagerParentForRoot function, but returns + * the GeckoContentController for the parent process. + */ + static GeckoContentController* GetGeckoContentControllerForRoot( + LayersId aContentLayersId); + +#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) + /** + * Calculates and requests the main thread update plugin positioning, clip, + * and visibility via ipc. + */ + bool UpdatePluginWindowState(LayersId aId); + + /** + * Plugin visibility helpers for the apz (main thread) and compositor + * thread. + */ + void ScheduleShowAllPluginWindows() override; + void ScheduleHideAllPluginWindows() override; + void ShowAllPluginWindows(); + void HideAllPluginWindows(); +#else + void ScheduleShowAllPluginWindows() override {} + void ScheduleHideAllPluginWindows() override {} +#endif + + /** + * Main thread response for a plugin visibility request made by the + * compositor thread. + */ + mozilla::ipc::IPCResult RecvRemotePluginsReady() override; + + /** + * Used by the profiler to denote when a vsync occured + */ + static void PostInsertVsyncProfilerMarker(mozilla::TimeStamp aVsyncTimestamp); + + widget::CompositorWidget* GetWidget() { return mWidget; } + + virtual void ForceComposeToTarget( + gfx::DrawTarget* aTarget, const gfx::IntRect* aRect = nullptr) override; + + PAPZCTreeManagerParent* AllocPAPZCTreeManagerParent( + const LayersId& aLayersId) override; + bool DeallocPAPZCTreeManagerParent(PAPZCTreeManagerParent* aActor) override; + + // Helper method so that we don't have to expose mApzcTreeManager to + // ContentCompositorBridgeParent. + void AllocateAPZCTreeManagerParent( + const MonitorAutoLock& aProofOfLayerTreeStateLock, + const LayersId& aLayersId, LayerTreeState& aLayerTreeStateToUpdate); + + PAPZParent* AllocPAPZParent(const LayersId& aLayersId) override; + bool DeallocPAPZParent(PAPZParent* aActor) override; + + RefPtr GetAPZSampler() const; + RefPtr GetAPZUpdater() const; + RefPtr GetOMTASampler() const; + + CompositorOptions GetOptions() const { return mOptions; } + + TimeDuration GetVsyncInterval() const override { + // the variable is called "rate" but really it's an interval + return mVsyncRate; + } + + PWebRenderBridgeParent* AllocPWebRenderBridgeParent( + const wr::PipelineId& aPipelineId, const LayoutDeviceIntSize& aSize, + const WindowKind& aWindowKind) override; + bool DeallocPWebRenderBridgeParent(PWebRenderBridgeParent* aActor) override; + RefPtr GetWebRenderBridgeParent() const; + Maybe GetTestingTimeStamp() const; + + webgpu::PWebGPUParent* AllocPWebGPUParent() override; + bool DeallocPWebGPUParent(webgpu::PWebGPUParent* aActor) override; + + static CompositorBridgeParent* GetCompositorBridgeParentFromLayersId( + const LayersId& aLayersId); + static RefPtr GetCompositorBridgeParentFromWindowId( + const wr::WindowId& aWindowId); + + /** + * This returns a reference to the IAPZCTreeManager "controller subinterface" + * to which pan/zoom-related events can be sent. The controller subinterface + * doesn't expose any sampler-thread APZCTreeManager methods. + */ + static already_AddRefed GetAPZCTreeManager( + LayersId aLayersId); + + WebRenderBridgeParent* GetWrBridge() { return mWrBridge; } + webgpu::WebGPUParent* GetWebGPUBridge() { return mWebGPUBridge; } + + already_AddRefed AllocPWebGLParent() override { + MOZ_ASSERT_UNREACHABLE( + "This message is CrossProcessCompositorBridgeParent only"); + return nullptr; + } + + private: + void Initialize(); + + /** + * Called during destruction in order to release resources as early as + * possible. + */ + void StopAndClearResources(); + + /** + * Release compositor-thread resources referred to by |aID|. + * + * Must run on the content main thread. + */ + static void DeallocateLayerTreeId(LayersId aId); + + /** + * Notify the compositor the quality settings have been updated. + */ + static void UpdateQualitySettings(); + + /** + * Notify the compositor the debug flags have been updated. + */ + static void UpdateDebugFlags(); + + /** + * Notify the compositor the debug flags have been updated. + */ + static void UpdateWebRenderMultithreading(); + + /** + * Notify the compositor webrender batching parameters have been updated. + */ + static void UpdateWebRenderBatchingParameters(); + + /** + * Notify the compositor webrender profiler UI string has been updated. + */ + static void UpdateWebRenderProfilerUI(); + + /** + * Wrap the data structure to be sent over IPC. + */ + Maybe WrapCollectedFrames(CollectedFrames&& aFrames); + + protected: + // Protected destructor, to discourage deletion outside of Release(): + virtual ~CompositorBridgeParent(); + + void DeferredDestroy(); + + PLayerTransactionParent* AllocPLayerTransactionParent( + const nsTArray& aBackendHints, + const LayersId& aId) override; + bool DeallocPLayerTransactionParent( + PLayerTransactionParent* aLayers) override; + + void SetEGLSurfaceRect(int x, int y, int width, int height); + + void InitializeLayerManager(const nsTArray& aBackendHints); + + public: + void PauseComposition(); + void ResumeComposition(); + void ResumeCompositionAndResize(int x, int y, int width, int height); + void Invalidate(); + bool IsPaused() { return mPaused; } + + protected: + void ForceComposition(); + void CancelCurrentCompositeTask(); + + // CompositorVsyncSchedulerOwner + bool IsPendingComposite() override; + void FinishPendingComposite() override; + void CompositeToTarget(VsyncId aId, gfx::DrawTarget* aTarget, + const gfx::IntRect* aRect = nullptr) override; + + bool InitializeAdvancedLayers(const nsTArray& aBackendHints, + TextureFactoryIdentifier* aOutIdentifier); + RefPtr NewCompositor( + const nsTArray& aBackendHints); + + /** + * Add a compositor to the global compositor map. + */ + static void AddCompositor(CompositorBridgeParent* compositor, uint64_t* id); + /** + * Remove a compositor from the global compositor map. + */ + static CompositorBridgeParent* RemoveCompositor(uint64_t id); + + /** + * Creates the global compositor map. + */ + static void Setup(); + + /** + * Remaning cleanups after the compositore thread is gone. + */ + static void FinishShutdown(); + + /** + * Return true if current state allows compositing, that is + * finishing a layers transaction. + */ + bool CanComposite(); + + void DidComposite(const VsyncId& aId, TimeStamp& aCompositeStart, + TimeStamp& aCompositeEnd); + + void NotifyDidComposite(TransactionId aTransactionId, VsyncId aId, + TimeStamp& aCompositeStart, TimeStamp& aCompositeEnd); + + // The indirect layer tree lock must be held before calling this function. + // Callback should take (LayerTreeState* aState, const LayersId& aLayersId) + template + inline void ForEachIndirectLayerTree(const Lambda& aCallback); + + // The indirect layer tree lock must be held before calling this function. + // Callback should take (LayerTreeState* aState, const LayersId& aLayersId) + template + static inline void ForEachWebRenderBridgeParent(const Lambda& aCallback); + + RefPtr mLayerManager; + RefPtr mCompositor; + RefPtr mCompositionManager; + RefPtr mAsyncImageManager; + RefPtr mWrBridge; + RefPtr mWebGPUBridge; + widget::CompositorWidget* mWidget; + Maybe mTestTime; + CSSToLayoutDeviceScale mScale; + TimeDuration mVsyncRate; + + TransactionId mPendingTransaction; + TimeStamp mRefreshStartTime; + TimeStamp mTxnStartTime; + TimeStamp mFwdTime; + + bool mPaused; + bool mHaveCompositionRecorder; + bool mIsForcedFirstPaint; + + bool mUseExternalSurfaceSize; + gfx::IntSize mEGLSurfaceSize; + + CompositorOptions mOptions; + + mozilla::Monitor mPauseCompositionMonitor; + mozilla::Monitor mResumeCompositionMonitor; + + uint64_t mCompositorBridgeID; + LayersId mRootLayerTreeID; + + bool mOverrideComposeReadiness; + RefPtr mForceCompositionTask; + + RefPtr mApzcTreeManager; + RefPtr mApzSampler; + RefPtr mApzUpdater; + RefPtr mOMTASampler; + + RefPtr mCompositorScheduler; + // This makes sure the compositorParent is not destroyed before receiving + // confirmation that the channel is closed. + // mSelfRef is cleared in DeferredDestroy which is scheduled by ActorDestroy. + RefPtr mSelfRef; + RefPtr mAnimationStorage; + + TimeDuration mPaintTime; + +#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) + // cached plugin data used to reduce the number of updates we request. + LayersId mLastPluginUpdateLayerTreeId; + nsIntPoint mPluginsLayerOffset; + nsIntRegion mPluginsLayerVisibleRegion; + nsTArray mCachedPluginData; + // Time until which we will block composition to wait for plugin updates. + TimeStamp mWaitForPluginsUntil; + // Indicates that we have actually blocked a composition waiting for plugins. + bool mHaveBlockedForPlugins = false; + // indicates if plugin window visibility and metric updates are currently + // being defered due to a scroll operation. + bool mDeferPluginWindows; + // indicates if the plugin windows were hidden, and need to be made + // visible again even if their geometry has not changed. + bool mPluginWindowsHidden; +#endif + + DISALLOW_EVIL_CONSTRUCTORS(CompositorBridgeParent); +}; + +int32_t RecordContentFrameTime( + const VsyncId& aTxnId, const TimeStamp& aVsyncStart, + const TimeStamp& aTxnStart, const VsyncId& aCompositeId, + const TimeStamp& aCompositeEnd, const TimeDuration& aFullPaintTime, + const TimeDuration& aVsyncRate, bool aContainsSVGGroup, + bool aRecordUploadStats, wr::RendererStats* aStats = nullptr); + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_CompositorBridgeParent_h diff --git a/gfx/layers/ipc/CompositorManagerChild.cpp b/gfx/layers/ipc/CompositorManagerChild.cpp new file mode 100644 index 0000000000..b629d28229 --- /dev/null +++ b/gfx/layers/ipc/CompositorManagerChild.cpp @@ -0,0 +1,256 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/CompositorManagerChild.h" + +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/layers/CompositorBridgeChild.h" +#include "mozilla/layers/CompositorManagerParent.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/gfx/GPUProcessManager.h" +#include "mozilla/dom/ContentChild.h" // for ContentChild +#include "mozilla/dom/BrowserChild.h" // for BrowserChild +#include "mozilla/ipc/Endpoint.h" +#include "VsyncSource.h" + +namespace mozilla { +namespace layers { + +using gfx::GPUProcessManager; + +StaticRefPtr CompositorManagerChild::sInstance; + +/* static */ +bool CompositorManagerChild::IsInitialized(uint64_t aProcessToken) { + MOZ_ASSERT(NS_IsMainThread()); + return sInstance && sInstance->CanSend() && + sInstance->mProcessToken == aProcessToken; +} + +/* static */ +void CompositorManagerChild::InitSameProcess(uint32_t aNamespace, + uint64_t aProcessToken) { + MOZ_ASSERT(NS_IsMainThread()); + if (NS_WARN_IF(IsInitialized(aProcessToken))) { + MOZ_ASSERT_UNREACHABLE("Already initialized same process"); + return; + } + + RefPtr parent = + CompositorManagerParent::CreateSameProcess(); + RefPtr child = + new CompositorManagerChild(parent, aProcessToken, aNamespace); + if (NS_WARN_IF(!child->CanSend())) { + MOZ_DIAGNOSTIC_ASSERT(false, "Failed to open same process protocol"); + return; + } + + parent->BindComplete(/* aIsRoot */ true); + sInstance = std::move(child); +} + +/* static */ +bool CompositorManagerChild::Init(Endpoint&& aEndpoint, + uint32_t aNamespace, + uint64_t aProcessToken /* = 0 */) { + MOZ_ASSERT(NS_IsMainThread()); + if (sInstance) { + MOZ_ASSERT(sInstance->mNamespace != aNamespace); + } + + sInstance = new CompositorManagerChild(std::move(aEndpoint), aProcessToken, + aNamespace); + return sInstance->CanSend(); +} + +/* static */ +void CompositorManagerChild::Shutdown() { + MOZ_ASSERT(NS_IsMainThread()); + CompositorBridgeChild::ShutDown(); + + if (!sInstance) { + return; + } + + sInstance->Close(); + sInstance = nullptr; +} + +/* static */ +void CompositorManagerChild::OnGPUProcessLost(uint64_t aProcessToken) { + MOZ_ASSERT(NS_IsMainThread()); + + // Since GPUChild and CompositorManagerChild will race on ActorDestroy, we + // cannot know if the CompositorManagerChild is about to be released but has + // yet to be. As such, we want to pre-emptively set mCanSend to false. + if (sInstance && sInstance->mProcessToken == aProcessToken) { + sInstance->mCanSend = false; + } +} + +/* static */ +bool CompositorManagerChild::CreateContentCompositorBridge( + uint32_t aNamespace) { + MOZ_ASSERT(NS_IsMainThread()); + if (NS_WARN_IF(!sInstance || !sInstance->CanSend())) { + return false; + } + + CompositorBridgeOptions options = ContentCompositorOptions(); + + RefPtr bridge = new CompositorBridgeChild(sInstance); + if (NS_WARN_IF( + !sInstance->SendPCompositorBridgeConstructor(bridge, options))) { + return false; + } + + bridge->InitForContent(aNamespace); + return true; +} + +/* static */ +already_AddRefed +CompositorManagerChild::CreateWidgetCompositorBridge( + uint64_t aProcessToken, LayerManager* aLayerManager, uint32_t aNamespace, + CSSToLayoutDeviceScale aScale, const CompositorOptions& aOptions, + bool aUseExternalSurfaceSize, const gfx::IntSize& aSurfaceSize) { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + if (NS_WARN_IF(!sInstance || !sInstance->CanSend())) { + return nullptr; + } + + TimeDuration vsyncRate = gfxPlatform::GetPlatform() + ->GetHardwareVsync() + ->GetGlobalDisplay() + .GetVsyncRate(); + + CompositorBridgeOptions options = WidgetCompositorOptions( + aScale, vsyncRate, aOptions, aUseExternalSurfaceSize, aSurfaceSize); + + RefPtr bridge = new CompositorBridgeChild(sInstance); + if (NS_WARN_IF( + !sInstance->SendPCompositorBridgeConstructor(bridge, options))) { + return nullptr; + } + + bridge->InitForWidget(aProcessToken, aLayerManager, aNamespace); + return bridge.forget(); +} + +/* static */ +already_AddRefed +CompositorManagerChild::CreateSameProcessWidgetCompositorBridge( + LayerManager* aLayerManager, uint32_t aNamespace) { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + if (NS_WARN_IF(!sInstance || !sInstance->CanSend())) { + return nullptr; + } + + CompositorBridgeOptions options = SameProcessWidgetCompositorOptions(); + + RefPtr bridge = new CompositorBridgeChild(sInstance); + if (NS_WARN_IF( + !sInstance->SendPCompositorBridgeConstructor(bridge, options))) { + return nullptr; + } + + bridge->InitForWidget(1, aLayerManager, aNamespace); + return bridge.forget(); +} + +CompositorManagerChild::CompositorManagerChild(CompositorManagerParent* aParent, + uint64_t aProcessToken, + uint32_t aNamespace) + : mProcessToken(aProcessToken), + mNamespace(aNamespace), + mResourceId(0), + mCanSend(false) { + MOZ_ASSERT(aParent); + + SetOtherProcessId(base::GetCurrentProcId()); + ipc::MessageChannel* channel = aParent->GetIPCChannel(); + if (NS_WARN_IF(!Open(channel, CompositorThread(), ipc::ChildSide))) { + return; + } + + mCanSend = true; + AddRef(); + SetReplyTimeout(); +} + +CompositorManagerChild::CompositorManagerChild( + Endpoint&& aEndpoint, uint64_t aProcessToken, + uint32_t aNamespace) + : mProcessToken(aProcessToken), + mNamespace(aNamespace), + mResourceId(0), + mCanSend(false) { + if (NS_WARN_IF(!aEndpoint.Bind(this))) { + return; + } + + mCanSend = true; + AddRef(); + SetReplyTimeout(); +} + +void CompositorManagerChild::ActorDealloc() { + MOZ_ASSERT(!mCanSend); + Release(); +} + +void CompositorManagerChild::ActorDestroy(ActorDestroyReason aReason) { + mCanSend = false; + if (sInstance == this) { + sInstance = nullptr; + } +} + +void CompositorManagerChild::HandleFatalError(const char* aMsg) const { + dom::ContentChild::FatalErrorIfNotUsingGPUProcess(aMsg, OtherPid()); +} + +void CompositorManagerChild::ProcessingError(Result aCode, + const char* aReason) { + if (aCode != MsgDropped) { + gfxDevCrash(gfx::LogReason::ProcessingError) + << "Processing error in CompositorBridgeChild: " << int(aCode); + } +} + +void CompositorManagerChild::SetReplyTimeout() { +#ifndef DEBUG + // Add a timeout for release builds to kill GPU process when it hangs. + if (XRE_IsParentProcess() && GPUProcessManager::Get()->GetGPUChild()) { + int32_t timeout = + StaticPrefs::layers_gpu_process_ipc_reply_timeout_ms_AtStartup(); + SetReplyTimeoutMs(timeout); + } +#endif +} + +bool CompositorManagerChild::ShouldContinueFromReplyTimeout() { + if (XRE_IsParentProcess()) { + gfxCriticalNote << "Killing GPU process due to IPC reply timeout"; + MOZ_DIAGNOSTIC_ASSERT(GPUProcessManager::Get()->GetGPUChild()); + GPUProcessManager::Get()->KillProcess(); + } + return false; +} + +mozilla::ipc::IPCResult CompositorManagerChild::RecvNotifyWebRenderError( + const WebRenderError&& aError) { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + GPUProcessManager::Get()->NotifyWebRenderError(aError); + return IPC_OK(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/CompositorManagerChild.h b/gfx/layers/ipc/CompositorManagerChild.h new file mode 100644 index 0000000000..50adedbb63 --- /dev/null +++ b/gfx/layers/ipc/CompositorManagerChild.h @@ -0,0 +1,109 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_COMPOSITORMANAGERCHILD_H +#define MOZILLA_GFX_COMPOSITORMANAGERCHILD_H + +#include // for size_t +#include // for uint32_t, uint64_t +#include "mozilla/Attributes.h" // for override +#include "mozilla/RefPtr.h" // for already_AddRefed +#include "mozilla/StaticPtr.h" // for StaticRefPtr +#include "mozilla/layers/PCompositorManagerChild.h" + +namespace mozilla { +namespace layers { + +class CompositorBridgeChild; +class CompositorManagerParent; +class LayerManager; + +class CompositorManagerChild : public PCompositorManagerChild { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CompositorManagerChild) + + public: + static bool IsInitialized(uint64_t aProcessToken); + static void InitSameProcess(uint32_t aNamespace, uint64_t aProcessToken); + static bool Init(Endpoint&& aEndpoint, + uint32_t aNamespace, uint64_t aProcessToken = 0); + static void Shutdown(); + static void OnGPUProcessLost(uint64_t aProcessToken); + + static bool CreateContentCompositorBridge(uint32_t aNamespace); + + static already_AddRefed CreateWidgetCompositorBridge( + uint64_t aProcessToken, LayerManager* aLayerManager, uint32_t aNamespace, + CSSToLayoutDeviceScale aScale, const CompositorOptions& aOptions, + bool aUseExternalSurfaceSize, const gfx::IntSize& aSurfaceSize); + + static already_AddRefed + CreateSameProcessWidgetCompositorBridge(LayerManager* aLayerManager, + uint32_t aNamespace); + + static CompositorManagerChild* GetInstance() { + MOZ_ASSERT(NS_IsMainThread()); + return sInstance; + } + + bool CanSend() const { + MOZ_ASSERT(NS_IsMainThread()); + return mCanSend; + } + + uint32_t GetNextResourceId() { + MOZ_ASSERT(NS_IsMainThread()); + return ++mResourceId; + } + + uint32_t GetNamespace() const { return mNamespace; } + + bool OwnsExternalImageId(const wr::ExternalImageId& aId) const { + return mNamespace == static_cast(wr::AsUint64(aId) >> 32); + } + + wr::ExternalImageId GetNextExternalImageId() { + uint64_t id = GetNextResourceId(); + MOZ_RELEASE_ASSERT(id != 0); + id |= (static_cast(mNamespace) << 32); + return wr::ToExternalImageId(id); + } + + void ActorDestroy(ActorDestroyReason aReason) override; + + void HandleFatalError(const char* aMsg) const override; + + void ProcessingError(Result aCode, const char* aReason) override; + + bool ShouldContinueFromReplyTimeout() override; + + mozilla::ipc::IPCResult RecvNotifyWebRenderError( + const WebRenderError&& aError); + + private: + static StaticRefPtr sInstance; + + CompositorManagerChild(CompositorManagerParent* aParent, + uint64_t aProcessToken, uint32_t aNamespace); + + CompositorManagerChild(Endpoint&& aEndpoint, + uint64_t aProcessToken, uint32_t aNamespace); + + virtual ~CompositorManagerChild() = default; + + void ActorDealloc() override; + + void SetReplyTimeout(); + + uint64_t mProcessToken; + uint32_t mNamespace; + uint32_t mResourceId; + bool mCanSend; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/ipc/CompositorManagerParent.cpp b/gfx/layers/ipc/CompositorManagerParent.cpp new file mode 100644 index 0000000000..8b6ee34080 --- /dev/null +++ b/gfx/layers/ipc/CompositorManagerParent.cpp @@ -0,0 +1,335 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/CompositorManagerParent.h" +#include "mozilla/gfx/GPUParent.h" +#include "mozilla/webrender/RenderThread.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/layers/CompositorBridgeParent.h" +#include "mozilla/layers/ContentCompositorBridgeParent.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/layers/SharedSurfacesParent.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Unused.h" +#include "gfxPlatform.h" +#include "VsyncSource.h" + +namespace mozilla { +namespace layers { + +StaticRefPtr CompositorManagerParent::sInstance; +StaticMutex CompositorManagerParent::sMutex; + +#ifdef COMPOSITOR_MANAGER_PARENT_EXPLICIT_SHUTDOWN +StaticAutoPtr> + CompositorManagerParent::sActiveActors; +#endif + +/* static */ +already_AddRefed +CompositorManagerParent::CreateSameProcess() { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + StaticMutexAutoLock lock(sMutex); + + // We are creating a manager for the UI process, inside the combined GPU/UI + // process. It is created more-or-less the same but we retain a reference to + // the parent to access state. + if (NS_WARN_IF(sInstance)) { + MOZ_ASSERT_UNREACHABLE("Already initialized"); + return nullptr; + } + + // The child is responsible for setting up the IPC channel in the same + // process case because if we open from the child perspective, we can do it + // on the main thread and complete before we return the manager handles. + RefPtr parent = new CompositorManagerParent(); + parent->SetOtherProcessId(base::GetCurrentProcId()); + return parent.forget(); +} + +/* static */ +bool CompositorManagerParent::Create( + Endpoint&& aEndpoint, bool aIsRoot) { + MOZ_ASSERT(NS_IsMainThread()); + + // We are creating a manager for the another process, inside the GPU process + // (or UI process if it subsumbed the GPU process). + MOZ_ASSERT(aEndpoint.OtherPid() != base::GetCurrentProcId()); + + if (!CompositorThreadHolder::IsActive()) { + return false; + } + + RefPtr bridge = new CompositorManagerParent(); + + RefPtr runnable = + NewRunnableMethod&&, bool>( + "CompositorManagerParent::Bind", bridge, + &CompositorManagerParent::Bind, std::move(aEndpoint), aIsRoot); + CompositorThread()->Dispatch(runnable.forget()); + return true; +} + +/* static */ +already_AddRefed +CompositorManagerParent::CreateSameProcessWidgetCompositorBridge( + CSSToLayoutDeviceScale aScale, const CompositorOptions& aOptions, + bool aUseExternalSurfaceSize, const gfx::IntSize& aSurfaceSize) { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + + // When we are in a combined UI / GPU process, InProcessCompositorSession + // requires both the parent and child PCompositorBridge actors for its own + // construction, which is done on the main thread. Normally + // CompositorBridgeParent is created on the compositor thread via the IPDL + // plumbing (CompositorManagerParent::AllocPCompositorBridgeParent). Thus to + // actually get a reference to the parent, we would need to block on the + // compositor thread until it handles our constructor message. Because only + // one one IPDL constructor is permitted per parent and child protocol, we + // cannot make the normal case async and this case sync. Instead what we do + // is leave the constructor async (a boon to the content process setup) and + // create the parent ahead of time. It will pull the preinitialized parent + // from the queue when it receives the message and give that to IPDL. + + // Note that the static mutex not only is used to protect sInstance, but also + // mPendingCompositorBridges. + StaticMutexAutoLock lock(sMutex); + if (NS_WARN_IF(!sInstance)) { + return nullptr; + } + + TimeDuration vsyncRate = gfxPlatform::GetPlatform() + ->GetHardwareVsync() + ->GetGlobalDisplay() + .GetVsyncRate(); + + RefPtr bridge = + new CompositorBridgeParent(sInstance, aScale, vsyncRate, aOptions, + aUseExternalSurfaceSize, aSurfaceSize); + + sInstance->mPendingCompositorBridges.AppendElement(bridge); + return bridge.forget(); +} + +CompositorManagerParent::CompositorManagerParent() + : mCompositorThreadHolder(CompositorThreadHolder::GetSingleton()) {} + +CompositorManagerParent::~CompositorManagerParent() = default; + +void CompositorManagerParent::Bind( + Endpoint&& aEndpoint, bool aIsRoot) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + if (NS_WARN_IF(!aEndpoint.Bind(this))) { + return; + } + + BindComplete(aIsRoot); +} + +void CompositorManagerParent::BindComplete(bool aIsRoot) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread() || + NS_IsMainThread()); + + // Add the IPDL reference to ourself, so we can't get freed until IPDL is + // done with us. + AddRef(); + + StaticMutexAutoLock lock(sMutex); + if (aIsRoot) { + sInstance = this; + } + +#ifdef COMPOSITOR_MANAGER_PARENT_EXPLICIT_SHUTDOWN + if (!sActiveActors) { + sActiveActors = new nsTArray(); + } + sActiveActors->AppendElement(this); +#endif +} + +void CompositorManagerParent::ActorDestroy(ActorDestroyReason aReason) { + SharedSurfacesParent::DestroyProcess(OtherPid()); + + StaticMutexAutoLock lock(sMutex); + if (sInstance == this) { + sInstance = nullptr; + } +} + +void CompositorManagerParent::ActorDealloc() { + GetCurrentSerialEventTarget()->Dispatch( + NewRunnableMethod("layers::CompositorManagerParent::DeferredDestroy", + this, &CompositorManagerParent::DeferredDestroy)); + +#ifdef COMPOSITOR_MANAGER_PARENT_EXPLICIT_SHUTDOWN + StaticMutexAutoLock lock(sMutex); + if (sActiveActors) { + sActiveActors->RemoveElement(this); + } +#endif + Release(); +} + +void CompositorManagerParent::DeferredDestroy() { + mCompositorThreadHolder = nullptr; +} + +#ifdef COMPOSITOR_MANAGER_PARENT_EXPLICIT_SHUTDOWN +/* static */ +void CompositorManagerParent::ShutdownInternal() { + UniquePtr> actors; + + // We move here because we may attempt to acquire the same lock during the + // destroy to remove the reference in sActiveActors. + { + StaticMutexAutoLock lock(sMutex); + actors = WrapUnique(sActiveActors.forget()); + } + + if (actors) { + for (auto& actor : *actors) { + actor->Close(); + } + } +} +#endif // COMPOSITOR_MANAGER_PARENT_EXPLICIT_SHUTDOWN + +/* static */ +void CompositorManagerParent::Shutdown() { + MOZ_ASSERT(NS_IsMainThread()); + +#ifdef COMPOSITOR_MANAGER_PARENT_EXPLICIT_SHUTDOWN + CompositorThread()->Dispatch(NS_NewRunnableFunction( + "layers::CompositorManagerParent::Shutdown", + []() -> void { CompositorManagerParent::ShutdownInternal(); })); +#endif +} + +already_AddRefed +CompositorManagerParent::AllocPCompositorBridgeParent( + const CompositorBridgeOptions& aOpt) { + switch (aOpt.type()) { + case CompositorBridgeOptions::TContentCompositorOptions: { + RefPtr bridge = + new ContentCompositorBridgeParent(this); + return bridge.forget(); + } + case CompositorBridgeOptions::TWidgetCompositorOptions: { + // Only the UI process is allowed to create widget compositors in the + // compositor process. + gfx::GPUParent* gpu = gfx::GPUParent::GetSingleton(); + if (NS_WARN_IF(!gpu || OtherPid() != gpu->OtherPid())) { + MOZ_ASSERT_UNREACHABLE("Child cannot create widget compositor!"); + break; + } + + const WidgetCompositorOptions& opt = aOpt.get_WidgetCompositorOptions(); + RefPtr bridge = new CompositorBridgeParent( + this, opt.scale(), opt.vsyncRate(), opt.options(), + opt.useExternalSurfaceSize(), opt.surfaceSize()); + return bridge.forget(); + } + case CompositorBridgeOptions::TSameProcessWidgetCompositorOptions: { + // If the GPU and UI process are combined, we actually already created the + // CompositorBridgeParent, so we need to reuse that to inject it into the + // IPDL framework. + if (NS_WARN_IF(OtherPid() != base::GetCurrentProcId())) { + MOZ_ASSERT_UNREACHABLE("Child cannot create same process compositor!"); + break; + } + + // Note that the static mutex not only is used to protect sInstance, but + // also mPendingCompositorBridges. + StaticMutexAutoLock lock(sMutex); + if (mPendingCompositorBridges.IsEmpty()) { + break; + } + + RefPtr bridge = mPendingCompositorBridges[0]; + mPendingCompositorBridges.RemoveElementAt(0); + return bridge.forget(); + } + default: + break; + } + + return nullptr; +} + +mozilla::ipc::IPCResult CompositorManagerParent::RecvAddSharedSurface( + const wr::ExternalImageId& aId, const SurfaceDescriptorShared& aDesc) { + SharedSurfacesParent::Add(aId, aDesc, OtherPid()); + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorManagerParent::RecvRemoveSharedSurface( + const wr::ExternalImageId& aId) { + SharedSurfacesParent::Remove(aId); + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorManagerParent::RecvReportSharedSurfacesMemory( + ReportSharedSurfacesMemoryResolver&& aResolver) { + SharedSurfacesMemoryReport report; + SharedSurfacesParent::AccumulateMemoryReport(OtherPid(), report); + aResolver(std::move(report)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorManagerParent::RecvNotifyMemoryPressure() { + nsTArray compositorBridges; + ManagedPCompositorBridgeParent(compositorBridges); + for (auto bridge : compositorBridges) { + static_cast(bridge)->NotifyMemoryPressure(); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult CompositorManagerParent::RecvReportMemory( + ReportMemoryResolver&& aResolver) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + MemoryReport aggregate; + PodZero(&aggregate); + + // Accumulate RenderBackend usage. + nsTArray compositorBridges; + ManagedPCompositorBridgeParent(compositorBridges); + for (auto bridge : compositorBridges) { + static_cast(bridge)->AccumulateMemoryReport( + &aggregate); + } + + // Accumulate Renderer usage asynchronously, and resolve. + // + // Note that the IPDL machinery requires aResolver to be called on this + // thread, so we can't just pass it over to the renderer thread. We use + // an intermediate MozPromise instead. + wr::RenderThread::AccumulateMemoryReport(aggregate)->Then( + CompositorThread(), __func__, + [resolver = std::move(aResolver)](MemoryReport aReport) { + resolver(aReport); + }, + [](bool) { + MOZ_ASSERT_UNREACHABLE("MemoryReport promises are never rejected"); + }); + + return IPC_OK(); +} + +/* static */ +void CompositorManagerParent::NotifyWebRenderError(wr::WebRenderError aError) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + + StaticMutexAutoLock lock(sMutex); + if (NS_WARN_IF(!sInstance)) { + return; + } + Unused << sInstance->SendNotifyWebRenderError(aError); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/CompositorManagerParent.h b/gfx/layers/ipc/CompositorManagerParent.h new file mode 100644 index 0000000000..f16a85aca4 --- /dev/null +++ b/gfx/layers/ipc/CompositorManagerParent.h @@ -0,0 +1,88 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_COMPOSITORMANAGERPARENT_H +#define MOZILLA_GFX_COMPOSITORMANAGERPARENT_H + +#include // for uint32_t +#include "mozilla/Attributes.h" // for override +#include "mozilla/StaticPtr.h" // for StaticRefPtr +#include "mozilla/StaticMutex.h" // for StaticMutex +#include "mozilla/RefPtr.h" // for already_AddRefed +#include "mozilla/layers/PCompositorManagerParent.h" +#include "nsTArray.h" // for AutoTArray + +namespace mozilla { +namespace layers { + +class CompositorBridgeParent; +class CompositorThreadHolder; + +#ifndef DEBUG +# define COMPOSITOR_MANAGER_PARENT_EXPLICIT_SHUTDOWN +#endif + +class CompositorManagerParent final : public PCompositorManagerParent { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CompositorManagerParent) + + public: + static already_AddRefed CreateSameProcess(); + static bool Create(Endpoint&& aEndpoint, + bool aIsRoot); + static void Shutdown(); + + static already_AddRefed + CreateSameProcessWidgetCompositorBridge(CSSToLayoutDeviceScale aScale, + const CompositorOptions& aOptions, + bool aUseExternalSurfaceSize, + const gfx::IntSize& aSurfaceSize); + + mozilla::ipc::IPCResult RecvAddSharedSurface( + const wr::ExternalImageId& aId, const SurfaceDescriptorShared& aDesc); + mozilla::ipc::IPCResult RecvRemoveSharedSurface( + const wr::ExternalImageId& aId); + mozilla::ipc::IPCResult RecvReportSharedSurfacesMemory( + ReportSharedSurfacesMemoryResolver&&); + + mozilla::ipc::IPCResult RecvNotifyMemoryPressure(); + + mozilla::ipc::IPCResult RecvReportMemory(ReportMemoryResolver&&); + + void BindComplete(bool aIsRoot); + void ActorDestroy(ActorDestroyReason aReason) override; + + already_AddRefed AllocPCompositorBridgeParent( + const CompositorBridgeOptions& aOpt); + + static void NotifyWebRenderError(wr::WebRenderError aError); + + private: + static StaticRefPtr sInstance; + static StaticMutex sMutex; + +#ifdef COMPOSITOR_MANAGER_PARENT_EXPLICIT_SHUTDOWN + static StaticAutoPtr> sActiveActors; + static void ShutdownInternal(); +#endif + + CompositorManagerParent(); + virtual ~CompositorManagerParent(); + + void Bind(Endpoint&& aEndpoint, bool aIsRoot); + + void ActorDealloc() override; + + void DeferredDestroy(); + + RefPtr mCompositorThreadHolder; + + AutoTArray, 1> mPendingCompositorBridges; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/ipc/CompositorThread.cpp b/gfx/layers/ipc/CompositorThread.cpp new file mode 100644 index 0000000000..53a23cbc6f --- /dev/null +++ b/gfx/layers/ipc/CompositorThread.cpp @@ -0,0 +1,159 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "CompositorThread.h" + +#include "CompositorBridgeParent.h" +#include "MainThreadUtils.h" +#include "VRManagerParent.h" +#include "mozilla/BackgroundHangMonitor.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/layers/CanvasTranslator.h" +#include "mozilla/layers/CompositorManagerParent.h" +#include "mozilla/layers/ImageBridgeParent.h" +#include "mozilla/media/MediaSystemResourceService.h" +#include "nsThread.h" +#include "nsThreadUtils.h" + +namespace mozilla { +namespace layers { + +static StaticRefPtr sCompositorThreadHolder; +static Atomic sFinishedCompositorShutDown(false); +static mozilla::BackgroundHangMonitor* sBackgroundHangMonitor; + +nsISerialEventTarget* CompositorThread() { + return sCompositorThreadHolder + ? sCompositorThreadHolder->GetCompositorThread() + : nullptr; +} + +CompositorThreadHolder* CompositorThreadHolder::GetSingleton() { + return sCompositorThreadHolder; +} + +CompositorThreadHolder::CompositorThreadHolder() + : mCompositorThread(CreateCompositorThread()) { + MOZ_ASSERT(NS_IsMainThread()); +} + +CompositorThreadHolder::~CompositorThreadHolder() { + sFinishedCompositorShutDown = true; +} + +/* static */ already_AddRefed +CompositorThreadHolder::CreateCompositorThread() { + MOZ_ASSERT(NS_IsMainThread()); + + MOZ_ASSERT(!sCompositorThreadHolder, + "The compositor thread has already been started!"); + + nsCOMPtr compositorThread; + nsresult rv = NS_NewNamedThread( + "Compositor", getter_AddRefs(compositorThread), + NS_NewRunnableFunction( + "CompositorThreadHolder::CompositorThreadHolderSetup", []() { + sBackgroundHangMonitor = new mozilla::BackgroundHangMonitor( + "Compositor", + /* Timeout values are powers-of-two to enable us get better + data. 128ms is chosen for transient hangs because 8Hz should + be the minimally acceptable goal for Compositor + responsiveness (normal goal is 60Hz). */ + 128, + /* 2048ms is chosen for permanent hangs because it's longer than + * most Compositor hangs seen in the wild, but is short enough + * to not miss getting native hang stacks. */ + 2048); + nsCOMPtr thread = NS_GetCurrentThread(); + static_cast(thread.get())->SetUseHangMonitor(true); + })); + + if (NS_FAILED(rv)) { + return nullptr; + } + + CompositorBridgeParent::Setup(); + ImageBridgeParent::Setup(); + + return compositorThread.forget(); +} + +void CompositorThreadHolder::Start() { + MOZ_ASSERT(NS_IsMainThread(), "Should be on the main Thread!"); + MOZ_ASSERT(!sCompositorThreadHolder, + "The compositor thread has already been started!"); + + // We unset the holder instead of asserting because failing to start the + // compositor thread may not be a fatal error. As long as this succeeds in + // either the GPU process or the UI process, the user will have a usable + // browser. If we get neither, it will crash as soon as we try to post to the + // compositor thread for the first time. + sCompositorThreadHolder = new CompositorThreadHolder(); + if (!sCompositorThreadHolder->GetCompositorThread()) { + gfxCriticalNote << "Compositor thread not started (" + << XRE_IsParentProcess() << ")"; + sCompositorThreadHolder = nullptr; + } +} + +void CompositorThreadHolder::Shutdown() { + MOZ_ASSERT(NS_IsMainThread(), "Should be on the main Thread!"); + if (!sCompositorThreadHolder) { + // We've already shutdown or never started. + return; + } + + ImageBridgeParent::Shutdown(); + gfx::VRManagerParent::Shutdown(); + MediaSystemResourceService::Shutdown(); + CompositorManagerParent::Shutdown(); + CanvasTranslator::Shutdown(); + + // Ensure there are no pending tasks that would cause an access to the + // thread's HangMonitor. APZ and Canvas can keep a reference to the compositor + // thread and may continue to dispatch tasks on it as the system shuts down. + CompositorThread()->Dispatch(NS_NewRunnableFunction( + "CompositorThreadHolder::Shutdown", + [compositorThreadHolder = + RefPtr(sCompositorThreadHolder), + backgroundHangMonitor = UniquePtr( + sBackgroundHangMonitor)]() { + nsCOMPtr thread = NS_GetCurrentThread(); + static_cast(thread.get())->SetUseHangMonitor(false); + })); + + sCompositorThreadHolder = nullptr; + sBackgroundHangMonitor = nullptr; + + SpinEventLoopUntil([&]() { + bool finished = sFinishedCompositorShutDown; + return finished; + }); + + // At this point, the CompositorThreadHolder instance will have been + // destroyed, but the compositor thread itself may still be running due to + // APZ/Canvas code holding a reference to the underlying + // nsIThread/nsISerialEventTarget. Any tasks scheduled to run on the + // compositor thread earlier in this function will have been run to + // completion. + CompositorBridgeParent::FinishShutdown(); +} + +/* static */ +bool CompositorThreadHolder::IsInCompositorThread() { + if (!CompositorThread()) { + return false; + } + bool in = false; + MOZ_ALWAYS_SUCCEEDS(CompositorThread()->IsOnCurrentThread(&in)); + return in; +} + +} // namespace layers +} // namespace mozilla + +bool NS_IsInCompositorThread() { + return mozilla::layers::CompositorThreadHolder::IsInCompositorThread(); +} diff --git a/gfx/layers/ipc/CompositorThread.h b/gfx/layers/ipc/CompositorThread.h new file mode 100644 index 0000000000..2dea93e9d0 --- /dev/null +++ b/gfx/layers/ipc/CompositorThread.h @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef mozilla_layers_CompositorThread_h +#define mozilla_layers_CompositorThread_h + +#include "nsISupportsImpl.h" +#include "nsIThread.h" + +class nsISerialEventTarget; +class nsIThread; + +namespace mozilla { +namespace layers { + +class CompositorThreadHolder final { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_MAIN_THREAD( + CompositorThreadHolder) + + public: + CompositorThreadHolder(); + + nsISerialEventTarget* GetCompositorThread() const { + return mCompositorThread; + } + + static CompositorThreadHolder* GetSingleton(); + + static bool IsActive() { return !!GetSingleton(); } + + /** + * Creates the compositor thread and the global compositor map. + */ + static void Start(); + + /* + * Waits for all [CrossProcess]CompositorBridgeParents to shutdown and + * releases compositor-thread owned resources. + */ + static void Shutdown(); + + // Returns true if the calling thread is the compositor thread. + static bool IsInCompositorThread(); + + private: + ~CompositorThreadHolder(); + + nsCOMPtr mCompositorThread; + + static already_AddRefed CreateCompositorThread(); + + friend class CompositorBridgeParent; +}; + +nsISerialEventTarget* CompositorThread(); + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_CompositorThread_h diff --git a/gfx/layers/ipc/CompositorVsyncScheduler.cpp b/gfx/layers/ipc/CompositorVsyncScheduler.cpp new file mode 100644 index 0000000000..4662a1e3ae --- /dev/null +++ b/gfx/layers/ipc/CompositorVsyncScheduler.cpp @@ -0,0 +1,365 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/CompositorVsyncScheduler.h" + +#include // for fprintf, stdout +#include // for uint64_t +#include "base/task.h" // for CancelableTask, etc +#include "base/thread.h" // for Thread +#include "gfxPlatform.h" // for gfxPlatform +#ifdef MOZ_WIDGET_GTK +# include "gfxPlatformGtk.h" // for gfxPlatform +#endif +#include "mozilla/AutoRestore.h" // for AutoRestore +#include "mozilla/DebugOnly.h" // for DebugOnly +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/gfx/2D.h" // for DrawTarget +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/gfx/Rect.h" // for IntSize +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/layers/CompositorVsyncSchedulerOwner.h" +#include "mozilla/mozalloc.h" // for operator new, etc +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsDebug.h" // for NS_ASSERTION, etc +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "nsIWidget.h" // for nsIWidget +#include "nsThreadUtils.h" // for NS_IsMainThread +#include "mozilla/Telemetry.h" +#include "mozilla/VsyncDispatcher.h" +#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) +# include "VsyncSource.h" +#endif +#include "mozilla/widget/CompositorWidget.h" +#include "VRManager.h" + +namespace mozilla { + +namespace layers { + +using namespace mozilla::gfx; + +CompositorVsyncScheduler::Observer::Observer(CompositorVsyncScheduler* aOwner) + : mMutex("CompositorVsyncScheduler.Observer.Mutex"), mOwner(aOwner) {} + +CompositorVsyncScheduler::Observer::~Observer() { MOZ_ASSERT(!mOwner); } + +bool CompositorVsyncScheduler::Observer::NotifyVsync(const VsyncEvent& aVsync) { + MutexAutoLock lock(mMutex); + if (!mOwner) { + return false; + } + return mOwner->NotifyVsync(aVsync); +} + +void CompositorVsyncScheduler::Observer::Destroy() { + MutexAutoLock lock(mMutex); + mOwner = nullptr; +} + +CompositorVsyncScheduler::CompositorVsyncScheduler( + CompositorVsyncSchedulerOwner* aVsyncSchedulerOwner, + widget::CompositorWidget* aWidget) + : mVsyncSchedulerOwner(aVsyncSchedulerOwner), + mLastComposeTime(SampleTime::FromNow()), + mLastVsyncTime(TimeStamp::Now()), + mLastVsyncOutputTime(TimeStamp::Now()), + mIsObservingVsync(false), + mVsyncNotificationsSkipped(0), + mWidget(aWidget), + mCurrentCompositeTaskMonitor("CurrentCompositeTaskMonitor"), + mCurrentCompositeTask(nullptr), + mCurrentVRTaskMonitor("CurrentVRTaskMonitor"), + mCurrentVRTask(nullptr) { + mVsyncObserver = new Observer(this); + + // mAsapScheduling is set on the main thread during init, + // but is only accessed after on the compositor thread. + mAsapScheduling = + StaticPrefs::layers_offmainthreadcomposition_frame_rate() == 0 || + gfxPlatform::IsInLayoutAsapMode(); +} + +CompositorVsyncScheduler::~CompositorVsyncScheduler() { + MOZ_ASSERT(!mIsObservingVsync); + MOZ_ASSERT(!mVsyncObserver); + // The CompositorVsyncDispatcher is cleaned up before this in the + // nsBaseWidget, which stops vsync listeners + mVsyncSchedulerOwner = nullptr; +} + +void CompositorVsyncScheduler::Destroy() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + + if (!mVsyncObserver) { + // Destroy was already called on this object. + return; + } + UnobserveVsync(); + mVsyncObserver->Destroy(); + mVsyncObserver = nullptr; + + mCompositeRequestedAt = TimeStamp(); + CancelCurrentCompositeTask(); + CancelCurrentVRTask(); +} + +void CompositorVsyncScheduler::PostCompositeTask( + const VsyncEvent& aVsyncEvent) { + MonitorAutoLock lock(mCurrentCompositeTaskMonitor); + if (mCurrentCompositeTask == nullptr && CompositorThread()) { + RefPtr task = NewCancelableRunnableMethod( + "layers::CompositorVsyncScheduler::Composite", this, + &CompositorVsyncScheduler::Composite, aVsyncEvent); + mCurrentCompositeTask = task; + CompositorThread()->Dispatch(task.forget()); + } +} + +void CompositorVsyncScheduler::PostVRTask(TimeStamp aTimestamp) { + MonitorAutoLock lockVR(mCurrentVRTaskMonitor); + if (mCurrentVRTask == nullptr && CompositorThread()) { + RefPtr task = NewCancelableRunnableMethod( + "layers::CompositorVsyncScheduler::DispatchVREvents", this, + &CompositorVsyncScheduler::DispatchVREvents, aTimestamp); + mCurrentVRTask = task; + CompositorThread()->Dispatch(task.forget()); + } +} + +void CompositorVsyncScheduler::ScheduleComposition() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + if (!mVsyncObserver) { + // Destroy was already called on this object. + return; + } + + // Make a synthetic vsync event for the calls to PostCompositeTask below. + TimeStamp vsyncTime = TimeStamp::Now(); + TimeStamp outputTime = vsyncTime + mVsyncSchedulerOwner->GetVsyncInterval(); + VsyncEvent vsyncEvent(VsyncId(), vsyncTime, outputTime); + + if (mAsapScheduling) { + // Used only for performance testing purposes, and when recording/replaying + // to ensure that graphics are up to date. + PostCompositeTask(vsyncEvent); + } else { + if (!mCompositeRequestedAt) { + mCompositeRequestedAt = TimeStamp::Now(); + } + if (!mIsObservingVsync && mCompositeRequestedAt) { + ObserveVsync(); + // Starting to observe vsync is an async operation that goes + // through the main thread of the UI process. It's possible that + // we're blocking there waiting on a composite, so schedule an initial + // one now to get things started. + PostCompositeTask(vsyncEvent); + } + } +} + +bool CompositorVsyncScheduler::NotifyVsync(const VsyncEvent& aVsync) { + // Called from the vsync dispatch thread. When in the GPU Process, that's + // the same as the compositor thread. +#ifdef DEBUG +# ifdef MOZ_WAYLAND + // On Wayland, we dispatch vsync from the main thread, without a GPU process. + // To allow this, we skip the following asserts if we're currently utilizing + // the Wayland backend. The IsParentProcess guard is needed to ensure that + // we don't accidentally attempt to initialize the gfxPlatform in the GPU + // process on X11. + if (!XRE_IsParentProcess() || + !gfxPlatformGtk::GetPlatform()->IsWaylandDisplay()) +# endif // MOZ_WAYLAND + { + MOZ_ASSERT_IF(XRE_IsParentProcess(), + !CompositorThreadHolder::IsInCompositorThread()); + MOZ_ASSERT(!NS_IsMainThread()); + } + + MOZ_ASSERT_IF(XRE_GetProcessType() == GeckoProcessType_GPU, + CompositorThreadHolder::IsInCompositorThread()); +#endif // DEBUG + +#if defined(MOZ_WIDGET_ANDROID) + gfx::VRManager* vm = gfx::VRManager::Get(); + if (!vm->IsPresenting()) { + PostCompositeTask(aVsync); + } +#else + PostCompositeTask(aVsync); +#endif // defined(MOZ_WIDGET_ANDROID) + + PostVRTask(aVsync.mTime); + return true; +} + +void CompositorVsyncScheduler::CancelCurrentVRTask() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread() || + NS_IsMainThread()); + MonitorAutoLock lock(mCurrentVRTaskMonitor); + if (mCurrentVRTask) { + mCurrentVRTask->Cancel(); + mCurrentVRTask = nullptr; + } +} + +void CompositorVsyncScheduler::CancelCurrentCompositeTask() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread() || + NS_IsMainThread()); + MonitorAutoLock lock(mCurrentCompositeTaskMonitor); + if (mCurrentCompositeTask) { + mCurrentCompositeTask->Cancel(); + mCurrentCompositeTask = nullptr; + } +} + +void CompositorVsyncScheduler::Composite(const VsyncEvent& aVsyncEvent) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + MOZ_ASSERT(mVsyncSchedulerOwner); + + { // scope lock + MonitorAutoLock lock(mCurrentCompositeTaskMonitor); + mCurrentCompositeTask = nullptr; + } + + mLastVsyncTime = aVsyncEvent.mTime; + mLastVsyncOutputTime = aVsyncEvent.mOutputTime; + mLastVsyncId = aVsyncEvent.mId; + + if (!mAsapScheduling) { + // Some early exit conditions if we're not in ASAP mode + if (aVsyncEvent.mTime < mLastComposeTime.Time()) { + // We can sometimes get vsync timestamps that are in the past + // compared to the last compose with force composites. + // In those cases, wait until the next vsync; + return; + } + + if (mVsyncSchedulerOwner->IsPendingComposite()) { + // If previous composite is still on going, finish it and wait for the + // next vsync. + mVsyncSchedulerOwner->FinishPendingComposite(); + return; + } + } + + if (mCompositeRequestedAt || mAsapScheduling) { + mCompositeRequestedAt = TimeStamp(); + mLastComposeTime = SampleTime::FromVsync(aVsyncEvent.mTime); + + // Tell the owner to do a composite + mVsyncSchedulerOwner->CompositeToTarget(aVsyncEvent.mId, nullptr, nullptr); + + mVsyncNotificationsSkipped = 0; + + TimeDuration compositeFrameTotal = TimeStamp::Now() - aVsyncEvent.mTime; + mozilla::Telemetry::Accumulate( + mozilla::Telemetry::COMPOSITE_FRAME_ROUNDTRIP_TIME, + compositeFrameTotal.ToMilliseconds()); + } else if (mVsyncNotificationsSkipped++ > + StaticPrefs::gfx_vsync_compositor_unobserve_count_AtStartup()) { + UnobserveVsync(); + } +} + +void CompositorVsyncScheduler::ForceComposeToTarget(gfx::DrawTarget* aTarget, + const IntRect* aRect) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + + /** + * bug 1138502 - There are cases such as during long-running window resizing + * events where we receive many force-composites. We also continue to get + * vsync notifications. Because the force-composites trigger compositing and + * clear the mCompositeRequestedAt timestamp, the vsync notifications will not + * need to do anything and so will increment the mVsyncNotificationsSkipped + * counter to indicate the vsync was ignored. If this happens enough times, we + * will disable listening for vsync entirely. On the next force-composite we + * will enable listening for vsync again, and continued force-composites and + * vsyncs will cause oscillation between observing vsync and not. On some + * platforms, enabling/disabling vsync is not free and this oscillating + * behavior causes a performance hit. In order to avoid this problem, we reset + * the mVsyncNotificationsSkipped counter to keep vsync enabled. + */ + mVsyncNotificationsSkipped = 0; + + mLastComposeTime = SampleTime::FromNow(); + MOZ_ASSERT(mVsyncSchedulerOwner); + mVsyncSchedulerOwner->CompositeToTarget(VsyncId(), aTarget, aRect); +} + +bool CompositorVsyncScheduler::NeedsComposite() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + return (bool)mCompositeRequestedAt; +} + +bool CompositorVsyncScheduler::FlushPendingComposite() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + if (mCompositeRequestedAt) { + CancelCurrentCompositeTask(); + ForceComposeToTarget(nullptr, nullptr); + return true; + } + return false; +} + +void CompositorVsyncScheduler::ObserveVsync() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + mWidget->ObserveVsync(mVsyncObserver); + mIsObservingVsync = true; +} + +void CompositorVsyncScheduler::UnobserveVsync() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + mWidget->ObserveVsync(nullptr); + mIsObservingVsync = false; +} + +void CompositorVsyncScheduler::DispatchVREvents(TimeStamp aVsyncTimestamp) { + { + MonitorAutoLock lock(mCurrentVRTaskMonitor); + mCurrentVRTask = nullptr; + } + // This only allows to be called by CompositorVsyncScheduler::PostVRTask() + // When the process is going to shutdown, the runnable has chance to be + // executed by other threads, we only want it to be run in the compositor + // thread. + if (!CompositorThreadHolder::IsInCompositorThread()) { + return; + } + + VRManager* vm = VRManager::Get(); + vm->NotifyVsync(aVsyncTimestamp); +} + +const SampleTime& CompositorVsyncScheduler::GetLastComposeTime() const { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + return mLastComposeTime; +} + +const TimeStamp& CompositorVsyncScheduler::GetLastVsyncTime() const { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + return mLastVsyncTime; +} + +const TimeStamp& CompositorVsyncScheduler::GetLastVsyncOutputTime() const { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + return mLastVsyncOutputTime; +} + +const VsyncId& CompositorVsyncScheduler::GetLastVsyncId() const { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + return mLastVsyncId; +} + +void CompositorVsyncScheduler::UpdateLastComposeTime() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + mLastComposeTime = SampleTime::FromNow(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/CompositorVsyncScheduler.h b/gfx/layers/ipc/CompositorVsyncScheduler.h new file mode 100644 index 0000000000..67e3d73b03 --- /dev/null +++ b/gfx/layers/ipc/CompositorVsyncScheduler.h @@ -0,0 +1,173 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_CompositorVsyncScheduler_h +#define mozilla_layers_CompositorVsyncScheduler_h + +#include // for uint64_t + +#include "mozilla/Attributes.h" // for override +#include "mozilla/Monitor.h" // for Monitor +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/TimeStamp.h" // for TimeStamp +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/layers/SampleTime.h" +#include "mozilla/VsyncDispatcher.h" +#include "mozilla/widget/CompositorWidget.h" +#include "nsISupportsImpl.h" + +namespace mozilla { + +class CancelableRunnable; +class Runnable; + +namespace gfx { +class DrawTarget; +} // namespace gfx + +namespace layers { + +class CompositorVsyncSchedulerOwner; + +/** + * Manages the vsync (de)registration and tracking on behalf of the + * compositor when it need to paint. + * Turns vsync notifications into scheduled composites. + **/ +class CompositorVsyncScheduler { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CompositorVsyncScheduler) + + public: + CompositorVsyncScheduler(CompositorVsyncSchedulerOwner* aVsyncSchedulerOwner, + widget::CompositorWidget* aWidget); + + /** + * Notify this class of a vsync. This will trigger a composite if one is + * needed. This must be called from the vsync dispatch thread. + */ + bool NotifyVsync(const VsyncEvent& aVsync); + + /** + * Do cleanup. This must be called on the compositor thread. + */ + void Destroy(); + + /** + * Notify this class that a composition is needed. This will trigger a + * composition soon (likely at the next vsync). This must be called on the + * compositor thread. + */ + void ScheduleComposition(); + + /** + * Cancel any composite task that has been scheduled but hasn't run yet. + */ + void CancelCurrentCompositeTask(); + + /** + * Check if a composite is pending. This is generally true between a call + * to ScheduleComposition() and the time the composite happens. + */ + bool NeedsComposite(); + + /** + * Force a composite to happen right away, without waiting for the next vsync. + * This must be called on the compositor thread. + */ + void ForceComposeToTarget(gfx::DrawTarget* aTarget, + const gfx::IntRect* aRect); + + /** + * If a composite is pending, force it to trigger right away. This must be + * called on the compositor thread. Returns true if there was a composite + * flushed. + */ + bool FlushPendingComposite(); + + /** + * Return the vsync timestamp of the last or ongoing composite. Must be called + * on the compositor thread. + */ + const SampleTime& GetLastComposeTime() const; + + /** + * Return the vsync timestamp and id of the most recently received + * vsync event. Must be called on the compositor thread. + */ + const TimeStamp& GetLastVsyncTime() const; + const TimeStamp& GetLastVsyncOutputTime() const; + const VsyncId& GetLastVsyncId() const; + + /** + * Update LastCompose TimeStamp to current timestamp. + * The function is typically used when composition is handled outside the + * CompositorVsyncScheduler. + */ + void UpdateLastComposeTime(); + + private: + virtual ~CompositorVsyncScheduler(); + + // Post a task to run Composite() on the compositor thread, if there isn't + // such a task already queued. Can be called from any thread. + void PostCompositeTask(const VsyncEvent& aVsyncEvent); + + // Post a task to run DispatchVREvents() on the VR thread, if there isn't + // such a task already queued. Can be called from any thread. + void PostVRTask(TimeStamp aTimestamp); + + /** + * Cancel any VR task that has been scheduled but hasn't run yet. + */ + void CancelCurrentVRTask(); + + // This gets run at vsync time and "does" a composite (which really means + // update internal state and call the owner to do the composite). + void Composite(const VsyncEvent& aVsyncEvent); + + void ObserveVsync(); + void UnobserveVsync(); + + void DispatchVREvents(TimeStamp aVsyncTimestamp); + + class Observer final : public VsyncObserver { + public: + explicit Observer(CompositorVsyncScheduler* aOwner); + bool NotifyVsync(const VsyncEvent& aVsync) override; + void Destroy(); + + private: + virtual ~Observer(); + + Mutex mMutex; + // Hold raw pointer to avoid mutual reference. + CompositorVsyncScheduler* mOwner; + }; + + CompositorVsyncSchedulerOwner* mVsyncSchedulerOwner; + SampleTime mLastComposeTime; + TimeStamp mLastVsyncTime; + TimeStamp mLastVsyncOutputTime; + VsyncId mLastVsyncId; + + bool mAsapScheduling; + bool mIsObservingVsync; + TimeStamp mCompositeRequestedAt; + int32_t mVsyncNotificationsSkipped; + widget::CompositorWidget* mWidget; + RefPtr mVsyncObserver; + + mozilla::Monitor mCurrentCompositeTaskMonitor; + RefPtr mCurrentCompositeTask; + + mozilla::Monitor mCurrentVRTaskMonitor; + RefPtr mCurrentVRTask; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_CompositorVsyncScheduler_h diff --git a/gfx/layers/ipc/CompositorVsyncSchedulerOwner.h b/gfx/layers/ipc/CompositorVsyncSchedulerOwner.h new file mode 100644 index 0000000000..1691f6535a --- /dev/null +++ b/gfx/layers/ipc/CompositorVsyncSchedulerOwner.h @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_CompositorVsyncSchedulerOwner_h +#define mozilla_layers_CompositorVsyncSchedulerOwner_h + +#include "mozilla/VsyncDispatcher.h" + +namespace mozilla { + +namespace gfx { +class DrawTarget; +} // namespace gfx + +namespace layers { + +class CompositorVsyncSchedulerOwner { + public: + virtual bool IsPendingComposite() = 0; + virtual void FinishPendingComposite() = 0; + virtual void CompositeToTarget(VsyncId aId, gfx::DrawTarget* aTarget, + const gfx::IntRect* aRect = nullptr) = 0; + virtual TimeDuration GetVsyncInterval() const = 0; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_CompositorVsyncSchedulerOwner_h diff --git a/gfx/layers/ipc/ContentCompositorBridgeParent.cpp b/gfx/layers/ipc/ContentCompositorBridgeParent.cpp new file mode 100644 index 0000000000..793919e7f3 --- /dev/null +++ b/gfx/layers/ipc/ContentCompositorBridgeParent.cpp @@ -0,0 +1,753 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/ContentCompositorBridgeParent.h" + +#include // for uint64_t + +#include "LayerTransactionParent.h" // for LayerTransactionParent +#include "apz/src/APZCTreeManager.h" // for APZCTreeManager +#include "gfxUtils.h" +#ifdef XP_WIN +# include "mozilla/gfx/DeviceManagerDx.h" // for DeviceManagerDx +# include "mozilla/layers/ImageDataSerializer.h" +#endif +#include "mozilla/D3DMessageUtils.h" // for DxgiAdapterDesc +#include "mozilla/dom/WebGLParent.h" +#include "mozilla/ipc/Transport.h" // for Transport +#include "mozilla/layers/AnimationHelper.h" // for CompositorAnimationStorage +#include "mozilla/layers/APZCTreeManagerParent.h" // for APZCTreeManagerParent +#include "mozilla/layers/APZUpdater.h" // for APZUpdater +#include "mozilla/layers/AsyncCompositionManager.h" +#include "mozilla/layers/CompositorOptions.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/layers/LayerManagerComposite.h" +#include "mozilla/layers/LayerTreeOwnerTracker.h" +#include "mozilla/layers/PLayerTransactionParent.h" +#include "mozilla/layers/RemoteContentController.h" +#include "mozilla/layers/WebRenderBridgeParent.h" +#include "mozilla/layers/AsyncImagePipelineManager.h" +#include "mozilla/webgpu/WebGPUParent.h" +#include "mozilla/mozalloc.h" // for operator new, etc +#include "nsDebug.h" // for NS_ASSERTION, etc +#include "nsTArray.h" // for nsTArray +#include "nsXULAppAPI.h" // for XRE_GetIOMessageLoop +#include "mozilla/Unused.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Telemetry.h" +#ifdef MOZ_GECKO_PROFILER +# include "mozilla/BaseProfilerMarkerTypes.h" +#endif + +namespace mozilla { + +namespace layers { + +// defined in CompositorBridgeParent.cpp +typedef std::map LayerTreeMap; +extern LayerTreeMap sIndirectLayerTrees; +extern StaticAutoPtr sIndirectLayerTreesLock; +void UpdateIndirectTree(LayersId aId, Layer* aRoot, + const TargetConfig& aTargetConfig); +void EraseLayerState(LayersId aId); + +mozilla::ipc::IPCResult +ContentCompositorBridgeParent::RecvRequestNotifyAfterRemotePaint() { + mNotifyAfterRemotePaint = true; + return IPC_OK(); +} + +void ContentCompositorBridgeParent::ActorDestroy(ActorDestroyReason aWhy) { + mCanSend = false; + + // We must keep this object alive untill the code handling message + // reception is finished on this thread. + GetCurrentSerialEventTarget()->Dispatch(NewRunnableMethod( + "layers::ContentCompositorBridgeParent::DeferredDestroy", this, + &ContentCompositorBridgeParent::DeferredDestroy)); +} + +PLayerTransactionParent* +ContentCompositorBridgeParent::AllocPLayerTransactionParent( + const nsTArray&, const LayersId& aId) { + MOZ_ASSERT(aId.IsValid()); + + // Check to see if this child process has access to this layer tree. + if (!LayerTreeOwnerTracker::Get()->IsMapped(aId, OtherPid())) { + NS_ERROR( + "Unexpected layers id in AllocPLayerTransactionParent; dropping " + "message..."); + return nullptr; + } + + MonitorAutoLock lock(*sIndirectLayerTreesLock); + + CompositorBridgeParent::LayerTreeState* state = nullptr; + LayerTreeMap::iterator itr = sIndirectLayerTrees.find(aId); + if (sIndirectLayerTrees.end() != itr) { + state = &itr->second; + } + + if (state && state->mLayerManager) { + state->mContentCompositorBridgeParent = this; + HostLayerManager* lm = state->mLayerManager; + CompositorAnimationStorage* animStorage = + state->mParent ? state->mParent->GetAnimationStorage() : nullptr; + TimeDuration vsyncRate = + state->mParent ? state->mParent->GetVsyncInterval() : TimeDuration(); + LayerTransactionParent* p = + new LayerTransactionParent(lm, this, animStorage, aId, vsyncRate); + p->AddIPDLReference(); + sIndirectLayerTrees[aId].mLayerTree = p; + return p; + } + + NS_WARNING("Created child without a matching parent?"); + LayerTransactionParent* p = new LayerTransactionParent( + /* aManager */ nullptr, this, /* aAnimStorage */ nullptr, aId, + TimeDuration()); + p->AddIPDLReference(); + return p; +} + +bool ContentCompositorBridgeParent::DeallocPLayerTransactionParent( + PLayerTransactionParent* aLayers) { + LayerTransactionParent* slp = static_cast(aLayers); + EraseLayerState(slp->GetId()); + static_cast(aLayers)->ReleaseIPDLReference(); + return true; +} + +PAPZCTreeManagerParent* +ContentCompositorBridgeParent::AllocPAPZCTreeManagerParent( + const LayersId& aLayersId) { + // Check to see if this child process has access to this layer tree. + if (!LayerTreeOwnerTracker::Get()->IsMapped(aLayersId, OtherPid())) { + NS_ERROR( + "Unexpected layers id in AllocPAPZCTreeManagerParent; dropping " + "message..."); + return nullptr; + } + + MonitorAutoLock lock(*sIndirectLayerTreesLock); + CompositorBridgeParent::LayerTreeState& state = + sIndirectLayerTrees[aLayersId]; + + // If the widget has shutdown its compositor, we may not have had a chance yet + // to unmap our layers id, and we could get here without a parent compositor. + // In this case return an empty APZCTM. + if (!state.mParent) { + // Note: we immediately call ClearTree since otherwise the APZCTM will + // retain a reference to itself, through the checkerboard observer. + LayersId dummyId{0}; + const bool useWebRender = false; + RefPtr temp = new APZCTreeManager(dummyId, useWebRender); + RefPtr tempUpdater = new APZUpdater(temp, useWebRender); + tempUpdater->ClearTree(dummyId); + return new APZCTreeManagerParent(aLayersId, temp, tempUpdater); + } + + state.mParent->AllocateAPZCTreeManagerParent(lock, aLayersId, state); + return state.mApzcTreeManagerParent; +} +bool ContentCompositorBridgeParent::DeallocPAPZCTreeManagerParent( + PAPZCTreeManagerParent* aActor) { + APZCTreeManagerParent* parent = static_cast(aActor); + + MonitorAutoLock lock(*sIndirectLayerTreesLock); + auto iter = sIndirectLayerTrees.find(parent->GetLayersId()); + if (iter != sIndirectLayerTrees.end()) { + CompositorBridgeParent::LayerTreeState& state = iter->second; + MOZ_ASSERT(state.mApzcTreeManagerParent == parent); + state.mApzcTreeManagerParent = nullptr; + } + + delete parent; + + return true; +} + +PAPZParent* ContentCompositorBridgeParent::AllocPAPZParent( + const LayersId& aLayersId) { + // Check to see if this child process has access to this layer tree. + if (!LayerTreeOwnerTracker::Get()->IsMapped(aLayersId, OtherPid())) { + NS_ERROR("Unexpected layers id in AllocPAPZParent; dropping message..."); + return nullptr; + } + + RemoteContentController* controller = new RemoteContentController(); + + // Increment the controller's refcount before we return it. This will keep the + // controller alive until it is released by IPDL in DeallocPAPZParent. + controller->AddRef(); + + MonitorAutoLock lock(*sIndirectLayerTreesLock); + CompositorBridgeParent::LayerTreeState& state = + sIndirectLayerTrees[aLayersId]; + MOZ_ASSERT(!state.mController); + state.mController = controller; + + return controller; +} + +bool ContentCompositorBridgeParent::DeallocPAPZParent(PAPZParent* aActor) { + RemoteContentController* controller = + static_cast(aActor); + controller->Release(); + return true; +} + +PWebRenderBridgeParent* +ContentCompositorBridgeParent::AllocPWebRenderBridgeParent( + const wr::PipelineId& aPipelineId, const LayoutDeviceIntSize& aSize, + const WindowKind& aWindowKind) { + LayersId layersId = wr::AsLayersId(aPipelineId); + // Check to see if this child process has access to this layer tree. + if (!LayerTreeOwnerTracker::Get()->IsMapped(layersId, OtherPid())) { + NS_ERROR( + "Unexpected layers id in AllocPWebRenderBridgeParent; dropping " + "message..."); + return nullptr; + } + + RefPtr cbp = nullptr; + RefPtr root = nullptr; + + { // scope lock + MonitorAutoLock lock(*sIndirectLayerTreesLock); + MOZ_ASSERT(sIndirectLayerTrees.find(layersId) != sIndirectLayerTrees.end()); + MOZ_ASSERT(sIndirectLayerTrees[layersId].mWrBridge == nullptr); + cbp = sIndirectLayerTrees[layersId].mParent; + if (cbp) { + root = sIndirectLayerTrees[cbp->RootLayerTreeId()].mWrBridge; + } + } + + RefPtr api; + if (root) { + api = root->GetWebRenderAPI(); + } + + if (!root || !api) { + // This could happen when this function is called after + // CompositorBridgeParent destruction. This was observed during Tab move + // between different windows. + NS_WARNING( + nsPrintfCString("Created child without a matching parent? root %p", + root.get()) + .get()); + nsCString error("NO_PARENT"); + WebRenderBridgeParent* parent = + WebRenderBridgeParent::CreateDestroyed(aPipelineId, std::move(error)); + parent->AddRef(); // IPDL reference + return parent; + } + + api = api->Clone(); + RefPtr holder = root->AsyncImageManager(); + WebRenderBridgeParent* parent = new WebRenderBridgeParent( + this, aPipelineId, nullptr, root->CompositorScheduler(), std::move(api), + std::move(holder), cbp->GetVsyncInterval()); + parent->AddRef(); // IPDL reference + + { // scope lock + MonitorAutoLock lock(*sIndirectLayerTreesLock); + sIndirectLayerTrees[layersId].mContentCompositorBridgeParent = this; + sIndirectLayerTrees[layersId].mWrBridge = parent; + } + + return parent; +} + +bool ContentCompositorBridgeParent::DeallocPWebRenderBridgeParent( + PWebRenderBridgeParent* aActor) { + WebRenderBridgeParent* parent = static_cast(aActor); + EraseLayerState(wr::AsLayersId(parent->PipelineId())); + parent->Release(); // IPDL reference + return true; +} + +webgpu::PWebGPUParent* ContentCompositorBridgeParent::AllocPWebGPUParent() { + webgpu::WebGPUParent* parent = new webgpu::WebGPUParent(); + parent->AddRef(); // IPDL reference + return parent; +} + +bool ContentCompositorBridgeParent::DeallocPWebGPUParent( + webgpu::PWebGPUParent* aActor) { + webgpu::WebGPUParent* parent = static_cast(aActor); + parent->Release(); // IPDL reference + return true; +} + +mozilla::ipc::IPCResult ContentCompositorBridgeParent::RecvNotifyChildCreated( + const LayersId& child, CompositorOptions* aOptions) { + MonitorAutoLock lock(*sIndirectLayerTreesLock); + for (LayerTreeMap::iterator it = sIndirectLayerTrees.begin(); + it != sIndirectLayerTrees.end(); it++) { + CompositorBridgeParent::LayerTreeState* lts = &it->second; + if (lts->mParent && lts->mContentCompositorBridgeParent == this) { + lts->mParent->NotifyChildCreated(child); + *aOptions = lts->mParent->GetOptions(); + return IPC_OK(); + } + } + return IPC_FAIL_NO_REASON(this); +} + +mozilla::ipc::IPCResult +ContentCompositorBridgeParent::RecvMapAndNotifyChildCreated( + const LayersId& child, const base::ProcessId& pid, + CompositorOptions* aOptions) { + // This can only be called from the browser process, as the mapping + // ensures proper window ownership of layer trees. + return IPC_FAIL_NO_REASON(this); +} + +mozilla::ipc::IPCResult ContentCompositorBridgeParent::RecvCheckContentOnlyTDR( + const uint32_t& sequenceNum, bool* isContentOnlyTDR) { + *isContentOnlyTDR = false; +#ifdef XP_WIN + gfx::ContentDeviceData compositor; + + gfx::DeviceManagerDx* dm = gfx::DeviceManagerDx::Get(); + + // Check that the D3D11 device sequence numbers match. + gfx::D3D11DeviceStatus status; + dm->ExportDeviceInfo(&status); + + if (sequenceNum == status.sequenceNumber() && !dm->HasDeviceReset()) { + *isContentOnlyTDR = true; + } + +#endif + return IPC_OK(); +}; + +void ContentCompositorBridgeParent::ShadowLayersUpdated( + LayerTransactionParent* aLayerTree, const TransactionInfo& aInfo, + bool aHitTestUpdate) { + LayersId id = aLayerTree->GetId(); + + MOZ_ASSERT(id.IsValid()); + + CompositorBridgeParent::LayerTreeState* state = + CompositorBridgeParent::GetIndirectShadowTree(id); + if (!state) { + return; + } + MOZ_ASSERT(state->mParent); + state->mParent->ScheduleRotationOnCompositorThread(aInfo.targetConfig(), + aInfo.isFirstPaint()); + + Layer* shadowRoot = aLayerTree->GetRoot(); + if (shadowRoot) { + CompositorBridgeParent::SetShadowProperties(shadowRoot); + } + UpdateIndirectTree(id, shadowRoot, aInfo.targetConfig()); + + // Cache the plugin data for this remote layer tree + state->mPluginData = aInfo.plugins().Clone(); + state->mUpdatedPluginDataAvailable = true; + + state->mParent->NotifyShadowTreeTransaction( + id, aInfo.isFirstPaint(), aInfo.focusTarget(), aInfo.scheduleComposite(), + aInfo.paintSequenceNumber(), aInfo.isRepeatTransaction(), aHitTestUpdate); + + // Send the 'remote paint ready' message to the content thread if it has + // already asked. + if (mNotifyAfterRemotePaint) { + Unused << SendRemotePaintIsReady(); + mNotifyAfterRemotePaint = false; + } + + if (aLayerTree->ShouldParentObserveEpoch()) { + // Note that we send this through the window compositor, since this needs + // to reach the widget owning the tab. + Unused << state->mParent->SendObserveLayersUpdate( + id, aLayerTree->GetChildEpoch(), true); + } + + auto endTime = TimeStamp::Now(); +#ifdef MOZ_GECKO_PROFILER + if (profiler_can_accept_markers()) { + profiler_add_marker( + "CONTENT_FULL_PAINT_TIME", geckoprofiler::category::GRAPHICS, + MarkerTiming::Interval(aInfo.transactionStart(), endTime), + baseprofiler::markers::ContentBuildMarker{}); + } +#endif + Telemetry::Accumulate( + Telemetry::CONTENT_FULL_PAINT_TIME, + static_cast( + (endTime - aInfo.transactionStart()).ToMilliseconds())); + + RegisterPayloads(aLayerTree, aInfo.payload()); + + aLayerTree->SetPendingTransactionId( + aInfo.id(), aInfo.vsyncId(), aInfo.vsyncStart(), aInfo.refreshStart(), + aInfo.transactionStart(), endTime, aInfo.containsSVG(), aInfo.url(), + aInfo.fwdTime()); +} + +void ContentCompositorBridgeParent::DidCompositeLocked( + LayersId aId, const VsyncId& aVsyncId, TimeStamp& aCompositeStart, + TimeStamp& aCompositeEnd) { + sIndirectLayerTreesLock->AssertCurrentThreadOwns(); + if (LayerTransactionParent* layerTree = sIndirectLayerTrees[aId].mLayerTree) { + TransactionId transactionId = + layerTree->FlushTransactionId(aVsyncId, aCompositeEnd); + if (transactionId.IsValid()) { + Unused << SendDidComposite(aId, transactionId, aCompositeStart, + aCompositeEnd); + } + } else if (sIndirectLayerTrees[aId].mWrBridge) { + MOZ_ASSERT(false); // this should never get called for a WR compositor + } +} + +void ContentCompositorBridgeParent::ScheduleComposite( + LayerTransactionParent* aLayerTree) { + LayersId id = aLayerTree->GetId(); + MOZ_ASSERT(id.IsValid()); + CompositorBridgeParent* parent; + { // scope lock + MonitorAutoLock lock(*sIndirectLayerTreesLock); + parent = sIndirectLayerTrees[id].mParent; + } + if (parent) { + parent->ScheduleComposite(aLayerTree); + } +} + +void ContentCompositorBridgeParent::NotifyClearCachedResources( + LayerTransactionParent* aLayerTree) { + LayersId id = aLayerTree->GetId(); + MOZ_ASSERT(id.IsValid()); + + const CompositorBridgeParent::LayerTreeState* state = + CompositorBridgeParent::GetIndirectShadowTree(id); + if (state && state->mParent) { + // Note that we send this through the window compositor, since this needs + // to reach the widget owning the tab. + Unused << state->mParent->SendObserveLayersUpdate( + id, aLayerTree->GetChildEpoch(), false); + } +} + +bool ContentCompositorBridgeParent::SetTestSampleTime(const LayersId& aId, + const TimeStamp& aTime) { + MOZ_ASSERT(aId.IsValid()); + const CompositorBridgeParent::LayerTreeState* state = + CompositorBridgeParent::GetIndirectShadowTree(aId); + if (!state) { + return false; + } + + MOZ_ASSERT(state->mParent); + return state->mParent->SetTestSampleTime(aId, aTime); +} + +void ContentCompositorBridgeParent::LeaveTestMode(const LayersId& aId) { + MOZ_ASSERT(aId.IsValid()); + const CompositorBridgeParent::LayerTreeState* state = + CompositorBridgeParent::GetIndirectShadowTree(aId); + if (!state) { + return; + } + + MOZ_ASSERT(state->mParent); + state->mParent->LeaveTestMode(aId); +} + +void ContentCompositorBridgeParent::ApplyAsyncProperties( + LayerTransactionParent* aLayerTree, TransformsToSkip aSkip) { + LayersId id = aLayerTree->GetId(); + MOZ_ASSERT(id.IsValid()); + const CompositorBridgeParent::LayerTreeState* state = + CompositorBridgeParent::GetIndirectShadowTree(id); + if (!state) { + return; + } + + MOZ_ASSERT(state->mParent); + state->mParent->ApplyAsyncProperties(aLayerTree, aSkip); +} + +void ContentCompositorBridgeParent::SetTestAsyncScrollOffset( + const LayersId& aLayersId, const ScrollableLayerGuid::ViewID& aScrollId, + const CSSPoint& aPoint) { + MOZ_ASSERT(aLayersId.IsValid()); + const CompositorBridgeParent::LayerTreeState* state = + CompositorBridgeParent::GetIndirectShadowTree(aLayersId); + if (!state) { + return; + } + + MOZ_ASSERT(state->mParent); + state->mParent->SetTestAsyncScrollOffset(aLayersId, aScrollId, aPoint); +} + +void ContentCompositorBridgeParent::SetTestAsyncZoom( + const LayersId& aLayersId, const ScrollableLayerGuid::ViewID& aScrollId, + const LayerToParentLayerScale& aZoom) { + MOZ_ASSERT(aLayersId.IsValid()); + const CompositorBridgeParent::LayerTreeState* state = + CompositorBridgeParent::GetIndirectShadowTree(aLayersId); + if (!state) { + return; + } + + MOZ_ASSERT(state->mParent); + state->mParent->SetTestAsyncZoom(aLayersId, aScrollId, aZoom); +} + +void ContentCompositorBridgeParent::FlushApzRepaints( + const LayersId& aLayersId) { + MOZ_ASSERT(aLayersId.IsValid()); + const CompositorBridgeParent::LayerTreeState* state = + CompositorBridgeParent::GetIndirectShadowTree(aLayersId); + if (!state || !state->mParent) { + return; + } + + state->mParent->FlushApzRepaints(aLayersId); +} + +void ContentCompositorBridgeParent::GetAPZTestData(const LayersId& aLayersId, + APZTestData* aOutData) { + MOZ_ASSERT(aLayersId.IsValid()); + const CompositorBridgeParent::LayerTreeState* state = + CompositorBridgeParent::GetIndirectShadowTree(aLayersId); + if (!state || !state->mParent) { + return; + } + + state->mParent->GetAPZTestData(aLayersId, aOutData); +} + +void ContentCompositorBridgeParent::GetFrameUniformity( + const LayersId& aLayersId, FrameUniformityData* aOutData) { + MOZ_ASSERT(aLayersId.IsValid()); + const CompositorBridgeParent::LayerTreeState* state = + CompositorBridgeParent::GetIndirectShadowTree(aLayersId); + if (!state || !state->mParent) { + return; + } + + state->mParent->GetFrameUniformity(aLayersId, aOutData); +} + +void ContentCompositorBridgeParent::SetConfirmedTargetAPZC( + const LayersId& aLayersId, const uint64_t& aInputBlockId, + nsTArray&& aTargets) { + MOZ_ASSERT(aLayersId.IsValid()); + const CompositorBridgeParent::LayerTreeState* state = + CompositorBridgeParent::GetIndirectShadowTree(aLayersId); + if (!state || !state->mParent) { + return; + } + + state->mParent->SetConfirmedTargetAPZC(aLayersId, aInputBlockId, + std::move(aTargets)); +} + +AsyncCompositionManager* ContentCompositorBridgeParent::GetCompositionManager( + LayerTransactionParent* aLayerTree) { + LayersId id = aLayerTree->GetId(); + const CompositorBridgeParent::LayerTreeState* state = + CompositorBridgeParent::GetIndirectShadowTree(id); + if (!state) { + return nullptr; + } + + MOZ_ASSERT(state->mParent); + return state->mParent->GetCompositionManager(aLayerTree); +} + +void ContentCompositorBridgeParent::DeferredDestroy() { mSelfRef = nullptr; } + +ContentCompositorBridgeParent::~ContentCompositorBridgeParent() { + MOZ_ASSERT(XRE_GetIOMessageLoop()); +} + +PTextureParent* ContentCompositorBridgeParent::AllocPTextureParent( + const SurfaceDescriptor& aSharedData, const ReadLockDescriptor& aReadLock, + const LayersBackend& aLayersBackend, const TextureFlags& aFlags, + const LayersId& aId, const uint64_t& aSerial, + const wr::MaybeExternalImageId& aExternalImageId) { + CompositorBridgeParent::LayerTreeState* state = nullptr; + + LayerTreeMap::iterator itr = sIndirectLayerTrees.find(aId); + if (sIndirectLayerTrees.end() != itr) { + state = &itr->second; + } + + TextureFlags flags = aFlags; + + LayersBackend actualBackend = LayersBackend::LAYERS_NONE; + if (state && state->mLayerManager) { + actualBackend = state->mLayerManager->GetBackendType(); + } + + if (!state) { + // The compositor was recreated, and we're receiving layers updates for a + // a layer manager that will soon be discarded or invalidated. We can't + // return null because this will mess up deserialization later and we'll + // kill the content process. Instead, we signal that the underlying + // TextureHost should not attempt to access the compositor. + flags |= TextureFlags::INVALID_COMPOSITOR; + } else if (actualBackend != LayersBackend::LAYERS_NONE && + aLayersBackend != actualBackend) { + gfxDevCrash(gfx::LogReason::PAllocTextureBackendMismatch) + << "Texture backend is wrong"; + } + + return TextureHost::CreateIPDLActor(this, aSharedData, aReadLock, + aLayersBackend, aFlags, aSerial, + aExternalImageId); +} + +bool ContentCompositorBridgeParent::DeallocPTextureParent( + PTextureParent* actor) { + return TextureHost::DestroyIPDLActor(actor); +} + +mozilla::ipc::IPCResult ContentCompositorBridgeParent::RecvInitPCanvasParent( + Endpoint&& aEndpoint) { + MOZ_RELEASE_ASSERT(!mCanvasTranslator, + "mCanvasTranslator must be released before recreating."); + + mCanvasTranslator = CanvasTranslator::Create(std::move(aEndpoint)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +ContentCompositorBridgeParent::RecvReleasePCanvasParent() { + MOZ_RELEASE_ASSERT(mCanvasTranslator, + "mCanvasTranslator hasn't been created."); + + mCanvasTranslator = nullptr; + return IPC_OK(); +} + +UniquePtr +ContentCompositorBridgeParent::LookupSurfaceDescriptorForClientTexture( + const int64_t aTextureId) { + return mCanvasTranslator->WaitForSurfaceDescriptor(aTextureId); +} + +bool ContentCompositorBridgeParent::IsSameProcess() const { + return OtherPid() == base::GetCurrentProcId(); +} + +void ContentCompositorBridgeParent::UpdatePaintTime( + LayerTransactionParent* aLayerTree, const TimeDuration& aPaintTime) { + LayersId id = aLayerTree->GetId(); + MOZ_ASSERT(id.IsValid()); + + CompositorBridgeParent::LayerTreeState* state = + CompositorBridgeParent::GetIndirectShadowTree(id); + if (!state || !state->mParent) { + return; + } + + state->mParent->UpdatePaintTime(aLayerTree, aPaintTime); +} + +void ContentCompositorBridgeParent::RegisterPayloads( + LayerTransactionParent* aLayerTree, + const nsTArray& aPayload) { + LayersId id = aLayerTree->GetId(); + MOZ_ASSERT(id.IsValid()); + + CompositorBridgeParent::LayerTreeState* state = + CompositorBridgeParent::GetIndirectShadowTree(id); + if (!state || !state->mParent) { + return; + } + + state->mParent->RegisterPayloads(aLayerTree, aPayload); +} + +void ContentCompositorBridgeParent::ObserveLayersUpdate( + LayersId aLayersId, LayersObserverEpoch aEpoch, bool aActive) { + MOZ_ASSERT(aLayersId.IsValid()); + + CompositorBridgeParent::LayerTreeState* state = + CompositorBridgeParent::GetIndirectShadowTree(aLayersId); + if (!state || !state->mParent) { + return; + } + + Unused << state->mParent->SendObserveLayersUpdate(aLayersId, aEpoch, aActive); +} + +static inline bool AllowDirectDXGISurfaceDrawing() { + if (!StaticPrefs::dom_ipc_plugins_asyncdrawing_enabled()) { + return false; + } +#if defined(XP_WIN) + gfx::DeviceManagerDx* dm = gfx::DeviceManagerDx::Get(); + MOZ_ASSERT(dm); + if (!dm || !dm->GetCompositorDevice() || !dm->TextureSharingWorks()) { + return false; + } + return true; +#else + return false; +#endif +} + +mozilla::ipc::IPCResult +ContentCompositorBridgeParent::RecvSupportsAsyncDXGISurface(bool* value) { + *value = AllowDirectDXGISurfaceDrawing(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentCompositorBridgeParent::RecvPreferredDXGIAdapter( + DxgiAdapterDesc* aOutDesc) { + PodZero(aOutDesc); +#ifdef XP_WIN + if (!AllowDirectDXGISurfaceDrawing()) { + return IPC_FAIL_NO_REASON(this); + } + + RefPtr device = + gfx::DeviceManagerDx::Get()->GetCompositorDevice(); + if (!device) { + return IPC_FAIL_NO_REASON(this); + } + + RefPtr dxgi; + if (FAILED(device->QueryInterface(__uuidof(IDXGIDevice), + getter_AddRefs(dxgi))) || + !dxgi) { + return IPC_FAIL_NO_REASON(this); + } + RefPtr adapter; + if (FAILED(dxgi->GetAdapter(getter_AddRefs(adapter))) || !adapter) { + return IPC_FAIL_NO_REASON(this); + } + + DXGI_ADAPTER_DESC desc; + if (FAILED(adapter->GetDesc(&desc))) { + return IPC_FAIL_NO_REASON(this); + } + + *aOutDesc = DxgiAdapterDesc::From(desc); +#endif + return IPC_OK(); +} + +already_AddRefed +ContentCompositorBridgeParent::AllocPWebGLParent() { + RefPtr parent = new dom::WebGLParent(); + return parent.forget(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/ContentCompositorBridgeParent.h b/gfx/layers/ipc/ContentCompositorBridgeParent.h new file mode 100644 index 0000000000..2d50894eb3 --- /dev/null +++ b/gfx/layers/ipc/ContentCompositorBridgeParent.h @@ -0,0 +1,246 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_ContentCompositorBridgeParent_h +#define mozilla_layers_ContentCompositorBridgeParent_h + +#include "mozilla/layers/CanvasTranslator.h" +#include "mozilla/layers/CompositorBridgeParent.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { +namespace webgpu { +class PWebGPUParent; +} // namespace webgpu + +namespace layers { + +class CompositorOptions; + +/** + * This class handles layer updates pushed directly from child processes to + * the compositor thread. It's associated with a CompositorBridgeParent on the + * compositor thread. While it uses the PCompositorBridge protocol to manage + * these updates, it doesn't actually drive compositing itself. For that it + * hands off work to the CompositorBridgeParent it's associated with. + */ +class ContentCompositorBridgeParent final : public CompositorBridgeParentBase { + friend class CompositorBridgeParent; + + public: + explicit ContentCompositorBridgeParent(CompositorManagerParent* aManager) + : CompositorBridgeParentBase(aManager), + mNotifyAfterRemotePaint(false), + mDestroyCalled(false) {} + + void ActorDestroy(ActorDestroyReason aWhy) override; + + // FIXME/bug 774388: work out what shutdown protocol we need. + mozilla::ipc::IPCResult RecvInitialize( + const LayersId& aRootLayerTreeId) override { + return IPC_FAIL_NO_REASON(this); + } + mozilla::ipc::IPCResult RecvWillClose() override { return IPC_OK(); } + mozilla::ipc::IPCResult RecvPause() override { return IPC_OK(); } + mozilla::ipc::IPCResult RecvRequestFxrOutput() override { + return IPC_FAIL_NO_REASON(this); + } + mozilla::ipc::IPCResult RecvResume() override { return IPC_OK(); } + mozilla::ipc::IPCResult RecvResumeAsync() override { return IPC_OK(); } + mozilla::ipc::IPCResult RecvNotifyChildCreated( + const LayersId& child, CompositorOptions* aOptions) override; + mozilla::ipc::IPCResult RecvMapAndNotifyChildCreated( + const LayersId& child, const base::ProcessId& pid, + CompositorOptions* aOptions) override; + mozilla::ipc::IPCResult RecvNotifyChildRecreated( + const LayersId& child, CompositorOptions* aOptions) override { + return IPC_FAIL_NO_REASON(this); + } + mozilla::ipc::IPCResult RecvAdoptChild(const LayersId& child) override { + return IPC_FAIL_NO_REASON(this); + } + mozilla::ipc::IPCResult RecvMakeSnapshot(const SurfaceDescriptor& aInSnapshot, + const gfx::IntRect& aRect) override { + return IPC_OK(); + } + mozilla::ipc::IPCResult RecvFlushRendering() override { return IPC_OK(); } + mozilla::ipc::IPCResult RecvFlushRenderingAsync() override { + return IPC_OK(); + } + mozilla::ipc::IPCResult RecvForcePresent() override { return IPC_OK(); } + mozilla::ipc::IPCResult RecvWaitOnTransactionProcessed() override { + return IPC_OK(); + } + mozilla::ipc::IPCResult RecvNotifyRegionInvalidated( + const nsIntRegion& aRegion) override { + return IPC_OK(); + } + mozilla::ipc::IPCResult RecvStartFrameTimeRecording( + const int32_t& aBufferSize, uint32_t* aOutStartIndex) override { + return IPC_OK(); + } + mozilla::ipc::IPCResult RecvStopFrameTimeRecording( + const uint32_t& aStartIndex, nsTArray* intervals) override { + return IPC_OK(); + } + + mozilla::ipc::IPCResult RecvCheckContentOnlyTDR( + const uint32_t& sequenceNum, bool* isContentOnlyTDR) override; + + mozilla::ipc::IPCResult RecvAllPluginsCaptured() override { return IPC_OK(); } + + mozilla::ipc::IPCResult RecvBeginRecording( + const TimeStamp& aRecordingStart, + BeginRecordingResolver&& aResolve) override { + aResolve(false); + return IPC_OK(); + } + + mozilla::ipc::IPCResult RecvEndRecordingToDisk( + EndRecordingToDiskResolver&& aResolve) override { + aResolve(false); + return IPC_OK(); + } + + mozilla::ipc::IPCResult RecvEndRecordingToMemory( + EndRecordingToMemoryResolver&& aResolve) override { + aResolve(Nothing()); + return IPC_OK(); + } + + /** + * Tells this CompositorBridgeParent to send a message when the compositor has + * received the transaction. + */ + mozilla::ipc::IPCResult RecvRequestNotifyAfterRemotePaint() override; + + PLayerTransactionParent* AllocPLayerTransactionParent( + const nsTArray& aBackendHints, + const LayersId& aId) override; + + bool DeallocPLayerTransactionParent( + PLayerTransactionParent* aLayers) override; + + void ShadowLayersUpdated(LayerTransactionParent* aLayerTree, + const TransactionInfo& aInfo, + bool aHitTestUpdate) override; + void ScheduleComposite(LayerTransactionParent* aLayerTree) override; + void NotifyClearCachedResources(LayerTransactionParent* aLayerTree) override; + bool SetTestSampleTime(const LayersId& aId, const TimeStamp& aTime) override; + void LeaveTestMode(const LayersId& aId) override; + void ApplyAsyncProperties(LayerTransactionParent* aLayerTree, + TransformsToSkip aSkip) override; + void SetTestAsyncScrollOffset(const LayersId& aLayersId, + const ScrollableLayerGuid::ViewID& aScrollId, + const CSSPoint& aPoint) override; + void SetTestAsyncZoom(const LayersId& aLayersId, + const ScrollableLayerGuid::ViewID& aScrollId, + const LayerToParentLayerScale& aZoom) override; + void FlushApzRepaints(const LayersId& aLayersId) override; + void GetAPZTestData(const LayersId& aLayersId, + APZTestData* aOutData) override; + void GetFrameUniformity(const LayersId& aLayersId, + FrameUniformityData* aOutData) override; + void SetConfirmedTargetAPZC( + const LayersId& aLayersId, const uint64_t& aInputBlockId, + nsTArray&& aTargets) override; + + AsyncCompositionManager* GetCompositionManager( + LayerTransactionParent* aParent) override; + mozilla::ipc::IPCResult RecvRemotePluginsReady() override { + return IPC_FAIL_NO_REASON(this); + } + + already_AddRefed AllocPWebGLParent() override; + + // Use DidCompositeLocked if you already hold a lock on + // sIndirectLayerTreesLock; Otherwise use DidComposite, which would request + // the lock automatically. + void DidCompositeLocked(LayersId aId, const VsyncId& aVsyncId, + TimeStamp& aCompositeStart, TimeStamp& aCompositeEnd); + + PTextureParent* AllocPTextureParent( + const SurfaceDescriptor& aSharedData, const ReadLockDescriptor& aReadLock, + const LayersBackend& aLayersBackend, const TextureFlags& aFlags, + const LayersId& aId, const uint64_t& aSerial, + const wr::MaybeExternalImageId& aExternalImageId) override; + + bool DeallocPTextureParent(PTextureParent* actor) override; + + mozilla::ipc::IPCResult RecvInitPCanvasParent( + Endpoint&& aEndpoint) final; + + mozilla::ipc::IPCResult RecvReleasePCanvasParent() final; + + bool IsSameProcess() const override; + + PCompositorWidgetParent* AllocPCompositorWidgetParent( + const CompositorWidgetInitData& aInitData) override { + // Not allowed. + return nullptr; + } + bool DeallocPCompositorWidgetParent( + PCompositorWidgetParent* aActor) override { + // Not allowed. + return false; + } + + PAPZCTreeManagerParent* AllocPAPZCTreeManagerParent( + const LayersId& aLayersId) override; + bool DeallocPAPZCTreeManagerParent(PAPZCTreeManagerParent* aActor) override; + + PAPZParent* AllocPAPZParent(const LayersId& aLayersId) override; + bool DeallocPAPZParent(PAPZParent* aActor) override; + + void UpdatePaintTime(LayerTransactionParent* aLayerTree, + const TimeDuration& aPaintTime) override; + void RegisterPayloads(LayerTransactionParent* aLayerTree, + const nsTArray& aPayload) override; + + PWebRenderBridgeParent* AllocPWebRenderBridgeParent( + const wr::PipelineId& aPipelineId, const LayoutDeviceIntSize& aSize, + const WindowKind& aWindowKind) override; + bool DeallocPWebRenderBridgeParent(PWebRenderBridgeParent* aActor) override; + + webgpu::PWebGPUParent* AllocPWebGPUParent() override; + bool DeallocPWebGPUParent(webgpu::PWebGPUParent* aActor) override; + + void ObserveLayersUpdate(LayersId aLayersId, LayersObserverEpoch aEpoch, + bool aActive) override; + + bool IsRemote() const override { return true; } + + UniquePtr LookupSurfaceDescriptorForClientTexture( + const int64_t aTextureId) final; + + mozilla::ipc::IPCResult RecvSupportsAsyncDXGISurface(bool* value) override; + mozilla::ipc::IPCResult RecvPreferredDXGIAdapter( + DxgiAdapterDesc* desc) override; + + private: + // Private destructor, to discourage deletion outside of Release(): + virtual ~ContentCompositorBridgeParent(); + + void DeferredDestroy(); + + // There can be many CPCPs, and IPDL-generated code doesn't hold a + // reference to top-level actors. So we hold a reference to + // ourself. This is released (deferred) in ActorDestroy(). + RefPtr mSelfRef; + + // If true, we should send a RemotePaintIsReady message when the layer + // transaction is received + bool mNotifyAfterRemotePaint; + bool mDestroyCalled; + + RefPtr mCanvasTranslator; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_ContentCompositorBridgeParent_h diff --git a/gfx/layers/ipc/ISurfaceAllocator.cpp b/gfx/layers/ipc/ISurfaceAllocator.cpp new file mode 100644 index 0000000000..bf38b22226 --- /dev/null +++ b/gfx/layers/ipc/ISurfaceAllocator.cpp @@ -0,0 +1,239 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ISurfaceAllocator.h" + +#include "mozilla/layers/ImageBridgeParent.h" // for ImageBridgeParent +#include "mozilla/layers/TextureHost.h" // for TextureHost +#include "mozilla/layers/TextureForwarder.h" +#include "mozilla/layers/CompositableForwarder.h" + +namespace mozilla { +namespace layers { + +NS_IMPL_ISUPPORTS(GfxMemoryImageReporter, nsIMemoryReporter) + +mozilla::Atomic GfxMemoryImageReporter::sAmount(0); + +/* static */ +uint32_t CompositableForwarder::GetMaxFileDescriptorsPerMessage() { +#if defined(OS_POSIX) + static const uint32_t kMaxFileDescriptors = + FileDescriptorSet::MAX_DESCRIPTORS_PER_MESSAGE; +#else + // default number that works everywhere else + static const uint32_t kMaxFileDescriptors = 250; +#endif + return kMaxFileDescriptors; +} + +mozilla::ipc::SharedMemory::SharedMemoryType OptimalShmemType() { + return ipc::SharedMemory::SharedMemoryType::TYPE_BASIC; +} + +void HostIPCAllocator::SendPendingAsyncMessages() { + if (mPendingAsyncMessage.empty()) { + return; + } + + // Some type of AsyncParentMessageData message could have + // one file descriptor (e.g. OpDeliverFence). + // A number of file descriptors per gecko ipc message have a limitation + // on OS_POSIX (MACOSX or LINUX). +#if defined(OS_POSIX) + static const uint32_t kMaxMessageNumber = + FileDescriptorSet::MAX_DESCRIPTORS_PER_MESSAGE; +#else + // default number that works everywhere else + static const uint32_t kMaxMessageNumber = 250; +#endif + + nsTArray messages; + messages.SetCapacity(mPendingAsyncMessage.size()); + for (size_t i = 0; i < mPendingAsyncMessage.size(); i++) { + messages.AppendElement(mPendingAsyncMessage[i]); + // Limit maximum number of messages. + if (messages.Length() >= kMaxMessageNumber) { + SendAsyncMessage(messages); + // Initialize Messages. + messages.Clear(); + } + } + + if (messages.Length() > 0) { + SendAsyncMessage(messages); + } + mPendingAsyncMessage.clear(); +} + +// XXX - We should actually figure out the minimum shmem allocation size on +// a certain platform and use that. +const uint32_t sShmemPageSize = 4096; + +#ifdef DEBUG +const uint32_t sSupportedBlockSize = 4; +#endif + +FixedSizeSmallShmemSectionAllocator::FixedSizeSmallShmemSectionAllocator( + LayersIPCChannel* aShmProvider) + : mShmProvider(aShmProvider) { + MOZ_ASSERT(mShmProvider); +} + +FixedSizeSmallShmemSectionAllocator::~FixedSizeSmallShmemSectionAllocator() { + ShrinkShmemSectionHeap(); +} + +bool FixedSizeSmallShmemSectionAllocator::IPCOpen() const { + return mShmProvider->IPCOpen(); +} + +bool FixedSizeSmallShmemSectionAllocator::AllocShmemSection( + uint32_t aSize, ShmemSection* aShmemSection) { + // For now we only support sizes of 4. If we want to support different sizes + // some more complicated bookkeeping should be added. + MOZ_ASSERT(aSize == sSupportedBlockSize); + MOZ_ASSERT(aShmemSection); + + if (!IPCOpen()) { + gfxCriticalError() << "Attempt to allocate a ShmemSection after shutdown."; + return false; + } + + uint32_t allocationSize = (aSize + sizeof(ShmemSectionHeapAllocation)); + + for (size_t i = 0; i < mUsedShmems.size(); i++) { + ShmemSectionHeapHeader* header = + mUsedShmems[i].get(); + if ((header->mAllocatedBlocks + 1) * allocationSize + + sizeof(ShmemSectionHeapHeader) < + sShmemPageSize) { + aShmemSection->shmem() = mUsedShmems[i]; + MOZ_ASSERT(mUsedShmems[i].IsWritable()); + break; + } + } + + if (!aShmemSection->shmem().IsWritable()) { + ipc::Shmem tmp; + if (!mShmProvider->AllocUnsafeShmem(sShmemPageSize, OptimalShmemType(), + &tmp)) { + return false; + } + + ShmemSectionHeapHeader* header = tmp.get(); + header->mTotalBlocks = 0; + header->mAllocatedBlocks = 0; + + mUsedShmems.push_back(tmp); + aShmemSection->shmem() = tmp; + } + + MOZ_ASSERT(aShmemSection->shmem().IsWritable()); + + ShmemSectionHeapHeader* header = + aShmemSection->shmem().get(); + uint8_t* heap = + aShmemSection->shmem().get() + sizeof(ShmemSectionHeapHeader); + + ShmemSectionHeapAllocation* allocHeader = nullptr; + + if (header->mTotalBlocks > header->mAllocatedBlocks) { + // Search for the first available block. + for (size_t i = 0; i < header->mTotalBlocks; i++) { + allocHeader = reinterpret_cast(heap); + + if (allocHeader->mStatus == STATUS_FREED) { + break; + } + heap += allocationSize; + } + MOZ_ASSERT(allocHeader && allocHeader->mStatus == STATUS_FREED); + MOZ_ASSERT(allocHeader->mSize == sSupportedBlockSize); + } else { + heap += header->mTotalBlocks * allocationSize; + + header->mTotalBlocks++; + allocHeader = reinterpret_cast(heap); + allocHeader->mSize = aSize; + } + + MOZ_ASSERT(allocHeader); + header->mAllocatedBlocks++; + allocHeader->mStatus = STATUS_ALLOCATED; + + aShmemSection->size() = aSize; + aShmemSection->offset() = (heap + sizeof(ShmemSectionHeapAllocation)) - + aShmemSection->shmem().get(); + ShrinkShmemSectionHeap(); + return true; +} + +void FixedSizeSmallShmemSectionAllocator::FreeShmemSection( + mozilla::layers::ShmemSection& aShmemSection) { + MOZ_ASSERT(aShmemSection.size() == sSupportedBlockSize); + MOZ_ASSERT(aShmemSection.offset() < sShmemPageSize - sSupportedBlockSize); + + if (!aShmemSection.shmem().IsWritable()) { + return; + } + + ShmemSectionHeapAllocation* allocHeader = + reinterpret_cast( + aShmemSection.shmem().get() + aShmemSection.offset() - + sizeof(ShmemSectionHeapAllocation)); + + MOZ_ASSERT(allocHeader->mSize == aShmemSection.size()); + + DebugOnly success = + allocHeader->mStatus.compareExchange(STATUS_ALLOCATED, STATUS_FREED); + // If this fails something really weird is going on. + MOZ_ASSERT(success); + + ShmemSectionHeapHeader* header = + aShmemSection.shmem().get(); + header->mAllocatedBlocks--; +} + +void FixedSizeSmallShmemSectionAllocator::DeallocShmemSection( + mozilla::layers::ShmemSection& aShmemSection) { + if (!IPCOpen()) { + gfxCriticalNote << "Attempt to dealloc a ShmemSections after shutdown."; + return; + } + + FreeShmemSection(aShmemSection); + ShrinkShmemSectionHeap(); +} + +void FixedSizeSmallShmemSectionAllocator::ShrinkShmemSectionHeap() { + if (!IPCOpen()) { + mUsedShmems.clear(); + return; + } + + // The loop will terminate as we either increase i, or decrease size + // every time through. + size_t i = 0; + while (i < mUsedShmems.size()) { + ShmemSectionHeapHeader* header = + mUsedShmems[i].get(); + if (header->mAllocatedBlocks == 0) { + mShmProvider->DeallocShmem(mUsedShmems[i]); + // We don't particularly care about order, move the last one in the array + // to this position. + if (i < mUsedShmems.size() - 1) { + mUsedShmems[i] = mUsedShmems[mUsedShmems.size() - 1]; + } + mUsedShmems.pop_back(); + } else { + i++; + } + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/ISurfaceAllocator.h b/gfx/layers/ipc/ISurfaceAllocator.h new file mode 100644 index 0000000000..740c3b2b3e --- /dev/null +++ b/gfx/layers/ipc/ISurfaceAllocator.h @@ -0,0 +1,287 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_LAYERS_ISURFACEDEALLOCATOR +#define GFX_LAYERS_ISURFACEDEALLOCATOR + +#include // for size_t +#include // for uint32_t +#include "gfxTypes.h" +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/ipc/SharedMemory.h" // for SharedMemory, etc +#include "mozilla/RefPtr.h" +#include "nsIMemoryReporter.h" // for nsIMemoryReporter +#include "mozilla/Atomics.h" // for Atomic +#include "mozilla/layers/LayersMessages.h" // for ShmemSection + +namespace mozilla { +namespace ipc { +class Shmem; +class IShmemAllocator; +} // namespace ipc +namespace gfx { +class DataSourceSurface; +} // namespace gfx + +namespace layers { + +class CompositableForwarder; +class CompositorBridgeParentBase; +class TextureForwarder; + +class ShmemSectionAllocator; +class LegacySurfaceDescriptorAllocator; +class ClientIPCAllocator; +class HostIPCAllocator; +class LayersIPCChannel; + +enum BufferCapabilities { + DEFAULT_BUFFER_CAPS = 0, + /** + * The allocated buffer must be efficiently mappable as a DataSourceSurface. + */ + MAP_AS_IMAGE_SURFACE = 1 << 0, + /** + * The allocated buffer will be used for GL rendering only + */ + USING_GL_RENDERING_ONLY = 1 << 1 +}; + +class SurfaceDescriptor; + +mozilla::ipc::SharedMemory::SharedMemoryType OptimalShmemType(); + +/** + * An interface used to create and destroy surfaces that are shared with the + * Compositor process (using shmem, or other platform specific memory) + * + * Most of the methods here correspond to methods that are implemented by IPDL + * actors without a common polymorphic interface. + * These methods should be only called in the ipdl implementor's thread, unless + * specified otherwise in the implementing class. + */ +class ISurfaceAllocator { + public: + MOZ_DECLARE_REFCOUNTED_TYPENAME(ISurfaceAllocator) + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ISurfaceAllocator) + + ISurfaceAllocator() = default; + + // down-casting + + virtual mozilla::ipc::IShmemAllocator* AsShmemAllocator() { return nullptr; } + + virtual ShmemSectionAllocator* AsShmemSectionAllocator() { return nullptr; } + + virtual CompositableForwarder* AsCompositableForwarder() { return nullptr; } + + virtual TextureForwarder* GetTextureForwarder() { return nullptr; } + + virtual ClientIPCAllocator* AsClientAllocator() { return nullptr; } + + virtual HostIPCAllocator* AsHostIPCAllocator() { return nullptr; } + + virtual LegacySurfaceDescriptorAllocator* + AsLegacySurfaceDescriptorAllocator() { + return nullptr; + } + + virtual CompositorBridgeParentBase* AsCompositorBridgeParentBase() { + return nullptr; + } + + // ipc info + + virtual bool IPCOpen() const { return true; } + + virtual bool IsSameProcess() const = 0; + + virtual bool UsesImageBridge() const { return false; } + + virtual bool UsesWebRenderBridge() const { return false; } + + protected: + void Finalize() {} + + virtual ~ISurfaceAllocator() = default; +}; + +/// Methods that are specific to the client/child side. +class ClientIPCAllocator : public ISurfaceAllocator { + public: + ClientIPCAllocator() = default; + + ClientIPCAllocator* AsClientAllocator() override { return this; } + + virtual base::ProcessId GetParentPid() const = 0; + + virtual MessageLoop* GetMessageLoop() const = 0; + + virtual void CancelWaitForNotifyNotUsed(uint64_t aTextureId) = 0; +}; + +/// Methods that are specific to the host/parent side. +class HostIPCAllocator : public ISurfaceAllocator { + public: + HostIPCAllocator() = default; + + HostIPCAllocator* AsHostIPCAllocator() override { return this; } + + /** + * Get child side's process Id. + */ + virtual base::ProcessId GetChildProcessId() = 0; + + virtual void NotifyNotUsed(PTextureParent* aTexture, + uint64_t aTransactionId) = 0; + + virtual void SendAsyncMessage( + const nsTArray& aMessage) = 0; + + virtual void SendPendingAsyncMessages(); + + virtual void SetAboutToSendAsyncMessages() { + mAboutToSendAsyncMessages = true; + } + + bool IsAboutToSendAsyncMessages() { return mAboutToSendAsyncMessages; } + + protected: + std::vector mPendingAsyncMessage; + bool mAboutToSendAsyncMessages = false; +}; + +/// An allocator that can group allocations in bigger chunks of shared memory. +/// +/// The allocated shmem sections can only be deallocated by the same allocator +/// instance (and only in the child process). +class ShmemSectionAllocator { + public: + virtual bool AllocShmemSection(uint32_t aSize, + ShmemSection* aShmemSection) = 0; + + virtual void DeallocShmemSection(ShmemSection& aShmemSection) = 0; + + virtual void MemoryPressure() {} +}; + +/// Some old stuff that's still around and used for screenshots. +/// +/// New code should not need this (see TextureClient). +class LegacySurfaceDescriptorAllocator { + public: + virtual bool AllocSurfaceDescriptor(const gfx::IntSize& aSize, + gfxContentType aContent, + SurfaceDescriptor* aBuffer) = 0; + + virtual bool AllocSurfaceDescriptorWithCaps(const gfx::IntSize& aSize, + gfxContentType aContent, + uint32_t aCaps, + SurfaceDescriptor* aBuffer) = 0; + + virtual void DestroySurfaceDescriptor(SurfaceDescriptor* aSurface) = 0; +}; + +bool IsSurfaceDescriptorValid(const SurfaceDescriptor& aSurface); + +already_AddRefed GetDrawTargetForDescriptor( + const SurfaceDescriptor& aDescriptor, gfx::BackendType aBackend); + +already_AddRefed GetSurfaceForDescriptor( + const SurfaceDescriptor& aDescriptor); + +uint8_t* GetAddressFromDescriptor(const SurfaceDescriptor& aDescriptor); + +void DestroySurfaceDescriptor(mozilla::ipc::IShmemAllocator* aAllocator, + SurfaceDescriptor* aSurface); + +class GfxMemoryImageReporter final : public nsIMemoryReporter { + ~GfxMemoryImageReporter() = default; + + public: + NS_DECL_ISUPPORTS + + GfxMemoryImageReporter() { +#ifdef DEBUG + // There must be only one instance of this class, due to |sAmount| + // being static. + static bool hasRun = false; + MOZ_ASSERT(!hasRun); + hasRun = true; +#endif + } + + MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC(MallocSizeOfOnAlloc) + MOZ_DEFINE_MALLOC_SIZE_OF_ON_FREE(MallocSizeOfOnFree) + + static void DidAlloc(void* aPointer) { + sAmount += MallocSizeOfOnAlloc(aPointer); + } + + static void WillFree(void* aPointer) { + sAmount -= MallocSizeOfOnFree(aPointer); + } + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { + MOZ_COLLECT_REPORT( + "explicit/gfx/heap-textures", KIND_HEAP, UNITS_BYTES, sAmount, + "Heap memory shared between threads by texture clients and hosts."); + + return NS_OK; + } + + private: + // Typically we use |size_t| in memory reporters, but in the past this + // variable has sometimes gone negative due to missing DidAlloc() calls. + // Therefore, we use a signed type so that any such negative values show up + // as negative in about:memory, rather than as enormous positive numbers. + static mozilla::Atomic sAmount; +}; + +/// A simple shmem section allocator that can only allocate small +/// fixed size elements (only intended to be used to store tile +/// copy-on-write locks for now). +class FixedSizeSmallShmemSectionAllocator final : public ShmemSectionAllocator { + public: + enum AllocationStatus { STATUS_ALLOCATED, STATUS_FREED }; + + struct ShmemSectionHeapHeader { + Atomic mTotalBlocks; + Atomic mAllocatedBlocks; + }; + + struct ShmemSectionHeapAllocation { + Atomic mStatus; + uint32_t mSize; + }; + + explicit FixedSizeSmallShmemSectionAllocator(LayersIPCChannel* aShmProvider); + + ~FixedSizeSmallShmemSectionAllocator(); + + bool AllocShmemSection(uint32_t aSize, ShmemSection* aShmemSection) override; + + void DeallocShmemSection(ShmemSection& aShmemSection) override; + + void MemoryPressure() override { ShrinkShmemSectionHeap(); } + + // can be called on the compositor process. + static void FreeShmemSection(ShmemSection& aShmemSection); + + void ShrinkShmemSectionHeap(); + + bool IPCOpen() const; + + protected: + std::vector mUsedShmems; + LayersIPCChannel* mShmProvider; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/ipc/ImageBridgeChild.cpp b/gfx/layers/ipc/ImageBridgeChild.cpp new file mode 100644 index 0000000000..0c3024426f --- /dev/null +++ b/gfx/layers/ipc/ImageBridgeChild.cpp @@ -0,0 +1,995 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ImageBridgeChild.h" + +#include // for vector + +#include "ImageBridgeParent.h" // for ImageBridgeParent +#include "ImageContainer.h" // for ImageContainer +#include "Layers.h" // for Layer, etc +#include "ShadowLayers.h" // for ShadowLayerForwarder +#include "SynchronousTask.h" +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/Monitor.h" // for Monitor, MonitorAutoLock +#include "mozilla/ReentrantMonitor.h" // for ReentrantMonitor, etc +#include "mozilla/StaticMutex.h" +#include "mozilla/StaticPtr.h" // for StaticRefPtr +#include "mozilla/dom/ContentChild.h" +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/MessageChannel.h" // for MessageChannel, etc +#include "mozilla/ipc/Transport.h" // for Transport +#include "mozilla/layers/CompositableClient.h" // for CompositableChild, etc +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/layers/ISurfaceAllocator.h" // for ISurfaceAllocator +#include "mozilla/layers/ImageClient.h" // for ImageClient +#include "mozilla/layers/LayersMessages.h" // for CompositableOperation +#include "mozilla/layers/TextureClient.h" // for TextureClient +#include "mozilla/layers/TextureClient.h" +#include "mozilla/media/MediaSystemResourceManager.h" // for MediaSystemResourceManager +#include "mozilla/media/MediaSystemResourceManagerChild.h" // for MediaSystemResourceManagerChild +#include "mozilla/mozalloc.h" // for operator new, etc +#include "transport/runnable_utils.h" +#include "nsContentUtils.h" +#include "nsISupportsImpl.h" // for ImageContainer::AddRef, etc +#include "nsTArray.h" // for AutoTArray, nsTArray, etc +#include "nsTArrayForwardDeclare.h" // for AutoTArray +#include "nsThreadUtils.h" // for NS_IsMainThread + +#if defined(XP_WIN) +# include "mozilla/gfx/DeviceManagerDx.h" +#endif + +#ifdef MOZ_WIDGET_ANDROID +# include "mozilla/layers/AndroidHardwareBuffer.h" +#endif + +namespace mozilla { +namespace ipc { +class Shmem; +} // namespace ipc + +namespace layers { + +using namespace mozilla::ipc; +using namespace mozilla::gfx; +using namespace mozilla::media; + +typedef std::vector OpVector; +typedef nsTArray OpDestroyVector; + +struct CompositableTransaction { + CompositableTransaction() : mFinished(true) {} + ~CompositableTransaction() { End(); } + bool Finished() const { return mFinished; } + void Begin() { + MOZ_ASSERT(mFinished); + mFinished = false; + } + void End() { + mFinished = true; + mOperations.clear(); + mDestroyedActors.Clear(); + } + bool IsEmpty() const { + return mOperations.empty() && mDestroyedActors.IsEmpty(); + } + void AddNoSwapEdit(const CompositableOperation& op) { + MOZ_ASSERT(!Finished(), "forgot BeginTransaction?"); + mOperations.push_back(op); + } + + OpVector mOperations; + OpDestroyVector mDestroyedActors; + + bool mFinished; +}; + +struct AutoEndTransaction final { + explicit AutoEndTransaction(CompositableTransaction* aTxn) : mTxn(aTxn) {} + ~AutoEndTransaction() { mTxn->End(); } + CompositableTransaction* mTxn; +}; + +void ImageBridgeChild::UseTextures( + CompositableClient* aCompositable, + const nsTArray& aTextures) { + MOZ_ASSERT(aCompositable); + MOZ_ASSERT(aCompositable->GetIPCHandle()); + MOZ_ASSERT(aCompositable->IsConnected()); + + AutoTArray textures; + + for (auto& t : aTextures) { + MOZ_ASSERT(t.mTextureClient); + MOZ_ASSERT(t.mTextureClient->GetIPDLActor()); + + if (!t.mTextureClient->IsSharedWithCompositor()) { + return; + } + + bool readLocked = t.mTextureClient->OnForwardedToHost(); + + auto fenceFd = t.mTextureClient->GetInternalData()->GetAcquireFence(); + if (fenceFd.IsValid()) { + mTxn->AddNoSwapEdit(CompositableOperation( + aCompositable->GetIPCHandle(), + OpDeliverAcquireFence(nullptr, t.mTextureClient->GetIPDLActor(), + fenceFd))); + } + + textures.AppendElement( + TimedTexture(nullptr, t.mTextureClient->GetIPDLActor(), t.mTimeStamp, + t.mPictureRect, t.mFrameID, t.mProducerID, readLocked)); + + // Wait end of usage on host side if TextureFlags::RECYCLE is set + HoldUntilCompositableRefReleasedIfNecessary(t.mTextureClient); + } + mTxn->AddNoSwapEdit(CompositableOperation(aCompositable->GetIPCHandle(), + OpUseTexture(textures))); +} + +void ImageBridgeChild::UseComponentAlphaTextures( + CompositableClient* aCompositable, TextureClient* aTextureOnBlack, + TextureClient* aTextureOnWhite) { + MOZ_CRASH("should not be called"); +} + +void ImageBridgeChild::HoldUntilCompositableRefReleasedIfNecessary( + TextureClient* aClient) { + if (!aClient) { + return; + } + +#ifdef MOZ_WIDGET_ANDROID + auto bufferId = aClient->GetInternalData()->GetBufferId(); + if (bufferId.isSome()) { + MOZ_ASSERT(aClient->GetFlags() & TextureFlags::WAIT_HOST_USAGE_END); + AndroidHardwareBufferManager::Get()->HoldUntilNotifyNotUsed( + bufferId.ref(), GetFwdTransactionId(), /* aUsesImageBridge */ true); + } +#endif + + // Wait ReleaseCompositableRef only when TextureFlags::RECYCLE or + // TextureFlags::WAIT_HOST_USAGE_END is set on ImageBridge. + bool waitNotifyNotUsed = + aClient->GetFlags() & TextureFlags::RECYCLE || + aClient->GetFlags() & TextureFlags::WAIT_HOST_USAGE_END; + if (!waitNotifyNotUsed) { + return; + } + + aClient->SetLastFwdTransactionId(GetFwdTransactionId()); + mTexturesWaitingNotifyNotUsed.emplace(aClient->GetSerial(), aClient); +} + +void ImageBridgeChild::NotifyNotUsed(uint64_t aTextureId, + uint64_t aFwdTransactionId) { + auto it = mTexturesWaitingNotifyNotUsed.find(aTextureId); + if (it != mTexturesWaitingNotifyNotUsed.end()) { + if (aFwdTransactionId < it->second->GetLastFwdTransactionId()) { + // Released on host side, but client already requested newer use texture. + return; + } + mTexturesWaitingNotifyNotUsed.erase(it); + } +} + +void ImageBridgeChild::CancelWaitForNotifyNotUsed(uint64_t aTextureId) { + MOZ_ASSERT(InImageBridgeChildThread()); + mTexturesWaitingNotifyNotUsed.erase(aTextureId); +} + +// Singleton +static StaticMutex sImageBridgeSingletonLock; +static StaticRefPtr sImageBridgeChildSingleton; +static StaticRefPtr sImageBridgeChildThread; + +// dispatched function +void ImageBridgeChild::ShutdownStep1(SynchronousTask* aTask) { + AutoCompleteTask complete(aTask); + + MOZ_ASSERT(InImageBridgeChildThread(), + "Should be in ImageBridgeChild thread."); + + MediaSystemResourceManager::Shutdown(); + + // Force all managed protocols to shut themselves down cleanly + nsTArray textures; + ManagedPTextureChild(textures); + for (int i = textures.Length() - 1; i >= 0; --i) { + RefPtr client = TextureClient::AsTextureClient(textures[i]); + if (client) { + client->Destroy(); + } + } + + if (mCanSend) { + SendWillClose(); + } + MarkShutDown(); + + // From now on, no message can be sent through the image bridge from the + // client side except the final Stop message. +} + +// dispatched function +void ImageBridgeChild::ShutdownStep2(SynchronousTask* aTask) { + AutoCompleteTask complete(aTask); + + MOZ_ASSERT(InImageBridgeChildThread(), + "Should be in ImageBridgeChild thread."); + if (!mDestroyed) { + Close(); + } +} + +void ImageBridgeChild::ActorDestroy(ActorDestroyReason aWhy) { + mCanSend = false; + mDestroyed = true; + { + MutexAutoLock lock(mContainerMapLock); + mImageContainerListeners.clear(); + } +} + +void ImageBridgeChild::ActorDealloc() { this->Release(); } + +void ImageBridgeChild::CreateImageClientSync(SynchronousTask* aTask, + RefPtr* result, + CompositableType aType, + ImageContainer* aImageContainer) { + AutoCompleteTask complete(aTask); + *result = CreateImageClientNow(aType, aImageContainer); +} + +ImageBridgeChild::ImageBridgeChild(uint32_t aNamespace) + : mNamespace(aNamespace), + mCanSend(false), + mDestroyed(false), + mFwdTransactionId(0), + mContainerMapLock("ImageBridgeChild.mContainerMapLock") { + MOZ_ASSERT(mNamespace); + MOZ_ASSERT(NS_IsMainThread()); + + mTxn = new CompositableTransaction(); +} + +ImageBridgeChild::~ImageBridgeChild() { delete mTxn; } + +void ImageBridgeChild::MarkShutDown() { + mTexturesWaitingNotifyNotUsed.clear(); + + mCanSend = false; +} + +void ImageBridgeChild::Connect(CompositableClient* aCompositable, + ImageContainer* aImageContainer) { + MOZ_ASSERT(aCompositable); + MOZ_ASSERT(InImageBridgeChildThread()); + MOZ_ASSERT(CanSend()); + + // Note: this is static, rather than per-IBC, so IDs are not re-used across + // ImageBridgeChild instances. This is relevant for the GPU process, where + // we don't want old IDs to potentially leak into a recreated ImageBridge. + static uint64_t sNextID = 1; + uint64_t id = sNextID++; + + // ImageClient of ImageContainer provides aImageContainer. + // But offscreen canvas does not provide it. + if (aImageContainer) { + MutexAutoLock lock(mContainerMapLock); + MOZ_ASSERT(mImageContainerListeners.find(id) == + mImageContainerListeners.end()); + mImageContainerListeners.emplace( + id, aImageContainer->GetImageContainerListener()); + } + + CompositableHandle handle(id); + aCompositable->InitIPDL(handle); + SendNewCompositable(handle, aCompositable->GetTextureInfo(), + GetCompositorBackendType()); +} + +void ImageBridgeChild::ForgetImageContainer(const CompositableHandle& aHandle) { + MutexAutoLock lock(mContainerMapLock); + mImageContainerListeners.erase(aHandle.Value()); +} + +/* static */ +RefPtr ImageBridgeChild::GetSingleton() { + StaticMutexAutoLock lock(sImageBridgeSingletonLock); + return sImageBridgeChildSingleton; +} + +void ImageBridgeChild::UpdateImageClient(RefPtr aContainer) { + if (!aContainer) { + return; + } + + if (!InImageBridgeChildThread()) { + RefPtr runnable = + WrapRunnable(RefPtr(this), + &ImageBridgeChild::UpdateImageClient, aContainer); + GetThread()->Dispatch(runnable.forget()); + return; + } + + if (!CanSend()) { + return; + } + + RefPtr client = aContainer->GetImageClient(); + if (NS_WARN_IF(!client)) { + return; + } + + // If the client has become disconnected before this event was dispatched, + // early return now. + if (!client->IsConnected()) { + return; + } + + BeginTransaction(); + client->UpdateImage(aContainer, Layer::CONTENT_OPAQUE); + EndTransaction(); +} + +void ImageBridgeChild::FlushAllImagesSync(SynchronousTask* aTask, + ImageClient* aClient, + ImageContainer* aContainer) { + AutoCompleteTask complete(aTask); + + if (!CanSend()) { + return; + } + + MOZ_ASSERT(aClient); + BeginTransaction(); + if (aContainer) { + aContainer->ClearImagesFromImageBridge(); + } + aClient->FlushAllImages(); + EndTransaction(); +} + +void ImageBridgeChild::FlushAllImages(ImageClient* aClient, + ImageContainer* aContainer) { + MOZ_ASSERT(aClient); + MOZ_ASSERT(!InImageBridgeChildThread()); + + if (InImageBridgeChildThread()) { + NS_ERROR( + "ImageBridgeChild::FlushAllImages() is called on ImageBridge thread."); + return; + } + + SynchronousTask task("FlushAllImages Lock"); + + // RefPtrs on arguments are not needed since this dispatches synchronously. + RefPtr runnable = WrapRunnable( + RefPtr(this), &ImageBridgeChild::FlushAllImagesSync, + &task, aClient, aContainer); + GetThread()->Dispatch(runnable.forget()); + + task.Wait(); +} + +void ImageBridgeChild::BeginTransaction() { + MOZ_ASSERT(CanSend()); + MOZ_ASSERT(mTxn->Finished(), "uncommitted txn?"); + UpdateFwdTransactionId(); + mTxn->Begin(); +} + +void ImageBridgeChild::EndTransaction() { + MOZ_ASSERT(CanSend()); + MOZ_ASSERT(!mTxn->Finished(), "forgot BeginTransaction?"); + + AutoEndTransaction _(mTxn); + + if (mTxn->IsEmpty()) { + return; + } + + AutoTArray cset; + cset.SetCapacity(mTxn->mOperations.size()); + if (!mTxn->mOperations.empty()) { + cset.AppendElements(&mTxn->mOperations.front(), mTxn->mOperations.size()); + } + + if (!IsSameProcess()) { + ShadowLayerForwarder::PlatformSyncBeforeUpdate(); + } + + if (!SendUpdate(cset, mTxn->mDestroyedActors, GetFwdTransactionId())) { + NS_WARNING("could not send async texture transaction"); + return; + } +} + +bool ImageBridgeChild::InitForContent(Endpoint&& aEndpoint, + uint32_t aNamespace) { + MOZ_ASSERT(NS_IsMainThread()); + + gfxPlatform::GetPlatform(); + + if (!sImageBridgeChildThread) { + nsCOMPtr thread; + nsresult rv = NS_NewNamedThread("ImageBridgeChld", getter_AddRefs(thread)); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), + "Failed to start ImageBridgeChild thread!"); + sImageBridgeChildThread = thread.forget(); + } + + RefPtr child = new ImageBridgeChild(aNamespace); + + child->GetThread()->Dispatch(NS_NewRunnableFunction( + "layers::ImageBridgeChild::Bind", + [child, endpoint = std::move(aEndpoint)]() mutable { + child->Bind(std::move(endpoint)); + })); + + // Assign this after so other threads can't post messages before we connect to + // IPDL. + { + StaticMutexAutoLock lock(sImageBridgeSingletonLock); + sImageBridgeChildSingleton = child; + } + + return true; +} + +bool ImageBridgeChild::ReinitForContent(Endpoint&& aEndpoint, + uint32_t aNamespace) { + MOZ_ASSERT(NS_IsMainThread()); + + // Note that at this point, ActorDestroy may not have been called yet, + // meaning mCanSend is still true. In this case we will try to send a + // synchronous WillClose message to the parent, and will certainly get a + // false result and a MsgDropped processing error. This is okay. + ShutdownSingleton(); + + return InitForContent(std::move(aEndpoint), aNamespace); +} + +void ImageBridgeChild::Bind(Endpoint&& aEndpoint) { + if (!aEndpoint.Bind(this)) { + return; + } + + // This reference is dropped in DeallocPImageBridgeChild. + this->AddRef(); + + mCanSend = true; +} + +void ImageBridgeChild::BindSameProcess(RefPtr aParent) { + ipc::MessageChannel* parentChannel = aParent->GetIPCChannel(); + Open(parentChannel, aParent->GetThread(), mozilla::ipc::ChildSide); + + // This reference is dropped in DeallocPImageBridgeChild. + this->AddRef(); + + mCanSend = true; +} + +/* static */ +void ImageBridgeChild::ShutDown() { + MOZ_ASSERT(NS_IsMainThread()); + + ShutdownSingleton(); + + if (sImageBridgeChildThread) { + sImageBridgeChildThread->Shutdown(); + sImageBridgeChildThread = nullptr; + } +} + +/* static */ +void ImageBridgeChild::ShutdownSingleton() { + MOZ_ASSERT(NS_IsMainThread()); + + if (RefPtr child = GetSingleton()) { + child->WillShutdown(); + + StaticMutexAutoLock lock(sImageBridgeSingletonLock); + sImageBridgeChildSingleton = nullptr; + } +} + +void ImageBridgeChild::WillShutdown() { + { + SynchronousTask task("ImageBridge ShutdownStep1 lock"); + + RefPtr runnable = + WrapRunnable(RefPtr(this), + &ImageBridgeChild::ShutdownStep1, &task); + GetThread()->Dispatch(runnable.forget()); + + task.Wait(); + } + + { + SynchronousTask task("ImageBridge ShutdownStep2 lock"); + + RefPtr runnable = + WrapRunnable(RefPtr(this), + &ImageBridgeChild::ShutdownStep2, &task); + GetThread()->Dispatch(runnable.forget()); + + task.Wait(); + } +} + +void ImageBridgeChild::InitSameProcess(uint32_t aNamespace) { + NS_ASSERTION(NS_IsMainThread(), "Should be on the main Thread!"); + + MOZ_ASSERT(!sImageBridgeChildSingleton); + MOZ_ASSERT(!sImageBridgeChildThread); + + nsCOMPtr thread; + nsresult rv = NS_NewNamedThread("ImageBridgeChld", getter_AddRefs(thread)); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), + "Failed to start ImageBridgeChild thread!"); + sImageBridgeChildThread = thread.forget(); + + RefPtr child = new ImageBridgeChild(aNamespace); + RefPtr parent = ImageBridgeParent::CreateSameProcess(); + + RefPtr runnable = + WrapRunnable(child, &ImageBridgeChild::BindSameProcess, parent); + child->GetThread()->Dispatch(runnable.forget()); + + // Assign this after so other threads can't post messages before we connect to + // IPDL. + { + StaticMutexAutoLock lock(sImageBridgeSingletonLock); + sImageBridgeChildSingleton = child; + } +} + +/* static */ +void ImageBridgeChild::InitWithGPUProcess( + Endpoint&& aEndpoint, uint32_t aNamespace) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!sImageBridgeChildSingleton); + MOZ_ASSERT(!sImageBridgeChildThread); + + nsCOMPtr thread; + nsresult rv = NS_NewNamedThread("ImageBridgeChld", getter_AddRefs(thread)); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), + "Failed to start ImageBridgeChild thread!"); + sImageBridgeChildThread = thread.forget(); + + RefPtr child = new ImageBridgeChild(aNamespace); + + child->GetThread()->Dispatch(NS_NewRunnableFunction( + "layers::ImageBridgeChild::Bind", + [child, endpoint = std::move(aEndpoint)]() mutable { + child->Bind(std::move(endpoint)); + })); + + // Assign this after so other threads can't post messages before we connect to + // IPDL. + { + StaticMutexAutoLock lock(sImageBridgeSingletonLock); + sImageBridgeChildSingleton = child; + } +} + +bool InImageBridgeChildThread() { + return sImageBridgeChildThread && + sImageBridgeChildThread->IsOnCurrentThread(); +} + +nsISerialEventTarget* ImageBridgeChild::GetThread() const { + return sImageBridgeChildThread; +} + +/* static */ +void ImageBridgeChild::IdentifyCompositorTextureHost( + const TextureFactoryIdentifier& aIdentifier) { + if (RefPtr child = GetSingleton()) { + child->UpdateTextureFactoryIdentifier(aIdentifier); + } +} + +void ImageBridgeChild::UpdateTextureFactoryIdentifier( + const TextureFactoryIdentifier& aIdentifier) { + // ImageHost is incompatible between WebRender enabled and WebRender disabled. + // Then drop all ImageContainers' ImageClients during disabling WebRender. + bool disablingWebRender = + GetCompositorBackendType() == LayersBackend::LAYERS_WR && + aIdentifier.mParentBackend != LayersBackend::LAYERS_WR; + + // Do not update TextureFactoryIdentifier if aIdentifier is going to disable + // WebRender, but gecko is still using WebRender. Since gecko uses different + // incompatible ImageHost and TextureHost between WebRender and non-WebRender. + // + // Even when WebRender is still in use, if non-accelerated widget is opened, + // aIdentifier disables WebRender at ImageBridgeChild. + if (disablingWebRender && gfxVars::UseWebRender()) { + return; + } + + // D3DTexture might become obsolte. To prevent to use obsoleted D3DTexture, + // drop all ImageContainers' ImageClients. + + // During re-creating GPU process, there was a period that ImageBridgeChild + // was re-created, but ImageBridgeChild::UpdateTextureFactoryIdentifier() was + // not called yet. In the period, if ImageBridgeChild::CreateImageClient() is + // called, ImageBridgeParent creates incompatible ImageHost than + // WebRenderImageHost. + bool initializingWebRender = + GetCompositorBackendType() != LayersBackend::LAYERS_WR && + aIdentifier.mParentBackend == LayersBackend::LAYERS_WR; + + bool needsDrop = disablingWebRender || initializingWebRender; + +#if defined(XP_WIN) + RefPtr device = gfx::DeviceManagerDx::Get()->GetImageDevice(); + needsDrop |= !!mImageDevice && mImageDevice != device && + GetCompositorBackendType() == LayersBackend::LAYERS_D3D11; + mImageDevice = device; +#endif + + IdentifyTextureHost(aIdentifier); + if (needsDrop) { + nsTArray > listeners; + { + MutexAutoLock lock(mContainerMapLock); + for (const auto& entry : mImageContainerListeners) { + listeners.AppendElement(entry.second); + } + } + // Drop ImageContainer's ImageClient whithout holding mContainerMapLock to + // avoid deadlock. + for (auto container : listeners) { + container->DropImageClient(); + } + } +} + +RefPtr ImageBridgeChild::CreateImageClient( + CompositableType aType, ImageContainer* aImageContainer) { + if (InImageBridgeChildThread()) { + return CreateImageClientNow(aType, aImageContainer); + } + + SynchronousTask task("CreateImageClient Lock"); + + RefPtr result = nullptr; + + RefPtr runnable = WrapRunnable( + RefPtr(this), &ImageBridgeChild::CreateImageClientSync, + &task, &result, aType, aImageContainer); + GetThread()->Dispatch(runnable.forget()); + + task.Wait(); + + return result; +} + +RefPtr ImageBridgeChild::CreateImageClientNow( + CompositableType aType, ImageContainer* aImageContainer) { + MOZ_ASSERT(InImageBridgeChildThread()); + if (!CanSend()) { + return nullptr; + } + + RefPtr client = + ImageClient::CreateImageClient(aType, this, TextureFlags::NO_FLAGS); + MOZ_ASSERT(client, "failed to create ImageClient"); + if (client) { + client->Connect(aImageContainer); + } + return client; +} + +bool ImageBridgeChild::AllocUnsafeShmem( + size_t aSize, ipc::SharedMemory::SharedMemoryType aType, + ipc::Shmem* aShmem) { + if (!InImageBridgeChildThread()) { + return DispatchAllocShmemInternal(aSize, aType, aShmem, + true); // true: unsafe + } + + if (!CanSend()) { + return false; + } + return PImageBridgeChild::AllocUnsafeShmem(aSize, aType, aShmem); +} + +bool ImageBridgeChild::AllocShmem(size_t aSize, + ipc::SharedMemory::SharedMemoryType aType, + ipc::Shmem* aShmem) { + if (!InImageBridgeChildThread()) { + return DispatchAllocShmemInternal(aSize, aType, aShmem, + false); // false: unsafe + } + + if (!CanSend()) { + return false; + } + return PImageBridgeChild::AllocShmem(aSize, aType, aShmem); +} + +void ImageBridgeChild::ProxyAllocShmemNow(SynchronousTask* aTask, size_t aSize, + SharedMemory::SharedMemoryType aType, + ipc::Shmem* aShmem, bool aUnsafe, + bool* aSuccess) { + AutoCompleteTask complete(aTask); + + if (!CanSend()) { + return; + } + + bool ok = false; + if (aUnsafe) { + ok = AllocUnsafeShmem(aSize, aType, aShmem); + } else { + ok = AllocShmem(aSize, aType, aShmem); + } + *aSuccess = ok; +} + +bool ImageBridgeChild::DispatchAllocShmemInternal( + size_t aSize, SharedMemory::SharedMemoryType aType, ipc::Shmem* aShmem, + bool aUnsafe) { + SynchronousTask task("AllocatorProxy alloc"); + + bool success = false; + RefPtr runnable = WrapRunnable( + RefPtr(this), &ImageBridgeChild::ProxyAllocShmemNow, + &task, aSize, aType, aShmem, aUnsafe, &success); + GetThread()->Dispatch(runnable.forget()); + + task.Wait(); + + return success; +} + +void ImageBridgeChild::ProxyDeallocShmemNow(SynchronousTask* aTask, + ipc::Shmem* aShmem, bool* aResult) { + AutoCompleteTask complete(aTask); + + if (!CanSend()) { + return; + } + *aResult = DeallocShmem(*aShmem); +} + +bool ImageBridgeChild::DeallocShmem(ipc::Shmem& aShmem) { + if (InImageBridgeChildThread()) { + if (!CanSend()) { + return false; + } + return PImageBridgeChild::DeallocShmem(aShmem); + } + + // If we can't post a task, then we definitely cannot send, so there's + // no reason to queue up this send. + if (!CanPostTask()) { + return false; + } + + SynchronousTask task("AllocatorProxy Dealloc"); + bool result = false; + + RefPtr runnable = WrapRunnable( + RefPtr(this), &ImageBridgeChild::ProxyDeallocShmemNow, + &task, &aShmem, &result); + GetThread()->Dispatch(runnable.forget()); + + task.Wait(); + return result; +} + +PTextureChild* ImageBridgeChild::AllocPTextureChild( + const SurfaceDescriptor&, const ReadLockDescriptor&, const LayersBackend&, + const TextureFlags&, const uint64_t& aSerial, + const wr::MaybeExternalImageId& aExternalImageId) { + MOZ_ASSERT(CanSend()); + return TextureClient::CreateIPDLActor(); +} + +bool ImageBridgeChild::DeallocPTextureChild(PTextureChild* actor) { + return TextureClient::DestroyIPDLActor(actor); +} + +PMediaSystemResourceManagerChild* +ImageBridgeChild::AllocPMediaSystemResourceManagerChild() { + MOZ_ASSERT(CanSend()); + return new mozilla::media::MediaSystemResourceManagerChild(); +} + +bool ImageBridgeChild::DeallocPMediaSystemResourceManagerChild( + PMediaSystemResourceManagerChild* aActor) { + MOZ_ASSERT(aActor); + delete static_cast(aActor); + return true; +} + +mozilla::ipc::IPCResult ImageBridgeChild::RecvParentAsyncMessages( + nsTArray&& aMessages) { + for (AsyncParentMessageArray::index_type i = 0; i < aMessages.Length(); ++i) { + const AsyncParentMessageData& message = aMessages[i]; + + switch (message.type()) { + case AsyncParentMessageData::TOpNotifyNotUsed: { + const OpNotifyNotUsed& op = message.get_OpNotifyNotUsed(); + NotifyNotUsed(op.TextureId(), op.fwdTransactionId()); + break; + } + case AsyncParentMessageData::TOpDeliverReleaseFence: { +#ifdef MOZ_WIDGET_ANDROID + const OpDeliverReleaseFence& op = message.get_OpDeliverReleaseFence(); + ipc::FileDescriptor fenceFd; + if (op.fenceFd().isSome()) { + fenceFd = *op.fenceFd(); + } + AndroidHardwareBufferManager::Get()->NotifyNotUsed( + std::move(fenceFd), op.bufferId(), op.fwdTransactionId(), + op.usesImageBridge()); +#else + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); +#endif + break; + } + default: + NS_ERROR("unknown AsyncParentMessageData type"); + return IPC_FAIL_NO_REASON(this); + } + } + return IPC_OK(); +} + +RefPtr ImageBridgeChild::FindListener( + const CompositableHandle& aHandle) { + RefPtr listener; + MutexAutoLock lock(mContainerMapLock); + auto it = mImageContainerListeners.find(aHandle.Value()); + if (it != mImageContainerListeners.end()) { + listener = it->second; + } + return listener; +} + +mozilla::ipc::IPCResult ImageBridgeChild::RecvDidComposite( + nsTArray&& aNotifications) { + for (auto& n : aNotifications) { + RefPtr listener = FindListener(n.compositable()); + if (listener) { + listener->NotifyComposite(n); + } + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ImageBridgeChild::RecvReportFramesDropped( + const CompositableHandle& aHandle, const uint32_t& aFrames) { + RefPtr listener = FindListener(aHandle); + if (listener) { + listener->NotifyDropped(aFrames); + } + + return IPC_OK(); +} + +PTextureChild* ImageBridgeChild::CreateTexture( + const SurfaceDescriptor& aSharedData, const ReadLockDescriptor& aReadLock, + LayersBackend aLayersBackend, TextureFlags aFlags, uint64_t aSerial, + wr::MaybeExternalImageId& aExternalImageId, nsISerialEventTarget* aTarget) { + MOZ_ASSERT(CanSend()); + return SendPTextureConstructor(aSharedData, aReadLock, aLayersBackend, aFlags, + aSerial, aExternalImageId); +} + +static bool IBCAddOpDestroy(CompositableTransaction* aTxn, + const OpDestroy& op) { + if (aTxn->Finished()) { + return false; + } + + aTxn->mDestroyedActors.AppendElement(op); + return true; +} + +bool ImageBridgeChild::DestroyInTransaction(PTextureChild* aTexture) { + return IBCAddOpDestroy(mTxn, OpDestroy(aTexture)); +} + +bool ImageBridgeChild::DestroyInTransaction(const CompositableHandle& aHandle) { + return IBCAddOpDestroy(mTxn, OpDestroy(aHandle)); +} + +void ImageBridgeChild::RemoveTextureFromCompositable( + CompositableClient* aCompositable, TextureClient* aTexture) { + MOZ_ASSERT(CanSend()); + MOZ_ASSERT(aTexture); + MOZ_ASSERT(aTexture->IsSharedWithCompositor()); + MOZ_ASSERT(aCompositable->IsConnected()); + if (!aTexture || !aTexture->IsSharedWithCompositor() || + !aCompositable->IsConnected()) { + return; + } + + mTxn->AddNoSwapEdit(CompositableOperation( + aCompositable->GetIPCHandle(), + OpRemoveTexture(nullptr, aTexture->GetIPDLActor()))); +} + +bool ImageBridgeChild::IsSameProcess() const { + return OtherPid() == base::GetCurrentProcId(); +} + +bool ImageBridgeChild::CanPostTask() const { + // During shutdown, the cycle collector may free objects that are holding a + // reference to ImageBridgeChild. Since this happens on the main thread, + // ImageBridgeChild will attempt to post a task to the ImageBridge thread. + // However the thread manager has already been shut down, so the task cannot + // post. + // + // It's okay if this races. We only care about the shutdown case where + // everything's happening on the main thread. Even if it races outside of + // shutdown, it's still harmless to post the task, since the task must + // check CanSend(). + return !mDestroyed; +} + +void ImageBridgeChild::ReleaseCompositable(const CompositableHandle& aHandle) { + if (!InImageBridgeChildThread()) { + // If we can't post a task, then we definitely cannot send, so there's + // no reason to queue up this send. + if (!CanPostTask()) { + return; + } + + RefPtr runnable = + WrapRunnable(RefPtr(this), + &ImageBridgeChild::ReleaseCompositable, aHandle); + GetThread()->Dispatch(runnable.forget()); + return; + } + + if (!CanSend()) { + return; + } + + if (!DestroyInTransaction(aHandle)) { + SendReleaseCompositable(aHandle); + } + + { + MutexAutoLock lock(mContainerMapLock); + mImageContainerListeners.erase(aHandle.Value()); + } +} + +bool ImageBridgeChild::CanSend() const { + MOZ_ASSERT(InImageBridgeChildThread()); + return mCanSend; +} + +void ImageBridgeChild::HandleFatalError(const char* aMsg) const { + dom::ContentChild::FatalErrorIfNotUsingGPUProcess(aMsg, OtherPid()); +} + +wr::MaybeExternalImageId ImageBridgeChild::GetNextExternalImageId() { + static uint32_t sNextID = 1; + ++sNextID; + MOZ_RELEASE_ASSERT(sNextID != UINT32_MAX); + + uint64_t imageId = mNamespace; + imageId = imageId << 32 | sNextID; + return Some(wr::ToExternalImageId(imageId)); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/ImageBridgeChild.h b/gfx/layers/ipc/ImageBridgeChild.h new file mode 100644 index 0000000000..76663f7ffd --- /dev/null +++ b/gfx/layers/ipc/ImageBridgeChild.h @@ -0,0 +1,390 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_IMAGEBRIDGECHILD_H +#define MOZILLA_GFX_IMAGEBRIDGECHILD_H + +#include // for size_t +#include // for uint32_t, uint64_t +#include + +#include "mozilla/Attributes.h" // for override +#include "mozilla/Atomics.h" +#include "mozilla/RefPtr.h" // for already_AddRefed +#include "mozilla/ipc/SharedMemory.h" // for SharedMemory, etc +#include "mozilla/layers/CompositableForwarder.h" +#include "mozilla/layers/CompositorTypes.h" +#include "mozilla/layers/PImageBridgeChild.h" +#include "mozilla/layers/TextureForwarder.h" +#include "mozilla/Mutex.h" +#include "mozilla/webrender/WebRenderTypes.h" +#include "nsRegion.h" // for nsIntRegion +#include "mozilla/gfx/Rect.h" +#include "mozilla/ReentrantMonitor.h" // for ReentrantMonitor, etc + +namespace mozilla { +namespace ipc { +class Shmem; +} // namespace ipc + +namespace layers { + +class ImageClient; +class ImageContainer; +class ImageContainerListener; +class ImageBridgeParent; +class CompositableClient; +struct CompositableTransaction; +class Image; +class TextureClient; +class SynchronousTask; + +/** + * Returns true if the current thread is the ImageBrdigeChild's thread. + * + * Can be called from any thread. + */ +bool InImageBridgeChildThread(); + +/** + * The ImageBridge protocol is meant to allow ImageContainers to forward images + * directly to the compositor thread/process without using the main thread. + * + * ImageBridgeChild is a CompositableForwarder just like ShadowLayerForwarder. + * This means it also does transactions with the compositor thread/process, + * except that the transactions are restricted to operations on the + * Compositables and cannot contain messages affecting layers directly. + * + * ImageBridgeChild is also a ISurfaceAllocator. It can be used to allocate or + * deallocate data that is shared with the compositor. The main differerence + * with other ISurfaceAllocators is that some of its overriden methods can be + * invoked from any thread. + * + * There are three important phases in the ImageBridge protocol. These three + * steps can do different things depending if (A) the ImageContainer uses + * ImageBridge or (B) it does not use ImageBridge: + * + * - When an ImageContainer calls its method SetCurrentImage: + * - (A) The image is sent directly to the compositor process through the + * ImageBridge IPDL protocol. + * On the compositor side the image is stored in a global table that + * associates the image with an ID corresponding to the ImageContainer, and a + * composition is triggered. + * - (B) Since it does not have an ImageBridge, the image is not sent yet. + * instead the will be sent to the compositor during the next layer + * transaction (on the main thread). + * + * - During a Layer transaction: + * - (A) The ImageContainer uses ImageBridge. The image is already available + * to the compositor process because it has been sent with SetCurrentImage. + * Yet, the CompositableHost on the compositor side will needs the ID + * referring to the ImageContainer to access the Image. So during the Swap + * operation that happens in the transaction, we swap the container ID rather + * than the image data. + * - (B) Since the ImageContainer does not use ImageBridge, the image data is + * swaped. + * + * - During composition: + * - (A) The CompositableHost has an AsyncID, it looks up the ID in the + * global table to see if there is an image. If there is no image, nothing is + * rendered. + * - (B) The CompositableHost has image data rather than an ID (meaning it is + * not using ImageBridge), then it just composites the image data normally. + * + * This means that there might be a possibility for the ImageBridge to send the + * first frame before the first layer transaction that will pass the container + * ID to the CompositableHost happens. In this (unlikely) case the layer is not + * composited until the layer transaction happens. This means this scenario is + * not harmful. + * + * Since sending an image through imageBridge triggers compositing, the main + * thread is not used at all (except for the very first transaction that + * provides the CompositableHost with an AsyncID). + */ +class ImageBridgeChild final : public PImageBridgeChild, + public CompositableForwarder, + public TextureForwarder { + friend class ImageContainer; + + typedef nsTArray AsyncParentMessageArray; + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ImageBridgeChild, override); + + TextureForwarder* GetTextureForwarder() override { return this; } + LayersIPCActor* GetLayersIPCActor() override { return this; } + + /** + * Creates the image bridge with a dedicated thread for ImageBridgeChild. + * + * We may want to use a specifi thread in the future. In this case, use + * CreateWithThread instead. + */ + static void InitSameProcess(uint32_t aNamespace); + + static void InitWithGPUProcess(Endpoint&& aEndpoint, + uint32_t aNamespace); + static bool InitForContent(Endpoint&& aEndpoint, + uint32_t aNamespace); + static bool ReinitForContent(Endpoint&& aEndpoint, + uint32_t aNamespace); + + /** + * Destroys the image bridge by calling DestroyBridge, and destroys the + * ImageBridge's thread. + * + * If you don't want to destroy the thread, call DestroyBridge directly + * instead. + */ + static void ShutDown(); + + /** + * returns the singleton instance. + * + * can be called from any thread. + */ + static RefPtr GetSingleton(); + + static void IdentifyCompositorTextureHost( + const TextureFactoryIdentifier& aIdentifier); + + void BeginTransaction(); + void EndTransaction(); + + /** + * Returns the ImageBridgeChild's thread. + * + * Can be called from any thread. + */ + nsISerialEventTarget* GetThread() const override; + + base::ProcessId GetParentPid() const override { return OtherPid(); } + + PTextureChild* AllocPTextureChild( + const SurfaceDescriptor& aSharedData, const ReadLockDescriptor& aReadLock, + const LayersBackend& aLayersBackend, const TextureFlags& aFlags, + const uint64_t& aSerial, + const wr::MaybeExternalImageId& aExternalImageId); + + bool DeallocPTextureChild(PTextureChild* actor); + + PMediaSystemResourceManagerChild* AllocPMediaSystemResourceManagerChild(); + bool DeallocPMediaSystemResourceManagerChild( + PMediaSystemResourceManagerChild* aActor); + + mozilla::ipc::IPCResult RecvParentAsyncMessages( + nsTArray&& aMessages); + + mozilla::ipc::IPCResult RecvDidComposite( + nsTArray&& aNotifications); + + mozilla::ipc::IPCResult RecvReportFramesDropped( + const CompositableHandle& aHandle, const uint32_t& aFrames); + + // Create an ImageClient from any thread. + RefPtr CreateImageClient(CompositableType aType, + ImageContainer* aImageContainer); + + // Create an ImageClient from the ImageBridge thread. + RefPtr CreateImageClientNow(CompositableType aType, + ImageContainer* aImageContainer); + + void UpdateImageClient(RefPtr aContainer); + + /** + * Flush all Images sent to CompositableHost. + */ + void FlushAllImages(ImageClient* aClient, ImageContainer* aContainer); + + bool IPCOpen() const override { return mCanSend; } + + private: + /** + * This must be called by the static function DeleteImageBridgeSync defined + * in ImageBridgeChild.cpp ONLY. + */ + virtual ~ImageBridgeChild(); + + // Helpers for dispatching. + void CreateImageClientSync(SynchronousTask* aTask, + RefPtr* result, + CompositableType aType, + ImageContainer* aImageContainer); + + void FlushAllImagesSync(SynchronousTask* aTask, ImageClient* aClient, + ImageContainer* aContainer); + + void ProxyAllocShmemNow(SynchronousTask* aTask, size_t aSize, + SharedMemory::SharedMemoryType aType, + mozilla::ipc::Shmem* aShmem, bool aUnsafe, + bool* aSuccess); + void ProxyDeallocShmemNow(SynchronousTask* aTask, mozilla::ipc::Shmem* aShmem, + bool* aResult); + + void UpdateTextureFactoryIdentifier( + const TextureFactoryIdentifier& aIdentifier); + + public: + // CompositableForwarder + + void Connect(CompositableClient* aCompositable, + ImageContainer* aImageContainer) override; + + bool UsesImageBridge() const override { return true; } + + /** + * See CompositableForwarder::UseTextures + */ + void UseTextures(CompositableClient* aCompositable, + const nsTArray& aTextures) override; + void UseComponentAlphaTextures(CompositableClient* aCompositable, + TextureClient* aClientOnBlack, + TextureClient* aClientOnWhite) override; + + void ReleaseCompositable(const CompositableHandle& aHandle) override; + + void ForgetImageContainer(const CompositableHandle& aHandle); + + /** + * Hold TextureClient ref until end of usage on host side if + * TextureFlags::RECYCLE is set. Host side's usage is checked via + * CompositableRef. + */ + void HoldUntilCompositableRefReleasedIfNecessary(TextureClient* aClient); + + /** + * Notify id of Texture When host side end its use. Transaction id is used to + * make sure if there is no newer usage. + */ + void NotifyNotUsed(uint64_t aTextureId, uint64_t aFwdTransactionId); + + void CancelWaitForNotifyNotUsed(uint64_t aTextureId) override; + + bool DestroyInTransaction(PTextureChild* aTexture) override; + bool DestroyInTransaction(const CompositableHandle& aHandle); + + void RemoveTextureFromCompositable(CompositableClient* aCompositable, + TextureClient* aTexture) override; + + void UseTiledLayerBuffer( + CompositableClient* aCompositable, + const SurfaceDescriptorTiles& aTileLayerDescriptor) override { + MOZ_CRASH("should not be called"); + } + + void UpdateTextureRegion(CompositableClient* aCompositable, + const ThebesBufferData& aThebesBufferData, + const nsIntRegion& aUpdatedRegion) override { + MOZ_CRASH("should not be called"); + } + + // ISurfaceAllocator + + /** + * See ISurfaceAllocator.h + * Can be used from any thread. + * If used outside the ImageBridgeChild thread, it will proxy a synchronous + * call on the ImageBridgeChild thread. + */ + bool AllocUnsafeShmem(size_t aSize, + mozilla::ipc::SharedMemory::SharedMemoryType aShmType, + mozilla::ipc::Shmem* aShmem) override; + bool AllocShmem(size_t aSize, + mozilla::ipc::SharedMemory::SharedMemoryType aShmType, + mozilla::ipc::Shmem* aShmem) override; + + /** + * See ISurfaceAllocator.h + * Can be used from any thread. + * If used outside the ImageBridgeChild thread, it will proxy a synchronous + * call on the ImageBridgeChild thread. + */ + bool DeallocShmem(mozilla::ipc::Shmem& aShmem) override; + + PTextureChild* CreateTexture( + const SurfaceDescriptor& aSharedData, const ReadLockDescriptor& aReadLock, + LayersBackend aLayersBackend, TextureFlags aFlags, uint64_t aSerial, + wr::MaybeExternalImageId& aExternalImageId, + nsISerialEventTarget* aTarget = nullptr) override; + + bool IsSameProcess() const override; + + void UpdateFwdTransactionId() override { ++mFwdTransactionId; } + uint64_t GetFwdTransactionId() override { return mFwdTransactionId; } + + bool InForwarderThread() override { return InImageBridgeChildThread(); } + + void HandleFatalError(const char* aMsg) const override; + + wr::MaybeExternalImageId GetNextExternalImageId() override; + + protected: + explicit ImageBridgeChild(uint32_t aNamespace); + bool DispatchAllocShmemInternal(size_t aSize, + SharedMemory::SharedMemoryType aType, + Shmem* aShmem, bool aUnsafe); + + void Bind(Endpoint&& aEndpoint); + void BindSameProcess(RefPtr aParent); + + void SendImageBridgeThreadId(); + + void WillShutdown(); + void ShutdownStep1(SynchronousTask* aTask); + void ShutdownStep2(SynchronousTask* aTask); + void MarkShutDown(); + + void ActorDestroy(ActorDestroyReason aWhy) override; + void ActorDealloc() override; + + bool CanSend() const; + bool CanPostTask() const; + + static void ShutdownSingleton(); + + private: + uint32_t mNamespace; + + CompositableTransaction* mTxn; + + bool mCanSend; + mozilla::Atomic mDestroyed; + + /** + * Transaction id of CompositableForwarder. + * It is incrementaed by UpdateFwdTransactionId() in each BeginTransaction() + * call. + */ + uint64_t mFwdTransactionId; + + /** + * Hold TextureClients refs until end of their usages on host side. + * It defer calling of TextureClient recycle callback. + */ + std::unordered_map> + mTexturesWaitingNotifyNotUsed; + + /** + * Mapping from async compositable IDs to image containers. + */ + Mutex mContainerMapLock; + std::unordered_map> + mImageContainerListeners; + RefPtr FindListener( + const CompositableHandle& aHandle); + +#if defined(XP_WIN) + /** + * Used for checking if D3D11Device is updated. + */ + RefPtr mImageDevice; +#endif +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/ipc/ImageBridgeParent.cpp b/gfx/layers/ipc/ImageBridgeParent.cpp new file mode 100644 index 0000000000..8022b76687 --- /dev/null +++ b/gfx/layers/ipc/ImageBridgeParent.cpp @@ -0,0 +1,796 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ImageBridgeParent.h" +#include // for uint64_t, uint32_t +#include "CompositableHost.h" // for CompositableParent, Create +#include "GeckoProfiler.h" +#include "base/process.h" // for ProcessId +#include "base/task.h" // for CancelableTask, DeleteTask, etc +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/Hal.h" // for hal::SetCurrentThreadPriority() +#include "mozilla/HalTypes.h" // for hal::THREAD_PRIORITY_COMPOSITOR +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/MessageChannel.h" // for MessageChannel, etc +#include "mozilla/ipc/Transport.h" // for Transport +#include "mozilla/media/MediaSystemResourceManagerParent.h" // for MediaSystemResourceManagerParent +#include "mozilla/layers/BufferTexture.h" +#include "mozilla/layers/CompositableTransactionParent.h" +#include "mozilla/layers/LayerManagerComposite.h" +#include "mozilla/layers/LayersMessages.h" // for EditReply +#include "mozilla/layers/PImageBridgeParent.h" +#include "mozilla/layers/TextureHostOGL.h" // for TextureHostOGL +#include "mozilla/layers/Compositor.h" +#include "mozilla/Monitor.h" +#include "mozilla/mozalloc.h" // for operator new, etc +#include "mozilla/Unused.h" +#include "nsDebug.h" // for NS_ASSERTION, etc +#include "nsISupportsImpl.h" // for ImageBridgeParent::Release, etc +#include "nsTArray.h" // for nsTArray, nsTArray_Impl +#include "nsTArrayForwardDeclare.h" // for nsTArray +#include "nsXULAppAPI.h" // for XRE_GetIOMessageLoop +#include "mozilla/layers/TextureHost.h" +#include "nsThreadUtils.h" + +#if defined(OS_WIN) +# include "mozilla/layers/TextureD3D11.h" +#endif + +#ifdef MOZ_WIDGET_ANDROID +# include "mozilla/layers/AndroidHardwareBuffer.h" +#endif + +namespace mozilla { +namespace layers { + +using namespace mozilla::ipc; +using namespace mozilla::gfx; +using namespace mozilla::media; + +ImageBridgeParent::ImageBridgeMap ImageBridgeParent::sImageBridges; + +StaticAutoPtr sImageBridgesLock; + +static StaticRefPtr sImageBridgeParentSingleton; + +/* static */ +void ImageBridgeParent::Setup() { + MOZ_ASSERT(NS_IsMainThread()); + if (!sImageBridgesLock) { + sImageBridgesLock = new Monitor("ImageBridges"); + mozilla::ClearOnShutdown(&sImageBridgesLock); + } +} + +ImageBridgeParent::ImageBridgeParent(nsISerialEventTarget* aThread, + ProcessId aChildProcessId) + : mThread(aThread), + mClosed(false), + mCompositorThreadHolder(CompositorThreadHolder::GetSingleton()) { + MOZ_ASSERT(NS_IsMainThread()); + SetOtherProcessId(aChildProcessId); +} + +ImageBridgeParent::~ImageBridgeParent() = default; + +/* static */ +ImageBridgeParent* ImageBridgeParent::CreateSameProcess() { + base::ProcessId pid = base::GetCurrentProcId(); + RefPtr parent = + new ImageBridgeParent(CompositorThread(), pid); + parent->mSelfRef = parent; + + { + MonitorAutoLock lock(*sImageBridgesLock); + MOZ_RELEASE_ASSERT(sImageBridges.count(pid) == 0); + sImageBridges[pid] = parent; + } + + sImageBridgeParentSingleton = parent; + return parent; +} + +/* static */ +bool ImageBridgeParent::CreateForGPUProcess( + Endpoint&& aEndpoint) { + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_GPU); + + nsCOMPtr compositorThread = CompositorThread(); + if (!compositorThread) { + return false; + } + + RefPtr parent = + new ImageBridgeParent(compositorThread, aEndpoint.OtherPid()); + + compositorThread->Dispatch(NewRunnableMethod&&>( + "layers::ImageBridgeParent::Bind", parent, &ImageBridgeParent::Bind, + std::move(aEndpoint))); + + sImageBridgeParentSingleton = parent; + return true; +} + +/* static */ +void ImageBridgeParent::ShutdownInternal() { + // We make a copy because we don't want to hold the lock while closing and we + // don't want the object to get freed underneath us. + nsTArray> actors; + { + MonitorAutoLock lock(*sImageBridgesLock); + for (const auto& iter : sImageBridges) { + actors.AppendElement(iter.second); + } + } + + for (auto const& actor : actors) { + MOZ_RELEASE_ASSERT(!actor->mClosed); + actor->Close(); + } + + sImageBridgeParentSingleton = nullptr; +} + +/* static */ +void ImageBridgeParent::Shutdown() { + CompositorThread()->Dispatch(NS_NewRunnableFunction( + "ImageBridgeParent::Shutdown", + []() -> void { ImageBridgeParent::ShutdownInternal(); })); +} + +void ImageBridgeParent::ActorDestroy(ActorDestroyReason aWhy) { + // Can't alloc/dealloc shmems from now on. + mClosed = true; + mCompositables.clear(); + { + MonitorAutoLock lock(*sImageBridgesLock); + sImageBridges.erase(OtherPid()); + } + GetThread()->Dispatch( + NewRunnableMethod("layers::ImageBridgeParent::DeferredDestroy", this, + &ImageBridgeParent::DeferredDestroy)); + + // It is very important that this method gets called at shutdown (be it a + // clean or an abnormal shutdown), because DeferredDestroy is what clears + // mSelfRef. If mSelfRef is not null and ActorDestroy is not called, the + // ImageBridgeParent is leaked which causes the CompositorThreadHolder to be + // leaked and CompsoitorParent's shutdown ends up spinning the event loop + // forever, waiting for the compositor thread to terminate. +} + +class MOZ_STACK_CLASS AutoImageBridgeParentAsyncMessageSender final { + public: + explicit AutoImageBridgeParentAsyncMessageSender( + ImageBridgeParent* aImageBridge, + nsTArray* aToDestroy = nullptr) + : mImageBridge(aImageBridge), mToDestroy(aToDestroy) { + mImageBridge->SetAboutToSendAsyncMessages(); + } + + ~AutoImageBridgeParentAsyncMessageSender() { + mImageBridge->SendPendingAsyncMessages(); + if (mToDestroy) { + for (const auto& op : *mToDestroy) { + mImageBridge->DestroyActor(op); + } + } + } + + private: + ImageBridgeParent* mImageBridge; + nsTArray* mToDestroy; +}; + +mozilla::ipc::IPCResult ImageBridgeParent::RecvUpdate( + EditArray&& aEdits, OpDestroyArray&& aToDestroy, + const uint64_t& aFwdTransactionId) { + AUTO_PROFILER_TRACING_MARKER("Paint", "ImageBridgeTransaction", GRAPHICS); + AUTO_PROFILER_LABEL("ImageBridgeParent::RecvUpdate", GRAPHICS); + + // This ensures that destroy operations are always processed. It is not safe + // to early-return from RecvUpdate without doing so. + AutoImageBridgeParentAsyncMessageSender autoAsyncMessageSender(this, + &aToDestroy); + UpdateFwdTransactionId(aFwdTransactionId); + + for (const auto& edit : aEdits) { + RefPtr compositable = + FindCompositable(edit.compositable()); + if (!compositable || + !ReceiveCompositableUpdate(edit.detail(), WrapNotNull(compositable))) { + return IPC_FAIL_NO_REASON(this); + } + uint32_t dropped = compositable->GetDroppedFrames(); + if (dropped) { + Unused << SendReportFramesDropped(edit.compositable(), dropped); + } + } + + if (!IsSameProcess()) { + // Ensure that any pending operations involving back and front + // buffers have completed, so that neither process stomps on the + // other's buffer contents. + LayerManagerComposite::PlatformSyncBeforeReplyUpdate(); + } + + return IPC_OK(); +} + +/* static */ +bool ImageBridgeParent::CreateForContent( + Endpoint&& aEndpoint) { + nsCOMPtr compositorThread = CompositorThread(); + if (!compositorThread) { + return false; + } + + RefPtr bridge = + new ImageBridgeParent(compositorThread, aEndpoint.OtherPid()); + compositorThread->Dispatch(NewRunnableMethod&&>( + "layers::ImageBridgeParent::Bind", bridge, &ImageBridgeParent::Bind, + std::move(aEndpoint))); + + return true; +} + +void ImageBridgeParent::Bind(Endpoint&& aEndpoint) { + if (!aEndpoint.Bind(this)) return; + mSelfRef = this; + + // If the child process ID was reused by the OS before the ImageBridgeParent + // object was destroyed, we need to clean it up first. + RefPtr oldActor; + { + MonitorAutoLock lock(*sImageBridgesLock); + ImageBridgeMap::const_iterator i = sImageBridges.find(OtherPid()); + if (i != sImageBridges.end()) { + oldActor = i->second; + } + } + + // We can't hold the lock during Close because it erases itself from the map. + if (oldActor) { + MOZ_RELEASE_ASSERT(!oldActor->mClosed); + oldActor->Close(); + } + + { + MonitorAutoLock lock(*sImageBridgesLock); + sImageBridges[OtherPid()] = this; + } +} + +mozilla::ipc::IPCResult ImageBridgeParent::RecvWillClose() { + // If there is any texture still alive we have to force it to deallocate the + // device data (GL textures, etc.) now because shortly after SenStop() returns + // on the child side the widget will be destroyed along with it's associated + // GL context. + nsTArray textures; + ManagedPTextureParent(textures); + for (unsigned int i = 0; i < textures.Length(); ++i) { + RefPtr tex = TextureHost::AsTextureHost(textures[i]); + tex->DeallocateDeviceData(); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ImageBridgeParent::RecvNewCompositable( + const CompositableHandle& aHandle, const TextureInfo& aInfo, + const LayersBackend& aLayersBackend) { + bool useWebRender = aLayersBackend == LayersBackend::LAYERS_WR; + RefPtr host = AddCompositable(aHandle, aInfo, useWebRender); + if (!host) { + return IPC_FAIL_NO_REASON(this); + } + + host->SetAsyncRef(AsyncCompositableRef(OtherPid(), aHandle)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ImageBridgeParent::RecvReleaseCompositable( + const CompositableHandle& aHandle) { + ReleaseCompositable(aHandle); + return IPC_OK(); +} + +PTextureParent* ImageBridgeParent::AllocPTextureParent( + const SurfaceDescriptor& aSharedData, const ReadLockDescriptor& aReadLock, + const LayersBackend& aLayersBackend, const TextureFlags& aFlags, + const uint64_t& aSerial, const wr::MaybeExternalImageId& aExternalImageId) { + return TextureHost::CreateIPDLActor(this, aSharedData, aReadLock, + aLayersBackend, aFlags, aSerial, + aExternalImageId); +} + +bool ImageBridgeParent::DeallocPTextureParent(PTextureParent* actor) { + return TextureHost::DestroyIPDLActor(actor); +} + +PMediaSystemResourceManagerParent* +ImageBridgeParent::AllocPMediaSystemResourceManagerParent() { + return new mozilla::media::MediaSystemResourceManagerParent(); +} + +bool ImageBridgeParent::DeallocPMediaSystemResourceManagerParent( + PMediaSystemResourceManagerParent* aActor) { + MOZ_ASSERT(aActor); + delete static_cast(aActor); + return true; +} + +void ImageBridgeParent::SendAsyncMessage( + const nsTArray& aMessage) { + mozilla::Unused << SendParentAsyncMessages(aMessage); +} + +class ProcessIdComparator { + public: + bool Equals(const ImageCompositeNotificationInfo& aA, + const ImageCompositeNotificationInfo& aB) const { + return aA.mImageBridgeProcessId == aB.mImageBridgeProcessId; + } + bool LessThan(const ImageCompositeNotificationInfo& aA, + const ImageCompositeNotificationInfo& aB) const { + return aA.mImageBridgeProcessId < aB.mImageBridgeProcessId; + } +}; + +/* static */ +bool ImageBridgeParent::NotifyImageComposites( + nsTArray& aNotifications) { + // Group the notifications by destination process ID and then send the + // notifications in one message per group. + aNotifications.Sort(ProcessIdComparator()); + uint32_t i = 0; + bool ok = true; + while (i < aNotifications.Length()) { + AutoTArray notifications; + notifications.AppendElement(aNotifications[i].mNotification); + uint32_t end = i + 1; + MOZ_ASSERT(aNotifications[i].mNotification.compositable()); + ProcessId pid = aNotifications[i].mImageBridgeProcessId; + while (end < aNotifications.Length() && + aNotifications[end].mImageBridgeProcessId == pid) { + notifications.AppendElement(aNotifications[end].mNotification); + ++end; + } + RefPtr bridge = GetInstance(pid); + if (!bridge || bridge->mClosed) { + i = end; + continue; + } + bridge->SendPendingAsyncMessages(); + if (!bridge->SendDidComposite(notifications)) { + ok = false; + } + i = end; + } + return ok; +} + +void ImageBridgeParent::DeferredDestroy() { + mCompositorThreadHolder = nullptr; + mSelfRef = nullptr; // "this" ImageBridge may get deleted here. +} + +already_AddRefed ImageBridgeParent::GetInstance( + ProcessId aId) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + MonitorAutoLock lock(*sImageBridgesLock); + ImageBridgeMap::const_iterator i = sImageBridges.find(aId); + if (i == sImageBridges.end()) { + NS_WARNING("Cannot find image bridge for process!"); + return nullptr; + } + RefPtr bridge = i->second; + return bridge.forget(); +} + +bool ImageBridgeParent::AllocShmem(size_t aSize, + ipc::SharedMemory::SharedMemoryType aType, + ipc::Shmem* aShmem) { + if (mClosed) { + return false; + } + return PImageBridgeParent::AllocShmem(aSize, aType, aShmem); +} + +bool ImageBridgeParent::AllocUnsafeShmem( + size_t aSize, ipc::SharedMemory::SharedMemoryType aType, + ipc::Shmem* aShmem) { + if (mClosed) { + return false; + } + return PImageBridgeParent::AllocUnsafeShmem(aSize, aType, aShmem); +} + +bool ImageBridgeParent::DeallocShmem(ipc::Shmem& aShmem) { + if (mClosed) { + return false; + } + return PImageBridgeParent::DeallocShmem(aShmem); +} + +bool ImageBridgeParent::IsSameProcess() const { + return OtherPid() == base::GetCurrentProcId(); +} + +void ImageBridgeParent::NotifyNotUsed(PTextureParent* aTexture, + uint64_t aTransactionId) { + RefPtr texture = TextureHost::AsTextureHost(aTexture); + if (!texture) { + return; + } + +#ifdef MOZ_WIDGET_ANDROID + if (auto hardwareBuffer = texture->GetAndroidHardwareBuffer()) { + MOZ_ASSERT(texture->GetFlags() & TextureFlags::RECYCLE); + + Maybe fenceFd = Some(FileDescriptor()); + auto* compositor = texture->GetProvider() + ? texture->GetProvider()->AsCompositorOGL() + : nullptr; + if (compositor) { + fenceFd = Some(compositor->GetReleaseFence()); + } + + auto* wrTexture = texture->AsWebRenderTextureHost(); + if (wrTexture) { + MOZ_ASSERT(!fenceFd->IsValid()); + fenceFd = Some(texture->GetAndResetReleaseFence()); + } + + // Invalid file descriptor could not be sent via IPC, but + // OpDeliverReleaseFence message needs to be sent to child side. + if (!fenceFd->IsValid()) { + fenceFd = Nothing(); + } + mPendingAsyncMessage.push_back(OpDeliverReleaseFence( + std::move(fenceFd), hardwareBuffer->mId, aTransactionId, + /* usesImageBridge */ true)); + } +#endif + + if (!(texture->GetFlags() & TextureFlags::RECYCLE) && + !(texture->GetFlags() & TextureFlags::WAIT_HOST_USAGE_END)) { + return; + } + + uint64_t textureId = TextureHost::GetTextureSerial(aTexture); + mPendingAsyncMessage.push_back(OpNotifyNotUsed(textureId, aTransactionId)); + + if (!IsAboutToSendAsyncMessages()) { + SendPendingAsyncMessages(); + } +} + +/* static */ +void ImageBridgeParent::NotifyBufferNotUsedOfCompositorBridge( + base::ProcessId aChildProcessId, TextureHost* aTexture, + uint64_t aTransactionId) { + RefPtr bridge = GetInstance(aChildProcessId); + if (!bridge || bridge->mClosed) { + return; + } + bridge->NotifyBufferNotUsedOfCompositorBridge(aTexture, aTransactionId); +} + +void ImageBridgeParent::NotifyBufferNotUsedOfCompositorBridge( + TextureHost* aTexture, uint64_t aTransactionId) { + MOZ_ASSERT(aTexture); + MOZ_ASSERT(aTexture->GetAndroidHardwareBuffer()); + +#ifdef MOZ_WIDGET_ANDROID + auto* compositor = aTexture->GetProvider() + ? aTexture->GetProvider()->AsCompositorOGL() + : nullptr; + Maybe fenceFd = Some(FileDescriptor()); + if (compositor) { + fenceFd = Some(compositor->GetReleaseFence()); + } + + auto* wrTexture = aTexture->AsWebRenderTextureHost(); + if (wrTexture) { + MOZ_ASSERT(!fenceFd->IsValid()); + fenceFd = Some(aTexture->GetAndResetReleaseFence()); + } + + // Invalid file descriptor could not be sent via IPC, but + // OpDeliverReleaseFence message needs to be sent to child side. + if (!fenceFd->IsValid()) { + fenceFd = Nothing(); + } + mPendingAsyncMessage.push_back( + OpDeliverReleaseFence(fenceFd, aTexture->GetAndroidHardwareBuffer()->mId, + aTransactionId, /* usesImageBridge */ false)); + SendPendingAsyncMessages(); +#else + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); +#endif +} + +#if defined(OS_WIN) + +ImageBridgeParent::PluginTextureDatas::PluginTextureDatas( + UniquePtr&& aPluginTextureData, + UniquePtr&& aDisplayTextureData) + : mPluginTextureData(std::move(aPluginTextureData)), + mDisplayTextureData(std::move(aDisplayTextureData)) {} + +ImageBridgeParent::PluginTextureDatas::~PluginTextureDatas() {} + +#endif // defined(OS_WIN) + +mozilla::ipc::IPCResult ImageBridgeParent::RecvMakeAsyncPluginSurfaces( + SurfaceFormat aFormat, IntSize aSize, SurfaceDescriptorPlugin* aSD) { +#if defined(OS_WIN) + *aSD = SurfaceDescriptorPlugin(); + + RefPtr d3dDevice = + DeviceManagerDx::Get()->GetCompositorDevice(); + if (!d3dDevice) { + NS_WARNING("Failed to get D3D11 device for plugin display"); + return IPC_OK(); + } + + auto pluginSurf = WrapUnique(D3D11TextureData::Create( + aSize, aFormat, ALLOC_FOR_OUT_OF_BAND_CONTENT, d3dDevice)); + if (!pluginSurf) { + NS_ERROR("Failed to create plugin surface"); + return IPC_OK(); + } + + auto dispSurf = WrapUnique(D3D11TextureData::Create( + aSize, aFormat, ALLOC_FOR_OUT_OF_BAND_CONTENT, d3dDevice)); + if (!dispSurf) { + NS_ERROR("Failed to create plugin display surface"); + return IPC_OK(); + } + + // Identify plugin surfaces with a simple non-zero 64-bit ID. + static uint64_t sPluginSurfaceId = 1; + + SurfaceDescriptor pluginSD, dispSD; + if ((!pluginSurf->Serialize(pluginSD)) || (!dispSurf->Serialize(dispSD))) { + NS_ERROR("Failed to make surface descriptors for plugin"); + return IPC_OK(); + } + + if (!mPluginTextureDatas.put( + sPluginSurfaceId, MakeUnique( + std::move(pluginSurf), std::move(dispSurf)))) { + NS_ERROR("Failed to add plugin surfaces to map"); + return IPC_OK(); + } + + SurfaceDescriptorPlugin sd(sPluginSurfaceId, pluginSD, dispSD); + RefPtr displayHost = CreateTextureHostD3D11( + dispSD, this, LayersBackend::LAYERS_NONE, TextureFlags::RECYCLE); + if (!displayHost) { + NS_ERROR("Failed to create plugin display texture host"); + return IPC_OK(); + } + + if (!mGPUVideoTextureHosts.put(sPluginSurfaceId, displayHost)) { + NS_ERROR("Failed to add plugin display texture host to map"); + return IPC_OK(); + } + + *aSD = sd; + ++sPluginSurfaceId; +#endif // defined(OS_WIN) + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ImageBridgeParent::RecvUpdateAsyncPluginSurface( + const SurfaceDescriptorPlugin& aSD) { +#if defined(OS_WIN) + uint64_t surfaceId = aSD.id(); + auto itTextures = mPluginTextureDatas.lookup(surfaceId); + if (!itTextures) { + return IPC_OK(); + } + + auto& textures = itTextures->value(); + if (!textures->IsValid()) { + // The display texture may be gone. The plugin texture should never be gone + // here. + MOZ_ASSERT(textures->mPluginTextureData); + return IPC_OK(); + } + + RefPtr device = DeviceManagerDx::Get()->GetCompositorDevice(); + if (!device) { + NS_WARNING("Failed to get D3D11 device for plugin display"); + return IPC_OK(); + } + + RefPtr context; + device->GetImmediateContext(getter_AddRefs(context)); + if (!context) { + NS_WARNING("Could not get an immediate D3D11 context"); + return IPC_OK(); + } + + RefPtr dispMutex; + HRESULT hr = textures->mDisplayTextureData->GetD3D11Texture()->QueryInterface( + __uuidof(IDXGIKeyedMutex), (void**)getter_AddRefs(dispMutex)); + if (FAILED(hr) || !dispMutex) { + NS_WARNING("Could not acquire plugin display IDXGIKeyedMutex"); + return IPC_OK(); + } + + RefPtr pluginMutex; + hr = textures->mPluginTextureData->GetD3D11Texture()->QueryInterface( + __uuidof(IDXGIKeyedMutex), (void**)getter_AddRefs(pluginMutex)); + if (FAILED(hr) || !pluginMutex) { + NS_WARNING("Could not acquire plugin offscreen IDXGIKeyedMutex"); + return IPC_OK(); + } + + { + AutoTextureLock lock1(dispMutex, hr); + if (hr == WAIT_ABANDONED || hr == WAIT_TIMEOUT || FAILED(hr)) { + NS_WARNING( + "Could not acquire DXGI surface lock - display forgot to release?"); + return IPC_OK(); + } + + AutoTextureLock lock2(pluginMutex, hr); + if (hr == WAIT_ABANDONED || hr == WAIT_TIMEOUT || FAILED(hr)) { + NS_WARNING( + "Could not acquire DXGI surface lock - plugin forgot to release?"); + return IPC_OK(); + } + + context->CopyResource(textures->mDisplayTextureData->GetD3D11Texture(), + textures->mPluginTextureData->GetD3D11Texture()); + } +#endif // defined(OS_WIN) + return IPC_OK(); +} + +mozilla::ipc::IPCResult ImageBridgeParent::RecvReadbackAsyncPluginSurface( + const SurfaceDescriptorPlugin& aSD, SurfaceDescriptor* aResult) { +#if defined(OS_WIN) + *aResult = null_t(); + + auto itTextures = mPluginTextureDatas.lookup(aSD.id()); + if (!itTextures) { + return IPC_OK(); + } + + auto& textures = itTextures->value(); + D3D11TextureData* displayTexData = textures->mDisplayTextureData.get(); + MOZ_RELEASE_ASSERT(displayTexData); + if ((!displayTexData) || (!displayTexData->GetD3D11Texture())) { + NS_WARNING("Error in plugin display texture"); + return IPC_OK(); + } + MOZ_ASSERT(displayTexData->GetSurfaceFormat() == SurfaceFormat::B8G8R8A8 || + displayTexData->GetSurfaceFormat() == SurfaceFormat::B8G8R8X8); + + RefPtr device; + displayTexData->GetD3D11Texture()->GetDevice(getter_AddRefs(device)); + if (!device) { + NS_WARNING("Failed to get D3D11 device for plugin display"); + return IPC_OK(); + } + + UniquePtr shmemTexData(BufferTextureData::Create( + displayTexData->GetSize(), displayTexData->GetSurfaceFormat(), + gfx::BackendType::SKIA, LayersBackend::LAYERS_NONE, + displayTexData->GetTextureFlags(), TextureAllocationFlags::ALLOC_DEFAULT, + this)); + if (!shmemTexData) { + NS_WARNING("Could not create BufferTextureData"); + return IPC_OK(); + } + + if (!gfx::Factory::ReadbackTexture(shmemTexData.get(), + displayTexData->GetD3D11Texture())) { + NS_WARNING("Failed to read plugin texture into Shmem"); + return IPC_OK(); + } + + // Take the Shmem from the TextureData. + shmemTexData->Serialize(*aResult); +#endif // defined(OS_WIN) + return IPC_OK(); +} + +mozilla::ipc::IPCResult ImageBridgeParent::RecvRemoveAsyncPluginSurface( + const SurfaceDescriptorPlugin& aSD, bool aIsFrontSurface) { +#if defined(OS_WIN) + auto itTextures = mPluginTextureDatas.lookup(aSD.id()); + if (!itTextures) { + return IPC_OK(); + } + + auto& textures = itTextures->value(); + if (aIsFrontSurface) { + textures->mDisplayTextureData = nullptr; + } else { + textures->mPluginTextureData = nullptr; + } + if ((!textures->mDisplayTextureData) && (!textures->mPluginTextureData)) { + mPluginTextureDatas.remove(aSD.id()); + } +#endif // defined(OS_WIN) + return IPC_OK(); +} + +#if defined(OS_WIN) +RefPtr GetNullPluginTextureHost() { + class NullPluginTextureHost : public TextureHost { + public: + NullPluginTextureHost() : TextureHost(TextureFlags::NO_FLAGS) {} + + ~NullPluginTextureHost() {} + + gfx::SurfaceFormat GetFormat() const override { + return gfx::SurfaceFormat::UNKNOWN; + } + + already_AddRefed GetAsSurface() override { + return nullptr; + } + + gfx::IntSize GetSize() const override { return gfx::IntSize(); } + + bool BindTextureSource(CompositableTextureSourceRef& aTexture) override { + return false; + } + + const char* Name() override { return "NullPluginTextureHost"; } + + virtual bool Lock() { return false; } + + void CreateRenderTexture( + const wr::ExternalImageId& aExternalImageId) override {} + + uint32_t NumSubTextures() override { return 0; } + + void PushResourceUpdates(wr::TransactionBuilder& aResources, + ResourceUpdateOp aOp, + const Range& aImageKeys, + const wr::ExternalImageId& aExtID) override {} + + void PushDisplayItems(wr::DisplayListBuilder& aBuilder, + const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, + wr::ImageRendering aFilter, + const Range& aImageKeys, + PushDisplayItemFlagSet aFlags) override {} + }; + + static StaticRefPtr sNullPluginTextureHost; + if (!sNullPluginTextureHost) { + sNullPluginTextureHost = new NullPluginTextureHost(); + ClearOnShutdown(&sNullPluginTextureHost); + }; + + MOZ_ASSERT(sNullPluginTextureHost); + return sNullPluginTextureHost.get(); +} +#endif // defined(OS_WIN) + +RefPtr ImageBridgeParent::LookupTextureHost( + const SurfaceDescriptorPlugin& aDescriptor) { +#if defined(OS_WIN) + auto it = mGPUVideoTextureHosts.lookup(aDescriptor.id()); + RefPtr ret = it ? it->value() : nullptr; + return ret ? ret : GetNullPluginTextureHost(); +#else + MOZ_ASSERT_UNREACHABLE("Unsupported architecture."); + return nullptr; +#endif // defined(OS_WIN) +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/ImageBridgeParent.h b/gfx/layers/ipc/ImageBridgeParent.h new file mode 100644 index 0000000000..08680cab39 --- /dev/null +++ b/gfx/layers/ipc/ImageBridgeParent.h @@ -0,0 +1,187 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef gfx_layers_ipc_ImageBridgeParent_h_ +#define gfx_layers_ipc_ImageBridgeParent_h_ + +#include // for size_t +#include // for uint32_t, uint64_t +#include "CompositableTransactionParent.h" +#include "mozilla/Assertions.h" // for MOZ_ASSERT_HELPER2 +#include "mozilla/Attributes.h" // for override +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/ipc/SharedMemory.h" // for SharedMemory, etc +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/layers/PImageBridgeParent.h" +#include "nsISupportsImpl.h" +#include "nsTArrayForwardDeclare.h" // for nsTArray + +namespace mozilla { +namespace ipc { +class Shmem; +} // namespace ipc + +namespace layers { + +struct ImageCompositeNotificationInfo; + +/** + * ImageBridgeParent is the manager Protocol of async Compositables. + */ +class ImageBridgeParent final : public PImageBridgeParent, + public CompositableParentManager, + public mozilla::ipc::IShmemAllocator { + public: + typedef nsTArray EditArray; + typedef nsTArray OpDestroyArray; + + protected: + ImageBridgeParent(nsISerialEventTarget* aThread, ProcessId aChildProcessId); + + public: + virtual ~ImageBridgeParent(); + + /** + * Creates the globals of ImageBridgeParent. + */ + static void Setup(); + + static ImageBridgeParent* CreateSameProcess(); + static bool CreateForGPUProcess(Endpoint&& aEndpoint); + static bool CreateForContent(Endpoint&& aEndpoint); + static void Shutdown(); + + IShmemAllocator* AsShmemAllocator() override { return this; } + + void ActorDestroy(ActorDestroyReason aWhy) override; + + // CompositableParentManager + void SendAsyncMessage( + const nsTArray& aMessage) override; + + void NotifyNotUsed(PTextureParent* aTexture, + uint64_t aTransactionId) override; + + static void NotifyBufferNotUsedOfCompositorBridge( + base::ProcessId aChildProcessId, TextureHost* aTexture, + uint64_t aTransactionId); + + void NotifyBufferNotUsedOfCompositorBridge(TextureHost* aTexture, + uint64_t aTransactionId); + + base::ProcessId GetChildProcessId() override { return OtherPid(); } + + // PImageBridge + mozilla::ipc::IPCResult RecvUpdate(EditArray&& aEdits, + OpDestroyArray&& aToDestroy, + const uint64_t& aFwdTransactionId); + + PTextureParent* AllocPTextureParent( + const SurfaceDescriptor& aSharedData, const ReadLockDescriptor& aReadLock, + const LayersBackend& aLayersBackend, const TextureFlags& aFlags, + const uint64_t& aSerial, + const wr::MaybeExternalImageId& aExternalImageId); + bool DeallocPTextureParent(PTextureParent* actor); + + mozilla::ipc::IPCResult RecvNewCompositable( + const CompositableHandle& aHandle, const TextureInfo& aInfo, + const LayersBackend& aLayersBackend); + mozilla::ipc::IPCResult RecvReleaseCompositable( + const CompositableHandle& aHandle); + + PMediaSystemResourceManagerParent* AllocPMediaSystemResourceManagerParent(); + bool DeallocPMediaSystemResourceManagerParent( + PMediaSystemResourceManagerParent* aActor); + + // Shutdown step 1 + mozilla::ipc::IPCResult RecvWillClose(); + + nsISerialEventTarget* GetThread() const { return mThread; } + + // IShmemAllocator + + bool AllocShmem(size_t aSize, ipc::SharedMemory::SharedMemoryType aType, + ipc::Shmem* aShmem) override; + + bool AllocUnsafeShmem(size_t aSize, ipc::SharedMemory::SharedMemoryType aType, + ipc::Shmem* aShmem) override; + + bool DeallocShmem(ipc::Shmem& aShmem) override; + + bool IsSameProcess() const override; + + static already_AddRefed GetInstance(ProcessId aId); + + static bool NotifyImageComposites( + nsTArray& aNotifications); + + bool UsesImageBridge() const override { return true; } + + bool IPCOpen() const override { return !mClosed; } + + // See PluginInstanceParent for details on the Windows async plugin + // rendering protocol. + mozilla::ipc::IPCResult RecvMakeAsyncPluginSurfaces( + SurfaceFormat aFormat, IntSize aSize, SurfaceDescriptorPlugin* aSD); + mozilla::ipc::IPCResult RecvUpdateAsyncPluginSurface( + const SurfaceDescriptorPlugin& aSD); + mozilla::ipc::IPCResult RecvReadbackAsyncPluginSurface( + const SurfaceDescriptorPlugin& aSD, SurfaceDescriptor* aResult); + mozilla::ipc::IPCResult RecvRemoveAsyncPluginSurface( + const SurfaceDescriptorPlugin& aSD, bool aIsFrontSurface); + + RefPtr LookupTextureHost( + const SurfaceDescriptorPlugin& aDescriptor); + + protected: + void Bind(Endpoint&& aEndpoint); + + private: + static void ShutdownInternal(); + + void DeferredDestroy(); + nsCOMPtr mThread; + // This keeps us alive until ActorDestroy(), at which point we do a + // deferred destruction of ourselves. + RefPtr mSelfRef; + + bool mClosed; + + /** + * Map of all living ImageBridgeParent instances + */ + typedef std::map ImageBridgeMap; + static ImageBridgeMap sImageBridges; + + RefPtr mCompositorThreadHolder; + +#if defined(OS_WIN) + // Owns a pair of textures used to double-buffer a plugin async rendering + // instance. + struct PluginTextureDatas { + UniquePtr mPluginTextureData; + UniquePtr mDisplayTextureData; + + PluginTextureDatas(UniquePtr&& aPluginTextureData, + UniquePtr&& aDisplayTextureData); + + ~PluginTextureDatas(); + + PluginTextureDatas(const PluginTextureDatas& o) = delete; + PluginTextureDatas& operator=(const PluginTextureDatas& o) = delete; + + bool IsValid() { return mPluginTextureData && mDisplayTextureData; } + }; + + HashMap> mGPUVideoTextureHosts; + HashMap> mPluginTextureDatas; +#endif // defined(OS_WIN) +}; + +} // namespace layers +} // namespace mozilla + +#endif // gfx_layers_ipc_ImageBridgeParent_h_ diff --git a/gfx/layers/ipc/KnowsCompositor.h b/gfx/layers/ipc/KnowsCompositor.h new file mode 100644 index 0000000000..68fd77eda1 --- /dev/null +++ b/gfx/layers/ipc/KnowsCompositor.h @@ -0,0 +1,237 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_LAYERS_KNOWSCOMPOSITOR +#define MOZILLA_LAYERS_KNOWSCOMPOSITOR + +#include "mozilla/layers/LayersTypes.h" // for LayersBackend +#include "mozilla/layers/CompositorTypes.h" +#include "nsExpirationTracker.h" +#include "mozilla/DataMutex.h" +#include "mozilla/layers/SyncObject.h" + +namespace mozilla { +namespace layers { + +class TextureForwarder; +class LayersIPCActor; +class ImageBridgeChild; + +/** + * See ActiveResourceTracker below. + */ +class ActiveResource { + public: + virtual void NotifyInactive() = 0; + nsExpirationState* GetExpirationState() { return &mExpirationState; } + bool IsActivityTracked() { return mExpirationState.IsTracked(); } + + private: + nsExpirationState mExpirationState; +}; + +/** + * A convenience class on top of nsExpirationTracker + */ +class ActiveResourceTracker : public nsExpirationTracker { + public: + ActiveResourceTracker(uint32_t aExpirationCycle, const char* aName, + nsIEventTarget* aEventTarget) + : nsExpirationTracker(aExpirationCycle, aName, aEventTarget) {} + + void NotifyExpired(ActiveResource* aResource) override { + RemoveObject(aResource); + aResource->NotifyInactive(); + } +}; + +/** + * An abstract interface for classes that are tied to a specific Compositor + * across IPDL and uses TextureFactoryIdentifier to describe this Compositor. + */ +class KnowsCompositor { + public: + NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING + + KnowsCompositor(); + virtual ~KnowsCompositor(); + + void IdentifyTextureHost(const TextureFactoryIdentifier& aIdentifier); + + // The sync object for the global content device. + RefPtr GetSyncObject() { + auto lock = mData.Lock(); + if (lock.ref().mSyncObject) { + lock.ref().mSyncObject->EnsureInitialized(); + } + return lock.ref().mSyncObject; + } + + /// And by "thread-safe" here we merely mean "okay to hold strong references + /// to from multiple threads". Not all methods actually are thread-safe. + virtual bool IsThreadSafe() const { return true; } + + virtual RefPtr GetForMedia() { + return RefPtr(this); + } + + int32_t GetMaxTextureSize() const { + auto lock = mData.Lock(); + return lock.ref().mTextureFactoryIdentifier.mMaxTextureSize; + } + + /** + * Returns the type of backend that is used off the main thread. + * We only don't allow changing the backend type at runtime so this value can + * be queried once and will not change until Gecko is restarted. + */ + LayersBackend GetCompositorBackendType() const { + auto lock = mData.Lock(); + return lock.ref().mTextureFactoryIdentifier.mParentBackend; + } + + bool SupportsTextureBlitting() const { + auto lock = mData.Lock(); + return lock.ref().mTextureFactoryIdentifier.mSupportsTextureBlitting; + } + + bool SupportsPartialUploads() const { + auto lock = mData.Lock(); + return lock.ref().mTextureFactoryIdentifier.mSupportsPartialUploads; + } + + bool SupportsComponentAlpha() const { + auto lock = mData.Lock(); + return lock.ref().mTextureFactoryIdentifier.mSupportsComponentAlpha; + } + + bool SupportsTextureDirectMapping() const { + auto lock = mData.Lock(); + return lock.ref().mTextureFactoryIdentifier.mSupportsTextureDirectMapping; + } + + bool SupportsD3D11() const { + auto lock = mData.Lock(); + return lock.ref().mTextureFactoryIdentifier.mParentBackend == + layers::LayersBackend::LAYERS_D3D11 || + (lock.ref().mTextureFactoryIdentifier.mParentBackend == + layers::LayersBackend::LAYERS_WR && + (lock.ref().mTextureFactoryIdentifier.mCompositorUseANGLE || + lock.ref().mTextureFactoryIdentifier.mWebRenderCompositor == + layers::WebRenderCompositor::D3D11)); + } + + bool GetCompositorUseANGLE() const { + auto lock = mData.Lock(); + return lock.ref().mTextureFactoryIdentifier.mCompositorUseANGLE; + } + + bool GetCompositorUseDComp() const { + auto lock = mData.Lock(); + return lock.ref().mTextureFactoryIdentifier.mCompositorUseDComp; + } + + bool GetUseCompositorWnd() const { + auto lock = mData.Lock(); + return lock.ref().mTextureFactoryIdentifier.mUseCompositorWnd; + } + + bool UsingSoftwareWebRender() const { + auto lock = mData.Lock(); + return lock.ref().mTextureFactoryIdentifier.mParentBackend == + layers::LayersBackend::LAYERS_WR && + lock.ref().mTextureFactoryIdentifier.mWebRenderBackend == + WebRenderBackend::SOFTWARE; + } + + bool UsingSoftwareWebRenderD3D11() const { + auto lock = mData.Lock(); + return lock.ref().mTextureFactoryIdentifier.mParentBackend == + layers::LayersBackend::LAYERS_WR && + lock.ref().mTextureFactoryIdentifier.mWebRenderBackend == + WebRenderBackend::SOFTWARE && + lock.ref().mTextureFactoryIdentifier.mWebRenderCompositor == + layers::WebRenderCompositor::D3D11; + } + + TextureFactoryIdentifier GetTextureFactoryIdentifier() const { + auto lock = mData.Lock(); + return lock.ref().mTextureFactoryIdentifier; + } + + bool DeviceCanReset() const { + return GetCompositorBackendType() != LayersBackend::LAYERS_BASIC; + } + + int32_t GetSerial() const { return mSerial; } + + /** + * Sends a synchronous ping to the compsoitor. + * + * This is bad for performance and should only be called as a last resort if + * the compositor may be blocked for a long period of time, to avoid that the + * content process accumulates resource allocations that the compositor is not + * consuming and releasing. + */ + virtual void SyncWithCompositor() { MOZ_ASSERT_UNREACHABLE("Unimplemented"); } + + /** + * Helpers for finding other related interface. These are infallible. + */ + virtual TextureForwarder* GetTextureForwarder() = 0; + virtual LayersIPCActor* GetLayersIPCActor() = 0; + virtual ActiveResourceTracker* GetActiveResourceTracker() { + MOZ_ASSERT_UNREACHABLE("Unimplemented"); + return nullptr; + } + + protected: + struct SharedData { + TextureFactoryIdentifier mTextureFactoryIdentifier; + RefPtr mSyncObject; + }; + mutable DataMutex mData; + + const int32_t mSerial; + static mozilla::Atomic sSerialCounter; +}; + +/// Some implementations of KnowsCompositor can be used off their IPDL thread +/// like the ImageBridgeChild, and others just can't. Instead of passing them +/// we create a proxy KnowsCompositor that has information about compositor +/// backend but proxies allocations to the ImageBridge. +/// This is kind of specific to the needs of media which wants to allocate +/// textures, usually through the Image Bridge accessed by KnowsCompositor but +/// also wants access to the compositor backend information that ImageBridge +/// doesn't know about. +/// +/// This is really a band aid to what turned into a class hierarchy horror show. +/// Hopefully we can come back and simplify this some way. +class KnowsCompositorMediaProxy : public KnowsCompositor { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(KnowsCompositorMediaProxy, override); + + explicit KnowsCompositorMediaProxy( + const TextureFactoryIdentifier& aIdentifier); + + TextureForwarder* GetTextureForwarder() override; + + LayersIPCActor* GetLayersIPCActor() override; + + ActiveResourceTracker* GetActiveResourceTracker() override; + + void SyncWithCompositor() override; + + protected: + virtual ~KnowsCompositorMediaProxy(); + + RefPtr mThreadSafeAllocator; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/ipc/LayerAnimationUtils.cpp b/gfx/layers/ipc/LayerAnimationUtils.cpp new file mode 100644 index 0000000000..2902122ad3 --- /dev/null +++ b/gfx/layers/ipc/LayerAnimationUtils.cpp @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "LayerAnimationUtils.h" +#include "mozilla/ComputedTimingFunction.h" // For ComputedTimingFunction +#include "mozilla/layers/LayersMessages.h" // For TimingFunction etc. +#include "nsTimingFunction.h" + +namespace mozilla { +namespace layers { + +/* static */ +Maybe +AnimationUtils::TimingFunctionToComputedTimingFunction( + const TimingFunction& aTimingFunction) { + switch (aTimingFunction.type()) { + case TimingFunction::Tnull_t: + return Nothing(); + case TimingFunction::TCubicBezierFunction: { + CubicBezierFunction cbf = aTimingFunction.get_CubicBezierFunction(); + return Some(ComputedTimingFunction::CubicBezier(cbf.x1(), cbf.y1(), + cbf.x2(), cbf.y2())); + } + case TimingFunction::TStepFunction: { + StepFunction sf = aTimingFunction.get_StepFunction(); + StyleStepPosition pos = static_cast(sf.type()); + return Some(ComputedTimingFunction::Steps(sf.steps(), pos)); + } + default: + MOZ_ASSERT_UNREACHABLE("Function must be null, bezier, step or frames"); + break; + } + return Nothing(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/LayerAnimationUtils.h b/gfx/layers/ipc/LayerAnimationUtils.h new file mode 100644 index 0000000000..0a17e51b70 --- /dev/null +++ b/gfx/layers/ipc/LayerAnimationUtils.h @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_LayerAnimationUtils_h +#define mozilla_layers_LayerAnimationUtils_h + +#include "mozilla/Maybe.h" + +namespace mozilla { + +class ComputedTimingFunction; + +namespace layers { + +class TimingFunction; + +class AnimationUtils { + public: + static Maybe TimingFunctionToComputedTimingFunction( + const TimingFunction& aTimingFunction); +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_LayerAnimationUtils_h diff --git a/gfx/layers/ipc/LayerTransactionChild.cpp b/gfx/layers/ipc/LayerTransactionChild.cpp new file mode 100644 index 0000000000..4dbd40e089 --- /dev/null +++ b/gfx/layers/ipc/LayerTransactionChild.cpp @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "LayerTransactionChild.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/layers/ShadowLayers.h" // for ShadowLayerForwarder +#include "mozilla/mozalloc.h" // for operator delete, etc +#include "nsTArray.h" // for nsTArray +#include "mozilla/layers/TextureClient.h" + +namespace mozilla { +namespace layers { + +void LayerTransactionChild::Destroy() { + if (!IPCOpen()) { + return; + } + // mDestroyed is used to prevent calling Send__delete__() twice. + // When this function is called from CompositorBridgeChild::Destroy(), + // under Send__delete__() call, this function is called from + // ShadowLayerForwarder's destructor. + // When it happens, IPCOpen() is still true. + // See bug 1004191. + mDestroyed = true; + + SendShutdown(); +} + +void LayerTransactionChild::ActorDestroy(ActorDestroyReason why) { + mDestroyed = true; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/LayerTransactionChild.h b/gfx/layers/ipc/LayerTransactionChild.h new file mode 100644 index 0000000000..83ce36b022 --- /dev/null +++ b/gfx/layers/ipc/LayerTransactionChild.h @@ -0,0 +1,74 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_LAYERS_LAYERTRANSACTIONCHILD_H +#define MOZILLA_LAYERS_LAYERTRANSACTIONCHILD_H + +#include // for uint32_t +#include "mozilla/Attributes.h" // for override +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/layers/PLayerTransactionChild.h" +#include "mozilla/RefPtr.h" + +namespace mozilla { + +namespace layers { + +class ShadowLayerForwarder; + +class LayerTransactionChild : public PLayerTransactionChild { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(LayerTransactionChild) + /** + * Clean this up, finishing with SendShutDown() which will cause __delete__ + * to be sent from the parent side. + * + * It is expected (checked with an assert) that all shadow layers + * created by this have already been destroyed and + * Send__delete__()d by the time this method is called. + */ + void Destroy(); + + bool IPCOpen() const { return mIPCOpen && !mDestroyed; } + bool IsDestroyed() const { return mDestroyed; } + + void SetForwarder(ShadowLayerForwarder* aForwarder) { + mForwarder = aForwarder; + } + + LayersId GetId() const { return mId; } + + void MarkDestroyed() { mDestroyed = true; } + + protected: + explicit LayerTransactionChild(const LayersId& aId) + : mForwarder(nullptr), mIPCOpen(false), mDestroyed(false), mId(aId) {} + virtual ~LayerTransactionChild() = default; + + void ActorDestroy(ActorDestroyReason why) override; + + void AddIPDLReference() { + MOZ_ASSERT(mIPCOpen == false); + mIPCOpen = true; + AddRef(); + } + void ReleaseIPDLReference() { + MOZ_ASSERT(mIPCOpen == true); + mIPCOpen = false; + Release(); + } + friend class CompositorBridgeChild; + + ShadowLayerForwarder* mForwarder; + bool mIPCOpen; + bool mDestroyed; + LayersId mId; +}; + +} // namespace layers +} // namespace mozilla + +#endif // MOZILLA_LAYERS_LAYERTRANSACTIONCHILD_H diff --git a/gfx/layers/ipc/LayerTransactionParent.cpp b/gfx/layers/ipc/LayerTransactionParent.cpp new file mode 100644 index 0000000000..e7dc07f1d9 --- /dev/null +++ b/gfx/layers/ipc/LayerTransactionParent.cpp @@ -0,0 +1,1020 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "LayerTransactionParent.h" +#include // for vector +#include "CompositableHost.h" // for CompositableParent, Get, etc +#include "ImageLayers.h" // for ImageLayer +#include "Layers.h" // for Layer, ContainerLayer, etc +#include "CompositableTransactionParent.h" // for EditReplyVector +#include "CompositorBridgeParent.h" +#include "mozilla/gfx/BasePoint3D.h" // for BasePoint3D +#include "mozilla/layers/AnimationHelper.h" // for GetAnimatedPropValue +#include "mozilla/layers/CanvasLayerComposite.h" +#include "mozilla/layers/ColorLayerComposite.h" +#include "mozilla/layers/Compositor.h" // for Compositor +#include "mozilla/layers/CompositorAnimationStorage.h" // for CompositorAnimationStorage +#include "mozilla/layers/ContainerLayerComposite.h" +#include "mozilla/layers/ImageBridgeParent.h" // for ImageBridgeParent +#include "mozilla/layers/ImageLayerComposite.h" +#include "mozilla/layers/LayerManagerComposite.h" +#include "mozilla/layers/LayersMessages.h" // for EditReply, etc +#include "mozilla/layers/LayersTypes.h" // for MOZ_LAYERS_LOG +#include "mozilla/layers/TextureHostOGL.h" // for TextureHostOGL +#include "mozilla/layers/PaintedLayerComposite.h" +#include "mozilla/mozalloc.h" // for operator delete, etc +#include "mozilla/PerfStats.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Unused.h" +#include "nsCoord.h" // for NSAppUnitsToFloatPixels +#include "nsISupportsImpl.h" // for Layer::Release, etc +#include "nsLayoutUtils.h" // for nsLayoutUtils +#include "nsMathUtils.h" // for NS_round +#include "nsPoint.h" // for nsPoint +#include "nsTArray.h" // for nsTArray, nsTArray_Impl, etc +#include "TreeTraversal.h" // for ForEachNode +#include "GeckoProfiler.h" +#include "mozilla/layers/TextureHost.h" +#include "mozilla/layers/AsyncCompositionManager.h" + +using mozilla::Telemetry::LABELS_CONTENT_FRAME_TIME_REASON; + +namespace mozilla { +namespace layers { + +//-------------------------------------------------- +// LayerTransactionParent +LayerTransactionParent::LayerTransactionParent( + HostLayerManager* aManager, CompositorBridgeParentBase* aBridge, + CompositorAnimationStorage* aAnimStorage, LayersId aId, + TimeDuration aVsyncRate) + : mLayerManager(aManager), + mCompositorBridge(aBridge), + mAnimStorage(aAnimStorage), + mId(aId), + mChildEpoch{0}, + mParentEpoch{0}, + mVsyncRate(aVsyncRate), + mDestroyed(false), + mIPCOpen(false), + mUpdateHitTestingTree(false) { + MOZ_ASSERT(mId.IsValid()); +} + +LayerTransactionParent::~LayerTransactionParent() = default; + +void LayerTransactionParent::SetLayerManager( + HostLayerManager* aLayerManager, CompositorAnimationStorage* aAnimStorage) { + if (mDestroyed) { + return; + } + mLayerManager = aLayerManager; + for (auto iter = mLayerMap.Iter(); !iter.Done(); iter.Next()) { + auto layer = iter.Data(); + if (mAnimStorage && layer->GetCompositorAnimationsId()) { + mAnimStorage->ClearById(layer->GetCompositorAnimationsId()); + } + layer->AsHostLayer()->SetLayerManager(aLayerManager); + } + mAnimStorage = aAnimStorage; +} + +mozilla::ipc::IPCResult LayerTransactionParent::RecvShutdown() { + Destroy(); + IProtocol* mgr = Manager(); + if (!Send__delete__(this)) { + return IPC_FAIL_NO_REASON(mgr); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult LayerTransactionParent::RecvShutdownSync() { + return RecvShutdown(); +} + +void LayerTransactionParent::Destroy() { + if (mDestroyed) { + return; + } + mDestroyed = true; + if (mAnimStorage) { + for (auto iter = mLayerMap.Iter(); !iter.Done(); iter.Next()) { + auto layer = iter.Data(); + if (layer->GetCompositorAnimationsId()) { + mAnimStorage->ClearById(layer->GetCompositorAnimationsId()); + } + layer->Disconnect(); + } + } + mCompositables.clear(); + mAnimStorage = nullptr; +} + +class MOZ_STACK_CLASS AutoLayerTransactionParentAsyncMessageSender final { + public: + explicit AutoLayerTransactionParentAsyncMessageSender( + LayerTransactionParent* aLayerTransaction, + const nsTArray* aDestroyActors = nullptr) + : mLayerTransaction(aLayerTransaction), mActorsToDestroy(aDestroyActors) { + mLayerTransaction->SetAboutToSendAsyncMessages(); + } + + ~AutoLayerTransactionParentAsyncMessageSender() { + mLayerTransaction->SendPendingAsyncMessages(); + if (mActorsToDestroy) { + // Destroy the actors after sending the async messages because the latter + // may contain references to some actors. + for (const auto& op : *mActorsToDestroy) { + mLayerTransaction->DestroyActor(op); + } + } + } + + private: + LayerTransactionParent* mLayerTransaction; + const nsTArray* mActorsToDestroy; +}; + +mozilla::ipc::IPCResult LayerTransactionParent::RecvPaintTime( + const TransactionId& aTransactionId, const TimeDuration& aPaintTime) { + mCompositorBridge->UpdatePaintTime(this, aPaintTime); + return IPC_OK(); +} + +mozilla::ipc::IPCResult LayerTransactionParent::RecvUpdate( + const TransactionInfo& aInfo) { + AUTO_PROFILER_TRACING_MARKER("Paint", "LayerTransaction", GRAPHICS); + AUTO_PROFILER_LABEL("LayerTransactionParent::RecvUpdate", GRAPHICS); + PerfStats::AutoMetricRecording + autoRecording; + + TimeStamp updateStart = TimeStamp::Now(); + + MOZ_LAYERS_LOG( + ("[ParentSide] received txn with %zu edits", aInfo.cset().Length())); + + UpdateFwdTransactionId(aInfo.fwdTransactionId()); + + if (mDestroyed || !mLayerManager || mLayerManager->IsDestroyed()) { + for (const auto& op : aInfo.toDestroy()) { + DestroyActor(op); + } + return IPC_OK(); + } + + // This ensures that destroy operations are always processed. It is not safe + // to early-return from RecvUpdate without doing so. + AutoLayerTransactionParentAsyncMessageSender autoAsyncMessageSender( + this, &aInfo.toDestroy()); + + { + AutoResolveRefLayers resolve( + mCompositorBridge->GetCompositionManager(this)); + nsCString none; + mLayerManager->BeginTransaction(none); + } + + // Not all edits require an update to the hit testing tree. + mUpdateHitTestingTree = false; + + for (EditArray::index_type i = 0; i < aInfo.cset().Length(); ++i) { + const Edit& edit = const_cast(aInfo.cset()[i]); + + switch (edit.type()) { + // Create* ops + case Edit::TOpCreatePaintedLayer: { + MOZ_LAYERS_LOG(("[ParentSide] CreatePaintedLayer")); + + RefPtr layer = mLayerManager->CreatePaintedLayer(); + if (!BindLayer(layer, edit.get_OpCreatePaintedLayer())) { + return IPC_FAIL_NO_REASON(this); + } + + UpdateHitTestingTree(layer, "CreatePaintedLayer"); + break; + } + case Edit::TOpCreateContainerLayer: { + MOZ_LAYERS_LOG(("[ParentSide] CreateContainerLayer")); + + RefPtr layer = mLayerManager->CreateContainerLayer(); + if (!BindLayer(layer, edit.get_OpCreateContainerLayer())) { + return IPC_FAIL_NO_REASON(this); + } + + UpdateHitTestingTree(layer, "CreateContainerLayer"); + break; + } + case Edit::TOpCreateImageLayer: { + MOZ_LAYERS_LOG(("[ParentSide] CreateImageLayer")); + + RefPtr layer = mLayerManager->CreateImageLayer(); + if (!BindLayer(layer, edit.get_OpCreateImageLayer())) { + return IPC_FAIL_NO_REASON(this); + } + + UpdateHitTestingTree(layer, "CreateImageLayer"); + break; + } + case Edit::TOpCreateColorLayer: { + MOZ_LAYERS_LOG(("[ParentSide] CreateColorLayer")); + + RefPtr layer = mLayerManager->CreateColorLayer(); + if (!BindLayer(layer, edit.get_OpCreateColorLayer())) { + return IPC_FAIL_NO_REASON(this); + } + + UpdateHitTestingTree(layer, "CreateColorLayer"); + break; + } + case Edit::TOpCreateCanvasLayer: { + MOZ_LAYERS_LOG(("[ParentSide] CreateCanvasLayer")); + + RefPtr layer = mLayerManager->CreateCanvasLayer(); + if (!BindLayer(layer, edit.get_OpCreateCanvasLayer())) { + return IPC_FAIL_NO_REASON(this); + } + + UpdateHitTestingTree(layer, "CreateCanvasLayer"); + break; + } + case Edit::TOpCreateRefLayer: { + MOZ_LAYERS_LOG(("[ParentSide] CreateRefLayer")); + + RefPtr layer = mLayerManager->CreateRefLayer(); + if (!BindLayer(layer, edit.get_OpCreateRefLayer())) { + return IPC_FAIL_NO_REASON(this); + } + + UpdateHitTestingTree(layer, "CreateRefLayer"); + break; + } + case Edit::TOpSetDiagnosticTypes: { + mLayerManager->SetDiagnosticTypes( + edit.get_OpSetDiagnosticTypes().diagnostics()); + break; + } + // Tree ops + case Edit::TOpSetRoot: { + MOZ_LAYERS_LOG(("[ParentSide] SetRoot")); + + Layer* newRoot = AsLayer(edit.get_OpSetRoot().root()); + if (!newRoot) { + return IPC_FAIL_NO_REASON(this); + } + if (newRoot->GetParent()) { + // newRoot is not a root! + return IPC_FAIL_NO_REASON(this); + } + mRoot = newRoot; + + UpdateHitTestingTree(mRoot, "SetRoot"); + break; + } + case Edit::TOpInsertAfter: { + MOZ_LAYERS_LOG(("[ParentSide] InsertAfter")); + + const OpInsertAfter& oia = edit.get_OpInsertAfter(); + Layer* child = AsLayer(oia.childLayer()); + Layer* layer = AsLayer(oia.container()); + Layer* after = AsLayer(oia.after()); + if (!child || !layer || !after) { + return IPC_FAIL_NO_REASON(this); + } + ContainerLayer* container = layer->AsContainerLayer(); + if (!container || !container->InsertAfter(child, after)) { + return IPC_FAIL_NO_REASON(this); + } + + UpdateHitTestingTree(layer, "InsertAfter"); + break; + } + case Edit::TOpPrependChild: { + MOZ_LAYERS_LOG(("[ParentSide] PrependChild")); + + const OpPrependChild& oac = edit.get_OpPrependChild(); + Layer* child = AsLayer(oac.childLayer()); + Layer* layer = AsLayer(oac.container()); + if (!child || !layer) { + return IPC_FAIL_NO_REASON(this); + } + ContainerLayer* container = layer->AsContainerLayer(); + if (!container || !container->InsertAfter(child, nullptr)) { + return IPC_FAIL_NO_REASON(this); + } + + UpdateHitTestingTree(layer, "PrependChild"); + break; + } + case Edit::TOpRemoveChild: { + MOZ_LAYERS_LOG(("[ParentSide] RemoveChild")); + + const OpRemoveChild& orc = edit.get_OpRemoveChild(); + Layer* childLayer = AsLayer(orc.childLayer()); + Layer* layer = AsLayer(orc.container()); + if (!childLayer || !layer) { + return IPC_FAIL_NO_REASON(this); + } + ContainerLayer* container = layer->AsContainerLayer(); + if (!container || !container->RemoveChild(childLayer)) { + return IPC_FAIL_NO_REASON(this); + } + + UpdateHitTestingTree(layer, "RemoveChild"); + break; + } + case Edit::TOpRepositionChild: { + MOZ_LAYERS_LOG(("[ParentSide] RepositionChild")); + + const OpRepositionChild& orc = edit.get_OpRepositionChild(); + Layer* child = AsLayer(orc.childLayer()); + Layer* after = AsLayer(orc.after()); + Layer* layer = AsLayer(orc.container()); + if (!child || !layer || !after) { + return IPC_FAIL_NO_REASON(this); + } + ContainerLayer* container = layer->AsContainerLayer(); + if (!container || !container->RepositionChild(child, after)) { + return IPC_FAIL_NO_REASON(this); + } + + UpdateHitTestingTree(layer, "RepositionChild"); + break; + } + case Edit::TOpRaiseToTopChild: { + MOZ_LAYERS_LOG(("[ParentSide] RaiseToTopChild")); + + const OpRaiseToTopChild& rtc = edit.get_OpRaiseToTopChild(); + Layer* child = AsLayer(rtc.childLayer()); + if (!child) { + return IPC_FAIL_NO_REASON(this); + } + Layer* layer = AsLayer(rtc.container()); + if (!layer) { + return IPC_FAIL_NO_REASON(this); + } + ContainerLayer* container = layer->AsContainerLayer(); + if (!container || !container->RepositionChild(child, nullptr)) { + return IPC_FAIL_NO_REASON(this); + } + + UpdateHitTestingTree(layer, "RaiseToTopChild"); + break; + } + case Edit::TCompositableOperation: { + if (!ReceiveCompositableUpdate(edit.get_CompositableOperation())) { + return IPC_FAIL_NO_REASON(this); + } + break; + } + case Edit::TOpAttachCompositable: { + const OpAttachCompositable& op = edit.get_OpAttachCompositable(); + RefPtr host = FindCompositable(op.compositable()); + if (!Attach(AsLayer(op.layer()), host, false)) { + return IPC_FAIL_NO_REASON(this); + } + host->SetCompositorBridgeID(mLayerManager->GetCompositorBridgeID()); + break; + } + case Edit::TOpAttachAsyncCompositable: { + const OpAttachAsyncCompositable& op = + edit.get_OpAttachAsyncCompositable(); + RefPtr imageBridge = + ImageBridgeParent::GetInstance(OtherPid()); + if (!imageBridge) { + return IPC_FAIL_NO_REASON(this); + } + RefPtr host = imageBridge->FindCompositable( + op.compositable(), /* aAllowDisablingWebRender */ true); + if (!host) { + // This normally should not happen, but can after a GPU process crash. + // Media may not have had time to update the ImageContainer associated + // with a video frame, and we may try to attach a stale + // CompositableHandle. Rather than break the whole transaction, we + // just continue. + gfxCriticalNote << "CompositableHost " << op.compositable().Value() + << " not found"; + continue; + } + if (!Attach(AsLayer(op.layer()), host, true)) { + return IPC_FAIL_NO_REASON(this); + } + host->SetCompositorBridgeID(mLayerManager->GetCompositorBridgeID()); + break; + } + default: + MOZ_CRASH("not reached"); + } + } + + // Process simple attribute updates. + for (const auto& op : aInfo.setSimpleAttrs()) { + MOZ_LAYERS_LOG(("[ParentSide] SetSimpleLayerAttributes")); + Layer* layer = AsLayer(op.layer()); + if (!layer) { + return IPC_FAIL_NO_REASON(this); + } + const SimpleLayerAttributes& attrs = op.attrs(); + const SimpleLayerAttributes& orig = layer->GetSimpleAttributes(); + if (!attrs.HitTestingInfoIsEqual(orig)) { + UpdateHitTestingTree(layer, "scrolling info changed"); + } + layer->SetSimpleAttributes(op.attrs()); + } + + // Process attribute updates. + for (const auto& op : aInfo.setAttrs()) { + MOZ_LAYERS_LOG(("[ParentSide] SetLayerAttributes")); + if (!SetLayerAttributes(op)) { + return IPC_FAIL_NO_REASON(this); + } + } + + // Process paints separately, after all normal edits. + for (const auto& op : aInfo.paints()) { + if (!ReceiveCompositableUpdate(op)) { + return IPC_FAIL_NO_REASON(this); + } + } + + mCompositorBridge->ShadowLayersUpdated(this, aInfo, mUpdateHitTestingTree); + + { + AutoResolveRefLayers resolve( + mCompositorBridge->GetCompositionManager(this)); + mLayerManager->EndTransaction(TimeStamp(), + LayerManager::END_NO_IMMEDIATE_REDRAW); + } + + if (!IsSameProcess()) { + // Ensure that any pending operations involving back and front + // buffers have completed, so that neither process stomps on the + // other's buffer contents. + LayerManagerComposite::PlatformSyncBeforeReplyUpdate(); + } + +#ifdef COMPOSITOR_PERFORMANCE_WARNING + int compositeTime = + (int)(mozilla::TimeStamp::Now() - updateStart).ToMilliseconds(); + if (compositeTime > 15) { + printf_stderr("Compositor: Layers update took %i ms (blocking gecko).\n", + compositeTime); + } +#endif + + // Enable visual warning for long transaction when draw FPS option is enabled + bool drawFps = StaticPrefs::layers_acceleration_draw_fps(); + if (drawFps) { + uint32_t visualWarningTrigger = + StaticPrefs::layers_transaction_warning_ms(); + // The default theshold is 200ms to trigger, hit red when it take 4 times + // longer + TimeDuration latency = TimeStamp::Now() - aInfo.transactionStart(); + if (latency > TimeDuration::FromMilliseconds(visualWarningTrigger)) { + float severity = + (latency - TimeDuration::FromMilliseconds(visualWarningTrigger)) + .ToMilliseconds() / + (4 * visualWarningTrigger); + if (severity > 1.f) { + severity = 1.f; + } + mLayerManager->VisualFrameWarning(severity); + printf_stderr( + "LayerTransactionParent::RecvUpdate transaction from process %d took " + "%f ms", + OtherPid(), latency.ToMilliseconds()); + } + + mLayerManager->RecordUpdateTime( + (TimeStamp::Now() - updateStart).ToMilliseconds()); + } + + return IPC_OK(); +} + +bool LayerTransactionParent::SetLayerAttributes( + const OpSetLayerAttributes& aOp) { + Layer* layer = AsLayer(aOp.layer()); + if (!layer) { + return false; + } + + const LayerAttributes& attrs = aOp.attrs(); + const CommonLayerAttributes& common = attrs.common(); + if (common.visibleRegion() != layer->GetVisibleRegion()) { + UpdateHitTestingTree(layer, "visible region changed"); + layer->SetVisibleRegion(common.visibleRegion()); + } + if (common.eventRegions() != layer->GetEventRegions()) { + UpdateHitTestingTree(layer, "event regions changed"); + layer->SetEventRegions(common.eventRegions()); + } + Maybe clipRect = + common.useClipRect() ? Some(common.clipRect()) : Nothing(); + if (clipRect != layer->GetClipRect()) { + UpdateHitTestingTree(layer, "clip rect changed"); + layer->SetClipRect(clipRect); + } + if (LayerHandle maskLayer = common.maskLayer()) { + layer->SetMaskLayer(AsLayer(maskLayer)); + } else { + layer->SetMaskLayer(nullptr); + } + layer->SetCompositorAnimations(mId, common.compositorAnimations()); + // Clean up the Animations by id in the CompositorAnimationStorage + // if there are no active animations on the layer + if (mAnimStorage && layer->GetCompositorAnimationsId() && + layer->GetPropertyAnimationGroups().IsEmpty()) { + mAnimStorage->ClearById(layer->GetCompositorAnimationsId()); + } + if (common.scrollMetadata() != layer->GetAllScrollMetadata()) { + UpdateHitTestingTree(layer, "scroll metadata changed"); + layer->SetScrollMetadata(common.scrollMetadata()); + } + layer->SetDisplayListLog(common.displayListLog().get()); + + // The updated invalid region is added to the existing one, since we can + // update multiple times before the next composite. + layer->AddInvalidRegion(common.invalidRegion()); + + nsTArray> maskLayers; + for (size_t i = 0; i < common.ancestorMaskLayers().Length(); i++) { + Layer* maskLayer = AsLayer(common.ancestorMaskLayers().ElementAt(i)); + if (!maskLayer) { + return false; + } + maskLayers.AppendElement(maskLayer); + } + layer->SetAncestorMaskLayers(maskLayers); + + typedef SpecificLayerAttributes Specific; + const SpecificLayerAttributes& specific = attrs.specific(); + switch (specific.type()) { + case Specific::Tnull_t: + break; + + case Specific::TPaintedLayerAttributes: { + MOZ_LAYERS_LOG(("[ParentSide] painted layer")); + + PaintedLayer* paintedLayer = layer->AsPaintedLayer(); + if (!paintedLayer) { + return false; + } + const PaintedLayerAttributes& attrs = + specific.get_PaintedLayerAttributes(); + + paintedLayer->SetValidRegion(attrs.validRegion()); + break; + } + case Specific::TContainerLayerAttributes: { + MOZ_LAYERS_LOG(("[ParentSide] container layer")); + + ContainerLayer* containerLayer = layer->AsContainerLayer(); + if (!containerLayer) { + return false; + } + const ContainerLayerAttributes& attrs = + specific.get_ContainerLayerAttributes(); + containerLayer->SetPreScale(attrs.preXScale(), attrs.preYScale()); + containerLayer->SetInheritedScale(attrs.inheritedXScale(), + attrs.inheritedYScale()); + containerLayer->SetScaleToResolution(attrs.presShellResolution()); + break; + } + case Specific::TColorLayerAttributes: { + MOZ_LAYERS_LOG(("[ParentSide] color layer")); + + ColorLayer* colorLayer = layer->AsColorLayer(); + if (!colorLayer) { + return false; + } + colorLayer->SetColor(specific.get_ColorLayerAttributes().color().value()); + colorLayer->SetBounds(specific.get_ColorLayerAttributes().bounds()); + break; + } + case Specific::TCanvasLayerAttributes: { + MOZ_LAYERS_LOG(("[ParentSide] canvas layer")); + + CanvasLayer* canvasLayer = layer->AsCanvasLayer(); + if (!canvasLayer) { + return false; + } + canvasLayer->SetSamplingFilter( + specific.get_CanvasLayerAttributes().samplingFilter()); + canvasLayer->SetBounds(specific.get_CanvasLayerAttributes().bounds()); + break; + } + case Specific::TRefLayerAttributes: { + MOZ_LAYERS_LOG(("[ParentSide] ref layer")); + + RefLayer* refLayer = layer->AsRefLayer(); + if (!refLayer) { + return false; + } + refLayer->SetReferentId(specific.get_RefLayerAttributes().id()); + refLayer->SetEventRegionsOverride( + specific.get_RefLayerAttributes().eventRegionsOverride()); + refLayer->SetRemoteDocumentSize( + specific.get_RefLayerAttributes().remoteDocumentSize()); + UpdateHitTestingTree(layer, "ref layer attributes changed"); + break; + } + case Specific::TImageLayerAttributes: { + MOZ_LAYERS_LOG(("[ParentSide] image layer")); + + ImageLayer* imageLayer = layer->AsImageLayer(); + if (!imageLayer) { + return false; + } + const ImageLayerAttributes& attrs = specific.get_ImageLayerAttributes(); + imageLayer->SetSamplingFilter(attrs.samplingFilter()); + imageLayer->SetScaleToSize(attrs.scaleToSize(), attrs.scaleMode()); + break; + } + default: + MOZ_CRASH("not reached"); + } + + return true; +} + +mozilla::ipc::IPCResult LayerTransactionParent::RecvSetLayersObserverEpoch( + const LayersObserverEpoch& aChildEpoch) { + mChildEpoch = aChildEpoch; + return IPC_OK(); +} + +bool LayerTransactionParent::ShouldParentObserveEpoch() { + if (mParentEpoch == mChildEpoch) { + return false; + } + + mParentEpoch = mChildEpoch; + return true; +} + +mozilla::ipc::IPCResult LayerTransactionParent::RecvSetTestSampleTime( + const TimeStamp& aTime) { + if (!mCompositorBridge->SetTestSampleTime(GetId(), aTime)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult LayerTransactionParent::RecvLeaveTestMode() { + if (mDestroyed) { + return IPC_FAIL_NO_REASON(this); + } + + mCompositorBridge->LeaveTestMode(GetId()); + return IPC_OK(); +} + +mozilla::ipc::IPCResult LayerTransactionParent::RecvGetAnimationValue( + const uint64_t& aCompositorAnimationsId, OMTAValue* aValue) { + if (mDestroyed || !mLayerManager || mLayerManager->IsDestroyed()) { + return IPC_FAIL_NO_REASON(this); + } + + // Make sure we apply the latest animation style or else we can end up with + // a race between when we temporarily clear the animation transform (in + // CompositorBridgeParent::SetShadowProperties) and when animation + // recalculates the value. + mCompositorBridge->ApplyAsyncProperties( + this, CompositorBridgeParentBase::TransformsToSkip::APZ); + + if (!mAnimStorage) { + return IPC_FAIL_NO_REASON(this); + } + + *aValue = mAnimStorage->GetOMTAValue(aCompositorAnimationsId); + return IPC_OK(); +} + +mozilla::ipc::IPCResult LayerTransactionParent::RecvGetTransform( + const LayerHandle& aLayerHandle, Maybe* aTransform) { + if (mDestroyed || !mLayerManager || mLayerManager->IsDestroyed()) { + return IPC_FAIL_NO_REASON(this); + } + + Layer* layer = AsLayer(aLayerHandle); + if (!layer) { + return IPC_FAIL_NO_REASON(this); + } + + mCompositorBridge->ApplyAsyncProperties( + this, CompositorBridgeParentBase::TransformsToSkip::NoneOfThem); + + Matrix4x4 transform = layer->AsHostLayer()->GetShadowBaseTransform(); + // Undo the scale transform applied by FrameTransformToTransformInDevice in + // AsyncCompositionManager.cpp. + if (ContainerLayer* c = layer->AsContainerLayer()) { + transform.PostScale(1.0f / c->GetInheritedXScale(), + 1.0f / c->GetInheritedYScale(), 1.0f); + } + float scale = 1; + Point3D scaledOrigin; + if (layer->GetTransformData()) { + const TransformData& data = *layer->GetTransformData(); + scale = data.appUnitsPerDevPixel(); + scaledOrigin = Point3D( + NS_round(NSAppUnitsToFloatPixels(data.origin().x, scale)), + NS_round(NSAppUnitsToFloatPixels(data.origin().y, scale)), 0.0f); + } + + // If our parent isn't a perspective layer, then the offset into reference + // frame coordinates will have been applied to us. Add an inverse translation + // to cancel it out. + if (!layer->GetParent() || !layer->GetParent()->GetTransformIsPerspective()) { + transform.PostTranslate(-scaledOrigin.x, -scaledOrigin.y, -scaledOrigin.z); + } + + *aTransform = Some(transform); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult LayerTransactionParent::RecvSetAsyncScrollOffset( + const ScrollableLayerGuid::ViewID& aScrollID, const float& aX, + const float& aY) { + if (mDestroyed || !mLayerManager || mLayerManager->IsDestroyed()) { + return IPC_FAIL_NO_REASON(this); + } + + mCompositorBridge->SetTestAsyncScrollOffset(GetId(), aScrollID, + CSSPoint(aX, aY)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult LayerTransactionParent::RecvSetAsyncZoom( + const ScrollableLayerGuid::ViewID& aScrollID, const float& aValue) { + if (mDestroyed || !mLayerManager || mLayerManager->IsDestroyed()) { + return IPC_FAIL_NO_REASON(this); + } + + mCompositorBridge->SetTestAsyncZoom(GetId(), aScrollID, + LayerToParentLayerScale(aValue)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult LayerTransactionParent::RecvFlushApzRepaints() { + mCompositorBridge->FlushApzRepaints(GetId()); + return IPC_OK(); +} + +mozilla::ipc::IPCResult LayerTransactionParent::RecvGetAPZTestData( + APZTestData* aOutData) { + mCompositorBridge->GetAPZTestData(GetId(), aOutData); + return IPC_OK(); +} + +mozilla::ipc::IPCResult LayerTransactionParent::RecvGetFrameUniformity( + FrameUniformityData* aOutData) { + mCompositorBridge->GetFrameUniformity(GetId(), aOutData); + return IPC_OK(); +} + +mozilla::ipc::IPCResult LayerTransactionParent::RecvRequestProperty( + const nsString& aProperty, float* aValue) { + *aValue = -1; + return IPC_OK(); +} + +mozilla::ipc::IPCResult LayerTransactionParent::RecvSetConfirmedTargetAPZC( + const uint64_t& aBlockId, nsTArray&& aTargets) { + for (size_t i = 0; i < aTargets.Length(); i++) { + // Guard against bad data from hijacked child processes + if (aTargets[i].mLayersId != GetId()) { + NS_ERROR( + "Unexpected layers id in RecvSetConfirmedTargetAPZC; dropping " + "message..."); + return IPC_FAIL(this, "Bad layers id"); + } + } + mCompositorBridge->SetConfirmedTargetAPZC(GetId(), aBlockId, + std::move(aTargets)); + return IPC_OK(); +} + +bool LayerTransactionParent::Attach(Layer* aLayer, + CompositableHost* aCompositable, + bool aIsAsync) { + if (!aCompositable || !aLayer) { + return false; + } + + HostLayer* layer = aLayer->AsHostLayer(); + if (!layer) { + return false; + } + + TextureSourceProvider* provider = + static_cast(aLayer->Manager()) + ->GetTextureSourceProvider(); + + MOZ_ASSERT(!aCompositable->AsWebRenderImageHost()); + if (aCompositable->AsWebRenderImageHost()) { + gfxCriticalNote << "Use WebRenderImageHost at LayerTransactionParent."; + } + if (!layer->SetCompositableHost(aCompositable)) { + // not all layer types accept a compositable, see bug 967824 + return false; + } + aCompositable->Attach(aLayer, provider, + aIsAsync ? CompositableHost::ALLOW_REATTACH | + CompositableHost::KEEP_ATTACHED + : CompositableHost::NO_FLAGS); + return true; +} + +mozilla::ipc::IPCResult LayerTransactionParent::RecvClearCachedResources() { + if (mRoot) { + // NB: |mRoot| here is the *child* context's root. In this parent + // context, it's just a subtree root. We need to scope the clear + // of resources to exactly that subtree, so we specify it here. + mLayerManager->ClearCachedResources(mRoot); + } + mCompositorBridge->NotifyClearCachedResources(this); + return IPC_OK(); +} + +mozilla::ipc::IPCResult LayerTransactionParent::RecvScheduleComposite() { + mCompositorBridge->ScheduleComposite(this); + return IPC_OK(); +} + +void LayerTransactionParent::ActorDestroy(ActorDestroyReason why) { Destroy(); } + +bool LayerTransactionParent::AllocShmem( + size_t aSize, ipc::SharedMemory::SharedMemoryType aType, + ipc::Shmem* aShmem) { + if (!mIPCOpen || mDestroyed) { + return false; + } + return PLayerTransactionParent::AllocShmem(aSize, aType, aShmem); +} + +bool LayerTransactionParent::AllocUnsafeShmem( + size_t aSize, ipc::SharedMemory::SharedMemoryType aType, + ipc::Shmem* aShmem) { + if (!mIPCOpen || mDestroyed) { + return false; + } + + return PLayerTransactionParent::AllocUnsafeShmem(aSize, aType, aShmem); +} + +bool LayerTransactionParent::DeallocShmem(ipc::Shmem& aShmem) { + if (!mIPCOpen || mDestroyed) { + return false; + } + return PLayerTransactionParent::DeallocShmem(aShmem); +} + +bool LayerTransactionParent::IsSameProcess() const { + return OtherPid() == base::GetCurrentProcId(); +} + +void LayerTransactionParent::SetPendingTransactionId( + TransactionId aId, const VsyncId& aVsyncId, + const TimeStamp& aVsyncStartTime, const TimeStamp& aRefreshStartTime, + const TimeStamp& aTxnStartTime, const TimeStamp& aTxnEndTime, + bool aContainsSVG, const nsCString& aURL, const TimeStamp& aFwdTime) { + mPendingTransactions.AppendElement(PendingTransaction{ + aId, aVsyncId, aVsyncStartTime, aRefreshStartTime, aTxnStartTime, + aTxnEndTime, aFwdTime, aURL, aContainsSVG}); +} + +TransactionId LayerTransactionParent::FlushTransactionId( + const VsyncId& aCompositeId, TimeStamp& aCompositeEnd) { + TransactionId id; + for (auto& transaction : mPendingTransactions) { + id = transaction.mId; + if (mId.IsValid() && transaction.mId.IsValid() && !mVsyncRate.IsZero()) { + RecordContentFrameTime( + transaction.mTxnVsyncId, transaction.mVsyncStartTime, + transaction.mTxnStartTime, aCompositeId, aCompositeEnd, + transaction.mTxnEndTime - transaction.mTxnStartTime, mVsyncRate, + transaction.mContainsSVG, false); + } + +#if defined(ENABLE_FRAME_LATENCY_LOG) + if (transaction.mId.IsValid()) { + if (transaction.mRefreshStartTime) { + int32_t latencyMs = lround( + (aCompositeEnd - transaction.mRefreshStartTime).ToMilliseconds()); + printf_stderr( + "From transaction start to end of generate frame latencyMs %d this " + "%p\n", + latencyMs, this); + } + if (transaction.mFwdTime) { + int32_t latencyMs = + lround((aCompositeEnd - transaction.mFwdTime).ToMilliseconds()); + printf_stderr( + "From forwarding transaction to end of generate frame latencyMs %d " + "this %p\n", + latencyMs, this); + } + } +#endif + } + + mPendingTransactions.Clear(); + return id; +} + +void LayerTransactionParent::SendAsyncMessage( + const nsTArray& aMessage) { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); +} + +void LayerTransactionParent::SendPendingAsyncMessages() { + mCompositorBridge->SendPendingAsyncMessages(); +} + +void LayerTransactionParent::SetAboutToSendAsyncMessages() { + mCompositorBridge->SetAboutToSendAsyncMessages(); +} + +void LayerTransactionParent::NotifyNotUsed(PTextureParent* aTexture, + uint64_t aTransactionId) { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); +} + +bool LayerTransactionParent::BindLayerToHandle(RefPtr aLayer, + const LayerHandle& aHandle) { + if (!aHandle || !aLayer) { + return false; + } + auto entry = mLayerMap.LookupForAdd(aHandle.Value()); + if (entry) { + return false; + } + entry.OrInsert([&aLayer]() { return aLayer; }); + return true; +} + +Layer* LayerTransactionParent::AsLayer(const LayerHandle& aHandle) { + if (!aHandle) { + return nullptr; + } + return mLayerMap.GetWeak(aHandle.Value()); +} + +mozilla::ipc::IPCResult LayerTransactionParent::RecvNewCompositable( + const CompositableHandle& aHandle, const TextureInfo& aInfo) { + if (!AddCompositable(aHandle, aInfo, /* aUseWebRender */ false)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult LayerTransactionParent::RecvReleaseLayer( + const LayerHandle& aHandle) { + RefPtr layer; + if (!aHandle || !mLayerMap.Remove(aHandle.Value(), getter_AddRefs(layer))) { + return IPC_FAIL_NO_REASON(this); + } + if (mAnimStorage && layer->GetCompositorAnimationsId()) { + mAnimStorage->ClearById(layer->GetCompositorAnimationsId()); + layer->ClearCompositorAnimations(); + } + layer->Disconnect(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult LayerTransactionParent::RecvReleaseCompositable( + const CompositableHandle& aHandle) { + ReleaseCompositable(aHandle); + return IPC_OK(); +} + +mozilla::ipc::IPCResult LayerTransactionParent::RecvRecordPaintTimes( + const PaintTiming& aTiming) { + // Currently we only add paint timings for remote layers. In the future + // we could be smarter and use paint timings from the UI process, either + // as a separate overlay or if no remote layers are attached. + if (mLayerManager && mCompositorBridge->IsRemote()) { + mLayerManager->RecordPaintTimes(aTiming); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult LayerTransactionParent::RecvGetTextureFactoryIdentifier( + TextureFactoryIdentifier* aIdentifier) { + if (mDestroyed || !mLayerManager || mLayerManager->IsDestroyed()) { + // Default constructor sets mParentBackend to LAYERS_NONE. + return IPC_OK(); + } + + *aIdentifier = mLayerManager->GetTextureFactoryIdentifier(); + return IPC_OK(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/LayerTransactionParent.h b/gfx/layers/ipc/LayerTransactionParent.h new file mode 100644 index 0000000000..73850a5e6b --- /dev/null +++ b/gfx/layers/ipc/LayerTransactionParent.h @@ -0,0 +1,236 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_LAYERS_LAYERTRANSACTIONPARENT_H +#define MOZILLA_LAYERS_LAYERTRANSACTIONPARENT_H + +#include // for size_t +#include // for uint64_t, uint32_t +#include "CompositableTransactionParent.h" +#include "mozilla/Attributes.h" // for override +#include "mozilla/ipc/SharedMemory.h" // for SharedMemory, etc +#include "mozilla/layers/PLayerTransactionParent.h" +#include "nsRefPtrHashtable.h" +#include "nsTArrayForwardDeclare.h" // for nsTArray + +namespace mozilla { + +namespace ipc { +class Shmem; +} // namespace ipc + +namespace layers { + +class Layer; +class HostLayerManager; +class ShadowLayerParent; +class CompositableParent; +class CompositorAnimationStorage; +class CompositorBridgeParentBase; + +class LayerTransactionParent final : public PLayerTransactionParent, + public CompositableParentManager, + public mozilla::ipc::IShmemAllocator { + typedef nsTArray EditArray; + typedef nsTArray OpDestroyArray; + typedef nsTArray PluginsArray; + + friend class PLayerTransactionParent; + + public: + LayerTransactionParent(HostLayerManager* aManager, + CompositorBridgeParentBase* aBridge, + CompositorAnimationStorage* aAnimStorage, LayersId aId, + TimeDuration aVsyncRate); + + protected: + virtual ~LayerTransactionParent(); + + public: + void Destroy(); + + void SetLayerManager(HostLayerManager* aLayerManager, + CompositorAnimationStorage* aAnimStorage); + + LayersId GetId() const { return mId; } + Layer* GetRoot() const { return mRoot; } + + LayersObserverEpoch GetChildEpoch() const { return mChildEpoch; } + bool ShouldParentObserveEpoch(); + + IShmemAllocator* AsShmemAllocator() override { return this; } + + bool AllocShmem(size_t aSize, ipc::SharedMemory::SharedMemoryType aType, + ipc::Shmem* aShmem) override; + + bool AllocUnsafeShmem(size_t aSize, ipc::SharedMemory::SharedMemoryType aType, + ipc::Shmem* aShmem) override; + + bool DeallocShmem(ipc::Shmem& aShmem) override; + + bool IsSameProcess() const override; + + void SetPendingTransactionId(TransactionId aId, const VsyncId& aVsyncId, + const TimeStamp& aVsyncStartTime, + const TimeStamp& aRefreshStartTime, + const TimeStamp& aTxnStartTime, + const TimeStamp& aTxnEndTime, bool aContainsSVG, + const nsCString& aURL, + const TimeStamp& aFwdTime); + TransactionId FlushTransactionId(const VsyncId& aId, + TimeStamp& aCompositeEnd); + + // CompositableParentManager + void SendAsyncMessage( + const nsTArray& aMessage) override; + + void SendPendingAsyncMessages() override; + + void SetAboutToSendAsyncMessages() override; + + void NotifyNotUsed(PTextureParent* aTexture, + uint64_t aTransactionId) override; + + base::ProcessId GetChildProcessId() override { return OtherPid(); } + + protected: + mozilla::ipc::IPCResult RecvShutdown(); + mozilla::ipc::IPCResult RecvShutdownSync(); + + mozilla::ipc::IPCResult RecvPaintTime(const TransactionId& aTransactionId, + const TimeDuration& aPaintTime); + + mozilla::ipc::IPCResult RecvUpdate(const TransactionInfo& aInfo); + + mozilla::ipc::IPCResult RecvSetLayersObserverEpoch( + const LayersObserverEpoch& aChildEpoch); + mozilla::ipc::IPCResult RecvNewCompositable(const CompositableHandle& aHandle, + const TextureInfo& aInfo); + mozilla::ipc::IPCResult RecvReleaseLayer(const LayerHandle& aHandle); + mozilla::ipc::IPCResult RecvReleaseCompositable( + const CompositableHandle& aHandle); + + mozilla::ipc::IPCResult RecvClearCachedResources(); + mozilla::ipc::IPCResult RecvScheduleComposite(); + mozilla::ipc::IPCResult RecvSetTestSampleTime(const TimeStamp& aTime); + mozilla::ipc::IPCResult RecvLeaveTestMode(); + mozilla::ipc::IPCResult RecvGetAnimationValue( + const uint64_t& aCompositorAnimationsId, OMTAValue* aValue); + mozilla::ipc::IPCResult RecvGetTransform(const LayerHandle& aHandle, + Maybe* aTransform); + mozilla::ipc::IPCResult RecvSetAsyncScrollOffset( + const ScrollableLayerGuid::ViewID& aId, const float& aX, const float& aY); + mozilla::ipc::IPCResult RecvSetAsyncZoom( + const ScrollableLayerGuid::ViewID& aId, const float& aValue); + mozilla::ipc::IPCResult RecvFlushApzRepaints(); + mozilla::ipc::IPCResult RecvGetAPZTestData(APZTestData* aOutData); + mozilla::ipc::IPCResult RecvGetFrameUniformity(FrameUniformityData* aOutData); + mozilla::ipc::IPCResult RecvRequestProperty(const nsString& aProperty, + float* aValue); + mozilla::ipc::IPCResult RecvSetConfirmedTargetAPZC( + const uint64_t& aBlockId, nsTArray&& aTargets); + mozilla::ipc::IPCResult RecvRecordPaintTimes(const PaintTiming& aTiming); + mozilla::ipc::IPCResult RecvGetTextureFactoryIdentifier( + TextureFactoryIdentifier* aIdentifier); + + bool SetLayerAttributes(const OpSetLayerAttributes& aOp); + + void ActorDestroy(ActorDestroyReason why) override; + + template + bool BindLayer(const RefPtr& aLayer, const T& aCreateOp) { + return BindLayerToHandle(aLayer, aCreateOp.layer()); + } + + bool BindLayerToHandle(RefPtr aLayer, const LayerHandle& aHandle); + + Layer* AsLayer(const LayerHandle& aLayer); + + bool Attach(Layer* aLayer, CompositableHost* aCompositable, + bool aIsAsyncVideo); + + void AddIPDLReference() { + MOZ_ASSERT(mIPCOpen == false); + mIPCOpen = true; + AddRef(); + } + void ReleaseIPDLReference() { + MOZ_ASSERT(mIPCOpen == true); + mIPCOpen = false; + Release(); + } + friend class CompositorBridgeParent; + friend class ContentCompositorBridgeParent; + + private: + // This is a function so we can log or breakpoint on why hit + // testing tree changes are made. + void UpdateHitTestingTree(Layer* aLayer, const char* aWhy) { + mUpdateHitTestingTree = true; + } + + private: + RefPtr mLayerManager; + CompositorBridgeParentBase* mCompositorBridge; + RefPtr mAnimStorage; + + // Hold the root because it might be grafted under various + // containers in the "real" layer tree + RefPtr mRoot; + + // Mapping from LayerHandles to Layers. + nsRefPtrHashtable mLayerMap; + + LayersId mId; + + // These fields keep track of the latest epoch values in the child and the + // parent. mChildEpoch is the latest epoch value received from the child. + // mParentEpoch is the latest epoch value that we have told BrowserParent + // about (via ObserveLayerUpdate). + LayersObserverEpoch mChildEpoch; + LayersObserverEpoch mParentEpoch; + + TimeDuration mVsyncRate; + + struct PendingTransaction { + TransactionId mId; + VsyncId mTxnVsyncId; + TimeStamp mVsyncStartTime; + TimeStamp mRefreshStartTime; + TimeStamp mTxnStartTime; + TimeStamp mTxnEndTime; + TimeStamp mFwdTime; + nsCString mTxnURL; + bool mContainsSVG; + }; + AutoTArray mPendingTransactions; + + // When the widget/frame/browser stuff in this process begins its + // destruction process, we need to Disconnect() all the currently + // live shadow layers, because some of them might be orphaned from + // the layer tree. This happens in Destroy() above. After we + // Destroy() ourself, there's a window in which that information + // hasn't yet propagated back to the child side and it might still + // send us layer transactions. We want to ignore those transactions + // because they refer to "zombie layers" on this side. So, we track + // that state with |mDestroyed|. This is similar to, but separate + // from, |mLayerManager->IsDestroyed()|; we might have had Destroy() + // called on us but the mLayerManager might not be destroyed, or + // vice versa. In both cases though, we want to ignore shadow-layer + // transactions posted by the child. + + bool mDestroyed; + bool mIPCOpen; + + // This is set during RecvUpdate to track whether we'll need to update + // APZ's hit test regions. + bool mUpdateHitTestingTree; +}; + +} // namespace layers +} // namespace mozilla + +#endif // MOZILLA_LAYERS_LAYERTRANSACTIONPARENT_H diff --git a/gfx/layers/ipc/LayerTreeOwnerTracker.cpp b/gfx/layers/ipc/LayerTreeOwnerTracker.cpp new file mode 100644 index 0000000000..ab3551ad29 --- /dev/null +++ b/gfx/layers/ipc/LayerTreeOwnerTracker.cpp @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "LayerTreeOwnerTracker.h" + +#include "mozilla/StaticPtr.h" // for StaticAutoPtr +#include "mozilla/gfx/GPUChild.h" // for GPUChild +#include "mozilla/gfx/GPUProcessManager.h" // for GPUProcessManager + +#include +#include // for std::make_pair + +namespace mozilla { +namespace layers { + +static StaticAutoPtr sSingleton; + +LayerTreeOwnerTracker::LayerTreeOwnerTracker() + : mLayerIdsLock("LayerTreeOwnerTrackerLock") {} + +void LayerTreeOwnerTracker::Initialize() { + MOZ_ASSERT(!sSingleton); + sSingleton = new LayerTreeOwnerTracker(); +} + +void LayerTreeOwnerTracker::Shutdown() { sSingleton = nullptr; } + +LayerTreeOwnerTracker* LayerTreeOwnerTracker::Get() { return sSingleton; } + +void LayerTreeOwnerTracker::Map(LayersId aLayersId, + base::ProcessId aProcessId) { + MutexAutoLock lock(mLayerIdsLock); + + // Add the mapping to the list + mLayerIds[aLayersId] = aProcessId; +} + +void LayerTreeOwnerTracker::Unmap(LayersId aLayersId, + base::ProcessId aProcessId) { + MutexAutoLock lock(mLayerIdsLock); + + MOZ_ASSERT(mLayerIds[aLayersId] == aProcessId); + mLayerIds.erase(aLayersId); +} + +bool LayerTreeOwnerTracker::IsMapped(LayersId aLayersId, + base::ProcessId aProcessId) { + MutexAutoLock lock(mLayerIdsLock); + + auto iter = mLayerIds.find(aLayersId); + return iter != mLayerIds.end() && iter->second == aProcessId; +} + +void LayerTreeOwnerTracker::Iterate( + const std::function& + aCallback) { + MutexAutoLock lock(mLayerIdsLock); + + for (const auto& iter : mLayerIds) { + aCallback(iter.first, iter.second); + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/LayerTreeOwnerTracker.h b/gfx/layers/ipc/LayerTreeOwnerTracker.h new file mode 100644 index 0000000000..a3454f2505 --- /dev/null +++ b/gfx/layers/ipc/LayerTreeOwnerTracker.h @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_LayerTreeOwnerTracker_h +#define mozilla_layers_LayerTreeOwnerTracker_h + +#include "base/process.h" // for base::ProcessId +#include "LayersTypes.h" // for LayersId +#include "mozilla/Mutex.h" // for mozilla::Mutex + +#include +#include + +namespace mozilla { + +namespace dom { +class ContentParent; +} + +namespace layers { + +/** + * A utility class for tracking which content processes should be allowed + * to access which layer trees. + * + * ProcessId's are used to track which content process can access the layer + * tree, and in the case of nested browser's we use the top level content + * processes' ProcessId. + * + * This class is only available in the main process and gpu process. Mappings + * are synced from main process to the gpu process. The actual syncing happens + * in GPUProcessManager, and so this class should not be used directly. + */ +class LayerTreeOwnerTracker final { + public: + static void Initialize(); + static void Shutdown(); + static LayerTreeOwnerTracker* Get(); + + /** + * Map aLayersId and aProcessId together so that that process + * can access that layer tree. + */ + void Map(LayersId aLayersId, base::ProcessId aProcessId); + + /** + * Remove an existing mapping. + */ + void Unmap(LayersId aLayersId, base::ProcessId aProcessId); + + /** + * Checks whether it is okay for aProcessId to access aLayersId. + */ + bool IsMapped(LayersId aLayersId, base::ProcessId aProcessId); + + void Iterate( + const std::function& + aCallback); + + private: + LayerTreeOwnerTracker(); + + mozilla::Mutex mLayerIdsLock; + std::map mLayerIds; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_LayerTreeOwnerTracker_h diff --git a/gfx/layers/ipc/LayersMessageUtils.h b/gfx/layers/ipc/LayersMessageUtils.h new file mode 100644 index 0000000000..1bef016108 --- /dev/null +++ b/gfx/layers/ipc/LayersMessageUtils.h @@ -0,0 +1,980 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_LayersMessageUtils +#define mozilla_layers_LayersMessageUtils + +#include + +#include + +#include "FrameMetrics.h" +#include "VsyncSource.h" +#include "chrome/common/ipc_message_utils.h" +#include "ipc/EnumSerializer.h" +#include "ipc/IPCMessageUtils.h" +#include "mozilla/MotionPathUtils.h" +#include "mozilla/ServoBindings.h" +#include "mozilla/ipc/ByteBuf.h" +#include "mozilla/layers/APZInputBridge.h" +#include "mozilla/layers/AsyncDragMetrics.h" +#include "mozilla/layers/CompositorOptions.h" +#include "mozilla/layers/CompositorTypes.h" +#include "mozilla/layers/FocusTarget.h" +#include "mozilla/layers/GeckoContentControllerTypes.h" +#include "mozilla/layers/KeyboardMap.h" +#include "mozilla/layers/LayerAttributes.h" +#include "mozilla/layers/LayersTypes.h" +#include "mozilla/layers/MatrixMessage.h" +#include "mozilla/layers/RepaintRequest.h" +#include "nsSize.h" + +// For ParamTraits, could be moved to cpp file +#include "ipc/nsGUIEventIPC.h" +#include "mozilla/GfxMessageUtils.h" +#include "mozilla/ipc/ByteBufUtils.h" + +#ifdef _MSC_VER +# pragma warning(disable : 4800) +#endif + +namespace IPC { + +template <> +struct ParamTraits + : public PlainOldDataSerializer {}; + +template +struct ParamTraits> + : public PlainOldDataSerializer> {}; + +template <> +struct ParamTraits + : public PlainOldDataSerializer {}; + +template <> +struct ParamTraits { + typedef mozilla::VsyncEvent paramType; + + static void Write(Message* msg, const paramType& param) { + WriteParam(msg, param.mId); + WriteParam(msg, param.mTime); + WriteParam(msg, param.mOutputTime); + } + static bool Read(const Message* msg, PickleIterator* iter, + paramType* result) { + return ReadParam(msg, iter, &result->mId) && + ReadParam(msg, iter, &result->mTime) && + ReadParam(msg, iter, &result->mOutputTime); + } +}; + +template <> +struct ParamTraits { + typedef mozilla::layers::MatrixMessage paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mMatrix); + WriteParam(aMsg, aParam.mTopLevelViewportVisibleRectInBrowserCoords); + WriteParam(aMsg, aParam.mLayersId); + } + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return ReadParam(aMsg, aIter, &aResult->mMatrix) && + ReadParam(aMsg, aIter, + &aResult->mTopLevelViewportVisibleRectInBrowserCoords) && + ReadParam(aMsg, aIter, &aResult->mLayersId); + } +}; + +template <> +struct ParamTraits + : public PlainOldDataSerializer {}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializer {}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializer< + mozilla::layers::LayersBackend, + mozilla::layers::LayersBackend::LAYERS_NONE, + mozilla::layers::LayersBackend::LAYERS_LAST> {}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializer< + mozilla::layers::WebRenderBackend, + mozilla::layers::WebRenderBackend::HARDWARE, + mozilla::layers::WebRenderBackend::LAST> {}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializer< + mozilla::layers::WebRenderCompositor, + mozilla::layers::WebRenderCompositor::DRAW, + mozilla::layers::WebRenderCompositor::LAST> {}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializer {}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializerInclusive< + mozilla::layers::ScaleMode, mozilla::layers::ScaleMode::SCALE_NONE, + mozilla::layers::kHighestScaleMode> {}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializerInclusive< + mozilla::StyleScrollSnapStrictness, + mozilla::StyleScrollSnapStrictness::None, + mozilla::StyleScrollSnapStrictness::Proximity> {}; + +template <> +struct ParamTraits + : public BitFlagsEnumSerializer {}; + +template <> +struct ParamTraits + : public BitFlagsEnumSerializer< + mozilla::layers::DiagnosticTypes, + mozilla::layers::DiagnosticTypes::ALL_BITS> {}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializerInclusive< + mozilla::layers::ScrollDirection, + mozilla::layers::ScrollDirection::eVertical, + mozilla::layers::kHighestScrollDirection> {}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializerInclusive< + mozilla::layers::FrameMetrics::ScrollOffsetUpdateType, + mozilla::layers::FrameMetrics::ScrollOffsetUpdateType::eNone, + mozilla::layers::FrameMetrics::sHighestScrollOffsetUpdateType> {}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializerInclusive< + mozilla::layers::RepaintRequest::ScrollOffsetUpdateType, + mozilla::layers::RepaintRequest::ScrollOffsetUpdateType::eNone, + mozilla::layers::RepaintRequest::sHighestScrollOffsetUpdateType> {}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializerInclusive< + mozilla::layers::OverscrollBehavior, + mozilla::layers::OverscrollBehavior::Auto, + mozilla::layers::kHighestOverscrollBehavior> {}; + +template <> +struct ParamTraits { + typedef mozilla::layers::LayerHandle paramType; + + static void Write(Message* msg, const paramType& param) { + WriteParam(msg, param.mHandle); + } + static bool Read(const Message* msg, PickleIterator* iter, + paramType* result) { + return ReadParam(msg, iter, &result->mHandle); + } +}; + +template <> +struct ParamTraits { + typedef mozilla::layers::CompositableHandle paramType; + + static void Write(Message* msg, const paramType& param) { + WriteParam(msg, param.mHandle); + } + static bool Read(const Message* msg, PickleIterator* iter, + paramType* result) { + return ReadParam(msg, iter, &result->mHandle); + } +}; + +template <> +struct ParamTraits + : BitfieldHelper { + typedef mozilla::layers::FrameMetrics paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mScrollId); + WriteParam(aMsg, aParam.mPresShellResolution); + WriteParam(aMsg, aParam.mCompositionBounds); + WriteParam(aMsg, aParam.mDisplayPort); + WriteParam(aMsg, aParam.mCriticalDisplayPort); + WriteParam(aMsg, aParam.mScrollableRect); + WriteParam(aMsg, aParam.mCumulativeResolution); + WriteParam(aMsg, aParam.mDevPixelsPerCSSPixel); + WriteParam(aMsg, aParam.mScrollOffset); + WriteParam(aMsg, aParam.mZoom); + WriteParam(aMsg, aParam.mScrollGeneration); + WriteParam(aMsg, aParam.mRootCompositionSize); + WriteParam(aMsg, aParam.mDisplayPortMargins); + WriteParam(aMsg, aParam.mPresShellId); + WriteParam(aMsg, aParam.mLayoutViewport); + WriteParam(aMsg, aParam.mExtraResolution); + WriteParam(aMsg, aParam.mPaintRequestTime); + WriteParam(aMsg, aParam.mVisualDestination); + WriteParam(aMsg, aParam.mVisualScrollUpdateType); + WriteParam(aMsg, aParam.mFixedLayerMargins); + WriteParam(aMsg, aParam.mCompositionSizeWithoutDynamicToolbar); + WriteParam(aMsg, aParam.mIsRootContent); + WriteParam(aMsg, aParam.mIsScrollInfoLayer); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return (ReadParam(aMsg, aIter, &aResult->mScrollId) && + ReadParam(aMsg, aIter, &aResult->mPresShellResolution) && + ReadParam(aMsg, aIter, &aResult->mCompositionBounds) && + ReadParam(aMsg, aIter, &aResult->mDisplayPort) && + ReadParam(aMsg, aIter, &aResult->mCriticalDisplayPort) && + ReadParam(aMsg, aIter, &aResult->mScrollableRect) && + ReadParam(aMsg, aIter, &aResult->mCumulativeResolution) && + ReadParam(aMsg, aIter, &aResult->mDevPixelsPerCSSPixel) && + ReadParam(aMsg, aIter, &aResult->mScrollOffset) && + ReadParam(aMsg, aIter, &aResult->mZoom) && + ReadParam(aMsg, aIter, &aResult->mScrollGeneration) && + ReadParam(aMsg, aIter, &aResult->mRootCompositionSize) && + ReadParam(aMsg, aIter, &aResult->mDisplayPortMargins) && + ReadParam(aMsg, aIter, &aResult->mPresShellId) && + ReadParam(aMsg, aIter, &aResult->mLayoutViewport) && + ReadParam(aMsg, aIter, &aResult->mExtraResolution) && + ReadParam(aMsg, aIter, &aResult->mPaintRequestTime) && + ReadParam(aMsg, aIter, &aResult->mVisualDestination) && + ReadParam(aMsg, aIter, &aResult->mVisualScrollUpdateType) && + ReadParam(aMsg, aIter, &aResult->mFixedLayerMargins) && + ReadParam(aMsg, aIter, + &aResult->mCompositionSizeWithoutDynamicToolbar) && + ReadBoolForBitfield(aMsg, aIter, aResult, + ¶mType::SetIsRootContent) && + ReadBoolForBitfield(aMsg, aIter, aResult, + ¶mType::SetIsScrollInfoLayer)); + } +}; + +template <> +struct ParamTraits + : BitfieldHelper { + typedef mozilla::layers::RepaintRequest paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mScrollId); + WriteParam(aMsg, aParam.mPresShellResolution); + WriteParam(aMsg, aParam.mCompositionBounds); + WriteParam(aMsg, aParam.mCumulativeResolution); + WriteParam(aMsg, aParam.mDevPixelsPerCSSPixel); + WriteParam(aMsg, aParam.mScrollOffset); + WriteParam(aMsg, aParam.mZoom); + WriteParam(aMsg, aParam.mScrollGeneration); + WriteParam(aMsg, aParam.mDisplayPortMargins); + WriteParam(aMsg, aParam.mPresShellId); + WriteParam(aMsg, aParam.mLayoutViewport); + WriteParam(aMsg, aParam.mExtraResolution); + WriteParam(aMsg, aParam.mPaintRequestTime); + WriteParam(aMsg, aParam.mScrollUpdateType); + WriteParam(aMsg, aParam.mIsRootContent); + WriteParam(aMsg, aParam.mIsAnimationInProgress); + WriteParam(aMsg, aParam.mIsScrollInfoLayer); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return (ReadParam(aMsg, aIter, &aResult->mScrollId) && + ReadParam(aMsg, aIter, &aResult->mPresShellResolution) && + ReadParam(aMsg, aIter, &aResult->mCompositionBounds) && + ReadParam(aMsg, aIter, &aResult->mCumulativeResolution) && + ReadParam(aMsg, aIter, &aResult->mDevPixelsPerCSSPixel) && + ReadParam(aMsg, aIter, &aResult->mScrollOffset) && + ReadParam(aMsg, aIter, &aResult->mZoom) && + ReadParam(aMsg, aIter, &aResult->mScrollGeneration) && + ReadParam(aMsg, aIter, &aResult->mDisplayPortMargins) && + ReadParam(aMsg, aIter, &aResult->mPresShellId) && + ReadParam(aMsg, aIter, &aResult->mLayoutViewport) && + ReadParam(aMsg, aIter, &aResult->mExtraResolution) && + ReadParam(aMsg, aIter, &aResult->mPaintRequestTime) && + ReadParam(aMsg, aIter, &aResult->mScrollUpdateType) && + ReadBoolForBitfield(aMsg, aIter, aResult, + ¶mType::SetIsRootContent) && + ReadBoolForBitfield(aMsg, aIter, aResult, + ¶mType::SetIsAnimationInProgress) && + ReadBoolForBitfield(aMsg, aIter, aResult, + ¶mType::SetIsScrollInfoLayer)); + } +}; + +template <> +struct ParamTraits { + typedef nsSize paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.width); + WriteParam(aMsg, aParam.height); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return ReadParam(aMsg, aIter, &aResult->width) && + ReadParam(aMsg, aIter, &aResult->height); + } +}; + +template <> +struct ParamTraits { + typedef mozilla::layers::ScrollSnapInfo::ScrollSnapRange paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mStart); + WriteParam(aMsg, aParam.mEnd); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return ReadParam(aMsg, aIter, &aResult->mStart) && + ReadParam(aMsg, aIter, &aResult->mEnd); + } +}; + +template <> +struct ParamTraits { + typedef mozilla::layers::ScrollSnapInfo paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mScrollSnapStrictnessX); + WriteParam(aMsg, aParam.mScrollSnapStrictnessY); + WriteParam(aMsg, aParam.mSnapPositionX); + WriteParam(aMsg, aParam.mSnapPositionY); + WriteParam(aMsg, aParam.mXRangeWiderThanSnapport); + WriteParam(aMsg, aParam.mYRangeWiderThanSnapport); + WriteParam(aMsg, aParam.mSnapportSize); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return (ReadParam(aMsg, aIter, &aResult->mScrollSnapStrictnessX) && + ReadParam(aMsg, aIter, &aResult->mScrollSnapStrictnessY) && + ReadParam(aMsg, aIter, &aResult->mSnapPositionX) && + ReadParam(aMsg, aIter, &aResult->mSnapPositionY) && + ReadParam(aMsg, aIter, &aResult->mXRangeWiderThanSnapport) && + ReadParam(aMsg, aIter, &aResult->mYRangeWiderThanSnapport) && + ReadParam(aMsg, aIter, &aResult->mSnapportSize)); + } +}; + +template <> +struct ParamTraits { + // Not using PlainOldDataSerializer so we get enum validation + // for the members. + + typedef mozilla::layers::OverscrollBehaviorInfo paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mBehaviorX); + WriteParam(aMsg, aParam.mBehaviorY); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return (ReadParam(aMsg, aIter, &aResult->mBehaviorX) && + ReadParam(aMsg, aIter, &aResult->mBehaviorY)); + } +}; + +template <> +struct ParamTraits { + typedef mozilla::layers::LayerClip paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mClipRect); + WriteParam(aMsg, aParam.mMaskLayerIndex); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return (ReadParam(aMsg, aIter, &aResult->mClipRect) && + ReadParam(aMsg, aIter, &aResult->mMaskLayerIndex)); + } +}; + +template <> +struct ParamTraits + : PlainOldDataSerializer {}; + +template <> +struct ParamTraits + : PlainOldDataSerializer {}; + +template <> +struct ParamTraits + : BitfieldHelper { + typedef mozilla::layers::ScrollMetadata paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mMetrics); + WriteParam(aMsg, aParam.mSnapInfo); + WriteParam(aMsg, aParam.mScrollParentId); + WriteParam(aMsg, aParam.mBackgroundColor); + WriteParam(aMsg, aParam.GetContentDescription()); + WriteParam(aMsg, aParam.mLineScrollAmount); + WriteParam(aMsg, aParam.mPageScrollAmount); + WriteParam(aMsg, aParam.mScrollClip); + WriteParam(aMsg, aParam.mHasScrollgrab); + WriteParam(aMsg, aParam.mIsLayersIdRoot); + WriteParam(aMsg, aParam.mIsAutoDirRootContentRTL); + WriteParam(aMsg, aParam.mForceDisableApz); + WriteParam(aMsg, aParam.mResolutionUpdated); + WriteParam(aMsg, aParam.mIsRDMTouchSimulationActive); + WriteParam(aMsg, aParam.mDidContentGetPainted); + WriteParam(aMsg, aParam.mDisregardedDirection); + WriteParam(aMsg, aParam.mOverscrollBehavior); + WriteParam(aMsg, aParam.mScrollUpdates); + } + + static bool ReadContentDescription(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + nsCString str; + if (!ReadParam(aMsg, aIter, &str)) { + return false; + } + aResult->SetContentDescription(str); + return true; + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return (ReadParam(aMsg, aIter, &aResult->mMetrics) && + ReadParam(aMsg, aIter, &aResult->mSnapInfo) && + ReadParam(aMsg, aIter, &aResult->mScrollParentId) && + ReadParam(aMsg, aIter, &aResult->mBackgroundColor) && + ReadContentDescription(aMsg, aIter, aResult) && + ReadParam(aMsg, aIter, &aResult->mLineScrollAmount) && + ReadParam(aMsg, aIter, &aResult->mPageScrollAmount) && + ReadParam(aMsg, aIter, &aResult->mScrollClip) && + ReadBoolForBitfield(aMsg, aIter, aResult, + ¶mType::SetHasScrollgrab) && + ReadBoolForBitfield(aMsg, aIter, aResult, + ¶mType::SetIsLayersIdRoot) && + ReadBoolForBitfield(aMsg, aIter, aResult, + ¶mType::SetIsAutoDirRootContentRTL) && + ReadBoolForBitfield(aMsg, aIter, aResult, + ¶mType::SetForceDisableApz) && + ReadBoolForBitfield(aMsg, aIter, aResult, + ¶mType::SetResolutionUpdated) && + ReadBoolForBitfield(aMsg, aIter, aResult, + ¶mType::SetIsRDMTouchSimulationActive)) && + ReadBoolForBitfield(aMsg, aIter, aResult, + ¶mType::SetDidContentGetPainted) && + ReadParam(aMsg, aIter, &aResult->mDisregardedDirection) && + ReadParam(aMsg, aIter, &aResult->mOverscrollBehavior) && + ReadParam(aMsg, aIter, &aResult->mScrollUpdates); + } +}; + +template <> +struct ParamTraits { + typedef mozilla::layers::TextureFactoryIdentifier paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mParentBackend); + WriteParam(aMsg, aParam.mWebRenderBackend); + WriteParam(aMsg, aParam.mWebRenderCompositor); + WriteParam(aMsg, aParam.mParentProcessType); + WriteParam(aMsg, aParam.mMaxTextureSize); + WriteParam(aMsg, aParam.mSupportsTextureDirectMapping); + WriteParam(aMsg, aParam.mCompositorUseANGLE); + WriteParam(aMsg, aParam.mCompositorUseDComp); + WriteParam(aMsg, aParam.mUseCompositorWnd); + WriteParam(aMsg, aParam.mSupportsTextureBlitting); + WriteParam(aMsg, aParam.mSupportsPartialUploads); + WriteParam(aMsg, aParam.mSupportsComponentAlpha); + WriteParam(aMsg, aParam.mUsingAdvancedLayers); + WriteParam(aMsg, aParam.mSyncHandle); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + bool result = + ReadParam(aMsg, aIter, &aResult->mParentBackend) && + ReadParam(aMsg, aIter, &aResult->mWebRenderBackend) && + ReadParam(aMsg, aIter, &aResult->mWebRenderCompositor) && + ReadParam(aMsg, aIter, &aResult->mParentProcessType) && + ReadParam(aMsg, aIter, &aResult->mMaxTextureSize) && + ReadParam(aMsg, aIter, &aResult->mSupportsTextureDirectMapping) && + ReadParam(aMsg, aIter, &aResult->mCompositorUseANGLE) && + ReadParam(aMsg, aIter, &aResult->mCompositorUseDComp) && + ReadParam(aMsg, aIter, &aResult->mUseCompositorWnd) && + ReadParam(aMsg, aIter, &aResult->mSupportsTextureBlitting) && + ReadParam(aMsg, aIter, &aResult->mSupportsPartialUploads) && + ReadParam(aMsg, aIter, &aResult->mSupportsComponentAlpha) && + ReadParam(aMsg, aIter, &aResult->mUsingAdvancedLayers) && + ReadParam(aMsg, aIter, &aResult->mSyncHandle); + return result; + } +}; + +template <> +struct ParamTraits { + typedef mozilla::layers::TextureInfo paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mCompositableType); + WriteParam(aMsg, aParam.mTextureFlags); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return ReadParam(aMsg, aIter, &aResult->mCompositableType) && + ReadParam(aMsg, aIter, &aResult->mTextureFlags); + } +}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializer< + mozilla::layers::CompositableType, + mozilla::layers::CompositableType::UNKNOWN, + mozilla::layers::CompositableType::COUNT> {}; + +template <> +struct ParamTraits { + typedef mozilla::layers::ScrollableLayerGuid paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mLayersId); + WriteParam(aMsg, aParam.mPresShellId); + WriteParam(aMsg, aParam.mScrollId); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return (ReadParam(aMsg, aIter, &aResult->mLayersId) && + ReadParam(aMsg, aIter, &aResult->mPresShellId) && + ReadParam(aMsg, aIter, &aResult->mScrollId)); + } +}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializer {}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializer< + mozilla::layers::APZHandledResult, + mozilla::layers::APZHandledResult::Unhandled, + mozilla::layers::APZHandledResult::Last> {}; + +template <> +struct ParamTraits { + typedef mozilla::layers::APZEventResult paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mStatus); + WriteParam(aMsg, aParam.mTargetGuid); + WriteParam(aMsg, aParam.mInputBlockId); + WriteParam(aMsg, aParam.mHandledResult); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return (ReadParam(aMsg, aIter, &aResult->mStatus) && + ReadParam(aMsg, aIter, &aResult->mTargetGuid) && + ReadParam(aMsg, aIter, &aResult->mInputBlockId) && + ReadParam(aMsg, aIter, &aResult->mHandledResult)); + } +}; + +template <> +struct ParamTraits { + typedef mozilla::layers::ZoomConstraints paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mAllowZoom); + WriteParam(aMsg, aParam.mAllowDoubleTapZoom); + WriteParam(aMsg, aParam.mMinZoom); + WriteParam(aMsg, aParam.mMaxZoom); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return (ReadParam(aMsg, aIter, &aResult->mAllowZoom) && + ReadParam(aMsg, aIter, &aResult->mAllowDoubleTapZoom) && + ReadParam(aMsg, aIter, &aResult->mMinZoom) && + ReadParam(aMsg, aIter, &aResult->mMaxZoom)); + } +}; + +template <> +struct ParamTraits { + typedef mozilla::layers::EventRegions paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mHitRegion); + WriteParam(aMsg, aParam.mDispatchToContentHitRegion); + WriteParam(aMsg, aParam.mNoActionRegion); + WriteParam(aMsg, aParam.mHorizontalPanRegion); + WriteParam(aMsg, aParam.mVerticalPanRegion); + WriteParam(aMsg, aParam.mDTCRequiresTargetConfirmation); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return (ReadParam(aMsg, aIter, &aResult->mHitRegion) && + ReadParam(aMsg, aIter, &aResult->mDispatchToContentHitRegion) && + ReadParam(aMsg, aIter, &aResult->mNoActionRegion) && + ReadParam(aMsg, aIter, &aResult->mHorizontalPanRegion) && + ReadParam(aMsg, aIter, &aResult->mVerticalPanRegion) && + ReadParam(aMsg, aIter, &aResult->mDTCRequiresTargetConfirmation)); + } +}; + +template <> +struct ParamTraits { + typedef mozilla::layers::FocusTarget::ScrollTargets paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mHorizontal); + WriteParam(aMsg, aParam.mVertical); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return ReadParam(aMsg, aIter, &aResult->mHorizontal) && + ReadParam(aMsg, aIter, &aResult->mVertical); + } +}; + +template <> +struct ParamTraits + : public EmptyStructSerializer< + mozilla::layers::FocusTarget::NoFocusTarget> {}; + +template <> +struct ParamTraits { + typedef mozilla::layers::FocusTarget paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mSequenceNumber); + WriteParam(aMsg, aParam.mFocusHasKeyEventListeners); + WriteParam(aMsg, aParam.mData); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + if (!ReadParam(aMsg, aIter, &aResult->mSequenceNumber) || + !ReadParam(aMsg, aIter, &aResult->mFocusHasKeyEventListeners) || + !ReadParam(aMsg, aIter, &aResult->mData)) { + return false; + } + return true; + } +}; + +template <> +struct ParamTraits< + mozilla::layers::KeyboardScrollAction::KeyboardScrollActionType> + : public ContiguousEnumSerializerInclusive< + mozilla::layers::KeyboardScrollAction::KeyboardScrollActionType, + mozilla::layers::KeyboardScrollAction::KeyboardScrollActionType:: + eScrollCharacter, + mozilla::layers::KeyboardScrollAction:: + sHighestKeyboardScrollActionType> {}; + +template <> +struct ParamTraits { + typedef mozilla::layers::KeyboardScrollAction paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mType); + WriteParam(aMsg, aParam.mForward); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return ReadParam(aMsg, aIter, &aResult->mType) && + ReadParam(aMsg, aIter, &aResult->mForward); + } +}; + +template <> +struct ParamTraits { + typedef mozilla::layers::KeyboardShortcut paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mAction); + WriteParam(aMsg, aParam.mKeyCode); + WriteParam(aMsg, aParam.mCharCode); + WriteParam(aMsg, aParam.mModifiers); + WriteParam(aMsg, aParam.mModifiersMask); + WriteParam(aMsg, aParam.mEventType); + WriteParam(aMsg, aParam.mDispatchToContent); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return ReadParam(aMsg, aIter, &aResult->mAction) && + ReadParam(aMsg, aIter, &aResult->mKeyCode) && + ReadParam(aMsg, aIter, &aResult->mCharCode) && + ReadParam(aMsg, aIter, &aResult->mModifiers) && + ReadParam(aMsg, aIter, &aResult->mModifiersMask) && + ReadParam(aMsg, aIter, &aResult->mEventType) && + ReadParam(aMsg, aIter, &aResult->mDispatchToContent); + } +}; + +template <> +struct ParamTraits { + typedef mozilla::layers::KeyboardMap paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.Shortcuts()); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + nsTArray shortcuts; + if (!ReadParam(aMsg, aIter, &shortcuts)) { + return false; + } + *aResult = mozilla::layers::KeyboardMap(std::move(shortcuts)); + return true; + } +}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializerInclusive< + mozilla::layers::GeckoContentController_TapType, + mozilla::layers::GeckoContentController_TapType::eSingleTap, + mozilla::layers::kHighestGeckoContentController_TapType> {}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializerInclusive< + mozilla::layers::GeckoContentController_APZStateChange, + mozilla::layers::GeckoContentController_APZStateChange:: + eTransformBegin, + mozilla::layers::kHighestGeckoContentController_APZStateChange> {}; + +template <> +struct ParamTraits + : public BitFlagsEnumSerializer< + mozilla::layers::EventRegionsOverride, + mozilla::layers::EventRegionsOverride::ALL_BITS> {}; + +template <> +struct ParamTraits { + typedef mozilla::layers::AsyncDragMetrics paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mViewId); + WriteParam(aMsg, aParam.mPresShellId); + WriteParam(aMsg, aParam.mDragStartSequenceNumber); + WriteParam(aMsg, aParam.mScrollbarDragOffset); + WriteParam(aMsg, aParam.mDirection); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return (ReadParam(aMsg, aIter, &aResult->mViewId) && + ReadParam(aMsg, aIter, &aResult->mPresShellId) && + ReadParam(aMsg, aIter, &aResult->mDragStartSequenceNumber) && + ReadParam(aMsg, aIter, &aResult->mScrollbarDragOffset) && + ReadParam(aMsg, aIter, &aResult->mDirection)); + } +}; + +template <> +struct ParamTraits { + typedef mozilla::layers::CompositorOptions paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mUseAPZ); + WriteParam(aMsg, aParam.mUseWebRender); + WriteParam(aMsg, aParam.mUseAdvancedLayers); + WriteParam(aMsg, aParam.mInitiallyPaused); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return ReadParam(aMsg, aIter, &aResult->mUseAPZ) && + ReadParam(aMsg, aIter, &aResult->mUseWebRender) && + ReadParam(aMsg, aIter, &aResult->mUseAdvancedLayers) && + ReadParam(aMsg, aIter, &aResult->mInitiallyPaused); + } +}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializerInclusive< + mozilla::layers::ScrollbarLayerType, + mozilla::layers::ScrollbarLayerType::None, + mozilla::layers::kHighestScrollbarLayerType> {}; + +template <> +struct ParamTraits { + typedef mozilla::layers::ScrollbarData paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mDirection); + WriteParam(aMsg, aParam.mScrollbarLayerType); + WriteParam(aMsg, aParam.mThumbRatio); + WriteParam(aMsg, aParam.mThumbStart); + WriteParam(aMsg, aParam.mThumbLength); + WriteParam(aMsg, aParam.mThumbIsAsyncDraggable); + WriteParam(aMsg, aParam.mScrollTrackStart); + WriteParam(aMsg, aParam.mScrollTrackLength); + WriteParam(aMsg, aParam.mTargetViewId); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return ReadParam(aMsg, aIter, &aResult->mDirection) && + ReadParam(aMsg, aIter, &aResult->mScrollbarLayerType) && + ReadParam(aMsg, aIter, &aResult->mThumbRatio) && + ReadParam(aMsg, aIter, &aResult->mThumbStart) && + ReadParam(aMsg, aIter, &aResult->mThumbLength) && + ReadParam(aMsg, aIter, &aResult->mThumbIsAsyncDraggable) && + ReadParam(aMsg, aIter, &aResult->mScrollTrackStart) && + ReadParam(aMsg, aIter, &aResult->mScrollTrackLength) && + ReadParam(aMsg, aIter, &aResult->mTargetViewId); + } +}; + +template <> +struct ParamTraits + : public PlainOldDataSerializer< + mozilla::layers::SimpleLayerAttributes::FixedPositionData> {}; + +template <> +struct ParamTraits + : public PlainOldDataSerializer< + mozilla::layers::SimpleLayerAttributes::StickyPositionData> {}; + +template <> +struct ParamTraits { + typedef mozilla::layers::SimpleLayerAttributes paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mTransform); + WriteParam(aMsg, aParam.mTransformIsPerspective); + WriteParam(aMsg, aParam.mScrolledClip); + WriteParam(aMsg, aParam.mPostXScale); + WriteParam(aMsg, aParam.mPostYScale); + WriteParam(aMsg, aParam.mContentFlags); + WriteParam(aMsg, aParam.mOpacity); + WriteParam(aMsg, aParam.mIsFixedPosition); + WriteParam(aMsg, aParam.mIsAsyncZoomContainerForViewId); + WriteParam(aMsg, aParam.mScrollbarData); + WriteParam(aMsg, aParam.mMixBlendMode); + WriteParam(aMsg, aParam.mForceIsolatedGroup); + WriteParam(aMsg, aParam.mFixedPositionData); + WriteParam(aMsg, aParam.mStickyPositionData); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return ReadParam(aMsg, aIter, &aResult->mTransform) && + ReadParam(aMsg, aIter, &aResult->mTransformIsPerspective) && + ReadParam(aMsg, aIter, &aResult->mScrolledClip) && + ReadParam(aMsg, aIter, &aResult->mPostXScale) && + ReadParam(aMsg, aIter, &aResult->mPostYScale) && + ReadParam(aMsg, aIter, &aResult->mContentFlags) && + ReadParam(aMsg, aIter, &aResult->mOpacity) && + ReadParam(aMsg, aIter, &aResult->mIsFixedPosition) && + ReadParam(aMsg, aIter, &aResult->mIsAsyncZoomContainerForViewId) && + ReadParam(aMsg, aIter, &aResult->mScrollbarData) && + ReadParam(aMsg, aIter, &aResult->mMixBlendMode) && + ReadParam(aMsg, aIter, &aResult->mForceIsolatedGroup) && + ReadParam(aMsg, aIter, &aResult->mFixedPositionData) && + ReadParam(aMsg, aIter, &aResult->mStickyPositionData); + } +}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializerInclusive< + mozilla::layers::CompositionPayloadType, + mozilla::layers::CompositionPayloadType::eKeyPress, + mozilla::layers::kHighestCompositionPayloadType> {}; + +template <> +struct ParamTraits { + typedef mozilla::layers::CompositionPayload paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mType); + WriteParam(aMsg, aParam.mTimeStamp); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return ReadParam(aMsg, aIter, &aResult->mType) && + ReadParam(aMsg, aIter, &aResult->mTimeStamp); + } +}; + +template <> +struct ParamTraits { + typedef mozilla::RayReferenceData paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mInitialPosition); + WriteParam(aMsg, aParam.mContainingBlockRect); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return (ReadParam(aMsg, aIter, &aResult->mInitialPosition) && + ReadParam(aMsg, aIter, &aResult->mContainingBlockRect)); + } +}; + +#define IMPL_PARAMTRAITS_BY_SERDE(type_) \ + template <> \ + struct ParamTraits { \ + typedef mozilla::type_ paramType; \ + static void Write(Message* aMsg, const paramType& aParam) { \ + mozilla::ipc::ByteBuf v; \ + mozilla::DebugOnly rv = Servo_##type_##_Serialize(&aParam, &v); \ + MOZ_ASSERT(rv, "Serialize ##type_## failed"); \ + WriteParam(aMsg, std::move(v)); \ + } \ + static bool Read(const Message* aMsg, PickleIterator* aIter, \ + paramType* aResult) { \ + mozilla::ipc::ByteBuf in; \ + bool rv = ReadParam(aMsg, aIter, &in); \ + if (!rv) { \ + return false; \ + } \ + return in.mData && Servo_##type_##_Deserialize(&in, aResult); \ + } \ + }; + +IMPL_PARAMTRAITS_BY_SERDE(LengthPercentage) +IMPL_PARAMTRAITS_BY_SERDE(StyleOffsetPath) +IMPL_PARAMTRAITS_BY_SERDE(StyleOffsetRotate) +IMPL_PARAMTRAITS_BY_SERDE(StylePositionOrAuto) +IMPL_PARAMTRAITS_BY_SERDE(StyleRotate) +IMPL_PARAMTRAITS_BY_SERDE(StyleScale) +IMPL_PARAMTRAITS_BY_SERDE(StyleTranslate) +IMPL_PARAMTRAITS_BY_SERDE(StyleTransform) + +} /* namespace IPC */ + +#endif /* mozilla_layers_LayersMessageUtils */ diff --git a/gfx/layers/ipc/LayersMessages.ipdlh b/gfx/layers/ipc/LayersMessages.ipdlh new file mode 100644 index 0000000000..ee283795e8 --- /dev/null +++ b/gfx/layers/ipc/LayersMessages.ipdlh @@ -0,0 +1,573 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include LayersSurfaces; +include protocol PCompositorBridge; +include protocol PTexture; + +include "gfxipc/ShadowLayerUtils.h"; +include "mozilla/GfxMessageUtils.h"; +include "ImageLayers.h"; + +using mozilla::gfx::Glyph from "mozilla/gfx/2D.h"; +using mozilla::gfx::SamplingFilter from "mozilla/gfx/2D.h"; +using struct mozilla::gfx::DeviceColor from "mozilla/gfx/2D.h"; +using struct mozilla::gfx::Point from "mozilla/gfx/Point.h"; +using struct mozilla::gfx::Point3D from "mozilla/gfx/Point.h"; +using mozilla::gfx::IntPoint from "mozilla/gfx/Point.h"; +using class mozilla::gfx::Matrix4x4 from "mozilla/gfx/Matrix.h"; +using class mozilla::SideBits from "mozilla/gfx/Types.h"; +using nscolor from "nsColor.h"; +using nscoord from "nsCoord.h"; +using struct nsRect from "nsRect.h"; +using struct nsPoint from "nsPoint.h"; +using class mozilla::TimeDuration from "mozilla/TimeStamp.h"; +using class mozilla::TimeStamp from "mozilla/TimeStamp.h"; +using mozilla::ScreenRotation from "mozilla/WidgetUtils.h"; +using nsCSSPropertyID from "nsCSSPropertyID.h"; +using hal::ScreenOrientation from "mozilla/HalScreenConfiguration.h"; +using struct mozilla::layers::TextureInfo from "mozilla/layers/CompositorTypes.h"; +using mozilla::CSSPoint from "Units.h"; +using mozilla::CSSRect from "Units.h"; +using mozilla::LayerMargin from "Units.h"; +using mozilla::LayerPoint from "Units.h"; +using mozilla::LayerCoord from "Units.h"; +using mozilla::LayerSize from "Units.h"; +using mozilla::LayerRect from "Units.h"; +using mozilla::LayerIntSize from "Units.h"; +using mozilla::LayerIntRect from "Units.h"; +using mozilla::LayerIntRegion from "Units.h"; +using mozilla::ParentLayerIntRect from "Units.h"; +using mozilla::ParentLayerRect from "Units.h"; +using mozilla::LayoutDeviceIntRect from "Units.h"; +using mozilla::LayoutDevicePoint from "Units.h"; +using mozilla::LayoutDeviceRect from "Units.h"; +using mozilla::layers::ScaleMode from "mozilla/layers/LayersTypes.h"; +using mozilla::layers::EventRegions from "mozilla/layers/LayersTypes.h"; +using mozilla::layers::EventRegionsOverride from "mozilla/layers/LayersTypes.h"; +using mozilla::layers::DiagnosticTypes from "mozilla/layers/CompositorTypes.h"; +using mozilla::layers::FocusTarget from "mozilla/layers/FocusTarget.h"; +using struct mozilla::layers::ScrollMetadata from "FrameMetrics.h"; +using mozilla::layers::ScrollableLayerGuid::ViewID from "mozilla/layers/ScrollableLayerGuid.h"; +using mozilla::layers::LayersBackend from "mozilla/layers/LayersTypes.h"; +using mozilla::layers::LayerHandle from "mozilla/layers/LayersTypes.h"; +using mozilla::layers::CompositableHandle from "mozilla/layers/LayersTypes.h"; +using mozilla::layers::CompositionPayload from "mozilla/layers/LayersTypes.h"; +using mozilla::layers::SimpleLayerAttributes from "mozilla/layers/LayerAttributes.h"; +using mozilla::CrossProcessSemaphoreHandle from "mozilla/ipc/CrossProcessSemaphore.h"; +using struct mozilla::void_t from "mozilla/ipc/IPCCore.h"; +using mozilla::layers::LayersId from "mozilla/layers/LayersTypes.h"; +using mozilla::layers::TransactionId from "mozilla/layers/LayersTypes.h"; +using mozilla::VsyncId from "mozilla/VsyncDispatcher.h"; +using mozilla::LengthPercentage from "mozilla/ServoStyleConsts.h"; +using mozilla::RayReferenceData from "mozilla/MotionPathUtils.h"; +using mozilla::StyleOffsetPath from "mozilla/ServoStyleConsts.h"; +using mozilla::StyleOffsetRotate from "mozilla/ServoStyleConsts.h"; +using mozilla::StylePositionOrAuto from "mozilla/ServoStyleConsts.h"; +using mozilla::StyleRotate from "mozilla/ServoStyleConsts.h"; +using mozilla::StyleScale from "mozilla/ServoStyleConsts.h"; +using mozilla::StyleTranslate from "mozilla/ServoStyleConsts.h"; +using mozilla::StyleTransform from "mozilla/ServoStyleConsts.h"; + +namespace mozilla { +namespace layers { + +struct TargetConfig { + IntRect naturalBounds; + ScreenRotation rotation; + ScreenOrientation orientation; + nsIntRegion clearRegion; +}; + +// Create a shadow layer for |layer| +struct OpCreatePaintedLayer { LayerHandle layer; }; +struct OpCreateContainerLayer { LayerHandle layer; }; +struct OpCreateImageLayer { LayerHandle layer; }; +struct OpCreateColorLayer { LayerHandle layer; }; +struct OpCreateCanvasLayer { LayerHandle layer; }; +struct OpCreateRefLayer { LayerHandle layer; }; + +struct OpAttachCompositable { + LayerHandle layer; + CompositableHandle compositable; +}; + +struct OpAttachAsyncCompositable { + LayerHandle layer; + CompositableHandle compositable; +}; + +struct ThebesBufferData { + IntRect rect; + IntPoint rotation; +}; + +struct CubicBezierFunction { + float x1; + float y1; + float x2; + float y2; +}; + +struct StepFunction { + int steps; + uint8_t type; // Converted from StyleStepPosition. +}; + +union TimingFunction { + null_t; + CubicBezierFunction; + StepFunction; +}; + +struct LayerColor { DeviceColor value; }; + +comparable union Animatable { + null_t; + float; + nscolor; + StyleRotate; + StyleScale; + StyleTranslate; + StyleTransform; + StyleOffsetPath; + LengthPercentage; + StyleOffsetRotate; + StylePositionOrAuto; +}; + +struct AnimationSegment { + Animatable startState; + Animatable endState; + float startPortion; + float endPortion; + uint8_t startComposite; + uint8_t endComposite; + TimingFunction sampleFn; +}; + +comparable struct MotionPathData { + // the transform-origin property for motion in css pixels + CSSPoint origin; + // the adjustment for the anchor point of motion path. + CSSPoint anchorAdjustment; + RayReferenceData rayReferenceData; +}; + +comparable struct PartialPrerenderData { + LayoutDeviceRect rect; + SideBits overflowedSides; + // the scroll id of the nearest scrollable frame of this partial prerender + // data. + ViewID scrollId; + // The clip rectangle of the nearest scrollable frame. + // NOTE: This should be used only for fallback cases where APZ is not enabled. + ParentLayerRect clipRect; + // a transform from the coordinate space of the animated element to a + // coordinate space where the `clipRect` can be applied. + Matrix4x4 transformInClip; // Used only for WebRender. + // the position relative to the reference frame of the animated transform + // element in the element coordinate space. + LayoutDevicePoint position; // Used only for WebRender. +}; + +// Transforms need extra information to correctly convert the list of transform +// functions to a Matrix4x4 that can be applied directly to the layer. +comparable struct TransformData { + // the origin of the frame being transformed in app units + nsPoint origin; + // the transform-origin property for the transform in device pixels + Point3D transformOrigin; + nsRect bounds; + int32_t appUnitsPerDevPixel; + MotionPathData? motionPathData; + PartialPrerenderData? partialPrerenderData; +}; + +struct Animation { + // The zero time of this Animation's timeline. May be null if isNotPlaying is + // true. + TimeStamp originTime; + // The start time is relative to the originTime. This allows us to represent + // start times in the distant past that cannot be expressed using a TimeStamp. + TimeDuration? startTime; + TimeDuration delay; + TimeDuration endDelay; + // The value of the animation's current time at the moment it was sent to the + // compositor. This value will be used for below cases: + // 1) Animations that are play-pending. Initially these animations will have a + // null |startTime|. Once the animation is ready to start (i.e. painting + // has finished), we calculate an appropriate value of |startTime| such + // that playback begins from |holdTime|. + // 2) Not playing animations (e.g. paused and finished animations). In this + // case the |holdTime| represents the current time the animation will + // maintain. + TimeDuration holdTime; + TimeDuration duration; + // For each frame, the interpolation point is computed based on the + // startTime, the direction, the duration, and the current time. + // The segments must uniquely cover the portion from 0.0 to 1.0 + AnimationSegment[] segments; + // Number of times to repeat the animation, including positive infinity. + // Values <= 0 mean the animation will not play (although events are still + // dispatched on the main thread). + float iterations; + float iterationStart; + // This uses the NS_STYLE_ANIMATION_DIRECTION_* constants. + uint8_t direction; + // This uses dom::FillMode. + uint8_t fillMode; + nsCSSPropertyID property; + float playbackRate; + // When performing an asynchronous update to the playbackRate, |playbackRate| + // above is the updated playbackRate while |previousPlaybackRate| is the + // existing playbackRate. This is used by AnimationInfo to update the + // startTime based on the 'readyTime' (timestamp at the end of painting) + // and is not used beyond that point. + // + // It is set to numeric_limits::quiet_NaN() when no asynchronous update + // to the playbackRate is being performed. + float previousPlaybackRate; + // This is used in the transformed progress calculation. + TimingFunction easingFunction; + uint8_t iterationComposite; + // True if the animation has a fixed current time (e.g. paused and + // forward-filling animations). + bool isNotPlaying; + // True if this is not an animating property. For some transform-like + // properties, we just send their baseStyles for merging with other animating + // properties. In this case, we don't have animation information on this + // property, and so don't need to do interpolation. + bool isNotAnimating; + // The base style that animations should composite with. This is only set for + // animations with a composite mode of additive or accumulate, and only for + // the first animation in the set (i.e. the animation that is lowest in the + // stack). In all other cases the value is null_t. + Animatable baseStyle; + // An optional data specific for transform like properies. + TransformData? transformData; +}; + +struct CompositorAnimations { + Animation[] animations; + // This id is used to map the layer animations between content + // and compositor side + uint64_t id; +}; + +// Change a layer's attributes +struct CommonLayerAttributes { + LayerIntRegion visibleRegion; + EventRegions eventRegions; + bool useClipRect; + ParentLayerIntRect clipRect; + LayerHandle maskLayer; + LayerHandle[] ancestorMaskLayers; + // Animated colors will only honored for ColorLayers. + CompositorAnimations compositorAnimations; + nsIntRegion invalidRegion; + ScrollMetadata[] scrollMetadata; + nsCString displayListLog; +}; + +struct PaintedLayerAttributes { + nsIntRegion validRegion; +}; +struct ContainerLayerAttributes { + float preXScale; + float preYScale; + float inheritedXScale; + float inheritedYScale; + float presShellResolution; +}; + +struct ColorLayerAttributes { LayerColor color; IntRect bounds; }; +struct CanvasLayerAttributes { SamplingFilter samplingFilter; IntRect bounds; }; +struct RefLayerAttributes { + LayersId id; + EventRegionsOverride eventRegionsOverride; + LayerIntSize remoteDocumentSize; +}; +struct ImageLayerAttributes { SamplingFilter samplingFilter; IntSize scaleToSize; ScaleMode scaleMode; }; + +union SpecificLayerAttributes { + null_t; + PaintedLayerAttributes; + ContainerLayerAttributes; + ColorLayerAttributes; + CanvasLayerAttributes; + RefLayerAttributes; + ImageLayerAttributes; +}; + +struct LayerAttributes { + CommonLayerAttributes common; + SpecificLayerAttributes specific; +}; + +// See nsIWidget Configurations +comparable struct PluginWindowData { + uintptr_t windowId; + LayoutDeviceIntRect[] clip; + LayoutDeviceIntRect bounds; + bool visible; +}; + +struct OpSetSimpleLayerAttributes { + LayerHandle layer; + SimpleLayerAttributes attrs; +}; + +struct OpSetLayerAttributes { + LayerHandle layer; + LayerAttributes attrs; +}; + +// Monkey with the tree structure +struct OpSetRoot { LayerHandle root; }; +struct OpInsertAfter { LayerHandle container; LayerHandle childLayer; LayerHandle after; }; +struct OpPrependChild { LayerHandle container; LayerHandle childLayer; }; +struct OpRemoveChild { LayerHandle container; LayerHandle childLayer; }; +struct OpRepositionChild { LayerHandle container; LayerHandle childLayer; LayerHandle after; }; +struct OpRaiseToTopChild { LayerHandle container; LayerHandle childLayer; }; + +struct OpSetDiagnosticTypes { DiagnosticTypes diagnostics; }; + +struct ShmemSection { + Shmem shmem; + uint32_t offset; + uint32_t size; +}; + +struct CrossProcessSemaphoreDescriptor { + CrossProcessSemaphoreHandle sem; +}; + +union ReadLockDescriptor { + ShmemSection; + CrossProcessSemaphoreDescriptor; + uintptr_t; + null_t; +}; + +struct TexturedTileDescriptor { + PTexture texture; + PTexture? textureOnWhite; + IntRect updateRect; + bool readLocked; + bool readLockedOnWhite; + bool wasPlaceholder; +}; + +struct PlaceholderTileDescriptor { +}; + +union TileDescriptor { + TexturedTileDescriptor; + PlaceholderTileDescriptor; +}; + +struct SurfaceDescriptorTiles { + nsIntRegion validRegion; + TileDescriptor[] tiles; + IntPoint tileOrigin; + IntSize tileSize; + int firstTileX; + int firstTileY; + int retainedWidth; + int retainedHeight; + float resolution; + float frameXResolution; + float frameYResolution; + bool isProgressive; +}; + +struct OpUseTiledLayerBuffer { + SurfaceDescriptorTiles tileLayerDescriptor; +}; + +struct OpPaintTextureRegion { + ThebesBufferData bufferData; + nsIntRegion updatedRegion; +}; + +/** + * Tells the CompositableHost to remove the corresponding TextureHost + */ +struct OpRemoveTexture { + PTexture texture; +}; + +struct TimedTexture { + PTexture texture; + TimeStamp timeStamp; + IntRect picture; + uint32_t frameID; + uint32_t producerID; + bool readLocked; +}; + +/** + * Tells the compositor-side which textures to use (for example, as front buffer + * if there are several textures for double buffering). + * This provides a list of textures with timestamps, ordered by timestamp. + * The newest texture whose timestamp is <= the current time is rendered + * (where null is considered less than every other timestamp). If there is no + * such texture, the first texture is rendered. + * The first timestamp value can be null, but the others must not be. + * The list must not be empty. + */ +struct OpUseTexture { + TimedTexture[] textures; +}; + +struct OpUseComponentAlphaTextures { + PTexture textureOnBlack; + PTexture textureOnWhite; + bool readLockedBlack; + bool readLockedWhite; +}; + +struct OpNotifyNotUsed { + uint64_t TextureId; + uint64_t fwdTransactionId; +}; + +struct OpDeliverAcquireFence { + PTexture texture; + FileDescriptor fenceFd; +}; + +struct OpDeliverReleaseFence { + FileDescriptor? fenceFd; + uint64_t bufferId; + uint64_t fwdTransactionId; + bool usesImageBridge; +}; + +union CompositableOperationDetail { + OpPaintTextureRegion; + + OpUseTiledLayerBuffer; + + OpRemoveTexture; + + OpUseTexture; + OpUseComponentAlphaTextures; + + OpDeliverAcquireFence; +}; + +struct CompositableOperation { + CompositableHandle compositable; + CompositableOperationDetail detail; +}; + +// A unit of a changeset; a set of these comprise a changeset +// If adding a new edit type that requires the hit testing tree to be updated, +// set the updateHitTestingTree flag to true in RecvUpdate() +union Edit { + OpCreatePaintedLayer; + OpCreateContainerLayer; + OpCreateImageLayer; + OpCreateColorLayer; + OpCreateCanvasLayer; + OpCreateRefLayer; + + OpSetDiagnosticTypes; + + OpSetRoot; + OpInsertAfter; + OpPrependChild; + OpRemoveChild; + OpRepositionChild; + OpRaiseToTopChild; + + OpAttachCompositable; + OpAttachAsyncCompositable; + + CompositableOperation; +}; + +// Operations related to destroying resources, always handled after the other +// operations for safety. +union OpDestroy { + PTexture; + CompositableHandle; +}; + +// Replies to operations + +struct OpContentBufferSwap { + CompositableHandle compositable; + nsIntRegion frontUpdatedRegion; +}; + +/** + * An ImageCompositeNotification is sent the first time a particular + * image is composited by an ImageHost. + */ +struct ImageCompositeNotification { + CompositableHandle compositable; + TimeStamp imageTimeStamp; + TimeStamp firstCompositeTimeStamp; + uint32_t frameID; + uint32_t producerID; +}; + +union AsyncParentMessageData { + OpNotifyNotUsed; + OpDeliverReleaseFence; +}; + +struct PaintTiming { + float serializeMs; + float sendMs; + float dlMs; + float dl2Ms; + float flbMs; + float rasterMs; +}; + +struct TransactionInfo +{ + Edit[] cset; + OpSetSimpleLayerAttributes[] setSimpleAttrs; + OpSetLayerAttributes[] setAttrs; + CompositableOperation[] paints; + OpDestroy[] toDestroy; + uint64_t fwdTransactionId; + TransactionId id; + TargetConfig targetConfig; + PluginWindowData[] plugins; + bool isFirstPaint; + FocusTarget focusTarget; + bool scheduleComposite; + uint32_t paintSequenceNumber; + bool isRepeatTransaction; + VsyncId vsyncId; + TimeStamp vsyncStart; + TimeStamp refreshStart; + TimeStamp transactionStart; + bool containsSVG; + nsCString url; + TimeStamp fwdTime; + /* This provides some timing information on any content that is meant to be + * presented during this transaction. + */ + CompositionPayload[] payload; +}; + +union OMTAValue { + null_t; + nscolor; + float; + Matrix4x4; +}; + +} // namespace +} // namespace diff --git a/gfx/layers/ipc/LayersSurfaces.ipdlh b/gfx/layers/ipc/LayersSurfaces.ipdlh new file mode 100644 index 0000000000..8661d6748f --- /dev/null +++ b/gfx/layers/ipc/LayersSurfaces.ipdlh @@ -0,0 +1,204 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include "gfxipc/ShadowLayerUtils.h"; + +using struct gfxPoint from "gfxPoint.h"; +using nsIntRegion from "nsRegion.h"; +using struct mozilla::layers::SurfaceDescriptorX11 from "gfxipc/SurfaceDescriptor.h"; +using mozilla::StereoMode from "ImageTypes.h"; +using struct mozilla::null_t from "mozilla/ipc/IPCCore.h"; +using mozilla::WindowsHandle from "mozilla/ipc/IPCTypes.h"; +using mozilla::gfx::YUVColorSpace from "mozilla/gfx/Types.h"; +using mozilla::gfx::ColorDepth from "mozilla/gfx/Types.h"; +using mozilla::gfx::ColorRange from "mozilla/gfx/Types.h"; +using mozilla::gfx::SurfaceFormat from "mozilla/gfx/Types.h"; +using mozilla::gfx::IntRect from "mozilla/gfx/Rect.h"; +using mozilla::gfx::IntSize from "mozilla/gfx/Point.h"; +using mozilla::ipc::SharedMemoryBasic::Handle from "mozilla/ipc/SharedMemoryBasic.h"; +using gfxImageFormat from "gfxTypes.h"; +using mozilla::layers::MaybeVideoBridgeSource from "mozilla/layers/VideoBridgeUtils.h"; + +namespace mozilla { +namespace layers { + +comparable struct SurfaceDescriptorFileMapping { + WindowsHandle handle; + SurfaceFormat format; + IntSize size; +}; + +comparable struct SurfaceDescriptorDIB { + // gfxWindowsSurface* + uintptr_t surface; +}; + +comparable struct SurfaceDescriptorD3D10 { + WindowsHandle handle; + SurfaceFormat format; + IntSize size; + YUVColorSpace yUVColorSpace; + ColorRange colorRange; +}; + +comparable struct SurfaceDescriptorDXGIYCbCr { + WindowsHandle handleY; + WindowsHandle handleCb; + WindowsHandle handleCr; + IntSize size; + IntSize sizeY; + IntSize sizeCbCr; + ColorDepth colorDepth; + YUVColorSpace yUVColorSpace; + ColorRange colorRange; +}; + +comparable struct SurfaceDescriptorMacIOSurface { + uint32_t surfaceId; + double scaleFactor; + bool isOpaque; + YUVColorSpace yUVColorSpace; +}; + +comparable struct SurfaceDescriptorDMABuf { + uint32_t bufferType; + uint64_t modifier; + uint32_t flags; + FileDescriptor[] fds; + uint32_t[] width; + uint32_t[] height; + uint32_t[] format; + uint32_t[] strides; + uint32_t[] offsets; + YUVColorSpace yUVColorSpace; + FileDescriptor[] fence; + uint32_t uid; + FileDescriptor[] refCount; +}; + +comparable struct SurfaceTextureDescriptor { + uint64_t handle; + IntSize size; + SurfaceFormat format; + bool continuous; + bool ignoreTransform; +}; + +comparable struct SurfaceDescriptorAndroidHardwareBuffer { + FileDescriptor handle; + uint64_t bufferId; + IntSize size; + SurfaceFormat format; +}; + +comparable struct EGLImageDescriptor { + uintptr_t image; // `EGLImage` is a `void*`. + uintptr_t fence; + IntSize size; + bool hasAlpha; +}; + +comparable struct SurfaceDescriptorSharedGLTexture { + uint32_t texture; + uint32_t target; + uintptr_t fence; + IntSize size; + bool hasAlpha; +}; + + +comparable union RemoteDecoderVideoSubDescriptor { + SurfaceDescriptorD3D10; + SurfaceDescriptorDXGIYCbCr; + SurfaceDescriptorDMABuf; + SurfaceDescriptorMacIOSurface; + null_t; +}; + +comparable struct SurfaceDescriptorRemoteDecoder { + uint64_t handle; + RemoteDecoderVideoSubDescriptor subdesc; + MaybeVideoBridgeSource source; +}; + +comparable struct SurfaceDescriptorPlugin { + uint64_t id; + SurfaceDescriptorD3D10 pluginSurf; + SurfaceDescriptorD3D10 displaySurf; +}; + +comparable union SurfaceDescriptorGPUVideo { + SurfaceDescriptorRemoteDecoder; + SurfaceDescriptorPlugin; +}; + +comparable struct RGBDescriptor { + IntSize size; + SurfaceFormat format; + bool hasIntermediateBuffer; +}; + +comparable struct YCbCrDescriptor { + IntRect display; + IntSize ySize; + uint32_t yStride; + IntSize cbCrSize; + uint32_t cbCrStride; + uint32_t yOffset; + uint32_t cbOffset; + uint32_t crOffset; + StereoMode stereoMode; + ColorDepth colorDepth; + YUVColorSpace yUVColorSpace; + ColorRange colorRange; + bool hasIntermediateBuffer; +}; + +comparable union BufferDescriptor { + RGBDescriptor; + YCbCrDescriptor; +}; + +comparable union MemoryOrShmem { + uintptr_t; + Shmem; +}; + +comparable struct SurfaceDescriptorBuffer { + BufferDescriptor desc; + MemoryOrShmem data; +}; + +comparable struct SurfaceDescriptorShared +{ + IntSize size; + int32_t stride; + SurfaceFormat format; + Handle handle; +}; + +comparable struct SurfaceDescriptorRecorded { + int64_t textureId; +}; + +comparable union SurfaceDescriptor { + SurfaceDescriptorBuffer; + SurfaceDescriptorDIB; + SurfaceDescriptorD3D10; + SurfaceDescriptorFileMapping; + SurfaceDescriptorDXGIYCbCr; + SurfaceDescriptorX11; + SurfaceDescriptorDMABuf; + SurfaceTextureDescriptor; + SurfaceDescriptorAndroidHardwareBuffer; + EGLImageDescriptor; + SurfaceDescriptorMacIOSurface; + SurfaceDescriptorSharedGLTexture; + SurfaceDescriptorGPUVideo; + SurfaceDescriptorRecorded; + null_t; +}; + +} // namespace +} // namespace diff --git a/gfx/layers/ipc/PAPZ.ipdl b/gfx/layers/ipc/PAPZ.ipdl new file mode 100644 index 0000000000..9f0a3db340 --- /dev/null +++ b/gfx/layers/ipc/PAPZ.ipdl @@ -0,0 +1,78 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include "mozilla/GfxMessageUtils.h"; +include "mozilla/layers/LayersMessageUtils.h"; + +include protocol PCompositorBridge; + +using CSSRect from "Units.h"; +using struct mozilla::layers::RepaintRequest from "mozilla/layers/RepaintRequest.h"; +using struct mozilla::layers::ScrollableLayerGuid from "mozilla/layers/ScrollableLayerGuid.h"; +using mozilla::layers::ScrollableLayerGuid::ViewID from "mozilla/layers/ScrollableLayerGuid.h"; +using mozilla::layers::MaybeZoomConstraints from "mozilla/layers/ZoomConstraints.h"; +using mozilla::layers::GeckoContentController_APZStateChange from "mozilla/layers/GeckoContentControllerTypes.h"; +using mozilla::layers::ScrollDirection from "mozilla/layers/LayersTypes.h"; +using mozilla::layers::MatrixMessage from "mozilla/layers/MatrixMessage.h"; +using mozilla::dom::TabId from "mozilla/dom/ipc/IdType.h"; +using mozilla::dom::ContentParentId from "mozilla/dom/ipc/IdType.h"; +using mozilla::layers::AsyncDragMetrics from "mozilla/layers/AsyncDragMetrics.h"; +using class nsRegion from "nsRegion.h"; + +namespace mozilla { +namespace layers { + + +/** + * PAPZ is a protocol for remoting a GeckoContentController. PAPZ lives on the + * PCompositorBridge protocol which either connects to the compositor thread + * in the main process, or to the compositor thread in the gpu processs. + * + * PAPZParent lives in the compositor thread, while PAPZChild lives wherever the remoted + * GeckoContentController lives (generally the main thread of the main or content process). + * RemoteContentController implements PAPZParent, while APZChild implements PAPZChild. + * + * PAPZ is always used for ContentProcessController and only used for ChromeProcessController + * when there is a gpu process, otherwhise ChromeProcessController is used directly on the + * compositor thread. Only the methods that are used by the [Chrome,Content]ProcessController + * are implemented. If a new method is needed then PAPZ, APZChild, and RemoteContentController + * must be updated to handle it. + */ +sync protocol PAPZ +{ + manager PCompositorBridge; + +parent: + async __delete__(); + +child: + async LayerTransforms(MatrixMessage[] aTransforms); + + async RequestContentRepaint(RepaintRequest request); + + async UpdateOverscrollVelocity(ScrollableLayerGuid aGuid, float aX, float aY, bool aIsRootContent); + + async UpdateOverscrollOffset(ScrollableLayerGuid aGuid, float aX, float aY, bool aIsRootContent); + + async NotifyMozMouseScrollEvent(ViewID aScrollId, nsString aEvent); + + async NotifyAPZStateChange(ScrollableLayerGuid aGuid, GeckoContentController_APZStateChange aChange, int aArg); + + async NotifyFlushComplete(); + + async NotifyAsyncScrollbarDragInitiated(uint64_t aDragBlockId, ViewID aScrollId, ScrollDirection aDirection); + + async NotifyAsyncScrollbarDragRejected(ViewID aScrollId); + + async NotifyAsyncAutoscrollRejected(ViewID aScrollId); + +both: + async Destroy(); +}; + +} // layers +} // mozilla diff --git a/gfx/layers/ipc/PAPZCTreeManager.ipdl b/gfx/layers/ipc/PAPZCTreeManager.ipdl new file mode 100644 index 0000000000..e6d70a9ddf --- /dev/null +++ b/gfx/layers/ipc/PAPZCTreeManager.ipdl @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include "mozilla/GfxMessageUtils.h"; +include "mozilla/layers/LayersMessageUtils.h"; +include "ipc/nsGUIEventIPC.h"; + +include protocol PCompositorBridge; + +using CSSRect from "Units.h"; +using LayoutDeviceCoord from "Units.h"; +using mozilla::LayoutDevicePoint from "Units.h"; +using ScreenPoint from "Units.h"; +using mozilla::layers::MaybeZoomConstraints from "mozilla/layers/ZoomConstraints.h"; +using mozilla::layers::ScrollableLayerGuid from "mozilla/layers/ScrollableLayerGuid.h"; +using mozilla::layers::ScrollableLayerGuid::ViewID from "mozilla/layers/ScrollableLayerGuid.h"; +using mozilla::layers::TouchBehaviorFlags from "mozilla/layers/LayersTypes.h"; +using mozilla::layers::AsyncDragMetrics from "mozilla/layers/AsyncDragMetrics.h"; +using mozilla::layers::GeckoContentController_TapType from "mozilla/layers/GeckoContentControllerTypes.h"; +using class mozilla::layers::KeyboardMap from "mozilla/layers/KeyboardMap.h"; +using mozilla::wr::RenderRoot from "mozilla/webrender/WebRenderTypes.h"; + +using mozilla::Modifiers from "mozilla/EventForwards.h"; +using mozilla::PinchGestureInput::PinchGestureType from "InputData.h"; + +namespace mozilla { +namespace layers { + +/** + * PAPZCTreeManager is a protocol for remoting an IAPZCTreeManager. PAPZCTreeManager + * lives on the PCompositorBridge protocol which either connects to the compositor + * thread in the main process, or to the compositor thread in the gpu processs. + * + * PAPZCTreeManagerParent lives in the compositor thread, while PAPZCTreeManagerChild + * lives in the main thread of the main or the content process. APZCTreeManagerParent + * and APZCTreeManagerChild implement this protocol. + */ +protocol PAPZCTreeManager +{ +manager PCompositorBridge; + +parent: + + // These messages correspond to the methods + // on the IAPZCTreeManager interface + + async ZoomToRect(ScrollableLayerGuid aGuid, CSSRect aRect, uint32_t Flags); + + async ContentReceivedInputBlock(uint64_t aInputBlockId, bool PreventDefault); + + async SetTargetAPZC(uint64_t aInputBlockId, ScrollableLayerGuid[] Targets); + + async UpdateZoomConstraints(ScrollableLayerGuid aGuid, MaybeZoomConstraints aConstraints); + + async SetKeyboardMap(KeyboardMap aKeyboardMap); + + async SetDPI(float aDpiValue); + + async SetAllowedTouchBehavior(uint64_t aInputBlockId, TouchBehaviorFlags[] aValues); + + async StartScrollbarDrag(ScrollableLayerGuid aGuid, AsyncDragMetrics aDragMetrics); + + async StartAutoscroll(ScrollableLayerGuid aGuid, ScreenPoint aAnchorLocation); + + async StopAutoscroll(ScrollableLayerGuid aGuid); + + async SetLongTapEnabled(bool aTapGestureEnabled); + + async __delete__(); + +child: + + async HandleTap(GeckoContentController_TapType aType, LayoutDevicePoint point, Modifiers aModifiers, + ScrollableLayerGuid aGuid, uint64_t aInputBlockId); + + async NotifyPinchGesture(PinchGestureType aType, ScrollableLayerGuid aGuid, + LayoutDevicePoint aFocusPoint, LayoutDeviceCoord aSpanChange, + Modifiers aModifiers); + + async CancelAutoscroll(ViewID aScrollId); +}; + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/layers/ipc/PAPZInputBridge.ipdl b/gfx/layers/ipc/PAPZInputBridge.ipdl new file mode 100644 index 0000000000..160a74f81c --- /dev/null +++ b/gfx/layers/ipc/PAPZInputBridge.ipdl @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +using LayoutDeviceIntPoint from "Units.h"; +using struct mozilla::layers::ScrollableLayerGuid from "mozilla/layers/ScrollableLayerGuid.h"; +using struct mozilla::layers::APZEventResult from "mozilla/layers/APZInputBridge.h"; + +using EventMessage from "mozilla/EventForwards.h"; +using class mozilla::MultiTouchInput from "InputData.h"; +using class mozilla::MouseInput from "InputData.h"; +using class mozilla::PanGestureInput from "InputData.h"; +using class mozilla::PinchGestureInput from "InputData.h"; +using class mozilla::TapGestureInput from "InputData.h"; +using class mozilla::ScrollWheelInput from "InputData.h"; +using class mozilla::KeyboardInput from "InputData.h"; + +using mozilla::layers::LayersId from "mozilla/layers/LayersTypes.h"; + +include protocol PGPU; + +namespace mozilla { +namespace layers { + +/** + * This protocol is used to send input events from the UI process to the + * GPU process for handling by APZ. There is one instance per top-level + * compositor, or in other words, one instance per concrete APZCTreeManager + * instance. The child side lives on the main thread in the UI process, + * and the parent side lives on the main thread in the GPU process. If there + * is no GPU process, then this protocol is not instantiated. + */ +sync refcounted protocol PAPZInputBridge +{ +manager PGPU; + +parent: + // The following messages are used to + // implement the ReceiveInputEvent methods + + sync ReceiveMultiTouchInputEvent(MultiTouchInput aEvent) + returns (APZEventResult aOutResult, + MultiTouchInput aOutEvent); + + sync ReceiveMouseInputEvent(MouseInput aEvent) + returns (APZEventResult aOutResult, + MouseInput aOutEvent); + + sync ReceivePanGestureInputEvent(PanGestureInput aEvent) + returns (APZEventResult aOutResult, + PanGestureInput aOutEvent); + + sync ReceivePinchGestureInputEvent(PinchGestureInput aEvent) + returns (APZEventResult aOutResult, + PinchGestureInput aOutEvent); + + sync ReceiveTapGestureInputEvent(TapGestureInput aEvent) + returns (APZEventResult aOutResult, + TapGestureInput aOutEvent); + + sync ReceiveScrollWheelInputEvent(ScrollWheelInput aEvent) + returns (APZEventResult aOutResult, + ScrollWheelInput aOutEvent); + + sync ReceiveKeyboardInputEvent(KeyboardInput aEvent) + returns (APZEventResult aOutResult, + KeyboardInput aOutEvent); + + async UpdateWheelTransaction(LayoutDeviceIntPoint aRefPoint, EventMessage aEventMessage); + + sync ProcessUnhandledEvent(LayoutDeviceIntPoint aRefPoint) + returns (LayoutDeviceIntPoint aOutRefPoint, + ScrollableLayerGuid aOutTargetGuid, + uint64_t aOutFocusSequenceNumber, + LayersId aOutLayersId); + + async __delete__(); +}; + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/layers/ipc/PCanvas.ipdl b/gfx/layers/ipc/PCanvas.ipdl new file mode 100644 index 0000000000..2be61bd142 --- /dev/null +++ b/gfx/layers/ipc/PCanvas.ipdl @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include "mozilla/layers/LayersMessageUtils.h"; + +using mozilla::CrossProcessSemaphoreHandle from "mozilla/ipc/CrossProcessSemaphore.h"; +using mozilla::layers::TextureType from "mozilla/layers/LayersTypes.h"; +using mozilla::ipc::SharedMemoryBasic::Handle from "mozilla/ipc/SharedMemoryBasic.h"; + +namespace mozilla { +namespace layers { + +/** + * PCanvas is the IPDL for recorded Canvas drawing. + */ +async refcounted protocol PCanvas { +parent: + /** + * Initialize a CanvasTranslator for a particular TextureType, which + * translates events from a CanvasEventRingBuffer. aReadHandle is the shared + * memory handle for the ring buffer. aReaderSem and aWriterSem are handles + * for the semaphores to handle waiting on either side. + */ + async InitTranslator(TextureType aTextureType, Handle aReadHandle, + CrossProcessSemaphoreHandle aReaderSem, + CrossProcessSemaphoreHandle aWriterSem); + + /** + * Used to tell the CanvasTranslator to start translating again after it has + * stopped due to a timeout waiting for events. + */ + async ResumeTranslation(); + + child: + /** + * Notify that the canvas device used by the translator has changed. + */ + async NotifyDeviceChanged(); + + /** + * Deactivate remote canvas, which will cause fall back to software. + */ + async Deactivate(); +}; + +} // layers +} // mozilla diff --git a/gfx/layers/ipc/PCompositorBridge.ipdl b/gfx/layers/ipc/PCompositorBridge.ipdl new file mode 100644 index 0000000000..1a14ac5d4b --- /dev/null +++ b/gfx/layers/ipc/PCompositorBridge.ipdl @@ -0,0 +1,310 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include LayersSurfaces; +include LayersMessages; +include PlatformWidgetTypes; +include PCompositorBridgeTypes; +include protocol PAPZ; +include protocol PAPZCTreeManager; +include protocol PBrowser; +include protocol PCanvas; +include protocol PCompositorManager; +include protocol PCompositorWidget; +include protocol PLayerTransaction; +include protocol PTexture; +include protocol PWebGL; +include protocol PWebRenderBridge; +include protocol PWebGPU; +include "mozilla/GfxMessageUtils.h"; +include "mozilla/layers/LayersMessageUtils.h"; +include "mozilla/layers/WebRenderMessageUtils.h"; + +using struct mozilla::null_t from "mozilla/ipc/IPCCore.h"; +using struct mozilla::layers::LayersId from "mozilla/layers/LayersTypes.h"; +using struct mozilla::layers::TextureFactoryIdentifier from "mozilla/layers/CompositorTypes.h"; +using struct mozilla::layers::ScrollableLayerGuid from "mozilla/layers/ScrollableLayerGuid.h"; +using mozilla::layers::ScrollableLayerGuid::ViewID from "mozilla/layers/ScrollableLayerGuid.h"; +using mozilla::layers::MaybeZoomConstraints from "mozilla/layers/ZoomConstraints.h"; +using mozilla::layers::WindowKind from "mozilla/layers/LayersTypes.h"; +using mozilla::layers::LayersBackend from "mozilla/layers/LayersTypes.h"; +using mozilla::CrossProcessMutexHandle from "mozilla/ipc/CrossProcessMutex.h"; +using mozilla::ipc::SharedMemoryBasic::Handle from "mozilla/ipc/SharedMemoryBasic.h"; +using mozilla::CSSIntRegion from "Units.h"; +using mozilla::LayoutDeviceIntPoint from "Units.h"; +using mozilla::LayoutDeviceIntRegion from "Units.h"; +using mozilla::LayoutDeviceIntSize from "Units.h"; +using class mozilla::TimeStamp from "mozilla/TimeStamp.h"; +using mozilla::layers::TextureFlags from "mozilla/layers/CompositorTypes.h"; +using mozilla::layers::CompositorOptions from "mozilla/layers/CompositorOptions.h"; +using mozilla::wr::PipelineId from "mozilla/webrender/WebRenderTypes.h"; +using mozilla::wr::IdNamespace from "mozilla/webrender/WebRenderTypes.h"; +using base::ProcessId from "base/process.h"; +using mozilla::wr::MaybeExternalImageId from "mozilla/webrender/WebRenderTypes.h"; +using mozilla::layers::LayersObserverEpoch from "mozilla/layers/LayersTypes.h"; +using mozilla::layers::TransactionId from "mozilla/layers/LayersTypes.h"; +using struct DxgiAdapterDesc from "mozilla/D3DMessageUtils.h"; + +namespace mozilla { +namespace layers { + +struct FrameStats { + TransactionId id; + TimeStamp compositeStart; + TimeStamp renderStart; + TimeStamp compositeEnd; + int32_t contentFrameTime; + double resourceUploadTime; + double gpuCacheUploadTime; + TimeStamp transactionStart; + TimeStamp refreshStart; + TimeStamp fwdTime; + TimeStamp sceneBuiltTime; + uint32_t skippedComposites; + nsCString url; +}; + +/** + * The PCompositorBridge protocol is a top-level protocol for the compositor. + * There is an instance of the protocol for each compositor, plus one for each + * content process. In other words: + * - There is a CompositorBridgeParent/CompositorBridgeChild pair created + * for each "top level browser window", which has its own compositor. The + * CompositorBridgeChild instance lives in the UI process, and the + * CompositorBridgeParent lives in the GPU process (if there is one) or the + * UI process otherwise. + * - There is also a ContentCompositorBridgeParent/CompositorBridgeChild + * pair created for each content process. The ContentCompositorBridgeParent + * lives in the GPU process (if there is one) or the UI process otherwise. The + * CompositorBridgeChild is a singleton in the content process. Note that + * a given content process may have multiple browser instances (represented + * by BrowserChild instances), that are attached to different windows, and therefore + * rendered by different compositors. This means that when a browser instance + * sends messages via its CompositorBridgeChild, the corresponding + * ContentCompositorBridgeParent has to use the layers id in the message + * to find the correct compositor or CompositorBridgeParent to pass the message + * on to. + * + * One of the main goals of this protocol is to manage the PLayerTransaction sub- + * protocol, which is per-browser. A lot of the functions in the protocol are + * basically multiplexing/demultiplexing stuff in PLayerTransaction. + */ +sync refcounted protocol PCompositorBridge +{ + manager PCompositorManager; + + manages PAPZ; + manages PAPZCTreeManager; + // A Compositor manages a single Layer Manager (PLayerTransaction) + manages PLayerTransaction; + manages PTexture; + manages PCompositorWidget; + manages PWebRenderBridge; + manages PWebGL; + manages PWebGPU; + +child: + // The child should invalidate retained layers. This is used for local + // compositor device resets, such as in CompositorD3D9, and ensures that + // TextureSources are recreated. + async InvalidateLayers(LayersId layersId); + + // The compositor completed a layers transaction. id is the layers id + // of the child layer tree that was composited (or 0 when notifying + // the root layer tree). + // transactionId is the id of the transaction before this composite, or 0 + // if there was no transaction since the last composite. + prio(mediumhigh) async DidComposite(LayersId id, TransactionId transactionId, + TimeStamp compositeStart, + TimeStamp compositeEnd); + + async NotifyFrameStats(FrameStats[] aFrameStats); + + /** + * Parent informs the child that the graphics objects are ready for + * compositing. This usually means that the graphics objects (textures + * and the like) are available on the GPU. This is used for chrome UI. + * @see RequestNotifyAfterRemotePaint + * @see PBrowser + */ + async RemotePaintIsReady(); + + /** + * Bounce plugin widget configurations over to the main thread for + * application on the widgets. Used on Windows and Linux in managing + * plugin widgets. + */ + async UpdatePluginConfigurations(LayoutDeviceIntPoint aContentOffset, + LayoutDeviceIntRegion aVisibleRegion, + PluginWindowData[] aPlugins); + + /** + * Captures an image for all visible child plugins of a given widget for use + * during scrolling. + * @param aParentWidget parent of widgets to be captured + */ + async CaptureAllPlugins(uintptr_t aParentWidget); + + /** + * Hides all registered plugin widgets associated with a particular chrome + * widget. + */ + async HideAllPlugins(uintptr_t aParentWidget); + + async ParentAsyncMessages(AsyncParentMessageData[] aMessages); + + async ObserveLayersUpdate(LayersId aLayersId, LayersObserverEpoch aEpoch, bool aActive); + + async CompositorOptionsChanged(LayersId id, CompositorOptions newOptions); + + async NotifyJankedAnimations(LayersId id, uint64_t[] aJankedAnimations); + +parent: + async __delete__(); + + // Must be called before Initialize(). + async PCompositorWidget(CompositorWidgetInitData aInitData); + + // When out-of-process, this must be called to finish initialization. + sync Initialize(LayersId rootLayerTreeId); + + // Must be called after Initialize(), and only succeeds if AsyncPanZoomEnabled() is true. + async PAPZ(LayersId layersId); + async PAPZCTreeManager(LayersId layersId); + + // Constructor for WebGPU IPDL + // Must be called before Initialize(). + async PWebGPU(); + + /** + * Confirmation callback for UpdatePluginConfigurations and HideAllPlugins. + */ + async RemotePluginsReady(); + + // The child is about to be destroyed, so perform any necessary cleanup. + sync WillClose(); + + // Pause/resume the compositor. These are intended to be used on mobile, when + // the compositor needs to pause/resume in lockstep with the application. + sync Pause(); + sync Resume(); + async ResumeAsync(); + + // See bug 1316632 comment #33 for why this has to be sync. Otherwise, + // there are ordering issues with SendPLayerTransactionConstructor. + sync NotifyChildCreated(LayersId id) + returns (CompositorOptions compositorOptions); + + // This version of NotifyChildCreated also performs a layer tree mapping. + // + // See bug 1316632 comment #33 for why this has to be sync. Otherwise, + // there are ordering issues with SendPLayerTransactionConstructor. + sync MapAndNotifyChildCreated(LayersId id, ProcessId owner) + returns (CompositorOptions compositorOptions); + + async AdoptChild(LayersId id); + + // Same as NotifyChildCreated, but used when child processes need to + // reassociate layers. This must be synchronous to ensure that the + // association happens before PLayerTransactions are sent over the + // cross-process bridge. + sync NotifyChildRecreated(LayersId id) + returns (CompositorOptions compositorOptions); + + // Make a snapshot of the content that would have been drawn to our + // render target at the time this message is received. If the size + // or format of |inSnapshot| doesn't match our render target, + // results are undefined. + // + // NB: this message will result in animations, transforms, effects, + // and so forth being interpolated. That's what we want to happen. + sync MakeSnapshot(SurfaceDescriptor inSnapshot, IntRect dirtyRect); + + // Make sure any pending composites are started immediately and + // block until they are completed. + sync FlushRendering(); + + // Same as FlushRendering, but asynchronous, since not all platforms require + // synchronous repaints on resize. + async FlushRenderingAsync(); + + // Make sure any pending composites have been received. + sync WaitOnTransactionProcessed(); + + // Force an additional frame presentation to be executed. This is used to + // work around a windows presentation bug (See Bug 1232042) + async ForcePresent(); + + sync StartFrameTimeRecording(int32_t bufferSize) + returns (uint32_t startIndex); + + sync StopFrameTimeRecording(uint32_t startIndex) + returns (float[] intervals); + + // layersBackendHints is an ordered list of preffered backends where + // layersBackendHints[0] is the best backend. If any hints are LayersBackend::LAYERS_NONE + // that hint is ignored. + async PLayerTransaction(LayersBackend[] layersBackendHints, LayersId id); + + // Notify the compositor that a region of the screen has been invalidated. + async NotifyRegionInvalidated(nsIntRegion region); + + /** + * The child (content/chrome thread) requests that the parent inform it when + * the graphics objects are ready to display. + * @see PBrowser + * @see RemotePaintIsReady + */ + async RequestNotifyAfterRemotePaint(); + + /** + * Sent when the child has finished CaptureAllPlugins. + */ + async AllPluginsCaptured(); + + async PTexture(SurfaceDescriptor aSharedData, ReadLockDescriptor aReadLock, LayersBackend aBackend, TextureFlags aTextureFlags, LayersId id, uint64_t aSerial, MaybeExternalImageId aExternalImageId); + + async InitPCanvasParent(Endpoint aEndpoint); + async ReleasePCanvasParent(); + + sync SyncWithCompositor(); + + // The pipelineId is the same as the layersId + async PWebRenderBridge(PipelineId pipelineId, LayoutDeviceIntSize aSize, WindowKind aKind); + + sync CheckContentOnlyTDR(uint32_t sequenceNum) + returns (bool isContentOnlyTDR); + + async BeginRecording(TimeStamp aRecordingStart) + returns (bool success); + + async EndRecordingToDisk() + returns (bool success); + + async EndRecordingToMemory() + returns (CollectedFramesParams? frames); + + sync SupportsAsyncDXGISurface() + returns (bool value); + sync PreferredDXGIAdapter() + returns (DxgiAdapterDesc desc); + + // To set up sharing the composited output to Firefox Reality on Desktop + async RequestFxrOutput(); + + // Actor that represents one WebGL context. + async PWebGL(); + +child: + // Send back Compositor Frame Metrics from APZCs so tiled layers can + // update progressively. + async SharedCompositorFrameMetrics(Handle metrics, CrossProcessMutexHandle mutex, LayersId aLayersId, uint32_t aAPZCId); + async ReleaseSharedCompositorFrameMetrics(ViewID aId, uint32_t aAPZCId); +}; + +} // layers +} // mozilla diff --git a/gfx/layers/ipc/PCompositorBridgeTypes.ipdlh b/gfx/layers/ipc/PCompositorBridgeTypes.ipdlh new file mode 100644 index 0000000000..e1f3a76fdb --- /dev/null +++ b/gfx/layers/ipc/PCompositorBridgeTypes.ipdlh @@ -0,0 +1,20 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +namespace mozilla { +namespace layers { + +struct CollectedFrameParams { + double timeOffset; + uint32_t length; +}; + +struct CollectedFramesParams { + double recordingStart; + CollectedFrameParams[] frames; + Shmem buffer; +}; + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/PCompositorManager.ipdl b/gfx/layers/ipc/PCompositorManager.ipdl new file mode 100644 index 0000000000..93f08e532b --- /dev/null +++ b/gfx/layers/ipc/PCompositorManager.ipdl @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include protocol PCompositorBridge; +include LayersSurfaces; +include "mozilla/GfxMessageUtils.h"; +include "mozilla/layers/WebRenderMessageUtils.h"; + +using struct mozilla::void_t from "mozilla/ipc/IPCCore.h"; +using mozilla::TimeDuration from "mozilla/TimeStamp.h"; +using mozilla::CSSToLayoutDeviceScale from "Units.h"; +using mozilla::gfx::IntSize from "mozilla/gfx/2D.h"; +using mozilla::ipc::SharedMemoryBasic::Handle from "mozilla/ipc/SharedMemoryBasic.h"; +using mozilla::layers::CompositorOptions from "mozilla/layers/CompositorOptions.h"; +using mozilla::wr::ExternalImageId from "mozilla/webrender/WebRenderTypes.h"; +using mozilla::wr::MemoryReport from "mozilla/webrender/WebRenderTypes.h"; +using mozilla::wr::WebRenderError from "mozilla/webrender/WebRenderTypes.h"; +using mozilla::layers::SharedSurfacesMemoryReport from "mozilla/layers/SharedSurfacesMemoryReport.h"; + +namespace mozilla { +namespace layers { + +struct WidgetCompositorOptions { + CSSToLayoutDeviceScale scale; + TimeDuration vsyncRate; + CompositorOptions options; + bool useExternalSurfaceSize; + IntSize surfaceSize; +}; + +struct ContentCompositorOptions { +}; + +struct SameProcessWidgetCompositorOptions { +}; + +union CompositorBridgeOptions { + ContentCompositorOptions; + WidgetCompositorOptions; + SameProcessWidgetCompositorOptions; +}; + +/** + * The PCompositorManager protocol is the top-level protocol between the + * compositor thread and the creators of compositors. It exists in the + * following conditions: + * - One PCompositorManager between the GPU process and each content process. + * If the GPU/UI processes are combined, there is one PCompositorManager + * between the combined GPU/UI process and each content process. + * - One PCompositorManager between the GPU process and the UI process. If + * they are combined, there is still one PCompositorManager, but both the + * child and parent live in the same process. + * The intention for this protocol is to facilitate communication with the + * compositor thread for compositor data that is only shared once, rather than + * per PCompositorBridge instance. + */ +sync protocol PCompositorManager +{ + manages PCompositorBridge; + +parent: + /** + * There are three variants of a PCompositorBridge protocol, each of which can + * only be created by certain processes and configurations: + * - A "content" PCompositorBridge is requested by each content process, + * representing the drawable area for Web content. + * - A "widget" PCompositorBridge is requested by the UI process for each + * "top level browser window" for chrome and such. + * - A "same process widget" PCompositorBridge is requested by the combined + * GPU/UI process for each "top level browser window" as above. + * See gfx/layers/ipc/PCompositorBridge.ipdl for more details. + */ + async PCompositorBridge(CompositorBridgeOptions options); + + async AddSharedSurface(ExternalImageId aId, SurfaceDescriptorShared aDesc); + async RemoveSharedSurface(ExternalImageId aId); + async ReportSharedSurfacesMemory() returns (SharedSurfacesMemoryReport aReport); + + async NotifyMemoryPressure(); + + async ReportMemory() returns (MemoryReport aReport); + +child: + async NotifyWebRenderError(WebRenderError error); +}; + +} // layers +} // mozilla diff --git a/gfx/layers/ipc/PImageBridge.ipdl b/gfx/layers/ipc/PImageBridge.ipdl new file mode 100644 index 0000000000..73afb55d4a --- /dev/null +++ b/gfx/layers/ipc/PImageBridge.ipdl @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include LayersSurfaces; +include LayersMessages; +include protocol PTexture; +include ProtocolTypes; +include protocol PMediaSystemResourceManager; + +include "mozilla/GfxMessageUtils.h"; +include "mozilla/layers/LayersMessageUtils.h"; +include "mozilla/layers/WebRenderMessageUtils.h"; + +using struct mozilla::layers::TextureInfo from "mozilla/layers/CompositorTypes.h"; +using mozilla::layers::TextureFlags from "mozilla/layers/CompositorTypes.h"; +using mozilla::layers::CompositableHandle from "mozilla/layers/LayersTypes.h"; +using mozilla::wr::MaybeExternalImageId from "mozilla/webrender/WebRenderTypes.h"; + +namespace mozilla { +namespace layers { + +/** + * The PImageBridge protocol is used to allow isolated threads or processes to push + * frames directly to the compositor thread/process without relying on the main thread + * which might be too busy dealing with content script. + */ +sync protocol PImageBridge +{ + manages PTexture; + manages PMediaSystemResourceManager; + +child: + async ParentAsyncMessages(AsyncParentMessageData[] aMessages); + + async DidComposite(ImageCompositeNotification[] aNotifications); + + // Report the number of frames dropped for the given CompositableHost. + async ReportFramesDropped(CompositableHandle aHandle, uint32_t aFrames); + +parent: + async Update(CompositableOperation[] ops, OpDestroy[] toDestroy, uint64_t fwdTransactionId); + + // First step of the destruction sequence. This puts ImageBridge + // in a state in which it can't send asynchronous messages + // so as to not race with the channel getting closed. + // In the child side, the Closing the channel does not happen right after WillClose, + // it is scheduled in the ImageBridgeChild's message queue in order to ensure + // that all of the messages from the parent side have been received and processed + // before sending closing the channel. + async WillClose(); + + async PTexture(SurfaceDescriptor aSharedData, ReadLockDescriptor aReadLock, LayersBackend aBackend, TextureFlags aTextureFlags, uint64_t aSerial, MaybeExternalImageId aExternalImageId); + async PMediaSystemResourceManager(); + + sync NewCompositable(CompositableHandle aHandle, TextureInfo aInfo, LayersBackend aBackend); + async ReleaseCompositable(CompositableHandle aHandle); + + sync MakeAsyncPluginSurfaces(SurfaceFormat format, IntSize size) + returns (SurfaceDescriptorPlugin aSD); + async UpdateAsyncPluginSurface(SurfaceDescriptorPlugin aSD); + sync ReadbackAsyncPluginSurface(SurfaceDescriptorPlugin aSD) + returns (SurfaceDescriptor result); + async RemoveAsyncPluginSurface(SurfaceDescriptorPlugin aSD, bool isFrontSurface); +}; + + +} // namespace +} // namespace + diff --git a/gfx/layers/ipc/PLayerTransaction.ipdl b/gfx/layers/ipc/PLayerTransaction.ipdl new file mode 100644 index 0000000000..ddc50216e5 --- /dev/null +++ b/gfx/layers/ipc/PLayerTransaction.ipdl @@ -0,0 +1,130 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include LayersSurfaces; +include LayersMessages; +include protocol PCompositorBridge; +include protocol PTexture; + +include "mozilla/GfxMessageUtils.h"; +include "mozilla/layers/LayersMessageUtils.h"; + +using struct mozilla::layers::TextureInfo from "mozilla/layers/CompositorTypes.h"; +using struct mozilla::void_t from "mozilla/ipc/IPCCore.h"; +using struct mozilla::null_t from "mozilla/ipc/IPCCore.h"; +using class mozilla::layers::APZTestData from "mozilla/layers/APZTestData.h"; +using mozilla::layers::FrameUniformityData from "mozilla/layers/FrameUniformityData.h"; +using mozilla::layers::ScrollableLayerGuid from "mozilla/layers/ScrollableLayerGuid.h"; +using mozilla::layers::ScrollableLayerGuid::ViewID from "mozilla/layers/ScrollableLayerGuid.h"; +using struct mozilla::layers::TextureFactoryIdentifier from "mozilla/layers/CompositorTypes.h"; +using mozilla::layers::LayersBackend from "mozilla/layers/LayersTypes.h"; +using mozilla::layers::LayerHandle from "mozilla/layers/LayersTypes.h"; +using mozilla::layers::CompositableHandle from "mozilla/layers/LayersTypes.h"; +using mozilla::layers::LayersObserverEpoch from "mozilla/layers/LayersTypes.h"; +using mozilla::layers::TransactionId from "mozilla/layers/LayersTypes.h"; + +/** + * The layers protocol is spoken between thread contexts that manage + * layer (sub)trees. The protocol comprises atomically publishing + * layer subtrees to a "shadow" thread context (which grafts the + * subtree into its own tree), and atomically updating a published + * subtree. ("Atomic" in this sense is wrt painting.) + */ + +namespace mozilla { +namespace layers { + +/** + * The PLayerTransaction protocol manages the layer tree for a single "browser". + * The "browser" can be a top-level browser window, in which case the PLayer- + * TransactionChild exists in the UI process. The "browser" can also be a content + * tab, in which case the PLayerTransactionChild exists in the content process. + * In either case, the PLayerTransactionParent exists in the GPU process (if + * there is one) or the UI process otherwise. + */ +sync protocol PLayerTransaction { + manager PCompositorBridge; + +parent: + // The isFirstPaint flag can be used to indicate that this is the first update + // for a particular document. + async Update(TransactionInfo txn); + + async PaintTime(TransactionId id, TimeDuration paintTime); + + async SetLayersObserverEpoch(LayersObserverEpoch aChildEpoch); + + // Create a new Compositable. + async NewCompositable(CompositableHandle handle, TextureInfo info); + + // Release an object that is no longer in use. + async ReleaseLayer(LayerHandle layer); + async ReleaseCompositable(CompositableHandle compositable); + + // Tell the compositor to notify APZ that a layer has been confirmed for an + // input event. + async SetConfirmedTargetAPZC(uint64_t aInputBlockId, ScrollableLayerGuid[] aTargets); + + // Testing APIs + + // Enter test mode, set the sample time to sampleTime, and resample + // animations. sampleTime must not be null. + sync SetTestSampleTime(TimeStamp sampleTime); + // Leave test mode and resume normal compositing + sync LeaveTestMode(); + + // Returns |OMTAValue| applied to the layer. + sync GetAnimationValue(uint64_t aCompositorAnimationId) returns (OMTAValue value); + + // Returns the value of the transform applied to the layer by animation and + // APZC. + sync GetTransform(LayerHandle layer) returns (Matrix4x4? transform); + + // The next time the layer tree is composited, add this async scroll offset in + // CSS pixels for the given ViewID. + // Useful for testing rendering of async scrolling. + sync SetAsyncScrollOffset(ViewID id, float x, float y); + + // The next time the layer tree is composited, include this async zoom in + // for the given ViewID. + // Useful for testing rendering of async zooming. + sync SetAsyncZoom(ViewID id, float zoom); + + // Flush any pending APZ repaints to the main thread. + async FlushApzRepaints(); + + // Drop any front buffers that might be retained on the compositor + // side. + async ClearCachedResources(); + + // Schedule a composite if one isn't already scheduled. + async ScheduleComposite(); + + // Get a copy of the compositor-side APZ test data instance for this + // layers id. + sync GetAPZTestData() returns (APZTestData data); + + // Child requests frame uniformity measurements + sync GetFrameUniformity() returns (FrameUniformityData data); + + // Query a named property from the last frame + sync RequestProperty(nsString property) returns (float value); + + // Return the TextureFactoryIdentifier for this compositor. + sync GetTextureFactoryIdentifier() returns (TextureFactoryIdentifier aIdentifier); + + async RecordPaintTimes(PaintTiming timing); + + async Shutdown(); + sync ShutdownSync(); + +child: + async __delete__(); +}; + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/PTexture.ipdl b/gfx/layers/ipc/PTexture.ipdl new file mode 100644 index 0000000000..b82a757bbc --- /dev/null +++ b/gfx/layers/ipc/PTexture.ipdl @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include LayersSurfaces; +include protocol PLayerTransaction; +include protocol PCompositorBridge; +include protocol PImageBridge; +include protocol PVideoBridge; +include "mozilla/GfxMessageUtils.h"; +include "mozilla/layers/LayersMessageUtils.h"; + +using mozilla::layers::TextureFlags from "mozilla/layers/CompositorTypes.h"; + +namespace mozilla { +namespace layers { + +/** + * PTexture is the IPDL glue between a TextureClient and a TextureHost. + */ +sync protocol PTexture { + manager PImageBridge or PCompositorBridge or PVideoBridge; + +child: + async __delete__(); + +parent: + /** + * Asynchronously tell the compositor side to remove the texture. + */ + async Destroy(); + + async RecycleTexture(TextureFlags aTextureFlags); +}; + +} // layers +} // mozilla diff --git a/gfx/layers/ipc/PUiCompositorController.ipdl b/gfx/layers/ipc/PUiCompositorController.ipdl new file mode 100644 index 0000000000..515fcfd023 --- /dev/null +++ b/gfx/layers/ipc/PUiCompositorController.ipdl @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +using CSSRect from "Units.h"; +using CSSToScreenScale from "Units.h"; +using ScreenIntSize from "Units.h"; +using ScreenPoint from "Units.h"; + +include "mozilla/GfxMessageUtils.h"; + +namespace mozilla { +namespace layers { + +/** + * The PUiCompositorController protocol is used to pause and resume the + * compositor from the UI thread. Primarily used on Android to coordinate registering and + * releasing the surface with the compositor. + */ +sync protocol PUiCompositorController +{ + +parent: + // Pause/resume the compositor. These are intended to be used on mobile, when + // the compositor needs to pause/resume in lockstep with the application. + sync Pause(); + sync Resume(); + sync ResumeAndResize(int32_t aX, int32_t aY, int32_t aWidth, int32_t aHeight); + + async InvalidateAndRender(); + async MaxToolbarHeight(int32_t aHeight); + async FixedBottomOffset(int32_t aOffset); + async DefaultClearColor(uint32_t aColor); + async RequestScreenPixels(); + async EnableLayerUpdateNotifications(bool aEnable); +child: + async ToolbarAnimatorMessageFromCompositor(int32_t aMessage); + async RootFrameMetrics(ScreenPoint aScrollOffset, CSSToScreenScale aZoom); + async ScreenPixels(Shmem aMem, ScreenIntSize aSize, bool aNeedsYFlip); +}; + +} // layers +} // mozilla diff --git a/gfx/layers/ipc/PVideoBridge.ipdl b/gfx/layers/ipc/PVideoBridge.ipdl new file mode 100644 index 0000000000..5a4270981b --- /dev/null +++ b/gfx/layers/ipc/PVideoBridge.ipdl @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include LayersSurfaces; +include LayersMessages; +include protocol PTexture; + +include "mozilla/GfxMessageUtils.h"; +include "mozilla/layers/LayersMessageUtils.h"; + +using mozilla::layers::TextureFlags from "mozilla/layers/CompositorTypes.h"; + +namespace mozilla { +namespace layers { + +/** + * The PVideoBridge protocol is used to share textures from the video decoders + * to the compositor. + */ +sync protocol PVideoBridge +{ + manages PTexture; + +parent: + async PTexture(SurfaceDescriptor aSharedData, ReadLockDescriptor aReadLock, LayersBackend aBackend, + TextureFlags aTextureFlags, uint64_t aSerial); +}; + +} // namespace +} // namespace + diff --git a/gfx/layers/ipc/PWebRenderBridge.ipdl b/gfx/layers/ipc/PWebRenderBridge.ipdl new file mode 100644 index 0000000000..8e19731230 --- /dev/null +++ b/gfx/layers/ipc/PWebRenderBridge.ipdl @@ -0,0 +1,106 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include LayersSurfaces; +include LayersMessages; +include "mozilla/GfxMessageUtils.h"; +include "mozilla/layers/WebRenderMessageUtils.h"; + +include WebRenderMessages; +include protocol PCompositorBridge; +include protocol PTexture; + +using mozilla::layers::APZTestData from "mozilla/layers/APZTestData.h"; +using mozilla::layers::FrameUniformityData from "mozilla/layers/FrameUniformityData.h"; +using mozilla::layers::ScrollableLayerGuid from "mozilla/layers/ScrollableLayerGuid.h"; +using struct mozilla::layers::TextureFactoryIdentifier from "mozilla/layers/CompositorTypes.h"; +using struct mozilla::layers::TextureInfo from "mozilla/layers/CompositorTypes.h"; +using mozilla::layers::CompositionPayload from "mozilla/layers/LayersTypes.h"; +using mozilla::layers::CompositableHandle from "mozilla/layers/LayersTypes.h"; +using mozilla::wr::BuiltDisplayListDescriptor from "mozilla/webrender/webrender_ffi.h"; +using mozilla::wr::IdNamespace from "mozilla/webrender/WebRenderTypes.h"; +using mozilla::wr::MaybeIdNamespace from "mozilla/webrender/WebRenderTypes.h"; +using mozilla::wr::ExternalImageKeyPair from "mozilla/webrender/WebRenderTypes.h"; +using moveonly mozilla::layers::DisplayListData from "mozilla/layers/RenderRootTypes.h"; +using moveonly mozilla::layers::MaybeTransactionData from "mozilla/layers/RenderRootTypes.h"; +using mozilla::layers::FocusTarget from "mozilla/layers/FocusTarget.h"; +using mozilla::layers::LayersObserverEpoch from "mozilla/layers/LayersTypes.h"; +using mozilla::layers::TransactionId from "mozilla/layers/LayersTypes.h"; +using mozilla::VsyncId from "mozilla/VsyncDispatcher.h"; + +namespace mozilla { +namespace layers { + +sync protocol PWebRenderBridge +{ + manager PCompositorBridge; + +parent: + sync EnsureConnected() + returns (TextureFactoryIdentifier textureFactoryIdentifier, MaybeIdNamespace maybeIdNamespace, nsCString error); + + async NewCompositable(CompositableHandle handle, TextureInfo info); + async ReleaseCompositable(CompositableHandle compositable); + + async DeleteCompositorAnimations(uint64_t[] aIds); + async SetDisplayList(DisplayListData displayList, + OpDestroy[] toDestroy, uint64_t fwdTransactionId, TransactionId transactionId, + bool containsSVGGroup, + VsyncId vsyncId, TimeStamp vsyncStartTime, + TimeStamp refreshStartTime, TimeStamp txnStartTime, nsCString txnURL, TimeStamp fwdTime, + CompositionPayload[] payloads); + async EmptyTransaction(FocusTarget focusTarget, + MaybeTransactionData transationData, + OpDestroy[] toDestroy, uint64_t fwdTransactionId, TransactionId transactionId, + VsyncId vsyncId, TimeStamp vsyncStartTime, + TimeStamp refreshStartTime, TimeStamp txnStartTime, + nsCString txnURL, TimeStamp fwdTime, + CompositionPayload[] payloads); + async SetFocusTarget(FocusTarget focusTarget); + async UpdateResources(IdNamespace aIdNamespace, OpUpdateResource[] aResourceUpdates, + RefCountedShmem[] aSmallShmems, Shmem[] aLargeShmems); + async ParentCommands(WebRenderParentCommand[] commands); + sync GetSnapshot(PTexture texture) returns (bool aNeedsYFlip); + async SetLayersObserverEpoch(LayersObserverEpoch childEpoch); + async ClearCachedResources(); + // Invalidate rendered frame + async InvalidateRenderedFrame(); + // Schedule a composite if one isn't already scheduled. + async ScheduleComposite(); + // Save the frame capture to disk + async Capture(); + // Start capturing each frame and save to disk, and if already started, stop. + async ToggleCaptureSequence(); + + // Replacement for PCompositorBridge::SyncWithCompositor, but for WR. We need + // it on PWebRenderBridge because it associated with a particular top-level + // window, and PCompositorBridge doesn't allow doing that in a secure manner. + sync SyncWithCompositor(); + + // These correspond exactly to the equivalent APIs in PLayerTransaction - + // see those for documentation. + async SetConfirmedTargetAPZC(uint64_t aInputBlockId, ScrollableLayerGuid[] aTargets); + // More copied from PLayerTransaction, but these are only used for testing. + sync SetTestSampleTime(TimeStamp sampleTime); + sync LeaveTestMode(); + sync GetAnimationValue(uint64_t aCompositorAnimationsId) returns (OMTAValue value); + sync SetAsyncScrollOffset(ViewID scrollId, float x, float y); + sync SetAsyncZoom(ViewID scrollId, float zoom); + async FlushApzRepaints(); + sync GetAPZTestData() returns (APZTestData data); + sync GetFrameUniformity() returns (FrameUniformityData data); + + async Shutdown(); + sync ShutdownSync(); +child: + async WrUpdated(IdNamespace aNewIdNamespace, TextureFactoryIdentifier textureFactoryIdentifier); + async WrReleasedImages(ExternalImageKeyPair[] pairs); + async __delete__(); +}; + +} // layers +} // mozilla diff --git a/gfx/layers/ipc/RefCountedShmem.cpp b/gfx/layers/ipc/RefCountedShmem.cpp new file mode 100644 index 0000000000..205fa990fd --- /dev/null +++ b/gfx/layers/ipc/RefCountedShmem.cpp @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "RefCountedShmem.h" + +#include "mozilla/Atomics.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/layers/WebRenderMessages.h" + +#define SHM_REFCOUNT_HEADER_SIZE 16 + +namespace mozilla { +namespace layers { + +uint8_t* RefCountedShm::GetBytes(const RefCountedShmem& aShm) { + uint8_t* data = aShm.buffer().get(); + if (!data) { + return nullptr; + } + return data + SHM_REFCOUNT_HEADER_SIZE; +} + +size_t RefCountedShm::GetSize(const RefCountedShmem& aShm) { + if (!IsValid(aShm)) { + return 0; + } + size_t totalSize = aShm.buffer().Size(); + if (totalSize < SHM_REFCOUNT_HEADER_SIZE) { + return 0; + } + + return totalSize - SHM_REFCOUNT_HEADER_SIZE; +} + +bool RefCountedShm::IsValid(const RefCountedShmem& aShm) { + return aShm.buffer().IsWritable() && + aShm.buffer().Size() > SHM_REFCOUNT_HEADER_SIZE; +} + +int32_t RefCountedShm::GetReferenceCount(const RefCountedShmem& aShm) { + if (!IsValid(aShm)) { + return 0; + } + + return *aShm.buffer().get>(); +} + +int32_t RefCountedShm::AddRef(const RefCountedShmem& aShm) { + if (!IsValid(aShm)) { + return 0; + } + + auto* counter = aShm.buffer().get>(); + if (counter) { + return (*counter)++; + } + return 0; +} + +int32_t RefCountedShm::Release(const RefCountedShmem& aShm) { + if (!IsValid(aShm)) { + return 0; + } + + auto* counter = aShm.buffer().get>(); + if (counter) { + return --(*counter); + } + + return 0; +} + +bool RefCountedShm::Alloc(mozilla::ipc::IProtocol* aAllocator, size_t aSize, + RefCountedShmem& aShm) { + MOZ_ASSERT(!IsValid(aShm)); + auto shmType = ipc::SharedMemory::SharedMemoryType::TYPE_BASIC; + auto size = aSize + SHM_REFCOUNT_HEADER_SIZE; + if (!aAllocator->AllocUnsafeShmem(size, shmType, &aShm.buffer())) { + return false; + } + return true; +} + +void RefCountedShm::Dealloc(mozilla::ipc::IProtocol* aAllocator, + RefCountedShmem& aShm) { + aAllocator->DeallocShmem(aShm.buffer()); + aShm.buffer() = ipc::Shmem(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/RefCountedShmem.h b/gfx/layers/ipc/RefCountedShmem.h new file mode 100644 index 0000000000..390d6ea25f --- /dev/null +++ b/gfx/layers/ipc/RefCountedShmem.h @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_LAYERS_REFCOUNTED_SHMEM_H +#define MOZILLA_LAYERS_REFCOUNTED_SHMEM_H + +#include "mozilla/ipc/Shmem.h" +#include "chrome/common/ipc_message_utils.h" + +namespace mozilla { +namespace ipc { +class IProtocol; +} + +namespace layers { + +// This class is IPDL-defined +class RefCountedShmem; + +// This just implement the methods externally. +class RefCountedShm { + public: + static uint8_t* GetBytes(const RefCountedShmem& aShm); + + static size_t GetSize(const RefCountedShmem& aShm); + + static bool IsValid(const RefCountedShmem& aShm); + + static bool Alloc(mozilla::ipc::IProtocol* aAllocator, size_t aSize, + RefCountedShmem& aShm); + + static void Dealloc(mozilla::ipc::IProtocol* aAllocator, + RefCountedShmem& aShm); + + static int32_t GetReferenceCount(const RefCountedShmem& aShm); + + static int32_t AddRef(const RefCountedShmem& aShm); + + static int32_t Release(const RefCountedShmem& aShm); +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/ipc/RemoteContentController.cpp b/gfx/layers/ipc/RemoteContentController.cpp new file mode 100644 index 0000000000..8b110f1397 --- /dev/null +++ b/gfx/layers/ipc/RemoteContentController.cpp @@ -0,0 +1,425 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/RemoteContentController.h" + +#include "CompositorThread.h" +#include "MainThreadUtils.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/layers/APZCCallbackHelper.h" +#include "mozilla/layers/APZCTreeManagerParent.h" // for APZCTreeManagerParent +#include "mozilla/layers/APZThreadUtils.h" +#include "mozilla/layers/CompositorBridgeParent.h" +#include "mozilla/layers/MatrixMessage.h" +#include "mozilla/gfx/GPUProcessManager.h" +#include "mozilla/Unused.h" +#include "Units.h" +#ifdef MOZ_WIDGET_ANDROID +# include "mozilla/jni/Utils.h" +#endif + +static mozilla::LazyLogModule sApzRemoteLog("apz.cc.remote"); + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +RemoteContentController::RemoteContentController() + : mCompositorThread(NS_GetCurrentThread()), mCanSend(true) { + MOZ_ASSERT(CompositorThread()->IsOnCurrentThread()); +} + +RemoteContentController::~RemoteContentController() = default; + +void RemoteContentController::NotifyLayerTransforms( + nsTArray&& aTransforms) { + if (!mCompositorThread->IsOnCurrentThread()) { + // We have to send messages from the compositor thread + mCompositorThread->Dispatch( + NewRunnableMethod>>( + "layers::RemoteContentController::NotifyLayerTransforms", this, + &RemoteContentController::NotifyLayerTransforms, + std::move(aTransforms))); + return; + } + + if (mCanSend) { + Unused << SendLayerTransforms(aTransforms); + } +} + +void RemoteContentController::RequestContentRepaint( + const RepaintRequest& aRequest) { + MOZ_ASSERT(IsRepaintThread()); + + if (mCanSend) { + Unused << SendRequestContentRepaint(aRequest); + } +} + +void RemoteContentController::HandleTapOnMainThread(TapType aTapType, + LayoutDevicePoint aPoint, + Modifiers aModifiers, + ScrollableLayerGuid aGuid, + uint64_t aInputBlockId) { + MOZ_LOG(sApzRemoteLog, LogLevel::Debug, + ("HandleTapOnMainThread(%d)", (int)aTapType)); + MOZ_ASSERT(NS_IsMainThread()); + + dom::BrowserParent* tab = + dom::BrowserParent::GetBrowserParentFromLayersId(aGuid.mLayersId); + if (tab) { + tab->SendHandleTap(aTapType, aPoint, aModifiers, aGuid, aInputBlockId); + } +} + +void RemoteContentController::HandleTapOnCompositorThread( + TapType aTapType, LayoutDevicePoint aPoint, Modifiers aModifiers, + ScrollableLayerGuid aGuid, uint64_t aInputBlockId) { + MOZ_ASSERT(XRE_IsGPUProcess()); + MOZ_ASSERT(mCompositorThread->IsOnCurrentThread()); + + // The raw pointer to APZCTreeManagerParent is ok here because we are on the + // compositor thread. + APZCTreeManagerParent* apzctmp = + CompositorBridgeParent::GetApzcTreeManagerParentForRoot(aGuid.mLayersId); + if (apzctmp) { + Unused << apzctmp->SendHandleTap(aTapType, aPoint, aModifiers, aGuid, + aInputBlockId); + } +} + +void RemoteContentController::HandleTap(TapType aTapType, + const LayoutDevicePoint& aPoint, + Modifiers aModifiers, + const ScrollableLayerGuid& aGuid, + uint64_t aInputBlockId) { + MOZ_LOG(sApzRemoteLog, LogLevel::Debug, ("HandleTap(%d)", (int)aTapType)); + APZThreadUtils::AssertOnControllerThread(); + + if (XRE_GetProcessType() == GeckoProcessType_GPU) { + if (mCompositorThread->IsOnCurrentThread()) { + HandleTapOnCompositorThread(aTapType, aPoint, aModifiers, aGuid, + aInputBlockId); + } else { + // We have to send messages from the compositor thread + mCompositorThread->Dispatch( + NewRunnableMethod( + "layers::RemoteContentController::HandleTapOnCompositorThread", + this, &RemoteContentController::HandleTapOnCompositorThread, + aTapType, aPoint, aModifiers, aGuid, aInputBlockId)); + } + return; + } + + MOZ_ASSERT(XRE_IsParentProcess()); + + if (NS_IsMainThread()) { + HandleTapOnMainThread(aTapType, aPoint, aModifiers, aGuid, aInputBlockId); + } else { + // We must be on Android, running on the Java UI thread +#ifndef MOZ_WIDGET_ANDROID + MOZ_ASSERT(false); +#else + // We don't want to get the BrowserParent or call + // BrowserParent::SendHandleTap() from a non-main thread, so we need to + // redispatch to the main thread. However, we should use the same mechanism + // that the Android widget uses when dispatching input events to Gecko, + // which is nsAppShell::PostEvent. Note in particular that using + // NS_DispatchToMainThread would post to a different message loop, and + // introduces the possibility of this tap event getting processed out of + // order with respect to the touch events that synthesized it. + mozilla::jni::DispatchToGeckoPriorityQueue( + NewRunnableMethod( + "layers::RemoteContentController::HandleTapOnMainThread", this, + &RemoteContentController::HandleTapOnMainThread, aTapType, aPoint, + aModifiers, aGuid, aInputBlockId)); +#endif + } +} + +void RemoteContentController::NotifyPinchGestureOnCompositorThread( + PinchGestureInput::PinchGestureType aType, const ScrollableLayerGuid& aGuid, + const LayoutDevicePoint& aFocusPoint, LayoutDeviceCoord aSpanChange, + Modifiers aModifiers) { + MOZ_ASSERT(mCompositorThread->IsOnCurrentThread()); + + // The raw pointer to APZCTreeManagerParent is ok here because we are on the + // compositor thread. + APZCTreeManagerParent* apzctmp = + CompositorBridgeParent::GetApzcTreeManagerParentForRoot(aGuid.mLayersId); + if (apzctmp) { + Unused << apzctmp->SendNotifyPinchGesture(aType, aGuid, aFocusPoint, + aSpanChange, aModifiers); + } +} + +void RemoteContentController::NotifyPinchGesture( + PinchGestureInput::PinchGestureType aType, const ScrollableLayerGuid& aGuid, + const LayoutDevicePoint& aFocusPoint, LayoutDeviceCoord aSpanChange, + Modifiers aModifiers) { + APZThreadUtils::AssertOnControllerThread(); + + // For now we only ever want to handle this NotifyPinchGesture message in + // the parent process, even if the APZ is sending it to a content process. + + // If we're in the GPU process, try to find a handle to the parent process + // and send it there. + if (XRE_IsGPUProcess()) { + if (mCompositorThread->IsOnCurrentThread()) { + NotifyPinchGestureOnCompositorThread(aType, aGuid, aFocusPoint, + aSpanChange, aModifiers); + } else { + mCompositorThread->Dispatch( + NewRunnableMethod( + "layers::RemoteContentController::" + "NotifyPinchGestureOnCompositorThread", + this, + &RemoteContentController::NotifyPinchGestureOnCompositorThread, + aType, aGuid, aFocusPoint, aSpanChange, aModifiers)); + } + return; + } + + // If we're in the parent process, handle it directly. We don't have a handle + // to the widget though, so we fish out the ChromeProcessController and + // delegate to that instead. + if (XRE_IsParentProcess()) { + MOZ_ASSERT(NS_IsMainThread()); + RefPtr rootController = + CompositorBridgeParent::GetGeckoContentControllerForRoot( + aGuid.mLayersId); + if (rootController) { + rootController->NotifyPinchGesture(aType, aGuid, aFocusPoint, aSpanChange, + aModifiers); + } + } +} + +bool RemoteContentController::IsRepaintThread() { + return mCompositorThread->IsOnCurrentThread(); +} + +void RemoteContentController::DispatchToRepaintThread( + already_AddRefed aTask) { + mCompositorThread->Dispatch(std::move(aTask)); +} + +void RemoteContentController::NotifyAPZStateChange( + const ScrollableLayerGuid& aGuid, APZStateChange aChange, int aArg) { + if (!mCompositorThread->IsOnCurrentThread()) { + // We have to send messages from the compositor thread + mCompositorThread->Dispatch( + NewRunnableMethod( + "layers::RemoteContentController::NotifyAPZStateChange", this, + &RemoteContentController::NotifyAPZStateChange, aGuid, aChange, + aArg)); + return; + } + + if (mCanSend) { + Unused << SendNotifyAPZStateChange(aGuid, aChange, aArg); + } +} + +void RemoteContentController::UpdateOverscrollVelocity( + const ScrollableLayerGuid& aGuid, float aX, float aY, bool aIsRootContent) { + if (XRE_IsParentProcess()) { +#ifdef MOZ_WIDGET_ANDROID + // We always want these to go to the parent process on Android + if (!NS_IsMainThread()) { + mozilla::jni::DispatchToGeckoPriorityQueue( + NewRunnableMethod( + "layers::RemoteContentController::UpdateOverscrollVelocity", this, + &RemoteContentController::UpdateOverscrollVelocity, aGuid, aX, aY, + aIsRootContent)); + return; + } +#endif + + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + RefPtr rootController = + CompositorBridgeParent::GetGeckoContentControllerForRoot( + aGuid.mLayersId); + if (rootController) { + rootController->UpdateOverscrollVelocity(aGuid, aX, aY, aIsRootContent); + } + } +} + +void RemoteContentController::UpdateOverscrollOffset( + const ScrollableLayerGuid& aGuid, float aX, float aY, bool aIsRootContent) { + if (XRE_IsParentProcess()) { +#ifdef MOZ_WIDGET_ANDROID + // We always want these to go to the parent process on Android + if (!NS_IsMainThread()) { + mozilla::jni::DispatchToGeckoPriorityQueue( + NewRunnableMethod( + "layers::RemoteContentController::UpdateOverscrollOffset", this, + &RemoteContentController::UpdateOverscrollOffset, aGuid, aX, aY, + aIsRootContent)); + return; + } +#endif + + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + RefPtr rootController = + CompositorBridgeParent::GetGeckoContentControllerForRoot( + aGuid.mLayersId); + if (rootController) { + rootController->UpdateOverscrollOffset(aGuid, aX, aY, aIsRootContent); + } + } +} + +void RemoteContentController::NotifyMozMouseScrollEvent( + const ScrollableLayerGuid::ViewID& aScrollId, const nsString& aEvent) { + if (!mCompositorThread->IsOnCurrentThread()) { + // We have to send messages from the compositor thread + mCompositorThread->Dispatch( + NewRunnableMethod( + "layers::RemoteContentController::NotifyMozMouseScrollEvent", this, + &RemoteContentController::NotifyMozMouseScrollEvent, aScrollId, + aEvent)); + return; + } + + if (mCanSend) { + Unused << SendNotifyMozMouseScrollEvent(aScrollId, aEvent); + } +} + +void RemoteContentController::NotifyFlushComplete() { + MOZ_ASSERT(IsRepaintThread()); + + if (mCanSend) { + Unused << SendNotifyFlushComplete(); + } +} + +void RemoteContentController::NotifyAsyncScrollbarDragInitiated( + uint64_t aDragBlockId, const ScrollableLayerGuid::ViewID& aScrollId, + ScrollDirection aDirection) { + if (!mCompositorThread->IsOnCurrentThread()) { + // We have to send messages from the compositor thread + mCompositorThread->Dispatch(NewRunnableMethod( + "layers::RemoteContentController::NotifyAsyncScrollbarDragInitiated", + this, &RemoteContentController::NotifyAsyncScrollbarDragInitiated, + aDragBlockId, aScrollId, aDirection)); + return; + } + + if (mCanSend) { + Unused << SendNotifyAsyncScrollbarDragInitiated(aDragBlockId, aScrollId, + aDirection); + } +} + +void RemoteContentController::NotifyAsyncScrollbarDragRejected( + const ScrollableLayerGuid::ViewID& aScrollId) { + if (!mCompositorThread->IsOnCurrentThread()) { + // We have to send messages from the compositor thread + mCompositorThread->Dispatch(NewRunnableMethod( + "layers::RemoteContentController::NotifyAsyncScrollbarDragRejected", + this, &RemoteContentController::NotifyAsyncScrollbarDragRejected, + aScrollId)); + return; + } + + if (mCanSend) { + Unused << SendNotifyAsyncScrollbarDragRejected(aScrollId); + } +} + +void RemoteContentController::NotifyAsyncAutoscrollRejected( + const ScrollableLayerGuid::ViewID& aScrollId) { + if (!mCompositorThread->IsOnCurrentThread()) { + // We have to send messages from the compositor thread + mCompositorThread->Dispatch(NewRunnableMethod( + "layers::RemoteContentController::NotifyAsyncAutoscrollRejected", this, + &RemoteContentController::NotifyAsyncAutoscrollRejected, aScrollId)); + return; + } + + if (mCanSend) { + Unused << SendNotifyAsyncAutoscrollRejected(aScrollId); + } +} + +void RemoteContentController::CancelAutoscroll( + const ScrollableLayerGuid& aGuid) { + if (XRE_GetProcessType() == GeckoProcessType_GPU) { + CancelAutoscrollCrossProcess(aGuid); + } else { + CancelAutoscrollInProcess(aGuid); + } +} + +void RemoteContentController::CancelAutoscrollInProcess( + const ScrollableLayerGuid& aGuid) { + MOZ_ASSERT(XRE_IsParentProcess()); + + if (!NS_IsMainThread()) { + NS_DispatchToMainThread(NewRunnableMethod( + "layers::RemoteContentController::CancelAutoscrollInProcess", this, + &RemoteContentController::CancelAutoscrollInProcess, aGuid)); + return; + } + + APZCCallbackHelper::CancelAutoscroll(aGuid.mScrollId); +} + +void RemoteContentController::CancelAutoscrollCrossProcess( + const ScrollableLayerGuid& aGuid) { + MOZ_ASSERT(XRE_IsGPUProcess()); + + if (!mCompositorThread->IsOnCurrentThread()) { + mCompositorThread->Dispatch(NewRunnableMethod( + "layers::RemoteContentController::CancelAutoscrollCrossProcess", this, + &RemoteContentController::CancelAutoscrollCrossProcess, aGuid)); + return; + } + + // The raw pointer to APZCTreeManagerParent is ok here because we are on the + // compositor thread. + if (APZCTreeManagerParent* parent = + CompositorBridgeParent::GetApzcTreeManagerParentForRoot( + aGuid.mLayersId)) { + Unused << parent->SendCancelAutoscroll(aGuid.mScrollId); + } +} + +void RemoteContentController::ActorDestroy(ActorDestroyReason aWhy) { + // This controller could possibly be kept alive longer after this + // by a RefPtr, but it is no longer valid to send messages. + mCanSend = false; +} + +void RemoteContentController::Destroy() { + if (mCanSend) { + mCanSend = false; + Unused << SendDestroy(); + } +} + +mozilla::ipc::IPCResult RemoteContentController::RecvDestroy() { + // The actor on the other side is about to get destroyed, so let's not send + // it any more messages. + mCanSend = false; + return IPC_OK(); +} + +bool RemoteContentController::IsRemote() { return true; } + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/RemoteContentController.h b/gfx/layers/ipc/RemoteContentController.h new file mode 100644 index 0000000000..1f18323a3c --- /dev/null +++ b/gfx/layers/ipc/RemoteContentController.h @@ -0,0 +1,115 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_RemoteContentController_h +#define mozilla_layers_RemoteContentController_h + +#include "mozilla/layers/GeckoContentController.h" +#include "mozilla/layers/PAPZParent.h" + +namespace mozilla { + +namespace dom { +class BrowserParent; +} + +namespace layers { + +/** + * RemoteContentController implements PAPZChild and is used to access a + * GeckoContentController that lives in a different process. + * + * RemoteContentController lives on the compositor thread. All methods can + * be called off the compositor thread and will get dispatched to the right + * thread, with the exception of RequestContentRepaint and NotifyFlushComplete, + * which must be called on the repaint thread, which in this case is the + * compositor thread. + */ +class RemoteContentController : public GeckoContentController, + public PAPZParent { + using GeckoContentController::APZStateChange; + using GeckoContentController::TapType; + + public: + RemoteContentController(); + + virtual ~RemoteContentController(); + + void NotifyLayerTransforms(nsTArray&& aTransforms) override; + + void RequestContentRepaint(const RepaintRequest& aRequest) override; + + void HandleTap(TapType aTapType, const LayoutDevicePoint& aPoint, + Modifiers aModifiers, const ScrollableLayerGuid& aGuid, + uint64_t aInputBlockId) override; + + void NotifyPinchGesture(PinchGestureInput::PinchGestureType aType, + const ScrollableLayerGuid& aGuid, + const LayoutDevicePoint& aFocusPoint, + LayoutDeviceCoord aSpanChange, + Modifiers aModifiers) override; + + bool IsRepaintThread() override; + + void DispatchToRepaintThread(already_AddRefed aTask) override; + + void NotifyAPZStateChange(const ScrollableLayerGuid& aGuid, + APZStateChange aChange, int aArg) override; + + void UpdateOverscrollVelocity(const ScrollableLayerGuid& aGuid, float aX, + float aY, bool aIsRootContent) override; + + void UpdateOverscrollOffset(const ScrollableLayerGuid& aGuid, float aX, + float aY, bool aIsRootContent) override; + + void NotifyMozMouseScrollEvent(const ScrollableLayerGuid::ViewID& aScrollId, + const nsString& aEvent) override; + + void NotifyFlushComplete() override; + + void NotifyAsyncScrollbarDragInitiated( + uint64_t aDragBlockId, const ScrollableLayerGuid::ViewID& aScrollId, + ScrollDirection aDirection) override; + void NotifyAsyncScrollbarDragRejected( + const ScrollableLayerGuid::ViewID& aScrollId) override; + + void NotifyAsyncAutoscrollRejected( + const ScrollableLayerGuid::ViewID& aScrollId) override; + + void CancelAutoscroll(const ScrollableLayerGuid& aScrollId) override; + + void ActorDestroy(ActorDestroyReason aWhy) override; + + void Destroy() override; + mozilla::ipc::IPCResult RecvDestroy(); + + bool IsRemote() override; + + private: + nsCOMPtr mCompositorThread; + bool mCanSend; + + void HandleTapOnMainThread(TapType aType, LayoutDevicePoint aPoint, + Modifiers aModifiers, ScrollableLayerGuid aGuid, + uint64_t aInputBlockId); + void HandleTapOnCompositorThread(TapType aType, LayoutDevicePoint aPoint, + Modifiers aModifiers, + ScrollableLayerGuid aGuid, + uint64_t aInputBlockId); + void NotifyPinchGestureOnCompositorThread( + PinchGestureInput::PinchGestureType aType, + const ScrollableLayerGuid& aGuid, const LayoutDevicePoint& aFocusPoint, + LayoutDeviceCoord aSpanChange, Modifiers aModifiers); + + void CancelAutoscrollInProcess(const ScrollableLayerGuid& aScrollId); + void CancelAutoscrollCrossProcess(const ScrollableLayerGuid& aScrollId); +}; + +} // namespace layers + +} // namespace mozilla + +#endif // mozilla_layers_RemoteContentController_h diff --git a/gfx/layers/ipc/ShadowLayerUtils.h b/gfx/layers/ipc/ShadowLayerUtils.h new file mode 100644 index 0000000000..941ddded1f --- /dev/null +++ b/gfx/layers/ipc/ShadowLayerUtils.h @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef IPC_ShadowLayerUtils_h +#define IPC_ShadowLayerUtils_h + +#include "ipc/EnumSerializer.h" +#include "ipc/IPCMessageUtils.h" +#include "GLContextTypes.h" +#include "SurfaceDescriptor.h" +#include "SurfaceTypes.h" +#include "mozilla/WidgetUtils.h" + +#if defined(MOZ_X11) +# include "mozilla/layers/ShadowLayerUtilsX11.h" +#endif + +namespace IPC { + +#if !defined(MOZ_HAVE_SURFACEDESCRIPTORX11) +template <> +struct ParamTraits { + typedef mozilla::layers::SurfaceDescriptorX11 paramType; + static void Write(Message*, const paramType&) {} + static bool Read(const Message*, PickleIterator*, paramType*) { + return false; + } +}; +#endif // !defined(MOZ_HAVE_XSURFACEDESCRIPTORX11) + +template <> +struct ParamTraits + : public ContiguousEnumSerializer {}; + +} // namespace IPC + +#endif // IPC_ShadowLayerUtils_h diff --git a/gfx/layers/ipc/ShadowLayerUtilsMac.cpp b/gfx/layers/ipc/ShadowLayerUtilsMac.cpp new file mode 100644 index 0000000000..891b16e8a2 --- /dev/null +++ b/gfx/layers/ipc/ShadowLayerUtilsMac.cpp @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/gfx/Point.h" +#include "mozilla/layers/PLayerTransaction.h" +#include "mozilla/layers/ShadowLayers.h" +#include "mozilla/layers/LayerManagerComposite.h" +#include "mozilla/layers/CompositorTypes.h" + +#include "gfx2DGlue.h" +#include "gfxPlatform.h" + +using namespace mozilla::gl; + +namespace mozilla { +namespace layers { + +/*static*/ +void ShadowLayerForwarder::PlatformSyncBeforeUpdate() {} + +/*static*/ +void LayerManagerComposite::PlatformSyncBeforeReplyUpdate() {} + +/*static*/ +bool LayerManagerComposite::SupportsDirectTexturing() { return false; } + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/ShadowLayerUtilsX11.cpp b/gfx/layers/ipc/ShadowLayerUtilsX11.cpp new file mode 100644 index 0000000000..25f6ceba21 --- /dev/null +++ b/gfx/layers/ipc/ShadowLayerUtilsX11.cpp @@ -0,0 +1,153 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ShadowLayerUtilsX11.h" +#include // for Drawable, XID +#include // for Display, Visual, etc +#include // for XRenderPictFormat, etc +#include // for PictFormat +#include "cairo-xlib.h" +#include "X11UndefineNone.h" +#include // for uint32_t +#include "GLDefs.h" // for GLenum +#include "gfxPlatform.h" // for gfxPlatform +#include "gfxXlibSurface.h" // for gfxXlibSurface +#include "gfx2DGlue.h" // for Moz2D transistion helpers +#include "mozilla/X11Util.h" // for DefaultXDisplay, FinishX, etc +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/layers/CompositableForwarder.h" +#include "mozilla/layers/CompositorTypes.h" // for OpenMode +#include "mozilla/layers/ISurfaceAllocator.h" // for ISurfaceAllocator, etc +#include "mozilla/layers/LayerManagerComposite.h" +#include "mozilla/layers/LayersMessageUtils.h" +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor, etc +#include "mozilla/layers/ShadowLayers.h" // for ShadowLayerForwarder, etc +#include "mozilla/mozalloc.h" // for operator new +#include "gfxEnv.h" +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsDebug.h" // for NS_ERROR + +using namespace mozilla::gl; + +namespace mozilla { +namespace gl { +class GLContext; +class TextureImage; +} // namespace gl + +namespace layers { + +// Return true if we're likely compositing using X and so should use +// Xlib surfaces in shadow layers. +static bool UsingXCompositing() { + if (!gfxEnv::LayersEnableXlibSurfaces()) { + return false; + } + return (gfxSurfaceType::Xlib == + gfxPlatform::GetPlatform()->ScreenReferenceSurface()->GetType()); +} + +// LookReturn a pointer to |aFormat| that lives in the Xrender library. +// All code using render formats assumes it doesn't need to copy. +static XRenderPictFormat* GetXRenderPictFormatFromId(Display* aDisplay, + PictFormat aFormatId) { + XRenderPictFormat tmplate; + tmplate.id = aFormatId; + return XRenderFindFormat(aDisplay, PictFormatID, &tmplate, 0); +} + +SurfaceDescriptorX11::SurfaceDescriptorX11(gfxXlibSurface* aSurf, + bool aForwardGLX) + : mId(aSurf->XDrawable()), mSize(aSurf->GetSize()), mGLXPixmap(X11None) { + const XRenderPictFormat* pictFormat = aSurf->XRenderFormat(); + if (pictFormat) { + mFormat = pictFormat->id; + } else { + mFormat = cairo_xlib_surface_get_visual(aSurf->CairoSurface())->visualid; + } + + if (aForwardGLX) { + mGLXPixmap = aSurf->GetGLXPixmap(); + } +} + +SurfaceDescriptorX11::SurfaceDescriptorX11(Drawable aDrawable, XID aFormatID, + const gfx::IntSize& aSize) + : mId(aDrawable), mFormat(aFormatID), mSize(aSize), mGLXPixmap(X11None) {} + +already_AddRefed SurfaceDescriptorX11::OpenForeign() const { + Display* display = DefaultXDisplay(); + if (!display) { + return nullptr; + } + Screen* screen = DefaultScreenOfDisplay(display); + + RefPtr surf; + XRenderPictFormat* pictFormat = GetXRenderPictFormatFromId(display, mFormat); + if (pictFormat) { + surf = new gfxXlibSurface(screen, mId, pictFormat, mSize); + } else { + Visual* visual; + int depth; + FindVisualAndDepth(display, mFormat, &visual, &depth); + if (!visual) return nullptr; + + surf = new gfxXlibSurface(display, mId, visual, mSize); + } + + if (mGLXPixmap) surf->BindGLXPixmap(mGLXPixmap); + + return surf->CairoStatus() ? nullptr : surf.forget(); +} + +/*static*/ +void ShadowLayerForwarder::PlatformSyncBeforeUpdate() { + if (UsingXCompositing()) { + // If we're using X surfaces, then we need to finish all pending + // operations on the back buffers before handing them to the + // parent, otherwise the surface might be used by the parent's + // Display in between two operations queued by our Display. + FinishX(DefaultXDisplay()); + } +} + +/*static*/ +void LayerManagerComposite::PlatformSyncBeforeReplyUpdate() { + if (UsingXCompositing()) { + // If we're using X surfaces, we need to finish all pending + // operations on the *front buffers* before handing them back to + // the child, even though they will be read operations. + // Otherwise, the child might start scribbling on new back buffers + // that are still participating in requests as old front buffers. + FinishX(DefaultXDisplay()); + } +} + +/*static*/ +bool LayerManagerComposite::SupportsDirectTexturing() { return false; } + +} // namespace layers +} // namespace mozilla + +namespace IPC { + +void ParamTraits::Write( + Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mId); + WriteParam(aMsg, aParam.mSize); + WriteParam(aMsg, aParam.mFormat); + WriteParam(aMsg, aParam.mGLXPixmap); +} + +bool ParamTraits::Read( + const Message* aMsg, PickleIterator* aIter, paramType* aResult) { + return (ReadParam(aMsg, aIter, &aResult->mId) && + ReadParam(aMsg, aIter, &aResult->mSize) && + ReadParam(aMsg, aIter, &aResult->mFormat) && + ReadParam(aMsg, aIter, &aResult->mGLXPixmap)); +} + +} // namespace IPC diff --git a/gfx/layers/ipc/ShadowLayerUtilsX11.h b/gfx/layers/ipc/ShadowLayerUtilsX11.h new file mode 100644 index 0000000000..9cbee37cf1 --- /dev/null +++ b/gfx/layers/ipc/ShadowLayerUtilsX11.h @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_ShadowLayerUtilsX11_h +#define mozilla_layers_ShadowLayerUtilsX11_h + +#include "gfxipc/SurfaceDescriptor.h" +#include "ipc/IPCMessageUtils.h" + +namespace IPC { +class Message; + +template <> +struct ParamTraits { + typedef mozilla::layers::SurfaceDescriptorX11 paramType; + + static void Write(Message* aMsg, const paramType& aParam); + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult); +}; + +} // namespace IPC + +#endif // mozilla_layers_ShadowLayerUtilsX11_h diff --git a/gfx/layers/ipc/ShadowLayers.cpp b/gfx/layers/ipc/ShadowLayers.cpp new file mode 100644 index 0000000000..571a05d491 --- /dev/null +++ b/gfx/layers/ipc/ShadowLayers.cpp @@ -0,0 +1,1074 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ShadowLayers.h" + +#include // for _Rb_tree_const_iterator, etc +#include // for vector + +#include "ClientLayerManager.h" // for ClientLayerManager +#include "GeckoProfiler.h" // for AUTO_PROFILER_LABEL +#include "IPDLActor.h" +#include "ISurfaceAllocator.h" // for IsSurfaceDescriptorValid +#include "Layers.h" // for Layer +#include "RenderTrace.h" // for RenderTraceScope +#include "gfx2DGlue.h" // for Moz2D transition helpers +#include "gfxPlatform.h" // for gfxImageFormat, gfxPlatform +#include "ipc/IPCMessageUtils.h" // for gfxContentType, null_t +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/layers/CompositableClient.h" // for CompositableClient, etc +#include "mozilla/layers/CompositorBridgeChild.h" +#include "mozilla/layers/ContentClient.h" +#include "mozilla/layers/ImageBridgeChild.h" +#include "mozilla/layers/ImageDataSerializer.h" +#include "mozilla/layers/LayerTransactionChild.h" +#include "mozilla/layers/LayersMessages.h" // for Edit, etc +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor, etc +#include "mozilla/layers/LayersTypes.h" // for MOZ_LAYERS_LOG +#include "mozilla/layers/PTextureChild.h" +#include "mozilla/layers/SyncObject.h" +#ifdef XP_DARWIN +# include "mozilla/layers/TextureSync.h" +#endif +#include "ShadowLayerUtils.h" +#include "mozilla/ReentrantMonitor.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/layers/TextureClient.h" // for TextureClient +#include "mozilla/mozalloc.h" // for operator new, etc +#include "nsIXULRuntime.h" // for BrowserTabsRemoteAutostart +#include "nsTArray.h" // for AutoTArray, nsTArray, etc +#include "nsXULAppAPI.h" // for XRE_GetProcessType, etc + +namespace mozilla { +namespace ipc { +class Shmem; +} // namespace ipc + +namespace layers { + +using namespace mozilla::gfx; +using namespace mozilla::gl; +using namespace mozilla::ipc; + +class ClientTiledLayerBuffer; + +typedef nsTArray BufferArray; +typedef nsTArray EditVector; +typedef nsTHashtable> ShadowableLayerSet; +typedef nsTArray OpDestroyVector; + +class Transaction { + public: + Transaction() + : mTargetRotation(ROTATION_0), + mTargetOrientation(hal::eScreenOrientation_None), + mOpen(false), + mRotationChanged(false) {} + + void Begin(const gfx::IntRect& aTargetBounds, ScreenRotation aRotation, + hal::ScreenOrientation aOrientation) { + mOpen = true; + mTargetBounds = aTargetBounds; + if (aRotation != mTargetRotation) { + // the first time this is called, mRotationChanged will be false if + // aRotation is 0, but we should be OK because for the first transaction + // we should only compose if it is non-empty. See the caller(s) of + // RotationChanged. + mRotationChanged = true; + } + mTargetRotation = aRotation; + mTargetOrientation = aOrientation; + } + void AddEdit(const Edit& aEdit) { + MOZ_ASSERT(!Finished(), "forgot BeginTransaction?"); + mCset.AppendElement(aEdit); + } + void AddEdit(const CompositableOperation& aEdit) { AddEdit(Edit(aEdit)); } + + void AddNoSwapPaint(const CompositableOperation& aPaint) { + MOZ_ASSERT(!Finished(), "forgot BeginTransaction?"); + mPaints.AppendElement(Edit(aPaint)); + } + void AddMutant(ShadowableLayer* aLayer) { + MOZ_ASSERT(!Finished(), "forgot BeginTransaction?"); + mMutants.PutEntry(aLayer); + } + void AddSimpleMutant(ShadowableLayer* aLayer) { + MOZ_ASSERT(!Finished(), "forgot BeginTransaction?"); + mSimpleMutants.PutEntry(aLayer); + } + void End() { + mCset.Clear(); + mPaints.Clear(); + mMutants.Clear(); + mSimpleMutants.Clear(); + mDestroyedActors.Clear(); + mOpen = false; + mRotationChanged = false; + } + + bool Empty() const { + return mCset.IsEmpty() && mPaints.IsEmpty() && mMutants.IsEmpty() && + mSimpleMutants.IsEmpty() && mDestroyedActors.IsEmpty(); + } + bool RotationChanged() const { return mRotationChanged; } + bool Finished() const { return !mOpen && Empty(); } + + bool Opened() const { return mOpen; } + + EditVector mCset; + nsTArray mPaints; + OpDestroyVector mDestroyedActors; + ShadowableLayerSet mMutants; + ShadowableLayerSet mSimpleMutants; + gfx::IntRect mTargetBounds; + ScreenRotation mTargetRotation; + hal::ScreenOrientation mTargetOrientation; + + private: + bool mOpen; + bool mRotationChanged; + + // disabled + Transaction(const Transaction&); + Transaction& operator=(const Transaction&); +}; +struct AutoTxnEnd final { + explicit AutoTxnEnd(Transaction* aTxn) : mTxn(aTxn) {} + ~AutoTxnEnd() { mTxn->End(); } + Transaction* mTxn; +}; + +void KnowsCompositor::IdentifyTextureHost( + const TextureFactoryIdentifier& aIdentifier) { + auto lock = mData.Lock(); + lock.ref().mTextureFactoryIdentifier = aIdentifier; + + lock.ref().mSyncObject = + SyncObjectClient::CreateSyncObjectClientForContentDevice( + aIdentifier.mSyncHandle); +} + +KnowsCompositor::KnowsCompositor() + : mData("KnowsCompositorMutex"), mSerial(++sSerialCounter) {} + +KnowsCompositor::~KnowsCompositor() = default; + +KnowsCompositorMediaProxy::KnowsCompositorMediaProxy( + const TextureFactoryIdentifier& aIdentifier) { + auto lock = mData.Lock(); + lock.ref().mTextureFactoryIdentifier = aIdentifier; + // overwrite mSerial's value set by the parent class because we use the same + // serial as the KnowsCompositor we are proxying. + mThreadSafeAllocator = ImageBridgeChild::GetSingleton(); + lock.ref().mSyncObject = mThreadSafeAllocator->GetSyncObject(); +} + +KnowsCompositorMediaProxy::~KnowsCompositorMediaProxy() = default; + +TextureForwarder* KnowsCompositorMediaProxy::GetTextureForwarder() { + return mThreadSafeAllocator->GetTextureForwarder(); +} + +LayersIPCActor* KnowsCompositorMediaProxy::GetLayersIPCActor() { + return mThreadSafeAllocator->GetLayersIPCActor(); +} + +ActiveResourceTracker* KnowsCompositorMediaProxy::GetActiveResourceTracker() { + return mThreadSafeAllocator->GetActiveResourceTracker(); +} + +void KnowsCompositorMediaProxy::SyncWithCompositor() { + mThreadSafeAllocator->SyncWithCompositor(); +} + +RefPtr ShadowLayerForwarder::GetForMedia() { + return MakeAndAddRef( + GetTextureFactoryIdentifier()); +} + +ShadowLayerForwarder::ShadowLayerForwarder( + ClientLayerManager* aClientLayerManager) + : mClientLayerManager(aClientLayerManager), + mThread(NS_GetCurrentThread()), + mDiagnosticTypes(DiagnosticTypes::NO_DIAGNOSTIC), + mIsFirstPaint(false), + mNextLayerHandle(1) { + mTxn = new Transaction(); + mEventTarget = GetMainThreadSerialEventTarget(); + + MOZ_ASSERT(mEventTarget || !XRE_IsContentProcess()); + mActiveResourceTracker = MakeUnique( + 1000, "CompositableForwarder", mEventTarget); +} + +template +struct ReleaseOnMainThreadTask : public Runnable { + UniquePtr mObj; + + explicit ReleaseOnMainThreadTask(UniquePtr& aObj) + : Runnable("layers::ReleaseOnMainThreadTask"), mObj(std::move(aObj)) {} + + NS_IMETHOD Run() override { + mObj = nullptr; + return NS_OK; + } +}; + +ShadowLayerForwarder::~ShadowLayerForwarder() { + MOZ_ASSERT(mTxn->Finished(), "unfinished transaction?"); + delete mTxn; + if (mShadowManager) { + mShadowManager->SetForwarder(nullptr); + if (NS_IsMainThread()) { + mShadowManager->Destroy(); + } else { + if (mEventTarget) { + mEventTarget->Dispatch( + NewRunnableMethod("LayerTransactionChild::Destroy", mShadowManager, + &LayerTransactionChild::Destroy), + nsIEventTarget::DISPATCH_NORMAL); + } else { + NS_DispatchToMainThread( + NewRunnableMethod("layers::LayerTransactionChild::Destroy", + mShadowManager, &LayerTransactionChild::Destroy)); + } + } + } + + if (!NS_IsMainThread()) { + RefPtr> event = + new ReleaseOnMainThreadTask( + mActiveResourceTracker); + if (mEventTarget) { + mEventTarget->Dispatch(event.forget(), nsIEventTarget::DISPATCH_NORMAL); + } else { + NS_DispatchToMainThread(event); + } + } +} + +void ShadowLayerForwarder::BeginTransaction( + const gfx::IntRect& aTargetBounds, ScreenRotation aRotation, + hal::ScreenOrientation aOrientation) { + MOZ_ASSERT(IPCOpen(), "no manager to forward to"); + MOZ_ASSERT(mTxn->Finished(), "uncommitted txn?"); + UpdateFwdTransactionId(); + mTxn->Begin(aTargetBounds, aRotation, aOrientation); +} + +static const LayerHandle& Shadow(ShadowableLayer* aLayer) { + return aLayer->GetShadow(); +} + +template +static void CreatedLayer(Transaction* aTxn, ShadowableLayer* aLayer) { + aTxn->AddEdit(OpCreateT(Shadow(aLayer))); +} + +void ShadowLayerForwarder::CreatedPaintedLayer(ShadowableLayer* aThebes) { + CreatedLayer(mTxn, aThebes); +} +void ShadowLayerForwarder::CreatedContainerLayer(ShadowableLayer* aContainer) { + CreatedLayer(mTxn, aContainer); +} +void ShadowLayerForwarder::CreatedImageLayer(ShadowableLayer* aImage) { + CreatedLayer(mTxn, aImage); +} +void ShadowLayerForwarder::CreatedColorLayer(ShadowableLayer* aColor) { + CreatedLayer(mTxn, aColor); +} +void ShadowLayerForwarder::CreatedCanvasLayer(ShadowableLayer* aCanvas) { + CreatedLayer(mTxn, aCanvas); +} +void ShadowLayerForwarder::CreatedRefLayer(ShadowableLayer* aRef) { + CreatedLayer(mTxn, aRef); +} + +void ShadowLayerForwarder::Mutated(ShadowableLayer* aMutant) { + mTxn->AddMutant(aMutant); +} + +void ShadowLayerForwarder::MutatedSimple(ShadowableLayer* aMutant) { + mTxn->AddSimpleMutant(aMutant); +} + +void ShadowLayerForwarder::SetRoot(ShadowableLayer* aRoot) { + mTxn->AddEdit(OpSetRoot(Shadow(aRoot))); +} +void ShadowLayerForwarder::InsertAfter(ShadowableLayer* aContainer, + ShadowableLayer* aChild, + ShadowableLayer* aAfter) { + if (!aChild->HasShadow()) { + return; + } + + while (aAfter && !aAfter->HasShadow()) { + aAfter = aAfter->AsLayer()->GetPrevSibling() + ? aAfter->AsLayer()->GetPrevSibling()->AsShadowableLayer() + : nullptr; + } + + if (aAfter) { + mTxn->AddEdit( + OpInsertAfter(Shadow(aContainer), Shadow(aChild), Shadow(aAfter))); + } else { + mTxn->AddEdit(OpPrependChild(Shadow(aContainer), Shadow(aChild))); + } +} +void ShadowLayerForwarder::RemoveChild(ShadowableLayer* aContainer, + ShadowableLayer* aChild) { + MOZ_LAYERS_LOG(("[LayersForwarder] OpRemoveChild container=%p child=%p\n", + aContainer->AsLayer(), aChild->AsLayer())); + + if (!aChild->HasShadow()) { + return; + } + + mTxn->AddEdit(OpRemoveChild(Shadow(aContainer), Shadow(aChild))); +} +void ShadowLayerForwarder::RepositionChild(ShadowableLayer* aContainer, + ShadowableLayer* aChild, + ShadowableLayer* aAfter) { + if (!aChild->HasShadow()) { + return; + } + + while (aAfter && !aAfter->HasShadow()) { + aAfter = aAfter->AsLayer()->GetPrevSibling() + ? aAfter->AsLayer()->GetPrevSibling()->AsShadowableLayer() + : nullptr; + } + + if (aAfter) { + MOZ_LAYERS_LOG( + ("[LayersForwarder] OpRepositionChild container=%p child=%p after=%p", + aContainer->AsLayer(), aChild->AsLayer(), aAfter->AsLayer())); + mTxn->AddEdit( + OpRepositionChild(Shadow(aContainer), Shadow(aChild), Shadow(aAfter))); + } else { + MOZ_LAYERS_LOG(("[LayersForwarder] OpRaiseToTopChild container=%p child=%p", + aContainer->AsLayer(), aChild->AsLayer())); + mTxn->AddEdit(OpRaiseToTopChild(Shadow(aContainer), Shadow(aChild))); + } +} + +#ifdef DEBUG +void ShadowLayerForwarder::CheckSurfaceDescriptor( + const SurfaceDescriptor* aDescriptor) const { + if (!aDescriptor) { + return; + } + + if (aDescriptor->type() == SurfaceDescriptor::TSurfaceDescriptorBuffer && + aDescriptor->get_SurfaceDescriptorBuffer().data().type() == + MemoryOrShmem::TShmem) { + const Shmem& shmem = + aDescriptor->get_SurfaceDescriptorBuffer().data().get_Shmem(); + shmem.AssertInvariants(); + MOZ_ASSERT(mShadowManager && + mShadowManager->IsTrackingSharedMemory(shmem.mSegment)); + } +} +#endif + +void ShadowLayerForwarder::UseTiledLayerBuffer( + CompositableClient* aCompositable, + const SurfaceDescriptorTiles& aTileLayerDescriptor) { + MOZ_ASSERT(aCompositable); + + if (!aCompositable->IsConnected()) { + return; + } + + mTxn->AddNoSwapPaint( + CompositableOperation(aCompositable->GetIPCHandle(), + OpUseTiledLayerBuffer(aTileLayerDescriptor))); +} + +void ShadowLayerForwarder::UpdateTextureRegion( + CompositableClient* aCompositable, + const ThebesBufferData& aThebesBufferData, + const nsIntRegion& aUpdatedRegion) { + MOZ_ASSERT(aCompositable); + + if (!aCompositable->IsConnected()) { + return; + } + + mTxn->AddNoSwapPaint(CompositableOperation( + aCompositable->GetIPCHandle(), + OpPaintTextureRegion(aThebesBufferData, aUpdatedRegion))); +} + +void ShadowLayerForwarder::UseTextures( + CompositableClient* aCompositable, + const nsTArray& aTextures) { + MOZ_ASSERT(aCompositable); + + if (!aCompositable->IsConnected()) { + return; + } + + AutoTArray textures; + + for (auto& t : aTextures) { + MOZ_ASSERT(t.mTextureClient); + MOZ_ASSERT(t.mTextureClient->GetIPDLActor()); + MOZ_RELEASE_ASSERT(t.mTextureClient->GetIPDLActor()->GetIPCChannel() == + mShadowManager->GetIPCChannel()); + bool readLocked = t.mTextureClient->OnForwardedToHost(); + textures.AppendElement( + TimedTexture(nullptr, t.mTextureClient->GetIPDLActor(), t.mTimeStamp, + t.mPictureRect, t.mFrameID, t.mProducerID, readLocked)); + mClientLayerManager->GetCompositorBridgeChild() + ->HoldUntilCompositableRefReleasedIfNecessary(t.mTextureClient); + + auto fenceFd = t.mTextureClient->GetInternalData()->GetAcquireFence(); + if (fenceFd.IsValid()) { + mTxn->AddEdit(CompositableOperation( + aCompositable->GetIPCHandle(), + OpDeliverAcquireFence(nullptr, t.mTextureClient->GetIPDLActor(), + fenceFd))); + } + } + mTxn->AddEdit(CompositableOperation(aCompositable->GetIPCHandle(), + OpUseTexture(textures))); +} + +void ShadowLayerForwarder::UseComponentAlphaTextures( + CompositableClient* aCompositable, TextureClient* aTextureOnBlack, + TextureClient* aTextureOnWhite) { + MOZ_ASSERT(aCompositable); + + if (!aCompositable->IsConnected()) { + return; + } + + MOZ_ASSERT(aTextureOnWhite); + MOZ_ASSERT(aTextureOnBlack); + MOZ_ASSERT(aCompositable->GetIPCHandle()); + MOZ_ASSERT(aTextureOnBlack->GetIPDLActor()); + MOZ_ASSERT(aTextureOnWhite->GetIPDLActor()); + MOZ_ASSERT(aTextureOnBlack->GetSize() == aTextureOnWhite->GetSize()); + MOZ_RELEASE_ASSERT(aTextureOnWhite->GetIPDLActor()->GetIPCChannel() == + mShadowManager->GetIPCChannel()); + MOZ_RELEASE_ASSERT(aTextureOnBlack->GetIPDLActor()->GetIPCChannel() == + mShadowManager->GetIPCChannel()); + + bool readLockedB = aTextureOnBlack->OnForwardedToHost(); + bool readLockedW = aTextureOnWhite->OnForwardedToHost(); + + mClientLayerManager->GetCompositorBridgeChild() + ->HoldUntilCompositableRefReleasedIfNecessary(aTextureOnBlack); + mClientLayerManager->GetCompositorBridgeChild() + ->HoldUntilCompositableRefReleasedIfNecessary(aTextureOnWhite); + + auto fenceFdB = aTextureOnBlack->GetInternalData()->GetAcquireFence(); + if (fenceFdB.IsValid()) { + mTxn->AddEdit(CompositableOperation( + aCompositable->GetIPCHandle(), + OpDeliverAcquireFence(nullptr, aTextureOnBlack->GetIPDLActor(), + fenceFdB))); + } + + auto fenceFdW = aTextureOnWhite->GetInternalData()->GetAcquireFence(); + if (fenceFdW.IsValid()) { + mTxn->AddEdit(CompositableOperation( + aCompositable->GetIPCHandle(), + OpDeliverAcquireFence(nullptr, aTextureOnWhite->GetIPDLActor(), + fenceFdW))); + } + + mTxn->AddEdit(CompositableOperation( + aCompositable->GetIPCHandle(), + OpUseComponentAlphaTextures(nullptr, aTextureOnBlack->GetIPDLActor(), + nullptr, aTextureOnWhite->GetIPDLActor(), + readLockedB, readLockedW))); +} + +static bool AddOpDestroy(Transaction* aTxn, const OpDestroy& op) { + if (!aTxn->Opened()) { + return false; + } + + aTxn->mDestroyedActors.AppendElement(op); + return true; +} + +bool ShadowLayerForwarder::DestroyInTransaction(PTextureChild* aTexture) { + return AddOpDestroy(mTxn, OpDestroy(aTexture)); +} + +bool ShadowLayerForwarder::DestroyInTransaction( + const CompositableHandle& aHandle) { + return AddOpDestroy(mTxn, OpDestroy(aHandle)); +} + +void ShadowLayerForwarder::RemoveTextureFromCompositable( + CompositableClient* aCompositable, TextureClient* aTexture) { + MOZ_ASSERT(aCompositable); + MOZ_ASSERT(aTexture); + MOZ_ASSERT(aTexture->GetIPDLActor()); + MOZ_RELEASE_ASSERT(aTexture->GetIPDLActor()->GetIPCChannel() == + mShadowManager->GetIPCChannel()); + if (!aCompositable->IsConnected() || !aTexture->GetIPDLActor()) { + // We don't have an actor anymore, don't try to use it! + return; + } + + mTxn->AddEdit(CompositableOperation( + aCompositable->GetIPCHandle(), + OpRemoveTexture(nullptr, aTexture->GetIPDLActor()))); +} + +bool ShadowLayerForwarder::InWorkerThread() { + return GetTextureForwarder()->GetThread()->IsOnCurrentThread(); +} + +void ShadowLayerForwarder::StorePluginWidgetConfigurations( + const nsTArray& aConfigurations) { + // Cache new plugin widget configs here until we call update, at which + // point this data will get shipped over to chrome. + mPluginWindowData.Clear(); + for (uint32_t idx = 0; idx < aConfigurations.Length(); idx++) { + const nsIWidget::Configuration& configuration = aConfigurations[idx]; + mPluginWindowData.AppendElement( + PluginWindowData(configuration.mWindowID, configuration.mClipRegion, + configuration.mBounds, configuration.mVisible)); + } +} + +void ShadowLayerForwarder::SendPaintTime(TransactionId aId, + TimeDuration aPaintTime) { + if (!IPCOpen() || !mShadowManager->SendPaintTime(aId, aPaintTime)) { + NS_WARNING("Could not send paint times over IPC"); + } +} + +bool ShadowLayerForwarder::EndTransaction( + const nsIntRegion& aRegionToClear, TransactionId aId, + bool aScheduleComposite, uint32_t aPaintSequenceNumber, + bool aIsRepeatTransaction, const mozilla::VsyncId& aVsyncId, + const mozilla::TimeStamp& aVsyncStart, + const mozilla::TimeStamp& aRefreshStart, + const mozilla::TimeStamp& aTransactionStart, bool aContainsSVG, + const nsCString& aURL, bool* aSent, + const nsTArray& aPayload) { + *aSent = false; + + TransactionInfo info; + + MOZ_ASSERT(IPCOpen(), "no manager to forward to"); + if (!IPCOpen()) { + return false; + } + + Maybe startTime; + if (StaticPrefs::layers_acceleration_draw_fps()) { + startTime = Some(TimeStamp::Now()); + } + + GetCompositorBridgeChild()->WillEndTransaction(); + + MOZ_ASSERT(aId.IsValid()); + + AUTO_PROFILER_LABEL("ShadowLayerForwarder::EndTransaction", GRAPHICS); + + RenderTraceScope rendertrace("Foward Transaction", "000091"); + MOZ_ASSERT(!mTxn->Finished(), "forgot BeginTransaction?"); + + DiagnosticTypes diagnostics = + gfxPlatform::GetPlatform()->GetLayerDiagnosticTypes(); + if (mDiagnosticTypes != diagnostics) { + mDiagnosticTypes = diagnostics; + mTxn->AddEdit(OpSetDiagnosticTypes(diagnostics)); + } + + AutoTxnEnd _(mTxn); + + if (mTxn->Empty() && !mTxn->RotationChanged()) { + MOZ_LAYERS_LOG( + ("[LayersForwarder] 0-length cset (?) and no rotation event, skipping " + "Update()")); + return true; + } + + if (!mTxn->mPaints.IsEmpty()) { + // With some platforms, telling the drawing backend that there will be no + // more drawing for this frame helps with preventing command queues from + // spanning across multiple frames. + gfxPlatform::GetPlatform()->FlushContentDrawing(); + } + + MOZ_LAYERS_LOG(("[LayersForwarder] destroying buffers...")); + + MOZ_LAYERS_LOG(("[LayersForwarder] building transaction...")); + + nsTArray setSimpleAttrs; + for (ShadowableLayerSet::Iterator it(&mTxn->mSimpleMutants); !it.Done(); + it.Next()) { + ShadowableLayer* shadow = it.Get()->GetKey(); + if (!shadow->HasShadow()) { + continue; + } + + Layer* mutant = shadow->AsLayer(); + setSimpleAttrs.AppendElement(OpSetSimpleLayerAttributes( + Shadow(shadow), mutant->GetSimpleAttributes())); + } + + nsTArray setAttrs; + + // We purposely add attribute-change ops to the final changeset + // before we add paint ops. This allows layers to record the + // attribute changes before new pixels arrive, which can be useful + // for setting up back/front buffers. + RenderTraceScope rendertrace2("Foward Transaction", "000092"); + for (ShadowableLayerSet::Iterator it(&mTxn->mMutants); !it.Done(); + it.Next()) { + ShadowableLayer* shadow = it.Get()->GetKey(); + + if (!shadow->HasShadow()) { + continue; + } + Layer* mutant = shadow->AsLayer(); + MOZ_ASSERT(!!mutant, "unshadowable layer?"); + + OpSetLayerAttributes op; + op.layer() = Shadow(shadow); + + LayerAttributes& attrs = op.attrs(); + CommonLayerAttributes& common = attrs.common(); + common.visibleRegion() = mutant->GetVisibleRegion(); + common.eventRegions() = mutant->GetEventRegions(); + common.useClipRect() = !!mutant->GetClipRect(); + common.clipRect() = + (common.useClipRect() ? *mutant->GetClipRect() : ParentLayerIntRect()); + if (Layer* maskLayer = mutant->GetMaskLayer()) { + common.maskLayer() = Shadow(maskLayer->AsShadowableLayer()); + } else { + common.maskLayer() = LayerHandle(); + } + common.compositorAnimations().id() = mutant->GetCompositorAnimationsId(); + common.compositorAnimations().animations() = + mutant->GetAnimations().Clone(); + common.invalidRegion() = mutant->GetInvalidRegion().GetRegion(); + common.scrollMetadata() = mutant->GetAllScrollMetadata().Clone(); + for (size_t i = 0; i < mutant->GetAncestorMaskLayerCount(); i++) { + auto layer = + Shadow(mutant->GetAncestorMaskLayerAt(i)->AsShadowableLayer()); + common.ancestorMaskLayers().AppendElement(layer); + } + nsCString log; + mutant->GetDisplayListLog(log); + common.displayListLog() = log; + + attrs.specific() = null_t(); + mutant->FillSpecificAttributes(attrs.specific()); + + MOZ_LAYERS_LOG(("[LayersForwarder] OpSetLayerAttributes(%p)\n", mutant)); + + setAttrs.AppendElement(op); + } + + if (mTxn->mCset.IsEmpty() && mTxn->mPaints.IsEmpty() && setAttrs.IsEmpty() && + !mTxn->RotationChanged()) { + return true; + } + + info.cset() = std::move(mTxn->mCset); + info.setSimpleAttrs() = std::move(setSimpleAttrs); + info.setAttrs() = std::move(setAttrs); + info.paints() = std::move(mTxn->mPaints); + info.toDestroy() = mTxn->mDestroyedActors.Clone(); + info.fwdTransactionId() = GetFwdTransactionId(); + info.id() = aId; + info.plugins() = mPluginWindowData.Clone(); + info.isFirstPaint() = mIsFirstPaint; + info.focusTarget() = mFocusTarget; + info.scheduleComposite() = aScheduleComposite; + info.paintSequenceNumber() = aPaintSequenceNumber; + info.isRepeatTransaction() = aIsRepeatTransaction; + info.vsyncId() = aVsyncId; + info.vsyncStart() = aVsyncStart; + info.refreshStart() = aRefreshStart; + info.transactionStart() = aTransactionStart; + info.url() = aURL; + info.containsSVG() = aContainsSVG; +#if defined(ENABLE_FRAME_LATENCY_LOG) + info.fwdTime() = TimeStamp::Now(); +#endif + info.payload() = aPayload.Clone(); + + TargetConfig targetConfig(mTxn->mTargetBounds, mTxn->mTargetRotation, + mTxn->mTargetOrientation, aRegionToClear); + info.targetConfig() = targetConfig; + + if (!GetTextureForwarder()->IsSameProcess()) { + MOZ_LAYERS_LOG(("[LayersForwarder] syncing before send...")); + PlatformSyncBeforeUpdate(); + } + + if (startTime) { + mPaintTiming.serializeMs() = + (TimeStamp::Now() - startTime.value()).ToMilliseconds(); + startTime = Some(TimeStamp::Now()); + } + + // We delay at the last possible minute, to give the paint thread a chance to + // finish. If it does we don't have to delay messages at all. + GetCompositorBridgeChild()->PostponeMessagesIfAsyncPainting(); + + MOZ_LAYERS_LOG(("[LayersForwarder] sending transaction...")); + RenderTraceScope rendertrace3("Forward Transaction", "000093"); + if (!mShadowManager->SendUpdate(info)) { + MOZ_LAYERS_LOG(("[LayersForwarder] WARNING: sending transaction failed!")); + return false; + } + + if (startTime) { + mPaintTiming.sendMs() = + (TimeStamp::Now() - startTime.value()).ToMilliseconds(); + mShadowManager->SendRecordPaintTimes(mPaintTiming); + } + + *aSent = true; + mIsFirstPaint = false; + mFocusTarget = FocusTarget(); + MOZ_LAYERS_LOG(("[LayersForwarder] ... done")); + return true; +} + +RefPtr ShadowLayerForwarder::FindCompositable( + const CompositableHandle& aHandle) { + CompositableClient* client = nullptr; + if (!mCompositables.Get(aHandle.Value(), &client)) { + return nullptr; + } + return client; +} + +void ShadowLayerForwarder::SetLayersObserverEpoch(LayersObserverEpoch aEpoch) { + if (!IPCOpen()) { + return; + } + Unused << mShadowManager->SendSetLayersObserverEpoch(aEpoch); +} + +void ShadowLayerForwarder::UpdateTextureLocks() { +#ifdef XP_DARWIN + if (!IPCOpen()) { + return; + } + + auto compositorBridge = GetCompositorBridgeChild(); + if (compositorBridge) { + auto pid = compositorBridge->OtherPid(); + TextureSync::UpdateTextureLocks(pid); + } +#endif +} + +void ShadowLayerForwarder::SyncTextures(const nsTArray& aSerials) { +#ifdef XP_DARWIN + if (!IPCOpen()) { + return; + } + + auto compositorBridge = GetCompositorBridgeChild(); + if (compositorBridge) { + auto pid = compositorBridge->OtherPid(); + TextureSync::WaitForTextures(pid, aSerials); + } +#endif +} + +void ShadowLayerForwarder::ReleaseLayer(const LayerHandle& aHandle) { + if (!IPCOpen()) { + return; + } + Unused << mShadowManager->SendReleaseLayer(aHandle); +} + +bool ShadowLayerForwarder::IPCOpen() const { + return HasShadowManager() && mShadowManager->IPCOpen(); +} + +/** + * We bail out when we have no shadow manager. That can happen when the + * layer manager is created by the preallocated process. + * See bug 914843 for details. + */ +LayerHandle ShadowLayerForwarder::ConstructShadowFor(ShadowableLayer* aLayer) { + return LayerHandle(mNextLayerHandle++); +} + +#if !defined(MOZ_HAVE_PLATFORM_SPECIFIC_LAYER_BUFFERS) + +/*static*/ +void ShadowLayerForwarder::PlatformSyncBeforeUpdate() {} + +#endif // !defined(MOZ_HAVE_PLATFORM_SPECIFIC_LAYER_BUFFERS) + +void ShadowLayerForwarder::Connect(CompositableClient* aCompositable, + ImageContainer* aImageContainer) { +#ifdef GFX_COMPOSITOR_LOGGING + printf("ShadowLayerForwarder::Connect(Compositable)\n"); +#endif + MOZ_ASSERT(aCompositable); + MOZ_ASSERT(mShadowManager); + if (!IPCOpen()) { + return; + } + + static uint64_t sNextID = 1; + uint64_t id = sNextID++; + + mCompositables.Put(id, aCompositable); + + CompositableHandle handle(id); + aCompositable->InitIPDL(handle); + mShadowManager->SendNewCompositable(handle, aCompositable->GetTextureInfo()); +} + +void ShadowLayerForwarder::Attach(CompositableClient* aCompositable, + ShadowableLayer* aLayer) { + MOZ_ASSERT(aLayer); + MOZ_ASSERT(aCompositable); + mTxn->AddEdit( + OpAttachCompositable(Shadow(aLayer), aCompositable->GetIPCHandle())); +} + +void ShadowLayerForwarder::AttachAsyncCompositable( + const CompositableHandle& aHandle, ShadowableLayer* aLayer) { + MOZ_ASSERT(aLayer); + MOZ_ASSERT(aHandle); + mTxn->AddEdit(OpAttachAsyncCompositable(Shadow(aLayer), aHandle)); +} + +void ShadowLayerForwarder::SetShadowManager( + PLayerTransactionChild* aShadowManager) { + mShadowManager = static_cast(aShadowManager); + mShadowManager->SetForwarder(this); +} + +void ShadowLayerForwarder::StopReceiveAsyncParentMessge() { + if (!IPCOpen()) { + return; + } + mShadowManager->SetForwarder(nullptr); +} + +void ShadowLayerForwarder::ClearCachedResources() { + if (!IPCOpen()) { + return; + } + mShadowManager->SendClearCachedResources(); +} + +void ShadowLayerForwarder::ScheduleComposite() { + if (!IPCOpen()) { + return; + } + mShadowManager->SendScheduleComposite(); +} + +bool IsSurfaceDescriptorValid(const SurfaceDescriptor& aSurface) { + return aSurface.type() != SurfaceDescriptor::T__None && + aSurface.type() != SurfaceDescriptor::Tnull_t; +} + +uint8_t* GetAddressFromDescriptor(const SurfaceDescriptor& aDescriptor) { + MOZ_ASSERT(IsSurfaceDescriptorValid(aDescriptor)); + MOZ_RELEASE_ASSERT( + aDescriptor.type() == SurfaceDescriptor::TSurfaceDescriptorBuffer, + "GFX: surface descriptor is not the right type."); + + auto memOrShmem = aDescriptor.get_SurfaceDescriptorBuffer().data(); + if (memOrShmem.type() == MemoryOrShmem::TShmem) { + return memOrShmem.get_Shmem().get(); + } else { + return reinterpret_cast(memOrShmem.get_uintptr_t()); + } +} + +already_AddRefed GetSurfaceForDescriptor( + const SurfaceDescriptor& aDescriptor) { + if (aDescriptor.type() != SurfaceDescriptor::TSurfaceDescriptorBuffer) { + return nullptr; + } + uint8_t* data = GetAddressFromDescriptor(aDescriptor); + auto rgb = + aDescriptor.get_SurfaceDescriptorBuffer().desc().get_RGBDescriptor(); + uint32_t stride = ImageDataSerializer::GetRGBStride(rgb); + return gfx::Factory::CreateWrappingDataSourceSurface(data, stride, rgb.size(), + rgb.format()); +} + +already_AddRefed GetDrawTargetForDescriptor( + const SurfaceDescriptor& aDescriptor, gfx::BackendType aBackend) { + uint8_t* data = GetAddressFromDescriptor(aDescriptor); + auto rgb = + aDescriptor.get_SurfaceDescriptorBuffer().desc().get_RGBDescriptor(); + uint32_t stride = ImageDataSerializer::GetRGBStride(rgb); + return gfx::Factory::CreateDrawTargetForData( + gfx::BackendType::CAIRO, data, rgb.size(), stride, rgb.format()); +} + +void DestroySurfaceDescriptor(IShmemAllocator* aAllocator, + SurfaceDescriptor* aSurface) { + MOZ_ASSERT(aSurface); + + SurfaceDescriptorBuffer& desc = aSurface->get_SurfaceDescriptorBuffer(); + switch (desc.data().type()) { + case MemoryOrShmem::TShmem: { + aAllocator->DeallocShmem(desc.data().get_Shmem()); + break; + } + case MemoryOrShmem::Tuintptr_t: { + uint8_t* ptr = (uint8_t*)desc.data().get_uintptr_t(); + GfxMemoryImageReporter::WillFree(ptr); + delete[] ptr; + break; + } + default: + MOZ_CRASH("surface type not implemented!"); + } + *aSurface = SurfaceDescriptor(); +} + +bool ShadowLayerForwarder::AllocSurfaceDescriptor(const gfx::IntSize& aSize, + gfxContentType aContent, + SurfaceDescriptor* aBuffer) { + if (!IPCOpen()) { + return false; + } + return AllocSurfaceDescriptorWithCaps(aSize, aContent, DEFAULT_BUFFER_CAPS, + aBuffer); +} + +bool ShadowLayerForwarder::AllocSurfaceDescriptorWithCaps( + const gfx::IntSize& aSize, gfxContentType aContent, uint32_t aCaps, + SurfaceDescriptor* aBuffer) { + if (!IPCOpen()) { + return false; + } + gfx::SurfaceFormat format = + gfxPlatform::GetPlatform()->Optimal2DFormatForContent(aContent); + size_t size = ImageDataSerializer::ComputeRGBBufferSize(aSize, format); + if (!size) { + return false; + } + + MemoryOrShmem bufferDesc; + if (GetTextureForwarder()->IsSameProcess()) { + uint8_t* data = new (std::nothrow) uint8_t[size]; + if (!data) { + return false; + } + GfxMemoryImageReporter::DidAlloc(data); + memset(data, 0, size); + bufferDesc = reinterpret_cast(data); + } else { + mozilla::ipc::Shmem shmem; + if (!GetTextureForwarder()->AllocUnsafeShmem(size, OptimalShmemType(), + &shmem)) { + return false; + } + + bufferDesc = std::move(shmem); + } + + // Use an intermediate buffer by default. Skipping the intermediate buffer is + // only possible in certain configurations so let's keep it simple here for + // now. + const bool hasIntermediateBuffer = true; + *aBuffer = SurfaceDescriptorBuffer( + RGBDescriptor(aSize, format, hasIntermediateBuffer), bufferDesc); + + return true; +} + +/* static */ +bool ShadowLayerForwarder::IsShmem(SurfaceDescriptor* aSurface) { + return aSurface && + (aSurface->type() == SurfaceDescriptor::TSurfaceDescriptorBuffer) && + (aSurface->get_SurfaceDescriptorBuffer().data().type() == + MemoryOrShmem::TShmem); +} + +void ShadowLayerForwarder::DestroySurfaceDescriptor( + SurfaceDescriptor* aSurface) { + MOZ_ASSERT(aSurface); + MOZ_ASSERT(IPCOpen()); + if (!IPCOpen() || !aSurface) { + return; + } + + ::mozilla::layers::DestroySurfaceDescriptor(GetTextureForwarder(), aSurface); +} + +void ShadowLayerForwarder::UpdateFwdTransactionId() { + auto compositorBridge = GetCompositorBridgeChild(); + if (compositorBridge) { + compositorBridge->UpdateFwdTransactionId(); + } +} + +uint64_t ShadowLayerForwarder::GetFwdTransactionId() { + auto compositorBridge = GetCompositorBridgeChild(); + MOZ_DIAGNOSTIC_ASSERT(compositorBridge); + return compositorBridge ? compositorBridge->GetFwdTransactionId() : 0; +} + +CompositorBridgeChild* ShadowLayerForwarder::GetCompositorBridgeChild() { + if (mCompositorBridgeChild) { + return mCompositorBridgeChild; + } + if (!mShadowManager) { + return nullptr; + } + mCompositorBridgeChild = + static_cast(mShadowManager->Manager()); + return mCompositorBridgeChild; +} + +void ShadowLayerForwarder::SyncWithCompositor() { + auto compositorBridge = GetCompositorBridgeChild(); + if (compositorBridge && compositorBridge->IPCOpen()) { + compositorBridge->SendSyncWithCompositor(); + } +} + +void ShadowLayerForwarder::ReleaseCompositable( + const CompositableHandle& aHandle) { + AssertInForwarderThread(); + if (!DestroyInTransaction(aHandle)) { + if (!IPCOpen()) { + return; + } + mShadowManager->SendReleaseCompositable(aHandle); + } + mCompositables.Remove(aHandle.Value()); +} + +void ShadowLayerForwarder::SynchronouslyShutdown() { + if (IPCOpen()) { + mShadowManager->SendShutdownSync(); + mShadowManager->MarkDestroyed(); + } +} + +ShadowableLayer::~ShadowableLayer() { + if (mShadow) { + mForwarder->ReleaseLayer(GetShadow()); + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/ShadowLayers.h b/gfx/layers/ipc/ShadowLayers.h new file mode 100644 index 0000000000..055aba63de --- /dev/null +++ b/gfx/layers/ipc/ShadowLayers.h @@ -0,0 +1,481 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_ShadowLayers_h +#define mozilla_layers_ShadowLayers_h 1 + +#include // for size_t +#include // for uint64_t +#include "gfxTypes.h" +#include "mozilla/Attributes.h" // for override +#include "mozilla/gfx/Rect.h" +#include "mozilla/WidgetUtils.h" // for ScreenRotation +#include "mozilla/ipc/SharedMemory.h" // for SharedMemory, etc +#include "mozilla/HalScreenConfiguration.h" // for ScreenOrientation +#include "mozilla/layers/CompositableForwarder.h" +#include "mozilla/layers/FocusTarget.h" +#include "mozilla/layers/LayersTypes.h" +#include "mozilla/layers/TextureForwarder.h" +#include "mozilla/layers/CompositorTypes.h" // for OpenMode, etc +#include "mozilla/layers/CompositorBridgeChild.h" +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsRegion.h" // for nsIntRegion +#include "nsTArrayForwardDeclare.h" // for nsTArray +#include "nsIWidget.h" +#include + +namespace mozilla { +namespace layers { + +class ClientLayerManager; +class CompositorBridgeChild; +class FixedSizeSmallShmemSectionAllocator; +class ImageContainer; +class Layer; +class PLayerTransactionChild; +class LayerTransactionChild; +class ShadowableLayer; +class SurfaceDescriptor; +class TextureClient; +class ThebesBuffer; +class ThebesBufferData; +class Transaction; + +/** + * We want to share layer trees across thread contexts and address + * spaces for several reasons; chief among them + * + * - a parent process can paint a child process's layer tree while + * the child process is blocked, say on content script. This is + * important on mobile devices where UI responsiveness is key. + * + * - a dedicated "compositor" process can asynchronously (wrt the + * browser process) composite and animate layer trees, allowing a + * form of pipeline parallelism between compositor/browser/content + * + * - a dedicated "compositor" process can take all responsibility for + * accessing the GPU, which is desirable on systems with + * buggy/leaky drivers because the compositor process can die while + * browser and content live on (and failover mechanisms can be + * installed to quickly bring up a replacement compositor) + * + * The Layers model has a crisply defined API, which makes it easy to + * safely "share" layer trees. The ShadowLayers API extends Layers to + * allow a remote, parent process to access a child process's layer + * tree. + * + * ShadowLayerForwarder publishes a child context's layer tree to a + * parent context. This comprises recording layer-tree modifications + * into atomic transactions and pushing them over IPC. + * + * LayerManagerComposite grafts layer subtrees published by child-context + * ShadowLayerForwarder(s) into a parent-context layer tree. + * + * (Advanced note: because our process tree may have a height >2, a + * non-leaf subprocess may both receive updates from child processes + * and publish them to parent processes. Put another way, + * LayerManagers may be both LayerManagerComposites and + * ShadowLayerForwarders.) + * + * There are only shadow types for layers that have different shadow + * vs. not-shadow behavior. ColorLayers and ContainerLayers behave + * the same way in both regimes (so far). + * + * + * The mecanism to shadow the layer tree on the compositor through IPC works as + * follows: + * The layer tree is managed on the content thread, and shadowed in the + * compositor thread. The shadow layer tree is only kept in sync with whatever + * happens in the content thread. To do this we use IPDL protocols. IPDL is a + * domain specific language that describes how two processes or thread should + * communicate. C++ code is generated from .ipdl files to implement the message + * passing, synchronization and serialization logic. To use the generated code + * we implement classes that inherit the generated IPDL actor. the ipdl actors + * of a protocol PX are PXChild or PXParent (the generated class), and we + * conventionally implement XChild and XParent. The Parent side of the protocol + * is the one that lives on the compositor thread. Think of IPDL actors as + * endpoints of communication. they are useful to send messages and also to + * dispatch the message to the right actor on the other side. One nice property + * of an IPDL actor is that when an actor, say PXChild is sent in a message, the + * PXParent comes out in the other side. we use this property a lot to dispatch + * messages to the right layers and compositable, each of which have their own + * ipdl actor on both side. + * + * Most of the synchronization logic happens in layer transactions and + * compositable transactions. + * A transaction is a set of changes to the layers and/or the compositables + * that are sent and applied together to the compositor thread to keep the + * LayerComposite in a coherent state. + * Layer transactions maintain the shape of the shadow layer tree, and + * synchronize the texture data held by compositables. Layer transactions + * are always between the content thread and the compositor thread. + * Compositable transactions are subset of a layer transaction with which only + * compositables and textures can be manipulated, and does not always originate + * from the content thread. (See CompositableForwarder.h and ImageBridgeChild.h) + */ + +class ShadowLayerForwarder final : public LayersIPCActor, + public CompositableForwarder, + public LegacySurfaceDescriptorAllocator { + friend class ClientLayerManager; + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ShadowLayerForwarder, override); + + /** + * Setup the IPDL actor for aCompositable to be part of layers + * transactions. + */ + void Connect(CompositableClient* aCompositable, + ImageContainer* aImageContainer) override; + + /** + * Adds an edit in the layers transaction in order to attach + * the corresponding compositable and layer on the compositor side. + * Connect must have been called on aCompositable beforehand. + */ + void Attach(CompositableClient* aCompositable, ShadowableLayer* aLayer); + + /** + * Adds an edit in the transaction in order to attach a Compositable that + * is not managed by this ShadowLayerForwarder (for example, by ImageBridge + * in the case of async-video). + * Since the compositable is not managed by this forwarder, we can't use + * the compositable or it's IPDL actor here, so we use an ID instead, that + * is matched on the compositor side. + */ + void AttachAsyncCompositable(const CompositableHandle& aHandle, + ShadowableLayer* aLayer); + + /** + * Begin recording a transaction to be forwarded atomically to a + * LayerManagerComposite. + */ + void BeginTransaction(const gfx::IntRect& aTargetBounds, + ScreenRotation aRotation, + hal::ScreenOrientation aOrientation); + + /** + * The following methods may only be called after BeginTransaction() + * but before EndTransaction(). They mirror the LayerManager + * interface in Layers.h. + */ + + /** + * Notify the shadow manager that a new, "real" layer has been + * created, and a corresponding shadow layer should be created in + * the compositing process. + */ + void CreatedPaintedLayer(ShadowableLayer* aThebes); + void CreatedContainerLayer(ShadowableLayer* aContainer); + void CreatedImageLayer(ShadowableLayer* aImage); + void CreatedColorLayer(ShadowableLayer* aColor); + void CreatedCanvasLayer(ShadowableLayer* aCanvas); + void CreatedRefLayer(ShadowableLayer* aRef); + + /** + * At least one attribute of |aMutant| has changed, and |aMutant| + * needs to sync to its shadow layer. This initial implementation + * forwards all attributes when any of the appropriate attribute + * set is mutated. + */ + void Mutated(ShadowableLayer* aMutant); + void MutatedSimple(ShadowableLayer* aMutant); + + void SetRoot(ShadowableLayer* aRoot); + /** + * Insert |aChild| after |aAfter| in |aContainer|. |aAfter| can be + * nullptr to indicated that |aChild| should be appended to the end of + * |aContainer|'s child list. + */ + void InsertAfter(ShadowableLayer* aContainer, ShadowableLayer* aChild, + ShadowableLayer* aAfter = nullptr); + void RemoveChild(ShadowableLayer* aContainer, ShadowableLayer* aChild); + void RepositionChild(ShadowableLayer* aContainer, ShadowableLayer* aChild, + ShadowableLayer* aAfter = nullptr); + + /** + * Set aMaskLayer as the mask on aLayer. + * Note that only image layers are properly supported + * LayerTransactionParent::UpdateMask and accompanying ipdl + * will need changing to update properties for other kinds + * of mask layer. + */ + void SetMask(ShadowableLayer* aLayer, ShadowableLayer* aMaskLayer); + + /** + * See CompositableForwarder::UseTiledLayerBuffer + */ + void UseTiledLayerBuffer( + CompositableClient* aCompositable, + const SurfaceDescriptorTiles& aTileLayerDescriptor) override; + + void ReleaseCompositable(const CompositableHandle& aHandle) override; + bool DestroyInTransaction(PTextureChild* aTexture) override; + bool DestroyInTransaction(const CompositableHandle& aHandle); + + void RemoveTextureFromCompositable(CompositableClient* aCompositable, + TextureClient* aTexture) override; + + /** + * Communicate to the compositor that aRegion in the texture identified by + * aLayer and aIdentifier has been updated to aThebesBuffer. + */ + void UpdateTextureRegion(CompositableClient* aCompositable, + const ThebesBufferData& aThebesBufferData, + const nsIntRegion& aUpdatedRegion) override; + + /** + * See CompositableForwarder::UseTextures + */ + void UseTextures(CompositableClient* aCompositable, + const nsTArray& aTextures) override; + void UseComponentAlphaTextures(CompositableClient* aCompositable, + TextureClient* aClientOnBlack, + TextureClient* aClientOnWhite) override; + + /** + * Used for debugging to tell the compositor how long this frame took to + * paint. + */ + void SendPaintTime(TransactionId aId, TimeDuration aPaintTime); + + /** + * End the current transaction and forward it to LayerManagerComposite. + * |aReplies| are directions from the LayerManagerComposite to the + * caller of EndTransaction(). + */ + bool EndTransaction(const nsIntRegion& aRegionToClear, TransactionId aId, + bool aScheduleComposite, uint32_t aPaintSequenceNumber, + bool aIsRepeatTransaction, + const mozilla::VsyncId& aVsyncId, + const mozilla::TimeStamp& aVsyncTime, + const mozilla::TimeStamp& aRefreshStart, + const mozilla::TimeStamp& aTransactionStart, + bool aContainsSVG, const nsCString& aURL, bool* aSent, + const nsTArray& aPayload = + nsTArray()); + + /** + * Set an actor through which layer updates will be pushed. + */ + void SetShadowManager(PLayerTransactionChild* aShadowManager); + + /** + * Layout calls here to cache current plugin widget configuration + * data. We ship this across with the rest of the layer updates when + * we update. Chrome handles applying these changes. + */ + void StorePluginWidgetConfigurations( + const nsTArray& aConfigurations); + + void StopReceiveAsyncParentMessge(); + + void ClearCachedResources(); + + void ScheduleComposite(); + + /** + * True if this is forwarding to a LayerManagerComposite. + */ + bool HasShadowManager() const { return !!mShadowManager; } + LayerTransactionChild* GetShadowManager() const { + return mShadowManager.get(); + } + + // Send a synchronous message asking the LayerTransactionParent in the + // compositor to shutdown. + void SynchronouslyShutdown(); + + /** + * The following Alloc/Open/Destroy interfaces abstract over the + * details of working with surfaces that are shared across + * processes. They provide the glue between C++ Layers and the + * LayerComposite IPC system. + * + * The basic lifecycle is + * + * - a Layer needs a buffer. Its ShadowableLayer subclass calls + * AllocBuffer(), then calls one of the Created*Buffer() methods + * above to transfer the (temporary) front buffer to its + * LayerComposite in the other process. The Layer needs a + * gfxASurface to paint, so the ShadowableLayer uses + * OpenDescriptor(backBuffer) to get that surface, and hands it + * out to the Layer. + * + * - a Layer has painted new pixels. Its ShadowableLayer calls one + * of the Painted*Buffer() methods above with the back buffer + * descriptor. This notification is forwarded to the LayerComposite, + * which uses OpenDescriptor() to access the newly-painted pixels. + * The LayerComposite then updates its front buffer in a Layer- and + * platform-dependent way, and sends a surface descriptor back to + * the ShadowableLayer that becomes its new back back buffer. + * + * - a Layer wants to destroy its buffers. Its ShadowableLayer + * calls Destroyed*Buffer(), which gives up control of the back + * buffer descriptor. The actual back buffer surface is then + * destroyed using DestroySharedSurface() just before notifying + * the parent process. When the parent process is notified, the + * LayerComposite also calls DestroySharedSurface() on its front + * buffer, and the double-buffer pair is gone. + */ + + bool IPCOpen() const override; + + /** + * Construct a shadow of |aLayer| on the "other side", at the + * LayerManagerComposite. + */ + LayerHandle ConstructShadowFor(ShadowableLayer* aLayer); + + /** + * Flag the next paint as the first for a document. + */ + void SetIsFirstPaint() { mIsFirstPaint = true; } + bool GetIsFirstPaint() const { return mIsFirstPaint; } + + /** + * Set the current focus target to be sent with the next paint. + */ + void SetFocusTarget(const FocusTarget& aFocusTarget) { + mFocusTarget = aFocusTarget; + } + + void SetLayersObserverEpoch(LayersObserverEpoch aEpoch); + + static void PlatformSyncBeforeUpdate(); + + bool AllocSurfaceDescriptor(const gfx::IntSize& aSize, + gfxContentType aContent, + SurfaceDescriptor* aBuffer) override; + + bool AllocSurfaceDescriptorWithCaps(const gfx::IntSize& aSize, + gfxContentType aContent, uint32_t aCaps, + SurfaceDescriptor* aBuffer) override; + + void DestroySurfaceDescriptor(SurfaceDescriptor* aSurface) override; + + void UpdateFwdTransactionId() override; + uint64_t GetFwdTransactionId() override; + + void UpdateTextureLocks(); + void SyncTextures(const nsTArray& aSerials); + + void ReleaseLayer(const LayerHandle& aHandle); + + bool InForwarderThread() override { return NS_IsMainThread(); } + + PaintTiming& GetPaintTiming() { return mPaintTiming; } + + ShadowLayerForwarder* AsLayerForwarder() override { return this; } + + // Returns true if aSurface wraps a Shmem. + static bool IsShmem(SurfaceDescriptor* aSurface); + + void SyncWithCompositor() override; + + TextureForwarder* GetTextureForwarder() override { + return GetCompositorBridgeChild(); + } + LayersIPCActor* GetLayersIPCActor() override { return this; } + + ActiveResourceTracker* GetActiveResourceTracker() override { + return mActiveResourceTracker.get(); + } + + CompositorBridgeChild* GetCompositorBridgeChild(); + + nsISerialEventTarget* GetEventTarget() { return mEventTarget; }; + + bool IsThreadSafe() const override { return false; } + + RefPtr GetForMedia() override; + + protected: + virtual ~ShadowLayerForwarder(); + + explicit ShadowLayerForwarder(ClientLayerManager* aClientLayerManager); + +#ifdef DEBUG + void CheckSurfaceDescriptor(const SurfaceDescriptor* aDescriptor) const; +#else + void CheckSurfaceDescriptor(const SurfaceDescriptor* aDescriptor) const {} +#endif + + RefPtr FindCompositable( + const CompositableHandle& aHandle); + + bool InWorkerThread(); + + RefPtr mShadowManager; + RefPtr mCompositorBridgeChild; + + private: + ClientLayerManager* mClientLayerManager; + Transaction* mTxn; + nsCOMPtr mThread; + DiagnosticTypes mDiagnosticTypes; + bool mIsFirstPaint; + FocusTarget mFocusTarget; + nsTArray mPluginWindowData; + UniquePtr mActiveResourceTracker; + uint64_t mNextLayerHandle; + nsDataHashtable mCompositables; + PaintTiming mPaintTiming; + /** + * ShadowLayerForwarder might dispatch tasks to main while puppet widget and + * browserChild don't exist anymore; therefore we hold the event target since + * its lifecycle is independent of these objects. + */ + nsCOMPtr mEventTarget; +}; + +class CompositableClient; + +/** + * A ShadowableLayer is a Layer can be shared with a parent context + * through a ShadowLayerForwarder. A ShadowableLayer maps to a + * Shadow*Layer in a parent context. + * + * Note that ShadowLayers can themselves be ShadowableLayers. + */ +class ShadowableLayer { + public: + virtual ~ShadowableLayer(); + + virtual Layer* AsLayer() = 0; + + /** + * True if this layer has a shadow in a parent process. + */ + bool HasShadow() { return mShadow.IsValid(); } + + /** + * Return the IPC handle to a Shadow*Layer referring to this if one + * exists, nullptr if not. + */ + const LayerHandle& GetShadow() { return mShadow; } + + void SetShadow(ShadowLayerForwarder* aForwarder, const LayerHandle& aShadow) { + MOZ_ASSERT(!mShadow, "can't have two shadows (yet)"); + mForwarder = aForwarder; + mShadow = aShadow; + } + + virtual CompositableClient* GetCompositableClient() { return nullptr; } + + protected: + ShadowableLayer() = default; + + private: + RefPtr mForwarder; + LayerHandle mShadow; +}; + +} // namespace layers +} // namespace mozilla + +#endif // ifndef mozilla_layers_ShadowLayers_h diff --git a/gfx/layers/ipc/SharedPlanarYCbCrImage.cpp b/gfx/layers/ipc/SharedPlanarYCbCrImage.cpp new file mode 100644 index 0000000000..8beec0f02d --- /dev/null +++ b/gfx/layers/ipc/SharedPlanarYCbCrImage.cpp @@ -0,0 +1,179 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SharedPlanarYCbCrImage.h" +#include // for size_t +#include // for printf +#include "gfx2DGlue.h" // for Moz2D transition helpers +#include "ISurfaceAllocator.h" // for ISurfaceAllocator, etc +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/gfx/Types.h" // for SurfaceFormat::SurfaceFormat::YUV +#include "mozilla/ipc/SharedMemory.h" // for SharedMemory, etc +#include "mozilla/layers/ImageClient.h" // for ImageClient +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor, etc +#include "mozilla/layers/TextureClient.h" +#include "mozilla/layers/TextureClientRecycleAllocator.h" +#include "mozilla/layers/BufferTexture.h" +#include "mozilla/layers/ImageDataSerializer.h" +#include "mozilla/layers/ImageBridgeChild.h" // for ImageBridgeChild +#include "mozilla/mozalloc.h" // for operator delete +#include "nsISupportsImpl.h" // for Image::AddRef +#include "mozilla/ipc/Shmem.h" + +namespace mozilla { +namespace layers { + +using namespace mozilla::ipc; + +SharedPlanarYCbCrImage::SharedPlanarYCbCrImage(ImageClient* aCompositable) + : mCompositable(aCompositable) { + MOZ_COUNT_CTOR(SharedPlanarYCbCrImage); +} + +SharedPlanarYCbCrImage::SharedPlanarYCbCrImage( + TextureClientRecycleAllocator* aRecycleAllocator) + : mRecycleAllocator(aRecycleAllocator) { + MOZ_COUNT_CTOR(SharedPlanarYCbCrImage); +} + +SharedPlanarYCbCrImage::~SharedPlanarYCbCrImage() { + MOZ_COUNT_DTOR(SharedPlanarYCbCrImage); +} + +TextureClientRecycleAllocator* SharedPlanarYCbCrImage::RecycleAllocator() { + static const uint32_t MAX_POOLED_VIDEO_COUNT = 5; + + if (!mRecycleAllocator && mCompositable) { + if (!mCompositable->HasTextureClientRecycler()) { + // Initialize TextureClientRecycler + mCompositable->GetTextureClientRecycler()->SetMaxPoolSize( + MAX_POOLED_VIDEO_COUNT); + } + mRecycleAllocator = mCompositable->GetTextureClientRecycler(); + } + return mRecycleAllocator; +} + +size_t SharedPlanarYCbCrImage::SizeOfExcludingThis( + MallocSizeOf aMallocSizeOf) const { + // NB: Explicitly skipping mTextureClient, the memory is already reported + // at time of allocation in GfxMemoryImageReporter. + // Not owned: + // - mCompositable + return 0; +} + +TextureClient* SharedPlanarYCbCrImage::GetTextureClient( + KnowsCompositor* aKnowsCompositor) { + return mTextureClient.get(); +} + +already_AddRefed +SharedPlanarYCbCrImage::GetAsSourceSurface() { + if (!IsValid()) { + NS_WARNING("Can't get as surface"); + return nullptr; + } + return PlanarYCbCrImage::GetAsSourceSurface(); +} + +bool SharedPlanarYCbCrImage::CopyData(const PlanarYCbCrData& aData) { + // If mTextureClient has not already been allocated (through Allocate(aData)) + // allocate it. This code path is slower than the one used when Allocate has + // been called since it will trigger a full copy. + PlanarYCbCrData data = aData; + if (!mTextureClient && !Allocate(data)) { + return false; + } + + TextureClientAutoLock autoLock(mTextureClient, OpenMode::OPEN_WRITE_ONLY); + if (!autoLock.Succeeded()) { + MOZ_ASSERT(false, "Failed to lock the texture."); + return false; + } + + if (!UpdateYCbCrTextureClient(mTextureClient, aData)) { + MOZ_ASSERT(false, "Failed to copy YCbCr data into the TextureClient"); + return false; + } + mTextureClient->MarkImmutable(); + return true; +} + +bool SharedPlanarYCbCrImage::AdoptData(const Data& aData) { + MOZ_ASSERT(false, "This shouldn't be used."); + return false; +} + +bool SharedPlanarYCbCrImage::IsValid() const { + return mTextureClient && mTextureClient->IsValid(); +} + +bool SharedPlanarYCbCrImage::Allocate(PlanarYCbCrData& aData) { + MOZ_ASSERT(!mTextureClient, "This image already has allocated data"); + + TextureFlags flags = + mCompositable ? mCompositable->GetTextureFlags() : TextureFlags::DEFAULT; + { + YCbCrTextureClientAllocationHelper helper(aData, flags); + mTextureClient = RecycleAllocator()->CreateOrRecycle(helper); + } + + if (!mTextureClient) { + NS_WARNING("SharedPlanarYCbCrImage::Allocate failed."); + return false; + } + + MappedYCbCrTextureData mapped; + // The locking here is sort of a lie. The SharedPlanarYCbCrImage just pulls + // pointers out of the TextureClient and keeps them around, which works only + // because the underlyin BufferTextureData is always mapped in memory even + // outside of the lock/unlock interval. That's sad and new code should follow + // this example. + if (!mTextureClient->Lock(OpenMode::OPEN_READ) || + !mTextureClient->BorrowMappedYCbCrData(mapped)) { + MOZ_CRASH("GFX: Cannot lock or borrow mapped YCbCr"); + } + + aData.mYChannel = mapped.y.data; + aData.mCbChannel = mapped.cb.data; + aData.mCrChannel = mapped.cr.data; + + // copy some of aData's values in mData (most of them) + mData.mYChannel = aData.mYChannel; + mData.mCbChannel = aData.mCbChannel; + mData.mCrChannel = aData.mCrChannel; + mData.mYSize = aData.mYSize; + mData.mCbCrSize = aData.mCbCrSize; + mData.mPicX = aData.mPicX; + mData.mPicY = aData.mPicY; + mData.mPicSize = aData.mPicSize; + mData.mStereoMode = aData.mStereoMode; + mData.mYUVColorSpace = aData.mYUVColorSpace; + mData.mColorDepth = aData.mColorDepth; + // those members are not always equal to aData's, due to potentially different + // packing. + mData.mYSkip = 0; + mData.mCbSkip = 0; + mData.mCrSkip = 0; + mData.mYStride = aData.mYStride; + mData.mCbCrStride = aData.mCbCrStride; + + // do not set mBuffer like in PlanarYCbCrImage because the later + // will try to manage this memory without knowing it belongs to a + // shmem. + mBufferSize = ImageDataSerializer::ComputeYCbCrBufferSize( + mData.mYSize, mData.mYStride, mData.mCbCrSize, mData.mCbCrStride); + mSize = mData.mPicSize; + mOrigin = gfx::IntPoint(aData.mPicX, aData.mPicY); + + mTextureClient->Unlock(); + + return mBufferSize > 0; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/SharedPlanarYCbCrImage.h b/gfx/layers/ipc/SharedPlanarYCbCrImage.h new file mode 100644 index 0000000000..6e99322268 --- /dev/null +++ b/gfx/layers/ipc/SharedPlanarYCbCrImage.h @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include // for uint8_t, uint32_t +#include "ImageContainer.h" // for PlanarYCbCrImage, etc +#include "mozilla/Attributes.h" // for override +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/ipc/Shmem.h" // for Shmem +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsDebug.h" // for NS_WARNING +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR + +#ifndef MOZILLA_LAYERS_SHAREDPLANARYCBCRIMAGE_H +# define MOZILLA_LAYERS_SHAREDPLANARYCBCRIMAGE_H + +namespace mozilla { +namespace layers { + +class ImageClient; +class TextureClient; +class TextureClientRecycleAllocator; + +class SharedPlanarYCbCrImage : public PlanarYCbCrImage { + public: + explicit SharedPlanarYCbCrImage(ImageClient* aCompositable); + explicit SharedPlanarYCbCrImage( + TextureClientRecycleAllocator* aRecycleAllocator); + + protected: + virtual ~SharedPlanarYCbCrImage(); + + public: + TextureClient* GetTextureClient(KnowsCompositor* aKnowsCompositor) override; + + already_AddRefed GetAsSourceSurface() override; + bool CopyData(const PlanarYCbCrData& aData) override; + bool AdoptData(const Data& aData) override; + + bool Allocate(PlanarYCbCrData& aData); + + bool IsValid() const override; + + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); + } + + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override; + + TextureClientRecycleAllocator* RecycleAllocator(); + + private: + RefPtr mTextureClient; + RefPtr mCompositable; + RefPtr mRecycleAllocator; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/ipc/SharedRGBImage.cpp b/gfx/layers/ipc/SharedRGBImage.cpp new file mode 100644 index 0000000000..c7cc028109 --- /dev/null +++ b/gfx/layers/ipc/SharedRGBImage.cpp @@ -0,0 +1,156 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SharedRGBImage.h" +#include "ImageTypes.h" // for ImageFormat::SHARED_RGB, etc +#include "Shmem.h" // for Shmem +#include "gfx2DGlue.h" // for ImageFormatToSurfaceFormat, etc +#include "gfxPlatform.h" // for gfxPlatform, gfxImageFormat +#include "mozilla/gfx/Point.h" // for IntSIze +#include "mozilla/layers/BufferTexture.h" +#include "mozilla/layers/ISurfaceAllocator.h" // for ISurfaceAllocator, etc +#include "mozilla/layers/ImageClient.h" // for ImageClient +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor, etc +#include "mozilla/layers/TextureClient.h" // for BufferTextureClient, etc +#include "mozilla/layers/TextureClientRecycleAllocator.h" // for ITextureClientAllocationHelper +#include "mozilla/layers/ImageBridgeChild.h" // for ImageBridgeChild +#include "mozilla/mozalloc.h" // for operator delete, etc +#include "nsDebug.h" // for NS_WARNING, NS_ASSERTION +#include "nsISupportsImpl.h" // for Image::AddRef, etc +#include "nsProxyRelease.h" +#include "nsRect.h" // for mozilla::gfx::IntRect + +// Just big enough for a 1080p RGBA32 frame +#define MAX_FRAME_SIZE (16 * 1024 * 1024) + +namespace mozilla { +namespace layers { + +class TextureClientForRawBufferAccessAllocationHelper + : public ITextureClientAllocationHelper { + public: + TextureClientForRawBufferAccessAllocationHelper(gfx::SurfaceFormat aFormat, + gfx::IntSize aSize, + TextureFlags aTextureFlags) + : ITextureClientAllocationHelper(aFormat, aSize, BackendSelector::Content, + aTextureFlags, ALLOC_DEFAULT) {} + + bool IsCompatible(TextureClient* aTextureClient) override { + bool ret = aTextureClient->GetFormat() == mFormat && + aTextureClient->GetSize() == mSize; + return ret; + } + + already_AddRefed Allocate( + KnowsCompositor* aAllocator) override { + return TextureClient::CreateForRawBufferAccess( + aAllocator, mFormat, mSize, gfx::BackendType::NONE, mTextureFlags); + } +}; + +SharedRGBImage::SharedRGBImage(ImageClient* aCompositable) + : Image(nullptr, ImageFormat::SHARED_RGB), mCompositable(aCompositable) { + MOZ_COUNT_CTOR(SharedRGBImage); +} + +SharedRGBImage::SharedRGBImage(TextureClientRecycleAllocator* aRecycleAllocator) + : Image(nullptr, ImageFormat::SHARED_RGB), + mRecycleAllocator(aRecycleAllocator) { + MOZ_COUNT_CTOR(SharedRGBImage); +} + +SharedRGBImage::~SharedRGBImage() { + MOZ_COUNT_DTOR(SharedRGBImage); + NS_ReleaseOnMainThread("SharedRGBImage::mSourceSurface", + mSourceSurface.forget()); +} + +TextureClientRecycleAllocator* SharedRGBImage::RecycleAllocator() { + static const uint32_t MAX_POOLED_VIDEO_COUNT = 5; + + if (!mRecycleAllocator && mCompositable) { + if (!mCompositable->HasTextureClientRecycler()) { + // Initialize TextureClientRecycler + mCompositable->GetTextureClientRecycler()->SetMaxPoolSize( + MAX_POOLED_VIDEO_COUNT); + } + mRecycleAllocator = mCompositable->GetTextureClientRecycler(); + } + return mRecycleAllocator; +} + +bool SharedRGBImage::Allocate(gfx::IntSize aSize, gfx::SurfaceFormat aFormat) { + mSize = aSize; + + TextureFlags flags = + mCompositable ? mCompositable->GetTextureFlags() : TextureFlags::DEFAULT; + { + TextureClientForRawBufferAccessAllocationHelper helper(aFormat, aSize, + flags); + mTextureClient = RecycleAllocator()->CreateOrRecycle(helper); + } + + return !!mTextureClient; +} + +gfx::IntSize SharedRGBImage::GetSize() const { return mSize; } + +TextureClient* SharedRGBImage::GetTextureClient( + KnowsCompositor* aKnowsCompositor) { + return mTextureClient.get(); +} + +static void ReleaseTextureClient(void* aData) { + RELEASE_MANUALLY(static_cast(aData)); +} + +static gfx::UserDataKey sTextureClientKey; + +already_AddRefed SharedRGBImage::GetAsSourceSurface() { + NS_ASSERTION(NS_IsMainThread(), "Must be main thread"); + + if (mSourceSurface) { + RefPtr surface(mSourceSurface); + return surface.forget(); + } + + RefPtr surface; + { + // We are 'borrowing' the DrawTarget and retaining a permanent reference to + // the underlying data (via the surface). It is in this instance since we + // know that the TextureClient is always wrapping a BufferTextureData and + // therefore it won't go away underneath us. + BufferTextureData* decoded_buffer = + mTextureClient->GetInternalData()->AsBufferTextureData(); + RefPtr drawTarget = decoded_buffer->BorrowDrawTarget(); + + if (!drawTarget) { + return nullptr; + } + + surface = drawTarget->Snapshot(); + if (!surface) { + return nullptr; + } + + // The surface may outlive the owning TextureClient. So, we need to ensure + // that the surface keeps the TextureClient alive via a reference held in + // user data. The TextureClient's DrawTarget only has a weak reference to + // the surface, so we won't create any cycles by just referencing the + // TextureClient. + if (!surface->GetUserData(&sTextureClientKey)) { + surface->AddUserData(&sTextureClientKey, mTextureClient, + ReleaseTextureClient); + ADDREF_MANUALLY(mTextureClient); + } + } + + mSourceSurface = surface; + return surface.forget(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/SharedRGBImage.h b/gfx/layers/ipc/SharedRGBImage.h new file mode 100644 index 0000000000..ddec2dc9aa --- /dev/null +++ b/gfx/layers/ipc/SharedRGBImage.h @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef SHAREDRGBIMAGE_H_ +#define SHAREDRGBIMAGE_H_ + +#include // for size_t +#include // for uint8_t + +#include "ImageContainer.h" // for ISharedImage, Image, etc +#include "gfxTypes.h" +#include "mozilla/Attributes.h" // for override +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/gfx/Types.h" // for SurfaceFormat +#include "nsCOMPtr.h" // for already_AddRefed + +namespace mozilla { +namespace layers { + +class ImageClient; +class TextureClient; + +/** + * Stores RGB data in shared memory + * It is assumed that the image width and stride are equal + */ +class SharedRGBImage : public Image { + public: + explicit SharedRGBImage(ImageClient* aCompositable); + explicit SharedRGBImage(TextureClientRecycleAllocator* aRecycleAllocator); + + protected: + virtual ~SharedRGBImage(); + + public: + TextureClient* GetTextureClient(KnowsCompositor* aKnowsCompositor) override; + + gfx::IntSize GetSize() const override; + + already_AddRefed GetAsSourceSurface() override; + + bool Allocate(gfx::IntSize aSize, gfx::SurfaceFormat aFormat); + + private: + TextureClientRecycleAllocator* RecycleAllocator(); + + gfx::IntSize mSize; + RefPtr mTextureClient; + RefPtr mCompositable; + RefPtr mRecycleAllocator; + RefPtr mSourceSurface; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/ipc/SharedSurfacesChild.cpp b/gfx/layers/ipc/SharedSurfacesChild.cpp new file mode 100644 index 0000000000..55ece9abdf --- /dev/null +++ b/gfx/layers/ipc/SharedSurfacesChild.cpp @@ -0,0 +1,657 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SharedSurfacesChild.h" +#include "SharedSurfacesParent.h" +#include "CompositorManagerChild.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/layers/IpcResourceUpdateQueue.h" +#include "mozilla/layers/SourceSurfaceSharedData.h" +#include "mozilla/layers/WebRenderBridgeChild.h" +#include "mozilla/layers/RenderRootStateManager.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/StaticPrefs_image.h" + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +/* static */ +UserDataKey SharedSurfacesChild::sSharedKey; + +SharedSurfacesChild::ImageKeyData::ImageKeyData( + RenderRootStateManager* aManager, const wr::ImageKey& aImageKey) + : mManager(aManager), mImageKey(aImageKey) {} + +SharedSurfacesChild::ImageKeyData::ImageKeyData( + SharedSurfacesChild::ImageKeyData&& aOther) + : mManager(std::move(aOther.mManager)), + mDirtyRect(std::move(aOther.mDirtyRect)), + mImageKey(aOther.mImageKey) {} + +SharedSurfacesChild::ImageKeyData& SharedSurfacesChild::ImageKeyData::operator=( + SharedSurfacesChild::ImageKeyData&& aOther) { + mManager = std::move(aOther.mManager); + mDirtyRect = std::move(aOther.mDirtyRect); + mImageKey = aOther.mImageKey; + return *this; +} + +SharedSurfacesChild::ImageKeyData::~ImageKeyData() = default; + +void SharedSurfacesChild::ImageKeyData::MergeDirtyRect( + const Maybe& aDirtyRect) { + if (mDirtyRect) { + if (aDirtyRect) { + mDirtyRect->UnionRect(mDirtyRect.ref(), aDirtyRect.ref()); + } + } else { + mDirtyRect = aDirtyRect; + } +} + +SharedSurfacesChild::SharedUserData::SharedUserData( + const wr::ExternalImageId& aId) + : Runnable("SharedSurfacesChild::SharedUserData"), + mId(aId), + mShared(false) {} + +SharedSurfacesChild::SharedUserData::~SharedUserData() { + // We may fail to dispatch during shutdown, and since it would be issued on + // the main thread, it releases the runnable instead of leaking it. + if (mShared || !mKeys.IsEmpty()) { + if (NS_IsMainThread()) { + SharedSurfacesChild::Unshare(mId, mShared, mKeys); + } else { + MOZ_ASSERT_UNREACHABLE("Shared resources not released!"); + } + } +} + +/* static */ +void SharedSurfacesChild::SharedUserData::Destroy(void* aClosure) { + MOZ_ASSERT(aClosure); + RefPtr data = + dont_AddRef(static_cast(aClosure)); + if (data->mShared || !data->mKeys.IsEmpty()) { + SchedulerGroup::Dispatch(TaskCategory::Other, data.forget()); + } +} + +NS_IMETHODIMP SharedSurfacesChild::SharedUserData::Run() { + SharedSurfacesChild::Unshare(mId, mShared, mKeys); + mShared = false; + mKeys.Clear(); + return NS_OK; +} + +wr::ImageKey SharedSurfacesChild::SharedUserData::UpdateKey( + RenderRootStateManager* aManager, wr::IpcResourceUpdateQueue& aResources, + const Maybe& aDirtyRect) { + MOZ_ASSERT(aManager); + MOZ_ASSERT(!aManager->IsDestroyed()); + + // We iterate through all of the items to ensure we clean up the old + // RenderRootStateManager references. Most of the time there will be few + // entries and this should not be particularly expensive compared to the + // cost of duplicating image keys. In an ideal world, we would generate a + // single key for the surface, and it would be usable on all of the + // renderer instances. For now, we must allocate a key for each WR bridge. + wr::ImageKey key; + bool found = false; + auto i = mKeys.Length(); + while (i > 0) { + --i; + ImageKeyData& entry = mKeys[i]; + if (entry.mManager->IsDestroyed()) { + mKeys.RemoveElementAt(i); + } else if (entry.mManager == aManager) { + WebRenderBridgeChild* wrBridge = aManager->WrBridge(); + MOZ_ASSERT(wrBridge); + + // Even if the manager is the same, its underlying WebRenderBridgeChild + // can change state. If our namespace differs, then our old key has + // already been discarded. + bool ownsKey = wrBridge->GetNamespace() == entry.mImageKey.mNamespace; + if (!ownsKey) { + entry.mImageKey = wrBridge->GetNextImageKey(); + entry.TakeDirtyRect(); + aResources.AddSharedExternalImage(mId, entry.mImageKey); + } else { + entry.MergeDirtyRect(aDirtyRect); + Maybe dirtyRect = entry.TakeDirtyRect(); + if (dirtyRect) { + MOZ_ASSERT(mShared); + aResources.UpdateSharedExternalImage( + mId, entry.mImageKey, ViewAs(dirtyRect.ref())); + } + } + + key = entry.mImageKey; + found = true; + } else { + // We don't have the resource update queue for this manager, so just + // accumulate the dirty rects until it is requested. + entry.MergeDirtyRect(aDirtyRect); + } + } + + if (!found) { + key = aManager->WrBridge()->GetNextImageKey(); + ImageKeyData data(aManager, key); + mKeys.AppendElement(std::move(data)); + aResources.AddSharedExternalImage(mId, key); + } + + return key; +} + +/* static */ +SourceSurfaceSharedData* SharedSurfacesChild::AsSourceSurfaceSharedData( + SourceSurface* aSurface) { + MOZ_ASSERT(aSurface); + switch (aSurface->GetType()) { + case SurfaceType::DATA_SHARED: + case SurfaceType::DATA_RECYCLING_SHARED: + return static_cast(aSurface); + default: + return nullptr; + } +} + +/* static */ +nsresult SharedSurfacesChild::ShareInternal(SourceSurfaceSharedData* aSurface, + SharedUserData** aUserData) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aSurface); + MOZ_ASSERT(aUserData); + + CompositorManagerChild* manager = CompositorManagerChild::GetInstance(); + if (NS_WARN_IF(!manager || !manager->CanSend() || !gfxVars::UseWebRender())) { + // We cannot try to share the surface, most likely because the GPU process + // crashed. Ideally, we would retry when it is ready, but the handles may be + // a scarce resource, which can cause much more serious problems if we run + // out. Better to copy into a fresh buffer later. + aSurface->FinishedSharing(); + return NS_ERROR_NOT_INITIALIZED; + } + + SharedUserData* data = + static_cast(aSurface->GetUserData(&sSharedKey)); + if (!data) { + data = + MakeAndAddRef(manager->GetNextExternalImageId()).take(); + aSurface->AddUserData(&sSharedKey, data, SharedUserData::Destroy); + } else if (!manager->OwnsExternalImageId(data->Id())) { + // If the id isn't owned by us, that means the bridge was reinitialized, due + // to the GPU process crashing. All previous mappings have been released. + data->SetId(manager->GetNextExternalImageId()); + } else if (data->IsShared()) { + // It has already been shared with the GPU process. + *aUserData = data; + return NS_OK; + } + + // Ensure that the handle doesn't get released until after we have finished + // sending the buffer to the GPU process and/or reallocating it. + // FinishedSharing is not a sufficient condition because another thread may + // decide we are done while we are in the processing of sharing our newly + // reallocated handle. Once it goes out of scope, it may release the handle. + SourceSurfaceSharedData::HandleLock lock(aSurface); + + // If we live in the same process, then it is a simple matter of directly + // asking the parent instance to store a pointer to the same data, no need + // to map the data into our memory space twice. + auto pid = manager->OtherPid(); + if (pid == base::GetCurrentProcId()) { + SharedSurfacesParent::AddSameProcess(data->Id(), aSurface); + data->MarkShared(); + *aUserData = data; + return NS_OK; + } + + // Attempt to share a handle with the GPU process. The handle may or may not + // be available -- it will only be available if it is either not yet finalized + // and/or if it has been finalized but never used for drawing in process. + ipc::SharedMemoryBasic::Handle handle = ipc::SharedMemoryBasic::NULLHandle(); + nsresult rv = aSurface->ShareToProcess(pid, handle); + if (rv == NS_ERROR_NOT_AVAILABLE) { + // It is at least as expensive to copy the image to the GPU process if we + // have already closed the handle necessary to share, but if we reallocate + // the shared buffer to get a new handle, we can save some memory. + if (NS_WARN_IF(!aSurface->ReallocHandle())) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // Reattempt the sharing of the handle to the GPU process. + rv = aSurface->ShareToProcess(pid, handle); + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_ASSERT(rv != NS_ERROR_NOT_AVAILABLE); + return rv; + } + + SurfaceFormat format = aSurface->GetFormat(); + MOZ_RELEASE_ASSERT( + format == SurfaceFormat::B8G8R8X8 || format == SurfaceFormat::B8G8R8A8, + "bad format"); + + data->MarkShared(); + manager->SendAddSharedSurface( + data->Id(), SurfaceDescriptorShared(aSurface->GetSize(), + aSurface->Stride(), format, handle)); + *aUserData = data; + return NS_OK; +} + +/* static */ +void SharedSurfacesChild::Share(SourceSurfaceSharedData* aSurface) { + MOZ_ASSERT(aSurface); + + // The IPDL actor to do sharing can only be accessed on the main thread so we + // need to dispatch if off the main thread. However there is no real danger if + // we end up racing because if it is already shared, this method will do + // nothing. + if (!NS_IsMainThread()) { + class ShareRunnable final : public Runnable { + public: + explicit ShareRunnable(SourceSurfaceSharedData* aSurface) + : Runnable("SharedSurfacesChild::Share"), mSurface(aSurface) {} + + NS_IMETHOD Run() override { + SharedUserData* unused = nullptr; + SharedSurfacesChild::ShareInternal(mSurface, &unused); + return NS_OK; + } + + private: + RefPtr mSurface; + }; + + SchedulerGroup::Dispatch(TaskCategory::Other, + MakeAndAddRef(aSurface)); + return; + } + + SharedUserData* unused = nullptr; + SharedSurfacesChild::ShareInternal(aSurface, &unused); +} + +/* static */ +nsresult SharedSurfacesChild::Share(SourceSurfaceSharedData* aSurface, + RenderRootStateManager* aManager, + wr::IpcResourceUpdateQueue& aResources, + wr::ImageKey& aKey) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aSurface); + MOZ_ASSERT(aManager); + + // Each time the surface changes, the producers of SourceSurfaceSharedData + // surfaces promise to increment the invalidation counter each time the + // surface has changed. We can use this counter to determine whether or not + // we should update our paired ImageKey. + Maybe dirtyRect = aSurface->TakeDirtyRect(); + SharedUserData* data = nullptr; + nsresult rv = SharedSurfacesChild::ShareInternal(aSurface, &data); + if (NS_SUCCEEDED(rv)) { + MOZ_ASSERT(data); + aKey = data->UpdateKey(aManager, aResources, dirtyRect); + } + + return rv; +} + +/* static */ +nsresult SharedSurfacesChild::Share(SourceSurface* aSurface, + RenderRootStateManager* aManager, + wr::IpcResourceUpdateQueue& aResources, + wr::ImageKey& aKey) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aSurface); + MOZ_ASSERT(aManager); + + auto sharedSurface = AsSourceSurfaceSharedData(aSurface); + if (!sharedSurface) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + return Share(sharedSurface, aManager, aResources, aKey); +} + +/* static */ +nsresult SharedSurfacesChild::Share(ImageContainer* aContainer, + RenderRootStateManager* aManager, + wr::IpcResourceUpdateQueue& aResources, + wr::ImageKey& aKey, + ContainerProducerID aProducerId) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aContainer); + MOZ_ASSERT(aManager); + + if (aContainer->IsAsync()) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + AutoTArray images; + aContainer->GetCurrentImages(&images); + if (images.IsEmpty()) { + return NS_ERROR_NOT_AVAILABLE; + } + + if (aProducerId != kContainerProducerID_Invalid && + images[0].mProducerID != aProducerId) { + // If the producer ID of the surface in the container does not match the + // expected producer ID, then we do not want to proceed with sharing. This + // is useful for when callers are unsure if given container is for the same + // producer / underlying image request. + return NS_ERROR_FAILURE; + } + + RefPtr surface = images[0].mImage->GetAsSourceSurface(); + if (!surface) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + auto sharedSurface = AsSourceSurfaceSharedData(surface); + if (!sharedSurface) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + SharedSurfacesAnimation* anim = aContainer->GetSharedSurfacesAnimation(); + if (anim) { + return anim->UpdateKey(sharedSurface, aManager, aResources, aKey); + } + + return Share(sharedSurface, aManager, aResources, aKey); +} + +/* static */ +nsresult SharedSurfacesChild::Share(SourceSurface* aSurface, + wr::ExternalImageId& aId) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aSurface); + + auto sharedSurface = AsSourceSurfaceSharedData(aSurface); + if (!sharedSurface) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + // The external image ID does not change with the invalidation counter. The + // caller of this should be aware of the invalidations of the surface through + // another mechanism (e.g. imgRequestProxy listener notifications). + SharedUserData* data = nullptr; + nsresult rv = ShareInternal(sharedSurface, &data); + if (NS_SUCCEEDED(rv)) { + MOZ_ASSERT(data); + aId = data->Id(); + } + + return rv; +} + +/* static */ +void SharedSurfacesChild::Unshare(const wr::ExternalImageId& aId, + bool aReleaseId, + nsTArray& aKeys) { + MOZ_ASSERT(NS_IsMainThread()); + + for (const auto& entry : aKeys) { + if (!entry.mManager->IsDestroyed()) { + entry.mManager->AddImageKeyForDiscard(entry.mImageKey); + } + } + + if (!aReleaseId) { + // We don't own the external image ID itself. + return; + } + + CompositorManagerChild* manager = CompositorManagerChild::GetInstance(); + if (MOZ_UNLIKELY(!manager || !manager->CanSend())) { + return; + } + + if (manager->OwnsExternalImageId(aId)) { + // Only attempt to release current mappings in the compositor process. It is + // possible we had a surface that was previously shared, the compositor + // process crashed / was restarted, and then we freed the surface. In that + // case we know the mapping has already been freed. + manager->SendRemoveSharedSurface(aId); + } +} + +/* static */ Maybe SharedSurfacesChild::GetExternalId( + const SourceSurfaceSharedData* aSurface) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aSurface); + + SharedUserData* data = + static_cast(aSurface->GetUserData(&sSharedKey)); + if (!data || !data->IsShared()) { + return Nothing(); + } + + return Some(data->Id()); +} + +/* static */ +nsresult SharedSurfacesChild::UpdateAnimation(ImageContainer* aContainer, + SourceSurface* aSurface, + const IntRect& aDirtyRect) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aContainer); + MOZ_ASSERT(!aContainer->IsAsync()); + MOZ_ASSERT(aSurface); + + // If we aren't using shared surfaces, then is nothing to do. + auto sharedSurface = SharedSurfacesChild::AsSourceSurfaceSharedData(aSurface); + if (!sharedSurface) { + MOZ_ASSERT(!aContainer->GetSharedSurfacesAnimation()); + return NS_ERROR_NOT_IMPLEMENTED; + } + + SharedSurfacesAnimation* anim = aContainer->EnsureSharedSurfacesAnimation(); + MOZ_ASSERT(anim); + + return anim->SetCurrentFrame(sharedSurface, aDirtyRect); +} + +AnimationImageKeyData::AnimationImageKeyData(RenderRootStateManager* aManager, + const wr::ImageKey& aImageKey) + : SharedSurfacesChild::ImageKeyData(aManager, aImageKey) {} + +AnimationImageKeyData::AnimationImageKeyData(AnimationImageKeyData&& aOther) + : SharedSurfacesChild::ImageKeyData(std::move(aOther)), + mPendingRelease(std::move(aOther.mPendingRelease)) {} + +AnimationImageKeyData& AnimationImageKeyData::operator=( + AnimationImageKeyData&& aOther) { + mPendingRelease = std::move(aOther.mPendingRelease); + SharedSurfacesChild::ImageKeyData::operator=(std::move(aOther)); + return *this; +} + +AnimationImageKeyData::~AnimationImageKeyData() = default; + +SharedSurfacesAnimation::~SharedSurfacesAnimation() { + MOZ_ASSERT(mKeys.IsEmpty()); +} + +void SharedSurfacesAnimation::Destroy() { + if (!NS_IsMainThread()) { + nsCOMPtr task = + NewRunnableMethod("SharedSurfacesAnimation::Destroy", this, + &SharedSurfacesAnimation::Destroy); + SchedulerGroup::Dispatch(TaskCategory::Other, task.forget()); + return; + } + + if (mKeys.IsEmpty()) { + return; + } + + for (const auto& entry : mKeys) { + MOZ_ASSERT(!entry.mManager->IsDestroyed()); + if (StaticPrefs::image_animated_decode_on_demand_recycle_AtStartup()) { + entry.mManager->DeregisterAsyncAnimation(entry.mImageKey); + } + entry.mManager->AddImageKeyForDiscard(entry.mImageKey); + } + + mKeys.Clear(); +} + +void SharedSurfacesAnimation::HoldSurfaceForRecycling( + AnimationImageKeyData& aEntry, SourceSurfaceSharedData* aSurface) { + if (aSurface->GetType() != SurfaceType::DATA_RECYCLING_SHARED) { + return; + } + + MOZ_ASSERT(StaticPrefs::image_animated_decode_on_demand_recycle_AtStartup()); + aEntry.mPendingRelease.AppendElement(aSurface); +} + +nsresult SharedSurfacesAnimation::SetCurrentFrame( + SourceSurfaceSharedData* aSurface, const gfx::IntRect& aDirtyRect) { + MOZ_ASSERT(aSurface); + + SharedSurfacesChild::SharedUserData* data = nullptr; + nsresult rv = SharedSurfacesChild::ShareInternal(aSurface, &data); + if (NS_FAILED(rv)) { + return rv; + } + + MOZ_ASSERT(data); + mId = data->Id(); + + auto i = mKeys.Length(); + while (i > 0) { + --i; + AnimationImageKeyData& entry = mKeys[i]; + MOZ_ASSERT(!entry.mManager->IsDestroyed()); + + entry.MergeDirtyRect(Some(aDirtyRect)); + Maybe dirtyRect = entry.TakeDirtyRect(); + if (dirtyRect) { + HoldSurfaceForRecycling(entry, aSurface); + auto& resourceUpdates = entry.mManager->AsyncResourceUpdates(); + resourceUpdates.UpdateSharedExternalImage( + mId, entry.mImageKey, ViewAs(dirtyRect.ref())); + } + } + + return NS_OK; +} + +nsresult SharedSurfacesAnimation::UpdateKey( + SourceSurfaceSharedData* aSurface, RenderRootStateManager* aManager, + wr::IpcResourceUpdateQueue& aResources, wr::ImageKey& aKey) { + SharedSurfacesChild::SharedUserData* data = nullptr; + nsresult rv = SharedSurfacesChild::ShareInternal(aSurface, &data); + if (NS_FAILED(rv)) { + return rv; + } + + MOZ_ASSERT(data); + if (wr::AsUint64(mId) != wr::AsUint64(data->Id())) { + mKeys.Clear(); + mId = data->Id(); + } + + // We iterate through all of the items to ensure we clean up the old + // RenderRootStateManager references. Most of the time there will be few + // entries and this should not be particularly expensive compared to the + // cost of duplicating image keys. In an ideal world, we would generate a + // single key for the surface, and it would be usable on all of the + // renderer instances. For now, we must allocate a key for each WR bridge. + bool found = false; + auto i = mKeys.Length(); + while (i > 0) { + --i; + AnimationImageKeyData& entry = mKeys[i]; + MOZ_ASSERT(!entry.mManager->IsDestroyed()); + if (entry.mManager == aManager) { + WebRenderBridgeChild* wrBridge = aManager->WrBridge(); + MOZ_ASSERT(wrBridge); + + // Even if the manager is the same, its underlying WebRenderBridgeChild + // can change state. If our namespace differs, then our old key has + // already been discarded. + bool ownsKey = wrBridge->GetNamespace() == entry.mImageKey.mNamespace; + if (!ownsKey) { + entry.mImageKey = wrBridge->GetNextImageKey(); + HoldSurfaceForRecycling(entry, aSurface); + aResources.AddSharedExternalImage(mId, entry.mImageKey); + } else { + MOZ_ASSERT(entry.mDirtyRect.isNothing()); + } + + aKey = entry.mImageKey; + found = true; + break; + } + } + + if (!found) { + aKey = aManager->WrBridge()->GetNextImageKey(); + if (StaticPrefs::image_animated_decode_on_demand_recycle_AtStartup()) { + aManager->RegisterAsyncAnimation(aKey, this); + } + + AnimationImageKeyData data(aManager, aKey); + HoldSurfaceForRecycling(data, aSurface); + mKeys.AppendElement(std::move(data)); + aResources.AddSharedExternalImage(mId, aKey); + } + + return NS_OK; +} + +void SharedSurfacesAnimation::ReleasePreviousFrame( + RenderRootStateManager* aManager, const wr::ExternalImageId& aId) { + MOZ_ASSERT(aManager); + + auto i = mKeys.Length(); + while (i > 0) { + --i; + AnimationImageKeyData& entry = mKeys[i]; + MOZ_ASSERT(!entry.mManager->IsDestroyed()); + if (entry.mManager == aManager) { + size_t k; + for (k = 0; k < entry.mPendingRelease.Length(); ++k) { + Maybe extId = + SharedSurfacesChild::GetExternalId(entry.mPendingRelease[k]); + if (extId && extId.ref() == aId) { + break; + } + } + + if (k == entry.mPendingRelease.Length()) { + continue; + } + + entry.mPendingRelease.RemoveElementsAt(0, k + 1); + break; + } + } +} + +void SharedSurfacesAnimation::Invalidate(RenderRootStateManager* aManager) { + auto i = mKeys.Length(); + while (i > 0) { + --i; + AnimationImageKeyData& entry = mKeys[i]; + if (entry.mManager == aManager) { + mKeys.RemoveElementAt(i); + break; + } + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/SharedSurfacesChild.h b/gfx/layers/ipc/SharedSurfacesChild.h new file mode 100644 index 0000000000..cc0f2fc2f5 --- /dev/null +++ b/gfx/layers/ipc/SharedSurfacesChild.h @@ -0,0 +1,267 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_SHAREDSURFACESCHILD_H +#define MOZILLA_GFX_SHAREDSURFACESCHILD_H + +#include // for uint32_t, uint64_t +#include "mozilla/Attributes.h" // for override +#include "mozilla/Maybe.h" // for Maybe +#include "mozilla/RefPtr.h" // for already_AddRefed +#include "mozilla/StaticPtr.h" // for StaticRefPtr +#include "mozilla/gfx/UserData.h" // for UserDataKey +#include "mozilla/webrender/WebRenderTypes.h" // for wr::ImageKey +#include "nsTArray.h" // for AutoTArray +#include "nsThreadUtils.h" // for Runnable +#include "ImageTypes.h" // for ContainerProducerID + +namespace mozilla { +namespace layers { +class AnimationImageKeyData; +} // namespace layers +} // namespace mozilla + +template <> +struct nsTArray_RelocationStrategy { + typedef nsTArray_RelocateUsingMoveConstructor< + mozilla::layers::AnimationImageKeyData> + Type; +}; + +namespace mozilla { +namespace gfx { +class SourceSurface; +class SourceSurfaceSharedData; +} // namespace gfx + +namespace wr { +class IpcResourceUpdateQueue; +} // namespace wr + +namespace layers { + +class CompositorManagerChild; +class ImageContainer; +class RenderRootStateManager; + +class SharedSurfacesChild { + public: + /** + * Request that the surface be mapped into the compositor thread's memory + * space. This is useful for when the caller itself has no present need for + * the surface to be mapped, but knows there will be such a need in the + * future. This may be called from any thread, but it may cause a dispatch to + * the main thread. + */ + static void Share(gfx::SourceSurfaceSharedData* aSurface); + + /** + * Request that the surface be mapped into the compositor thread's memory + * space, and a valid ExternalImageId be generated for it for use with + * WebRender. This must be called from the main thread. + */ + static nsresult Share(gfx::SourceSurface* aSurface, wr::ExternalImageId& aId); + + /** + * Request that the surface be mapped into the compositor thread's memory + * space, and a valid ImageKey be generated for it for use with WebRender. + * This must be called from the main thread. + */ + static nsresult Share(gfx::SourceSurfaceSharedData* aSurface, + RenderRootStateManager* aManager, + wr::IpcResourceUpdateQueue& aResources, + wr::ImageKey& aKey); + + /** + * Request that the surface be mapped into the compositor thread's memory + * space, and a valid ImageKey be generated for it for use with WebRender. + * This must be called from the main thread. + */ + static nsresult Share(gfx::SourceSurface* aSurface, + RenderRootStateManager* aManager, + wr::IpcResourceUpdateQueue& aResources, + wr::ImageKey& aKey); + + /** + * Request that the first surface in the image container's current images be + * mapped into the compositor thread's memory space, and a valid ImageKey be + * generated for it for use with WebRender. If a different method should be + * used to share the image data for this particular container, it will return + * NS_ERROR_NOT_IMPLEMENTED. This must be called from the main thread. + */ + static nsresult Share(ImageContainer* aContainer, + RenderRootStateManager* aManager, + wr::IpcResourceUpdateQueue& aResources, + wr::ImageKey& aKey, ContainerProducerID aProducerId); + + /** + * Get the external ID, if any, bound to the shared surface. Used for memory + * reporting purposes. + */ + static Maybe GetExternalId( + const gfx::SourceSurfaceSharedData* aSurface); + + /** + * Get the surface (or its underlying surface) as a SourceSurfaceSharedData + * pointer, if valid. + */ + static gfx::SourceSurfaceSharedData* AsSourceSurfaceSharedData( + gfx::SourceSurface* aSurface); + + static nsresult UpdateAnimation(ImageContainer* aContainer, + gfx::SourceSurface* aSurface, + const gfx::IntRect& aDirtyRect); + + class ImageKeyData { + public: + ImageKeyData(RenderRootStateManager* aManager, + const wr::ImageKey& aImageKey); + virtual ~ImageKeyData(); + + ImageKeyData(ImageKeyData&& aOther); + ImageKeyData& operator=(ImageKeyData&& aOther); + ImageKeyData(const ImageKeyData&) = delete; + ImageKeyData& operator=(const ImageKeyData&) = delete; + + void MergeDirtyRect(const Maybe& aDirtyRect); + + Maybe TakeDirtyRect() { return std::move(mDirtyRect); } + + RefPtr mManager; + Maybe mDirtyRect; + wr::ImageKey mImageKey; + }; + + private: + SharedSurfacesChild() = delete; + ~SharedSurfacesChild() = delete; + + friend class SharedSurfacesAnimation; + + class SharedUserData final : public Runnable { + public: + explicit SharedUserData(const wr::ExternalImageId& aId); + virtual ~SharedUserData(); + + SharedUserData(const SharedUserData& aOther) = delete; + SharedUserData& operator=(const SharedUserData& aOther) = delete; + + SharedUserData(SharedUserData&& aOther) = delete; + SharedUserData& operator=(SharedUserData&& aOther) = delete; + + static void Destroy(void* aClosure); + + NS_IMETHOD Run() override; + + const wr::ExternalImageId& Id() const { return mId; } + + void SetId(const wr::ExternalImageId& aId) { + mId = aId; + mKeys.Clear(); + mShared = false; + } + + bool IsShared() const { return mShared; } + + void MarkShared() { + MOZ_ASSERT(!mShared); + mShared = true; + } + + wr::ImageKey UpdateKey(RenderRootStateManager* aManager, + wr::IpcResourceUpdateQueue& aResources, + const Maybe& aDirtyRect); + + protected: + AutoTArray mKeys; + wr::ExternalImageId mId; + bool mShared : 1; + }; + + static nsresult ShareInternal(gfx::SourceSurfaceSharedData* aSurface, + SharedUserData** aUserData); + + static void Unshare(const wr::ExternalImageId& aId, bool aReleaseId, + nsTArray& aKeys); + + static void DestroySharedUserData(void* aClosure); + + static gfx::UserDataKey sSharedKey; +}; + +class AnimationImageKeyData final : public SharedSurfacesChild::ImageKeyData { + public: + AnimationImageKeyData(RenderRootStateManager* aManager, + const wr::ImageKey& aImageKey); + + virtual ~AnimationImageKeyData(); + + AnimationImageKeyData(AnimationImageKeyData&& aOther); + AnimationImageKeyData& operator=(AnimationImageKeyData&& aOther); + + AutoTArray, 2> mPendingRelease; +}; + +/** + * This helper class owns a single ImageKey which will map to different external + * image IDs representing different frames in an animation. + */ +class SharedSurfacesAnimation final { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedSurfacesAnimation) + + SharedSurfacesAnimation() = default; + + void Destroy(); + + /** + * Set the animation to display the given frame. + * @param aSurface The current frame. + * @param aDirtyRect Dirty rect representing the change between the new frame + * and the previous frame. We will request only the delta + * be reuploaded by WebRender. + */ + nsresult SetCurrentFrame(gfx::SourceSurfaceSharedData* aSurface, + const gfx::IntRect& aDirtyRect); + + /** + * Generate an ImageKey for the given frame. + * @param aSurface The current frame. This should match what was cached via + * SetCurrentFrame, but if it does not, it will need to + * regenerate the cached ImageKey. + */ + nsresult UpdateKey(gfx::SourceSurfaceSharedData* aSurface, + RenderRootStateManager* aManager, + wr::IpcResourceUpdateQueue& aResources, + wr::ImageKey& aKey); + + /** + * Release our reference to all frames up to and including the frame which + * has an external image ID which matches aId. + */ + void ReleasePreviousFrame(RenderRootStateManager* aManager, + const wr::ExternalImageId& aId); + + /** + * Destroy any state information bound for the given layer manager. Any + * image keys are already invalid. + */ + void Invalidate(RenderRootStateManager* aManager); + + private: + ~SharedSurfacesAnimation(); + + void HoldSurfaceForRecycling(AnimationImageKeyData& aEntry, + gfx::SourceSurfaceSharedData* aSurface); + + AutoTArray mKeys; + wr::ExternalImageId mId; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/ipc/SharedSurfacesMemoryReport.h b/gfx/layers/ipc/SharedSurfacesMemoryReport.h new file mode 100644 index 0000000000..57d9edddb7 --- /dev/null +++ b/gfx/layers/ipc/SharedSurfacesMemoryReport.h @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_SHAREDSURFACESMEMORYREPORT_H +#define MOZILLA_GFX_SHAREDSURFACESMEMORYREPORT_H + +#include // for uint32_t +#include +#include "base/process.h" +#include "ipc/IPCMessageUtils.h" +#include "ipc/IPCMessageUtilsSpecializations.h" +#include "mozilla/gfx/Point.h" // for IntSize + +namespace mozilla { +namespace layers { + +class SharedSurfacesMemoryReport final { + public: + class SurfaceEntry final { + public: + base::ProcessId mCreatorPid; + gfx::IntSize mSize; + int32_t mStride; + uint32_t mConsumers; + bool mCreatorRef; + }; + + std::unordered_map mSurfaces; +}; + +} // namespace layers +} // namespace mozilla + +namespace IPC { + +template <> +struct ParamTraits { + typedef mozilla::layers::SharedSurfacesMemoryReport paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mSurfaces); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return ReadParam(aMsg, aIter, &aResult->mSurfaces); + } +}; + +template <> +struct ParamTraits + : public PlainOldDataSerializer< + mozilla::layers::SharedSurfacesMemoryReport::SurfaceEntry> {}; + +} // namespace IPC + +#endif diff --git a/gfx/layers/ipc/SharedSurfacesParent.cpp b/gfx/layers/ipc/SharedSurfacesParent.cpp new file mode 100644 index 0000000000..844e3d83b3 --- /dev/null +++ b/gfx/layers/ipc/SharedSurfacesParent.cpp @@ -0,0 +1,257 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SharedSurfacesParent.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/gfx/GPUProcessManager.h" +#include "mozilla/layers/SharedSurfacesMemoryReport.h" +#include "mozilla/layers/SourceSurfaceSharedData.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/webrender/RenderSharedSurfaceTextureHost.h" +#include "mozilla/webrender/RenderSharedSurfaceTextureHostSWGL.h" +#include "mozilla/webrender/RenderThread.h" + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +StaticMutex SharedSurfacesParent::sMutex; +StaticAutoPtr SharedSurfacesParent::sInstance; + +SharedSurfacesParent::SharedSurfacesParent() = default; + +SharedSurfacesParent::~SharedSurfacesParent() { + for (auto i = mSurfaces.Iter(); !i.Done(); i.Next()) { + // There may be lingering consumers of the surfaces that didn't get shutdown + // yet but since we are here, we know the render thread is finished and we + // can unregister everything. + wr::RenderThread::Get()->UnregisterExternalImageDuringShutdown(i.Key()); + } +} + +/* static */ +void SharedSurfacesParent::Initialize() { + MOZ_ASSERT(NS_IsMainThread()); + StaticMutexAutoLock lock(sMutex); + if (!sInstance) { + sInstance = new SharedSurfacesParent(); + } +} + +/* static */ +void SharedSurfacesParent::Shutdown() { + // The main thread should blocked on waiting for the render thread to + // complete so this should be safe to release off the main thread. + MOZ_ASSERT(wr::RenderThread::IsInRenderThread()); + StaticMutexAutoLock lock(sMutex); + sInstance = nullptr; +} + +/* static */ +already_AddRefed SharedSurfacesParent::Get( + const wr::ExternalImageId& aId) { + StaticMutexAutoLock lock(sMutex); + if (!sInstance) { + gfxCriticalNote << "SSP:Get " << wr::AsUint64(aId) << " shtd"; + return nullptr; + } + + RefPtr surface; + sInstance->mSurfaces.Get(wr::AsUint64(aId), getter_AddRefs(surface)); + return surface.forget(); +} + +/* static */ +already_AddRefed SharedSurfacesParent::Acquire( + const wr::ExternalImageId& aId) { + StaticMutexAutoLock lock(sMutex); + if (!sInstance) { + gfxCriticalNote << "SSP:Acq " << wr::AsUint64(aId) << " shtd"; + return nullptr; + } + + RefPtr surface; + sInstance->mSurfaces.Get(wr::AsUint64(aId), getter_AddRefs(surface)); + + if (surface) { + DebugOnly rv = surface->AddConsumer(); + MOZ_ASSERT(!rv); + } + return surface.forget(); +} + +/* static */ +bool SharedSurfacesParent::Release(const wr::ExternalImageId& aId, + bool aForCreator) { + StaticMutexAutoLock lock(sMutex); + if (!sInstance) { + return false; + } + + uint64_t id = wr::AsUint64(aId); + RefPtr surface; + sInstance->mSurfaces.Get(wr::AsUint64(aId), getter_AddRefs(surface)); + if (!surface) { + return false; + } + + if (surface->RemoveConsumer(aForCreator)) { + wr::RenderThread::Get()->UnregisterExternalImage(id); + sInstance->mSurfaces.Remove(id); + } + + return true; +} + +/* static */ +void SharedSurfacesParent::AddSameProcess(const wr::ExternalImageId& aId, + SourceSurfaceSharedData* aSurface) { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + StaticMutexAutoLock lock(sMutex); + if (!sInstance) { + gfxCriticalNote << "SSP:Ads " << wr::AsUint64(aId) << " shtd"; + return; + } + + // If the child bridge detects it is in the combined UI/GPU process, then it + // will insert a wrapper surface holding the shared memory buffer directly. + // This is good because we avoid mapping the same shared memory twice, but + // still allow the original surface to be freed and remove the wrapper from + // the table when it is no longer needed. + RefPtr surface = + new SourceSurfaceSharedDataWrapper(); + surface->Init(aSurface); + + uint64_t id = wr::AsUint64(aId); + MOZ_ASSERT(!sInstance->mSurfaces.Contains(id)); + + RefPtr texture; + if (gfx::gfxVars::UseSoftwareWebRender()) { + texture = new wr::RenderSharedSurfaceTextureHostSWGL(surface); + } else { + texture = new wr::RenderSharedSurfaceTextureHost(surface); + } + wr::RenderThread::Get()->RegisterExternalImage(id, texture.forget()); + + surface->AddConsumer(); + sInstance->mSurfaces.Put(id, std::move(surface)); +} + +/* static */ +void SharedSurfacesParent::DestroyProcess(base::ProcessId aPid) { + StaticMutexAutoLock lock(sMutex); + if (!sInstance) { + return; + } + + // Note that the destruction of a parent may not be cheap if it still has a + // lot of surfaces still bound that require unmapping. + for (auto i = sInstance->mSurfaces.Iter(); !i.Done(); i.Next()) { + SourceSurfaceSharedDataWrapper* surface = i.Data(); + if (surface->GetCreatorPid() == aPid && surface->HasCreatorRef() && + surface->RemoveConsumer(/* aForCreator */ true)) { + wr::RenderThread::Get()->UnregisterExternalImage(i.Key()); + i.Remove(); + } + } +} + +/* static */ +void SharedSurfacesParent::Add(const wr::ExternalImageId& aId, + const SurfaceDescriptorShared& aDesc, + base::ProcessId aPid) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + MOZ_ASSERT(aPid != base::GetCurrentProcId()); + StaticMutexAutoLock lock(sMutex); + if (!sInstance) { + gfxCriticalNote << "SSP:Add " << wr::AsUint64(aId) << " shtd"; + return; + } + + // Note that the surface wrapper maps in the given handle as read only. + RefPtr surface = + new SourceSurfaceSharedDataWrapper(); + if (NS_WARN_IF(!surface->Init(aDesc.size(), aDesc.stride(), aDesc.format(), + aDesc.handle(), aPid))) { + gfxCriticalNote << "SSP:Add " << wr::AsUint64(aId) << " init"; + return; + } + + uint64_t id = wr::AsUint64(aId); + MOZ_ASSERT(!sInstance->mSurfaces.Contains(id)); + + RefPtr texture; + if (gfx::gfxVars::UseSoftwareWebRender()) { + texture = new wr::RenderSharedSurfaceTextureHostSWGL(surface); + } else { + texture = new wr::RenderSharedSurfaceTextureHost(surface); + } + wr::RenderThread::Get()->RegisterExternalImage(id, texture.forget()); + + surface->AddConsumer(); + sInstance->mSurfaces.Put(id, std::move(surface)); +} + +/* static */ +void SharedSurfacesParent::Remove(const wr::ExternalImageId& aId) { + DebugOnly rv = Release(aId, /* aForCreator */ true); + MOZ_ASSERT(rv); +} + +/* static */ +void SharedSurfacesParent::AccumulateMemoryReport( + base::ProcessId aPid, SharedSurfacesMemoryReport& aReport) { + StaticMutexAutoLock lock(sMutex); + if (!sInstance) { + return; + } + + for (auto i = sInstance->mSurfaces.ConstIter(); !i.Done(); i.Next()) { + SourceSurfaceSharedDataWrapper* surface = i.Data(); + if (surface->GetCreatorPid() == aPid) { + aReport.mSurfaces.insert(std::make_pair( + i.Key(), SharedSurfacesMemoryReport::SurfaceEntry{ + aPid, surface->GetSize(), surface->Stride(), + surface->GetConsumers(), surface->HasCreatorRef()})); + } + } +} + +/* static */ +bool SharedSurfacesParent::AccumulateMemoryReport( + SharedSurfacesMemoryReport& aReport) { + if (XRE_IsParentProcess()) { + GPUProcessManager* gpm = GPUProcessManager::Get(); + if (!gpm || gpm->GPUProcessPid() != -1) { + return false; + } + } else if (!XRE_IsGPUProcess()) { + return false; + } + + StaticMutexAutoLock lock(sMutex); + if (!sInstance) { + return true; + } + + for (auto i = sInstance->mSurfaces.ConstIter(); !i.Done(); i.Next()) { + SourceSurfaceSharedDataWrapper* surface = i.Data(); + aReport.mSurfaces.insert(std::make_pair( + i.Key(), + SharedSurfacesMemoryReport::SurfaceEntry{ + surface->GetCreatorPid(), surface->GetSize(), surface->Stride(), + surface->GetConsumers(), surface->HasCreatorRef()})); + } + + return true; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/SharedSurfacesParent.h b/gfx/layers/ipc/SharedSurfacesParent.h new file mode 100644 index 0000000000..a990c1b19d --- /dev/null +++ b/gfx/layers/ipc/SharedSurfacesParent.h @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_SHAREDSURFACESPARENT_H +#define MOZILLA_GFX_SHAREDSURFACESPARENT_H + +#include // for uint32_t +#include "mozilla/Attributes.h" // for override +#include "mozilla/StaticMutex.h" // for StaticMutex +#include "mozilla/StaticPtr.h" // for StaticAutoPtr +#include "mozilla/RefPtr.h" // for already_AddRefed +#include "mozilla/ipc/SharedMemory.h" // for SharedMemory, etc +#include "mozilla/gfx/2D.h" // for SurfaceFormat +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptorShared +#include "mozilla/webrender/WebRenderTypes.h" // for wr::ExternalImageId +#include "nsRefPtrHashtable.h" + +namespace mozilla { +namespace gfx { +class DataSourceSurface; +class SourceSurfaceSharedData; +class SourceSurfaceSharedDataWrapper; +} // namespace gfx + +namespace layers { + +class SharedSurfacesChild; +class SharedSurfacesMemoryReport; + +class SharedSurfacesParent final { + public: + static void Initialize(); + static void Shutdown(); + + // Get without increasing the consumer count. + static already_AddRefed Get( + const wr::ExternalImageId& aId); + + // Get but also increase the consumer count. Must call Release after finished. + static already_AddRefed Acquire( + const wr::ExternalImageId& aId); + + static bool Release(const wr::ExternalImageId& aId, bool aForCreator = false); + + static void Add(const wr::ExternalImageId& aId, + const SurfaceDescriptorShared& aDesc, base::ProcessId aPid); + + static void Remove(const wr::ExternalImageId& aId); + + static void DestroyProcess(base::ProcessId aPid); + + static void AccumulateMemoryReport(base::ProcessId aPid, + SharedSurfacesMemoryReport& aReport); + + static bool AccumulateMemoryReport(SharedSurfacesMemoryReport& aReport); + + ~SharedSurfacesParent(); + + private: + friend class SharedSurfacesChild; + + SharedSurfacesParent(); + + static void AddSameProcess(const wr::ExternalImageId& aId, + gfx::SourceSurfaceSharedData* aSurface); + + static StaticMutex sMutex; + + static StaticAutoPtr sInstance; + + nsRefPtrHashtable + mSurfaces; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/ipc/SurfaceDescriptor.h b/gfx/layers/ipc/SurfaceDescriptor.h new file mode 100644 index 0000000000..17ee00c6ae --- /dev/null +++ b/gfx/layers/ipc/SurfaceDescriptor.h @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef IPC_SurfaceDescriptor_h +#define IPC_SurfaceDescriptor_h + +#if defined(XP_MACOSX) +# define MOZ_HAVE_PLATFORM_SPECIFIC_LAYER_BUFFERS +#endif + +#if defined(MOZ_X11) +# include "mozilla/AlreadyAddRefed.h" +# include "mozilla/gfx/Point.h" + +# define MOZ_HAVE_SURFACEDESCRIPTORX11 +# define MOZ_HAVE_PLATFORM_SPECIFIC_LAYER_BUFFERS + +typedef unsigned long XID; +typedef XID Drawable; + +class gfxXlibSurface; + +namespace mozilla { +namespace layers { + +struct SurfaceDescriptorX11 { + SurfaceDescriptorX11() = default; + + explicit SurfaceDescriptorX11(gfxXlibSurface* aSurf, + bool aForwardGLX = false); + + SurfaceDescriptorX11(Drawable aDrawable, XID aFormatID, + const gfx::IntSize& aSize); + + // Default copy ctor and operator= are OK + + bool operator==(const SurfaceDescriptorX11& aOther) const { + // Define == as two descriptors having the same XID for now, + // ignoring size and render format. If the two indeed refer to + // the same valid XID, then size/format are "actually" the same + // anyway, regardless of the values of the fields in + // SurfaceDescriptorX11. + return mId == aOther.mId; + } + + already_AddRefed OpenForeign() const; + + MOZ_INIT_OUTSIDE_CTOR Drawable mId; + MOZ_INIT_OUTSIDE_CTOR XID mFormat; // either a PictFormat or VisualID + MOZ_INIT_OUTSIDE_CTOR gfx::IntSize mSize; + MOZ_INIT_OUTSIDE_CTOR Drawable + mGLXPixmap; // used to prevent multiple bindings to the same GLXPixmap + // in-process +}; + +} // namespace layers +} // namespace mozilla +#else +namespace mozilla { +namespace layers { +struct SurfaceDescriptorX11 { + bool operator==(const SurfaceDescriptorX11&) const { return false; } +}; +} // namespace layers +} // namespace mozilla +#endif + +#endif // IPC_SurfaceDescriptor_h diff --git a/gfx/layers/ipc/SynchronousTask.h b/gfx/layers/ipc/SynchronousTask.h new file mode 100644 index 0000000000..fc6acda81b --- /dev/null +++ b/gfx/layers/ipc/SynchronousTask.h @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_SYNCHRONOUSTASK_H +#define MOZILLA_GFX_SYNCHRONOUSTASK_H + +#include "mozilla/ReentrantMonitor.h" // for ReentrantMonitor, etc + +namespace mozilla { +namespace layers { + +// Helper that creates a monitor and a "done" flag, then enters the monitor. +class MOZ_STACK_CLASS SynchronousTask { + friend class AutoCompleteTask; + + public: + explicit SynchronousTask(const char* name) + : mMonitor(name), mAutoEnter(mMonitor), mDone(false) {} + + void Wait() { + while (!mDone) { + mMonitor.Wait(); + } + } + + private: + void Complete() { + mDone = true; + mMonitor.NotifyAll(); + } + + private: + ReentrantMonitor mMonitor; + ReentrantMonitorAutoEnter mAutoEnter; + bool mDone; +}; + +class MOZ_STACK_CLASS AutoCompleteTask final { + public: + explicit AutoCompleteTask(SynchronousTask* aTask) + : mTask(aTask), mAutoEnter(aTask->mMonitor) {} + ~AutoCompleteTask() { mTask->Complete(); } + + private: + SynchronousTask* mTask; + ReentrantMonitorAutoEnter mAutoEnter; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/ipc/TextureForwarder.h b/gfx/layers/ipc/TextureForwarder.h new file mode 100644 index 0000000000..bbd9b72153 --- /dev/null +++ b/gfx/layers/ipc/TextureForwarder.h @@ -0,0 +1,91 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_LAYERS_TEXTUREFORWARDER +#define MOZILLA_LAYERS_TEXTUREFORWARDER + +#include // for int32_t, uint64_t +#include "gfxTypes.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/layers/LayersMessages.h" // for Edit, etc +#include "mozilla/layers/LayersTypes.h" // for LayersBackend +#include "mozilla/layers/TextureClient.h" // for TextureClient +#include "mozilla/layers/KnowsCompositor.h" +#include "nsISerialEventTarget.h" + +namespace mozilla { +namespace layers { +class CanvasChild; + +/** + * An abstract interface for classes that implement the autogenerated + * IPDL actor class. Lets us check if they are still valid for IPC. + */ +class LayersIPCActor { + public: + virtual bool IPCOpen() const { return true; } +}; + +/** + * An abstract interface for LayersIPCActors that implement a top-level + * IPDL protocol so also have their own channel. + * Has their own MessageLoop for message dispatch, and can allocate + * shmem. + */ +class LayersIPCChannel : public LayersIPCActor, + public mozilla::ipc::IShmemAllocator { + public: + NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING + + virtual bool IsSameProcess() const = 0; + + virtual bool UsesImageBridge() const { return false; } + + virtual base::ProcessId GetParentPid() const = 0; + + virtual nsISerialEventTarget* GetThread() const = 0; + + virtual FixedSizeSmallShmemSectionAllocator* GetTileLockAllocator() { + return nullptr; + } + + virtual void CancelWaitForNotifyNotUsed(uint64_t aTextureId) = 0; + + virtual wr::MaybeExternalImageId GetNextExternalImageId() { + return Nothing(); + } + + protected: + virtual ~LayersIPCChannel() = default; +}; + +/** + * An abstract interface for classes that can allocate PTexture objects + * across IPDL. Currently a sub-class of LayersIPCChannel for simplicity + * since all our implementations use both, but could be independant if needed. + */ +class TextureForwarder : public LayersIPCChannel { + public: + /** + * Create a TextureChild/Parent pair as as well as the TextureHost on the + * parent side. + */ + virtual PTextureChild* CreateTexture( + const SurfaceDescriptor& aSharedData, const ReadLockDescriptor& aReadLock, + LayersBackend aLayersBackend, TextureFlags aFlags, uint64_t aSerial, + wr::MaybeExternalImageId& aExternalImageId, + nsISerialEventTarget* aTarget = nullptr) = 0; + + /** + * Returns the CanvasChild for this TextureForwarder. + */ + virtual already_AddRefed GetCanvasChild() { return nullptr; }; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/ipc/UiCompositorControllerChild.cpp b/gfx/layers/ipc/UiCompositorControllerChild.cpp new file mode 100644 index 0000000000..fc12c61641 --- /dev/null +++ b/gfx/layers/ipc/UiCompositorControllerChild.cpp @@ -0,0 +1,322 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/UiCompositorControllerChild.h" + +#include "mozilla/dom/ContentChild.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/layers/UiCompositorControllerMessageTypes.h" +#include "mozilla/layers/UiCompositorControllerParent.h" +#include "mozilla/gfx/GPUProcessManager.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/StaticPtr.h" +#include "nsBaseWidget.h" +#include "nsProxyRelease.h" +#include "nsThreadUtils.h" + +#if defined(MOZ_WIDGET_ANDROID) +# include "mozilla/widget/AndroidUiThread.h" + +static RefPtr GetUiThread() { return mozilla::GetAndroidUiThread(); } +#else +static RefPtr GetUiThread() { + MOZ_CRASH("Platform does not support UiCompositorController"); + return nullptr; +} +#endif // defined(MOZ_WIDGET_ANDROID) + +static bool IsOnUiThread() { return GetUiThread()->IsOnCurrentThread(); } + +namespace mozilla { +namespace layers { + +// public: +/* static */ +RefPtr +UiCompositorControllerChild::CreateForSameProcess( + const LayersId& aRootLayerTreeId) { + RefPtr child = + new UiCompositorControllerChild(0); + child->mParent = new UiCompositorControllerParent(aRootLayerTreeId); + GetUiThread()->Dispatch( + NewRunnableMethod( + "layers::UiCompositorControllerChild::OpenForSameProcess", child, + &UiCompositorControllerChild::OpenForSameProcess), + nsIThread::DISPATCH_NORMAL); + return child; +} + +/* static */ +RefPtr +UiCompositorControllerChild::CreateForGPUProcess( + const uint64_t& aProcessToken, + Endpoint&& aEndpoint) { + RefPtr child = + new UiCompositorControllerChild(aProcessToken); + + RefPtr task = + NewRunnableMethod&&>( + "layers::UiCompositorControllerChild::OpenForGPUProcess", child, + &UiCompositorControllerChild::OpenForGPUProcess, + std::move(aEndpoint)); + + GetUiThread()->Dispatch(task.forget(), nsIThread::DISPATCH_NORMAL); + return child; +} + +bool UiCompositorControllerChild::Pause() { + if (!mIsOpen) { + return false; + } + return SendPause(); +} + +bool UiCompositorControllerChild::Resume() { + if (!mIsOpen) { + return false; + } + return SendResume(); +} + +bool UiCompositorControllerChild::ResumeAndResize(const int32_t& aX, + const int32_t& aY, + const int32_t& aWidth, + const int32_t& aHeight) { + if (!mIsOpen) { + mResize = Some(gfx::IntRect(aX, aY, aWidth, aHeight)); + // Since we are caching these values, pretend the call succeeded. + return true; + } + return SendResumeAndResize(aX, aY, aWidth, aHeight); +} + +bool UiCompositorControllerChild::InvalidateAndRender() { + if (!mIsOpen) { + return false; + } + return SendInvalidateAndRender(); +} + +bool UiCompositorControllerChild::SetMaxToolbarHeight(const int32_t& aHeight) { + if (!mIsOpen) { + mMaxToolbarHeight = Some(aHeight); + // Since we are caching this value, pretend the call succeeded. + return true; + } + return SendMaxToolbarHeight(aHeight); +} + +bool UiCompositorControllerChild::SetFixedBottomOffset(int32_t aOffset) { + return SendFixedBottomOffset(aOffset); +} + +bool UiCompositorControllerChild::ToolbarAnimatorMessageFromUI( + const int32_t& aMessage) { + if (!mIsOpen) { + return false; + } + + if (aMessage == IS_COMPOSITOR_CONTROLLER_OPEN) { + RecvToolbarAnimatorMessageFromCompositor(COMPOSITOR_CONTROLLER_OPEN); + } + + return true; +} + +bool UiCompositorControllerChild::SetDefaultClearColor(const uint32_t& aColor) { + if (!mIsOpen) { + mDefaultClearColor = Some(aColor); + // Since we are caching this value, pretend the call succeeded. + return true; + } + + return SendDefaultClearColor(aColor); +} + +bool UiCompositorControllerChild::RequestScreenPixels() { + if (!mIsOpen) { + return false; + } + + return SendRequestScreenPixels(); +} + +bool UiCompositorControllerChild::EnableLayerUpdateNotifications( + const bool& aEnable) { + if (!mIsOpen) { + mLayerUpdateEnabled = Some(aEnable); + // Since we are caching this value, pretend the call succeeded. + return true; + } + + return SendEnableLayerUpdateNotifications(aEnable); +} + +void UiCompositorControllerChild::Destroy() { + if (!IsOnUiThread()) { + GetUiThread()->Dispatch( + NewRunnableMethod("layers::UiCompositorControllerChild::Destroy", this, + &UiCompositorControllerChild::Destroy), + nsIThread::DISPATCH_SYNC); + return; + } + + if (mWidget) { + // Dispatch mWidget to main thread to prevent it from being destructed by + // the ui thread. + RefPtr widget = std::move(mWidget); + NS_ReleaseOnMainThread("UiCompositorControllerChild::mWidget", + widget.forget()); + } + + if (mIsOpen) { + // Close the underlying IPC channel. + PUiCompositorControllerChild::Close(); + mIsOpen = false; + } +} + +void UiCompositorControllerChild::SetBaseWidget(nsBaseWidget* aWidget) { + mWidget = aWidget; +} + +bool UiCompositorControllerChild::DeallocPixelBuffer(Shmem& aMem) { + return DeallocShmem(aMem); +} + +// protected: +void UiCompositorControllerChild::ActorDestroy(ActorDestroyReason aWhy) { + mIsOpen = false; + mParent = nullptr; + + if (mProcessToken) { + gfx::GPUProcessManager::Get()->NotifyRemoteActorDestroyed(mProcessToken); + mProcessToken = 0; + } +} + +void UiCompositorControllerChild::ActorDealloc() { + if (mParent) { + mParent = nullptr; + } + Release(); +} + +void UiCompositorControllerChild::ProcessingError(Result aCode, + const char* aReason) { + if (aCode != MsgDropped) { + gfxDevCrash(gfx::LogReason::ProcessingError) + << "Processing error in UiCompositorControllerChild: " << int(aCode); + } +} + +void UiCompositorControllerChild::HandleFatalError(const char* aMsg) const { + dom::ContentChild::FatalErrorIfNotUsingGPUProcess(aMsg, OtherPid()); +} + +mozilla::ipc::IPCResult +UiCompositorControllerChild::RecvToolbarAnimatorMessageFromCompositor( + const int32_t& aMessage) { +#if defined(MOZ_WIDGET_ANDROID) + if (mWidget) { + mWidget->RecvToolbarAnimatorMessageFromCompositor(aMessage); + } +#endif // defined(MOZ_WIDGET_ANDROID) + + return IPC_OK(); +} + +mozilla::ipc::IPCResult UiCompositorControllerChild::RecvRootFrameMetrics( + const ScreenPoint& aScrollOffset, const CSSToScreenScale& aZoom) { +#if defined(MOZ_WIDGET_ANDROID) + if (mWidget) { + mWidget->UpdateRootFrameMetrics(aScrollOffset, aZoom); + } +#endif // defined(MOZ_WIDGET_ANDROID) + + return IPC_OK(); +} + +mozilla::ipc::IPCResult UiCompositorControllerChild::RecvScreenPixels( + ipc::Shmem&& aMem, const ScreenIntSize& aSize, bool aNeedsYFlip) { +#if defined(MOZ_WIDGET_ANDROID) + if (mWidget) { + mWidget->RecvScreenPixels(std::move(aMem), aSize, aNeedsYFlip); + } +#endif // defined(MOZ_WIDGET_ANDROID) + + return IPC_OK(); +} + +// private: +UiCompositorControllerChild::UiCompositorControllerChild( + const uint64_t& aProcessToken) + : mIsOpen(false), mProcessToken(aProcessToken), mWidget(nullptr) {} + +UiCompositorControllerChild::~UiCompositorControllerChild() = default; + +void UiCompositorControllerChild::OpenForSameProcess() { + MOZ_ASSERT(IsOnUiThread()); + + mIsOpen = Open(mParent->GetIPCChannel(), mozilla::layers::CompositorThread(), + mozilla::ipc::ChildSide); + + if (!mIsOpen) { + mParent = nullptr; + return; + } + + mParent->InitializeForSameProcess(); + AddRef(); + SendCachedValues(); + // Let Ui thread know the connection is open; + RecvToolbarAnimatorMessageFromCompositor(COMPOSITOR_CONTROLLER_OPEN); +} + +void UiCompositorControllerChild::OpenForGPUProcess( + Endpoint&& aEndpoint) { + MOZ_ASSERT(IsOnUiThread()); + + mIsOpen = aEndpoint.Bind(this); + + if (!mIsOpen) { + // The GPU Process Manager might be gone if we receive ActorDestroy very + // late in shutdown. + if (gfx::GPUProcessManager* gpm = gfx::GPUProcessManager::Get()) { + gpm->NotifyRemoteActorDestroyed(mProcessToken); + } + return; + } + + AddRef(); + SendCachedValues(); + // Let Ui thread know the connection is open; + RecvToolbarAnimatorMessageFromCompositor(COMPOSITOR_CONTROLLER_OPEN); +} + +void UiCompositorControllerChild::SendCachedValues() { + MOZ_ASSERT(mIsOpen); + if (mResize) { + SendResumeAndResize(mResize.ref().x, mResize.ref().y, mResize.ref().width, + mResize.ref().height); + mResize.reset(); + } + if (mMaxToolbarHeight) { + SendMaxToolbarHeight(mMaxToolbarHeight.ref()); + mMaxToolbarHeight.reset(); + } + if (mDefaultClearColor) { + SendDefaultClearColor(mDefaultClearColor.ref()); + mDefaultClearColor.reset(); + } + if (mLayerUpdateEnabled) { + SendEnableLayerUpdateNotifications(mLayerUpdateEnabled.ref()); + mLayerUpdateEnabled.reset(); + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/UiCompositorControllerChild.h b/gfx/layers/ipc/UiCompositorControllerChild.h new file mode 100644 index 0000000000..fb822eaddf --- /dev/null +++ b/gfx/layers/ipc/UiCompositorControllerChild.h @@ -0,0 +1,87 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef include_gfx_ipc_UiCompositorControllerChild_h +#define include_gfx_ipc_UiCompositorControllerChild_h + +#include "mozilla/layers/PUiCompositorControllerChild.h" + +#include "mozilla/gfx/2D.h" +#include "mozilla/Maybe.h" +#include "mozilla/ipc/Shmem.h" +#include "mozilla/layers/UiCompositorControllerParent.h" +#include "mozilla/RefPtr.h" +#include "nsThread.h" + +class nsBaseWidget; + +namespace mozilla { +namespace layers { + +class UiCompositorControllerChild final + : protected PUiCompositorControllerChild { + friend class PUiCompositorControllerChild; + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(UiCompositorControllerChild) + + static RefPtr CreateForSameProcess( + const LayersId& aRootLayerTreeId); + static RefPtr CreateForGPUProcess( + const uint64_t& aProcessToken, + Endpoint&& aEndpoint); + + bool Pause(); + bool Resume(); + bool ResumeAndResize(const int32_t& aX, const int32_t& aY, + const int32_t& aHeight, const int32_t& aWidth); + bool InvalidateAndRender(); + bool SetMaxToolbarHeight(const int32_t& aHeight); + bool SetFixedBottomOffset(int32_t aOffset); + bool ToolbarAnimatorMessageFromUI(const int32_t& aMessage); + bool SetDefaultClearColor(const uint32_t& aColor); + bool RequestScreenPixels(); + bool EnableLayerUpdateNotifications(const bool& aEnable); + + void Destroy(); + + void SetBaseWidget(nsBaseWidget* aWidget); + bool DeallocPixelBuffer(Shmem& aMem); + + protected: + void ActorDestroy(ActorDestroyReason aWhy) override; + void ActorDealloc() override; + void ProcessingError(Result aCode, const char* aReason) override; + void HandleFatalError(const char* aMsg) const override; + mozilla::ipc::IPCResult RecvToolbarAnimatorMessageFromCompositor( + const int32_t& aMessage); + mozilla::ipc::IPCResult RecvRootFrameMetrics(const ScreenPoint& aScrollOffset, + const CSSToScreenScale& aZoom); + mozilla::ipc::IPCResult RecvScreenPixels(Shmem&& aMem, + const ScreenIntSize& aSize, + bool aNeedsYFlip); + + private: + explicit UiCompositorControllerChild(const uint64_t& aProcessToken); + virtual ~UiCompositorControllerChild(); + void OpenForSameProcess(); + void OpenForGPUProcess(Endpoint&& aEndpoint); + void SendCachedValues(); + + bool mIsOpen; + uint64_t mProcessToken; + Maybe mResize; + Maybe mMaxToolbarHeight; + Maybe mDefaultClearColor; + Maybe mLayerUpdateEnabled; + RefPtr mWidget; + // Should only be set when compositor is in process. + RefPtr mParent; +}; + +} // namespace layers +} // namespace mozilla + +#endif // include_gfx_ipc_UiCompositorControllerChild_h diff --git a/gfx/layers/ipc/UiCompositorControllerMessageTypes.h b/gfx/layers/ipc/UiCompositorControllerMessageTypes.h new file mode 100644 index 0000000000..3e68e7be31 --- /dev/null +++ b/gfx/layers/ipc/UiCompositorControllerMessageTypes.h @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef include_gfx_ipc_UiCompositorControllerMessageTypes_h +#define include_gfx_ipc_UiCompositorControllerMessageTypes_h + +namespace mozilla { +namespace layers { + +// +// NOTE: These values are also defined in +// mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java +// and must be kept in sync. Any new message added here must also be added +// there. +// + +// clang-format off +enum UiCompositorControllerMessageTypes { + FIRST_PAINT = 0, // Sent from compositor after first paint + LAYERS_UPDATED = 1, // Sent from the compositor when any layer has been updated + COMPOSITOR_CONTROLLER_OPEN = 2, // Compositor controller IPC is open + IS_COMPOSITOR_CONTROLLER_OPEN = 3 // Special message sent from controller to query if the compositor controller is open +}; +// clang-format on + +} // namespace layers +} // namespace mozilla + +#endif // include_gfx_ipc_UiCompositorControllerMessageTypes_h diff --git a/gfx/layers/ipc/UiCompositorControllerParent.cpp b/gfx/layers/ipc/UiCompositorControllerParent.cpp new file mode 100644 index 0000000000..9cf60df6e1 --- /dev/null +++ b/gfx/layers/ipc/UiCompositorControllerParent.cpp @@ -0,0 +1,305 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "UiCompositorControllerParent.h" + +#if defined(MOZ_WIDGET_ANDROID) +# include "apz/src/APZCTreeManager.h" +# include "mozilla/layers/AsyncCompositionManager.h" +#endif +#include + +#include "FrameMetrics.h" +#include "SynchronousTask.h" +#include "mozilla/Unused.h" +#include "mozilla/gfx/Types.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/layers/Compositor.h" +#include "mozilla/layers/CompositorBridgeParent.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/layers/LayerManagerComposite.h" +#include "mozilla/layers/UiCompositorControllerMessageTypes.h" +#include "mozilla/layers/WebRenderBridgeParent.h" + +namespace mozilla { +namespace layers { + +typedef CompositorBridgeParent::LayerTreeState LayerTreeState; + +/* static */ +RefPtr +UiCompositorControllerParent::GetFromRootLayerTreeId( + const LayersId& aRootLayerTreeId) { + RefPtr controller; + CompositorBridgeParent::CallWithIndirectShadowTree( + aRootLayerTreeId, [&](LayerTreeState& aState) -> void { + controller = aState.mUiControllerParent; + }); + return controller; +} + +/* static */ +RefPtr UiCompositorControllerParent::Start( + const LayersId& aRootLayerTreeId, + Endpoint&& aEndpoint) { + RefPtr parent = + new UiCompositorControllerParent(aRootLayerTreeId); + + RefPtr task = + NewRunnableMethod&&>( + "layers::UiCompositorControllerParent::Open", parent, + &UiCompositorControllerParent::Open, std::move(aEndpoint)); + CompositorThread()->Dispatch(task.forget()); + + return parent; +} + +mozilla::ipc::IPCResult UiCompositorControllerParent::RecvPause() { + CompositorBridgeParent* parent = + CompositorBridgeParent::GetCompositorBridgeParentFromLayersId( + mRootLayerTreeId); + if (parent) { + parent->PauseComposition(); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult UiCompositorControllerParent::RecvResume() { + CompositorBridgeParent* parent = + CompositorBridgeParent::GetCompositorBridgeParentFromLayersId( + mRootLayerTreeId); + if (parent) { + parent->ResumeComposition(); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult UiCompositorControllerParent::RecvResumeAndResize( + const int32_t& aX, const int32_t& aY, const int32_t& aWidth, + const int32_t& aHeight) { + CompositorBridgeParent* parent = + CompositorBridgeParent::GetCompositorBridgeParentFromLayersId( + mRootLayerTreeId); + if (parent) { + // Front-end expects a first paint callback upon resume/resize. + parent->ForceIsFirstPaint(); + parent->ResumeCompositionAndResize(aX, aY, aWidth, aHeight); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult +UiCompositorControllerParent::RecvInvalidateAndRender() { + CompositorBridgeParent* parent = + CompositorBridgeParent::GetCompositorBridgeParentFromLayersId( + mRootLayerTreeId); + if (parent) { + parent->Invalidate(); + parent->ScheduleComposition(); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult UiCompositorControllerParent::RecvMaxToolbarHeight( + const int32_t& aHeight) { + mMaxToolbarHeight = aHeight; + + return IPC_OK(); +} + +mozilla::ipc::IPCResult UiCompositorControllerParent::RecvFixedBottomOffset( + const int32_t& aOffset) { +#if defined(MOZ_WIDGET_ANDROID) + CompositorBridgeParent* parent = + CompositorBridgeParent::GetCompositorBridgeParentFromLayersId( + mRootLayerTreeId); + if (parent) { + parent->SetFixedLayerMargins(0, aOffset); + } +#endif // defined(MOZ_WIDGET_ANDROID) + + return IPC_OK(); +} + +mozilla::ipc::IPCResult UiCompositorControllerParent::RecvDefaultClearColor( + const uint32_t& aColor) { + LayerTreeState* state = + CompositorBridgeParent::GetIndirectShadowTree(mRootLayerTreeId); + + if (state && state->mLayerManager) { + Compositor* compositor = state->mLayerManager->GetCompositor(); + if (compositor) { + // Android Color is ARGB which is apparently unusual. + compositor->SetDefaultClearColor( + gfx::DeviceColor::UnusualFromARGB(aColor)); + } + } else if (state && state->mWrBridge) { + state->mWrBridge->SetClearColor(gfx::DeviceColor::UnusualFromARGB(aColor)); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult +UiCompositorControllerParent::RecvRequestScreenPixels() { +#if defined(MOZ_WIDGET_ANDROID) + LayerTreeState* state = + CompositorBridgeParent::GetIndirectShadowTree(mRootLayerTreeId); + + if (state && state->mLayerManager && state->mParent) { + state->mLayerManager->RequestScreenPixels(this); + state->mParent->Invalidate(); + state->mParent->ScheduleComposition(); + } else if (state && state->mWrBridge) { + state->mWrBridge->RequestScreenPixels(this); + state->mWrBridge->ScheduleForcedGenerateFrame(); + } +#endif // defined(MOZ_WIDGET_ANDROID) + + return IPC_OK(); +} + +mozilla::ipc::IPCResult +UiCompositorControllerParent::RecvEnableLayerUpdateNotifications( + const bool& aEnable) { +#if defined(MOZ_WIDGET_ANDROID) + // Layers updates are need by Robocop test which enables them + mCompositorLayersUpdateEnabled = aEnable; +#endif // defined(MOZ_WIDGET_ANDROID) + + return IPC_OK(); +} + +void UiCompositorControllerParent::ActorDestroy(ActorDestroyReason aWhy) {} + +void UiCompositorControllerParent::ActorDealloc() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + Shutdown(); + Release(); // For AddRef in Initialize() +} + +void UiCompositorControllerParent::ToolbarAnimatorMessageFromCompositor( + int32_t aMessage) { + // This function can be call from ether compositor or controller thread. + if (!CompositorThreadHolder::IsInCompositorThread()) { + CompositorThread()->Dispatch(NewRunnableMethod( + "layers::UiCompositorControllerParent::" + "ToolbarAnimatorMessageFromCompositor", + this, + &UiCompositorControllerParent::ToolbarAnimatorMessageFromCompositor, + aMessage)); + return; + } + + Unused << SendToolbarAnimatorMessageFromCompositor(aMessage); +} + +bool UiCompositorControllerParent::AllocPixelBuffer(const int32_t aSize, + ipc::Shmem* aMem) { + MOZ_ASSERT(aSize > 0); + return AllocShmem(aSize, ipc::SharedMemory::TYPE_BASIC, aMem); +} + +void UiCompositorControllerParent::NotifyLayersUpdated() { +#ifdef MOZ_WIDGET_ANDROID + if (mCompositorLayersUpdateEnabled) { + ToolbarAnimatorMessageFromCompositor(LAYERS_UPDATED); + } +#endif +} + +void UiCompositorControllerParent::NotifyFirstPaint() { + ToolbarAnimatorMessageFromCompositor(FIRST_PAINT); +} + +void UiCompositorControllerParent::NotifyUpdateScreenMetrics( + const GeckoViewMetrics& aMetrics) { +#if defined(MOZ_WIDGET_ANDROID) + CSSToScreenScale scale = ViewTargetAs( + aMetrics.mZoom.ToScaleFactor(), + PixelCastJustification::ScreenIsParentLayerForRoot); + ScreenPoint scrollOffset = aMetrics.mVisualScrollOffset * scale; + CompositorThread()->Dispatch(NewRunnableMethod( + "UiCompositorControllerParent::SendRootFrameMetrics", this, + &UiCompositorControllerParent::SendRootFrameMetrics, scrollOffset, + scale)); +#endif +} + +UiCompositorControllerParent::UiCompositorControllerParent( + const LayersId& aRootLayerTreeId) + : mRootLayerTreeId(aRootLayerTreeId) +#ifdef MOZ_WIDGET_ANDROID + , + mCompositorLayersUpdateEnabled(false) +#endif + , + mMaxToolbarHeight(0) { + MOZ_COUNT_CTOR(UiCompositorControllerParent); +} + +UiCompositorControllerParent::~UiCompositorControllerParent() { + MOZ_COUNT_DTOR(UiCompositorControllerParent); +} + +void UiCompositorControllerParent::InitializeForSameProcess() { + // This function is called by UiCompositorControllerChild in the main thread. + // So dispatch to the compositor thread to Initialize. + if (!CompositorThreadHolder::IsInCompositorThread()) { + SynchronousTask task( + "UiCompositorControllerParent::InitializeForSameProcess"); + + CompositorThread()->Dispatch(NS_NewRunnableFunction( + "UiCompositorControllerParent::InitializeForSameProcess", [&]() { + AutoCompleteTask complete(&task); + InitializeForSameProcess(); + })); + + task.Wait(); + return; + } + + Initialize(); +} + +void UiCompositorControllerParent::InitializeForOutOfProcess() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + Initialize(); +} + +void UiCompositorControllerParent::Initialize() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + AddRef(); + LayerTreeState* state = + CompositorBridgeParent::GetIndirectShadowTree(mRootLayerTreeId); + MOZ_ASSERT(state); + MOZ_ASSERT(state->mParent); + if (!state || !state->mParent) { + return; + } + state->mUiControllerParent = this; +} + +void UiCompositorControllerParent::Open( + Endpoint&& aEndpoint) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + if (!aEndpoint.Bind(this)) { + // We can't recover from this. + MOZ_CRASH("Failed to bind UiCompositorControllerParent to endpoint"); + } + InitializeForOutOfProcess(); +} + +void UiCompositorControllerParent::Shutdown() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + LayerTreeState* state = + CompositorBridgeParent::GetIndirectShadowTree(mRootLayerTreeId); + if (state) { + state->mUiControllerParent = nullptr; + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/UiCompositorControllerParent.h b/gfx/layers/ipc/UiCompositorControllerParent.h new file mode 100644 index 0000000000..2093a92c10 --- /dev/null +++ b/gfx/layers/ipc/UiCompositorControllerParent.h @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef include_gfx_ipc_UiCompositorControllerParent_h +#define include_gfx_ipc_UiCompositorControllerParent_h + +#include "mozilla/layers/PUiCompositorControllerParent.h" +#include "mozilla/layers/APZUtils.h" +#include "mozilla/ipc/Shmem.h" +#include "mozilla/RefPtr.h" + +namespace mozilla { +namespace layers { + +struct FrameMetrics; + +class UiCompositorControllerParent final + : public PUiCompositorControllerParent { + // UiCompositorControllerChild needs to call the private constructor when + // running in process. + friend class UiCompositorControllerChild; + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(UiCompositorControllerParent) + + static RefPtr GetFromRootLayerTreeId( + const LayersId& aRootLayerTreeId); + static RefPtr Start( + const LayersId& aRootLayerTreeId, + Endpoint&& aEndpoint); + + // PUiCompositorControllerParent functions + mozilla::ipc::IPCResult RecvPause(); + mozilla::ipc::IPCResult RecvResume(); + mozilla::ipc::IPCResult RecvResumeAndResize(const int32_t& aX, + const int32_t& aY, + const int32_t& aHeight, + const int32_t& aWidth); + mozilla::ipc::IPCResult RecvInvalidateAndRender(); + mozilla::ipc::IPCResult RecvMaxToolbarHeight(const int32_t& aHeight); + mozilla::ipc::IPCResult RecvFixedBottomOffset(const int32_t& aOffset); + mozilla::ipc::IPCResult RecvDefaultClearColor(const uint32_t& aColor); + mozilla::ipc::IPCResult RecvRequestScreenPixels(); + mozilla::ipc::IPCResult RecvEnableLayerUpdateNotifications( + const bool& aEnable); + void ActorDestroy(ActorDestroyReason aWhy) override; + void ActorDealloc() override; + + // Class specific functions + void ToolbarAnimatorMessageFromCompositor(int32_t aMessage); + bool AllocPixelBuffer(const int32_t aSize, Shmem* aMem); + + // Called when a layer has been updated so the UI thread may be notified if + // necessary. + void NotifyLayersUpdated(); + void NotifyFirstPaint(); + void NotifyUpdateScreenMetrics(const GeckoViewMetrics& aMetrics); + + private: + explicit UiCompositorControllerParent(const LayersId& aRootLayerTreeId); + virtual ~UiCompositorControllerParent(); + void InitializeForSameProcess(); + void InitializeForOutOfProcess(); + void Initialize(); + void Open(Endpoint&& aEndpoint); + void Shutdown(); + + LayersId mRootLayerTreeId; + +#if defined(MOZ_WIDGET_ANDROID) + bool mCompositorLayersUpdateEnabled; // Flag set to true when the UI thread + // is expecting to be notified when a + // layer has been updated +#endif // defined(MOZ_WIDGET_ANDROID) + + int32_t mMaxToolbarHeight; +}; + +} // namespace layers +} // namespace mozilla + +#endif // include_gfx_ipc_UiCompositorControllerParent_h diff --git a/gfx/layers/ipc/VideoBridgeChild.cpp b/gfx/layers/ipc/VideoBridgeChild.cpp new file mode 100644 index 0000000000..d417442919 --- /dev/null +++ b/gfx/layers/ipc/VideoBridgeChild.cpp @@ -0,0 +1,184 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "VideoBridgeChild.h" +#include "VideoBridgeParent.h" +#include "CompositorThread.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/ipc/Endpoint.h" +#include "transport/runnable_utils.h" +#include "SynchronousTask.h" + +namespace mozilla { +namespace layers { + +StaticRefPtr sVideoBridge; + +/* static */ +void VideoBridgeChild::StartupForGPUProcess() { + ipc::Endpoint parentPipe; + ipc::Endpoint childPipe; + + PVideoBridge::CreateEndpoints(base::GetCurrentProcId(), + base::GetCurrentProcId(), &parentPipe, + &childPipe); + + VideoBridgeChild::Open(std::move(childPipe)); + VideoBridgeParent::Open(std::move(parentPipe), VideoBridgeSource::GpuProcess); +} + +void VideoBridgeChild::Open(Endpoint&& aEndpoint) { + MOZ_ASSERT(!sVideoBridge || !sVideoBridge->CanSend()); + sVideoBridge = new VideoBridgeChild(); + + if (!aEndpoint.Bind(sVideoBridge)) { + // We can't recover from this. + MOZ_CRASH("Failed to bind VideoBridgeChild to endpoint"); + } +} + +/* static */ +void VideoBridgeChild::Shutdown() { + if (sVideoBridge) { + sVideoBridge->Close(); + sVideoBridge = nullptr; + } +} + +VideoBridgeChild::VideoBridgeChild() + : mIPDLSelfRef(this), + mThread(GetCurrentSerialEventTarget()), + mCanSend(true) {} + +VideoBridgeChild::~VideoBridgeChild() = default; + +VideoBridgeChild* VideoBridgeChild::GetSingleton() { return sVideoBridge; } + +bool VideoBridgeChild::AllocUnsafeShmem( + size_t aSize, ipc::SharedMemory::SharedMemoryType aType, + ipc::Shmem* aShmem) { + if (!mThread->IsOnCurrentThread()) { + return DispatchAllocShmemInternal(aSize, aType, aShmem, + true); // true: unsafe + } + + if (!CanSend()) { + return false; + } + + return PVideoBridgeChild::AllocUnsafeShmem(aSize, aType, aShmem); +} + +bool VideoBridgeChild::AllocShmem(size_t aSize, + ipc::SharedMemory::SharedMemoryType aType, + ipc::Shmem* aShmem) { + MOZ_ASSERT(CanSend()); + return PVideoBridgeChild::AllocShmem(aSize, aType, aShmem); +} + +void VideoBridgeChild::ProxyAllocShmemNow(SynchronousTask* aTask, size_t aSize, + SharedMemory::SharedMemoryType aType, + ipc::Shmem* aShmem, bool aUnsafe, + bool* aSuccess) { + AutoCompleteTask complete(aTask); + + if (!CanSend()) { + return; + } + + bool ok = false; + if (aUnsafe) { + ok = AllocUnsafeShmem(aSize, aType, aShmem); + } else { + ok = AllocShmem(aSize, aType, aShmem); + } + *aSuccess = ok; +} + +bool VideoBridgeChild::DispatchAllocShmemInternal( + size_t aSize, SharedMemory::SharedMemoryType aType, ipc::Shmem* aShmem, + bool aUnsafe) { + SynchronousTask task("AllocatorProxy alloc"); + + bool success = false; + RefPtr runnable = WrapRunnable( + RefPtr(this), &VideoBridgeChild::ProxyAllocShmemNow, + &task, aSize, aType, aShmem, aUnsafe, &success); + GetThread()->Dispatch(runnable.forget()); + + task.Wait(); + + return success; +} + +void VideoBridgeChild::ProxyDeallocShmemNow(SynchronousTask* aTask, + ipc::Shmem* aShmem, bool* aResult) { + AutoCompleteTask complete(aTask); + + if (!CanSend()) { + return; + } + *aResult = DeallocShmem(*aShmem); +} + +bool VideoBridgeChild::DeallocShmem(ipc::Shmem& aShmem) { + if (GetThread()->IsOnCurrentThread()) { + if (!CanSend()) { + return false; + } + return PVideoBridgeChild::DeallocShmem(aShmem); + } + + SynchronousTask task("AllocatorProxy Dealloc"); + bool result = false; + + RefPtr runnable = WrapRunnable( + RefPtr(this), &VideoBridgeChild::ProxyDeallocShmemNow, + &task, &aShmem, &result); + GetThread()->Dispatch(runnable.forget()); + + task.Wait(); + return result; +} + +PTextureChild* VideoBridgeChild::AllocPTextureChild(const SurfaceDescriptor&, + const ReadLockDescriptor&, + const LayersBackend&, + const TextureFlags&, + const uint64_t& aSerial) { + MOZ_ASSERT(CanSend()); + return TextureClient::CreateIPDLActor(); +} + +bool VideoBridgeChild::DeallocPTextureChild(PTextureChild* actor) { + return TextureClient::DestroyIPDLActor(actor); +} + +void VideoBridgeChild::ActorDestroy(ActorDestroyReason aWhy) { + mCanSend = false; +} + +void VideoBridgeChild::ActorDealloc() { mIPDLSelfRef = nullptr; } + +PTextureChild* VideoBridgeChild::CreateTexture( + const SurfaceDescriptor& aSharedData, const ReadLockDescriptor& aReadLock, + LayersBackend aLayersBackend, TextureFlags aFlags, uint64_t aSerial, + wr::MaybeExternalImageId& aExternalImageId, nsISerialEventTarget* aTarget) { + MOZ_ASSERT(CanSend()); + return SendPTextureConstructor(aSharedData, aReadLock, aLayersBackend, aFlags, + aSerial); +} + +bool VideoBridgeChild::IsSameProcess() const { + return OtherPid() == base::GetCurrentProcId(); +} + +void VideoBridgeChild::HandleFatalError(const char* aMsg) const { + dom::ContentChild::FatalErrorIfNotUsingGPUProcess(aMsg, OtherPid()); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/VideoBridgeChild.h b/gfx/layers/ipc/VideoBridgeChild.h new file mode 100644 index 0000000000..9370bab04e --- /dev/null +++ b/gfx/layers/ipc/VideoBridgeChild.h @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_VIDEOBRIDGECHILD_H +#define MOZILLA_GFX_VIDEOBRIDGECHILD_H + +#include "mozilla/layers/PVideoBridgeChild.h" +#include "mozilla/layers/VideoBridgeUtils.h" +#include "ISurfaceAllocator.h" +#include "TextureForwarder.h" + +namespace mozilla { +namespace layers { + +class SynchronousTask; + +class VideoBridgeChild final : public PVideoBridgeChild, + public TextureForwarder { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VideoBridgeChild, override); + + static void StartupForGPUProcess(); + static void Shutdown(); + + static VideoBridgeChild* GetSingleton(); + + // PVideoBridgeChild + PTextureChild* AllocPTextureChild(const SurfaceDescriptor& aSharedData, + const ReadLockDescriptor& aReadLock, + const LayersBackend& aLayersBackend, + const TextureFlags& aFlags, + const uint64_t& aSerial); + bool DeallocPTextureChild(PTextureChild* actor); + + void ActorDestroy(ActorDestroyReason aWhy) override; + void ActorDealloc() override; + + // ISurfaceAllocator + bool AllocUnsafeShmem(size_t aSize, + mozilla::ipc::SharedMemory::SharedMemoryType aShmType, + mozilla::ipc::Shmem* aShmem) override; + bool AllocShmem(size_t aSize, + mozilla::ipc::SharedMemory::SharedMemoryType aShmType, + mozilla::ipc::Shmem* aShmem) override; + bool DeallocShmem(mozilla::ipc::Shmem& aShmem) override; + + // TextureForwarder + PTextureChild* CreateTexture( + const SurfaceDescriptor& aSharedData, const ReadLockDescriptor& aReadLock, + LayersBackend aLayersBackend, TextureFlags aFlags, uint64_t aSerial, + wr::MaybeExternalImageId& aExternalImageId, + nsISerialEventTarget* aTarget = nullptr) override; + + // ClientIPCAllocator + base::ProcessId GetParentPid() const override { return OtherPid(); } + nsISerialEventTarget* GetThread() const override { return mThread; } + void CancelWaitForNotifyNotUsed(uint64_t aTextureId) override { + MOZ_ASSERT(false, "NO RECYCLING HERE"); + } + + // ISurfaceAllocator + bool IsSameProcess() const override; + + bool CanSend() { return mCanSend; } + + static void Open(Endpoint&& aEndpoint); + + protected: + void HandleFatalError(const char* aMsg) const override; + bool DispatchAllocShmemInternal(size_t aSize, + SharedMemory::SharedMemoryType aType, + mozilla::ipc::Shmem* aShmem, bool aUnsafe); + void ProxyAllocShmemNow(SynchronousTask* aTask, size_t aSize, + SharedMemory::SharedMemoryType aType, + mozilla::ipc::Shmem* aShmem, bool aUnsafe, + bool* aSuccess); + void ProxyDeallocShmemNow(SynchronousTask* aTask, mozilla::ipc::Shmem* aShmem, + bool* aResult); + + private: + VideoBridgeChild(); + virtual ~VideoBridgeChild(); + + RefPtr mIPDLSelfRef; + nsCOMPtr mThread; + bool mCanSend; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/ipc/VideoBridgeParent.cpp b/gfx/layers/ipc/VideoBridgeParent.cpp new file mode 100644 index 0000000000..89acfa0601 --- /dev/null +++ b/gfx/layers/ipc/VideoBridgeParent.cpp @@ -0,0 +1,155 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "VideoBridgeParent.h" +#include "CompositorThread.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/layers/TextureHost.h" +#include "mozilla/layers/VideoBridgeUtils.h" + +namespace mozilla { +namespace layers { + +using namespace mozilla::ipc; +using namespace mozilla::gfx; + +static VideoBridgeParent* sVideoBridgeFromRddProcess; +static VideoBridgeParent* sVideoBridgeFromGpuProcess; + +VideoBridgeParent::VideoBridgeParent(VideoBridgeSource aSource) + : mCompositorThreadHolder(CompositorThreadHolder::GetSingleton()), + mClosed(false) { + mSelfRef = this; + switch (aSource) { + default: + MOZ_CRASH("Unhandled case"); + case VideoBridgeSource::RddProcess: + sVideoBridgeFromRddProcess = this; + break; + case VideoBridgeSource::GpuProcess: + sVideoBridgeFromGpuProcess = this; + break; + } +} + +VideoBridgeParent::~VideoBridgeParent() { + if (sVideoBridgeFromRddProcess == this) { + sVideoBridgeFromRddProcess = nullptr; + } + if (sVideoBridgeFromGpuProcess == this) { + sVideoBridgeFromGpuProcess = nullptr; + } +} + +/* static */ +void VideoBridgeParent::Open(Endpoint&& aEndpoint, + VideoBridgeSource aSource) { + RefPtr parent = new VideoBridgeParent(aSource); + + CompositorThread()->Dispatch( + NewRunnableMethod&&>( + "gfx::layers::VideoBridgeParent::Bind", parent, + &VideoBridgeParent::Bind, std::move(aEndpoint))); +} + +void VideoBridgeParent::Bind(Endpoint&& aEndpoint) { + if (!aEndpoint.Bind(this)) { + // We can't recover from this. + MOZ_CRASH("Failed to bind VideoBridgeParent to endpoint"); + } +} + +/* static */ +VideoBridgeParent* VideoBridgeParent::GetSingleton( + const Maybe& aSource) { + MOZ_ASSERT(aSource.isSome()); + switch (aSource.value()) { + default: + MOZ_CRASH("Unhandled case"); + case VideoBridgeSource::RddProcess: + MOZ_ASSERT(sVideoBridgeFromRddProcess); + return sVideoBridgeFromRddProcess; + case VideoBridgeSource::GpuProcess: + MOZ_ASSERT(sVideoBridgeFromGpuProcess); + return sVideoBridgeFromGpuProcess; + } +} + +TextureHost* VideoBridgeParent::LookupTexture(uint64_t aSerial) { + MOZ_DIAGNOSTIC_ASSERT(CompositorThread() && + CompositorThread()->IsOnCurrentThread()); + return TextureHost::AsTextureHost(mTextureMap[aSerial]); +} + +void VideoBridgeParent::ActorDestroy(ActorDestroyReason aWhy) { + // Can't alloc/dealloc shmems from now on. + mClosed = true; +} + +void VideoBridgeParent::ActorDealloc() { + mCompositorThreadHolder = nullptr; + mSelfRef = nullptr; +} + +PTextureParent* VideoBridgeParent::AllocPTextureParent( + const SurfaceDescriptor& aSharedData, const ReadLockDescriptor& aReadLock, + const LayersBackend& aLayersBackend, const TextureFlags& aFlags, + const uint64_t& aSerial) { + PTextureParent* parent = TextureHost::CreateIPDLActor( + this, aSharedData, aReadLock, aLayersBackend, aFlags, aSerial, Nothing()); + + if (!parent) { + return nullptr; + } + + mTextureMap[aSerial] = parent; + return parent; +} + +bool VideoBridgeParent::DeallocPTextureParent(PTextureParent* actor) { + mTextureMap.erase(TextureHost::GetTextureSerial(actor)); + return TextureHost::DestroyIPDLActor(actor); +} + +void VideoBridgeParent::SendAsyncMessage( + const nsTArray& aMessage) { + MOZ_ASSERT(false, "AsyncMessages not supported"); +} + +bool VideoBridgeParent::AllocShmem(size_t aSize, + ipc::SharedMemory::SharedMemoryType aType, + ipc::Shmem* aShmem) { + if (mClosed) { + return false; + } + return PVideoBridgeParent::AllocShmem(aSize, aType, aShmem); +} + +bool VideoBridgeParent::AllocUnsafeShmem( + size_t aSize, ipc::SharedMemory::SharedMemoryType aType, + ipc::Shmem* aShmem) { + if (mClosed) { + return false; + } + return PVideoBridgeParent::AllocUnsafeShmem(aSize, aType, aShmem); +} + +bool VideoBridgeParent::DeallocShmem(ipc::Shmem& aShmem) { + if (mClosed) { + return false; + } + return PVideoBridgeParent::DeallocShmem(aShmem); +} + +bool VideoBridgeParent::IsSameProcess() const { + return OtherPid() == base::GetCurrentProcId(); +} + +void VideoBridgeParent::NotifyNotUsed(PTextureParent* aTexture, + uint64_t aTransactionId) {} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/ipc/VideoBridgeParent.h b/gfx/layers/ipc/VideoBridgeParent.h new file mode 100644 index 0000000000..855c018df3 --- /dev/null +++ b/gfx/layers/ipc/VideoBridgeParent.h @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef gfx_layers_ipc_VideoBridgeParent_h_ +#define gfx_layers_ipc_VideoBridgeParent_h_ + +#include "mozilla/layers/ISurfaceAllocator.h" +#include "mozilla/layers/PVideoBridgeParent.h" + +namespace mozilla { +namespace layers { + +enum class VideoBridgeSource : uint8_t; +class CompositorThreadHolder; + +class VideoBridgeParent final : public PVideoBridgeParent, + public HostIPCAllocator, + public mozilla::ipc::IShmemAllocator { + public: + ~VideoBridgeParent(); + + static VideoBridgeParent* GetSingleton( + const Maybe& aSource); + + static void Open(Endpoint&& aEndpoint, + VideoBridgeSource aSource); + + TextureHost* LookupTexture(uint64_t aSerial); + + // PVideoBridgeParent + void ActorDestroy(ActorDestroyReason aWhy) override; + PTextureParent* AllocPTextureParent(const SurfaceDescriptor& aSharedData, + const ReadLockDescriptor& aReadLock, + const LayersBackend& aLayersBackend, + const TextureFlags& aFlags, + const uint64_t& aSerial); + bool DeallocPTextureParent(PTextureParent* actor); + + // HostIPCAllocator + base::ProcessId GetChildProcessId() override { return OtherPid(); } + void NotifyNotUsed(PTextureParent* aTexture, + uint64_t aTransactionId) override; + void SendAsyncMessage( + const nsTArray& aMessage) override; + + // ISurfaceAllocator + IShmemAllocator* AsShmemAllocator() override { return this; } + bool IsSameProcess() const override; + bool IPCOpen() const override { return !mClosed; } + + // IShmemAllocator + bool AllocShmem(size_t aSize, ipc::SharedMemory::SharedMemoryType aType, + ipc::Shmem* aShmem) override; + + bool AllocUnsafeShmem(size_t aSize, ipc::SharedMemory::SharedMemoryType aType, + ipc::Shmem* aShmem) override; + + bool DeallocShmem(ipc::Shmem& aShmem) override; + + private: + explicit VideoBridgeParent(VideoBridgeSource aSource); + void Bind(Endpoint&& aEndpoint); + + void ActorDealloc() override; + + // This keeps us alive until ActorDestroy(), at which point we do a + // deferred destruction of ourselves. + RefPtr mSelfRef; + RefPtr mCompositorThreadHolder; + + std::map mTextureMap; + + bool mClosed; +}; + +} // namespace layers +} // namespace mozilla + +#endif // gfx_layers_ipc_VideoBridgeParent_h_ diff --git a/gfx/layers/ipc/VideoBridgeUtils.h b/gfx/layers/ipc/VideoBridgeUtils.h new file mode 100644 index 0000000000..e8e7090063 --- /dev/null +++ b/gfx/layers/ipc/VideoBridgeUtils.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef IPC_VideoBridgeUtils_h +#define IPC_VideoBridgeUtils_h + +#include "ipc/EnumSerializer.h" + +namespace mozilla { +namespace layers { + +enum class VideoBridgeSource : uint8_t { + RddProcess, + GpuProcess, + _Count, +}; + +typedef Maybe MaybeVideoBridgeSource; + +} // namespace layers +} // namespace mozilla + +namespace IPC { + +template <> +struct ParamTraits + : public ContiguousEnumSerializer< + mozilla::layers::VideoBridgeSource, + mozilla::layers::VideoBridgeSource::RddProcess, + mozilla::layers::VideoBridgeSource::_Count> {}; + +} // namespace IPC + +#endif // IPC_VideoBridgeUtils_h diff --git a/gfx/layers/ipc/WebRenderMessages.ipdlh b/gfx/layers/ipc/WebRenderMessages.ipdlh new file mode 100644 index 0000000000..dda247b9e0 --- /dev/null +++ b/gfx/layers/ipc/WebRenderMessages.ipdlh @@ -0,0 +1,213 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include "mozilla/layers/LayersMessageUtils.h"; +include "mozilla/layers/WebRenderMessageUtils.h"; + +include LayersSurfaces; +include LayersMessages; +include protocol PTexture; + +using mozilla::layers::CompositableHandle from "mozilla/layers/LayersTypes.h"; +using mozilla::wr::LayoutSize from "mozilla/webrender/webrender_ffi.h"; +using mozilla::wr::ImageDescriptor from "mozilla/webrender/webrender_ffi.h"; +using mozilla::wr::ImageRendering from "mozilla/webrender/webrender_ffi.h"; +using mozilla::wr::MixBlendMode from "mozilla/webrender/webrender_ffi.h"; +using mozilla::wr::ExternalImageId from "mozilla/webrender/WebRenderTypes.h"; +using mozilla::wr::MaybeFontInstanceOptions from "mozilla/webrender/WebRenderTypes.h"; +using mozilla::wr::MaybeFontInstancePlatformOptions from "mozilla/webrender/WebRenderTypes.h"; +using mozilla::wr::FontInstanceKey from "mozilla/webrender/WebRenderTypes.h"; +using mozilla::wr::FontKey from "mozilla/webrender/WebRenderTypes.h"; +using mozilla::wr::ImageKey from "mozilla/webrender/WebRenderTypes.h"; +using mozilla::wr::BlobImageKey from "mozilla/webrender/WebRenderTypes.h"; +using mozilla::wr::PipelineId from "mozilla/webrender/WebRenderTypes.h"; +using mozilla::gfx::MaybeIntSize from "mozilla/gfx/Point.h"; +using mozilla::LayoutDeviceRect from "Units.h"; +using mozilla::LayoutDeviceSize from "Units.h"; +using mozilla::ImageIntRect from "Units.h"; +using mozilla::gfx::Rect from "mozilla/gfx/Rect.h"; +using mozilla::VideoInfo::Rotation from "MediaInfo.h"; +using class mozilla::gfx::Matrix4x4 from "mozilla/gfx/Matrix.h"; +using struct mozilla::void_t from "mozilla/ipc/IPCCore.h"; + +namespace mozilla { +namespace layers { + +struct RefCountedShmem { + Shmem buffer; +}; + +struct OpAddPrivateExternalImage { + ExternalImageId externalImageId; + ImageKey key; + ImageDescriptor descriptor; +}; + +struct OpAddSharedExternalImage { + ExternalImageId externalImageId; + ImageKey key; +}; + +struct OpPushExternalImageForTexture { + ExternalImageId externalImageId; + ImageKey key; + PTexture texture; + bool isUpdate; +}; + +struct OpAddCompositorAnimations { + CompositorAnimations data; +}; + +struct OpAddPipelineIdForCompositable { + PipelineId pipelineId; + CompositableHandle handle; + bool isAsync; +}; + +struct OpRemovePipelineIdForCompositable { + PipelineId pipelineId; +}; + +struct OpReleaseTextureOfImage { + ImageKey key; +}; + +struct OpUpdateAsyncImagePipeline { + PipelineId pipelineId; + LayoutDeviceRect scBounds; + Rotation rotation; + ImageRendering filter; + MixBlendMode mixBlendMode; +}; + +struct OpUpdatedAsyncImagePipeline { + PipelineId pipelineId; +}; + +union WebRenderParentCommand { + OpAddPipelineIdForCompositable; + OpRemovePipelineIdForCompositable; + OpReleaseTextureOfImage; + OpUpdateAsyncImagePipeline; + OpUpdatedAsyncImagePipeline; + CompositableOperation; + OpAddCompositorAnimations; +}; + +struct OffsetRange { + uint32_t source; + uint32_t start; + uint32_t length; +}; + +struct OpAddImage { + ImageDescriptor descriptor; + OffsetRange bytes; + uint16_t tiling; + ImageKey key; +}; + +struct OpAddBlobImage { + ImageDescriptor descriptor; + OffsetRange bytes; + ImageIntRect visibleRect; + uint16_t tiling; + BlobImageKey key; +}; + +struct OpUpdateImage { + ImageDescriptor descriptor; + OffsetRange bytes; + ImageKey key; +}; + +struct OpUpdateBlobImage { + ImageDescriptor descriptor; + OffsetRange bytes; + BlobImageKey key; + ImageIntRect visibleRect; + ImageIntRect dirtyRect; +}; + +struct OpSetBlobImageVisibleArea { + ImageIntRect area; + BlobImageKey key; +}; + +struct OpUpdatePrivateExternalImage { + ExternalImageId externalImageId; + ImageKey key; + ImageDescriptor descriptor; + ImageIntRect dirtyRect; +}; + +struct OpUpdateSharedExternalImage { + ExternalImageId externalImageId; + ImageKey key; + ImageIntRect dirtyRect; +}; + +struct OpDeleteImage { + ImageKey key; +}; + +struct OpDeleteBlobImage { + BlobImageKey key; +}; + +struct OpAddRawFont { + OffsetRange bytes; + uint32_t fontIndex; + FontKey key; +}; + +struct OpAddFontDescriptor { + OffsetRange bytes; + uint32_t fontIndex; + FontKey key; +}; + +struct OpDeleteFont { + FontKey key; +}; + +struct OpAddFontInstance { + MaybeFontInstanceOptions options; + MaybeFontInstancePlatformOptions platformOptions; + OffsetRange variations; + FontInstanceKey instanceKey; + FontKey fontKey; + float glyphSize; +}; + +struct OpDeleteFontInstance { + FontInstanceKey key; +}; + +union OpUpdateResource { + OpAddImage; + OpAddBlobImage; + OpUpdateImage; + OpUpdateBlobImage; + OpSetBlobImageVisibleArea; + OpDeleteImage; + OpDeleteBlobImage; + OpAddRawFont; + OpAddFontDescriptor; + OpDeleteFont; + OpAddFontInstance; + OpDeleteFontInstance; + OpAddPrivateExternalImage; + OpAddSharedExternalImage; + OpPushExternalImageForTexture; + OpUpdatePrivateExternalImage; + OpUpdateSharedExternalImage; +}; + +} // namespace +} // namespace diff --git a/gfx/layers/ipc/fuzztest/compositor_manager_parent_ipc_libfuzz.cpp b/gfx/layers/ipc/fuzztest/compositor_manager_parent_ipc_libfuzz.cpp new file mode 100644 index 0000000000..6e76035031 --- /dev/null +++ b/gfx/layers/ipc/fuzztest/compositor_manager_parent_ipc_libfuzz.cpp @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" + +#include "FuzzingInterface.h" +#include "ProtocolFuzzer.h" + +#include "mozilla/layers/CompositorBridgeParent.h" +#include "mozilla/layers/CompositorManagerParent.h" +#include "mozilla/layers/LayerTreeOwnerTracker.h" + +int FuzzingInitCompositorManagerParentIPC(int* argc, char*** argv) { + mozilla::ipc::ProtocolFuzzerHelper::CompositorBridgeParentSetup(); + mozilla::layers::LayerTreeOwnerTracker::Initialize(); + return 0; +} + +static int RunCompositorManagerParentIPCFuzzing(const uint8_t* data, + size_t size) { + static mozilla::layers::CompositorManagerParent* p = + mozilla::layers::CompositorManagerParent::CreateSameProcess().take(); + + static nsTArray ignored = mozilla::ipc::LoadIPCMessageBlacklist( + getenv("MOZ_IPC_MESSAGE_FUZZ_BLACKLIST")); + + mozilla::ipc::FuzzProtocol(p, data, size, ignored); + + return 0; +} + +MOZ_FUZZING_INTERFACE_RAW(FuzzingInitCompositorManagerParentIPC, + RunCompositorManagerParentIPCFuzzing, + CompositorManagerParentIPC); diff --git a/gfx/layers/ipc/fuzztest/moz.build b/gfx/layers/ipc/fuzztest/moz.build new file mode 100644 index 0000000000..a60293a520 --- /dev/null +++ b/gfx/layers/ipc/fuzztest/moz.build @@ -0,0 +1,16 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Library("FuzzingCompositorManagerParentIPC") + +SOURCES += ["compositor_manager_parent_ipc_libfuzz.cpp"] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul-gtest" + +# Add libFuzzer configuration directives +include("/tools/fuzzing/libfuzzer-config.mozbuild") diff --git a/gfx/layers/layerviewer/hide.png b/gfx/layers/layerviewer/hide.png new file mode 100644 index 0000000000..9a92e2c1b1 Binary files /dev/null and b/gfx/layers/layerviewer/hide.png differ diff --git a/gfx/layers/layerviewer/index.html b/gfx/layers/layerviewer/index.html new file mode 100644 index 0000000000..000e40b8bd --- /dev/null +++ b/gfx/layers/layerviewer/index.html @@ -0,0 +1,51 @@ + + + + + + GFX Display List & Layer Visualizer + + + + + + +

    GFX Layers dump visualizer:

    + Paste your display list or layers dump into this textarea:
    + +
    + +
    +
    + Help: To get a layers dump go to about:config and set layout.display-list.dump;true or layers.dump;true. + + + diff --git a/gfx/layers/layerviewer/layerTreeView.js b/gfx/layers/layerviewer/layerTreeView.js new file mode 100644 index 0000000000..5cf21c71b4 --- /dev/null +++ b/gfx/layers/layerviewer/layerTreeView.js @@ -0,0 +1,972 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function toFixed(num, fixed) { + fixed = fixed || 0; + fixed = Math.pow(10, fixed); + return Math.floor(num * fixed) / fixed; +} +function createElement(name, props) { + var el = document.createElement(name); + + for (var key in props) { + if (key === "style") { + for (var styleName in props.style) { + el.style[styleName] = props.style[styleName]; + } + } else { + el[key] = props[key]; + } + } + + return el; +} + +function parseDisplayList(lines) { + var root = { + line: "DisplayListRoot 0", + name: "DisplayListRoot", + address: "0x0", + frame: "Root", + children: [], + }; + + var objectAtIndentation = { + "-1": root, + }; + + for (var i = 0; i < lines.length; i++) { + var line = lines[i]; + + var layerObject = { + line, + children: [], + }; + if (!root) { + root = layerObject; + } + + var matches = line.match( + "(\\s*)(\\w+)\\sp=(\\w+)\\sf=(.*?)\\((.*?)\\)\\s(z=(\\w+)\\s)?(.*?)?( layer=(\\w+))?$" + ); + if (!matches) { + dump("Failed to match: " + line + "\n"); + continue; + } + + var indentation = Math.floor(matches[1].length / 2); + objectAtIndentation[indentation] = layerObject; + var parent = objectAtIndentation[indentation - 1]; + if (parent) { + parent.children.push(layerObject); + } + + layerObject.name = matches[2]; + layerObject.address = matches[3]; // Use 0x prefix to be consistent with layer dump + layerObject.frame = matches[4]; + layerObject.contentDescriptor = matches[5]; + layerObject.z = matches[7]; + var rest = matches[8]; + if (matches[10]) { + // WrapList don't provide a layer + layerObject.layer = matches[10]; + } + layerObject.rest = rest; + + // the content node name doesn't have a prefix, this makes the parsing easier + rest = "content" + rest; + + var nesting = 0; + var startIndex; + var lastSpace = -1; + for (var j = 0; j < rest.length; j++) { + if (rest.charAt(j) == "(") { + nesting++; + if (nesting == 1) { + startIndex = j; + } + } else if (rest.charAt(j) == ")") { + nesting--; + if (nesting == 0) { + var name = rest.substring(lastSpace + 1, startIndex); + var value = rest.substring(startIndex + 1, j); + + var rectMatches = value.match("^(.*?),(.*?),(.*?),(.*?)$"); + if (rectMatches) { + layerObject[name] = [ + parseFloat(rectMatches[1]), + parseFloat(rectMatches[2]), + parseFloat(rectMatches[3]), + parseFloat(rectMatches[4]), + ]; + } else { + layerObject[name] = value; + } + } + } else if (nesting == 0 && rest.charAt(j) == " ") { + lastSpace = j; + } + } + // dump("FIELDS: " + JSON.stringify(fields) + "\n"); + } + return root; +} + +function trim(s) { + return (s || "").replace(/^\s+|\s+$/g, ""); +} + +function getDataURI(str) { + if (str.indexOf("data:image/png;base64,") == 0) { + return str; + } + + var matches = str.match( + "data:image/lz4bgra;base64,([0-9]+),([0-9]+),([0-9]+),(.*)" + ); + if (!matches) { + return null; + } + + var canvas = document.createElement("canvas"); + var w = parseInt(matches[1]); + var stride = parseInt(matches[2]); + var h = parseInt(matches[3]); + canvas.width = w; + canvas.height = h; + + // TODO handle stride + + var binary_string = window.atob(matches[4]); + var len = binary_string.length; + var bytes = new Uint8Array(len); + var decoded = new Uint8Array(stride * h); + for (var i = 0; i < len; i++) { + var ascii = binary_string.charCodeAt(i); + bytes[i] = ascii; + } + + var ctxt = canvas.getContext("2d"); + var out = ctxt.createImageData(w, h); + // This is actually undefined throughout the tree and it isn't clear what it + // should be. Since this is only development code, leave it alone for now. + // eslint-disable-next-line no-undef + LZ4_uncompressChunk(bytes, decoded); + + for (var x = 0; x < w; x++) { + for (var y = 0; y < h; y++) { + out.data[4 * x + 4 * y * w + 0] = decoded[4 * x + y * stride + 2]; + out.data[4 * x + 4 * y * w + 1] = decoded[4 * x + y * stride + 1]; + out.data[4 * x + 4 * y * w + 2] = decoded[4 * x + y * stride + 0]; + out.data[4 * x + 4 * y * w + 3] = decoded[4 * x + y * stride + 3]; + } + } + + ctxt.putImageData(out, 0, 0); + return canvas.toDataURL(); +} + +function parseLayers(layersDumpLines) { + function parseMatrix2x3(str) { + str = trim(str); + + // Something like '[ 1 0; 0 1; 0 158; ]' + var matches = str.match("^\\[ (.*?) (.*?); (.*?) (.*?); (.*?) (.*?); \\]$"); + if (!matches) { + return null; + } + + var matrix = [ + [parseFloat(matches[1]), parseFloat(matches[2])], + [parseFloat(matches[3]), parseFloat(matches[4])], + [parseFloat(matches[5]), parseFloat(matches[6])], + ]; + + return matrix; + } + function parseColor(str) { + str = trim(str); + + // Something like 'rgba(0, 0, 0, 0)' + var colorMatches = str.match("^rgba\\((.*), (.*), (.*), (.*)\\)$"); + if (!colorMatches) { + return null; + } + + var color = { + r: colorMatches[1], + g: colorMatches[2], + b: colorMatches[3], + a: colorMatches[4], + }; + return color; + } + function parseFloat_cleo(str) { + str = trim(str); + + // Something like 2.000 + if (parseFloat(str) == str) { + return parseFloat(str); + } + + return null; + } + function parseRect2D(str) { + str = trim(str); + + // Something like '(x=0, y=0, w=2842, h=158)' + var rectMatches = str.match("^\\(x=(.*?), y=(.*?), w=(.*?), h=(.*?)\\)$"); + if (!rectMatches) { + return null; + } + + var rect = [ + parseFloat(rectMatches[1]), + parseFloat(rectMatches[2]), + parseFloat(rectMatches[3]), + parseFloat(rectMatches[4]), + ]; + return rect; + } + function parseRegion(str) { + str = trim(str); + + // Something like '< (x=0, y=0, w=2842, h=158); (x=0, y=1718, w=2842, h=500); >' + if (str.charAt(0) != "<" || str.charAt(str.length - 1) != ">") { + return null; + } + + var region = []; + str = trim(str.substring(1, str.length - 1)); + while (str != "") { + var rectMatches = str.match( + "^\\(x=(.*?), y=(.*?), w=(.*?), h=(.*?)\\);(.*)$" + ); + if (!rectMatches) { + return null; + } + + var rect = [ + parseFloat(rectMatches[1]), + parseFloat(rectMatches[2]), + parseFloat(rectMatches[3]), + parseFloat(rectMatches[4]), + ]; + str = trim(rectMatches[5]); + region.push(rect); + } + return region; + } + + var LAYERS_LINE_REGEX = "(\\s*)(\\w+)\\s\\((\\w+)\\)(.*)"; + + var root; + var objectAtIndentation = []; + for (var i = 0; i < layersDumpLines.length; i++) { + // Something like 'ThebesLayerComposite (0x12104cc00) [shadow-visible=< (x=0, y=0, w=1920, h=158); >] [visible=< (x=0, y=0, w=1920, h=158); >] [opaqueContent] [valid=< (x=0, y=0, w=1920, h=2218); >]' + var line = layersDumpLines[i].name || layersDumpLines[i]; + + var tileMatches = line.match("(\\s*)Tile \\(x=(.*), y=(.*)\\): (.*)"); + if (tileMatches) { + let indentation = Math.floor(matches[1].length / 2); + var x = tileMatches[2]; + var y = tileMatches[3]; + var dataUri = tileMatches[4]; + let parent = objectAtIndentation[indentation - 1]; + var tiles = parent.tiles || {}; + + tiles[x] = tiles[x] || {}; + tiles[x][y] = dataUri; + + parent.tiles = tiles; + + continue; + } + + var surfaceMatches = line.match("(\\s*)Surface: (.*)"); + if (surfaceMatches) { + let indentation = Math.floor(matches[1].length / 2); + let parent = + objectAtIndentation[indentation - 1] || + objectAtIndentation[indentation - 2]; + + var surfaceURI = surfaceMatches[2]; + if (parent.surfaceURI != null) { + console.log( + "error: surfaceURI already set for this layer " + parent.line + ); + } + parent.surfaceURI = surfaceURI; + + // Look for the buffer-rect offset + var contentHostLine = + layersDumpLines[i - 2].name || layersDumpLines[i - 2]; + let matches = contentHostLine.match(LAYERS_LINE_REGEX); + if (matches) { + var contentHostRest = matches[4]; + parent.contentHostProp = {}; + parseProperties(contentHostRest, parent.contentHostProp); + } + + continue; + } + + var layerObject = { + line, + children: [], + }; + if (!root) { + root = layerObject; + } + + let matches = line.match(LAYERS_LINE_REGEX); + if (!matches) { + continue; // Something like a texturehost dump. Safe to ignore + } + + if ( + matches[2].includes("TiledContentHost") || + matches[2].includes("ContentHost") || + matches[2].includes("ContentClient") || + matches[2].includes("MemoryTextureHost") || + matches[2].includes("ImageHost") + ) { + continue; // We're already pretty good at visualizing these + } + + var indentation = Math.floor(matches[1].length / 2); + objectAtIndentation[indentation] = layerObject; + for (var c = indentation + 1; c < objectAtIndentation.length; c++) { + objectAtIndentation[c] = null; + } + if (indentation > 0) { + var parent = objectAtIndentation[indentation - 1]; + while (!parent) { + indentation--; + parent = objectAtIndentation[indentation - 1]; + } + + parent.children.push(layerObject); + } + + layerObject.name = matches[2]; + layerObject.address = matches[3]; + + var rest = matches[4]; + + function parseProperties(rest, layerObject) { + var fields = []; + var nesting = 0; + var startIndex; + for (let j = 0; j < rest.length; j++) { + if (rest.charAt(j) == "[") { + nesting++; + if (nesting == 1) { + startIndex = j; + } + } else if (rest.charAt(j) == "]") { + nesting--; + if (nesting == 0) { + fields.push(rest.substring(startIndex + 1, j)); + } + } + } + + for (let j = 0; j < fields.length; j++) { + // Something like 'valid=< (x=0, y=0, w=1920, h=2218); >' or 'opaqueContent' + var field = fields[j]; + // dump("FIELD: " + field + "\n"); + var parts = field.split("=", 2); + var fieldName = parts[0]; + rest = field.substring(fieldName.length + 1); + if (parts.length == 1) { + layerObject[fieldName] = "true"; + layerObject[fieldName].type = "bool"; + continue; + } + var float = parseFloat_cleo(rest); + if (float) { + layerObject[fieldName] = float; + layerObject[fieldName].type = "float"; + continue; + } + var region = parseRegion(rest); + if (region) { + layerObject[fieldName] = region; + layerObject[fieldName].type = "region"; + continue; + } + var rect = parseRect2D(rest); + if (rect) { + layerObject[fieldName] = rect; + layerObject[fieldName].type = "rect2d"; + continue; + } + var matrix = parseMatrix2x3(rest); + if (matrix) { + layerObject[fieldName] = matrix; + layerObject[fieldName].type = "matrix2x3"; + continue; + } + var color = parseColor(rest); + if (color) { + layerObject[fieldName] = color; + layerObject[fieldName].type = "color"; + continue; + } + if (rest[0] == "{" && rest[rest.length - 1] == "}") { + var object = {}; + parseProperties(rest.substring(1, rest.length - 2).trim(), object); + layerObject[fieldName] = object; + layerObject[fieldName].type = "object"; + continue; + } + fieldName = fieldName.split(" ")[0]; + layerObject[fieldName] = rest[0]; + layerObject[fieldName].type = "string"; + } + } + parseProperties(rest, layerObject); + + if (!layerObject["shadow-transform"]) { + // No shadow transform = identify + layerObject["shadow-transform"] = [ + [1, 0], + [0, 1], + [0, 0], + ]; + } + + // Compute screenTransformX/screenTransformY + // TODO Fully support transforms + if (layerObject["shadow-transform"] && layerObject.transform) { + layerObject["screen-transform"] = [ + layerObject["shadow-transform"][2][0], + layerObject["shadow-transform"][2][1], + ]; + var currIndentation = indentation - 1; + while (currIndentation >= 0) { + var transform = + objectAtIndentation[currIndentation]["shadow-transform"] || + objectAtIndentation[currIndentation].transform; + if (transform) { + layerObject["screen-transform"][0] += transform[2][0]; + layerObject["screen-transform"][1] += transform[2][1]; + } + currIndentation--; + } + } + + // dump("Fields: " + JSON.stringify(fields) + "\n"); + } + root.compositeTime = layersDumpLines.compositeTime; + // dump("OBJECTS: " + JSON.stringify(root) + "\n"); + return root; +} +function populateLayers( + root, + displayList, + pane, + previewParent, + hasSeenRoot, + contentScale, + rootPreviewParent +) { + contentScale = contentScale || 1; + rootPreviewParent = rootPreviewParent || previewParent; + + function getDisplayItemForLayer(displayList) { + var items = []; + if (!displayList) { + return items; + } + if (displayList.layer == root.address) { + items.push(displayList); + } + for (var i = 0; i < displayList.children.length; i++) { + var subDisplayItems = getDisplayItemForLayer(displayList.children[i]); + for (let j = 0; j < subDisplayItems.length; j++) { + items.push(subDisplayItems[j]); + } + } + return items; + } + var elem = createElement("div", { + className: "layerObjectDescription", + textContent: root.line, + style: { + whiteSpace: "pre", + }, + onmouseover() { + if (this.layerViewport) { + this.layerViewport.classList.add("layerHover"); + } + }, + onmouseout() { + if (this.layerViewport) { + this.layerViewport.classList.remove("layerHover"); + } + }, + }); + var icon = createElement("img", { + src: "show.png", + style: { + width: "12px", + height: "12px", + marginLeft: "4px", + marginRight: "4px", + cursor: "pointer", + }, + onclick() { + if (this.layerViewport) { + if (this.layerViewport.style.visibility == "hidden") { + this.layerViewport.style.visibility = ""; + this.src = "show.png"; + } else { + this.layerViewport.style.visibility = "hidden"; + this.src = "hide.png"; + } + } + }, + }); + elem.insertBefore(icon, elem.firstChild); + pane.appendChild(elem); + + if (root["shadow-visible"] || root.visible) { + var visibleRegion = root["shadow-visible"] || root.visible; + var layerViewport = createElement("div", { + id: root.address + "_viewport", + style: { + position: "absolute", + pointerEvents: "none", + }, + }); + elem.layerViewport = layerViewport; + icon.layerViewport = layerViewport; + var layerViewportMatrix = [1, 0, 0, 1, 0, 0]; + if (root["shadow-clip"] || root.clip) { + var clip = root["shadow-clip"] || root.clip; + var clipElem = createElement("div", { + id: root.address + "_clip", + style: { + left: clip[0] + "px", + top: clip[1] + "px", + width: clip[2] + "px", + height: clip[3] + "px", + position: "absolute", + overflow: "hidden", + pointerEvents: "none", + }, + }); + layerViewportMatrix[4] += -clip[0]; + layerViewportMatrix[5] += -clip[1]; + layerViewport.style.transform = + "translate(-" + clip[0] + "px, -" + clip[1] + "px)"; + } + if (root["shadow-transform"] || root.transform) { + var matrix = root["shadow-transform"] || root.transform; + layerViewportMatrix[0] = matrix[0][0]; + layerViewportMatrix[1] = matrix[0][1]; + layerViewportMatrix[2] = matrix[1][0]; + layerViewportMatrix[3] = matrix[1][1]; + layerViewportMatrix[4] += matrix[2][0]; + layerViewportMatrix[5] += matrix[2][1]; + } + layerViewport.style.transform = + "matrix(" + + layerViewportMatrix[0] + + "," + + layerViewportMatrix[1] + + "," + + layerViewportMatrix[2] + + "," + + layerViewportMatrix[3] + + "," + + layerViewportMatrix[4] + + "," + + layerViewportMatrix[5] + + ")"; + if (!hasSeenRoot) { + hasSeenRoot = true; + layerViewport.style.transform = + "scale(" + 1 / contentScale + "," + 1 / contentScale + ")"; + } + if (clipElem) { + previewParent.appendChild(clipElem); + clipElem.appendChild(layerViewport); + } else { + previewParent.appendChild(layerViewport); + } + previewParent = layerViewport; + for (let i = 0; i < visibleRegion.length; i++) { + let rect2d = visibleRegion[i]; + var layerPreview = createElement("div", { + id: root.address + "_visible_part" + i + "-" + visibleRegion.length, + className: "layerPreview", + style: { + position: "absolute", + left: rect2d[0] + "px", + top: rect2d[1] + "px", + width: rect2d[2] + "px", + height: rect2d[3] + "px", + overflow: "hidden", + border: "solid 1px black", + background: + 'url("noise.png"), linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.2))', + }, + }); + layerViewport.appendChild(layerPreview); + + function isInside(rect1, rect2) { + if ( + rect1[0] + rect1[2] < rect2[0] && + rect2[0] + rect2[2] < rect1[0] && + rect1[1] + rect1[3] < rect2[1] && + rect2[1] + rect2[3] < rect1[1] + ) { + return true; + } + return true; + } + + var hasImg = false; + // Add tile img objects for this part + var previewOffset = rect2d; + + if (root.tiles) { + hasImg = true; + for (var x in root.tiles) { + for (var y in root.tiles[x]) { + if (isInside(rect2d, [x, y, 512, 512])) { + var tileImgElem = createElement("img", { + src: getDataURI(root.tiles[x][y]), + style: { + position: "absolute", + left: x - previewOffset[0] + "px", + top: y - previewOffset[1] + "px", + pointerEvents: "auto", + }, + }); + layerPreview.appendChild(tileImgElem); + } + } + } + layerPreview.style.background = ""; + } else if (root.surfaceURI) { + hasImg = true; + var offsetX = 0; + var offsetY = 0; + if (root.contentHostProp && root.contentHostProp["buffer-rect"]) { + offsetX = root.contentHostProp["buffer-rect"][0]; + offsetY = root.contentHostProp["buffer-rect"][1]; + } + var surfaceImgElem = createElement("img", { + src: getDataURI(root.surfaceURI), + style: { + position: "absolute", + left: offsetX - previewOffset[0] + "px", + top: offsetY - previewOffset[1] + "px", + pointerEvents: "auto", + }, + }); + layerPreview.appendChild(surfaceImgElem); + layerPreview.style.background = ""; + } else if (root.color) { + hasImg = true; + layerPreview.style.background = + "rgba(" + + root.color.r + + ", " + + root.color.g + + ", " + + root.color.b + + ", " + + root.color.a + + ")"; + } + + if (hasImg || true) { + layerPreview.mouseoverElem = elem; + layerPreview.onmouseenter = function() { + this.mouseoverElem.onmouseover(); + }; + layerPreview.onmouseout = function() { + this.mouseoverElem.onmouseout(); + }; + } + } + + var layerDisplayItems = getDisplayItemForLayer(displayList); + for (let i = 0; i < layerDisplayItems.length; i++) { + var displayItem = layerDisplayItems[i]; + var displayElem = createElement("div", { + className: "layerObjectDescription", + textContent: " " + trim(displayItem.line), + style: { + whiteSpace: "pre", + }, + displayItem, + layerViewport, + onmouseover() { + if (this.diPreview) { + this.diPreview.classList.add("displayHover"); + + var description = ""; + if (this.displayItem.contentDescriptor) { + description += "Content: " + this.displayItem.contentDescriptor; + } else { + description += "Content: Unknown"; + } + description += + "
    Item: " + + this.displayItem.name + + " (" + + this.displayItem.address + + ")"; + description += + "
    Layer: " + root.name + " (" + root.address + ")"; + if (this.displayItem.frame) { + description += "
    Frame: " + this.displayItem.frame; + } + if (this.displayItem.layerBounds) { + description += + "
    Bounds: [" + + toFixed(this.displayItem.layerBounds[0] / 60, 2) + + ", " + + toFixed(this.displayItem.layerBounds[1] / 60, 2) + + ", " + + toFixed(this.displayItem.layerBounds[2] / 60, 2) + + ", " + + toFixed(this.displayItem.layerBounds[3] / 60, 2) + + "] (CSS Pixels)"; + } + if (this.displayItem.z) { + description += "
    Z: " + this.displayItem.z; + } + // At the end + if (this.displayItem.rest) { + description += "
    " + this.displayItem.rest; + } + + var box = this.diPreview.getBoundingClientRect(); + this.diPreview.tooltip = createElement("div", { + className: "csstooltip", + innerHTML: description, + style: { + top: + Math.min( + box.bottom, + document.documentElement.clientHeight - 150 + ) + "px", + left: box.left + "px", + }, + }); + + document.body.appendChild(this.diPreview.tooltip); + } + }, + onmouseout() { + if (this.diPreview) { + this.diPreview.classList.remove("displayHover"); + document.body.removeChild(this.diPreview.tooltip); + } + }, + }); + + icon = createElement("img", { + style: { + width: "12px", + height: "12px", + marginLeft: "4px", + marginRight: "4px", + }, + }); + displayElem.insertBefore(icon, displayElem.firstChild); + pane.appendChild(displayElem); + // bounds doesn't adjust for within the layer. It's not a bad fallback but + // will have the wrong offset + let rect2d = displayItem.layerBounds || displayItem.bounds; + if (rect2d) { + // This doesn't place them corectly + var appUnitsToPixels = 60 / contentScale; + let diPreview = createElement("div", { + id: "displayitem_" + displayItem.content + "_" + displayItem.address, + className: "layerPreview", + style: { + position: "absolute", + left: rect2d[0] / appUnitsToPixels + "px", + top: rect2d[1] / appUnitsToPixels + "px", + width: rect2d[2] / appUnitsToPixels + "px", + height: rect2d[3] / appUnitsToPixels + "px", + border: "solid 1px gray", + pointerEvents: "auto", + }, + displayElem, + onmouseover() { + this.displayElem.onmouseover(); + }, + onmouseout() { + this.displayElem.onmouseout(); + }, + }); + + layerViewport.appendChild(diPreview); + displayElem.diPreview = diPreview; + } + } + } + + for (var i = 0; i < root.children.length; i++) { + populateLayers( + root.children[i], + displayList, + pane, + previewParent, + hasSeenRoot, + contentScale, + rootPreviewParent + ); + } +} + +// This function takes a stdout snippet and finds the frames +function parseMultiLineDump(log) { + var container = createElement("div", { + style: { + height: "100%", + position: "relative", + }, + }); + + var layerManagerFirstLine = "[a-zA-Z]*LayerManager \\(.*$\n"; + var nextLineStartWithSpace = "([ \\t].*$\n)*"; + var layersRegex = "(" + layerManagerFirstLine + nextLineStartWithSpace + ")"; + + var startLine = "Painting --- after optimization:\n"; + var endLine = "Painting --- layer tree:"; + var displayListRegex = "(" + startLine + "(.*\n)*?" + endLine + ")"; + + var regex = new RegExp(layersRegex + "|" + displayListRegex, "gm"); + var matches = log.match(regex); + console.log(matches); + window.matches = matches; + + var matchList = createElement("span", { + style: { + height: "95%", + width: "10%", + position: "relative", + border: "solid black 2px", + display: "inline-block", + float: "left", + overflow: "auto", + }, + }); + container.appendChild(matchList); + var contents = createElement("span", { + style: { + height: "95%", + width: "88%", + display: "inline-block", + }, + textContent: "Click on a frame on the left to view the layer tree", + }); + container.appendChild(contents); + + var lastDisplayList = null; + var frameID = 1; + for (let i = 0; i < matches.length; i++) { + var currMatch = matches[i]; + + if (currMatch.indexOf(startLine) == 0) { + // Display list match + var matchLines = matches[i].split("\n"); + lastDisplayList = parseDisplayList(matchLines); + } else { + // Layer tree match: + let displayList = lastDisplayList; + lastDisplayList = null; + var currFrameDiv = createElement("a", { + style: { + padding: "3px", + display: "block", + }, + href: "#", + textContent: "LayerTree " + frameID++, + onclick() { + contents.innerHTML = ""; + var matchLines = matches[i].split("\n"); + var dumpDiv = parseDump(matchLines, displayList); + contents.appendChild(dumpDiv); + }, + }); + matchList.appendChild(currFrameDiv); + } + } + + return container; +} + +function parseDump(log, displayList, compositeTitle, compositeTime) { + compositeTitle |= ""; + compositeTime |= 0; + + var container = createElement("div", { + style: { + background: "white", + height: "100%", + position: "relative", + }, + }); + + if (compositeTitle == null && compositeTime == null) { + var titleDiv = createElement("div", { + className: "treeColumnHeader", + style: { + width: "100%", + }, + textContent: + compositeTitle + + (compositeTitle ? " (near " + compositeTime.toFixed(0) + " ms)" : ""), + }); + container.appendChild(titleDiv); + } + + var mainDiv = createElement("div", { + style: { + position: "absolute", + top: "16px", + left: "0px", + right: "0px", + bottom: "0px", + }, + }); + container.appendChild(mainDiv); + + var layerListPane = createElement("div", { + style: { + cssFloat: "left", + height: "100%", + width: "300px", + overflowY: "scroll", + }, + }); + mainDiv.appendChild(layerListPane); + + var previewDiv = createElement("div", { + style: { + position: "absolute", + left: "300px", + right: "0px", + top: "0px", + bottom: "0px", + overflow: "auto", + }, + }); + mainDiv.appendChild(previewDiv); + + var root = parseLayers(log); + populateLayers(root, displayList, layerListPane, previewDiv); + return container; +} diff --git a/gfx/layers/layerviewer/noise.png b/gfx/layers/layerviewer/noise.png new file mode 100644 index 0000000000..01d340aaa9 Binary files /dev/null and b/gfx/layers/layerviewer/noise.png differ diff --git a/gfx/layers/layerviewer/show.png b/gfx/layers/layerviewer/show.png new file mode 100644 index 0000000000..7038b660c8 Binary files /dev/null and b/gfx/layers/layerviewer/show.png differ diff --git a/gfx/layers/layerviewer/tree.css b/gfx/layers/layerviewer/tree.css new file mode 100644 index 0000000000..18e5881c00 --- /dev/null +++ b/gfx/layers/layerviewer/tree.css @@ -0,0 +1,39 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +html, body { + height: 100%; + overflow: hidden; +} +.layerObjectDescription:hover { + background-color: #E8E8E8; +} + +.layerHover > .layerPreview::after { + position: absolute; + top: 0; right: 0; bottom: 0; left: 0; + background-color: inherit; + content: ""; + background-color: rgba(0,0,0,0.2); + box-shadow: -2px 2px 0 #FFF; +} + +@keyframes layerHoverAnimation { + 0% { transform: scale(1); } + 50% { transform: scale(1.2); } + 100% { transform: scale(1); } +} + +.displayHover { + background: rgba(0, 128, 0, 0.8); +} + +.layerHover > .layerPreview { + animation: layerHoverAnimation 200ms; + animation-transform-origin: 50% 50%; + background: gold !important; + box-shadow: 10px 10px 5px #888888; + border-color: blue !important; + z-index: 10; +} diff --git a/gfx/layers/mlgpu/BufferCache.cpp b/gfx/layers/mlgpu/BufferCache.cpp new file mode 100644 index 0000000000..8a2668a5ef --- /dev/null +++ b/gfx/layers/mlgpu/BufferCache.cpp @@ -0,0 +1,96 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "BufferCache.h" +#include "MLGDevice.h" +#include "ShaderDefinitionsMLGPU.h" +#include "mozilla/MathAlgorithms.h" + +namespace mozilla { +namespace layers { + +using namespace mlg; + +BufferCache::BufferCache(MLGDevice* aDevice) + : mDevice(aDevice), + mFirstSizeClass(CeilingLog2(kConstantBufferElementSize)), + mFrameNumber(0), + mNextSizeClassToShrink(0) { + // Create a cache of buffers for each size class, where each size class is a + // power of 2 between the minimum and maximum size of a constant buffer. + size_t maxBindSize = mDevice->GetMaxConstantBufferBindSize(); + MOZ_ASSERT(IsPowerOfTwo(maxBindSize)); + + size_t lastSizeClass = CeilingLog2(maxBindSize); + MOZ_ASSERT(lastSizeClass >= mFirstSizeClass); + + mCaches.resize(lastSizeClass - mFirstSizeClass + 1); +} + +BufferCache::~BufferCache() = default; + +RefPtr BufferCache::GetOrCreateBuffer(size_t aBytes) { + size_t sizeClass = CeilingLog2(aBytes); + size_t sizeClassIndex = sizeClass - mFirstSizeClass; + if (sizeClassIndex >= mCaches.size()) { + return mDevice->CreateBuffer(MLGBufferType::Constant, aBytes, + MLGUsage::Dynamic, nullptr); + } + + CachePool& pool = mCaches[sizeClassIndex]; + + // See if we've cached a buffer that wasn't used in the past 2 frames. A + // buffer used this frame could have already been mapped and written to, and a + // buffer used the previous frame might still be in-use by the GPU. While the + // latter case is okay, it causes aliasing in the driver. Since content is + // double buffered we do not let the compositor get more than 1 frames ahead, + // and a count of 2 frames should ensure the buffer is unused. + if (!pool.empty() && mFrameNumber >= pool.front().mLastUsedFrame + 2) { + RefPtr buffer = pool.front().mBuffer; + pool.pop_front(); + pool.push_back(CacheEntry(mFrameNumber, buffer)); + MOZ_RELEASE_ASSERT(buffer->GetSize() >= aBytes); + return buffer; + } + + // Allocate a new buffer and cache it. + size_t bytes = (size_t(1) << sizeClass); + MOZ_ASSERT(bytes >= aBytes); + + RefPtr buffer = mDevice->CreateBuffer( + MLGBufferType::Constant, bytes, MLGUsage::Dynamic, nullptr); + if (!buffer) { + return nullptr; + } + + pool.push_back(CacheEntry(mFrameNumber, buffer)); + return buffer; +} + +void BufferCache::EndFrame() { + // Consider a buffer dead after ~5 seconds assuming 60 fps. + static size_t kMaxUnusedFrameCount = 60 * 5; + + // At the end of each frame we pick one size class and see if it has any + // buffers that haven't been used for many frames. If so we clear them. + // The next frame we'll search the next size class. (This is just to spread + // work over more than one frame.) + CachePool& pool = mCaches[mNextSizeClassToShrink]; + while (!pool.empty()) { + // Since the deque is sorted oldest-to-newest, front-to-back, we can stop + // searching as soon as a buffer is active. + if (mFrameNumber - pool.front().mLastUsedFrame < kMaxUnusedFrameCount) { + break; + } + pool.pop_front(); + } + mNextSizeClassToShrink = (mNextSizeClassToShrink + 1) % mCaches.size(); + + mFrameNumber++; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/mlgpu/BufferCache.h b/gfx/layers/mlgpu/BufferCache.h new file mode 100644 index 0000000000..0f67597e3a --- /dev/null +++ b/gfx/layers/mlgpu/BufferCache.h @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_layers_mlgpu_BufferCache_h +#define mozilla_gfx_layers_mlgpu_BufferCache_h + +#include "mozilla/EnumeratedArray.h" +#include "mozilla/RefPtr.h" +#include +#include + +namespace mozilla { +namespace layers { + +class MLGBuffer; +class MLGDevice; + +// Cache MLGBuffers based on how long ago they were last used. +class BufferCache final { + public: + explicit BufferCache(MLGDevice* aDevice); + ~BufferCache(); + + // Get a buffer that has at least |aBytes|, or create a new one + // if none can be re-used. + RefPtr GetOrCreateBuffer(size_t aBytes); + + // Age out old buffers after a frame has been completed. + void EndFrame(); + + private: + // Not RefPtr since this would create a cycle. + MLGDevice* mDevice; + + // The first size class is Log2(N), where 16 is the minimum size of a + // constant buffer (currently 16 bytes). + size_t mFirstSizeClass; + + // Each size class is a power of 2. Each pool of buffers is represented as a + // deque, with the least-recently-used (i.e., oldest) buffers at the front, + // and most-recently-used (i.e., newest) buffers at the back. To re-use a + // buffer it is popped off the front and re-added to the back. + // + // This is not always efficient use of storage: if a single frame allocates + // 300 buffers of the same size, we may keep recycling through all those + // buffers for a long time, as long as at least one gets used per frame. + // But since buffers use tiny amounts of memory, and they are only mapped + // while drawing, it shouldn't be a big deal. + struct CacheEntry { + CacheEntry() : mLastUsedFrame(0) {} + // XXX The copy constructor can be deleted once RefPtr's move constructor is + // declared noexcept, see Bug 1612680. + CacheEntry(const CacheEntry& aEntry) = default; + CacheEntry(CacheEntry&& aEntry) = default; + CacheEntry(size_t aLastUsedFrame, MLGBuffer* aBuffer) + : mLastUsedFrame(aLastUsedFrame), mBuffer(aBuffer) {} + + uint64_t mLastUsedFrame; + RefPtr mBuffer; + }; + typedef std::deque CachePool; + + // We track how many frames have occurred to determine the age of cache + // entries. + uint64_t mFrameNumber; + + // To avoid doing too much work in one frame, we only shrink one size class + // per frame. + uint64_t mNextSizeClassToShrink; + + // There is one pool of buffers for each power of 2 allocation size. The + // maximum buffer size is at most 64KB on Direct3D 11. + std::vector mCaches; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_gfx_layers_mlgpu_BufferCache_h diff --git a/gfx/layers/mlgpu/CanvasLayerMLGPU.cpp b/gfx/layers/mlgpu/CanvasLayerMLGPU.cpp new file mode 100644 index 0000000000..3e47709bc7 --- /dev/null +++ b/gfx/layers/mlgpu/CanvasLayerMLGPU.cpp @@ -0,0 +1,81 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "CanvasLayerMLGPU.h" +#include "composite/CompositableHost.h" // for CompositableHost +#include "gfx2DGlue.h" // for ToFilter +#include "gfxEnv.h" // for gfxEnv, etc +#include "mozilla/gfx/Matrix.h" // for Matrix4x4 +#include "mozilla/gfx/Point.h" // for Point +#include "mozilla/gfx/Rect.h" // for Rect +#include "mozilla/layers/Compositor.h" // for Compositor +#include "mozilla/layers/Effects.h" // for EffectChain +#include "mozilla/layers/ImageHost.h" +#include "mozilla/mozalloc.h" // for operator delete +#include "nsAString.h" +#include "mozilla/RefPtr.h" // for nsRefPtr +#include "MaskOperation.h" +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "nsString.h" // for nsAutoCString + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +CanvasLayerMLGPU::CanvasLayerMLGPU(LayerManagerMLGPU* aManager) + : CanvasLayer(aManager, nullptr), TexturedLayerMLGPU(aManager) {} + +CanvasLayerMLGPU::~CanvasLayerMLGPU() { CleanupResources(); } + +Layer* CanvasLayerMLGPU::GetLayer() { return this; } + +gfx::SamplingFilter CanvasLayerMLGPU::GetSamplingFilter() { + gfx::SamplingFilter filter = mSamplingFilter; +#ifdef ANDROID + // Bug 691354 + // Using the LINEAR filter we get unexplained artifacts. + // Use NEAREST when no scaling is required. + Matrix matrix; + bool is2D = GetEffectiveTransform().Is2D(&matrix); + if (is2D && !ThebesMatrix(matrix).HasNonTranslationOrFlip()) { + filter = SamplingFilter::POINT; + } +#endif + return filter; +} + +void CanvasLayerMLGPU::PrintInfo(std::stringstream& aStream, + const char* aPrefix) { + CanvasLayer::PrintInfo(aStream, aPrefix); + aStream << "\n"; + if (mHost && mHost->IsAttached()) { + nsAutoCString pfx(aPrefix); + pfx += " "; + mHost->PrintInfo(aStream, pfx.get()); + } +} + +void CanvasLayerMLGPU::CleanupResources() { + if (mHost) { + mHost->Detach(this); + } + mTexture = nullptr; + mBigImageTexture = nullptr; + mHost = nullptr; +} + +void CanvasLayerMLGPU::Disconnect() { CleanupResources(); } + +void CanvasLayerMLGPU::ClearCachedResources() { CleanupResources(); } + +void CanvasLayerMLGPU::SetRenderRegion(LayerIntRegion&& aRegion) { + aRegion.AndWith(LayerIntRect::FromUnknownRect(mPictureRect)); + LayerMLGPU::SetRenderRegion(std::move(aRegion)); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/mlgpu/CanvasLayerMLGPU.h b/gfx/layers/mlgpu/CanvasLayerMLGPU.h new file mode 100644 index 0000000000..d7c740a40d --- /dev/null +++ b/gfx/layers/mlgpu/CanvasLayerMLGPU.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_CanvasLayerMLGPU_H +#define GFX_CanvasLayerMLGPU_H + +#include "Layers.h" // for CanvasLayer, etc +#include "TexturedLayerMLGPU.h" +#include "mozilla/Attributes.h" // for override +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/layers/LayerManagerMLGPU.h" // for LayerComposite, etc +#include "mozilla/layers/LayersTypes.h" // for LayerRenderState, etc +#include "nsRect.h" // for mozilla::gfx::IntRect +#include "nscore.h" // for nsACString + +namespace mozilla { +namespace layers { + +class CompositableHost; +class ImageHost; + +class CanvasLayerMLGPU final : public CanvasLayer, public TexturedLayerMLGPU { + public: + explicit CanvasLayerMLGPU(LayerManagerMLGPU* aManager); + + protected: + virtual ~CanvasLayerMLGPU(); + + public: + Layer* GetLayer() override; + void Disconnect() override; + + HostLayer* AsHostLayer() override { return this; } + CanvasLayerMLGPU* AsCanvasLayerMLGPU() override { return this; } + gfx::SamplingFilter GetSamplingFilter() override; + void ClearCachedResources() override; + void SetRenderRegion(LayerIntRegion&& aRegion) override; + + MOZ_LAYER_DECL_NAME("CanvasLayerMLGPU", TYPE_CANVAS) + + protected: + RefPtr CreateCanvasRendererInternal() override { + MOZ_CRASH("Incompatible surface type"); + return nullptr; + } + + void PrintInfo(std::stringstream& aStream, const char* aPrefix) override; + void CleanupResources(); +}; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_CanvasLayerMLGPU_H */ diff --git a/gfx/layers/mlgpu/ClearRegionHelper.h b/gfx/layers/mlgpu/ClearRegionHelper.h new file mode 100644 index 0000000000..910972c9c1 --- /dev/null +++ b/gfx/layers/mlgpu/ClearRegionHelper.h @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_layers_mlgpu_ClearRegionHelper_h +#define mozilla_gfx_layers_mlgpu_ClearRegionHelper_h + +#include "SharedBufferMLGPU.h" + +namespace mozilla { +namespace layers { + +// This is a helper class for issuing a clear operation based on either +// a shader or an API like ClearView. It also works when the depth +// buffer is enabled. +struct ClearRegionHelper { + // If using ClearView-based clears, we fill mRegions. + nsTArray mRects; + + // If using shader-based clears, we fill these buffers. + VertexBufferSection mInput; + ConstantBufferSection mVSBuffer; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_gfx_layers_mlgpu_ClearRegionHelper_h diff --git a/gfx/layers/mlgpu/ContainerLayerMLGPU.cpp b/gfx/layers/mlgpu/ContainerLayerMLGPU.cpp new file mode 100644 index 0000000000..e4fc8f737e --- /dev/null +++ b/gfx/layers/mlgpu/ContainerLayerMLGPU.cpp @@ -0,0 +1,242 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ContainerLayerMLGPU.h" +#include "mozilla/StaticPrefs_layers.h" +#include "LayerManagerMLGPU.h" +#include "MLGDevice.h" +#include "mozilla/gfx/Rect.h" +#include "mozilla/gfx/Types.h" +#include "UnitTransforms.h" +#include "UtilityMLGPU.h" + +namespace mozilla { +namespace layers { + +using namespace gfx; + +ContainerLayerMLGPU::ContainerLayerMLGPU(LayerManagerMLGPU* aManager) + : ContainerLayer(aManager, nullptr), + LayerMLGPU(aManager), + mInvalidateEntireSurface(false), + mSurfaceCopyNeeded(false), + mView(nullptr) {} + +ContainerLayerMLGPU::~ContainerLayerMLGPU() { + while (mFirstChild) { + RemoveChild(mFirstChild); + } +} + +bool ContainerLayerMLGPU::OnPrepareToRender(FrameBuilder* aBuilder) { + mView = nullptr; + + if (!UseIntermediateSurface()) { + // Set this so we invalidate the entire cached render target (if any) + // if our container uses an intermediate surface again later. + mInvalidateEntireSurface = true; + return true; + } + + mChildrenChanged = false; + + mTargetOffset = GetIntermediateSurfaceRect().TopLeft().ToUnknownPoint(); + mTargetSize = GetIntermediateSurfaceRect().Size().ToUnknownSize(); + + if (mRenderTarget && mRenderTarget->GetSize() != mTargetSize) { + mRenderTarget = nullptr; + } + + // Note that if a surface copy is needed, we always redraw the + // whole surface (on-demand). This is a rare case - the old + // Compositor already does this - and it saves us having to + // do much more complicated invalidation. + bool surfaceCopyNeeded = false; + DefaultComputeSupportsComponentAlphaChildren(&surfaceCopyNeeded); + if (surfaceCopyNeeded != mSurfaceCopyNeeded || surfaceCopyNeeded) { + mInvalidateEntireSurface = true; + } + mSurfaceCopyNeeded = surfaceCopyNeeded; + + gfx::IntRect viewport(gfx::IntPoint(0, 0), mTargetSize); + if (!mRenderTarget || !StaticPrefs::layers_mlgpu_enable_invalidation() || + mInvalidateEntireSurface) { + // Fine-grained invalidation is disabled, invalidate everything. + mInvalidRect = viewport; + } else { + // Clamp the invalid rect to the viewport. + mInvalidRect -= mTargetOffset; + mInvalidRect = mInvalidRect.Intersect(viewport); + } + + mInvalidateEntireSurface = false; + return true; +} + +static IntRect GetTransformedBounds(Layer* aLayer) { + IntRect bounds = aLayer->GetLocalVisibleRegion().GetBounds().ToUnknownRect(); + if (bounds.IsEmpty()) { + return bounds; + } + + const Matrix4x4& transform = aLayer->GetEffectiveTransform(); + Rect rect = + transform.TransformAndClipBounds(Rect(bounds), Rect::MaxIntRect()); + rect.RoundOut(); + rect.ToIntRect(&bounds); + return bounds; +} + +/* static */ +Maybe ContainerLayerMLGPU::FindVisibleBounds( + Layer* aLayer, const Maybe& aClip) { + AL_LOG(" visiting child %p\n", aLayer); + AL_LOG_IF(aClip, " parent clip: %s\n", Stringify(aClip.value()).c_str()); + + ContainerLayer* container = aLayer->AsContainerLayer(); + if (container) { + if (container->UseIntermediateSurface()) { + ContainerLayerMLGPU* c = + container->AsHostLayer()->AsLayerMLGPU()->AsContainerLayerMLGPU(); + if (!c) { + gfxCriticalError() + << "not container: " + << container->AsHostLayer()->AsLayerMLGPU()->GetType(); + } + MOZ_RELEASE_ASSERT(c); + c->ComputeIntermediateSurfaceBounds(); + } else { + Maybe accumulated = Some(IntRect()); + + // Traverse children. + for (Layer* child = container->GetFirstChild(); child; + child = child->GetNextSibling()) { + Maybe clip = aClip; + if (const Maybe& childClip = + child->AsHostLayer()->GetShadowClipRect()) { + RenderTargetIntRect rtChildClip = TransformBy( + ViewAs( + aLayer->GetEffectiveTransform(), + PixelCastJustification::RenderTargetIsParentLayerForRoot), + childClip.value()); + clip = IntersectMaybeRects(clip, Some(rtChildClip)); + AL_LOG(" target clip: %s\n", Stringify(rtChildClip).c_str()); + AL_LOG_IF(clip, " full clip: %s\n", + Stringify(clip.value()).c_str()); + } + + Maybe childBounds = FindVisibleBounds(child, clip); + if (!childBounds) { + return Nothing(); + } + + accumulated = accumulated->SafeUnion(childBounds.value()); + if (!accumulated) { + return Nothing(); + } + } + return accumulated; + } + } + + IntRect bounds = GetTransformedBounds(aLayer); + AL_LOG(" layer bounds: %s\n", Stringify(bounds).c_str()); + + if (aClip) { + bounds = bounds.Intersect(aClip.value().ToUnknownRect()); + AL_LOG(" clipped bounds: %s\n", Stringify(bounds).c_str()); + } + return Some(bounds); +} + +void ContainerLayerMLGPU::ComputeIntermediateSurfaceBounds() { + Maybe bounds = Some(IntRect()); + for (Layer* child = GetFirstChild(); child; child = child->GetNextSibling()) { + Maybe clip = ViewAs( + child->AsHostLayer()->GetShadowClipRect(), + PixelCastJustification::RenderTargetIsParentLayerForRoot); + Maybe childBounds = FindVisibleBounds(child, clip); + if (!childBounds) { + return; + } + + bounds = bounds->SafeUnion(childBounds.value()); + if (!bounds) { + return; + } + } + + SetShadowVisibleRegion(LayerIntRect::FromUnknownRect(bounds.value())); +} + +void ContainerLayerMLGPU::OnLayerManagerChange(LayerManagerMLGPU* aManager) { + ClearCachedResources(); +} + +RefPtr ContainerLayerMLGPU::UpdateRenderTarget( + MLGDevice* aDevice, MLGRenderTargetFlags aFlags) { + if (mRenderTarget) { + return mRenderTarget; + } + + mRenderTarget = aDevice->CreateRenderTarget(mTargetSize, aFlags); + if (!mRenderTarget) { + gfxWarning() + << "Failed to create an intermediate render target for ContainerLayer"; + return nullptr; + } + + return mRenderTarget; +} + +void ContainerLayerMLGPU::SetInvalidCompositeRect(const gfx::IntRect* aRect) { + // For simplicity we only track the bounds of the invalid area, since regions + // are expensive. + // + // Note we add the bounds to the invalid rect from the last frame, since we + // only clear the area that we actually paint. If this overflows we use the + // last render target size, since if that changes we'll invalidate everything + // anyway. + if (aRect) { + if (Maybe result = mInvalidRect.SafeUnion(*aRect)) { + mInvalidRect = result.value(); + } else { + mInvalidateEntireSurface = true; + } + } else { + mInvalidateEntireSurface = true; + } +} + +void ContainerLayerMLGPU::ClearCachedResources() { mRenderTarget = nullptr; } + +bool ContainerLayerMLGPU::IsContentOpaque() { + if (GetMixBlendMode() != gfx::CompositionOp::OP_OVER) { + // We need to read from what's underneath us, so we consider our content to + // be not opaque. + return false; + } + return LayerMLGPU::IsContentOpaque(); +} + +const LayerIntRegion& ContainerLayerMLGPU::GetShadowVisibleRegion() { + if (!UseIntermediateSurface()) { + RecomputeShadowVisibleRegionFromChildren(); + } + + return mShadowVisibleRegion; +} + +const LayerIntRegion& RefLayerMLGPU::GetShadowVisibleRegion() { + if (!UseIntermediateSurface()) { + RecomputeShadowVisibleRegionFromChildren(); + } + + return mShadowVisibleRegion; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/mlgpu/ContainerLayerMLGPU.h b/gfx/layers/mlgpu/ContainerLayerMLGPU.h new file mode 100644 index 0000000000..733bd8477f --- /dev/null +++ b/gfx/layers/mlgpu/ContainerLayerMLGPU.h @@ -0,0 +1,96 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_layers_mlgpu_ContainerLayerMLGPU_h +#define mozilla_gfx_layers_mlgpu_ContainerLayerMLGPU_h + +#include "LayerMLGPU.h" +#include "MLGDeviceTypes.h" + +namespace mozilla { +namespace layers { + +class MLGDevice; +class RenderViewMLGPU; + +class ContainerLayerMLGPU final : public ContainerLayer, public LayerMLGPU { + public: + explicit ContainerLayerMLGPU(LayerManagerMLGPU* aManager); + virtual ~ContainerLayerMLGPU(); + + MOZ_LAYER_DECL_NAME("ContainerLayerMLGPU", TYPE_CONTAINER) + + HostLayer* AsHostLayer() override { return this; } + ContainerLayerMLGPU* AsContainerLayerMLGPU() override { return this; } + Layer* GetLayer() override { return this; } + + void ComputeEffectiveTransforms( + const gfx::Matrix4x4& aTransformToSurface) override { + DefaultComputeEffectiveTransforms(aTransformToSurface); + } + void SetInvalidCompositeRect(const gfx::IntRect* aRect) override; + void ClearCachedResources() override; + + const LayerIntRegion& GetShadowVisibleRegion() override; + + RefPtr UpdateRenderTarget(MLGDevice* aDevice, + MLGRenderTargetFlags aFlags); + + MLGRenderTarget* GetRenderTarget() const { return mRenderTarget; } + gfx::IntPoint GetTargetOffset() const { return mTargetOffset; } + gfx::IntSize GetTargetSize() const { return mTargetSize; } + const gfx::IntRect& GetInvalidRect() const { return mInvalidRect; } + void ClearInvalidRect() { mInvalidRect.SetEmpty(); } + bool IsContentOpaque() override; + bool NeedsSurfaceCopy() const { return mSurfaceCopyNeeded; } + + RenderViewMLGPU* GetRenderView() const { return mView; } + void SetRenderView(RenderViewMLGPU* aView) { + MOZ_ASSERT(!mView); + mView = aView; + } + + void ComputeIntermediateSurfaceBounds(); + + // Similar to ContainerLayerComposite, we need to include the pres shell + // resolution, if there is one, in the layer's post-scale. + float GetPostXScale() const override { + return mSimpleAttrs.GetPostXScale() * mPresShellResolution; + } + float GetPostYScale() const override { + return mSimpleAttrs.GetPostYScale() * mPresShellResolution; + } + + protected: + bool OnPrepareToRender(FrameBuilder* aBuilder) override; + void OnLayerManagerChange(LayerManagerMLGPU* aManager) override; + + private: + static Maybe FindVisibleBounds( + Layer* aLayer, const Maybe& aClip); + + RefPtr mRenderTarget; + + // We cache these since occlusion culling can change the visible region. + gfx::IntPoint mTargetOffset; + gfx::IntSize mTargetSize; + + // The region of the container that needs to be recomposited if visible. We + // store this as a rectangle instead of an nsIntRegion for efficiency. This + // is in layer coordinates. + gfx::IntRect mInvalidRect; + bool mInvalidateEntireSurface; + bool mSurfaceCopyNeeded; + + // This is only valid for intermediate surfaces while an instance of + // FrameBuilder is live. + RenderViewMLGPU* mView; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_gfx_layers_mlgpu_ContainerLayerMLGPU_h diff --git a/gfx/layers/mlgpu/FrameBuilder.cpp b/gfx/layers/mlgpu/FrameBuilder.cpp new file mode 100644 index 0000000000..adb4e8425c --- /dev/null +++ b/gfx/layers/mlgpu/FrameBuilder.cpp @@ -0,0 +1,412 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FrameBuilder.h" +#include "ContainerLayerMLGPU.h" +#include "GeckoProfiler.h" // for profiler_* +#include "LayerMLGPU.h" +#include "LayerManagerMLGPU.h" +#include "MaskOperation.h" +#include "MLGDevice.h" // for MLGSwapChain +#include "RenderPassMLGPU.h" +#include "RenderViewMLGPU.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/gfx/Polygon.h" +#include "mozilla/layers/BSPTree.h" +#include "mozilla/layers/LayersHelpers.h" + +namespace mozilla { +namespace layers { + +using namespace mlg; + +FrameBuilder::FrameBuilder(LayerManagerMLGPU* aManager, + MLGSwapChain* aSwapChain) + : mManager(aManager), + mDevice(aManager->GetDevice()), + mSwapChain(aSwapChain) { + // test_bug1124898.html has a root ColorLayer, so we don't assume the root is + // a container. + mRoot = mManager->GetRoot()->AsHostLayer()->AsLayerMLGPU(); +} + +FrameBuilder::~FrameBuilder() = default; + +bool FrameBuilder::Build() { + AUTO_PROFILER_LABEL("FrameBuilder::Build", GRAPHICS); + + // AcquireBackBuffer can fail, so we check the result here. + RefPtr target = mSwapChain->AcquireBackBuffer(); + if (!target) { + return false; + } + + // This updates the frame sequence number, so layers can quickly check if + // they've already been prepared. + LayerMLGPU::BeginFrame(); + + // Note: we don't clip draw calls to the invalid region per se, but instead + // the region bounds. Clipping all draw calls would incur a significant + // CPU cost on large layer trees, and would greatly complicate how draw + // rects are added in RenderPassMLGPU, since we would need to break + // each call into additional items based on the intersection with the + // invalid region. + // + // Instead we scissor to the invalid region bounds. As a result, all items + // affecting the invalid bounds are redrawn, even if not all are in the + // precise region. + const nsIntRegion& region = mSwapChain->GetBackBufferInvalidRegion(); + + mWidgetRenderView = new RenderViewMLGPU(this, target, region); + + // Traverse the layer tree and compute visible region for intermediate + // surfaces + if (ContainerLayerMLGPU* root = + mRoot->AsLayerMLGPU()->AsContainerLayerMLGPU()) { + root->ComputeIntermediateSurfaceBounds(); + } + + // Traverse the layer tree and assign each layer to tiles. + { + Maybe geometry; + RenderTargetIntRect clip(0, 0, target->GetSize().width, + target->GetSize().height); + + AssignLayer(mRoot->GetLayer(), mWidgetRenderView, clip, + std::move(geometry)); + } + + // Build the default mask buffer. + { + MaskInformation defaultMaskInfo(1.0f, false); + if (!mDevice->GetSharedPSBuffer()->Allocate(&mDefaultMaskInfo, + defaultMaskInfo)) { + return false; + } + } + + // Build render passes and buffer information for each pass. + mWidgetRenderView->FinishBuilding(); + mWidgetRenderView->Prepare(); + + // Prepare masks that need to be combined. + for (const auto& pair : mCombinedTextureMasks) { + pair.second->PrepareForRendering(); + } + + FinishCurrentLayerBuffer(); + FinishCurrentMaskRectBuffer(); + return true; +} + +void FrameBuilder::Render() { + AUTO_PROFILER_LABEL("FrameBuilder::Render", GRAPHICS); + + // Render combined masks into single mask textures. + for (const auto& pair : mCombinedTextureMasks) { + pair.second->Render(); + } + + // Render to all targets, front-to-back. + mWidgetRenderView->Render(); +} + +void FrameBuilder::AssignLayer(Layer* aLayer, RenderViewMLGPU* aView, + const RenderTargetIntRect& aClipRect, + Maybe&& aGeometry) { + LayerMLGPU* layer = aLayer->AsHostLayer()->AsLayerMLGPU(); + + if (ContainerLayer* container = aLayer->AsContainerLayer()) { + // This returns false if we don't need to (or can't) process the layer any + // further. This always returns false for non-leaf ContainerLayers. + if (!ProcessContainerLayer(container, aView, aClipRect, aGeometry)) { + return; + } + } else { + // Set the precomputed clip and any textures/resources that are needed. + if (!layer->PrepareToRender(this, aClipRect)) { + return; + } + } + + // If we are dealing with a nested 3D context, we might need to transform + // the geometry back to the coordinate space of the current layer. + if (aGeometry) { + TransformLayerGeometry(aLayer, aGeometry); + } + + // Finally, assign the layer to a rendering batch in the current render + // target. + layer->AssignToView(this, aView, std::move(aGeometry)); +} + +bool FrameBuilder::ProcessContainerLayer(ContainerLayer* aContainer, + RenderViewMLGPU* aView, + const RenderTargetIntRect& aClipRect, + Maybe& aGeometry) { + LayerMLGPU* layer = aContainer->AsHostLayer()->AsLayerMLGPU(); + + // Diagnostic information for bug 1387467. + if (!layer) { + gfxDevCrash(gfx::LogReason::InvalidLayerType) + << "Layer type is invalid: " << aContainer->Name(); + return false; + } + + // We don't want to traverse containers twice, so we only traverse them if + // they haven't been prepared yet. + bool isFirstVisit = !layer->IsPrepared(); + if (isFirstVisit && !layer->PrepareToRender(this, aClipRect)) { + return false; + } + + if (!aContainer->UseIntermediateSurface()) { + // In case the layer previously required an intermediate surface, we + // clear any intermediate render targets here. + layer->ClearCachedResources(); + + // This is a pass-through container, so we just process children and + // instruct AssignLayer to early-return. + ProcessChildList(aContainer, aView, aClipRect, aGeometry); + return false; + } + + // If this is the first visit of the container this frame, and the + // container has an unpainted area, we traverse the container. Note that + // RefLayers do not have intermediate surfaces so this is guaranteed + // to be a full-fledged ContainerLayerMLGPU. + ContainerLayerMLGPU* viewContainer = layer->AsContainerLayerMLGPU(); + if (!viewContainer) { + gfxDevCrash(gfx::LogReason::InvalidLayerType) + << "Container layer type is invalid: " << aContainer->Name(); + return false; + } + + if (isFirstVisit && !viewContainer->GetInvalidRect().IsEmpty()) { + // The RenderView constructor automatically attaches itself to the parent. + RefPtr view = + new RenderViewMLGPU(this, viewContainer, aView); + ProcessChildList(aContainer, view, aClipRect, Nothing()); + view->FinishBuilding(); + } + return true; +} + +void FrameBuilder::ProcessChildList( + ContainerLayer* aContainer, RenderViewMLGPU* aView, + const RenderTargetIntRect& aParentClipRect, + const Maybe& aParentGeometry) { + nsTArray polygons = aContainer->SortChildrenBy3DZOrder( + ContainerLayer::SortMode::WITH_GEOMETRY); + + // Visit layers in front-to-back order. + for (auto iter = polygons.rbegin(); iter != polygons.rend(); iter++) { + LayerPolygon& entry = *iter; + Layer* child = entry.layer; + if (child->IsBackfaceHidden() || !child->IsVisible()) { + continue; + } + + RenderTargetIntRect clip = child->CalculateScissorRect(aParentClipRect); + if (clip.IsEmpty()) { + continue; + } + + Maybe geometry; + if (aParentGeometry && entry.geometry) { + // Both parent and child are split. + geometry = Some(aParentGeometry->ClipPolygon(*entry.geometry)); + } else if (aParentGeometry) { + geometry = aParentGeometry; + } else if (entry.geometry) { + geometry = std::move(entry.geometry); + } + + AssignLayer(child, aView, clip, std::move(geometry)); + } +} + +bool FrameBuilder::AddLayerToConstantBuffer(ItemInfo& aItem) { + LayerMLGPU* layer = aItem.layer; + + // If this layer could appear multiple times, cache it. + if (aItem.geometry) { + if (mLayerBufferMap.Get(layer, &aItem.layerIndex)) { + return true; + } + } + + LayerConstants* info = AllocateLayerInfo(aItem); + if (!info) { + return false; + } + + // Note we do not use GetEffectiveTransformForBuffer, since we calculate + // the correct scaling when we build texture coordinates. + Layer* baseLayer = layer->GetLayer(); + const gfx::Matrix4x4& transform = baseLayer->GetEffectiveTransform(); + + memcpy(&info->transform, &transform._11, 64); + info->clipRect = gfx::Rect(layer->GetComputedClipRect().ToUnknownRect()); + info->maskIndex = 0; + if (MaskOperation* op = layer->GetMask()) { + // Note: we use 0 as an invalid index, and so indices are offset by 1. + gfx::Rect rect = op->ComputeMaskRect(baseLayer); + AddMaskRect(rect, &info->maskIndex); + } + + if (aItem.geometry) { + mLayerBufferMap.Put(layer, aItem.layerIndex); + } + return true; +} + +MaskOperation* FrameBuilder::AddMaskOperation(LayerMLGPU* aLayer) { + Layer* layer = aLayer->GetLayer(); + MOZ_ASSERT(layer->HasMaskLayers()); + + // Multiple masks are combined into a single mask. + if ((layer->GetMaskLayer() && layer->GetAncestorMaskLayerCount()) || + layer->GetAncestorMaskLayerCount() > 1) { + // Since each mask can be moved independently of the other, we must create + // a separate combined mask for every new positioning we encounter. + MaskTextureList textures; + if (Layer* maskLayer = layer->GetMaskLayer()) { + AppendToMaskTextureList(textures, maskLayer); + } + for (size_t i = 0; i < layer->GetAncestorMaskLayerCount(); i++) { + AppendToMaskTextureList(textures, layer->GetAncestorMaskLayerAt(i)); + } + + auto iter = mCombinedTextureMasks.find(textures); + if (iter != mCombinedTextureMasks.end()) { + return iter->second; + } + + RefPtr op = new MaskCombineOperation(this); + op->Init(textures); + + mCombinedTextureMasks[textures] = op; + return op; + } + + Layer* maskLayer = layer->GetMaskLayer() ? layer->GetMaskLayer() + : layer->GetAncestorMaskLayerAt(0); + RefPtr texture = GetMaskLayerTexture(maskLayer); + if (!texture) { + return nullptr; + } + + RefPtr op; + mSingleTextureMasks.Get(texture, getter_AddRefs(op)); + if (op) { + return op; + } + + RefPtr wrapped = mDevice->CreateTexture(texture); + + op = new MaskOperation(this, wrapped); + mSingleTextureMasks.Put(texture, RefPtr{op}); + return op; +} + +void FrameBuilder::RetainTemporaryLayer(LayerMLGPU* aLayer) { + // This should only be used with temporary layers. Temporary layers do not + // have parents. + MOZ_ASSERT(!aLayer->GetLayer()->GetParent()); + mTemporaryLayers.push_back(aLayer->GetLayer()); +} + +MLGRenderTarget* FrameBuilder::GetWidgetRT() { + return mWidgetRenderView->GetRenderTarget(); +} + +LayerConstants* FrameBuilder::AllocateLayerInfo(ItemInfo& aItem) { + if (((mCurrentLayerBuffer.Length() + 1) * sizeof(LayerConstants)) > + mDevice->GetMaxConstantBufferBindSize()) { + FinishCurrentLayerBuffer(); + mLayerBufferMap.Clear(); + mCurrentLayerBuffer.ClearAndRetainStorage(); + } + + LayerConstants* info = mCurrentLayerBuffer.AppendElement(mozilla::fallible); + if (!info) { + return nullptr; + } + + aItem.layerIndex = mCurrentLayerBuffer.Length() - 1; + return info; +} + +void FrameBuilder::FinishCurrentLayerBuffer() { + if (mCurrentLayerBuffer.IsEmpty()) { + return; + } + + // Note: we append the buffer even if we couldn't allocate one, since + // that keeps the indices sane. + ConstantBufferSection section; + mDevice->GetSharedVSBuffer()->Allocate( + §ion, mCurrentLayerBuffer.Elements(), mCurrentLayerBuffer.Length()); + mLayerBuffers.AppendElement(section); +} + +size_t FrameBuilder::CurrentLayerBufferIndex() const { + // The mask rect buffer list doesn't contain the buffer currently being + // built, so we don't subtract 1 here. + return mLayerBuffers.Length(); +} + +ConstantBufferSection FrameBuilder::GetLayerBufferByIndex(size_t aIndex) const { + if (aIndex >= mLayerBuffers.Length()) { + return ConstantBufferSection(); + } + return mLayerBuffers[aIndex]; +} + +bool FrameBuilder::AddMaskRect(const gfx::Rect& aRect, uint32_t* aOutIndex) { + if (((mCurrentMaskRectList.Length() + 1) * sizeof(gfx::Rect)) > + mDevice->GetMaxConstantBufferBindSize()) { + FinishCurrentMaskRectBuffer(); + mCurrentMaskRectList.ClearAndRetainStorage(); + } + + mCurrentMaskRectList.AppendElement(aRect); + + // Mask indices start at 1 so the shader can use 0 as a no-mask indicator. + *aOutIndex = mCurrentMaskRectList.Length(); + return true; +} + +void FrameBuilder::FinishCurrentMaskRectBuffer() { + if (mCurrentMaskRectList.IsEmpty()) { + return; + } + + // Note: we append the buffer even if we couldn't allocate one, since + // that keeps the indices sane. + ConstantBufferSection section; + mDevice->GetSharedVSBuffer()->Allocate( + §ion, mCurrentMaskRectList.Elements(), mCurrentMaskRectList.Length()); + mMaskRectBuffers.AppendElement(section); +} + +size_t FrameBuilder::CurrentMaskRectBufferIndex() const { + // The mask rect buffer list doesn't contain the buffer currently being + // built, so we don't subtract 1 here. + return mMaskRectBuffers.Length(); +} + +ConstantBufferSection FrameBuilder::GetMaskRectBufferByIndex( + size_t aIndex) const { + if (aIndex >= mMaskRectBuffers.Length()) { + return ConstantBufferSection(); + } + return mMaskRectBuffers[aIndex]; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/mlgpu/FrameBuilder.h b/gfx/layers/mlgpu/FrameBuilder.h new file mode 100644 index 0000000000..5cf5714767 --- /dev/null +++ b/gfx/layers/mlgpu/FrameBuilder.h @@ -0,0 +1,126 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_layers_mlgpu_FrameBuilder_h +#define mozilla_gfx_layers_mlgpu_FrameBuilder_h + +#include "mozilla/RefPtr.h" +#include "mozilla/gfx/Point.h" +#include "mozilla/gfx/Types.h" +#include "MaskOperation.h" +#include "MLGDevice.h" +#include "nsDataHashtable.h" +#include "nsRefPtrHashtable.h" +#include "ShaderDefinitionsMLGPU.h" +#include "SharedBufferMLGPU.h" +#include "Units.h" +#include +#include + +namespace mozilla { +namespace layers { + +class ContainerLayer; +class ContainerLayerMLGPU; +class Layer; +class LayerMLGPU; +class LayerManagerMLGPU; +class MLGDevice; +class MLGRenderTarget; +class MLGSwapChain; +class RenderViewMLGPU; +struct ItemInfo; + +class FrameBuilder final { + public: + FrameBuilder(LayerManagerMLGPU* aManager, MLGSwapChain* aSwapChain); + ~FrameBuilder(); + + bool Build(); + void Render(); + + bool AddLayerToConstantBuffer(ItemInfo& aItem); + + LayerManagerMLGPU* GetManager() const { return mManager; } + MLGDevice* GetDevice() const { return mDevice; } + const ConstantBufferSection& GetDefaultMaskInfo() const { + return mDefaultMaskInfo; + } + + // Called during tile construction. Finds or adds a mask layer chain to the + // cache, that will be flattened as a dependency to rendering batches. + MaskOperation* AddMaskOperation(LayerMLGPU* aLayer); + + // Note: These should only be called during batch construction. + size_t CurrentLayerBufferIndex() const; + size_t CurrentMaskRectBufferIndex() const; + + // These are called during rendering, and may return null if a buffer + // couldn't be allocated. + ConstantBufferSection GetLayerBufferByIndex(size_t aIndex) const; + ConstantBufferSection GetMaskRectBufferByIndex(size_t aIndex) const; + + // Hold a layer alive until the frame ends. + void RetainTemporaryLayer(LayerMLGPU* aLayer); + + MLGRenderTarget* GetWidgetRT(); + + private: + void AssignLayer(Layer* aLayer, RenderViewMLGPU* aView, + const RenderTargetIntRect& aClipRect, + Maybe&& aGeometry); + + void ProcessChildList(ContainerLayer* aContainer, RenderViewMLGPU* aView, + const RenderTargetIntRect& aParentClipRect, + const Maybe& aParentGeometry); + + mlg::LayerConstants* AllocateLayerInfo(ItemInfo& aItem); + bool AddMaskRect(const gfx::Rect& aRect, uint32_t* aOutIndex); + void FinishCurrentLayerBuffer(); + void FinishCurrentMaskRectBuffer(); + + // Returns true to continue, false to stop - false does not indicate + // failure. + bool ProcessContainerLayer(ContainerLayer* aLayer, RenderViewMLGPU* aView, + const RenderTargetIntRect& aClipRect, + Maybe& aGeometry); + + private: + RefPtr mManager; + RefPtr mDevice; + RefPtr mSwapChain; + RefPtr mWidgetRenderView; + LayerMLGPU* mRoot; + + // Each time we consume a layer in a tile, we make sure a constant buffer + // exists that contains information about the layer. The mapping is valid + // for the most recent buffer, and once the buffer fills, we begin a new + // one and clear the map. + nsTArray mLayerBuffers; + nsTArray mCurrentLayerBuffer; + nsDataHashtable, uint32_t> mLayerBufferMap; + + // We keep mask rects in a separate buffer since they're rare. + nsTArray mMaskRectBuffers; + nsTArray mCurrentMaskRectList; + + // For values that *can* change every render pass, but almost certainly do + // not, we pre-fill and cache some buffers. + ConstantBufferSection mDefaultMaskInfo; + + // Cache for MaskOperations. + nsRefPtrHashtable, MaskOperation> + mSingleTextureMasks; + std::map> mCombinedTextureMasks; + + // This list of temporary layers is wiped out when the frame is completed. + std::vector> mTemporaryLayers; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_gfx_layers_mlgpu_FrameBuilder_h diff --git a/gfx/layers/mlgpu/ImageLayerMLGPU.cpp b/gfx/layers/mlgpu/ImageLayerMLGPU.cpp new file mode 100644 index 0000000000..9d236bf91f --- /dev/null +++ b/gfx/layers/mlgpu/ImageLayerMLGPU.cpp @@ -0,0 +1,108 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ImageLayerMLGPU.h" +#include "LayerManagerMLGPU.h" + +namespace mozilla { + +using namespace gfx; + +namespace layers { + +ImageLayerMLGPU::ImageLayerMLGPU(LayerManagerMLGPU* aManager) + : ImageLayer(aManager, static_cast(this)), + TexturedLayerMLGPU(aManager) {} + +ImageLayerMLGPU::~ImageLayerMLGPU() { CleanupResources(); } + +void ImageLayerMLGPU::ComputeEffectiveTransforms( + const gfx::Matrix4x4& aTransformToSurface) { + Matrix4x4 local = GetLocalTransform(); + + // Snap image edges to pixel boundaries. + gfxRect sourceRect(0, 0, 0, 0); + if (mHost && mHost->IsAttached()) { + IntSize size = mHost->GetImageSize(); + sourceRect.SizeTo(size.width, size.height); + } + + // Snap our local transform first, and snap the inherited transform as well. + // This makes our snapping equivalent to what would happen if our content + // was drawn into a PaintedLayer (gfxContext would snap using the local + // transform, then we'd snap again when compositing the PaintedLayer). + mEffectiveTransform = SnapTransform(local, sourceRect, nullptr) * + SnapTransformTranslation(aTransformToSurface, nullptr); + mEffectiveTransformForBuffer = mEffectiveTransform; + + if (mScaleMode == ScaleMode::STRETCH && mScaleToSize.width != 0.0 && + mScaleToSize.height != 0.0) { + Size scale(sourceRect.Width() / mScaleToSize.width, + sourceRect.Height() / mScaleToSize.height); + mScale = Some(scale); + } + + ComputeEffectiveTransformForMaskLayers(aTransformToSurface); +} + +gfx::SamplingFilter ImageLayerMLGPU::GetSamplingFilter() { + return ImageLayer::GetSamplingFilter(); +} + +bool ImageLayerMLGPU::IsContentOpaque() { + if (mPictureRect.Width() == 0 || mPictureRect.Height() == 0) { + return false; + } + if (mScaleMode == ScaleMode::STRETCH) { + return gfx::IsOpaque(mHost->CurrentTextureHost()->GetFormat()); + } + return false; +} + +void ImageLayerMLGPU::SetRenderRegion(LayerIntRegion&& aRegion) { + switch (mScaleMode) { + case ScaleMode::STRETCH: + // See bug 1264142. + aRegion.AndWith( + LayerIntRect(0, 0, mScaleToSize.width, mScaleToSize.height)); + break; + default: + // Clamp the visible region to the texture size. (see bug 1396507) + MOZ_ASSERT(mScaleMode == ScaleMode::SCALE_NONE); + aRegion.AndWith( + LayerIntRect(0, 0, mPictureRect.Width(), mPictureRect.Height())); + break; + } + LayerMLGPU::SetRenderRegion(std::move(aRegion)); +} + +void ImageLayerMLGPU::CleanupResources() { + if (mHost) { + mHost->CleanupResources(); + mHost->Detach(this); + } + mTexture = nullptr; + mBigImageTexture = nullptr; + mHost = nullptr; +} + +void ImageLayerMLGPU::PrintInfo(std::stringstream& aStream, + const char* aPrefix) { + ImageLayer::PrintInfo(aStream, aPrefix); + if (mHost && mHost->IsAttached()) { + aStream << "\n"; + nsAutoCString pfx(aPrefix); + pfx += " "; + mHost->PrintInfo(aStream, pfx.get()); + } +} + +void ImageLayerMLGPU::Disconnect() { CleanupResources(); } + +void ImageLayerMLGPU::ClearCachedResources() { CleanupResources(); } + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/mlgpu/ImageLayerMLGPU.h b/gfx/layers/mlgpu/ImageLayerMLGPU.h new file mode 100644 index 0000000000..33d2e4f3e9 --- /dev/null +++ b/gfx/layers/mlgpu/ImageLayerMLGPU.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_IMAGELAYERMLGPU_H +#define MOZILLA_GFX_IMAGELAYERMLGPU_H + +#include "LayerManagerMLGPU.h" +#include "TexturedLayerMLGPU.h" +#include "ImageLayers.h" +#include "mozilla/layers/ImageHost.h" + +namespace mozilla { +namespace layers { + +class ImageLayerMLGPU final : public ImageLayer, public TexturedLayerMLGPU { + public: + explicit ImageLayerMLGPU(LayerManagerMLGPU* aManager); + + Layer* GetLayer() override { return this; } + HostLayer* AsHostLayer() override { return this; } + ImageLayerMLGPU* AsImageLayerMLGPU() override { return this; } + + void ComputeEffectiveTransforms( + const gfx::Matrix4x4& aTransformToSurface) override; + void SetRenderRegion(LayerIntRegion&& aRegion) override; + gfx::SamplingFilter GetSamplingFilter() override; + void ClearCachedResources() override; + bool IsContentOpaque() override; + void Disconnect() override; + + Maybe GetPictureScale() const override { return mScale; } + + MOZ_LAYER_DECL_NAME("ImageLayerMLGPU", TYPE_IMAGE) + + protected: + virtual ~ImageLayerMLGPU(); + + void PrintInfo(std::stringstream& aStream, const char* aPrefix) override; + void CleanupResources(); + + private: + Maybe mScale; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/mlgpu/LayerMLGPU.cpp b/gfx/layers/mlgpu/LayerMLGPU.cpp new file mode 100644 index 0000000000..714214e83b --- /dev/null +++ b/gfx/layers/mlgpu/LayerMLGPU.cpp @@ -0,0 +1,141 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "LayerManagerMLGPU.h" +#include "RenderPassMLGPU.h" +#include "RenderViewMLGPU.h" +#include "FrameBuilder.h" +#include "mozilla/layers/ImageHost.h" +#include "mozilla/layers/LayerManagerComposite.h" + +namespace mozilla { +namespace layers { + +using namespace gfx; + +uint64_t LayerMLGPU::sFrameKey = 0; + +LayerMLGPU::~LayerMLGPU() = default; + +LayerMLGPU::LayerMLGPU(LayerManagerMLGPU* aManager) + : HostLayer(aManager), + mFrameKey(0), + mComputedOpacity(0.0), + mPrepared(false) {} + +/* static */ +void LayerMLGPU::BeginFrame() { sFrameKey++; } + +LayerManagerMLGPU* LayerMLGPU::GetManager() { + return static_cast(mCompositorManager); +} + +bool LayerMLGPU::PrepareToRender(FrameBuilder* aBuilder, + const RenderTargetIntRect& aClipRect) { + if (mFrameKey == sFrameKey) { + return mPrepared; + } + mFrameKey = sFrameKey; + mPrepared = false; + + Layer* layer = GetLayer(); + + // Only container layers may have mixed blend modes. + MOZ_ASSERT_IF(layer->GetMixBlendMode() != CompositionOp::OP_OVER, + layer->GetType() == Layer::TYPE_CONTAINER); + + mComputedClipRect = aClipRect; + mComputedOpacity = layer->GetEffectiveOpacity(); + + if (layer->HasMaskLayers()) { + mMask = aBuilder->AddMaskOperation(this); + // If the mask has no texture, the pixel shader can't read any non-zero + // values for the mask, so we can consider the whole thing invisible. + if (mMask && mMask->IsEmpty()) { + mComputedOpacity = 0.0f; + } + } else { + mMask = nullptr; + } + + if (!OnPrepareToRender(aBuilder)) { + return false; + } + + mPrepared = true; + return true; +} + +void LayerMLGPU::AssignToView(FrameBuilder* aBuilder, RenderViewMLGPU* aView, + Maybe&& aGeometry) { + AddBoundsToView(aBuilder, aView, std::move(aGeometry)); +} + +void LayerMLGPU::AddBoundsToView(FrameBuilder* aBuilder, RenderViewMLGPU* aView, + Maybe&& aGeometry) { + IntRect bounds = GetClippedBoundingBox(aView, aGeometry); + aView->AddItem(this, bounds, std::move(aGeometry)); +} + +IntRect LayerMLGPU::GetClippedBoundingBox( + RenderViewMLGPU* aView, const Maybe& aGeometry) { + MOZ_ASSERT(IsPrepared()); + + Layer* layer = GetLayer(); + const Matrix4x4& transform = layer->GetEffectiveTransform(); + + Rect rect = + aGeometry + ? aGeometry->BoundingBox() + : Rect(layer->GetLocalVisibleRegion().GetBounds().ToUnknownRect()); + rect = transform.TransformBounds(rect); + rect.MoveBy(-aView->GetTargetOffset()); + rect = rect.Intersect(Rect(mComputedClipRect.ToUnknownRect())); + + IntRect bounds; + rect.RoundOut(); + rect.ToIntRect(&bounds); + return bounds; +} + +void LayerMLGPU::MarkPrepared() { + mFrameKey = sFrameKey; + mPrepared = true; +} + +bool LayerMLGPU::IsContentOpaque() { return GetLayer()->IsOpaque(); } + +void LayerMLGPU::SetRenderRegion(LayerIntRegion&& aRegion) { + mRenderRegion = std::move(aRegion); +} + +void LayerMLGPU::SetLayerManager(HostLayerManager* aManager) { + LayerManagerMLGPU* manager = aManager->AsLayerManagerMLGPU(); + MOZ_RELEASE_ASSERT(manager); + + HostLayer::SetLayerManager(aManager); + GetLayer()->SetManager(manager, this); + + if (CompositableHost* host = GetCompositableHost()) { + host->SetTextureSourceProvider(manager->GetTextureSourceProvider()); + } + + OnLayerManagerChange(manager); +} + +RefLayerMLGPU::RefLayerMLGPU(LayerManagerMLGPU* aManager) + : RefLayer(aManager, static_cast(this)), LayerMLGPU(aManager) {} + +RefLayerMLGPU::~RefLayerMLGPU() = default; + +ColorLayerMLGPU::ColorLayerMLGPU(LayerManagerMLGPU* aManager) + : ColorLayer(aManager, static_cast(this)), + LayerMLGPU(aManager) {} + +ColorLayerMLGPU::~ColorLayerMLGPU() = default; + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/mlgpu/LayerMLGPU.h b/gfx/layers/mlgpu/LayerMLGPU.h new file mode 100644 index 0000000000..d11106688f --- /dev/null +++ b/gfx/layers/mlgpu/LayerMLGPU.h @@ -0,0 +1,161 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_layers_mlgpu_LayerMLGPU_h +#define mozilla_gfx_layers_mlgpu_LayerMLGPU_h + +#include "Layers.h" +#include "mozilla/layers/LayerManagerComposite.h" + +namespace mozilla { +namespace layers { + +class CanvasLayerMLGPU; +class ColorLayerMLGPU; +class ContainerLayerMLGPU; +class FrameBuilder; +class ImageHost; +class ImageLayerMLGPU; +class LayerManagerMLGPU; +class MaskOperation; +class MLGRenderTarget; +class PaintedLayerMLGPU; +class RefLayerMLGPU; +class RenderViewMLGPU; +class TexturedLayerMLGPU; +class TextureSource; + +class LayerMLGPU : public HostLayer { + public: + LayerMLGPU* AsLayerMLGPU() override { return this; } + virtual PaintedLayerMLGPU* AsPaintedLayerMLGPU() { return nullptr; } + virtual ImageLayerMLGPU* AsImageLayerMLGPU() { return nullptr; } + virtual CanvasLayerMLGPU* AsCanvasLayerMLGPU() { return nullptr; } + virtual ContainerLayerMLGPU* AsContainerLayerMLGPU() { return nullptr; } + virtual RefLayerMLGPU* AsRefLayerMLGPU() { return nullptr; } + virtual ColorLayerMLGPU* AsColorLayerMLGPU() { return nullptr; } + virtual TexturedLayerMLGPU* AsTexturedLayerMLGPU() { return nullptr; } + + static void BeginFrame(); + + // Ask the layer to acquire any resources or per-frame information needed + // to render. If this returns false, the layer will be skipped entirely. + bool PrepareToRender(FrameBuilder* aBuilder, + const RenderTargetIntRect& aClipRect); + + Layer::LayerType GetType() { return GetLayer()->GetType(); } + const RenderTargetIntRect& GetComputedClipRect() const { + return mComputedClipRect; + } + MaskOperation* GetMask() const { return mMask; } + float GetComputedOpacity() const { return mComputedOpacity; } + + // Return the bounding box of this layer in render target space, clipped to + // the computed clip rect, and rounded out to an integer rect. + gfx::IntRect GetClippedBoundingBox(RenderViewMLGPU* aView, + const Maybe& aGeometry); + + // If this layer has already been prepared for the current frame, return + // true. This should only be used to guard against double-processing + // container layers after 3d-sorting. + bool IsPrepared() const { return mFrameKey == sFrameKey && mPrepared; } + + // Return true if the content in this layer is opaque (not factoring in + // blend modes or opacity), false otherwise. + virtual bool IsContentOpaque(); + + // Returns the region that this layer will draw pixels to. If the layer and + // its content are opaque, this is the layer's opaque region. + const LayerIntRegion& GetRenderRegion() const { return mRenderRegion; } + + // Some layers have visible regions that extend beyond what is actually drawn. + // When performing CPU-based occlusion culling we must clamp the visible + // region to the actual area. Note that if a layer is opaque, it must not + // expand its visible region such that it might include non-opaque pixels, as + // may be the case for PaintedLayers with a restricted visible region. + virtual void SetRenderRegion(LayerIntRegion&& aRegion); + + virtual void AssignToView(FrameBuilder* aBuilder, RenderViewMLGPU* aView, + Maybe&& aGeometry); + + // Callback for when PrepareToRender has finished successfully. If this + // returns false, PrepareToRender will return false. + virtual bool OnPrepareToRender(FrameBuilder* aBuilder) { return true; } + + virtual void ClearCachedResources() {} + CompositableHost* GetCompositableHost() override { return nullptr; } + + protected: + LayerMLGPU(LayerManagerMLGPU* aManager); + ~LayerMLGPU(); + LayerManagerMLGPU* GetManager(); + + void AddBoundsToView(FrameBuilder* aBuilder, RenderViewMLGPU* aView, + Maybe&& aGeometry); + + void MarkPrepared(); + + // We don't want derivative layers overriding this directly - we provide a + // callback instead. + void SetLayerManager(HostLayerManager* aManager) override; + virtual void OnLayerManagerChange(LayerManagerMLGPU* aManager) {} + + private: + // This is a monotonic counter used to check whether a layer appears twice + // when 3d sorting. + static uint64_t sFrameKey; + + protected: + // These are set during PrepareToRender. + RenderTargetIntRect mComputedClipRect; + RefPtr mMask; + uint64_t mFrameKey; + float mComputedOpacity; + bool mPrepared; + LayerIntRegion mRenderRegion; +}; + +class RefLayerMLGPU final : public RefLayer, public LayerMLGPU { + public: + explicit RefLayerMLGPU(LayerManagerMLGPU* aManager); + virtual ~RefLayerMLGPU(); + + // Layer + HostLayer* AsHostLayer() override { return this; } + RefLayerMLGPU* AsRefLayerMLGPU() override { return this; } + Layer* GetLayer() override { return this; } + + // ContainerLayer + void ComputeEffectiveTransforms( + const gfx::Matrix4x4& aTransformToSurface) override { + DefaultComputeEffectiveTransforms(aTransformToSurface); + } + + const LayerIntRegion& GetShadowVisibleRegion() override; + + MOZ_LAYER_DECL_NAME("RefLayerMLGPU", TYPE_REF) +}; + +class ColorLayerMLGPU final : public ColorLayer, public LayerMLGPU { + public: + explicit ColorLayerMLGPU(LayerManagerMLGPU* aManager); + virtual ~ColorLayerMLGPU(); + + // LayerMLGPU + bool IsContentOpaque() override { return mColor.a >= 1.0f; } + + // Layer + HostLayer* AsHostLayer() override { return this; } + ColorLayerMLGPU* AsColorLayerMLGPU() override { return this; } + Layer* GetLayer() override { return this; } + + MOZ_LAYER_DECL_NAME("ColorLayerMLGPU", TYPE_COLOR) +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_gfx_layers_mlgpu_LayerMLGPU_h diff --git a/gfx/layers/mlgpu/LayerManagerMLGPU.cpp b/gfx/layers/mlgpu/LayerManagerMLGPU.cpp new file mode 100644 index 0000000000..0379ffe785 --- /dev/null +++ b/gfx/layers/mlgpu/LayerManagerMLGPU.cpp @@ -0,0 +1,588 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "LayerManagerMLGPU.h" +#include "LayerTreeInvalidation.h" +#include "PaintedLayerMLGPU.h" +#include "ImageLayerMLGPU.h" +#include "CanvasLayerMLGPU.h" +#include "ContainerLayerMLGPU.h" +#include "GeckoProfiler.h" // for profiler_* +#include "gfxEnv.h" // for gfxEnv +#include "MLGDevice.h" +#include "RenderPassMLGPU.h" +#include "RenderViewMLGPU.h" +#include "ShaderDefinitionsMLGPU.h" +#include "SharedBufferMLGPU.h" +#include "UnitTransforms.h" +#include "TextureSourceProviderMLGPU.h" +#include "TreeTraversal.h" +#include "FrameBuilder.h" +#include "UtilityMLGPU.h" +#include "CompositionRecorder.h" +#include "mozilla/layers/Diagnostics.h" +#include "mozilla/layers/TextRenderer.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/ToString.h" + +#ifdef XP_WIN +# include "mozilla/widget/WinCompositorWidget.h" +# include "mozilla/gfx/DeviceManagerDx.h" +#endif + +namespace mozilla { +namespace layers { + +using namespace gfx; + +static const int kDebugOverlayX = 2; +static const int kDebugOverlayY = 5; +static const int kDebugOverlayMaxWidth = 600; +static const int kDebugOverlayMaxHeight = 96; + +class RecordedFrameMLGPU : public RecordedFrame { + public: + RecordedFrameMLGPU(MLGDevice* aDevice, MLGTexture* aTexture, + const TimeStamp& aTimestamp) + : RecordedFrame(aTimestamp), mDevice(aDevice) { + mSoftTexture = + aDevice->CreateTexture(aTexture->GetSize(), SurfaceFormat::B8G8R8A8, + MLGUsage::Staging, MLGTextureFlags::None); + + aDevice->CopyTexture(mSoftTexture, IntPoint(), aTexture, + IntRect(IntPoint(), aTexture->GetSize())); + } + + ~RecordedFrameMLGPU() { + if (mIsMapped) { + mDevice->Unmap(mSoftTexture); + } + } + + virtual already_AddRefed GetSourceSurface() override { + if (mDataSurf) { + return RefPtr(mDataSurf).forget(); + } + MLGMappedResource map; + if (!mDevice->Map(mSoftTexture, MLGMapType::READ, &map)) { + return nullptr; + } + + mIsMapped = true; + mDataSurf = Factory::CreateWrappingDataSourceSurface( + map.mData, map.mStride, mSoftTexture->GetSize(), + SurfaceFormat::B8G8R8A8); + return RefPtr(mDataSurf).forget(); + } + + private: + RefPtr mDevice; + // Software texture in VRAM. + RefPtr mSoftTexture; + RefPtr mDataSurf; + bool mIsMapped = false; +}; + +LayerManagerMLGPU::LayerManagerMLGPU(widget::CompositorWidget* aWidget) + : mWidget(aWidget), + mDrawDiagnostics(false), + mUsingInvalidation(false), + mCurrentFrame(nullptr), + mDebugFrameNumber(0) { + if (!aWidget) { + return; + } + +#ifdef WIN32 + mDevice = DeviceManagerDx::Get()->GetMLGDevice(); +#endif + if (!mDevice || !mDevice->IsValid()) { + gfxWarning() << "Could not acquire an MLGDevice!"; + return; + } + + mSwapChain = mDevice->CreateSwapChainForWidget(aWidget); + if (!mSwapChain) { + gfxWarning() << "Could not acquire an MLGSwapChain!"; + return; + } + + mDiagnostics = MakeUnique(); + mTextRenderer = new TextRenderer(); +} + +LayerManagerMLGPU::~LayerManagerMLGPU() { + if (mTextureSourceProvider) { + mTextureSourceProvider->Destroy(); + } +} + +bool LayerManagerMLGPU::Initialize() { + if (!mDevice || !mSwapChain) { + return false; + } + + mTextureSourceProvider = new TextureSourceProviderMLGPU(this, mDevice); + return true; +} + +void LayerManagerMLGPU::Destroy() { + if (IsDestroyed()) { + return; + } + + LayerManager::Destroy(); + mProfilerScreenshotGrabber.Destroy(); + + if (mDevice && mDevice->IsValid()) { + mDevice->Flush(); + } + if (mSwapChain) { + mSwapChain->Destroy(); + mSwapChain = nullptr; + } + if (mTextureSourceProvider) { + mTextureSourceProvider->Destroy(); + mTextureSourceProvider = nullptr; + } + mWidget = nullptr; + mDevice = nullptr; +} + +void LayerManagerMLGPU::ForcePresent() { + if (!mDevice->IsValid()) { + return; + } + + IntSize windowSize = mWidget->GetClientSize().ToUnknownSize(); + if (mSwapChain->GetSize() != windowSize) { + return; + } + + mSwapChain->ForcePresent(); +} + +already_AddRefed LayerManagerMLGPU::CreateContainerLayer() { + return MakeAndAddRef(this); +} + +already_AddRefed LayerManagerMLGPU::CreateColorLayer() { + return MakeAndAddRef(this); +} + +already_AddRefed LayerManagerMLGPU::CreateRefLayer() { + return MakeAndAddRef(this); +} + +already_AddRefed LayerManagerMLGPU::CreatePaintedLayer() { + return MakeAndAddRef(this); +} + +already_AddRefed LayerManagerMLGPU::CreateImageLayer() { + return MakeAndAddRef(this); +} + +already_AddRefed LayerManagerMLGPU::CreateCanvasLayer() { + return MakeAndAddRef(this); +} + +TextureFactoryIdentifier LayerManagerMLGPU::GetTextureFactoryIdentifier() { + TextureFactoryIdentifier ident; + if (mDevice) { + ident = mDevice->GetTextureFactoryIdentifier(mWidget); + } + ident.mUsingAdvancedLayers = true; + return ident; +} + +LayersBackend LayerManagerMLGPU::GetBackendType() { + return mDevice ? mDevice->GetLayersBackend() : LayersBackend::LAYERS_NONE; +} + +void LayerManagerMLGPU::SetRoot(Layer* aLayer) { mRoot = aLayer; } + +bool LayerManagerMLGPU::BeginTransaction(const nsCString& aURL) { return true; } + +void LayerManagerMLGPU::BeginTransactionWithDrawTarget( + gfx::DrawTarget* aTarget, const gfx::IntRect& aRect) { + MOZ_ASSERT(!mTarget); + + mTarget = aTarget; + mTargetRect = aRect; +} + +// Helper class for making sure textures are unlocked. +class MOZ_STACK_CLASS AutoUnlockAllTextures final { + public: + explicit AutoUnlockAllTextures(MLGDevice* aDevice) : mDevice(aDevice) {} + ~AutoUnlockAllTextures() { mDevice->UnlockAllTextures(); } + + private: + RefPtr mDevice; +}; + +void LayerManagerMLGPU::EndTransaction(const TimeStamp& aTimeStamp, + EndTransactionFlags aFlags) { + AUTO_PROFILER_LABEL("LayerManager::EndTransaction", GRAPHICS); + + TextureSourceProvider::AutoReadUnlockTextures unlock(mTextureSourceProvider); + + if (!mRoot || (aFlags & END_NO_IMMEDIATE_REDRAW) || !mWidget) { + return; + } + + if (!mDevice->IsValid()) { + // Waiting device reset handling. + return; + } + + mCompositionOpportunityId = mCompositionOpportunityId.Next(); + SetCompositionTime(aTimeStamp); + + mCompositionStartTime = TimeStamp::Now(); + + IntSize windowSize = mWidget->GetClientSize().ToUnknownSize(); + if (windowSize.IsEmpty()) { + return; + } + + // Resize the window if needed. +#ifdef XP_WIN + mWidget->AsWindows()->UpdateCompositorWndSizeIfNecessary(); +#endif + if (mSwapChain->GetSize() != windowSize) { + // Note: all references to the backbuffer must be cleared. + mDevice->SetRenderTarget(nullptr); + if (!mSwapChain->ResizeBuffers(windowSize)) { + gfxCriticalNote << "Could not resize the swapchain (" + << hexa(windowSize.width) << "," + << hexa(windowSize.height) << ")"; + return; + } + } + + // Don't draw the diagnostic overlay if we want to snapshot the output. + mDrawDiagnostics = StaticPrefs::layers_acceleration_draw_fps() && !mTarget; + mUsingInvalidation = StaticPrefs::layers_mlgpu_enable_invalidation(); + mDebugFrameNumber++; + + AL_LOG("--- Compositing frame %d ---\n", mDebugFrameNumber); + + // Compute transforms - and the changed area, if enabled. + mRoot->ComputeEffectiveTransforms(Matrix4x4()); + ComputeInvalidRegion(); + + // Build and execute draw commands, and present. + if (PreRender()) { + Composite(); + PostRender(); + } + + mTextureSourceProvider->FlushPendingNotifyNotUsed(); + + // Finish composition. + mLastCompositionEndTime = TimeStamp::Now(); +} + +void LayerManagerMLGPU::Composite() { + if (gfxEnv::SkipComposition()) { + return; + } + + AUTO_PROFILER_LABEL("LayerManagerMLGPU::Composite", GRAPHICS); + + // Don't composite if we're minimized/hidden, or if there is nothing to draw. + if (mWidget->IsHidden()) { + return; + } + + // Make sure the diagnostic area gets invalidated. We do this now, rather than + // earlier, so we don't accidentally cause extra composites. + Maybe diagnosticRect; + if (mDrawDiagnostics) { + diagnosticRect = + Some(IntRect(kDebugOverlayX, kDebugOverlayY, kDebugOverlayMaxWidth, + kDebugOverlayMaxHeight)); + } + + AL_LOG("Computed invalid region: %s\n", Stringify(mInvalidRegion).c_str()); + + // Now that we have the final invalid region, give it to the swap chain which + // will tell us if we still need to render. + if (!mSwapChain->ApplyNewInvalidRegion(std::move(mInvalidRegion), + diagnosticRect)) { + mProfilerScreenshotGrabber.NotifyEmptyFrame(); + + // Discard the current payloads. These payloads did not require a composite + // (they caused no changes to anything visible), so we don't want to measure + // their latency. + mPayload.Clear(); + + return; + } + + AutoUnlockAllTextures autoUnlock(mDevice); + + mDevice->BeginFrame(); + + RenderLayers(); + + if (mDrawDiagnostics) { + DrawDebugOverlay(); + } + + if (mTarget) { + mSwapChain->CopyBackbuffer(mTarget, mTargetRect); + mTarget = nullptr; + mTargetRect = IntRect(); + } + mSwapChain->Present(); + + // We call this here to mimic the behavior in LayerManagerComposite, as to + // not change what Talos measures. That is, we do not record an empty frame + // as a frame, since we short-circuit at the top of this function. + RecordFrame(); + + mDevice->EndFrame(); + + // Free the old cloned property tree, then clone a new one. Note that we do + // this after compositing, since layer preparation actually mutates the layer + // tree (for example, ImageHost::mLastFrameID). We want the property tree to + // pick up these changes. Similarly, we are careful to not mutate the tree + // in any way that we *don't* want LayerProperties to catch, lest we cause + // extra invalidation. + // + // Note that the old Compositor performs occlusion culling directly on the + // shadow visible region, and does this *before* cloning layer tree + // properties. Advanced Layers keeps the occlusion region separate and + // performs invalidation against the clean layer tree. + mClonedLayerTreeProperties = nullptr; + mClonedLayerTreeProperties = LayerProperties::CloneFrom(mRoot); + + PayloadPresented(TimeStamp::Now()); + + mPayload.Clear(); +} + +void LayerManagerMLGPU::RenderLayers() { + AUTO_PROFILER_LABEL("LayerManagerMLGPU::RenderLayers", GRAPHICS); + + // Traverse the layer tree and assign each layer to a render target. + FrameBuilder builder(this, mSwapChain); + mCurrentFrame = &builder; + + if (!builder.Build()) { + return; + } + + if (mDrawDiagnostics) { + mDiagnostics->RecordPrepareTime( + (TimeStamp::Now() - mCompositionStartTime).ToMilliseconds()); + } + + // Make sure we acquire/release the sync object. + if (!mDevice->Synchronize()) { + // Catastrophic failure - probably a device reset. + return; + } + + TimeStamp start = TimeStamp::Now(); + + // Upload shared buffers. + mDevice->FinishSharedBufferUse(); + + // Prepare the pipeline. + if (mDrawDiagnostics) { + IntSize size = mSwapChain->GetBackBufferInvalidRegion().GetBounds().Size(); + uint32_t numPixels = size.width * size.height; + mDevice->StartDiagnostics(numPixels); + } + + // Execute all render passes. + builder.Render(); + + mProfilerScreenshotGrabber.MaybeGrabScreenshot( + mDevice, builder.GetWidgetRT()->GetTexture()); + + if (mCompositionRecorder) { + bool hasContentPaint = false; + for (CompositionPayload& payload : mPayload) { + if (payload.mType == CompositionPayloadType::eContentPaint) { + hasContentPaint = true; + break; + } + } + + if (hasContentPaint) { + RefPtr frame = new RecordedFrameMLGPU( + mDevice, builder.GetWidgetRT()->GetTexture(), TimeStamp::Now()); + mCompositionRecorder->RecordFrame(frame); + } + } + mCurrentFrame = nullptr; + + if (mDrawDiagnostics) { + mDiagnostics->RecordCompositeTime( + (TimeStamp::Now() - start).ToMilliseconds()); + mDevice->EndDiagnostics(); + } +} + +void LayerManagerMLGPU::DrawDebugOverlay() { + IntSize windowSize = mSwapChain->GetSize(); + + GPUStats stats; + mDevice->GetDiagnostics(&stats); + stats.mScreenPixels = windowSize.width * windowSize.height; + + std::string text = mDiagnostics->GetFrameOverlayString(stats); + RefPtr texture = mTextRenderer->RenderText( + mTextureSourceProvider, text, 600, TextRenderer::FontType::FixedWidth); + if (!texture) { + return; + } + + if (mUsingInvalidation && + (texture->GetSize().width > kDebugOverlayMaxWidth || + texture->GetSize().height > kDebugOverlayMaxHeight)) { + gfxCriticalNote << "Diagnostic overlay exceeds invalidation area: %s" + << ToString(texture->GetSize()).c_str(); + } + + struct DebugRect { + Rect bounds; + Rect texCoords; + }; + + if (!mDiagnosticVertices) { + DebugRect rect; + rect.bounds = + Rect(Point(kDebugOverlayX, kDebugOverlayY), Size(texture->GetSize())); + rect.texCoords = Rect(0.0, 0.0, 1.0, 1.0); + + VertexStagingBuffer instances; + if (!instances.AppendItem(rect)) { + return; + } + + mDiagnosticVertices = mDevice->CreateBuffer( + MLGBufferType::Vertex, instances.NumItems() * instances.SizeOfItem(), + MLGUsage::Immutable, instances.GetBufferStart()); + if (!mDiagnosticVertices) { + return; + } + } + + // Note: we rely on the world transform being correctly left bound by the + // outermost render view. + mDevice->SetScissorRect(Nothing()); + mDevice->SetDepthTestMode(MLGDepthTestMode::Disabled); + mDevice->SetTopology(MLGPrimitiveTopology::UnitQuad); + mDevice->SetVertexShader(VertexShaderID::DiagnosticText); + mDevice->SetVertexBuffer(1, mDiagnosticVertices, sizeof(DebugRect)); + mDevice->SetPixelShader(PixelShaderID::DiagnosticText); + mDevice->SetBlendState(MLGBlendState::Over); + mDevice->SetPSTexture(0, texture); + mDevice->SetSamplerMode(0, SamplerMode::Point); + mDevice->DrawInstanced(4, 1, 0, 0); +} + +void LayerManagerMLGPU::ComputeInvalidRegion() { + // If invalidation is disabled, throw away cloned properties and redraw the + // whole target area. + if (!mUsingInvalidation) { + mInvalidRegion = mTarget ? mTargetRect : mRenderBounds; + mNextFrameInvalidRegion.SetEmpty(); + return; + } + + nsIntRegion changed; + if (mClonedLayerTreeProperties) { + if (!mClonedLayerTreeProperties->ComputeDifferences(mRoot, changed, + nullptr)) { + changed = mRenderBounds; + } + } else { + changed = mRenderBounds; + } + + // We compute the change region, but if we're painting to a target, we save + // it for the next frame instead. + if (mTarget) { + mInvalidRegion = mTargetRect; + mNextFrameInvalidRegion.OrWith(changed); + } else { + mInvalidRegion = std::move(mNextFrameInvalidRegion); + mInvalidRegion.OrWith(changed); + } +} + +void LayerManagerMLGPU::AddInvalidRegion(const nsIntRegion& aRegion) { + mNextFrameInvalidRegion.OrWith(aRegion); +} + +TextureSourceProvider* LayerManagerMLGPU::GetTextureSourceProvider() const { + return mTextureSourceProvider; +} + +bool LayerManagerMLGPU::IsCompositingToScreen() const { return !mTarget; } + +bool LayerManagerMLGPU::AreComponentAlphaLayersEnabled() { + return LayerManager::AreComponentAlphaLayersEnabled(); +} + +bool LayerManagerMLGPU::BlendingRequiresIntermediateSurface() { return true; } + +void LayerManagerMLGPU::EndTransaction(DrawPaintedLayerCallback aCallback, + void* aCallbackData, + EndTransactionFlags aFlags) { + MOZ_CRASH("GFX: Use EndTransaction(aTimeStamp)"); +} + +void LayerManagerMLGPU::ClearCachedResources(Layer* aSubtree) { + Layer* root = aSubtree ? aSubtree : mRoot.get(); + if (!root) { + return; + } + + ForEachNode(root, [](Layer* aLayer) { + LayerMLGPU* layer = aLayer->AsHostLayer()->AsLayerMLGPU(); + if (!layer) { + return; + } + layer->ClearCachedResources(); + }); +} + +void LayerManagerMLGPU::NotifyShadowTreeTransaction() { + if (StaticPrefs::layers_acceleration_draw_fps()) { + mDiagnostics->AddTxnFrame(); + } +} + +void LayerManagerMLGPU::UpdateRenderBounds(const gfx::IntRect& aRect) { + mRenderBounds = aRect; +} + +bool LayerManagerMLGPU::PreRender() { + AUTO_PROFILER_LABEL("LayerManagerMLGPU::PreRender", GRAPHICS); + + widget::WidgetRenderingContext context; + if (!mWidget->PreRender(&context)) { + return false; + } + mWidgetContext = Some(context); + return true; +} + +void LayerManagerMLGPU::PostRender() { + mWidget->PostRender(mWidgetContext.ptr()); + mProfilerScreenshotGrabber.MaybeProcessQueue(); + mWidgetContext = Nothing(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/mlgpu/LayerManagerMLGPU.h b/gfx/layers/mlgpu/LayerManagerMLGPU.h new file mode 100644 index 0000000000..438779a337 --- /dev/null +++ b/gfx/layers/mlgpu/LayerManagerMLGPU.h @@ -0,0 +1,145 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_LAYERMANAGERMLGPU_H +#define MOZILLA_GFX_LAYERMANAGERMLGPU_H + +#include // for uint32_t +#include "mozilla/AlreadyAddRefed.h" // for already_AddRefed +#include "mozilla/Assertions.h" // for AssertionConditionType, MOZ_ASSERT, MOZ_ASSERT_HELPER1 +#include "mozilla/Maybe.h" // for Maybe +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/TimeStamp.h" // for TimeStamp +#include "mozilla/UniquePtr.h" // for UniquePtr +#include "mozilla/gfx/Rect.h" // for IntRect +#include "mozilla/layers/CompositorTypes.h" // for TextureFactoryIdentifier +#include "mozilla/layers/LayerManager.h" // for LayerManager::EndTransactionFlags, LayerManager::DrawPaintedLayerCallback +#include "mozilla/layers/LayerManagerComposite.h" // for HostLayerManager +#include "mozilla/layers/LayersTypes.h" // for LayersBackend +#include "mozilla/layers/MLGPUScreenshotGrabber.h" // for MLGPUScreenshotGrabber +#include "nsRegion.h" // for nsIntRegion +#include "nsStringFwd.h" // for nsCString + +namespace mozilla { +namespace layers { + +class FrameBuilder; +class RenderPassMLGPU; +class SharedBufferMLGPU; +class TextRenderer; +class TextureSourceProviderMLGPU; +class MLGBuffer; +class MLGDevice; +class MLGSwapChain; +class MLGTileBuffer; +struct LayerProperties; + +class LayerManagerMLGPU final : public HostLayerManager { + public: + explicit LayerManagerMLGPU(widget::CompositorWidget* aWidget); + virtual ~LayerManagerMLGPU(); + + bool Initialize(); + void Destroy() override; + + // LayerManager methods + bool BeginTransaction(const nsCString& aURL) override; + void BeginTransactionWithDrawTarget(gfx::DrawTarget* aTarget, + const gfx::IntRect& aRect) override; + void SetRoot(Layer* aLayer) override; + already_AddRefed CreatePaintedLayer() override; + already_AddRefed CreateContainerLayer() override; + already_AddRefed CreateImageLayer() override; + already_AddRefed CreateColorLayer() override; + already_AddRefed CreateCanvasLayer() override; + already_AddRefed CreateRefLayer() override; + + bool AreComponentAlphaLayersEnabled() override; + bool BlendingRequiresIntermediateSurface() override; + + // HostLayerManager methods + void ForcePresent() override; + TextureFactoryIdentifier GetTextureFactoryIdentifier() override; + LayersBackend GetBackendType() override; + void AddInvalidRegion(const nsIntRegion& aRegion) override; + void EndTransaction(const TimeStamp& aTimeStamp, + EndTransactionFlags aFlags) override; + void EndTransaction(DrawPaintedLayerCallback aCallback, void* aCallbackData, + EndTransactionFlags aFlags) override; + Compositor* GetCompositor() const override { return nullptr; } + bool IsCompositingToScreen() const override; + TextureSourceProvider* GetTextureSourceProvider() const override; + void ClearCachedResources(Layer* aSubtree = nullptr) override; + void NotifyShadowTreeTransaction() override; + void UpdateRenderBounds(const gfx::IntRect& aRect) override; + + void InvalidateAll() override { + AddInvalidRegion(nsIntRegion(mRenderBounds)); + } + + LayerManagerMLGPU* AsLayerManagerMLGPU() override { return this; } + const char* Name() const override { return ""; } + + // This should only be called while a FrameBuilder is live. + FrameBuilder* GetCurrentFrame() const { + MOZ_ASSERT(mCurrentFrame); + return mCurrentFrame; + } + MLGDevice* GetDevice() { return mDevice; } + + TimeStamp GetLastCompositionEndTime() const { + return mLastCompositionEndTime; + } + const nsIntRegion& GetRegionToClear() const { return mRegionToClear; } + uint32_t GetDebugFrameNumber() const { return mDebugFrameNumber; } + + private: + void Composite(); + void ComputeInvalidRegion(); + void RenderLayers(); + void DrawDebugOverlay(); + bool PreRender(); + void PostRender(); + + private: + RefPtr mDevice; + RefPtr mSwapChain; + RefPtr mTextureSourceProvider; + RefPtr mTextRenderer; + widget::CompositorWidget* mWidget; + + UniquePtr mClonedLayerTreeProperties; + nsIntRegion mNextFrameInvalidRegion; + gfx::IntRect mRenderBounds; + + // These are per-frame only. + bool mDrawDiagnostics; + bool mUsingInvalidation; + nsIntRegion mInvalidRegion; + Maybe mWidgetContext; + + IntSize mWindowSize; + TimeStamp mCompositionStartTime; + TimeStamp mLastCompositionEndTime; + + RefPtr mTarget; + gfx::IntRect mTargetRect; + FrameBuilder* mCurrentFrame; + + // The debug frame number is incremented every frame and is included in the + // WorldConstants bound to vertex shaders. This allows us to correlate + // a frame in RenderDoc to spew in the console. + uint32_t mDebugFrameNumber; + RefPtr mDiagnosticVertices; + + // Screenshotting for the profiler. + MLGPUScreenshotGrabber mProfilerScreenshotGrabber; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/mlgpu/MLGDevice.cpp b/gfx/layers/mlgpu/MLGDevice.cpp new file mode 100644 index 0000000000..ca6e35e49d --- /dev/null +++ b/gfx/layers/mlgpu/MLGDevice.cpp @@ -0,0 +1,348 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MLGDevice.h" +#include "mozilla/layers/TextureHost.h" +#include "BufferCache.h" +#include "ClearRegionHelper.h" +#include "gfxConfig.h" +#include "mozilla/StaticPrefs_layers.h" +#include "gfxUtils.h" +#include "ShaderDefinitionsMLGPU.h" +#include "SharedBufferMLGPU.h" +#include "UtilityMLGPU.h" + +namespace mozilla { +namespace layers { + +using namespace gfx; +using namespace mlg; + +MLGRenderTarget::MLGRenderTarget(MLGRenderTargetFlags aFlags) + : mFlags(aFlags), mLastDepthStart(-1) {} + +MLGSwapChain::MLGSwapChain() : mIsDoubleBuffered(false) {} + +bool MLGSwapChain::ApplyNewInvalidRegion( + nsIntRegion&& aRegion, const Maybe& aExtraRect) { + // We clamp the invalid region to the backbuffer size, otherwise the present + // can fail. + IntRect bounds(IntPoint(0, 0), GetSize()); + nsIntRegion invalid = std::move(aRegion); + invalid.AndWith(bounds); + if (invalid.IsEmpty()) { + return false; + } + + if (aExtraRect) { + IntRect rect = aExtraRect.value().Intersect(bounds); + if (!rect.IsEmpty()) { + invalid.OrWith(rect); + } + } + + // This area is now invalid in the back and front buffers. Note that the front + // buffer is either totally valid or totally invalid, since either the last + // paint succeeded or was thrown out due to a buffer resize. Effectively, it + // will now contain the invalid region specific to this frame. + mBackBufferInvalid.OrWith(invalid); + AL_LOG("Backbuffer invalid region: %s\n", + Stringify(mBackBufferInvalid).c_str()); + + if (mIsDoubleBuffered) { + mFrontBufferInvalid.OrWith(invalid); + AL_LOG("Frontbuffer invalid region: %s\n", + Stringify(mFrontBufferInvalid).c_str()); + } + return true; +} + +MLGDevice::MLGDevice() + : mTopology(MLGPrimitiveTopology::Unknown), + mInitialized(false), + mIsValid(false), + mCanUseClearView(false), + mCanUseConstantBufferOffsetBinding(false), + mMaxConstantBufferBindSize(0) {} + +MLGDevice::~MLGDevice() = default; + +bool MLGDevice::Initialize() { + if (!mMaxConstantBufferBindSize) { + return Fail("FEATURE_FAILURE_NO_MAX_CB_BIND_SIZE", + "Failed to set a max constant buffer bind size"); + } + if (mMaxConstantBufferBindSize < mlg::kMaxConstantBufferSize) { + // StagingBuffer depends on this value being accurate, so for now we just + // double-check it here. + return Fail("FEATURE_FAILURE_MIN_MAX_CB_BIND_SIZE", + "Minimum constant buffer bind size not met"); + } + + // We allow this to be pref'd off for testing. Switching it off enables + // Direct3D 11.0/Windows 7/OpenGL-style buffer code paths. + if (!StaticPrefs::layers_mlgpu_enable_buffer_sharing_AtStartup()) { + gfxConfig::EnableFallback(Fallback::NO_CONSTANT_BUFFER_OFFSETTING, + "Disabled by pref"); + mCanUseConstantBufferOffsetBinding = false; + } + if (mCanUseConstantBufferOffsetBinding && !VerifyConstantBufferOffsetting()) { + gfxConfig::EnableFallback(Fallback::NO_CONSTANT_BUFFER_OFFSETTING, + "Constant buffer offset binding does not work"); + mCanUseConstantBufferOffsetBinding = false; + } + + // We allow this to be pref'd off for testing. Disabling it turns on + // ID3D11DeviceContext1::ClearView support, which is present on + // newer Windows 8+ drivers. + if (!StaticPrefs::layers_mlgpu_enable_clear_view_AtStartup()) { + mCanUseClearView = false; + } + + // When compositing normal sized layer trees, we typically have small vertex + // buffers. Empirically the vertex and pixel constant buffer sizes are + // generally under 1KB and the vertex constant buffer size is under 8KB. + static const size_t kDefaultVertexBufferSize = 4096; + static const size_t kDefaultVSConstantBufferSize = + 512 * kConstantBufferElementSize; + static const size_t kDefaultPSConstantBufferSize = + 256 * kConstantBufferElementSize; + + // Note: we create these after we've verified all the device-specific + // properties above. + mSharedVertexBuffer = + MakeUnique(this, kDefaultVertexBufferSize); + mSharedVSBuffer = + MakeUnique(this, kDefaultVSConstantBufferSize); + mSharedPSBuffer = + MakeUnique(this, kDefaultPSConstantBufferSize); + + if (!mSharedVertexBuffer->Init() || !mSharedVSBuffer->Init() || + !mSharedPSBuffer->Init()) { + return Fail("FEATURE_FAILURE_ALLOC_SHARED_BUFFER", + "Failed to allocate a shared shader buffer"); + } + + if (StaticPrefs::layers_mlgpu_enable_buffer_cache_AtStartup()) { + mConstantBufferCache = MakeUnique(this); + } + + mInitialized = true; + mIsValid = true; + return true; +} + +void MLGDevice::BeginFrame() { + mSharedVertexBuffer->Reset(); + mSharedPSBuffer->Reset(); + mSharedVSBuffer->Reset(); +} + +void MLGDevice::EndFrame() { + if (mConstantBufferCache) { + mConstantBufferCache->EndFrame(); + } +} + +void MLGDevice::FinishSharedBufferUse() { + mSharedVertexBuffer->PrepareForUsage(); + mSharedPSBuffer->PrepareForUsage(); + mSharedVSBuffer->PrepareForUsage(); +} + +void MLGDevice::SetTopology(MLGPrimitiveTopology aTopology) { + if (mTopology == aTopology) { + return; + } + SetPrimitiveTopology(aTopology); + mTopology = aTopology; +} + +void MLGDevice::SetVertexBuffer(uint32_t aSlot, + const VertexBufferSection* aSection) { + if (!aSection->IsValid()) { + return; + } + SetVertexBuffer(aSlot, aSection->GetBuffer(), aSection->Stride(), + aSection->Offset()); +} + +void MLGDevice::SetPSConstantBuffer(uint32_t aSlot, + const ConstantBufferSection* aSection) { + if (!aSection->IsValid()) { + return; + } + + MLGBuffer* buffer = aSection->GetBuffer(); + + if (aSection->HasOffset()) { + uint32_t first = aSection->Offset(); + uint32_t numConstants = aSection->NumConstants(); + SetPSConstantBuffer(aSlot, buffer, first, numConstants); + } else { + SetPSConstantBuffer(aSlot, buffer); + } +} + +void MLGDevice::SetVSConstantBuffer(uint32_t aSlot, + const ConstantBufferSection* aSection) { + if (!aSection->IsValid()) { + return; + } + + MLGBuffer* buffer = aSection->GetBuffer(); + + if (aSection->HasOffset()) { + uint32_t first = aSection->Offset(); + uint32_t numConstants = aSection->NumConstants(); + SetVSConstantBuffer(aSlot, buffer, first, numConstants); + } else { + SetVSConstantBuffer(aSlot, buffer); + } +} + +void MLGDevice::SetPSTexturesYUV(uint32_t aSlot, TextureSource* aTexture) { + // Note, we don't support tiled YCbCr textures. + const int Y = 0, Cb = 1, Cr = 2; + TextureSource* textures[3] = {aTexture->GetSubSource(Y), + aTexture->GetSubSource(Cb), + aTexture->GetSubSource(Cr)}; + MOZ_ASSERT(textures[0]); + MOZ_ASSERT(textures[1]); + MOZ_ASSERT(textures[2]); + + SetPSTextures(0, 3, textures); +} + +void MLGDevice::SetPSTexture(uint32_t aSlot, TextureSource* aSource) { + SetPSTextures(aSlot, 1, &aSource); +} + +void MLGDevice::SetSamplerMode(uint32_t aIndex, gfx::SamplingFilter aFilter) { + SetSamplerMode(aIndex, FilterToSamplerMode(aFilter)); +} + +bool MLGDevice::Fail(const nsCString& aFailureId, const nsCString* aMessage) { + const char* message = + aMessage ? aMessage->get() : "Failed initializing MLGDeviceD3D11"; + gfxWarning() << "Failure initializing MLGDeviceD3D11: " << message; + mFailureId = aFailureId; + mFailureMessage = message; + return false; +} + +void MLGDevice::UnmapSharedBuffers() { + mSharedVertexBuffer->Reset(); + mSharedPSBuffer->Reset(); + mSharedVSBuffer->Reset(); +} + +RefPtr MLGDevice::GetBufferForColorSpace(YUVColorSpace aColorSpace) { + if (mColorSpaceBuffers[aColorSpace]) { + return mColorSpaceBuffers[aColorSpace]; + } + + YCbCrShaderConstants buffer; + memcpy(&buffer.yuvColorMatrix, + gfxUtils::YuvToRgbMatrix4x3RowMajor(aColorSpace), + sizeof(buffer.yuvColorMatrix)); + + RefPtr resource = CreateBuffer( + MLGBufferType::Constant, sizeof(buffer), MLGUsage::Immutable, &buffer); + if (!resource) { + return nullptr; + } + + mColorSpaceBuffers[aColorSpace] = resource; + return resource; +} + +RefPtr MLGDevice::GetBufferForColorDepthCoefficient( + ColorDepth aColorDepth) { + if (mColorDepthBuffers[aColorDepth]) { + return mColorDepthBuffers[aColorDepth]; + } + + YCbCrColorDepthConstants buffer; + buffer.coefficient = gfx::RescalingFactorForColorDepth(aColorDepth); + + RefPtr resource = CreateBuffer( + MLGBufferType::Constant, sizeof(buffer), MLGUsage::Immutable, &buffer); + if (!resource) { + return nullptr; + } + + mColorDepthBuffers[aColorDepth] = resource; + return resource; +} + +bool MLGDevice::Synchronize() { return true; } + +void MLGDevice::PrepareClearRegion(ClearRegionHelper* aOut, + nsTArray&& aRects, + const Maybe& aSortIndex) { + if (CanUseClearView() && !aSortIndex) { + aOut->mRects = std::move(aRects); + return; + } + + mSharedVertexBuffer->Allocate(&aOut->mInput, aRects.Length(), sizeof(IntRect), + aRects.Elements()); + + ClearConstants consts(aSortIndex ? aSortIndex.value() : 1); + mSharedVSBuffer->Allocate(&aOut->mVSBuffer, consts); +} + +void MLGDevice::DrawClearRegion(const ClearRegionHelper& aHelper) { + // If we've set up vertices for a shader-based clear, execute that now. + if (aHelper.mInput.IsValid()) { + SetTopology(MLGPrimitiveTopology::UnitQuad); + SetVertexShader(VertexShaderID::Clear); + SetVertexBuffer(1, &aHelper.mInput); + SetVSConstantBuffer(kClearConstantBufferSlot, &aHelper.mVSBuffer); + SetBlendState(MLGBlendState::Copy); + SetPixelShader(PixelShaderID::Clear); + DrawInstanced(4, aHelper.mInput.NumVertices(), 0, 0); + return; + } + + // Otherwise, if we have a normal rect list, we wanted to use the faster + // ClearView. + if (!aHelper.mRects.IsEmpty()) { + DeviceColor color(0.0, 0.0, 0.0, 0.0); + ClearView(mCurrentRT, color, aHelper.mRects.Elements(), + aHelper.mRects.Length()); + } +} + +void MLGDevice::WriteAsPNG(MLGTexture* aTexture, const char* aPath) { + MLGMappedResource map; + if (!Map(aTexture, MLGMapType::READ, &map)) { + return; + } + + RefPtr surface = Factory::CreateWrappingDataSourceSurface( + map.mData, map.mStride, aTexture->GetSize(), SurfaceFormat::B8G8R8A8); + gfxUtils::WriteAsPNG(surface, aPath); + + Unmap(aTexture); +} + +RefPtr MLGDevice::CopyAndCreateReadbackTexture( + MLGTexture* aTexture) { + RefPtr copy = + CreateTexture(aTexture->GetSize(), SurfaceFormat::B8G8R8A8, + MLGUsage::Staging, MLGTextureFlags::None); + if (!copy) { + return nullptr; + } + CopyTexture(copy, IntPoint(0, 0), aTexture, + IntRect(IntPoint(0, 0), aTexture->GetSize())); + return copy; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/mlgpu/MLGDevice.h b/gfx/layers/mlgpu/MLGDevice.h new file mode 100644 index 0000000000..a8e49add2c --- /dev/null +++ b/gfx/layers/mlgpu/MLGDevice.h @@ -0,0 +1,481 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_layers_mlgpu_MLGDevice_h +#define mozilla_gfx_layers_mlgpu_MLGDevice_h + +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/EnumeratedArray.h" +#include "mozilla/RefPtr.h" // for already_AddRefed, RefCounted +#include "mozilla/TypedEnumBits.h" +#include "mozilla/WidgetUtils.h" +#include "mozilla/gfx/Types.h" +#include "mozilla/layers/CompositorTypes.h" +#include "mozilla/layers/LayersTypes.h" +#include "ImageTypes.h" +#include "MLGDeviceTypes.h" +#include "nsISupportsImpl.h" +#include "nsString.h" +#include "nsPrintfCString.h" + +namespace mozilla { + +namespace widget { +class CompositorWidget; +} // namespace widget +namespace gfx { +class DrawTarget; +} // namespace gfx + +namespace layers { + +struct GPUStats; +class BufferCache; +class ConstantBufferSection; +class DataTextureSource; +class MLGBufferD3D11; +class MLGDeviceD3D11; +class MLGRenderTargetD3D11; +class MLGResourceD3D11; +class MLGTexture; +class MLGTextureD3D11; +class SharedVertexBuffer; +class SharedConstantBuffer; +class TextureSource; +class VertexBufferSection; +struct ClearRegionHelper; + +class MLGRenderTarget { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MLGRenderTarget) + + virtual gfx::IntSize GetSize() const = 0; + virtual MLGRenderTargetD3D11* AsD3D11() { return nullptr; } + + // Returns the underlying texture of the render target. + virtual MLGTexture* GetTexture() = 0; + + bool HasDepthBuffer() const { + return (mFlags & MLGRenderTargetFlags::ZBuffer) == + MLGRenderTargetFlags::ZBuffer; + } + + int32_t GetLastDepthStart() const { return mLastDepthStart; } + void SetLastDepthStart(int32_t aDepthStart) { mLastDepthStart = aDepthStart; } + + protected: + explicit MLGRenderTarget(MLGRenderTargetFlags aFlags); + virtual ~MLGRenderTarget() = default; + + protected: + MLGRenderTargetFlags mFlags; + + // When using a depth buffer, callers can track the range of depth values + // that were last used. + int32_t mLastDepthStart; +}; + +class MLGSwapChain { + protected: + virtual ~MLGSwapChain() = default; + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MLGSwapChain) + + virtual RefPtr AcquireBackBuffer() = 0; + virtual bool ResizeBuffers(const gfx::IntSize& aSize) = 0; + virtual gfx::IntSize GetSize() const = 0; + + // Present to the screen. + virtual void Present() = 0; + + // Force a present without waiting for the previous frame's present to + // complete. + virtual void ForcePresent() = 0; + + // Copy an area of the backbuffer to a draw target. + virtual void CopyBackbuffer(gfx::DrawTarget* aTarget, + const gfx::IntRect& aBounds) = 0; + + // Free any internal resources. + virtual void Destroy() = 0; + + // Give the new invalid region to the swap chain in preparation for + // acquiring the backbuffer. If the new invalid region is empty, + // this returns false and no composite is required. + // + // The extra rect is used for the debug overlay, which is factored in + // separately to avoid causing unnecessary composites. + bool ApplyNewInvalidRegion(nsIntRegion&& aRegion, + const Maybe& aExtraRect); + + const nsIntRegion& GetBackBufferInvalidRegion() const { + return mBackBufferInvalid; + } + + protected: + MLGSwapChain(); + + protected: + gfx::IntSize mLastPresentSize; + // The swap chain tracks the invalid region of its buffers. After presenting, + // the invalid region for the backbuffer is cleared. If using double + // buffering, it is set to the area of the non-presented buffer that was not + // painted this frame. The initial invalid region each frame comes from + // LayerManagerMLGPU, and is combined with the back buffer's invalid region + // before frame building begins. + nsIntRegion mBackBufferInvalid; + nsIntRegion mFrontBufferInvalid; + bool mIsDoubleBuffered; +}; + +class MLGResource { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MLGResource) + + public: + enum class Type { Buffer, Texture }; + + virtual Type GetType() const = 0; + virtual MLGResourceD3D11* AsResourceD3D11() { return nullptr; } + + protected: + virtual ~MLGResource() = default; +}; + +// A buffer for use as a shader input. +class MLGBuffer : public MLGResource { + public: + Type GetType() const override { return Type::Buffer; } + virtual MLGBufferD3D11* AsD3D11() { return nullptr; } + virtual size_t GetSize() const = 0; + + protected: + virtual ~MLGBuffer() = default; +}; + +// This is a lower-level resource than a TextureSource. It wraps +// a 2D texture. +class MLGTexture : public MLGResource { + public: + Type GetType() const override { return Type::Texture; } + virtual MLGTextureD3D11* AsD3D11() { return nullptr; } + const gfx::IntSize& GetSize() const { return mSize; } + + protected: + gfx::IntSize mSize; +}; + +enum class VertexShaderID { + TexturedQuad, + TexturedVertex, + ColoredQuad, + ColoredVertex, + BlendVertex, + Clear, + MaskCombiner, + DiagnosticText, + MaxShaders +}; + +enum class PixelShaderID { + ColoredQuad, + ColoredVertex, + TexturedQuadRGB, + TexturedQuadRGBA, + TexturedVertexRGB, + TexturedVertexRGBA, + TexturedQuadIMC4, + TexturedQuadIdentityIMC4, + TexturedQuadNV12, + TexturedVertexIMC4, + TexturedVertexIdentityIMC4, + TexturedVertexNV12, + ComponentAlphaQuad, + ComponentAlphaVertex, + BlendMultiply, + BlendScreen, + BlendOverlay, + BlendDarken, + BlendLighten, + BlendColorDodge, + BlendColorBurn, + BlendHardLight, + BlendSoftLight, + BlendDifference, + BlendExclusion, + BlendHue, + BlendSaturation, + BlendColor, + BlendLuminosity, + Clear, + MaskCombiner, + DiagnosticText, + MaxShaders +}; + +class MLGDevice { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MLGDevice) + + MLGDevice(); + + virtual bool Initialize(); + + virtual TextureFactoryIdentifier GetTextureFactoryIdentifier( + widget::CompositorWidget* aWidget) const = 0; + virtual int32_t GetMaxTextureSize() const = 0; + virtual LayersBackend GetLayersBackend() const = 0; + + virtual RefPtr CreateSwapChainForWidget( + widget::CompositorWidget* aWidget) = 0; + + // Markers for when we start and finish issuing "normal" (i.e., non- + // diagnostic) draw commands for the frame. + virtual void StartDiagnostics(uint32_t aInvalidPixels) = 0; + virtual void EndDiagnostics() = 0; + virtual void GetDiagnostics(GPUStats* aStats) = 0; + + // Layers interaction. + virtual RefPtr CreateDataTextureSource( + TextureFlags aFlags) = 0; + + // Resource access + virtual bool Map(MLGResource* aResource, MLGMapType aType, + MLGMappedResource* aMap) = 0; + virtual void Unmap(MLGResource* aResource) = 0; + virtual void UpdatePartialResource(MLGResource* aResource, + const gfx::IntRect* aRect, void* aData, + uint32_t aStride) = 0; + virtual void CopyTexture(MLGTexture* aDest, const gfx::IntPoint& aTarget, + MLGTexture* aSource, const gfx::IntRect& aRect) = 0; + + // Begin a frame. This clears and resets all shared buffers. + virtual void BeginFrame(); + virtual void EndFrame(); + + // State setup commands. + virtual void SetRenderTarget(MLGRenderTarget* aRT) = 0; + virtual MLGRenderTarget* GetRenderTarget() = 0; + virtual void SetViewport(const gfx::IntRect& aRT) = 0; + virtual void SetScissorRect(const Maybe& aScissorRect) = 0; + virtual void SetVertexShader(VertexShaderID aVertexShader) = 0; + virtual void SetPixelShader(PixelShaderID aPixelShader) = 0; + virtual void SetSamplerMode(uint32_t aIndex, SamplerMode aSamplerMode) = 0; + virtual void SetBlendState(MLGBlendState aBlendState) = 0; + virtual void SetVertexBuffer(uint32_t aSlot, MLGBuffer* aBuffer, + uint32_t aStride, uint32_t aOffset = 0) = 0; + virtual void SetVSConstantBuffer(uint32_t aSlot, MLGBuffer* aBuffer) = 0; + virtual void SetPSConstantBuffer(uint32_t aSlot, MLGBuffer* aBuffer) = 0; + virtual void SetPSTextures(uint32_t aSlot, uint32_t aNumTextures, + TextureSource* const* aTextures) = 0; + virtual void SetPSTexture(uint32_t aSlot, MLGTexture* aTexture) = 0; + virtual void SetDepthTestMode(MLGDepthTestMode aMode) = 0; + + // If supported, bind constant buffers at a particular offset. These can only + // be used if CanUseConstantBufferOffsetBinding returns true. + virtual void SetVSConstantBuffer(uint32_t aSlot, MLGBuffer* aBuffer, + uint32_t aFirstConstant, + uint32_t aNumConstants) = 0; + virtual void SetPSConstantBuffer(uint32_t aSlot, MLGBuffer* aBuffer, + uint32_t aFirstConstant, + uint32_t aNumConstants) = 0; + + // Set the topology. No API call is made if the topology has not changed. + // The UnitQuad topology implicity binds a unit quad triangle strip as + // vertex buffer #0. + void SetTopology(MLGPrimitiveTopology aTopology); + + // Set textures that have special binding logic, and bind to multiple slots. + virtual void SetPSTexturesNV12(uint32_t aSlot, TextureSource* aTexture) = 0; + void SetPSTexturesYUV(uint32_t aSlot, TextureSource* aTexture); + + virtual RefPtr CreateBuffer( + MLGBufferType aType, uint32_t aSize, MLGUsage aUsage, + const void* aInitialData = nullptr) = 0; + + virtual RefPtr CreateTexture(const gfx::IntSize& aSize, + gfx::SurfaceFormat aFormat, + MLGUsage aUsage, + MLGTextureFlags aFlags) = 0; + + // Unwrap the underlying GPU texture in the given TextureSource, and re-wrap + // it in an MLGTexture structure. + virtual RefPtr CreateTexture(TextureSource* aSource) = 0; + + virtual RefPtr CreateRenderTarget( + const gfx::IntSize& aSize, + MLGRenderTargetFlags aFlags = MLGRenderTargetFlags::Default) = 0; + + // Clear a render target to the given color, or clear a depth buffer. + virtual void Clear(MLGRenderTarget* aRT, const gfx::DeviceColor& aColor) = 0; + virtual void ClearDepthBuffer(MLGRenderTarget* aRT) = 0; + + // This is only available if CanUseClearView() returns true. + virtual void ClearView(MLGRenderTarget* aRT, const gfx::DeviceColor& aColor, + const gfx::IntRect* aRects, size_t aNumRects) = 0; + + // Drawing Commands + virtual void Draw(uint32_t aVertexCount, uint32_t aOffset) = 0; + virtual void DrawInstanced(uint32_t aVertexCountPerInstance, + uint32_t aInstanceCount, uint32_t aVertexOffset, + uint32_t aInstanceOffset) = 0; + virtual void Flush() = 0; + + // This unlocks any textures that were implicitly locked during drawing. + virtual void UnlockAllTextures() = 0; + + virtual MLGDeviceD3D11* AsD3D11() { return nullptr; } + + // Helpers. + void SetVertexBuffer(uint32_t aSlot, const VertexBufferSection* aSection); + void SetPSConstantBuffer(uint32_t aSlot, + const ConstantBufferSection* aSection); + void SetVSConstantBuffer(uint32_t aSlot, + const ConstantBufferSection* aSection); + void SetPSTexture(uint32_t aSlot, TextureSource* aSource); + void SetSamplerMode(uint32_t aIndex, gfx::SamplingFilter aFilter); + + // This creates or returns a previously created constant buffer, containing + // a YCbCrShaderConstants instance. + RefPtr GetBufferForColorSpace(gfx::YUVColorSpace aColorSpace); + // This creates or returns a previously created constant buffer, containing + // a YCbCrBitDepthConstants instance. + RefPtr GetBufferForColorDepthCoefficient( + gfx::ColorDepth aColorDepth); + + // A shared buffer that can be used to build VertexBufferSections. + SharedVertexBuffer* GetSharedVertexBuffer() { + return mSharedVertexBuffer.get(); + } + // A shared buffer that can be used to build ConstantBufferSections. Intended + // to be used with vertex shaders. + SharedConstantBuffer* GetSharedVSBuffer() { return mSharedVSBuffer.get(); } + // A shared buffer that can be used to build ConstantBufferSections. Intended + // to be used with pixel shaders. + SharedConstantBuffer* GetSharedPSBuffer() { return mSharedPSBuffer.get(); } + // A cache for constant buffers, used when offset-based binding is not + // supported. + BufferCache* GetConstantBufferCache() { return mConstantBufferCache.get(); } + + // Unmap and upload all shared buffers to the GPU. + void FinishSharedBufferUse(); + + // These are used to detect and report initialization failure. + virtual bool IsValid() const { return mInitialized && mIsValid; } + const nsCString& GetFailureId() const { return mFailureId; } + const nsCString& GetFailureMessage() const { return mFailureMessage; } + + // Prepare a clear-region operation to be run at a later time. + void PrepareClearRegion(ClearRegionHelper* aOut, + nsTArray&& aRects, + const Maybe& aSortIndex); + + // Execute a clear-region operation. This may change shader state. + void DrawClearRegion(const ClearRegionHelper& aHelper); + + // If supported, synchronize with the SyncObject given to clients. + virtual bool Synchronize(); + + // If this returns true, ClearView() can be called. + bool CanUseClearView() const { return mCanUseClearView; } + + // If this returns true, constant buffers can be bound at specific offsets for + // a given run of bytes. This is only supported on Windows 8+ for Direct3D 11. + bool CanUseConstantBufferOffsetBinding() const { + return mCanUseConstantBufferOffsetBinding; + } + + // Return the maximum number of elements that can be bound to a constant + // buffer. This is different than the maximum size of a buffer (there is + // no such limit on Direct3D 11.1). + // + // The return value must be a power of two. + size_t GetMaxConstantBufferBindSize() const { + return mMaxConstantBufferBindSize; + } + + // Helper function for unbinding textures since SetPSTexture is overloaded. + void UnsetPSTexture(uint32_t aSlot) { + TextureSource* nullTexture = nullptr; + SetPSTexture(aSlot, nullTexture); + } + + // Debugging helper function for dumping an MLGTexture to a file. + void WriteAsPNG(MLGTexture* aTexture, const char* aPath); + + // Debugging helper function for copying a texture for later dumping to a + // file. + RefPtr CopyAndCreateReadbackTexture(MLGTexture* aTexture); + + protected: + virtual ~MLGDevice(); + + virtual void SetPrimitiveTopology(MLGPrimitiveTopology aTopology) = 0; + + // Optionally run a runtime test to determine if constant buffer offset + // binding works. + virtual bool VerifyConstantBufferOffsetting() { return true; } + + // Used during initialization to record failure reasons. + bool Fail(const nsCString& aFailureId, const nsCString* aMessage); + + // Used during initialization to record failure reasons. Note: our + // MOZ_FORMAT_PRINTF macro does not work on this function, so we + // disable the warning. +#if defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wformat-security" +#endif + template + bool Fail(const char* aFailureId) { + nsCString failureId(aFailureId); + return Fail(failureId, nullptr); + } + template + bool Fail(const char* aFailureId, const char* aMessage, const T&... args) { + nsCString failureId(aFailureId); + nsPrintfCString message(aMessage, args...); + return Fail(failureId, &message); + } +#if defined(__GNUC__) +# pragma GCC diagnostic pop +#endif + + void UnmapSharedBuffers(); + + private: + MLGPrimitiveTopology mTopology; + UniquePtr mSharedVertexBuffer; + UniquePtr mSharedVSBuffer; + UniquePtr mSharedPSBuffer; + UniquePtr mConstantBufferCache; + + nsCString mFailureId; + nsCString mFailureMessage; + bool mInitialized; + + typedef EnumeratedArray> + ColorSpaceArray; + ColorSpaceArray mColorSpaceBuffers; + typedef EnumeratedArray> + ColorDepthArray; + ColorDepthArray mColorDepthBuffers; + + protected: + bool mIsValid; + bool mCanUseClearView; + bool mCanUseConstantBufferOffsetBinding; + size_t mMaxConstantBufferBindSize; + + RefPtr mCurrentRT; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_gfx_layers_mlgpu_MLGDevice_h diff --git a/gfx/layers/mlgpu/MLGDeviceTypes.h b/gfx/layers/mlgpu/MLGDeviceTypes.h new file mode 100644 index 0000000000..3af46e6a36 --- /dev/null +++ b/gfx/layers/mlgpu/MLGDeviceTypes.h @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_layers_mlgpu_MLGDeviceTypes_h +#define mozilla_gfx_layers_mlgpu_MLGDeviceTypes_h + +#include "mozilla/TypedEnumBits.h" +#include "mozilla/gfx/Types.h" + +namespace mozilla { +namespace layers { + +enum class MLGUsage { + // GPU read-only, CPU write once on creation and read/write never. + Immutable, + + // GPU read-only, CPU write-only. Must be mapped with WRITE_DISCARD. + Dynamic, + + // GPU read/write-only, no CPU access. + Default, + + // GPU->CPU transfer, and read from the CPU. + Staging +}; + +enum class MLGDepthTestMode { + Disabled, + Write, + ReadOnly, + AlwaysWrite, + MaxModes +}; + +enum class MLGBufferType : uint32_t { Vertex, Constant }; + +enum class SamplerMode { + // Linear filter, clamped to border. + LinearClamp = 0, + // Linear filter, clamped to transparent pixels. + LinearClampToZero, + // Linear filter, wrap edges. + LinearRepeat, + // Point filter, clamped to border. + Point, + MaxModes +}; + +enum class MLGBlendState { + Copy = 0, + Over, + OverAndPremultiply, + Min, + ComponentAlpha, + MaxStates +}; + +enum class MLGPrimitiveTopology { + Unknown = 0, + TriangleStrip = 1, + TriangleList = 2, + UnitQuad = 3, + UnitTriangle = 4 +}; + +struct MLGMappedResource { + uint8_t* mData; + uint32_t mStride; +}; + +enum class MLGMapType { READ = 0, WRITE, READ_WRITE, WRITE_DISCARD }; + +enum class MLGTextureFlags { None, ShaderResource, RenderTarget }; +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(MLGTextureFlags); + +enum class MLGRenderTargetFlags : uint32_t { Default = 0, ZBuffer = (1 << 0) }; +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(MLGRenderTargetFlags); + +// NVIDIA drivers crash when we supply too many rects to ClearView - it +// seems to cause a stack overflow >= 20 rects. We cap to 12 for now. +static const size_t kMaxClearViewRects = 12; + +static inline SamplerMode FilterToSamplerMode(gfx::SamplingFilter aFilter) { + switch (aFilter) { + case gfx::SamplingFilter::POINT: + return SamplerMode::Point; + case gfx::SamplingFilter::LINEAR: + case gfx::SamplingFilter::GOOD: + return SamplerMode::LinearClamp; + default: + MOZ_ASSERT_UNREACHABLE("Unknown sampler mode"); + return SamplerMode::LinearClamp; + } +} + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_gfx_layers_mlgpu_MLGDeviceTypes_h diff --git a/gfx/layers/mlgpu/MLGPUScreenshotGrabber.cpp b/gfx/layers/mlgpu/MLGPUScreenshotGrabber.cpp new file mode 100644 index 0000000000..01ca9dbf9c --- /dev/null +++ b/gfx/layers/mlgpu/MLGPUScreenshotGrabber.cpp @@ -0,0 +1,336 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MLGPUScreenshotGrabber.h" + +#include "mozilla/RefPtr.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/UniquePtr.h" + +#include "mozilla/layers/ProfilerScreenshots.h" +#include "mozilla/gfx/Point.h" +#include "mozilla/gfx/Swizzle.h" +#include "mozilla/ProfilerMarkers.h" +#include "SharedBufferMLGPU.h" +#include "ShaderDefinitionsMLGPU.h" +#include "nsTArray.h" + +namespace mozilla { + +using namespace gfx; + +namespace layers { + +using namespace mlg; + +/** + * The actual implementation of screenshot grabbing. + * The MLGPUScreenshotGrabberImpl object is destroyed if the profiler is + * disabled and MaybeGrabScreenshot notices it. + */ +class MLGPUScreenshotGrabberImpl final { + public: + explicit MLGPUScreenshotGrabberImpl(const IntSize& aReadbackTextureSize); + ~MLGPUScreenshotGrabberImpl(); + + void GrabScreenshot(MLGDevice* aDevice, MLGTexture* aTexture); + void ProcessQueue(); + + private: + struct QueueItem final { + mozilla::TimeStamp mTimeStamp; + RefPtr mScreenshotReadbackTexture; + gfx::IntSize mScreenshotSize; + gfx::IntSize mWindowSize; + RefPtr mDevice; + uintptr_t mWindowIdentifier; + }; + + RefPtr ScaleDownWindowTargetToSize(MLGDevice* aCompositor, + const gfx::IntSize& aDestSize, + MLGTexture* aWindowTarget, + size_t aLevel); + + struct CachedLevel { + RefPtr mRenderTarget; + RefPtr mVertexBuffer; + RefPtr mWorldConstants; + }; + bool BlitTexture(MLGDevice* aDevice, CachedLevel& aDest, MLGTexture* aSource, + const IntSize& aSourceSize, const IntSize& aDestSize); + + already_AddRefed TakeNextReadbackTexture(MLGDevice* aCompositor); + void ReturnReadbackTexture(MLGTexture* aReadbackTexture); + + nsTArray mCachedLevels; + nsTArray> mAvailableReadbackTextures; + Maybe mCurrentFrameQueueItem; + nsTArray mQueue; + RefPtr mProfilerScreenshots; + const IntSize mReadbackTextureSize; +}; + +MLGPUScreenshotGrabber::MLGPUScreenshotGrabber() = default; + +MLGPUScreenshotGrabber::~MLGPUScreenshotGrabber() = default; + +void MLGPUScreenshotGrabber::MaybeGrabScreenshot(MLGDevice* aDevice, + MLGTexture* aTexture) { + if (ProfilerScreenshots::IsEnabled()) { + if (!mImpl) { + mImpl = MakeUnique( + ProfilerScreenshots::ScreenshotSize()); + } + mImpl->GrabScreenshot(aDevice, aTexture); + } else if (mImpl) { + Destroy(); + } +} + +void MLGPUScreenshotGrabber::MaybeProcessQueue() { + if (ProfilerScreenshots::IsEnabled()) { + if (!mImpl) { + mImpl = MakeUnique( + ProfilerScreenshots::ScreenshotSize()); + } + mImpl->ProcessQueue(); + } else if (mImpl) { + Destroy(); + } +} + +void MLGPUScreenshotGrabber::NotifyEmptyFrame() { +#ifdef MOZ_GECKO_PROFILER + PROFILER_MARKER_UNTYPED("NoCompositorScreenshot because nothing changed", + GRAPHICS); +#endif +} + +void MLGPUScreenshotGrabber::Destroy() { mImpl = nullptr; } + +MLGPUScreenshotGrabberImpl::MLGPUScreenshotGrabberImpl( + const IntSize& aReadbackTextureSize) + : mReadbackTextureSize(aReadbackTextureSize) {} + +MLGPUScreenshotGrabberImpl::~MLGPUScreenshotGrabberImpl() { + // Any queue items in mQueue or mCurrentFrameQueueItem will be lost. + // That's ok: Either the profiler has stopped and we don't care about these + // screenshots, or the window is closing and we don't really need the last + // few frames from the window. +} + +// Scale down aWindowTexture into a MLGTexture of size +// mReadbackTextureSize * (1 << aLevel) and return that MLGTexture. +// Don't scale down by more than a factor of 2 with a single scaling operation, +// because it'll look bad. If higher scales are needed, use another +// intermediate target by calling this function recursively with aLevel + 1. +RefPtr MLGPUScreenshotGrabberImpl::ScaleDownWindowTargetToSize( + MLGDevice* aDevice, const IntSize& aDestSize, MLGTexture* aWindowTexture, + size_t aLevel) { + aDevice->SetScissorRect(Nothing()); + aDevice->SetDepthTestMode(MLGDepthTestMode::Disabled); + aDevice->SetTopology(MLGPrimitiveTopology::UnitQuad); + // DiagnosticText happens to be the simplest shader we have to draw a quad. + aDevice->SetVertexShader(VertexShaderID::DiagnosticText); + aDevice->SetPixelShader(PixelShaderID::DiagnosticText); + aDevice->SetBlendState(MLGBlendState::Copy); + aDevice->SetSamplerMode(0, SamplerMode::LinearClamp); + + if (aLevel == mCachedLevels.Length()) { + RefPtr rt = + aDevice->CreateRenderTarget(mReadbackTextureSize * (1 << aLevel)); + mCachedLevels.AppendElement(CachedLevel{rt, nullptr, nullptr}); + } + MOZ_RELEASE_ASSERT(aLevel < mCachedLevels.Length()); + + RefPtr sourceTarget = aWindowTexture; + IntSize sourceSize = aWindowTexture->GetSize(); + if (aWindowTexture->GetSize().width > aDestSize.width * 2) { + sourceSize = aDestSize * 2; + sourceTarget = ScaleDownWindowTargetToSize(aDevice, sourceSize, + aWindowTexture, aLevel + 1); + } + + if (sourceTarget) { + if (BlitTexture(aDevice, mCachedLevels[aLevel], sourceTarget, sourceSize, + aDestSize)) { + return mCachedLevels[aLevel].mRenderTarget->GetTexture(); + } + } + return nullptr; +} + +bool MLGPUScreenshotGrabberImpl::BlitTexture(MLGDevice* aDevice, + CachedLevel& aLevel, + MLGTexture* aSource, + const IntSize& aSourceSize, + const IntSize& aDestSize) { + MOZ_ASSERT(aLevel.mRenderTarget); + MLGRenderTarget* rt = aLevel.mRenderTarget; + MOZ_ASSERT(aDestSize <= rt->GetSize()); + + struct TextureRect { + Rect bounds; + Rect texCoords; + }; + + if (!aLevel.mVertexBuffer) { + TextureRect rect; + rect.bounds = Rect(Point(), Size(aDestSize)); + rect.texCoords = + Rect(0.0, 0.0, Float(aSourceSize.width) / aSource->GetSize().width, + Float(aSourceSize.height) / aSource->GetSize().height); + + VertexStagingBuffer instances; + if (!instances.AppendItem(rect)) { + return false; + } + + RefPtr vertices = aDevice->CreateBuffer( + MLGBufferType::Vertex, instances.NumItems() * instances.SizeOfItem(), + MLGUsage::Immutable, instances.GetBufferStart()); + if (!vertices) { + return false; + } + + aLevel.mVertexBuffer = vertices; + } + + if (!aLevel.mWorldConstants) { + WorldConstants vsConstants; + Matrix4x4 projection = Matrix4x4::Translation(-1.0, 1.0, 0.0); + projection.PreScale(2.0 / float(rt->GetSize().width), + 2.0 / float(rt->GetSize().height), 1.0f); + projection.PreScale(1.0f, -1.0f, 1.0f); + + memcpy(vsConstants.projection, &projection._11, 64); + vsConstants.targetOffset = Point(); + vsConstants.sortIndexOffset = 0; + vsConstants.debugFrameNumber = 0; + + aLevel.mWorldConstants = + aDevice->CreateBuffer(MLGBufferType::Constant, sizeof(vsConstants), + MLGUsage::Immutable, &vsConstants); + + if (!aLevel.mWorldConstants) { + return false; + } + } + + aDevice->SetRenderTarget(rt); + aDevice->SetPSTexture(0, aSource); + aDevice->SetViewport(IntRect(IntPoint(0, 0), rt->GetSize())); + aDevice->SetVertexBuffer(1, aLevel.mVertexBuffer, sizeof(TextureRect)); + aDevice->SetVSConstantBuffer(kWorldConstantBufferSlot, + aLevel.mWorldConstants); + aDevice->DrawInstanced(4, 1, 0, 0); + return true; +} + +void MLGPUScreenshotGrabberImpl::GrabScreenshot(MLGDevice* aDevice, + MLGTexture* aTexture) { + Size windowSize(aTexture->GetSize()); + float scale = std::min(mReadbackTextureSize.width / windowSize.width, + mReadbackTextureSize.height / windowSize.height); + IntSize scaledSize = IntSize::Round(windowSize * scale); + + // The initial target is non-GPU readable. This copy could probably be + // avoided if we had created the swap chain differently. However we + // don't know if that may inadvertently affect performance in the + // non-profiling case. + RefPtr windowTexture = aDevice->CreateTexture( + aTexture->GetSize(), SurfaceFormat::B8G8R8A8, MLGUsage::Default, + MLGTextureFlags::ShaderResource); + aDevice->CopyTexture(windowTexture, IntPoint(), aTexture, + IntRect(IntPoint(), aTexture->GetSize())); + + RefPtr scaledTarget = + ScaleDownWindowTargetToSize(aDevice, scaledSize, windowTexture, 0); + + if (!scaledTarget) { + PROFILER_MARKER_UNTYPED( + "NoCompositorScreenshot because ScaleDownWindowTargetToSize failed", + GRAPHICS); + return; + } + + RefPtr readbackTexture = TakeNextReadbackTexture(aDevice); + if (!readbackTexture) { + PROFILER_MARKER_UNTYPED( + "NoCompositorScreenshot because AsyncReadbackReadbackTexture creation " + "failed", + GRAPHICS); + return; + } + + aDevice->CopyTexture(readbackTexture, IntPoint(), scaledTarget, + IntRect(IntPoint(), mReadbackTextureSize)); + + // This QueueItem will be added to the queue at the end of the next call to + // ProcessQueue(). This ensures that the ReadbackTexture isn't mapped into + // main memory until the next frame. If we did it in this frame, we'd block on + // the GPU. + mCurrentFrameQueueItem = + Some(QueueItem{TimeStamp::Now(), std::move(readbackTexture), scaledSize, + aTexture->GetSize(), aDevice, + reinterpret_cast(static_cast(this))}); +} + +already_AddRefed +MLGPUScreenshotGrabberImpl::TakeNextReadbackTexture(MLGDevice* aDevice) { + if (!mAvailableReadbackTextures.IsEmpty()) { + RefPtr readbackTexture = mAvailableReadbackTextures[0]; + mAvailableReadbackTextures.RemoveElementAt(0); + return readbackTexture.forget(); + } + return aDevice + ->CreateTexture(mReadbackTextureSize, SurfaceFormat::B8G8R8A8, + MLGUsage::Staging, MLGTextureFlags::None) + .forget(); +} + +void MLGPUScreenshotGrabberImpl::ReturnReadbackTexture( + MLGTexture* aReadbackTexture) { + mAvailableReadbackTextures.AppendElement(aReadbackTexture); +} + +void MLGPUScreenshotGrabberImpl::ProcessQueue() { + if (!mQueue.IsEmpty()) { + if (!mProfilerScreenshots) { + mProfilerScreenshots = new ProfilerScreenshots(); + } + for (const auto& item : mQueue) { + mProfilerScreenshots->SubmitScreenshot( + item.mWindowIdentifier, item.mWindowSize, item.mScreenshotSize, + item.mTimeStamp, [&item](DataSourceSurface* aTargetSurface) { + MLGMappedResource map; + if (!item.mDevice->Map(item.mScreenshotReadbackTexture, + MLGMapType::READ, &map)) { + return false; + } + DataSourceSurface::ScopedMap destMap(aTargetSurface, + DataSourceSurface::WRITE); + bool result = + SwizzleData(map.mData, map.mStride, SurfaceFormat::B8G8R8A8, + destMap.GetData(), destMap.GetStride(), + aTargetSurface->GetFormat(), item.mScreenshotSize); + + item.mDevice->Unmap(item.mScreenshotReadbackTexture); + return result; + }); + ReturnReadbackTexture(item.mScreenshotReadbackTexture); + } + } + mQueue.Clear(); + + if (mCurrentFrameQueueItem) { + mQueue.AppendElement(std::move(*mCurrentFrameQueueItem)); + mCurrentFrameQueueItem = Nothing(); + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/mlgpu/MLGPUScreenshotGrabber.h b/gfx/layers/mlgpu/MLGPUScreenshotGrabber.h new file mode 100644 index 0000000000..1be5c0f0fb --- /dev/null +++ b/gfx/layers/mlgpu/MLGPUScreenshotGrabber.h @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_MLGPUScreenshotGrabber_h +#define mozilla_layers_MLGPUScreenshotGrabber_h + +#include "mozilla/UniquePtr.h" +#include "mozilla/layers/MLGDevice.h" + +namespace mozilla { +namespace layers { + +class MLGPUScreenshotGrabberImpl; + +/** + * Used by LayerManagerComposite to grab snapshots from the compositor and + * submit them to the Gecko profiler. + * Doesn't do any work if the profiler is not running or the "screenshots" + * feature is not enabled. + * Screenshots are scaled down to fit within a fixed size, and read back to + * main memory using async readback. Scaling is done in multiple scale-by-0.5x + * steps using CompositingRenderTargets and Compositor::BlitFromRenderTarget, + * and readback is done using AsyncReadbackBuffers. + */ +class MLGPUScreenshotGrabber final { + public: + MLGPUScreenshotGrabber(); + ~MLGPUScreenshotGrabber(); + + // Scale the contents of aTexture into an appropriately sized MLGTexture + // and read its contents into an AsyncReadbackBuffer. The AsyncReadbackBuffer + // is not mapped into main memory until the second call to + // MaybeProcessQueue() after this call to MaybeGrabScreenshot(). + void MaybeGrabScreenshot(MLGDevice* aDevice, MLGTexture* aTexture); + + // Map the contents of any outstanding AsyncReadbackBuffers from previous + // composites into main memory and submit each screenshot to the profiler. + void MaybeProcessQueue(); + + // Insert a special profiler marker for a composite that didn't do any actual + // compositing, so that the profiler knows why no screenshot was taken for + // this frame. + void NotifyEmptyFrame(); + + // Destroy all Compositor-related resources that this class is holding on to. + void Destroy(); + + private: + // non-null while ProfilerScreenshots::IsEnabled() returns true + UniquePtr mImpl; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_MLGPUScreenshotGrabber_h diff --git a/gfx/layers/mlgpu/MaskOperation.cpp b/gfx/layers/mlgpu/MaskOperation.cpp new file mode 100644 index 0000000000..9976ec4f62 --- /dev/null +++ b/gfx/layers/mlgpu/MaskOperation.cpp @@ -0,0 +1,173 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MaskOperation.h" +#include "FrameBuilder.h" +#include "LayerMLGPU.h" +#include "mozilla/layers/LayersHelpers.h" +#include "MLGDevice.h" +#include "TexturedLayerMLGPU.h" + +namespace mozilla { +namespace layers { + +using namespace gfx; + +MaskOperation::MaskOperation(FrameBuilder* aBuilder) {} + +MaskOperation::MaskOperation(FrameBuilder* aBuilder, MLGTexture* aSource) + : mTexture(aSource) {} + +MaskOperation::~MaskOperation() = default; + +static gfx::Rect ComputeQuadForMaskLayer(Layer* aLayer, const IntSize& aSize) { + const Matrix4x4& transform = aLayer->GetEffectiveTransform(); + MOZ_ASSERT(transform.Is2D(), "Mask layers should not have 3d transforms"); + + Rect bounds(Point(0, 0), Size(aSize)); + return transform.As2D().TransformBounds(bounds); +} + +Rect MaskOperation::ComputeMaskRect(Layer* aLayer) const { + Layer* maskLayer = aLayer->GetMaskLayer() ? aLayer->GetMaskLayer() + : aLayer->GetAncestorMaskLayerAt(0); + MOZ_ASSERT( + (aLayer->GetAncestorMaskLayerCount() == 0 && aLayer->GetMaskLayer()) || + (aLayer->GetAncestorMaskLayerCount() == 1 && !aLayer->GetMaskLayer())); + + return ComputeQuadForMaskLayer(maskLayer, mTexture->GetSize()); +} + +// This is only needed for std::map. +bool MaskTexture::operator<(const MaskTexture& aOther) const { + if (mRect.X() != aOther.mRect.X()) { + return mRect.X() < aOther.mRect.X(); + } + if (mRect.Y() != aOther.mRect.Y()) { + return mRect.Y() < aOther.mRect.Y(); + } + if (mRect.Width() != aOther.mRect.Width()) { + return mRect.Width() < aOther.mRect.Width(); + } + if (mRect.Height() != aOther.mRect.Height()) { + return mRect.Height() < aOther.mRect.Height(); + } + return mSource < aOther.mSource; +} + +RefPtr GetMaskLayerTexture(Layer* aLayer) { + LayerMLGPU* layer = aLayer->AsHostLayer()->AsLayerMLGPU(); + TexturedLayerMLGPU* texLayer = layer->AsTexturedLayerMLGPU(); + if (!texLayer) { + MOZ_ASSERT_UNREACHABLE("Mask layers should be texture layers"); + return nullptr; + } + + RefPtr source = texLayer->BindAndGetTexture(); + if (!source) { + gfxWarning() << "Mask layer does not have a TextureSource"; + return nullptr; + } + return source; +} + +MaskCombineOperation::MaskCombineOperation(FrameBuilder* aBuilder) + : MaskOperation(aBuilder), mBuilder(aBuilder) {} + +MaskCombineOperation::~MaskCombineOperation() = default; + +void MaskCombineOperation::Init(const MaskTextureList& aTextures) { + // All masks for a single layer exist in the same coordinate space. Find the + // area that covers all rects. + Rect area = aTextures[0].mRect; + for (size_t i = 1; i < aTextures.size(); i++) { + area = area.Intersect(aTextures[i].mRect); + } + + // Go through and decide which areas of the textures are relevant. + for (size_t i = 0; i < aTextures.size(); i++) { + Rect rect = aTextures[i].mRect.Intersect(area); + if (rect.IsEmpty()) { + continue; + } + + rect -= aTextures[i].mRect.TopLeft(); + mTextures.push_back(MaskTexture(rect, aTextures[i].mSource)); + } + + IntRect size; + Rect bounds = area; + bounds.RoundOut(); + bounds.ToIntRect(&size); + + if (size.IsEmpty()) { + return; + } + + mTarget = mBuilder->GetDevice()->CreateRenderTarget(size.Size()); + if (mTarget) { + mTexture = mTarget->GetTexture(); + } + mArea = area; +} + +void MaskCombineOperation::PrepareForRendering() { + for (const auto& entry : mTextures) { + Rect texCoords = TextureRectToCoords(entry.mRect, entry.mSource->GetSize()); + + SharedVertexBuffer* shared = mBuilder->GetDevice()->GetSharedVertexBuffer(); + + VertexBufferSection section; + if (!shared->Allocate(§ion, 1, sizeof(texCoords), &texCoords)) { + continue; + } + mInputBuffers.push_back(section); + } +} + +void MaskCombineOperation::Render() { + if (!mTarget) { + return; + } + + RefPtr device = mBuilder->GetDevice(); + + device->SetTopology(MLGPrimitiveTopology::UnitQuad); + device->SetVertexShader(VertexShaderID::MaskCombiner); + + device->SetPixelShader(PixelShaderID::MaskCombiner); + device->SetSamplerMode(0, SamplerMode::LinearClamp); + device->SetBlendState(MLGBlendState::Min); + + // Since the mask operation is effectively an AND operation, we initialize + // the entire r-channel to 1. + device->Clear(mTarget, DeviceColor(1, 0, 0, 1)); + device->SetScissorRect(Nothing()); + device->SetRenderTarget(mTarget); + device->SetViewport(IntRect(IntPoint(0, 0), mTarget->GetSize())); + + for (size_t i = 0; i < mInputBuffers.size(); i++) { + if (!mInputBuffers[i].IsValid()) { + continue; + } + device->SetVertexBuffer(1, &mInputBuffers[i]); + device->SetPSTexture(0, mTextures[i].mSource); + device->DrawInstanced(4, mInputBuffers[i].NumVertices(), 0, 0); + } +} + +void AppendToMaskTextureList(MaskTextureList& aList, Layer* aLayer) { + RefPtr source = GetMaskLayerTexture(aLayer); + if (!source) { + return; + } + + gfx::Rect rect = ComputeQuadForMaskLayer(aLayer, source->GetSize()); + aList.push_back(MaskTexture(rect, source)); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/mlgpu/MaskOperation.h b/gfx/layers/mlgpu/MaskOperation.h new file mode 100644 index 0000000000..2cf74196ea --- /dev/null +++ b/gfx/layers/mlgpu/MaskOperation.h @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_layers_mlgpu_MaskOperation_h +#define mozilla_gfx_layers_mlgpu_MaskOperation_h + +#include "mozilla/RefPtr.h" +#include "mozilla/gfx/Rect.h" +#include "SharedBufferMLGPU.h" +#include + +namespace mozilla { +namespace layers { + +class FrameBuilder; +class Layer; +class MLGDevice; +class MLGRenderTarget; +class MLGTexture; +class TextureSource; + +class MaskOperation { + NS_INLINE_DECL_REFCOUNTING(MaskOperation) + + public: + // For when the exact texture is known ahead of time. + MaskOperation(FrameBuilder* aBuilder, MLGTexture* aSource); + + // Return the mask rectangle in screen coordinates. This function takes a + // layer because a single-texture mask operation is not dependent on a + // specific mask transform. (Multiple mask layer operations are, and they + // ignore the layer parameter). + virtual gfx::Rect ComputeMaskRect(Layer* aLayer) const; + + MLGTexture* GetTexture() const { return mTexture; } + bool IsEmpty() const { return !mTexture; } + + protected: + explicit MaskOperation(FrameBuilder* aBuilder); + virtual ~MaskOperation(); + + protected: + RefPtr mTexture; +}; + +struct MaskTexture { + MaskTexture() : mSource(nullptr) {} + MaskTexture(const gfx::Rect& aRect, TextureSource* aSource) + : mRect(aRect), mSource(aSource) {} + + bool operator<(const MaskTexture& aOther) const; + + gfx::Rect mRect; + RefPtr mSource; +}; + +typedef std::vector MaskTextureList; + +class MaskCombineOperation final : public MaskOperation { + public: + explicit MaskCombineOperation(FrameBuilder* aBuilder); + virtual ~MaskCombineOperation(); + + void Init(const MaskTextureList& aTextures); + + void PrepareForRendering(); + void Render(); + + gfx::Rect ComputeMaskRect(Layer* aLayer) const override { return mArea; } + + private: + FrameBuilder* mBuilder; + gfx::Rect mArea; + MaskTextureList mTextures; + RefPtr mTarget; + + std::vector mInputBuffers; +}; + +RefPtr GetMaskLayerTexture(Layer* aLayer); +void AppendToMaskTextureList(MaskTextureList& aList, Layer* aLayer); + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_gfx_layers_mlgpu_MaskOperation_h diff --git a/gfx/layers/mlgpu/MemoryReportingMLGPU.cpp b/gfx/layers/mlgpu/MemoryReportingMLGPU.cpp new file mode 100644 index 0000000000..efb66220b9 --- /dev/null +++ b/gfx/layers/mlgpu/MemoryReportingMLGPU.cpp @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MemoryReportingMLGPU.h" +#include "nsIMemoryReporter.h" + +namespace mozilla { +namespace layers { +namespace mlg { + +mozilla::Atomic sConstantBufferUsage; +mozilla::Atomic sVertexBufferUsage; +mozilla::Atomic sRenderTargetUsage; + +class MemoryReportingMLGPU final : public nsIMemoryReporter { + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { + if (sConstantBufferUsage) { + MOZ_COLLECT_REPORT("mlgpu-constant-buffers", KIND_OTHER, UNITS_BYTES, + sConstantBufferUsage, + "Advanced Layers shader constant buffers."); + } + if (sVertexBufferUsage) { + MOZ_COLLECT_REPORT("mlgpu-vertex-buffers", KIND_OTHER, UNITS_BYTES, + sVertexBufferUsage, + "Advanced Layers shader vertex buffers."); + } + if (sRenderTargetUsage) { + MOZ_COLLECT_REPORT( + "mlgpu-render-targets", KIND_OTHER, UNITS_BYTES, sRenderTargetUsage, + "Advanced Layers render target textures and depth buffers."); + } + return NS_OK; + } + + private: + ~MemoryReportingMLGPU() = default; +}; + +NS_IMPL_ISUPPORTS(MemoryReportingMLGPU, nsIMemoryReporter); + +void InitializeMemoryReporters() { + RegisterStrongMemoryReporter(new MemoryReportingMLGPU()); +} + +} // namespace mlg +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/mlgpu/MemoryReportingMLGPU.h b/gfx/layers/mlgpu/MemoryReportingMLGPU.h new file mode 100644 index 0000000000..21ac0ea940 --- /dev/null +++ b/gfx/layers/mlgpu/MemoryReportingMLGPU.h @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_layers_mlgpu_MemoryReportingMLGPU_h +#define mozilla_gfx_layers_mlgpu_MemoryReportingMLGPU_h + +#include "mozilla/Atomics.h" + +namespace mozilla { +namespace layers { +namespace mlg { + +void InitializeMemoryReporters(); + +extern mozilla::Atomic sConstantBufferUsage; +extern mozilla::Atomic sVertexBufferUsage; +extern mozilla::Atomic sRenderTargetUsage; + +} // namespace mlg +} // namespace layers +} // namespace mozilla + +#endif // mozilla_gfx_layers_mlgpu_MemoryReportingMLGPU_h diff --git a/gfx/layers/mlgpu/PaintedLayerMLGPU.cpp b/gfx/layers/mlgpu/PaintedLayerMLGPU.cpp new file mode 100644 index 0000000000..cdd7ac386d --- /dev/null +++ b/gfx/layers/mlgpu/PaintedLayerMLGPU.cpp @@ -0,0 +1,219 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PaintedLayerMLGPU.h" +#include "LayerManagerMLGPU.h" +#include "mozilla/layers/LayersHelpers.h" +#include "mozilla/layers/TiledContentHost.h" +#include "UnitTransforms.h" + +namespace mozilla { + +using namespace gfx; + +namespace layers { + +PaintedLayerMLGPU::PaintedLayerMLGPU(LayerManagerMLGPU* aManager) + : PaintedLayer(aManager, static_cast(this)), + LayerMLGPU(aManager) { + MOZ_COUNT_CTOR(PaintedLayerMLGPU); +} + +PaintedLayerMLGPU::~PaintedLayerMLGPU() { + MOZ_COUNT_DTOR(PaintedLayerMLGPU); + + CleanupResources(); +} + +bool PaintedLayerMLGPU::OnPrepareToRender(FrameBuilder* aBuilder) { + // Reset our cached texture pointers. The next call to AssignToView will + // populate them again. + mTexture = nullptr; + mTextureOnWhite = nullptr; + return !!mHost; +} + +void PaintedLayerMLGPU::SetRenderRegion(LayerIntRegion&& aRegion) { + mRenderRegion = std::move(aRegion); + + LayerIntRect bounds(mRenderRegion.GetBounds().TopLeft(), + ViewAs(mTexture->GetSize())); + mRenderRegion.AndWith(bounds); +} + +const LayerIntRegion& PaintedLayerMLGPU::GetDrawRects() { +#ifndef MOZ_IGNORE_PAINT_WILL_RESAMPLE + // Note: we don't set PaintWillResample on our ContentTextureHost. The old + // compositor must do this since ContentHost is responsible for issuing + // draw calls, but in AL we can handle it directly here. + // + // Note that when AL performs CPU-based occlusion culling (the default + // behavior), we might break up the visible region again. If that turns + // out to be a problem, we can factor this into ForEachDrawRect instead. + if (MayResample()) { + mDrawRects = mRenderRegion.GetBounds(); + return mDrawRects; + } +#endif + return mRenderRegion; +} + +bool PaintedLayerMLGPU::SetCompositableHost(CompositableHost* aHost) { + switch (aHost->GetType()) { + case CompositableType::CONTENT_TILED: + case CompositableType::CONTENT_SINGLE: + case CompositableType::CONTENT_DOUBLE: { + if (mHost && mHost != aHost->AsContentHost()) { + mHost->Detach(this); + } + mHost = aHost->AsContentHost(); + if (!mHost) { + gfxWarning() << "ContentHostBase is not a ContentHostTexture"; + } + return true; + } + default: + return false; + } +} + +CompositableHost* PaintedLayerMLGPU::GetCompositableHost() { return mHost; } + +gfx::Point PaintedLayerMLGPU::GetDestOrigin() const { return mDestOrigin; } + +void PaintedLayerMLGPU::AssignToView(FrameBuilder* aBuilder, + RenderViewMLGPU* aView, + Maybe&& aGeometry) { + if (TiledContentHost* tiles = mHost->AsTiledContentHost()) { + // Note: we do not support the low-res buffer yet. + MOZ_ASSERT(tiles->GetLowResBuffer().GetTileCount() == 0); + AssignHighResTilesToView(aBuilder, aView, tiles, aGeometry); + return; + } + + // If we don't have a texture yet, acquire one from the ContentHost now. + if (!mTexture) { + ContentHostTexture* single = mHost->AsContentHostTexture(); + if (!single) { + return; + } + + mTexture = single->AcquireTextureSource(); + if (!mTexture) { + return; + } + mTextureOnWhite = single->AcquireTextureSourceOnWhite(); + mDestOrigin = single->GetOriginOffset(); + } + + // Fall through to the single texture case. + LayerMLGPU::AssignToView(aBuilder, aView, std::move(aGeometry)); +} + +void PaintedLayerMLGPU::AssignHighResTilesToView( + FrameBuilder* aBuilder, RenderViewMLGPU* aView, TiledContentHost* aTileHost, + const Maybe& aGeometry) { + TiledLayerBufferComposite& tiles = aTileHost->GetHighResBuffer(); + + LayerIntRegion compositeRegion = ViewAs(tiles.GetValidRegion()); + compositeRegion.AndWith(GetShadowVisibleRegion()); + if (compositeRegion.IsEmpty()) { + return; + } + + AssignTileBufferToView(aBuilder, aView, tiles, compositeRegion, aGeometry); +} + +void PaintedLayerMLGPU::AssignTileBufferToView( + FrameBuilder* aBuilder, RenderViewMLGPU* aView, + TiledLayerBufferComposite& aTiles, const LayerIntRegion& aCompositeRegion, + const Maybe& aGeometry) { + float resolution = aTiles.GetResolution(); + + // Save these so they can be restored at the end. + float baseOpacity = mComputedOpacity; + LayerIntRegion visible = GetShadowVisibleRegion(); + + for (size_t i = 0; i < aTiles.GetTileCount(); i++) { + TileHost& tile = aTiles.GetTile(i); + if (tile.IsPlaceholderTile()) { + continue; + } + + TileCoordIntPoint coord = aTiles.GetPlacement().TileCoord(i); + // A sanity check that catches a lot of mistakes. + MOZ_ASSERT(coord.x == tile.mTileCoord.x && coord.y == tile.mTileCoord.y); + + IntPoint offset = aTiles.GetTileOffset(coord); + + // Use LayerIntRect here so we don't have to keep re-allocating the region + // to change the unit type. + LayerIntRect tileRect(ViewAs(offset), + ViewAs(aTiles.GetScaledTileSize())); + LayerIntRegion tileDrawRegion = tileRect; + tileDrawRegion.AndWith(aCompositeRegion); + if (tileDrawRegion.IsEmpty()) { + continue; + } + tileDrawRegion.ScaleRoundOut(resolution, resolution); + + // Update layer state for this tile - that includes the texture, visible + // region, and opacity. + mTexture = tile.AcquireTextureSource(); + if (!mTexture) { + continue; + } + + mTextureOnWhite = tile.AcquireTextureSourceOnWhite(); + + SetShadowVisibleRegion(tileDrawRegion); + mComputedOpacity = tile.GetFadeInOpacity(baseOpacity); + mDestOrigin = offset; + + // Yes, it's a bit weird that we're assigning the same layer to the same + // view multiple times. Note that each time, the texture, computed + // opacity, origin, and visible region are updated to match the current + // tile, and we restore these properties after we've finished processing + // all tiles. + Maybe geometry = aGeometry; + LayerMLGPU::AssignToView(aBuilder, aView, std::move(geometry)); + } + + // Restore the computed opacity and visible region. + mComputedOpacity = baseOpacity; + SetShadowVisibleRegion(std::move(visible)); +} + +void PaintedLayerMLGPU::CleanupResources() { + if (mHost) { + mHost->Detach(this); + } + mTexture = nullptr; + mTextureOnWhite = nullptr; + mHost = nullptr; +} + +void PaintedLayerMLGPU::PrintInfo(std::stringstream& aStream, + const char* aPrefix) { + PaintedLayer::PrintInfo(aStream, aPrefix); + if (mHost && mHost->IsAttached()) { + aStream << "\n"; + nsAutoCString pfx(aPrefix); + pfx += " "; + mHost->PrintInfo(aStream, pfx.get()); + } +} + +void PaintedLayerMLGPU::Disconnect() { CleanupResources(); } + +bool PaintedLayerMLGPU::IsContentOpaque() { + return !!(GetContentFlags() & CONTENT_OPAQUE); +} + +void PaintedLayerMLGPU::CleanupCachedResources() { CleanupResources(); } + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/mlgpu/PaintedLayerMLGPU.h b/gfx/layers/mlgpu/PaintedLayerMLGPU.h new file mode 100644 index 0000000000..670ede556a --- /dev/null +++ b/gfx/layers/mlgpu/PaintedLayerMLGPU.h @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_PAINTEDLAYERMLGPU_H +#define MOZILLA_GFX_PAINTEDLAYERMLGPU_H + +#include "LayerManagerMLGPU.h" +#include "mozilla/layers/ContentHost.h" +#include "mozilla/layers/LayerMLGPU.h" +#include "MLGDeviceTypes.h" +#include "nsRegionFwd.h" +#include + +namespace mozilla { +namespace layers { + +class TiledLayerBufferComposite; + +class PaintedLayerMLGPU final : public PaintedLayer, public LayerMLGPU { + public: + explicit PaintedLayerMLGPU(LayerManagerMLGPU* aManager); + virtual ~PaintedLayerMLGPU(); + + // Layer + HostLayer* AsHostLayer() override { return this; } + PaintedLayerMLGPU* AsPaintedLayerMLGPU() override { return this; } + Layer* GetLayer() override { return this; } + bool SetCompositableHost(CompositableHost*) override; + CompositableHost* GetCompositableHost() override; + void Disconnect() override; + bool IsContentOpaque() override; + + // PaintedLayer + void InvalidateRegion(const nsIntRegion& aRegion) override { + MOZ_CRASH("PaintedLayerMLGPU can't fill invalidated regions"); + } + + bool HasComponentAlpha() const { return !!mTextureOnWhite; } + TextureSource* GetTexture() const { return mTexture; } + TextureSource* GetTextureOnWhite() const { + MOZ_ASSERT(HasComponentAlpha()); + return mTextureOnWhite; + } + gfx::Point GetDestOrigin() const; + + SamplerMode GetSamplerMode() { + // Note that when resamping, we must break the texture coordinates into + // no-repeat rects. When we have simple integer translations we can + // simply wrap around the edge of the buffer texture. + return MayResample() ? SamplerMode::LinearClamp : SamplerMode::LinearRepeat; + } + + void SetRenderRegion(LayerIntRegion&& aRegion) override; + + // To avoid sampling issues with complex regions and transforms, we + // squash the visible region for PaintedLayers into a single draw + // rect. RenderPasses should use this method instead of GetRenderRegion. + const LayerIntRegion& GetDrawRects(); + + MOZ_LAYER_DECL_NAME("PaintedLayerMLGPU", TYPE_PAINTED) + + void CleanupCachedResources(); + + protected: + void PrintInfo(std::stringstream& aStream, const char* aPrefix) override; + bool OnPrepareToRender(FrameBuilder* aBuilder) override; + + // We override this to support tiling. + void AssignToView(FrameBuilder* aBuilder, RenderViewMLGPU* aView, + Maybe&& aGeometry) override; + + void AssignHighResTilesToView(FrameBuilder* aBuilder, RenderViewMLGPU* aView, + TiledContentHost* aTileHost, + const Maybe& aGeometry); + + // Helper for Assign*TilesToView. + void AssignTileBufferToView(FrameBuilder* aBuilder, RenderViewMLGPU* aView, + TiledLayerBufferComposite& aTiles, + const LayerIntRegion& aCompositeRegion, + const Maybe& aGeometry); + + void CleanupResources(); + + private: + RefPtr mHost; + RefPtr mTexture; + RefPtr mTextureOnWhite; +#ifndef MOZ_IGNORE_PAINT_WILL_RESAMPLE + LayerIntRegion mDrawRects; +#endif + gfx::IntPoint mDestOrigin; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/mlgpu/RenderPassMLGPU-inl.h b/gfx/layers/mlgpu/RenderPassMLGPU-inl.h new file mode 100644 index 0000000000..6e497a8595 --- /dev/null +++ b/gfx/layers/mlgpu/RenderPassMLGPU-inl.h @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_layers_mlgpu_RenderPassMLGPU_inl_h +#define mozilla_gfx_layers_mlgpu_RenderPassMLGPU_inl_h + +namespace mozilla { +namespace layers { + +template +static inline bool AddShaderTriangles(VertexStagingBuffer* aBuffer, + const Traits& aTraits, + const gfx::Polygon* aGeometry = nullptr) { + typedef typename Traits::TriangleVertices TriangleVertices; + typedef typename Traits::FirstTriangle FirstTriangle; + typedef typename Traits::SecondTriangle SecondTriangle; + + if (!aGeometry) { + TriangleVertices base1 = aTraits.MakeVertex(FirstTriangle()); + TriangleVertices base2 = aTraits.MakeVertex(SecondTriangle()); + auto data1 = aTraits.MakeVertexData(FirstTriangle()); + auto data2 = aTraits.MakeVertexData(SecondTriangle()); + return aBuffer->PrependItem(base1, data1) && + aBuffer->PrependItem(base2, data2); + } + + auto triangles = aTraits.GenerateTriangles(*aGeometry); + for (const auto& triangle : triangles) { + TriangleVertices base = aTraits.MakeVertex(triangle); + auto data = aTraits.MakeVertexData(triangle); + if (!aBuffer->PrependItem(base, data)) { + return false; + } + } + return true; +} + +template +inline bool BatchRenderPass::Txn::AddImpl(const Traits& aTraits) { + VertexStagingBuffer* instances = mPass->GetInstances(); + + if (mPass->mGeometry == GeometryMode::Polygon) { + if (const Maybe& geometry = aTraits.geometry()) { + gfx::Polygon polygon = geometry->ClipPolygon(aTraits.rect()); + if (polygon.IsEmpty()) { + return true; + } + return AddShaderTriangles(instances, aTraits, &polygon); + } + return AddShaderTriangles(instances, aTraits); + } + + typedef typename Traits::UnitQuadVertex UnitQuadVertex; + typedef typename Traits::UnitQuad UnitQuad; + + UnitQuadVertex base = aTraits.MakeUnitQuadVertex(); + auto data = aTraits.MakeVertexData(UnitQuad()); + return instances->AddItem(base, data); +} + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_gfx_layers_mlgpu_RenderPassMLGPU_inl_h diff --git a/gfx/layers/mlgpu/RenderPassMLGPU.cpp b/gfx/layers/mlgpu/RenderPassMLGPU.cpp new file mode 100644 index 0000000000..1fd970e4a8 --- /dev/null +++ b/gfx/layers/mlgpu/RenderPassMLGPU.cpp @@ -0,0 +1,971 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "RenderPassMLGPU.h" +#include "ContainerLayerMLGPU.h" +#include "FrameBuilder.h" +#include "ImageLayerMLGPU.h" +#include "MaskOperation.h" +#include "MLGDevice.h" +#include "PaintedLayerMLGPU.h" +#include "RenderViewMLGPU.h" +#include "ShaderDefinitionsMLGPU.h" +#include "ShaderDefinitionsMLGPU-inl.h" +#include "SharedBufferMLGPU.h" +#include "mozilla/layers/LayersHelpers.h" +#include "mozilla/layers/LayersMessages.h" +#include "RenderPassMLGPU-inl.h" + +namespace mozilla { +namespace layers { + +using namespace gfx; + +ItemInfo::ItemInfo(FrameBuilder* aBuilder, RenderViewMLGPU* aView, + LayerMLGPU* aLayer, int32_t aSortOrder, + const IntRect& aBounds, Maybe&& aGeometry) + : view(aView), + layer(aLayer), + type(RenderPassType::Unknown), + layerIndex(kInvalidResourceIndex), + sortOrder(aSortOrder), + bounds(aBounds), + geometry(std::move(aGeometry)) { + const Matrix4x4& transform = aLayer->GetLayer()->GetEffectiveTransform(); + + Matrix transform2D; + if (!geometry && transform.Is2D(&transform2D) && + transform2D.IsRectilinear()) { + this->rectilinear = true; + if (transform2D.IsIntegerTranslation()) { + this->translation = + Some(IntPoint::Truncate(transform2D.GetTranslation())); + } + } else { + this->rectilinear = false; + } + + // Layers can have arbitrary clips or transforms, and we can't use built-in + // scissor functionality when batching. Instead, pixel shaders will write + // transparent pixels for positions outside of the clip. Unfortunately that + // breaks z-buffering because the transparent pixels will still write to + // the depth buffer. + // + // To make this work, we clamp the final vertices in the vertex shader to + // the clip rect. We can only do this for rectilinear transforms. If a + // transform can produce a rotation or perspective change, then we might + // accidentally change the geometry. These items are not treated as + // opaque. + // + // Also, we someday want non-rectilinear items to be antialiased with DEAA, + // and we can't do this if the items are rendered front-to-back, since + // such items cannot be blended. (Though we could consider adding these + // items in two separate draw calls, one for DEAA and for not - that is + // definitely future work.) + if (aLayer->GetComputedOpacity() != 1.0f || aLayer->GetMask() || + !aLayer->IsContentOpaque() || !rectilinear) { + this->opaque = false; + this->renderOrder = RenderOrder::BackToFront; + } else { + this->opaque = true; + this->renderOrder = aView->HasDepthBuffer() ? RenderOrder::FrontToBack + : RenderOrder::BackToFront; + } + + this->type = RenderPassMLGPU::GetPreferredPassType(aBuilder, *this); +} + +RenderPassType RenderPassMLGPU::GetPreferredPassType(FrameBuilder* aBuilder, + const ItemInfo& aItem) { + LayerMLGPU* layer = aItem.layer; + switch (layer->GetType()) { + case Layer::TYPE_COLOR: { + if (aBuilder->GetDevice()->CanUseClearView() && + aItem.HasRectTransformAndClip() && aItem.translation && + aItem.opaque && !aItem.view->HasDepthBuffer()) { + // Note: we don't have ClearView set up to do depth buffer writes, so we + // exclude depth buffering from the test above. + return RenderPassType::ClearView; + } + return RenderPassType::SolidColor; + } + case Layer::TYPE_PAINTED: { + PaintedLayerMLGPU* painted = layer->AsPaintedLayerMLGPU(); + if (painted->HasComponentAlpha()) { + return RenderPassType::ComponentAlpha; + } + return RenderPassType::SingleTexture; + } + case Layer::TYPE_CANVAS: + return RenderPassType::SingleTexture; + case Layer::TYPE_IMAGE: { + ImageHost* host = layer->AsTexturedLayerMLGPU()->GetImageHost(); + TextureHost* texture = host->CurrentTextureHost(); + if (texture->GetReadFormat() == SurfaceFormat::YUV || + texture->GetReadFormat() == SurfaceFormat::NV12 || + texture->GetReadFormat() == SurfaceFormat::P010 || + texture->GetReadFormat() == SurfaceFormat::P016) { + return RenderPassType::Video; + } + return RenderPassType::SingleTexture; + } + case Layer::TYPE_CONTAINER: + return RenderPassType::RenderView; + default: + return RenderPassType::Unknown; + } +} + +RefPtr RenderPassMLGPU::CreatePass(FrameBuilder* aBuilder, + const ItemInfo& aItem) { + switch (aItem.type) { + case RenderPassType::SolidColor: + return MakeAndAddRef(aBuilder, aItem); + case RenderPassType::SingleTexture: + return MakeAndAddRef(aBuilder, aItem); + case RenderPassType::RenderView: + return MakeAndAddRef(aBuilder, aItem); + case RenderPassType::Video: + return MakeAndAddRef(aBuilder, aItem); + case RenderPassType::ComponentAlpha: + return MakeAndAddRef(aBuilder, aItem); + case RenderPassType::ClearView: + return MakeAndAddRef(aBuilder, aItem); + default: + return nullptr; + } +} + +RenderPassMLGPU::RenderPassMLGPU(FrameBuilder* aBuilder, const ItemInfo& aItem) + : mBuilder(aBuilder), + mDevice(aBuilder->GetDevice()), + mLayerBufferIndex(aBuilder->CurrentLayerBufferIndex()), + mMaskRectBufferIndex(kInvalidResourceIndex), + mPrepared(false) {} + +RenderPassMLGPU::~RenderPassMLGPU() = default; + +bool RenderPassMLGPU::IsCompatible(const ItemInfo& aItem) { + if (GetType() != aItem.type) { + return false; + } + if (mLayerBufferIndex != mBuilder->CurrentLayerBufferIndex()) { + return false; + } + return true; +} + +bool RenderPassMLGPU::AcceptItem(ItemInfo& aInfo) { + MOZ_ASSERT(IsCompatible(aInfo)); + + if (!AddToPass(aInfo.layer, aInfo)) { + return false; + } + + if (aInfo.renderOrder == RenderOrder::BackToFront) { + mAffectedRegion.OrWith(aInfo.bounds); + mAffectedRegion.SimplifyOutward(4); + } + return true; +} + +bool RenderPassMLGPU::Intersects(const ItemInfo& aItem) { + MOZ_ASSERT(aItem.renderOrder == RenderOrder::BackToFront); + return !mAffectedRegion.Intersect(aItem.bounds).IsEmpty(); +} + +void RenderPassMLGPU::PrepareForRendering() { mPrepared = true; } + +ShaderRenderPass::ShaderRenderPass(FrameBuilder* aBuilder, + const ItemInfo& aItem) + : RenderPassMLGPU(aBuilder, aItem), + mGeometry(GeometryMode::Unknown), + mHasRectTransformAndClip(aItem.HasRectTransformAndClip()) { + mMask = aItem.layer->GetMask(); + if (mMask) { + mMaskRectBufferIndex = mBuilder->CurrentMaskRectBufferIndex(); + } +} + +bool ShaderRenderPass::IsCompatible(const ItemInfo& aItem) { + MOZ_ASSERT(mGeometry != GeometryMode::Unknown); + + if (!RenderPassMLGPU::IsCompatible(aItem)) { + return false; + } + + // A masked batch cannot accept non-masked items, since the pixel shader + // bakes in whether a mask is present. Also, the pixel shader can only bind + // one specific mask at a time. + if (aItem.layer->GetMask() != mMask) { + return false; + } + if (mMask && mBuilder->CurrentMaskRectBufferIndex() != mMaskRectBufferIndex) { + return false; + } + + // We key batches on this property, since we can use more efficient pixel + // shaders if we don't need to propagate a clip and a mask. + if (mHasRectTransformAndClip != aItem.HasRectTransformAndClip()) { + return false; + } + + // We should be assured at this point, that if the item requires complex + // geometry, then it should have already been rejected from a unit-quad + // batch. Therefore this batch should be in polygon mode. + MOZ_ASSERT_IF(aItem.geometry.isSome(), mGeometry == GeometryMode::Polygon); + return true; +} + +void ShaderRenderPass::SetGeometry(const ItemInfo& aItem, GeometryMode aMode) { + MOZ_ASSERT(mGeometry == GeometryMode::Unknown); + + if (aMode == GeometryMode::Unknown) { + mGeometry = mHasRectTransformAndClip ? GeometryMode::UnitQuad + : GeometryMode::Polygon; + } else { + mGeometry = aMode; + } + + // Since we process layers front-to-back, back-to-front items are + // in the wrong order. We address this by automatically reversing + // the buffers we use to build vertices. + if (aItem.renderOrder != RenderOrder::FrontToBack) { + mInstances.SetReversed(); + } +} + +void ShaderRenderPass::PrepareForRendering() { + if (mInstances.IsEmpty()) { + return; + } + if (!mDevice->GetSharedVertexBuffer()->Allocate(&mInstanceBuffer, + mInstances) || + !SetupPSBuffer0(GetOpacity()) || !OnPrepareBuffers()) { + return; + } + return RenderPassMLGPU::PrepareForRendering(); +} + +bool ShaderRenderPass::SetupPSBuffer0(float aOpacity) { + if (aOpacity == 1.0f && !HasMask()) { + mPSBuffer0 = mBuilder->GetDefaultMaskInfo(); + return true; + } + + MaskInformation cb(aOpacity, HasMask()); + return mDevice->GetSharedPSBuffer()->Allocate(&mPSBuffer0, cb); +} + +void ShaderRenderPass::ExecuteRendering() { + if (mInstances.IsEmpty()) { + return; + } + + // Change the blend state if needed. + if (Maybe blendState = GetBlendState()) { + mDevice->SetBlendState(blendState.value()); + } + + mDevice->SetPSConstantBuffer(0, &mPSBuffer0); + if (MaskOperation* mask = GetMask()) { + mDevice->SetPSTexture(kMaskLayerTextureSlot, mask->GetTexture()); + mDevice->SetSamplerMode(kMaskSamplerSlot, SamplerMode::LinearClampToZero); + } + + SetupPipeline(); + + if (mGeometry == GeometryMode::Polygon) { + mDevice->SetTopology(MLGPrimitiveTopology::UnitTriangle); + } else { + mDevice->SetTopology(MLGPrimitiveTopology::UnitQuad); + } + mDevice->SetVertexBuffer(1, &mInstanceBuffer); + + if (mGeometry == GeometryMode::Polygon) { + mDevice->DrawInstanced(3, mInstanceBuffer.NumVertices(), 0, 0); + } else { + mDevice->DrawInstanced(4, mInstanceBuffer.NumVertices(), 0, 0); + } +} + +static inline DeviceColor ComputeLayerColor(LayerMLGPU* aLayer, + const DeviceColor& aColor) { + float opacity = aLayer->GetComputedOpacity(); + return DeviceColor(aColor.r * aColor.a * opacity, + aColor.g * aColor.a * opacity, + aColor.b * aColor.a * opacity, aColor.a * opacity); +} + +ClearViewPass::ClearViewPass(FrameBuilder* aBuilder, const ItemInfo& aItem) + : RenderPassMLGPU(aBuilder, aItem), mView(aItem.view) { + // Note: we could write to the depth buffer, but since the depth buffer is + // disabled by default, we don't bother yet. + MOZ_ASSERT(!mView->HasDepthBuffer()); + + ColorLayer* colorLayer = aItem.layer->GetLayer()->AsColorLayer(); + mColor = ComputeLayerColor(aItem.layer, colorLayer->GetColor()); +} + +bool ClearViewPass::IsCompatible(const ItemInfo& aItem) { + if (!RenderPassMLGPU::IsCompatible(aItem)) { + return false; + } + + // These should be true if we computed a ClearView pass type. + MOZ_ASSERT(aItem.translation); + MOZ_ASSERT(aItem.opaque); + MOZ_ASSERT(aItem.HasRectTransformAndClip()); + + // Each call only supports a single color. + ColorLayer* colorLayer = aItem.layer->GetLayer()->AsColorLayer(); + if (mColor != ComputeLayerColor(aItem.layer, colorLayer->GetColor())) { + return false; + } + + // We don't support opacity here since it would not blend correctly. + MOZ_ASSERT(mColor.a == 1.0f); + return true; +} + +bool ClearViewPass::AddToPass(LayerMLGPU* aItem, ItemInfo& aInfo) { + const LayerIntRegion& region = aItem->GetRenderRegion(); + for (auto iter = region.RectIter(); !iter.Done(); iter.Next()) { + IntRect rect = iter.Get().ToUnknownRect(); + rect += aInfo.translation.value(); + rect -= mView->GetTargetOffset(); + mRects.AppendElement(rect); + } + return true; +} + +void ClearViewPass::ExecuteRendering() { + mDevice->ClearView(mDevice->GetRenderTarget(), mColor, mRects.Elements(), + mRects.Length()); +} + +SolidColorPass::SolidColorPass(FrameBuilder* aBuilder, const ItemInfo& aItem) + : BatchRenderPass(aBuilder, aItem) { + SetDefaultGeometry(aItem); +} + +bool SolidColorPass::AddToPass(LayerMLGPU* aLayer, ItemInfo& aInfo) { + MOZ_ASSERT(aLayer->GetType() == Layer::TYPE_COLOR); + + ColorLayer* colorLayer = aLayer->GetLayer()->AsColorLayer(); + + Txn txn(this); + + gfx::DeviceColor color = ComputeLayerColor(aLayer, colorLayer->GetColor()); + + const LayerIntRegion& region = aLayer->GetRenderRegion(); + for (auto iter = region.RectIter(); !iter.Done(); iter.Next()) { + const IntRect rect = iter.Get().ToUnknownRect(); + ColorTraits traits(aInfo, Rect(rect), color); + + if (!txn.Add(traits)) { + return false; + } + } + return txn.Commit(); +} + +float SolidColorPass::GetOpacity() const { + // Note our pixel shader just ignores the opacity, since we baked it + // into our color values already. Just return 1, which ensures we can + // use the default constant buffer binding. + return 1.0f; +} + +void SolidColorPass::SetupPipeline() { + if (mGeometry == GeometryMode::UnitQuad) { + mDevice->SetVertexShader(VertexShaderID::ColoredQuad); + mDevice->SetPixelShader(PixelShaderID::ColoredQuad); + } else { + mDevice->SetVertexShader(VertexShaderID::ColoredVertex); + mDevice->SetPixelShader(PixelShaderID::ColoredVertex); + } +} + +TexturedRenderPass::TexturedRenderPass(FrameBuilder* aBuilder, + const ItemInfo& aItem) + : BatchRenderPass(aBuilder, aItem), mTextureFlags(TextureFlags::NO_FLAGS) {} + +TexturedRenderPass::Info::Info(const ItemInfo& aItem, PaintedLayerMLGPU* aLayer) + : item(aItem), + textureSize(aLayer->GetTexture()->GetSize()), + destOrigin(aLayer->GetDestOrigin()), + decomposeIntoNoRepeatRects(aLayer->MayResample()) {} + +TexturedRenderPass::Info::Info(const ItemInfo& aItem, + TexturedLayerMLGPU* aLayer) + : item(aItem), + textureSize(aLayer->GetTexture()->GetSize()), + scale(aLayer->GetPictureScale()), + decomposeIntoNoRepeatRects(false) {} + +TexturedRenderPass::Info::Info(const ItemInfo& aItem, + ContainerLayerMLGPU* aLayer) + : item(aItem), + textureSize(aLayer->GetTargetSize()), + destOrigin(aLayer->GetTargetOffset()), + decomposeIntoNoRepeatRects(false) {} + +bool TexturedRenderPass::AddItem(Txn& aTxn, const Info& aInfo, + const Rect& aDrawRect) { + if (mGeometry == GeometryMode::Polygon) { + // This path will not clamp the draw rect to the layer clip, so we can pass + // the draw rect texture rects straight through. + return AddClippedItem(aTxn, aInfo, aDrawRect); + } + + const ItemInfo& item = aInfo.item; + + MOZ_ASSERT(!item.geometry); + MOZ_ASSERT(item.HasRectTransformAndClip()); + MOZ_ASSERT(mHasRectTransformAndClip); + + const Matrix4x4& fullTransform = + item.layer->GetLayer()->GetEffectiveTransformForBuffer(); + Matrix transform = fullTransform.As2D(); + Matrix inverse = transform; + if (!inverse.Invert()) { + // Degenerate transforms are not visible, since there is no mapping to + // screen space. Just return without adding any draws. + return true; + } + MOZ_ASSERT(inverse.IsRectilinear()); + + // Transform the clip rect. + IntRect clipRect = item.layer->GetComputedClipRect().ToUnknownRect(); + clipRect += item.view->GetTargetOffset(); + + // Clip and adjust the texture rect. + Rect localClip = inverse.TransformBounds(Rect(clipRect)); + Rect clippedDrawRect = aDrawRect.Intersect(localClip); + if (clippedDrawRect.IsEmpty()) { + return true; + } + + return AddClippedItem(aTxn, aInfo, clippedDrawRect); +} + +bool TexturedRenderPass::AddClippedItem(Txn& aTxn, const Info& aInfo, + const gfx::Rect& aDrawRect) { + float xScale = 1.0; + float yScale = 1.0; + if (aInfo.scale) { + xScale = aInfo.scale->width; + yScale = aInfo.scale->height; + } + + Point offset = aDrawRect.TopLeft() - aInfo.destOrigin; + Rect textureRect(offset.x * xScale, offset.y * yScale, + aDrawRect.Width() * xScale, aDrawRect.Height() * yScale); + + Rect textureCoords = TextureRectToCoords(textureRect, aInfo.textureSize); + if (mTextureFlags & TextureFlags::ORIGIN_BOTTOM_LEFT) { + textureCoords.MoveToY(1.0 - textureCoords.Y()); + textureCoords.SetHeight(-textureCoords.Height()); + } + + if (!aInfo.decomposeIntoNoRepeatRects) { + // Fast, normal case, we can use the texture coordinates as-s and the caller + // will use a repeat sampler if needed. + TexturedTraits traits(aInfo.item, aDrawRect, textureCoords); + if (!aTxn.Add(traits)) { + return false; + } + } else { + Rect layerRects[4]; + Rect textureRects[4]; + size_t numRects = DecomposeIntoNoRepeatRects(aDrawRect, textureCoords, + &layerRects, &textureRects); + + for (size_t i = 0; i < numRects; i++) { + TexturedTraits traits(aInfo.item, layerRects[i], textureRects[i]); + if (!aTxn.Add(traits)) { + return false; + } + } + } + return true; +} + +SingleTexturePass::SingleTexturePass(FrameBuilder* aBuilder, + const ItemInfo& aItem) + : TexturedRenderPass(aBuilder, aItem), + mSamplerMode(SamplerMode::LinearClamp), + mOpacity(1.0f) { + SetDefaultGeometry(aItem); +} + +bool SingleTexturePass::AddToPass(LayerMLGPU* aLayer, ItemInfo& aItem) { + RefPtr texture; + + SamplerMode sampler; + TextureFlags flags = TextureFlags::NO_FLAGS; + if (PaintedLayerMLGPU* paintedLayer = aLayer->AsPaintedLayerMLGPU()) { + if (paintedLayer->HasComponentAlpha()) { + return false; + } + texture = paintedLayer->GetTexture(); + sampler = paintedLayer->GetSamplerMode(); + } else if (TexturedLayerMLGPU* texLayer = aLayer->AsTexturedLayerMLGPU()) { + texture = texLayer->GetTexture(); + sampler = FilterToSamplerMode(texLayer->GetSamplingFilter()); + TextureHost* host = texLayer->GetImageHost()->CurrentTextureHost(); + flags = host->GetFlags(); + } else { + return false; + } + + // We should not assign a texture-based layer to tiles if it has no texture. + MOZ_ASSERT(texture); + + float opacity = aLayer->GetComputedOpacity(); + if (mTexture) { + if (texture != mTexture) { + return false; + } + if (mSamplerMode != sampler) { + return false; + } + if (mOpacity != opacity) { + return false; + } + // Note: premultiplied, origin-bottom-left are already implied by the + // texture source. + } else { + mTexture = texture; + mSamplerMode = sampler; + mOpacity = opacity; + mTextureFlags = flags; + } + + Txn txn(this); + + // Note: these are two separate cases since no Info constructor takes in a + // base LayerMLGPU class. + if (PaintedLayerMLGPU* layer = aLayer->AsPaintedLayerMLGPU()) { + Info info(aItem, layer); + if (!AddItems(txn, info, layer->GetDrawRects())) { + return false; + } + } else if (TexturedLayerMLGPU* layer = aLayer->AsTexturedLayerMLGPU()) { + Info info(aItem, layer); + if (!AddItems(txn, info, layer->GetRenderRegion())) { + return false; + } + } + + return txn.Commit(); +} + +Maybe SingleTexturePass::GetBlendState() const { + return (mTextureFlags & TextureFlags::NON_PREMULTIPLIED) + ? Some(MLGBlendState::OverAndPremultiply) + : Some(MLGBlendState::Over); +} + +void SingleTexturePass::SetupPipeline() { + MOZ_ASSERT(mTexture); + + if (mGeometry == GeometryMode::UnitQuad) { + mDevice->SetVertexShader(VertexShaderID::TexturedQuad); + } else { + mDevice->SetVertexShader(VertexShaderID::TexturedVertex); + } + + mDevice->SetPSTexture(0, mTexture); + mDevice->SetSamplerMode(kDefaultSamplerSlot, mSamplerMode); + switch (mTexture.get()->GetFormat()) { + case SurfaceFormat::B8G8R8A8: + case SurfaceFormat::R8G8B8A8: + if (mGeometry == GeometryMode::UnitQuad) + mDevice->SetPixelShader(PixelShaderID::TexturedQuadRGBA); + else + mDevice->SetPixelShader(PixelShaderID::TexturedVertexRGBA); + break; + default: + if (mGeometry == GeometryMode::UnitQuad) + mDevice->SetPixelShader(PixelShaderID::TexturedQuadRGB); + else + mDevice->SetPixelShader(PixelShaderID::TexturedVertexRGB); + break; + } +} + +ComponentAlphaPass::ComponentAlphaPass(FrameBuilder* aBuilder, + const ItemInfo& aItem) + : TexturedRenderPass(aBuilder, aItem), + mOpacity(1.0f), + mSamplerMode(SamplerMode::LinearClamp) { + SetDefaultGeometry(aItem); +} + +bool ComponentAlphaPass::AddToPass(LayerMLGPU* aLayer, ItemInfo& aItem) { + PaintedLayerMLGPU* layer = aLayer->AsPaintedLayerMLGPU(); + MOZ_ASSERT(layer); + + if (mTextureOnBlack) { + if (layer->GetTexture() != mTextureOnBlack || + layer->GetTextureOnWhite() != mTextureOnWhite || + layer->GetOpacity() != mOpacity || + layer->GetSamplerMode() != mSamplerMode) { + return false; + } + } else { + mOpacity = layer->GetComputedOpacity(); + mSamplerMode = layer->GetSamplerMode(); + mTextureOnBlack = layer->GetTexture(); + mTextureOnWhite = layer->GetTextureOnWhite(); + } + + Txn txn(this); + + Info info(aItem, layer); + if (!AddItems(txn, info, layer->GetDrawRects())) { + return false; + } + return txn.Commit(); +} + +float ComponentAlphaPass::GetOpacity() const { return mOpacity; } + +void ComponentAlphaPass::SetupPipeline() { + TextureSource* textures[2] = {mTextureOnBlack, mTextureOnWhite}; + MOZ_ASSERT(textures[0]); + MOZ_ASSERT(textures[1]); + + if (mGeometry == GeometryMode::UnitQuad) { + mDevice->SetVertexShader(VertexShaderID::TexturedQuad); + mDevice->SetPixelShader(PixelShaderID::ComponentAlphaQuad); + } else { + mDevice->SetVertexShader(VertexShaderID::TexturedVertex); + mDevice->SetPixelShader(PixelShaderID::ComponentAlphaVertex); + } + + mDevice->SetSamplerMode(kDefaultSamplerSlot, mSamplerMode); + mDevice->SetPSTextures(0, 2, textures); +} + +VideoRenderPass::VideoRenderPass(FrameBuilder* aBuilder, const ItemInfo& aItem) + : TexturedRenderPass(aBuilder, aItem), + mSamplerMode(SamplerMode::LinearClamp), + mOpacity(1.0f) { + SetDefaultGeometry(aItem); +} + +bool VideoRenderPass::AddToPass(LayerMLGPU* aLayer, ItemInfo& aItem) { + ImageLayerMLGPU* layer = aLayer->AsImageLayerMLGPU(); + if (!layer) { + return false; + } + + RefPtr host = layer->GetImageHost()->CurrentTextureHost(); + RefPtr source = layer->GetTexture(); + float opacity = layer->GetComputedOpacity(); + SamplerMode sampler = FilterToSamplerMode(layer->GetSamplingFilter()); + + if (mHost) { + if (mHost != host) { + return false; + } + if (mTexture != source) { + return false; + } + if (mOpacity != opacity) { + return false; + } + if (mSamplerMode != sampler) { + return false; + } + } else { + mHost = host; + mTexture = source; + mOpacity = opacity; + mSamplerMode = sampler; + } + MOZ_ASSERT(!mTexture->AsBigImageIterator()); + MOZ_ASSERT(!(mHost->GetFlags() & TextureFlags::NON_PREMULTIPLIED)); + MOZ_ASSERT(!(mHost->GetFlags() & TextureFlags::ORIGIN_BOTTOM_LEFT)); + + Txn txn(this); + + Info info(aItem, layer); + if (!AddItems(txn, info, layer->GetRenderRegion())) { + return false; + } + return txn.Commit(); +} + +void VideoRenderPass::SetupPipeline() { + YUVColorSpace colorSpace = YUVColorSpace::UNKNOWN; + switch (mHost->GetReadFormat()) { + case SurfaceFormat::YUV: + case SurfaceFormat::NV12: + case SurfaceFormat::P010: + case SurfaceFormat::P016: + colorSpace = mHost->GetYUVColorSpace(); + break; + default: + MOZ_ASSERT_UNREACHABLE("Unexpected surface format in VideoRenderPass"); + break; + } + MOZ_ASSERT(colorSpace != YUVColorSpace::UNKNOWN); + + RefPtr ps1 = mDevice->GetBufferForColorSpace(colorSpace); + if (!ps1) { + return; + } + + RefPtr ps2 = + mDevice->GetBufferForColorDepthCoefficient(mHost->GetColorDepth()); + if (!ps2) { + return; + } + + if (mGeometry == GeometryMode::UnitQuad) { + mDevice->SetVertexShader(VertexShaderID::TexturedQuad); + } else { + mDevice->SetVertexShader(VertexShaderID::TexturedVertex); + } + + switch (mHost->GetReadFormat()) { + case SurfaceFormat::YUV: { + if (colorSpace == YUVColorSpace::Identity) { + if (mGeometry == GeometryMode::UnitQuad) + mDevice->SetPixelShader(PixelShaderID::TexturedQuadIdentityIMC4); + else + mDevice->SetPixelShader(PixelShaderID::TexturedVertexIdentityIMC4); + } else { + if (mGeometry == GeometryMode::UnitQuad) + mDevice->SetPixelShader(PixelShaderID::TexturedQuadIMC4); + else + mDevice->SetPixelShader(PixelShaderID::TexturedVertexIMC4); + } + mDevice->SetPSTexturesYUV(0, mTexture); + break; + } + case SurfaceFormat::NV12: + case SurfaceFormat::P010: + case SurfaceFormat::P016: + if (mGeometry == GeometryMode::UnitQuad) + mDevice->SetPixelShader(PixelShaderID::TexturedQuadNV12); + else + mDevice->SetPixelShader(PixelShaderID::TexturedVertexNV12); + mDevice->SetPSTexturesNV12(0, mTexture); + break; + default: + MOZ_ASSERT_UNREACHABLE("Unknown video format"); + break; + } + + mDevice->SetSamplerMode(kDefaultSamplerSlot, mSamplerMode); + mDevice->SetPSConstantBuffer(1, ps1); + mDevice->SetPSConstantBuffer(2, ps2); +} + +RenderViewPass::RenderViewPass(FrameBuilder* aBuilder, const ItemInfo& aItem) + : TexturedRenderPass(aBuilder, aItem), mParentView(nullptr) { + mAssignedLayer = aItem.layer->AsContainerLayerMLGPU(); + + CompositionOp blendOp = mAssignedLayer->GetMixBlendMode(); + if (BlendOpIsMixBlendMode(blendOp)) { + mBlendMode = Some(blendOp); + } + + if (mBlendMode) { + // We do not have fast-path rect shaders for blending. + SetGeometry(aItem, GeometryMode::Polygon); + } else { + SetDefaultGeometry(aItem); + } +} + +bool RenderViewPass::AddToPass(LayerMLGPU* aLayer, ItemInfo& aItem) { + // We bake in the layer ahead of time, which also guarantees the blend mode + // is baked in, as well as the geometry requirement. + if (mAssignedLayer != aLayer) { + return false; + } + + mSource = mAssignedLayer->GetRenderTarget(); + if (!mSource) { + return false; + } + + mParentView = aItem.view; + + Txn txn(this); + + IntPoint offset = mAssignedLayer->GetTargetOffset(); + IntSize size = mAssignedLayer->GetTargetSize(); + + // Clamp the visible region to the texture size. + nsIntRegion visible = mAssignedLayer->GetRenderRegion().ToUnknownRegion(); + visible.AndWith(IntRect(offset, size)); + + Info info(aItem, mAssignedLayer); + if (!AddItems(txn, info, visible)) { + return false; + } + return txn.Commit(); +} + +float RenderViewPass::GetOpacity() const { + return mAssignedLayer->GetLayer()->GetEffectiveOpacity(); +} + +bool RenderViewPass::OnPrepareBuffers() { + if (mBlendMode && !PrepareBlendState()) { + return false; + } + return true; +} + +static inline PixelShaderID GetShaderForBlendMode(CompositionOp aOp) { + switch (aOp) { + case CompositionOp::OP_MULTIPLY: + return PixelShaderID::BlendMultiply; + case CompositionOp::OP_SCREEN: + return PixelShaderID::BlendScreen; + case CompositionOp::OP_OVERLAY: + return PixelShaderID::BlendOverlay; + case CompositionOp::OP_DARKEN: + return PixelShaderID::BlendDarken; + case CompositionOp::OP_LIGHTEN: + return PixelShaderID::BlendLighten; + case CompositionOp::OP_COLOR_DODGE: + return PixelShaderID::BlendColorDodge; + case CompositionOp::OP_COLOR_BURN: + return PixelShaderID::BlendColorBurn; + case CompositionOp::OP_HARD_LIGHT: + return PixelShaderID::BlendHardLight; + case CompositionOp::OP_SOFT_LIGHT: + return PixelShaderID::BlendSoftLight; + case CompositionOp::OP_DIFFERENCE: + return PixelShaderID::BlendDifference; + case CompositionOp::OP_EXCLUSION: + return PixelShaderID::BlendExclusion; + case CompositionOp::OP_HUE: + return PixelShaderID::BlendHue; + case CompositionOp::OP_SATURATION: + return PixelShaderID::BlendSaturation; + case CompositionOp::OP_COLOR: + return PixelShaderID::BlendColor; + case CompositionOp::OP_LUMINOSITY: + return PixelShaderID::BlendLuminosity; + default: + MOZ_ASSERT_UNREACHABLE("Unexpected blend mode"); + return PixelShaderID::TexturedVertexRGBA; + } +} + +bool RenderViewPass::PrepareBlendState() { + Rect visibleRect( + mAssignedLayer->GetRenderRegion().GetBounds().ToUnknownRect()); + IntRect clipRect(mAssignedLayer->GetComputedClipRect().ToUnknownRect()); + const Matrix4x4& transform = + mAssignedLayer->GetLayer()->GetEffectiveTransformForBuffer(); + + // Note that we must use our parent RenderView for this calculation, + // since we're copying the backdrop, not our actual local target. + IntRect rtRect(mParentView->GetTargetOffset(), mParentView->GetSize()); + + Matrix4x4 backdropTransform; + mBackdropCopyRect = ComputeBackdropCopyRect(visibleRect, clipRect, transform, + rtRect, &backdropTransform); + + AutoBufferUpload cb; + if (!mDevice->GetSharedVSBuffer()->Allocate(&mBlendConstants, &cb)) { + return false; + } + memcpy(cb->backdropTransform, &backdropTransform._11, 64); + return true; +} + +void RenderViewPass::SetupPipeline() { + if (mBlendMode) { + RefPtr backdrop = mParentView->GetRenderTarget(); + MOZ_ASSERT(mDevice->GetRenderTarget() == backdrop); + + RefPtr copy = mDevice->CreateTexture( + mBackdropCopyRect.Size(), SurfaceFormat::B8G8R8A8, MLGUsage::Default, + MLGTextureFlags::ShaderResource); + if (!copy) { + return; + } + + mDevice->CopyTexture(copy, IntPoint(0, 0), backdrop->GetTexture(), + mBackdropCopyRect); + + MOZ_ASSERT(mGeometry == GeometryMode::Polygon); + mDevice->SetVertexShader(VertexShaderID::BlendVertex); + mDevice->SetPixelShader(GetShaderForBlendMode(mBlendMode.value())); + mDevice->SetVSConstantBuffer(kBlendConstantBufferSlot, &mBlendConstants); + mDevice->SetPSTexture(1, copy); + } else { + if (mGeometry == GeometryMode::UnitQuad) { + mDevice->SetVertexShader(VertexShaderID::TexturedQuad); + mDevice->SetPixelShader(PixelShaderID::TexturedQuadRGBA); + } else { + mDevice->SetVertexShader(VertexShaderID::TexturedVertex); + mDevice->SetPixelShader(PixelShaderID::TexturedVertexRGBA); + } + } + + mDevice->SetPSTexture(0, mSource->GetTexture()); + mDevice->SetSamplerMode(kDefaultSamplerSlot, SamplerMode::LinearClamp); +} + +void RenderViewPass::ExecuteRendering() { + if (mAssignedLayer->NeedsSurfaceCopy()) { + RenderWithBackdropCopy(); + return; + } + + TexturedRenderPass::ExecuteRendering(); +} + +void RenderViewPass::RenderWithBackdropCopy() { + MOZ_ASSERT(mAssignedLayer->NeedsSurfaceCopy()); + + DebugOnly transform2d; + const Matrix4x4& transform = mAssignedLayer->GetEffectiveTransform(); + MOZ_ASSERT(transform.Is2D(&transform2d) && + !gfx::ThebesMatrix(transform2d).HasNonIntegerTranslation()); + + IntPoint translation = IntPoint::Truncate(transform._41, transform._42); + + RenderViewMLGPU* childView = mAssignedLayer->GetRenderView(); + + IntRect visible = + mAssignedLayer->GetRenderRegion().GetBounds().ToUnknownRect(); + IntRect sourceRect = visible + translation - mParentView->GetTargetOffset(); + IntPoint destPoint = visible.TopLeft() - childView->GetTargetOffset(); + + RefPtr dest = mAssignedLayer->GetRenderTarget()->GetTexture(); + RefPtr source = mParentView->GetRenderTarget()->GetTexture(); + + // Clamp the source rect to the source texture size. + sourceRect = sourceRect.Intersect(IntRect(IntPoint(0, 0), source->GetSize())); + + // Clamp the source rect to the destination texture size. + IntRect destRect(destPoint, sourceRect.Size()); + destRect = destRect.Intersect(IntRect(IntPoint(0, 0), dest->GetSize())); + sourceRect = + sourceRect.Intersect(IntRect(sourceRect.TopLeft(), destRect.Size())); + + mDevice->CopyTexture(dest, destPoint, source, sourceRect); + childView->RenderAfterBackdropCopy(); + mParentView->RestoreDeviceState(); + TexturedRenderPass::ExecuteRendering(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/mlgpu/RenderPassMLGPU.h b/gfx/layers/mlgpu/RenderPassMLGPU.h new file mode 100644 index 0000000000..55739953ea --- /dev/null +++ b/gfx/layers/mlgpu/RenderPassMLGPU.h @@ -0,0 +1,439 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_RENDERPASSMLGPU_H +#define MOZILLA_GFX_RENDERPASSMLGPU_H + +#include "LayerMLGPU.h" +#include "LayerManagerMLGPU.h" +#include "ShaderDefinitionsMLGPU.h" +#include "SharedBufferMLGPU.h" +#include "StagingBuffer.h" + +namespace mozilla { +namespace layers { + +using namespace mlg; + +class RenderViewMLGPU; + +enum class RenderPassType { + ClearView, + SolidColor, + SingleTexture, + RenderView, + Video, + ComponentAlpha, + Unknown +}; + +enum class RenderOrder { + // Used for all items when not using a depth buffer. Otherwise, used for + // items that may draw transparent pixels. + BackToFront, + + // Only used when the depth buffer is enabled, and only for items that are + // guaranteed to only draw opaque pixels. + FrontToBack +}; + +static const uint32_t kInvalidResourceIndex = uint32_t(-1); + +struct ItemInfo final { + ItemInfo(FrameBuilder* aBuilder, RenderViewMLGPU* aView, LayerMLGPU* aLayer, + int32_t aSortOrder, const gfx::IntRect& aBounds, + Maybe&& aGeometry); + + // Return true if a layer can be clipped by the vertex shader; false + // otherwise. Any kind of textured mask or non-rectilinear transform + // will cause this to return false. + bool HasRectTransformAndClip() const { + return rectilinear && !layer->GetMask(); + } + + RenderViewMLGPU* view; + LayerMLGPU* layer; + RenderPassType type; + uint32_t layerIndex; + int32_t sortOrder; + gfx::IntRect bounds; + RenderOrder renderOrder; + Maybe geometry; + + // Set only when the transform is a 2D integer translation. + Maybe translation; + + // Set when the item bounds will occlude anything below it. + bool opaque; + + // Set when the item's transform is 2D and rectilinear. + bool rectilinear; +}; + +// Base class for anything that can render in a batch to the GPU. +class RenderPassMLGPU { + NS_INLINE_DECL_REFCOUNTING(RenderPassMLGPU) + + public: + static RenderPassType GetPreferredPassType(FrameBuilder* aBuilder, + const ItemInfo& aInfo); + + static RefPtr CreatePass(FrameBuilder* aBuilder, + const ItemInfo& aInfo); + + // Return true if this pass is compatible with the given item, false + // otherwise. This does not guarantee the pass will accept the item, + // but does guarantee we can try. + virtual bool IsCompatible(const ItemInfo& aItem); + + virtual RenderPassType GetType() const = 0; + + // Return true if the layer was compatible with and added to this pass, + // false otherwise. + bool AcceptItem(ItemInfo& aInfo); + + // Prepare constants buffers and textures. + virtual void PrepareForRendering(); + + // Execute this render pass to the currently selected surface. + virtual void ExecuteRendering() = 0; + + virtual Maybe GetBlendState() const { return Nothing(); } + + size_t GetLayerBufferIndex() const { return mLayerBufferIndex; } + Maybe GetMaskRectBufferIndex() const { + return mMaskRectBufferIndex == kInvalidResourceIndex + ? Nothing() + : Some(mMaskRectBufferIndex); + } + + // Returns true if this pass overlaps the affected region of an item. This + // only ever returns true for transparent items and transparent batches, + // and should not be used otherwise. + bool Intersects(const ItemInfo& aItem); + + // Returns true if pass has been successfully prepared. + bool IsPrepared() const { return mPrepared; } + + protected: + RenderPassMLGPU(FrameBuilder* aBuilder, const ItemInfo& aItem); + virtual ~RenderPassMLGPU(); + + // Return true if the item was consumed, false otherwise. + virtual bool AddToPass(LayerMLGPU* aItem, ItemInfo& aInfo) = 0; + + protected: + enum class GeometryMode { Unknown, UnitQuad, Polygon }; + + protected: + FrameBuilder* mBuilder; + RefPtr mDevice; + size_t mLayerBufferIndex; + size_t mMaskRectBufferIndex; + gfx::IntRegion mAffectedRegion; + bool mPrepared; +}; + +// Shader-based render passes execute a draw call, vs. non-shader passes that +// use non-shader APIs (like ClearView). +class ShaderRenderPass : public RenderPassMLGPU { + public: + ShaderRenderPass(FrameBuilder* aBuilder, const ItemInfo& aItem); + + // Used by ShaderDefinitions for writing traits. + VertexStagingBuffer* GetInstances() { return &mInstances; } + + bool IsCompatible(const ItemInfo& aItem) override; + void PrepareForRendering() override; + void ExecuteRendering() override; + + Maybe GetBlendState() const override { + return Some(MLGBlendState::Over); + } + + protected: + // If this batch has a uniform opacity, return it here. Otherwise this should + // return 1.0. + virtual float GetOpacity() const = 0; + + // Set any components of the pipeline that won't be handled by + // ExecuteRendering. This is called only once even if multiple draw calls + // are issued. + virtual void SetupPipeline() = 0; + + protected: + // Set the geometry this pass will use. This must be called by every + // derived constructor. Use GeometryMode::Unknown to pick the default + // behavior: UnitQuads for rectilinear transform+clips, and polygons + // otherwise. + void SetGeometry(const ItemInfo& aItem, GeometryMode aMode); + + void SetDefaultGeometry(const ItemInfo& aItem) { + SetGeometry(aItem, GeometryMode::Unknown); + } + + // Called after PrepareForRendering() has finished. If this returns false, + // PrepareForRendering() will return false. + virtual bool OnPrepareBuffers() { return true; } + + // Prepare the mask/opacity buffer bound in most pixel shaders. + bool SetupPSBuffer0(float aOpacity); + + bool HasMask() const { return !!mMask; } + MaskOperation* GetMask() const { return mMask; } + + protected: + GeometryMode mGeometry; + RefPtr mMask; + bool mHasRectTransformAndClip; + + VertexStagingBuffer mInstances; + VertexBufferSection mInstanceBuffer; + + ConstantBufferSection mPSBuffer0; +}; + +// This contains various helper functions for building vertices and shader +// inputs for layers. +template +class BatchRenderPass : public ShaderRenderPass { + public: + BatchRenderPass(FrameBuilder* aBuilder, const ItemInfo& aItem) + : ShaderRenderPass(aBuilder, aItem) {} + + protected: + // It is tricky to determine ahead of time whether or not we'll have enough + // room in our buffers to hold all draw commands for a layer, especially + // since layers can have multiple draw rects. We don't want to draw one rect, + // reject the item, then redraw the same rect again in another batch. + // To deal with this we use a transaction approach and reject the transaction + // if we couldn't add everything. + class Txn final { + public: + explicit Txn(BatchRenderPass* aPass) + : mPass(aPass), mPrevInstancePos(aPass->mInstances.GetPosition()) {} + + bool Add(const Traits& aTraits) { + if (!AddImpl(aTraits)) { + return Fail(); + } + return true; + } + + // Add an item based on a draw rect, layer, and optional geometry. This is + // defined in RenderPassMLGPU-inl.h, since it needs access to + // ShaderDefinitionsMLGPU-inl.h. + bool AddImpl(const Traits& aTraits); + + bool Fail() { + MOZ_ASSERT(!mStatus.isSome() || !mStatus.value()); + mStatus = Some(false); + return false; + } + + bool Commit() { + MOZ_ASSERT(!mStatus.isSome() || !mStatus.value()); + if (mStatus.isSome()) { + return false; + } + mStatus = Some(true); + return true; + } + + ~Txn() { + if (!mStatus.isSome() || !mStatus.value()) { + mPass->mInstances.RestorePosition(mPrevInstancePos); + } + } + + private: + BatchRenderPass* mPass; + VertexStagingBuffer::Position mPrevVertexPos; + VertexStagingBuffer::Position mPrevItemPos; + ConstantStagingBuffer::Position mPrevInstancePos; + Maybe mStatus; + }; +}; + +// Shaders which sample from a texture should inherit from this. +class TexturedRenderPass : public BatchRenderPass { + public: + explicit TexturedRenderPass(FrameBuilder* aBuilder, const ItemInfo& aItem); + + protected: + struct Info final { + Info(const ItemInfo& aItem, PaintedLayerMLGPU* aLayer); + Info(const ItemInfo& aItem, TexturedLayerMLGPU* aLayer); + Info(const ItemInfo& aItem, ContainerLayerMLGPU* aLayer); + + const ItemInfo& item; + gfx::IntSize textureSize; + gfx::Point destOrigin; + Maybe scale; + bool decomposeIntoNoRepeatRects; + }; + + // Add a set of draw rects based on a visible region. The texture size and + // scaling factor are used to compute uv-coordinates. + // + // The origin is the offset from the draw rect to the layer bounds. You can + // also think of it as the translation from layer space into texture space, + // pre-scaling. For example, ImageLayers use the texture bounds as their + // draw rect, so the origin will be (0, 0). ContainerLayer intermediate + // surfaces, on the other hand, are relative to the target offset of the + // layer. In all cases the visible region may be partially occluded, so + // knowing the true origin is important. + template + bool AddItems(Txn& aTxn, const Info& aInfo, const RegionType& aDrawRegion) { + for (auto iter = aDrawRegion.RectIter(); !iter.Done(); iter.Next()) { + gfx::Rect drawRect = gfx::Rect(iter.Get().ToUnknownRect()); + if (!AddItem(aTxn, aInfo, drawRect)) { + return false; + } + } + return true; + } + + private: + // Add a draw instance to the given destination rect. Texture coordinates + // are built from the given texture size, optional scaling factor, and + // texture origin relative to the draw rect. This will ultimately call + // AddClippedItem, potentially clipping the draw rect if needed. + bool AddItem(Txn& aTxn, const Info& aInfo, const gfx::Rect& aDrawRect); + + // Add an item that has gone through any necessary clipping already. This + // is the final destination for handling textured items. + bool AddClippedItem(Txn& aTxn, const Info& aInfo, const gfx::Rect& aDrawRect); + + protected: + TextureFlags mTextureFlags; +}; + +// This is only available when MLGDevice::CanUseClearView returns true. +class ClearViewPass final : public RenderPassMLGPU { + public: + ClearViewPass(FrameBuilder* aBuilder, const ItemInfo& aItem); + + bool IsCompatible(const ItemInfo& aItem) override; + void ExecuteRendering() override; + + RenderPassType GetType() const override { return RenderPassType::ClearView; } + + private: + bool AddToPass(LayerMLGPU* aItem, ItemInfo& aInfo) override; + + private: + // Note: Not a RefPtr since this would create a cycle. + RenderViewMLGPU* mView; + gfx::DeviceColor mColor; + nsTArray mRects; +}; + +// SolidColorPass is used when ClearViewPass is not available, or when +// the layer has masks, or subpixel or complex transforms. +class SolidColorPass final : public BatchRenderPass { + public: + explicit SolidColorPass(FrameBuilder* aBuilder, const ItemInfo& aItem); + + RenderPassType GetType() const override { return RenderPassType::SolidColor; } + + private: + bool AddToPass(LayerMLGPU* aItem, ItemInfo& aInfo) override; + void SetupPipeline() override; + float GetOpacity() const override; +}; + +class SingleTexturePass final : public TexturedRenderPass { + public: + explicit SingleTexturePass(FrameBuilder* aBuilder, const ItemInfo& aItem); + + RenderPassType GetType() const override { + return RenderPassType::SingleTexture; + } + + private: + bool AddToPass(LayerMLGPU* aItem, ItemInfo& aInfo) override; + void SetupPipeline() override; + float GetOpacity() const override { return mOpacity; } + Maybe GetBlendState() const override; + + private: + RefPtr mTexture; + SamplerMode mSamplerMode; + float mOpacity; +}; + +class ComponentAlphaPass final : public TexturedRenderPass { + public: + explicit ComponentAlphaPass(FrameBuilder* aBuilder, const ItemInfo& aItem); + + RenderPassType GetType() const override { + return RenderPassType::ComponentAlpha; + } + + private: + bool AddToPass(LayerMLGPU* aItem, ItemInfo& aInfo) override; + void SetupPipeline() override; + float GetOpacity() const override; + Maybe GetBlendState() const override { + return Some(MLGBlendState::ComponentAlpha); + } + + private: + float mOpacity; + SamplerMode mSamplerMode; + RefPtr mTextureOnBlack; + RefPtr mTextureOnWhite; +}; + +class VideoRenderPass final : public TexturedRenderPass { + public: + explicit VideoRenderPass(FrameBuilder* aBuilder, const ItemInfo& aItem); + + RenderPassType GetType() const override { return RenderPassType::Video; } + + private: + bool AddToPass(LayerMLGPU* aItem, ItemInfo& aInfo) override; + void SetupPipeline() override; + float GetOpacity() const override { return mOpacity; } + + private: + RefPtr mHost; + RefPtr mTexture; + SamplerMode mSamplerMode; + float mOpacity; +}; + +class RenderViewPass final : public TexturedRenderPass { + public: + RenderViewPass(FrameBuilder* aBuilder, const ItemInfo& aItem); + + RenderPassType GetType() const override { return RenderPassType::RenderView; } + + private: + bool AddToPass(LayerMLGPU* aItem, ItemInfo& aInfo) override; + void SetupPipeline() override; + bool OnPrepareBuffers() override; + void ExecuteRendering() override; + float GetOpacity() const override; + bool PrepareBlendState(); + void RenderWithBackdropCopy(); + + private: + ConstantBufferSection mBlendConstants; + ContainerLayerMLGPU* mAssignedLayer; + RefPtr mSource; + // Note: we don't use RefPtr here since that would cause a cycle. RenderViews + // and RenderPasses are both scoped to the frame anyway. + RenderViewMLGPU* mParentView; + gfx::IntRect mBackdropCopyRect; + Maybe mBlendMode; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/mlgpu/RenderViewMLGPU.cpp b/gfx/layers/mlgpu/RenderViewMLGPU.cpp new file mode 100644 index 0000000000..f6aceadeb5 --- /dev/null +++ b/gfx/layers/mlgpu/RenderViewMLGPU.cpp @@ -0,0 +1,549 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "RenderViewMLGPU.h" +#include "ContainerLayerMLGPU.h" +#include "FrameBuilder.h" +#include "mozilla/StaticPrefs_layers.h" +#include "LayersHelpers.h" +#include "MLGDevice.h" +#include "RenderPassMLGPU.h" +#include "ShaderDefinitionsMLGPU.h" +#include "Units.h" +#include "UnitTransforms.h" +#include "UtilityMLGPU.h" + +namespace mozilla { +namespace layers { + +using namespace gfx; + +RenderViewMLGPU::RenderViewMLGPU(FrameBuilder* aBuilder, + MLGRenderTarget* aTarget, + const nsIntRegion& aInvalidRegion) + : RenderViewMLGPU(aBuilder, nullptr) { + mTarget = aTarget; + mInvalidBounds = aInvalidRegion.GetBounds(); + + // The clear region on the layer manager is the area that must be clear after + // we finish drawing. + mPostClearRegion = aBuilder->GetManager()->GetRegionToClear(); + + // Clamp the post-clear region to the invalid bounds, since clears don't go + // through the scissor rect if using ClearView. + mPostClearRegion.AndWith(mInvalidBounds); + + // Since the post-clear will occlude everything, we include it in the final + // opaque area. + mOccludedRegion.OrWith(ViewAs( + mPostClearRegion, + PixelCastJustification::RenderTargetIsParentLayerForRoot)); + + AL_LOG("RenderView %p root with invalid area %s, clear area %s\n", this, + Stringify(mInvalidBounds).c_str(), + Stringify(mPostClearRegion).c_str()); +} + +RenderViewMLGPU::RenderViewMLGPU(FrameBuilder* aBuilder, + ContainerLayerMLGPU* aContainer, + RenderViewMLGPU* aParent) + : RenderViewMLGPU(aBuilder, aParent) { + mContainer = aContainer; + mTargetOffset = aContainer->GetTargetOffset(); + mInvalidBounds = aContainer->GetInvalidRect(); + MOZ_ASSERT(!mInvalidBounds.IsEmpty()); + + AL_LOG("RenderView %p starting with container %p and invalid area %s\n", this, + aContainer->GetLayer(), Stringify(mInvalidBounds).c_str()); + + mContainer->SetRenderView(this); +} + +RenderViewMLGPU::RenderViewMLGPU(FrameBuilder* aBuilder, + RenderViewMLGPU* aParent) + : mBuilder(aBuilder), + mDevice(aBuilder->GetDevice()), + mParent(aParent), + mContainer(nullptr), + mFinishedBuilding(false), + mCurrentLayerBufferIndex(kInvalidResourceIndex), + mCurrentMaskRectBufferIndex(kInvalidResourceIndex), + mCurrentDepthMode(MLGDepthTestMode::Disabled), + mNextSortIndex(1), + mUseDepthBuffer( + StaticPrefs::layers_mlgpu_enable_depth_buffer_AtStartup()), + mDepthBufferNeedsClear(false) { + if (aParent) { + aParent->AddChild(this); + } +} + +RenderViewMLGPU::~RenderViewMLGPU() { + for (const auto& child : mChildren) { + child->mParent = nullptr; + } +} + +IntSize RenderViewMLGPU::GetSize() const { + MOZ_ASSERT(mFinishedBuilding); + return mTarget->GetSize(); +} + +MLGRenderTarget* RenderViewMLGPU::GetRenderTarget() const { + MOZ_ASSERT(mFinishedBuilding); + return mTarget; +} + +void RenderViewMLGPU::AddChild(RenderViewMLGPU* aParent) { + mChildren.push_back(aParent); +} + +void RenderViewMLGPU::Render() { + // We render views depth-first to minimize render target switching. + for (const auto& child : mChildren) { + child->Render(); + } + + // If the view requires a surface copy (of its backdrop), then we delay + // rendering it until it is added to a batch. + if (mContainer && mContainer->NeedsSurfaceCopy()) { + return; + } + ExecuteRendering(); +} + +void RenderViewMLGPU::RenderAfterBackdropCopy() { + MOZ_ASSERT(mContainer && mContainer->NeedsSurfaceCopy()); + + // Update the invalid bounds based on the container's visible region. This + // of course won't affect the prepared pipeline, but it will change the + // scissor rect in SetDeviceState. + mInvalidBounds = mContainer->GetRenderRegion().GetBounds().ToUnknownRect() - + GetTargetOffset(); + + ExecuteRendering(); +} + +void RenderViewMLGPU::FinishBuilding() { + MOZ_ASSERT(!mFinishedBuilding); + mFinishedBuilding = true; + + if (mContainer) { + MOZ_ASSERT(!mTarget); + + MLGRenderTargetFlags flags = MLGRenderTargetFlags::Default; + if (mUseDepthBuffer) { + flags |= MLGRenderTargetFlags::ZBuffer; + } + mTarget = mContainer->UpdateRenderTarget(mDevice, flags); + } +} + +void RenderViewMLGPU::AddItem(LayerMLGPU* aItem, const IntRect& aRect, + Maybe&& aGeometry) { + AL_LOG("RenderView %p analyzing layer %p\n", this, aItem->GetLayer()); + + // If the item is not visible at all, skip it. + if (aItem->GetComputedOpacity() == 0.0f) { + AL_LOG("RenderView %p culling item %p with no opacity\n", this, + aItem->GetLayer()); + return; + } + + // When using the depth buffer, the z-index for items is important. + // + // Sort order starts at 1 and goes to positive infinity, with smaller values + // being closer to the screen. Our viewport is the same, with anything + // outside of [0.0, 1.0] being culled, and lower values occluding higher + // values. To make this work our projection transform scales the z-axis. + // Note that we do not use 0 as a sorting index (when depth-testing is + // enabled) because this would result in a z-value of 1.0, which would be + // culled. + ItemInfo info(mBuilder, this, aItem, mNextSortIndex++, aRect, + std::move(aGeometry)); + + // If the item is not visible, or we can't add it to the layer constant + // buffer for some reason, bail out. + if (!UpdateVisibleRegion(info) || !mBuilder->AddLayerToConstantBuffer(info)) { + AL_LOG("RenderView %p culled item %p!\n", this, aItem->GetLayer()); + return; + } + + // We support all layer types now. + MOZ_ASSERT(info.type != RenderPassType::Unknown); + + if (info.renderOrder == RenderOrder::FrontToBack) { + AddItemFrontToBack(aItem, info); + } else { + AddItemBackToFront(aItem, info); + } +} + +bool RenderViewMLGPU::UpdateVisibleRegion(ItemInfo& aItem) { + // If the item has some kind of complex transform, we perform a very + // simple occlusion test and move on. We using a depth buffer we skip + // CPU-based occlusion culling as well, since the GPU will do most of our + // culling work for us. + if (mUseDepthBuffer || !aItem.translation || + !StaticPrefs::layers_mlgpu_enable_cpu_occlusion_AtStartup()) { + // Update the render region even if we won't compute visibility, since some + // layer types (like Canvas and Image) need to have the visible region + // clamped. + LayerIntRegion region = aItem.layer->GetShadowVisibleRegion(); + aItem.layer->SetRenderRegion(std::move(region)); + + AL_LOG("RenderView %p simple occlusion test, bounds=%s, translation?=%d\n", + this, Stringify(aItem.bounds).c_str(), aItem.translation ? 1 : 0); + return mInvalidBounds.Intersects(aItem.bounds); + } + + MOZ_ASSERT(aItem.rectilinear); + + AL_LOG("RenderView %p starting visibility tests:\n", this); + AL_LOG(" occluded=%s\n", Stringify(mOccludedRegion).c_str()); + + // Compute the translation into render target space. + LayerIntPoint translation = LayerIntPoint::FromUnknownPoint( + aItem.translation.value() - mTargetOffset); + AL_LOG(" translation=%s\n", Stringify(translation).c_str()); + + IntRect clip = aItem.layer->GetComputedClipRect().ToUnknownRect(); + AL_LOG(" clip=%s\n", Stringify(translation).c_str()); + + LayerIntRegion region = aItem.layer->GetShadowVisibleRegion(); + region.MoveBy(translation); + AL_LOG(" effective-visible=%s\n", Stringify(region).c_str()); + + region.SubOut(mOccludedRegion); + region.AndWith(LayerIntRect::FromUnknownRect(mInvalidBounds)); + region.AndWith(LayerIntRect::FromUnknownRect(clip)); + if (region.IsEmpty()) { + return false; + } + + // Move the visible region back into layer space. + region.MoveBy(-translation); + AL_LOG(" new-local-visible=%s\n", Stringify(region).c_str()); + + aItem.layer->SetRenderRegion(std::move(region)); + + // Apply the new occluded area. We do another dance with the translation to + // avoid copying the region. We do this after the SetRegionToRender call to + // accomodate the possiblity of a layer changing its visible region. + if (aItem.opaque) { + mOccludedRegion.MoveBy(-translation); + mOccludedRegion.OrWith(aItem.layer->GetRenderRegion()); + mOccludedRegion.MoveBy(translation); + AL_LOG(" new-occluded=%s\n", Stringify(mOccludedRegion).c_str()); + + // If the occluded region gets too complicated, we reset it. + if (mOccludedRegion.GetNumRects() >= 32) { + mOccludedRegion.SetEmpty(); + AL_LOG(" clear-occluded, too many rects\n"); + } + } + return true; +} + +void RenderViewMLGPU::AddItemFrontToBack(LayerMLGPU* aLayer, ItemInfo& aItem) { + // We receive items in front-to-back order. Ideally we want to push items + // as far back into batches impossible, to ensure the GPU can do a good + // job at culling. However we also want to make sure we actually batch + // items versus drawing one primitive per pass. + // + // As a compromise we look at the most 3 recent batches and then give up. + // This can be tweaked in the future. + static const size_t kMaxSearch = 3; + size_t iterations = 0; + for (auto iter = mFrontToBack.rbegin(); iter != mFrontToBack.rend(); iter++) { + RenderPassMLGPU* pass = (*iter); + if (pass->IsCompatible(aItem) && pass->AcceptItem(aItem)) { + AL_LOG("RenderView %p added layer %p to pass %p (%d)\n", this, + aLayer->GetLayer(), pass, int(pass->GetType())); + return; + } + if (++iterations > kMaxSearch) { + break; + } + } + + RefPtr pass = RenderPassMLGPU::CreatePass(mBuilder, aItem); + if (!pass || !pass->AcceptItem(aItem)) { + MOZ_ASSERT_UNREACHABLE("Could not build a pass for item!"); + return; + } + AL_LOG("RenderView %p added layer %p to new pass %p (%d)\n", this, + aLayer->GetLayer(), pass.get(), int(pass->GetType())); + + mFrontToBack.push_back(pass); +} + +void RenderViewMLGPU::AddItemBackToFront(LayerMLGPU* aLayer, ItemInfo& aItem) { + // We receive layers in front-to-back order, but there are two cases when we + // actually draw back-to-front: when the depth buffer is disabled, or when + // using the depth buffer and the item has transparent pixels (and therefore + // requires blending). In these cases we will build vertex and constant + // buffers in reverse, as well as execute batches in reverse, to ensure the + // correct ordering. + // + // Note: We limit the number of batches we search through, since it's better + // to add new draw calls than spend too much time finding compatible + // batches further down. + static const size_t kMaxSearch = 10; + size_t iterations = 0; + for (auto iter = mBackToFront.begin(); iter != mBackToFront.end(); iter++) { + RenderPassMLGPU* pass = (*iter); + if (pass->IsCompatible(aItem) && pass->AcceptItem(aItem)) { + AL_LOG("RenderView %p added layer %p to pass %p (%d)\n", this, + aLayer->GetLayer(), pass, int(pass->GetType())); + return; + } + if (pass->Intersects(aItem)) { + break; + } + if (++iterations > kMaxSearch) { + break; + } + } + + RefPtr pass = RenderPassMLGPU::CreatePass(mBuilder, aItem); + if (!pass || !pass->AcceptItem(aItem)) { + MOZ_ASSERT_UNREACHABLE("Could not build a pass for item!"); + return; + } + AL_LOG("RenderView %p added layer %p to new pass %p (%d)\n", this, + aLayer->GetLayer(), pass.get(), int(pass->GetType())); + + mBackToFront.push_front(pass); +} + +void RenderViewMLGPU::Prepare() { + if (!mTarget) { + return; + } + + // Prepare front-to-back passes. These are only present when using the depth + // buffer, and they contain only opaque data. + for (RefPtr& pass : mFrontToBack) { + pass->PrepareForRendering(); + } + + // Prepare the Clear buffer, which will fill the render target with + // transparent pixels. This must happen before we set up world constants, + // since it can create new z-indices. + PrepareClears(); + + // Prepare the world constant buffer. This must be called after we've + // finished allocating all z-indices. + { + WorldConstants vsConstants; + Matrix4x4 projection = Matrix4x4::Translation(-1.0, 1.0, 0.0); + projection.PreScale(2.0 / float(mTarget->GetSize().width), + 2.0 / float(mTarget->GetSize().height), 1.0f); + projection.PreScale(1.0f, -1.0f, 1.0f); + + memcpy(vsConstants.projection, &projection._11, 64); + vsConstants.targetOffset = Point(mTargetOffset); + vsConstants.sortIndexOffset = PrepareDepthBuffer(); + vsConstants.debugFrameNumber = + mBuilder->GetManager()->GetDebugFrameNumber(); + + SharedConstantBuffer* shared = mDevice->GetSharedVSBuffer(); + if (!shared->Allocate(&mWorldConstants, vsConstants)) { + return; + } + } + + // Prepare back-to-front passes. In depth buffer mode, these contain draw + // calls that might produce transparent pixels. When using CPU-based occlusion + // culling, all draw calls are back-to-front. + for (RefPtr& pass : mBackToFront) { + pass->PrepareForRendering(); + } + + // Now, process children. + for (const auto& iter : mChildren) { + iter->Prepare(); + } +} + +void RenderViewMLGPU::ExecuteRendering() { + if (!mTarget) { + return; + } + if (!mWorldConstants.IsValid()) { + gfxWarning() << "Failed to allocate constant buffer for world transform"; + return; + } + + SetDeviceState(); + + // If using the depth buffer, clear it (if needed) and enable writes. + if (mUseDepthBuffer) { + if (mDepthBufferNeedsClear) { + mDevice->ClearDepthBuffer(mTarget); + } + SetDepthTestMode(MLGDepthTestMode::Write); + } + + // Opaque items, rendered front-to-back. + for (auto iter = mFrontToBack.begin(); iter != mFrontToBack.end(); iter++) { + ExecutePass(*iter); + } + + if (mUseDepthBuffer) { + // From now on we might be rendering transparent pixels, so we disable + // writing to the z-buffer. + SetDepthTestMode(MLGDepthTestMode::ReadOnly); + } + + // Clear any pixels that are not occluded, and therefore might require + // blending. + mDevice->DrawClearRegion(mPreClear); + + // Render back-to-front passes. + for (auto iter = mBackToFront.begin(); iter != mBackToFront.end(); iter++) { + ExecutePass(*iter); + } + + // Make sure the post-clear area has no pixels. + if (!mPostClearRegion.IsEmpty()) { + mDevice->DrawClearRegion(mPostClear); + } + + // We repaint the entire invalid region, even if it is partially occluded. + // Thus it's safe for us to clear the invalid area here. If we ever switch + // to nsIntRegions, we will have to take the difference between the paitned + // area and the invalid area. + if (mContainer) { + mContainer->ClearInvalidRect(); + } +} + +void RenderViewMLGPU::ExecutePass(RenderPassMLGPU* aPass) { + if (!aPass->IsPrepared()) { + return; + } + + // Change the layer buffer if needed. + if (aPass->GetLayerBufferIndex() != mCurrentLayerBufferIndex) { + mCurrentLayerBufferIndex = aPass->GetLayerBufferIndex(); + + ConstantBufferSection section = + mBuilder->GetLayerBufferByIndex(mCurrentLayerBufferIndex); + mDevice->SetVSConstantBuffer(kLayerBufferSlot, §ion); + } + + // Change the mask rect buffer if needed. + if (aPass->GetMaskRectBufferIndex() && + aPass->GetMaskRectBufferIndex().value() != mCurrentMaskRectBufferIndex) { + mCurrentMaskRectBufferIndex = aPass->GetMaskRectBufferIndex().value(); + + ConstantBufferSection section = + mBuilder->GetMaskRectBufferByIndex(mCurrentMaskRectBufferIndex); + mDevice->SetVSConstantBuffer(kMaskBufferSlot, §ion); + } + + aPass->ExecuteRendering(); +} + +void RenderViewMLGPU::SetDeviceState() { + // Note: we unbind slot 0 (which is where the render target could have been + // bound on a previous frame). Otherwise we trigger + // D3D11_DEVICE_PSSETSHADERRESOURCES_HAZARD. + mDevice->UnsetPSTexture(0); + mDevice->SetRenderTarget(mTarget); + mDevice->SetViewport(IntRect(IntPoint(0, 0), mTarget->GetSize())); + mDevice->SetScissorRect(Some(mInvalidBounds)); + mDevice->SetVSConstantBuffer(kWorldConstantBufferSlot, &mWorldConstants); +} + +void RenderViewMLGPU::SetDepthTestMode(MLGDepthTestMode aMode) { + mDevice->SetDepthTestMode(aMode); + mCurrentDepthMode = aMode; +} + +void RenderViewMLGPU::RestoreDeviceState() { + SetDeviceState(); + mDevice->SetDepthTestMode(mCurrentDepthMode); + mCurrentLayerBufferIndex = kInvalidResourceIndex; + mCurrentMaskRectBufferIndex = kInvalidResourceIndex; +} + +int32_t RenderViewMLGPU::PrepareDepthBuffer() { + if (!mUseDepthBuffer) { + return 0; + } + + // Rather than clear the depth buffer every frame, we offset z-indices each + // frame, starting with indices far away from the screen and moving toward + // the user each successive frame. This ensures that frames can re-use the + // depth buffer but never collide with previously written values. + // + // Once a frame runs out of sort indices, we finally clear the depth buffer + // and start over again. + + // Note: the lowest sort index (kDepthLimit) is always occluded since it will + // resolve to the clear value - kDepthLimit / kDepthLimit == 1.0. + // + // If we don't have any more indices to allocate, we need to clear the depth + // buffer and start fresh. + int32_t highestIndex = mTarget->GetLastDepthStart(); + if (highestIndex < mNextSortIndex) { + mDepthBufferNeedsClear = true; + highestIndex = kDepthLimit; + } + + // We should not have more than kDepthLimit layers to draw. The last known + // sort index might appear in the depth buffer and occlude something, so + // we subtract 1. This ensures all our indices will compare less than all + // old indices. + int32_t sortOffset = highestIndex - mNextSortIndex - 1; + MOZ_ASSERT(sortOffset >= 0); + + mTarget->SetLastDepthStart(sortOffset); + return sortOffset; +} + +void RenderViewMLGPU::PrepareClears() { + // We don't do any clearing if we're copying from a source backdrop. + if (mContainer && mContainer->NeedsSurfaceCopy()) { + return; + } + + // Get the list of rects to clear. If using the depth buffer, we don't + // care if it's accurate since the GPU will do occlusion testing for us. + // If not using the depth buffer, we subtract out the occluded region. + LayerIntRegion region = LayerIntRect::FromUnknownRect(mInvalidBounds); + if (!mUseDepthBuffer) { + // Don't let the clear region become too complicated. + region.SubOut(mOccludedRegion); + region.SimplifyOutward(kMaxClearViewRects); + } + + Maybe sortIndex; + if (mUseDepthBuffer) { + // Note that we use the lowest available sorting index, to ensure that when + // using the z-buffer, we don't draw over already-drawn content. + sortIndex = Some(mNextSortIndex++); + } + + nsTArray rects = ToRectArray(region); + mDevice->PrepareClearRegion(&mPreClear, std::move(rects), sortIndex); + + if (!mPostClearRegion.IsEmpty()) { + // Prepare the final clear as well. Note that we always do this clear at the + // very end, even when the depth buffer is enabled, so we don't bother + // setting a useful sorting index. If and when we try to ship the depth + // buffer, we would execute this clear earlier in the pipeline and give it + // the closest possible z-ordering to the screen. + nsTArray rects = ToRectArray(mPostClearRegion); + mDevice->PrepareClearRegion(&mPostClear, std::move(rects), Nothing()); + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/mlgpu/RenderViewMLGPU.h b/gfx/layers/mlgpu/RenderViewMLGPU.h new file mode 100644 index 0000000000..1ad6c20eda --- /dev/null +++ b/gfx/layers/mlgpu/RenderViewMLGPU.h @@ -0,0 +1,136 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_layers_mlgpu_RenderViewMLGPU_h +#define mozilla_gfx_layers_mlgpu_RenderViewMLGPU_h + +#include "LayerManagerMLGPU.h" +#include "ClearRegionHelper.h" +#include "RenderPassMLGPU.h" +#include "Units.h" +#include + +namespace mozilla { +namespace layers { + +class FrameBuilder; +class ContainerLayerMLGPU; +class MLGRenderTarget; + +class RenderViewMLGPU { + public: + NS_INLINE_DECL_REFCOUNTING(RenderViewMLGPU) + + // Constructor for the widget render target. + RenderViewMLGPU(FrameBuilder* aBuilder, MLGRenderTarget* aTarget, + const nsIntRegion& aInvalidRegion); + + // Constructor for intermediate surfaces. + RenderViewMLGPU(FrameBuilder* aBuilder, ContainerLayerMLGPU* aContainer, + RenderViewMLGPU* aParent); + + void Prepare(); + void Render(); + void AddChild(RenderViewMLGPU* aParent); + void AddItem(LayerMLGPU* aItem, const gfx::IntRect& aBounds, + Maybe&& aGeometry); + void FinishBuilding(); + + const gfx::IntPoint& GetTargetOffset() const { return mTargetOffset; } + RenderViewMLGPU* GetParent() const { return mParent; } + bool HasDepthBuffer() const { return mUseDepthBuffer; } + + // Render after having previously delayed rendering due to the view + // requiring a backdrop copy. + void RenderAfterBackdropCopy(); + void RestoreDeviceState(); + + // The size and render target cannot be read until the view has finished + // building, since we try to right-size the render target to the visible + // region. + MLGRenderTarget* GetRenderTarget() const; + gfx::IntSize GetSize() const; + + gfx::IntRect GetInvalidRect() const { return mInvalidBounds; } + + private: + RenderViewMLGPU(FrameBuilder* aBuilder, RenderViewMLGPU* aParent); + ~RenderViewMLGPU(); + + void ExecuteRendering(); + bool UpdateVisibleRegion(ItemInfo& aItem); + void AddItemFrontToBack(LayerMLGPU* aLayer, ItemInfo& aItem); + void AddItemBackToFront(LayerMLGPU* aLayer, ItemInfo& aItem); + + void PrepareClears(); + void SetDeviceState(); + void SetDepthTestMode(MLGDepthTestMode aMode); + + void ExecutePass(RenderPassMLGPU* aPass); + + // Return the sorting index offset to use. + int32_t PrepareDepthBuffer(); + + private: + std::deque> mFrontToBack; + std::deque> mBackToFront; + + FrameBuilder* mBuilder; + RefPtr mDevice; + RenderViewMLGPU* mParent; + std::vector> mChildren; + + // Shader data. + ConstantBufferSection mWorldConstants; + + // Information for the initial target surface clear. This covers the area that + // won't be occluded by opaque content. + ClearRegionHelper mPreClear; + + // The post-clear region, that must be cleared after all drawing is done. + nsIntRegion mPostClearRegion; + ClearRegionHelper mPostClear; + + // Either an MLGSwapChain-derived render target, or an intermediate surface. + RefPtr mTarget; + + // For intermediate render targets only, this is the layer owning the render + // target. + ContainerLayerMLGPU* mContainer; + + // The offset adjustment from container layer space to render target space. + // This is 0,0 for the root view. + gfx::IntPoint mTargetOffset; + + // The invalid bounds as computed by LayerTreeInvalidation. This is the + // initial render bounds size, if invalidation is disabled. + gfx::IntRect mInvalidBounds; + + // The occluded region, which is updated every time we process an opaque, + // rectangular item. This is not actually in LayerPixels, we do this to + // avoid FromUnknownRegion which has array copies. + LayerIntRegion mOccludedRegion; + + // True if we've finished adding layers to the view. + bool mFinishedBuilding; + + // This state is used to avoid changing buffers while we execute batches. + size_t mCurrentLayerBufferIndex; + size_t mCurrentMaskRectBufferIndex; + + // This state is saved locally so it can be restored in RestoreDeviceState. + MLGDepthTestMode mCurrentDepthMode; + + // Depth-buffer tracking. + int32_t mNextSortIndex; + bool mUseDepthBuffer; + bool mDepthBufferNeedsClear; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_gfx_layers_mlgpu_RenderViewMLGPU_h diff --git a/gfx/layers/mlgpu/ShaderDefinitionsMLGPU-inl.h b/gfx/layers/mlgpu/ShaderDefinitionsMLGPU-inl.h new file mode 100644 index 0000000000..9197f7e870 --- /dev/null +++ b/gfx/layers/mlgpu/ShaderDefinitionsMLGPU-inl.h @@ -0,0 +1,79 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _include_gfx_layers_mlgpu_ShaderDefinitions_inl_h +#define _include_gfx_layers_mlgpu_ShaderDefinitions_inl_h + +namespace mozilla { +namespace layers { +namespace mlg { + +inline const Maybe& SimpleTraits::geometry() const { + return mItem.geometry; +} + +inline nsTArray SimpleTraits::GenerateTriangles( + const gfx::Polygon& aPolygon) const { + return aPolygon.ToTriangles(); +} + +inline SimpleTraits::TriangleVertices SimpleTraits::MakeVertex( + const FirstTriangle& aIgnore) const { + TriangleVertices v = {mRect.BottomLeft(), mRect.TopLeft(), mRect.TopRight(), + mItem.layerIndex, mItem.sortOrder}; + return v; +} + +inline SimpleTraits::TriangleVertices SimpleTraits::MakeVertex( + const SecondTriangle& aIgnore) const { + TriangleVertices v = {mRect.TopRight(), mRect.BottomRight(), + mRect.BottomLeft(), mItem.layerIndex, mItem.sortOrder}; + return v; +} + +inline SimpleTraits::TriangleVertices SimpleTraits::MakeVertex( + const gfx::Triangle& aTriangle) const { + TriangleVertices v = {aTriangle.p1, aTriangle.p2, aTriangle.p3, + mItem.layerIndex, mItem.sortOrder}; + return v; +} + +inline SimpleTraits::UnitQuadVertex SimpleTraits::MakeUnitQuadVertex() const { + UnitQuadVertex v = {mRect, mItem.layerIndex, mItem.sortOrder}; + return v; +} + +inline nsTArray TexturedTraits::GenerateTriangles( + const gfx::Polygon& aPolygon) const { + return GenerateTexturedTriangles(aPolygon, mRect, mTexCoords); +} + +inline TexturedTraits::VertexData TexturedTraits::MakeVertexData( + const FirstTriangle& aIgnore) const { + VertexData v = {mTexCoords.BottomLeft(), mTexCoords.TopLeft(), + mTexCoords.TopRight()}; + return v; +} + +inline TexturedTraits::VertexData TexturedTraits::MakeVertexData( + const SecondTriangle& aIgnore) const { + VertexData v = {mTexCoords.TopRight(), mTexCoords.BottomRight(), + mTexCoords.BottomLeft()}; + return v; +} + +inline TexturedTraits::VertexData TexturedTraits::MakeVertexData( + const gfx::TexturedTriangle& aTriangle) const { + VertexData v = {aTriangle.textureCoords.p1, aTriangle.textureCoords.p2, + aTriangle.textureCoords.p3}; + return v; +} + +} // namespace mlg +} // namespace layers +} // namespace mozilla + +#endif // _include_gfx_layers_mlgpu_ShaderDefinitions_inl_h diff --git a/gfx/layers/mlgpu/ShaderDefinitionsMLGPU.h b/gfx/layers/mlgpu/ShaderDefinitionsMLGPU.h new file mode 100644 index 0000000000..9c3d490a28 --- /dev/null +++ b/gfx/layers/mlgpu/ShaderDefinitionsMLGPU.h @@ -0,0 +1,195 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_SHADERDEFINITIONSMLGPU_H +#define MOZILLA_GFX_SHADERDEFINITIONSMLGPU_H + +#include "mozilla/gfx/Point.h" +#include "mozilla/gfx/Triangle.h" +#include "mozilla/gfx/Types.h" +#include "mozilla/layers/LayersHelpers.h" +#include "nsTArray.h" + +namespace mozilla { +namespace layers { + +struct ItemInfo; +class ShaderRenderPass; + +namespace mlg { + +// These may need to move into run-time values determined by MLGDevice. +static const size_t kConstantBufferElementSize = 16; +static const size_t kMaxConstantBufferSize = 4096 * kConstantBufferElementSize; + +// Vertex shader slots. We reverse the first two slots across all shaders, +// and the first three slots free across all RenderPass shaders, for +// uniformity. +static const uint32_t kWorldConstantBufferSlot = 0; +static const uint32_t kLayerBufferSlot = 1; +static const uint32_t kMaskBufferSlot = 3; +static const uint32_t kBlendConstantBufferSlot = 4; +static const uint32_t kClearConstantBufferSlot = 2; + +// This is specified in common-ps.hlsl. +static const uint32_t kMaskLayerTextureSlot = 4; +static const uint32_t kDefaultSamplerSlot = 0; +static const uint32_t kMaskSamplerSlot = 1; + +// These are the maximum slot numbers we bind. We assert that no binding +// happens above the max slot, since we try to clear buffer bindings at +// the end of each frame. +static const uint32_t kMaxVertexShaderConstantBuffers = 5; +static const uint32_t kMaxPixelShaderConstantBuffers = 3; + +// Maximum depth in the depth buffer. This must match common-vs.hlsl. +static const int32_t kDepthLimit = 1000000; + +struct WorldConstants { + float projection[4][4]; + gfx::Point targetOffset; + int sortIndexOffset; + unsigned debugFrameNumber; +}; + +struct ClearConstants { + explicit ClearConstants(int aDepth) : depth(aDepth) {} + int depth; + int padding[3]; +}; + +struct LayerConstants { + float transform[4][4]; + gfx::Rect clipRect; + uint32_t maskIndex; + uint32_t padding[3]; +}; + +struct MaskCombineInput { + float texCoords[4]; +}; + +struct MaskInformation { + MaskInformation(float aOpacity, bool aHasMask) + : opacity(aOpacity), hasMask(aHasMask ? 1 : 0) {} + float opacity; + uint32_t hasMask; + uint32_t padding[2]; +}; + +struct YCbCrShaderConstants { + float yuvColorMatrix[3][4]; +}; + +struct YCbCrColorDepthConstants { + float coefficient; + uint32_t padding[3]; +}; + +struct BlendVertexShaderConstants { + float backdropTransform[4][4]; +}; + +template +static inline nsTArray ToRectArray(const T& aRegion) { + nsTArray rects; + for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) { + rects.AppendElement(iter.Get().ToUnknownRect()); + } + return rects; +} + +struct SimpleTraits { + SimpleTraits(const ItemInfo& aItem, const gfx::Rect& aRect) + : mItem(aItem), mRect(aRect) {} + + // Helper nonce structs so functions can break vertex data up by each + // triangle in a quad, or return vertex info for a unit quad. + struct AnyTriangle {}; + struct FirstTriangle : AnyTriangle {}; + struct SecondTriangle : AnyTriangle {}; + struct UnitQuad {}; + + // This is the base vertex layout used by all unit quad shaders. + struct UnitQuadVertex { + gfx::Rect rect; + uint32_t layerIndex; + int depth; + }; + + // This is the base vertex layout used by all unit triangle shaders. + struct TriangleVertices { + gfx::Point p1, p2, p3; + uint32_t layerIndex; + int depth; + }; + + // Helper functions for populating a TriangleVertices. The first two use mRect + // to generate triangles, the third function uses coordinates from an already + // computed triangle. + TriangleVertices MakeVertex(const FirstTriangle& aIgnore) const; + TriangleVertices MakeVertex(const SecondTriangle& aIgnore) const; + TriangleVertices MakeVertex(const gfx::Triangle& aTriangle) const; + + UnitQuadVertex MakeUnitQuadVertex() const; + + // This default GenerateTriangles only computes the 3 points of each triangle + // in the polygon. If needed, shaders can override this and return a more + // complex triangle, to encode dependent information in extended vertex data. + // + // AddShaderVertices will deduce this return type. It should be an nsTArray + // where T inherits from Triangle. + nsTArray GenerateTriangles(const gfx::Polygon& aPolygon) const; + + // Accessors. + const Maybe& geometry() const; + const gfx::Rect& rect() const { return mRect; } + + const ItemInfo& mItem; + gfx::Rect mRect; +}; + +struct ColorTraits : public SimpleTraits { + ColorTraits(const ItemInfo& aItem, const gfx::Rect& aRect, + const gfx::DeviceColor& aColor) + : SimpleTraits(aItem, aRect), mColor(aColor) {} + + // Color data is the same across all vertex types. + template + const gfx::DeviceColor& MakeVertexData(const VertexType& aIgnore) const { + return mColor; + } + + gfx::DeviceColor mColor; +}; + +struct TexturedTraits : public SimpleTraits { + TexturedTraits(const ItemInfo& aItem, const gfx::Rect& aRect, + const gfx::Rect& aTexCoords) + : SimpleTraits(aItem, aRect), mTexCoords(aTexCoords) {} + + // Textured triangles need to compute a texture coordinate for each vertex. + nsTArray GenerateTriangles( + const gfx::Polygon& aPolygon) const; + + struct VertexData { + gfx::Point p1, p2, p3; + }; + VertexData MakeVertexData(const FirstTriangle& aIgnore) const; + VertexData MakeVertexData(const SecondTriangle& aIgnore) const; + VertexData MakeVertexData(const gfx::TexturedTriangle& aTriangle) const; + const gfx::Rect& MakeVertexData(const UnitQuad& aIgnore) const { + return mTexCoords; + } + + gfx::Rect mTexCoords; +}; + +} // namespace mlg +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/mlgpu/SharedBufferMLGPU.cpp b/gfx/layers/mlgpu/SharedBufferMLGPU.cpp new file mode 100644 index 0000000000..b6c9978c80 --- /dev/null +++ b/gfx/layers/mlgpu/SharedBufferMLGPU.cpp @@ -0,0 +1,275 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SharedBufferMLGPU.h" +#include "BufferCache.h" +#include "MLGDevice.h" + +namespace mozilla { +namespace layers { + +SharedBufferMLGPU::SharedBufferMLGPU(MLGDevice* aDevice, MLGBufferType aType, + size_t aDefaultSize) + : mDevice(aDevice), + mType(aType), + mDefaultSize(aDefaultSize), + mCanUseOffsetAllocation(true), + mCurrentPosition(0), + mMaxSize(0), + mMap(), + mMapped(false), + mBytesUsedThisFrame(0), + mNumSmallFrames(0) { + MOZ_COUNT_CTOR(SharedBufferMLGPU); +} + +SharedBufferMLGPU::~SharedBufferMLGPU() { + MOZ_COUNT_DTOR(SharedBufferMLGPU); + Unmap(); +} + +bool SharedBufferMLGPU::Init() { + // If we can't use buffer offset binding, we never allocated shared buffers. + if (!mCanUseOffsetAllocation) { + return true; + } + + // If we can use offset binding, allocate an initial shared buffer now. + if (!GrowBuffer(mDefaultSize)) { + return false; + } + return true; +} + +void SharedBufferMLGPU::Reset() { + // We shouldn't be mapped here, but just in case, unmap now. + Unmap(); + mBytesUsedThisFrame = 0; + + // If we allocated a large buffer for a particularly heavy layer tree, + // but have not used most of the buffer again for many frames, we + // discard the buffer. This is to prevent having to perform large + // pointless uploads after visiting a single havy page - it also + // lessens ping-ponging between large and small buffers. + if (mBuffer && (mBuffer->GetSize() > mDefaultSize * 4) && + mNumSmallFrames >= 10) { + mBuffer = nullptr; + } + + // Note that we do not aggressively map a new buffer. There's no reason to, + // and it'd cause unnecessary uploads when painting empty frames. +} + +bool SharedBufferMLGPU::EnsureMappedBuffer(size_t aBytes) { + if (!mBuffer || (mMaxSize - mCurrentPosition < aBytes)) { + if (!GrowBuffer(aBytes)) { + return false; + } + } + if (!mMapped && !Map()) { + return false; + } + return true; +} + +// We don't want to cache large buffers, since it results in larger uploads +// that might not be needed. +static const size_t kMaxCachedBufferSize = 128 * 1024; + +bool SharedBufferMLGPU::GrowBuffer(size_t aBytes) { + // We only pre-allocate buffers if we can use offset allocation. + MOZ_ASSERT(mCanUseOffsetAllocation); + + // Unmap the previous buffer. This will retain mBuffer, but free up the + // address space used by its mapping. + Unmap(); + + size_t maybeSize = mDefaultSize; + if (mBuffer) { + // Try to first grow the previous allocation size. + maybeSize = std::min(kMaxCachedBufferSize, mBuffer->GetSize() * 2); + } + + size_t bytes = std::max(aBytes, maybeSize); + mBuffer = mDevice->CreateBuffer(mType, bytes, MLGUsage::Dynamic); + if (!mBuffer) { + return false; + } + + mCurrentPosition = 0; + mMaxSize = mBuffer->GetSize(); + return true; +} + +void SharedBufferMLGPU::PrepareForUsage() { + Unmap(); + + if (mBytesUsedThisFrame <= mDefaultSize) { + mNumSmallFrames++; + } else { + mNumSmallFrames = 0; + } +} + +bool SharedBufferMLGPU::Map() { + MOZ_ASSERT(mBuffer); + MOZ_ASSERT(!mMapped); + + if (!mDevice->Map(mBuffer, MLGMapType::WRITE_DISCARD, &mMap)) { + // Don't retain the buffer, it's useless if we can't map it. + mBuffer = nullptr; + return false; + } + + mCurrentPosition = 0; + mMapped = true; + return true; +} + +void SharedBufferMLGPU::Unmap() { + if (!mMapped) { + return; + } + + mBytesUsedThisFrame += mCurrentPosition; + + mDevice->Unmap(mBuffer); + mMap = MLGMappedResource(); + mMapped = false; +} + +uint8_t* SharedBufferMLGPU::GetBufferPointer(size_t aBytes, + ptrdiff_t* aOutOffset, + RefPtr* aOutBuffer) { + if (!EnsureMappedBuffer(aBytes)) { + return nullptr; + } + + ptrdiff_t newPos = mCurrentPosition + aBytes; + MOZ_ASSERT(size_t(newPos) <= mMaxSize); + + *aOutOffset = mCurrentPosition; + *aOutBuffer = mBuffer; + + uint8_t* ptr = reinterpret_cast(mMap.mData) + mCurrentPosition; + mCurrentPosition = newPos; + return ptr; +} + +VertexBufferSection::VertexBufferSection() + : mOffset(-1), mNumVertices(0), mStride(0) {} + +void VertexBufferSection::Init(MLGBuffer* aBuffer, ptrdiff_t aOffset, + size_t aNumVertices, size_t aStride) { + mBuffer = aBuffer; + mOffset = aOffset; + mNumVertices = aNumVertices; + mStride = aStride; +} + +ConstantBufferSection::ConstantBufferSection() + : mOffset(-1), mNumBytes(0), mNumItems(0) {} + +void ConstantBufferSection::Init(MLGBuffer* aBuffer, ptrdiff_t aOffset, + size_t aBytes, size_t aNumItems) { + mBuffer = aBuffer; + mOffset = aOffset; + mNumBytes = aBytes; + mNumItems = aNumItems; +} + +SharedVertexBuffer::SharedVertexBuffer(MLGDevice* aDevice, size_t aDefaultSize) + : SharedBufferMLGPU(aDevice, MLGBufferType::Vertex, aDefaultSize) {} + +bool SharedVertexBuffer::Allocate(VertexBufferSection* aHolder, + size_t aNumItems, size_t aSizeOfItem, + const void* aData) { + RefPtr buffer; + ptrdiff_t offset; + size_t bytes = aSizeOfItem * aNumItems; + uint8_t* ptr = GetBufferPointer(bytes, &offset, &buffer); + if (!ptr) { + return false; + } + + memcpy(ptr, aData, bytes); + aHolder->Init(buffer, offset, aNumItems, aSizeOfItem); + return true; +} + +AutoBufferUploadBase::AutoBufferUploadBase() : mPtr(nullptr) {} + +AutoBufferUploadBase::~AutoBufferUploadBase() { + if (mBuffer) { + UnmapBuffer(); + } +} + +void AutoBufferUploadBase::Init(void* aPtr, MLGDevice* aDevice, + MLGBuffer* aBuffer) { + MOZ_ASSERT(!mPtr && aPtr); + mPtr = aPtr; + mDevice = aDevice; + mBuffer = aBuffer; +} + +SharedConstantBuffer::SharedConstantBuffer(MLGDevice* aDevice, + size_t aDefaultSize) + : SharedBufferMLGPU(aDevice, MLGBufferType::Constant, aDefaultSize) { + mMaxConstantBufferBindSize = aDevice->GetMaxConstantBufferBindSize(); + mCanUseOffsetAllocation = aDevice->CanUseConstantBufferOffsetBinding(); +} + +bool SharedConstantBuffer::Allocate(ConstantBufferSection* aHolder, + AutoBufferUploadBase* aPtr, + size_t aNumItems, size_t aSizeOfItem) { + MOZ_ASSERT(aSizeOfItem % 16 == 0, "Items must be padded to 16 bytes"); + + size_t bytes = aNumItems * aSizeOfItem; + if (bytes > mMaxConstantBufferBindSize) { + gfxWarning() + << "Attempted to allocate too many bytes into a constant buffer"; + return false; + } + + RefPtr buffer; + ptrdiff_t offset; + if (!GetBufferPointer(aPtr, bytes, &offset, &buffer)) { + return false; + } + + aHolder->Init(buffer, offset, bytes, aNumItems); + return true; +} + +uint8_t* SharedConstantBuffer::AllocateNewBuffer( + size_t aBytes, ptrdiff_t* aOutOffset, RefPtr* aOutBuffer) { + RefPtr buffer; + if (BufferCache* cache = mDevice->GetConstantBufferCache()) { + buffer = cache->GetOrCreateBuffer(aBytes); + } else { + buffer = mDevice->CreateBuffer(MLGBufferType::Constant, aBytes, + MLGUsage::Dynamic); + } + if (!buffer) { + return nullptr; + } + + MLGMappedResource map; + if (!mDevice->Map(buffer, MLGMapType::WRITE_DISCARD, &map)) { + return nullptr; + } + + // Signal that offsetting is not supported. + *aOutOffset = -1; + *aOutBuffer = buffer; + return reinterpret_cast(map.mData); +} + +void AutoBufferUploadBase::UnmapBuffer() { mDevice->Unmap(mBuffer); } + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/mlgpu/SharedBufferMLGPU.h b/gfx/layers/mlgpu/SharedBufferMLGPU.h new file mode 100644 index 0000000000..19ece7ff11 --- /dev/null +++ b/gfx/layers/mlgpu/SharedBufferMLGPU.h @@ -0,0 +1,273 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_layers_mlgpu_SharedBufferMLGPU_h +#define mozilla_gfx_layers_mlgpu_SharedBufferMLGPU_h + +#include "ShaderDefinitionsMLGPU.h" +#include "MLGDevice.h" +#include "MLGDeviceTypes.h" +#include "StagingBuffer.h" +#include "mozilla/gfx/Logging.h" + +namespace mozilla { +namespace layers { + +class MLGBuffer; + +class SharedBufferMLGPU { + public: + virtual ~SharedBufferMLGPU(); + + bool Init(); + + // Call before starting a new frame. + void Reset(); + + // Call to finish any pending uploads. + void PrepareForUsage(); + + protected: + SharedBufferMLGPU(MLGDevice* aDevice, MLGBufferType aType, + size_t aDefaultSize); + + bool EnsureMappedBuffer(size_t aBytes); + bool GrowBuffer(size_t aBytes); + void ForgetBuffer(); + bool Map(); + void Unmap(); + + uint8_t* GetBufferPointer(size_t aBytes, ptrdiff_t* aOutOffset, + RefPtr* aOutBuffer); + + protected: + // Note: RefPtr here would cause a cycle. Only MLGDevice should own + // SharedBufferMLGPU objects for now. + MLGDevice* mDevice; + MLGBufferType mType; + size_t mDefaultSize; + bool mCanUseOffsetAllocation; + + // When |mBuffer| is non-null, mMaxSize is the buffer size. If mapped, the + // position is between 0 and mMaxSize, otherwise it is always 0. + RefPtr mBuffer; + ptrdiff_t mCurrentPosition; + size_t mMaxSize; + + MLGMappedResource mMap; + bool mMapped; + + // These are used to track how many frames come in under the default + // buffer size in a row. + size_t mBytesUsedThisFrame; + size_t mNumSmallFrames; +}; + +class VertexBufferSection final { + friend class SharedVertexBuffer; + + public: + VertexBufferSection(); + + uint32_t Stride() const { return mStride; } + MLGBuffer* GetBuffer() const { return mBuffer; } + ptrdiff_t Offset() const { + MOZ_ASSERT(IsValid()); + return mOffset; + } + size_t NumVertices() const { return mNumVertices; } + bool IsValid() const { return !!mBuffer; } + + protected: + void Init(MLGBuffer* aBuffer, ptrdiff_t aOffset, size_t aNumVertices, + size_t aStride); + + protected: + RefPtr mBuffer; + ptrdiff_t mOffset; + size_t mNumVertices; + size_t mStride; +}; + +class ConstantBufferSection final { + friend class SharedConstantBuffer; + + public: + ConstantBufferSection(); + + uint32_t NumConstants() const { return NumConstantsForBytes(mNumBytes); } + size_t NumItems() const { return mNumItems; } + uint32_t Offset() const { + MOZ_ASSERT(IsValid()); + return mOffset / 16; + } + MLGBuffer* GetBuffer() const { return mBuffer; } + bool IsValid() const { return !!mBuffer; } + bool HasOffset() const { return mOffset != -1; } + + protected: + static constexpr size_t NumConstantsForBytes(size_t aBytes) { + return (aBytes + ((256 - (aBytes % 256)) % 256)) / 16; + } + + void Init(MLGBuffer* aBuffer, ptrdiff_t aOffset, size_t aBytes, + size_t aNumItems); + + protected: + RefPtr mBuffer; + ptrdiff_t mOffset; + size_t mNumBytes; + size_t mNumItems; +}; + +// Vertex buffers don't need special alignment. +typedef StagingBuffer<0> VertexStagingBuffer; + +class SharedVertexBuffer final : public SharedBufferMLGPU { + public: + SharedVertexBuffer(MLGDevice* aDevice, size_t aDefaultSize); + + // Allocate a buffer that can be uploaded immediately. + bool Allocate(VertexBufferSection* aHolder, + const VertexStagingBuffer& aStaging) { + return Allocate(aHolder, aStaging.NumItems(), aStaging.SizeOfItem(), + aStaging.GetBufferStart()); + } + + // Allocate a buffer that can be uploaded immediately. This is the + // direct access version, for cases where a StagingBuffer is not + // needed. + bool Allocate(VertexBufferSection* aHolder, size_t aNumItems, + size_t aSizeOfItem, const void* aData); + + template + bool Allocate(VertexBufferSection* aHolder, const T& aItem) { + return Allocate(aHolder, 1, sizeof(T), &aItem); + } +}; + +// To support older Direct3D versions, we need to support one-off MLGBuffers, +// where data is uploaded immediately rather than at the end of all batch +// preparation. We achieve this through a small helper class. +// +// Note: the unmap is not inline sincce we don't include MLGDevice.h. +class MOZ_STACK_CLASS AutoBufferUploadBase { + public: + AutoBufferUploadBase(); + ~AutoBufferUploadBase(); + + void Init(void* aPtr) { + MOZ_ASSERT(!mPtr && aPtr); + mPtr = aPtr; + } + void Init(void* aPtr, MLGDevice* aDevice, MLGBuffer* aBuffer); + void* get() { return const_cast(mPtr); } + + private: + void UnmapBuffer(); + + protected: + RefPtr mDevice; + RefPtr mBuffer; + void* mPtr; +}; + +// This is a typed helper for AutoBufferUploadBase. +template +class AutoBufferUpload : public AutoBufferUploadBase { + public: + AutoBufferUpload() = default; + + T* operator->() const { return reinterpret_cast(mPtr); } +}; + +class SharedConstantBuffer final : public SharedBufferMLGPU { + public: + SharedConstantBuffer(MLGDevice* aDevice, size_t aDefaultSize); + + // Allocate a buffer that can be immediately uploaded. + bool Allocate(ConstantBufferSection* aHolder, + const ConstantStagingBuffer& aStaging) { + MOZ_ASSERT(aStaging.NumItems() * aStaging.SizeOfItem() == + aStaging.NumBytes()); + return Allocate(aHolder, aStaging.NumItems(), aStaging.SizeOfItem(), + aStaging.GetBufferStart()); + } + + // Allocate a buffer of one item that can be immediately uploaded. + template + bool Allocate(ConstantBufferSection* aHolder, const T& aItem) { + return Allocate(aHolder, 1, sizeof(aItem), &aItem); + } + + // Allocate a buffer of N items that can be immediately uploaded. + template + bool Allocate(ConstantBufferSection* aHolder, const T* aItems, + size_t aNumItems) { + return Allocate(aHolder, aNumItems, sizeof(T), aItems); + } + + // Allocate a buffer that is uploaded after the caller has finished writing + // to it. This should method should generally not be used unless copying T + // is expensive, since the default immediate-upload version has an implicit + // extra copy to the GPU. This version exposes the mapped memory directly. + template + bool Allocate(ConstantBufferSection* aHolder, AutoBufferUpload* aPtr) { + MOZ_ASSERT(sizeof(T) % 16 == 0, "Items must be padded to 16 bytes"); + + return Allocate(aHolder, aPtr, 1, sizeof(T)); + } + + private: + bool Allocate(ConstantBufferSection* aHolder, size_t aNumItems, + size_t aSizeOfItem, const void* aData) { + AutoBufferUploadBase ptr; + if (!Allocate(aHolder, &ptr, aNumItems, aSizeOfItem)) { + return false; + } + memcpy(ptr.get(), aData, aNumItems * aSizeOfItem); + return true; + } + + bool Allocate(ConstantBufferSection* aHolder, AutoBufferUploadBase* aPtr, + size_t aNumItems, size_t aSizeOfItem); + + bool GetBufferPointer(AutoBufferUploadBase* aPtr, size_t aBytes, + ptrdiff_t* aOutOffset, RefPtr* aOutBuffer) { + if (!mCanUseOffsetAllocation) { + uint8_t* ptr = AllocateNewBuffer(aBytes, aOutOffset, aOutBuffer); + if (!ptr) { + return false; + } + aPtr->Init(ptr, mDevice, *aOutBuffer); + return true; + } + + // Align up the allocation to 256 bytes, since D3D11 requires that + // constant buffers start at multiples of 16 elements. + size_t alignedBytes = AlignUp<256>::calc(aBytes); + + uint8_t* ptr = SharedBufferMLGPU::GetBufferPointer(alignedBytes, aOutOffset, + aOutBuffer); + if (!ptr) { + return false; + } + + aPtr->Init(ptr); + return true; + } + + uint8_t* AllocateNewBuffer(size_t aBytes, ptrdiff_t* aOutOffset, + RefPtr* aOutBuffer); + + private: + size_t mMaxConstantBufferBindSize; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_gfx_layers_mlgpu_SharedBufferMLGPU_h diff --git a/gfx/layers/mlgpu/StagingBuffer.cpp b/gfx/layers/mlgpu/StagingBuffer.cpp new file mode 100644 index 0000000000..d82531be5e --- /dev/null +++ b/gfx/layers/mlgpu/StagingBuffer.cpp @@ -0,0 +1,18 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "StagingBuffer.h" +#include "MLGDevice.h" +#include "ShaderDefinitionsMLGPU.h" + +namespace mozilla { +namespace layers { + +ConstantStagingBuffer::ConstantStagingBuffer(MLGDevice* aDevice) + : StagingBuffer(mlg::kMaxConstantBufferSize) {} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/mlgpu/StagingBuffer.h b/gfx/layers/mlgpu/StagingBuffer.h new file mode 100644 index 0000000000..1bbb0959ba --- /dev/null +++ b/gfx/layers/mlgpu/StagingBuffer.h @@ -0,0 +1,271 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_layers_mlgpu_StagingBuffer_h +#define mozilla_gfx_layers_mlgpu_StagingBuffer_h + +#include "mozilla/MathAlgorithms.h" +#include "mozilla/UniquePtr.h" +#include "UtilityMLGPU.h" +#include +#include +#include + +namespace mozilla { +namespace layers { + +class MLGDevice; + +// A StagingBuffer is a writable memory buffer for arbitrary contents. +template +class StagingBuffer { + public: + StagingBuffer() : StagingBuffer(0) {} + + // By default, staging buffers operate in "forward" mode: items are added to + // the end of the buffer. In "reverse" mode the cursor is at the end of the + // buffer, and items are added to the beginning. + // + // This must be called before the buffer is written. + void SetReversed() { + MOZ_ASSERT(IsEmpty()); + mReversed = true; + } + + // Write a series of components as a single item. When this is first used, the + // buffer records the initial item size and requires that all future items be + // the exact same size. + // + // This directs to either AppendItem or PrependItem depending on the buffer + // state. + template + bool AddItem(const T& aItem) { + if (mReversed) { + return PrependItem(aItem); + } + return AppendItem(aItem); + } + + // Helper for adding a single item as two components. + template + bool AddItem(const T1& aItem1, const T2& aItem2) { + if (mReversed) { + return PrependItem(aItem1, aItem2); + } + return AppendItem(aItem1, aItem2); + } + + // This may only be called on forward buffers. + template + bool AppendItem(const T& aItem) { + MOZ_ASSERT(!mReversed); + + size_t alignedBytes = AlignUp::calc(sizeof(aItem)); + if (!mUniformSize) { + mUniformSize = alignedBytes; + } + + if (!EnsureForwardRoomFor(alignedBytes)) { + return false; + } + if (mUniformSize != alignedBytes) { + MOZ_ASSERT_UNREACHABLE("item of incorrect size added!"); + return false; + } + + *reinterpret_cast(mPos) = aItem; + mPos += alignedBytes; + MOZ_ASSERT(mPos <= mEnd); + + mNumItems++; + return true; + } + + // Append an item in two stages. + template + bool AppendItem(const T1& aFirst, const T2& aSecond) { + struct Combined { + T1 first; + T2 second; + } value = {aFirst, aSecond}; + + // The combined value must be packed. + static_assert(sizeof(value) == sizeof(aFirst) + sizeof(aSecond), + "Items must be packed within struct"); + return AppendItem(value); + } + + // This may only be called on reversed buffers. + template + bool PrependItem(const T& aItem) { + MOZ_ASSERT(mReversed); + + size_t alignedBytes = AlignUp::calc(sizeof(aItem)); + if (!mUniformSize) { + mUniformSize = alignedBytes; + } + + if (!EnsureBackwardRoomFor(alignedBytes)) { + return false; + } + if (mUniformSize != alignedBytes) { + MOZ_ASSERT_UNREACHABLE("item of incorrect size added!"); + return false; + } + + mPos -= alignedBytes; + *reinterpret_cast(mPos) = aItem; + MOZ_ASSERT(mPos >= mBuffer.get()); + + mNumItems++; + return true; + } + + // Prepend an item in two stages. + template + bool PrependItem(const T1& aFirst, const T2& aSecond) { + struct Combined { + T1 first; + T2 second; + } value = {aFirst, aSecond}; + + // The combined value must be packed. + static_assert(sizeof(value) == sizeof(aFirst) + sizeof(aSecond), + "Items must be packed within struct"); + return PrependItem(value); + } + + size_t NumBytes() const { + return mReversed ? mEnd - mPos : mPos - mBuffer.get(); + } + uint8_t* GetBufferStart() const { return mReversed ? mPos : mBuffer.get(); } + size_t SizeOfItem() const { return mUniformSize; } + size_t NumItems() const { return mNumItems; } + + void Reset() { + mPos = mReversed ? mEnd : mBuffer.get(); + mUniformSize = 0; + mNumItems = 0; + } + + // RestorePosition must only be called with a previous call to + // GetPosition. + typedef std::pair Position; + Position GetPosition() const { return std::make_pair(NumBytes(), mNumItems); } + void RestorePosition(const Position& aPosition) { + mPos = mBuffer.get() + aPosition.first; + mNumItems = aPosition.second; + if (mNumItems == 0) { + mUniformSize = 0; + } + + // Make sure the buffer is still coherent. + MOZ_ASSERT(mPos >= mBuffer.get() && mPos <= mEnd); + MOZ_ASSERT(mNumItems * mUniformSize == NumBytes()); + } + + bool IsEmpty() const { return mNumItems == 0; } + + protected: + explicit StagingBuffer(size_t aMaxSize) + : mPos(nullptr), + mEnd(nullptr), + mUniformSize(0), + mNumItems(0), + mMaxSize(aMaxSize), + mReversed(false) {} + + static const size_t kDefaultSize = 8; + + bool EnsureForwardRoomFor(size_t aAlignedBytes) { + if (size_t(mEnd - mPos) < aAlignedBytes) { + return GrowBuffer(aAlignedBytes); + } + return true; + } + + bool EnsureBackwardRoomFor(size_t aAlignedBytes) { + if (size_t(mPos - mBuffer.get()) < aAlignedBytes) { + return GrowBuffer(aAlignedBytes); + } + return true; + } + + bool GrowBuffer(size_t aAlignedBytes) { + // We should not be writing items that are potentially bigger than the + // maximum constant buffer size, that's crazy. An assert should be good + // enough since the size of writes is static - and shader compilers + // would explode anyway. + MOZ_ASSERT_IF(mMaxSize, aAlignedBytes < mMaxSize); + MOZ_ASSERT_IF(mMaxSize, kDefaultSize * Alignment < mMaxSize); + + if (!mBuffer) { + size_t newSize = std::max(kDefaultSize * Alignment, aAlignedBytes); + MOZ_ASSERT_IF(mMaxSize, newSize < mMaxSize); + + mBuffer = MakeUnique(newSize); + mEnd = mBuffer.get() + newSize; + mPos = mReversed ? mEnd : mBuffer.get(); + return true; + } + + // Take the bigger of exact-fit or 1.5x the previous size, and make sure + // the new size doesn't overflow size_t. If needed, clamp to the max + // size. + size_t oldSize = mEnd - mBuffer.get(); + size_t trySize = std::max(oldSize + aAlignedBytes, oldSize + oldSize / 2); + size_t newSize = mMaxSize ? std::min(trySize, mMaxSize) : trySize; + if (newSize < oldSize || newSize - oldSize < aAlignedBytes) { + return false; + } + + UniquePtr newBuffer = MakeUnique(newSize); + if (!newBuffer) { + return false; + } + + // When the buffer is in reverse mode, we have to copy from the end of the + // buffer, not the beginning. + if (mReversed) { + size_t usedBytes = mEnd - mPos; + size_t newPos = newSize - usedBytes; + MOZ_RELEASE_ASSERT(newPos + usedBytes <= newSize); + + memcpy(newBuffer.get() + newPos, mPos, usedBytes); + mPos = newBuffer.get() + newPos; + } else { + size_t usedBytes = mPos - mBuffer.get(); + MOZ_RELEASE_ASSERT(usedBytes <= newSize); + + memcpy(newBuffer.get(), mBuffer.get(), usedBytes); + mPos = newBuffer.get() + usedBytes; + } + mEnd = newBuffer.get() + newSize; + mBuffer = std::move(newBuffer); + + MOZ_RELEASE_ASSERT(mPos >= mBuffer.get() && mPos <= mEnd); + return true; + } + + protected: + UniquePtr mBuffer; + uint8_t* mPos; + uint8_t* mEnd; + size_t mUniformSize; + size_t mNumItems; + size_t mMaxSize; + bool mReversed; +}; + +class ConstantStagingBuffer : public StagingBuffer<16> { + public: + explicit ConstantStagingBuffer(MLGDevice* aDevice); +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_gfx_layers_mlgpu_StagingBuffer_h diff --git a/gfx/layers/mlgpu/TextureSourceProviderMLGPU.cpp b/gfx/layers/mlgpu/TextureSourceProviderMLGPU.cpp new file mode 100644 index 0000000000..7ddf475588 --- /dev/null +++ b/gfx/layers/mlgpu/TextureSourceProviderMLGPU.cpp @@ -0,0 +1,96 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TextureSourceProviderMLGPU.h" +#include "LayerManagerMLGPU.h" +#include "MLGDevice.h" +#ifdef XP_WIN +# include "mozilla/layers/MLGDeviceD3D11.h" +#endif + +namespace mozilla { +namespace layers { + +TextureSourceProviderMLGPU::TextureSourceProviderMLGPU( + LayerManagerMLGPU* aLayerManager, MLGDevice* aDevice) + : mLayerManager(aLayerManager), mDevice(aDevice) {} + +TextureSourceProviderMLGPU::~TextureSourceProviderMLGPU() = default; + +int32_t TextureSourceProviderMLGPU::GetMaxTextureSize() const { + if (!mDevice) { + return 0; + } + return mDevice->GetMaxTextureSize(); +} + +bool TextureSourceProviderMLGPU::SupportsEffect(EffectTypes aEffect) { + switch (aEffect) { + case EffectTypes::YCBCR: + return true; + default: + MOZ_ASSERT_UNREACHABLE("NYI"); + } + return false; +} + +bool TextureSourceProviderMLGPU::IsValid() const { return !!mLayerManager; } + +void TextureSourceProviderMLGPU::Destroy() { + mLayerManager = nullptr; + mDevice = nullptr; + TextureSourceProvider::Destroy(); +} + +#ifdef XP_WIN +ID3D11Device* TextureSourceProviderMLGPU::GetD3D11Device() const { + if (!mDevice) { + return nullptr; + } + return mDevice->AsD3D11()->GetD3D11Device(); +} +#endif + +TimeStamp TextureSourceProviderMLGPU::GetLastCompositionEndTime() const { + if (!mLayerManager) { + return TimeStamp(); + } + return mLayerManager->GetLastCompositionEndTime(); +} + +already_AddRefed +TextureSourceProviderMLGPU::CreateDataTextureSource(TextureFlags aFlags) { + RefPtr texture = mDevice->CreateDataTextureSource(aFlags); + return texture.forget(); +} + +already_AddRefed +TextureSourceProviderMLGPU::CreateDataTextureSourceAround( + gfx::DataSourceSurface* aSurface) { + MOZ_ASSERT_UNREACHABLE("NYI"); + return nullptr; +} + +void TextureSourceProviderMLGPU::UnlockAfterComposition(TextureHost* aTexture) { + TextureSourceProvider::UnlockAfterComposition(aTexture); + + // If this is being called after we shutdown the compositor, we must finish + // read unlocking now to prevent a cycle. + if (!IsValid()) { + ReadUnlockTextures(); + } +} + +bool TextureSourceProviderMLGPU::NotifyNotUsedAfterComposition( + TextureHost* aTextureHost) { + if (!IsValid()) { + return false; + } + return TextureSourceProvider::NotifyNotUsedAfterComposition(aTextureHost); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/mlgpu/TextureSourceProviderMLGPU.h b/gfx/layers/mlgpu/TextureSourceProviderMLGPU.h new file mode 100644 index 0000000000..4210206c72 --- /dev/null +++ b/gfx/layers/mlgpu/TextureSourceProviderMLGPU.h @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_layers_mlgpu_TextureSourceProviderMLGPU_h +#define mozilla_gfx_layers_mlgpu_TextureSourceProviderMLGPU_h + +#include "mozilla/layers/TextureSourceProvider.h" + +namespace mozilla { +namespace layers { + +class MLGDevice; +class LayerManagerMLGPU; + +class TextureSourceProviderMLGPU final : public TextureSourceProvider { + public: + TextureSourceProviderMLGPU(LayerManagerMLGPU* aLayerManager, + MLGDevice* aDevice); + virtual ~TextureSourceProviderMLGPU(); + + already_AddRefed CreateDataTextureSource( + TextureFlags aFlags) override; + + already_AddRefed CreateDataTextureSourceAround( + gfx::DataSourceSurface* aSurface) override; + + void UnlockAfterComposition(TextureHost* aTexture) override; + bool NotifyNotUsedAfterComposition(TextureHost* aTextureHost) override; + + int32_t GetMaxTextureSize() const override; + TimeStamp GetLastCompositionEndTime() const override; + bool SupportsEffect(EffectTypes aEffect) override; + bool IsValid() const override; + +#ifdef XP_WIN + ID3D11Device* GetD3D11Device() const override; +#endif + + void ReadUnlockTextures() { TextureSourceProvider::ReadUnlockTextures(); } + + // Release references to the layer manager. + void Destroy() override; + + private: + // Using RefPtr<> here would be a circular reference. + LayerManagerMLGPU* mLayerManager; + RefPtr mDevice; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_gfx_layers_mlgpu_TextureSourceProviderMLGPU_h diff --git a/gfx/layers/mlgpu/TexturedLayerMLGPU.cpp b/gfx/layers/mlgpu/TexturedLayerMLGPU.cpp new file mode 100644 index 0000000000..0cb8ccfc16 --- /dev/null +++ b/gfx/layers/mlgpu/TexturedLayerMLGPU.cpp @@ -0,0 +1,196 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TexturedLayerMLGPU.h" +#include "LayerManagerMLGPU.h" +#include "RenderViewMLGPU.h" +#include "FrameBuilder.h" +#include "mozilla/gfx/Types.h" +#include "mozilla/layers/ImageHost.h" +#include "UnitTransforms.h" + +namespace mozilla { +namespace layers { + +using namespace gfx; + +TexturedLayerMLGPU::TexturedLayerMLGPU(LayerManagerMLGPU* aManager) + : LayerMLGPU(aManager) {} + +TexturedLayerMLGPU::~TexturedLayerMLGPU() { + // Note: we have to cleanup resources in derived classes, since we can't + // easily tell in our destructor if we have a TempImageLayerMLGPU, which + // should not have its compositable detached, and we can't call GetLayer + // here. +} + +bool TexturedLayerMLGPU::SetCompositableHost(CompositableHost* aHost) { + switch (aHost->GetType()) { + case CompositableType::IMAGE: + mHost = aHost->AsImageHost(); + return true; + default: + return false; + } +} + +CompositableHost* TexturedLayerMLGPU::GetCompositableHost() { + if (mHost && mHost->IsAttached()) { + return mHost.get(); + } + return nullptr; +} + +RefPtr TexturedLayerMLGPU::BindAndGetTexture() { + if (!mHost) { + return nullptr; + } + + LayerManagerMLGPU* lm = GetLayerManager()->AsLayerManagerMLGPU(); + + // Note: we don't call FinishRendering since mask layers do not need + // composite notifications or bias updates. (This function should + // not be called for non-mask-layers). + ImageHost::RenderInfo info; + if (!mHost->PrepareToRender(lm->GetTextureSourceProvider(), &info)) { + return nullptr; + } + + RefPtr source = mHost->AcquireTextureSource(info); + if (!source) { + return nullptr; + } + + mTexture = source; + return source; +} + +bool TexturedLayerMLGPU::OnPrepareToRender(FrameBuilder* aBuilder) { + if (!mHost) { + return false; + } + + LayerManagerMLGPU* lm = GetLayerManager()->AsLayerManagerMLGPU(); + + ImageHost::RenderInfo info; + if (!mHost->PrepareToRender(lm->GetTextureSourceProvider(), &info)) { + return false; + } + + RefPtr source = mHost->AcquireTextureSource(info); + if (!source) { + return false; + } + + if (source->AsBigImageIterator()) { + mBigImageTexture = source; + mTexture = nullptr; + } else { + mTexture = source; + } + + mPictureRect = IntRect(0, 0, info.img->mPictureRect.Width(), + info.img->mPictureRect.Height()); + + mHost->FinishRendering(info); + return true; +} + +void TexturedLayerMLGPU::AssignToView(FrameBuilder* aBuilder, + RenderViewMLGPU* aView, + Maybe&& aGeometry) { + if (mBigImageTexture) { + BigImageIterator* iter = mBigImageTexture->AsBigImageIterator(); + iter->BeginBigImageIteration(); + AssignBigImage(aBuilder, aView, iter, aGeometry); + iter->EndBigImageIteration(); + } else { + LayerMLGPU::AssignToView(aBuilder, aView, std::move(aGeometry)); + } +} + +void TexturedLayerMLGPU::AssignBigImage(FrameBuilder* aBuilder, + RenderViewMLGPU* aView, + BigImageIterator* aIter, + const Maybe& aGeometry) { + const Matrix4x4& transform = GetLayer()->GetEffectiveTransformForBuffer(); + + // Note that we don't need to assign these in any particular order, since + // they do not overlap. + do { + IntRect tileRect = aIter->GetTileRect(); + IntRect rect = tileRect.Intersect(mPictureRect); + if (rect.IsEmpty()) { + continue; + } + + { + Rect screenRect = transform.TransformBounds(Rect(rect)); + screenRect.MoveBy(-aView->GetTargetOffset()); + screenRect = + screenRect.Intersect(Rect(mComputedClipRect.ToUnknownRect())); + if (screenRect.IsEmpty()) { + // This tile is not in the clip region, so skip it. + continue; + } + } + + RefPtr tile = mBigImageTexture->ExtractCurrentTile(); + if (!tile) { + continue; + } + + // Create a temporary item. + RefPtr item = + new TempImageLayerMLGPU(aBuilder->GetManager()); + item->Init(this, tile, rect); + + Maybe geometry = aGeometry; + item->AddBoundsToView(aBuilder, aView, std::move(geometry)); + + // Since the layer tree is not holding this alive, we have to ask the + // FrameBuilder to do it for us. + aBuilder->RetainTemporaryLayer(item); + } while (aIter->NextTile()); +} + +TempImageLayerMLGPU::TempImageLayerMLGPU(LayerManagerMLGPU* aManager) + : ImageLayer(aManager, static_cast(this)), + TexturedLayerMLGPU(aManager), + mFilter(gfx::SamplingFilter::GOOD), + mIsOpaque(false) {} + +TempImageLayerMLGPU::~TempImageLayerMLGPU() = default; + +void TempImageLayerMLGPU::Init(TexturedLayerMLGPU* aSource, + const RefPtr& aTexture, + const gfx::IntRect& aPictureRect) { + // ImageLayer properties. + mEffectiveTransform = aSource->GetLayer()->GetEffectiveTransform(); + mEffectiveTransformForBuffer = + aSource->GetLayer()->GetEffectiveTransformForBuffer(); + + // Base LayerMLGPU properties. + mComputedClipRect = aSource->GetComputedClipRect(); + mMask = aSource->GetMask(); + mComputedOpacity = aSource->GetComputedOpacity(); + + // TexturedLayerMLGPU properties. + mHost = aSource->GetImageHost(); + mTexture = aTexture; + mPictureRect = aPictureRect; + + // Local properties. + mFilter = aSource->GetSamplingFilter(); + mShadowVisibleRegion = aSource->GetShadowVisibleRegion(); + mIsOpaque = aSource->IsContentOpaque(); + + // Set this layer to prepared so IsPrepared() assertions don't fire. + MarkPrepared(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/mlgpu/TexturedLayerMLGPU.h b/gfx/layers/mlgpu/TexturedLayerMLGPU.h new file mode 100644 index 0000000000..e6b4e69587 --- /dev/null +++ b/gfx/layers/mlgpu/TexturedLayerMLGPU.h @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_layers_mlgpu_TexturedLayerMLGPU_h +#define mozilla_gfx_layers_mlgpu_TexturedLayerMLGPU_h + +#include "LayerMLGPU.h" +#include "ImageLayers.h" +#include "mozilla/layers/ImageHost.h" + +namespace mozilla { +namespace layers { + +// This is the base class for canvas and image layers. +class TexturedLayerMLGPU : public LayerMLGPU { + public: + TexturedLayerMLGPU* AsTexturedLayerMLGPU() override { return this; } + + virtual gfx::SamplingFilter GetSamplingFilter() = 0; + + bool SetCompositableHost(CompositableHost* aHost) override; + CompositableHost* GetCompositableHost() override; + + void AssignToView(FrameBuilder* aBuilder, RenderViewMLGPU* aView, + Maybe&& aGeometry) override; + + TextureSource* GetTexture() const { return mTexture; } + ImageHost* GetImageHost() const { return mHost; } + + // Return the scale factor from the texture source to the picture rect. + virtual Maybe GetPictureScale() const { return Nothing(); } + + // Mask layers aren't prepared like normal layers. They are bound as + // mask operations are built. Mask layers are never tiled (they are + // scaled to a lower resolution if too big), so this pathway returns + // a TextureSource. + RefPtr BindAndGetTexture(); + + protected: + explicit TexturedLayerMLGPU(LayerManagerMLGPU* aManager); + virtual ~TexturedLayerMLGPU(); + + void AssignBigImage(FrameBuilder* aBuilder, RenderViewMLGPU* aView, + BigImageIterator* aIter, + const Maybe& aGeometry); + + bool OnPrepareToRender(FrameBuilder* aBuilder) override; + + protected: + RefPtr mHost; + RefPtr mTexture; + RefPtr mBigImageTexture; + gfx::IntRect mPictureRect; +}; + +// This is a pseudo layer that wraps a tile in an ImageLayer backed by a +// BigImage. Without this, we wouldn't have anything sensible to add to +// RenderPasses. In the future we could potentially consume the source +// layer more intelligently instead (for example, having it compute +// which textures are relevant for a given tile). +class TempImageLayerMLGPU final : public ImageLayer, public TexturedLayerMLGPU { + public: + explicit TempImageLayerMLGPU(LayerManagerMLGPU* aManager); + + // Layer + HostLayer* AsHostLayer() override { return this; } + gfx::SamplingFilter GetSamplingFilter() override { return mFilter; } + bool IsContentOpaque() override { return mIsOpaque; } + + void Init(TexturedLayerMLGPU* aSource, const RefPtr& aTexture, + const gfx::IntRect& aPictureRect); + + // HostLayer + Layer* GetLayer() override { return this; } + + protected: + virtual ~TempImageLayerMLGPU(); + + private: + gfx::SamplingFilter mFilter; + bool mIsOpaque; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_gfx_layers_mlgpu_TexturedLayerMLGPU_h diff --git a/gfx/layers/mlgpu/UtilityMLGPU.h b/gfx/layers/mlgpu/UtilityMLGPU.h new file mode 100644 index 0000000000..2432bf88b6 --- /dev/null +++ b/gfx/layers/mlgpu/UtilityMLGPU.h @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_gfx_layers_mlgpu_UtilityMLGPU_h +#define mozilla_gfx_layers_mlgpu_UtilityMLGPU_h + +#include "mozilla/Assertions.h" +#include "mozilla/MathAlgorithms.h" + +namespace mozilla { +namespace layers { + +template +struct AlignUp { + static inline size_t calc(size_t aAmount) { + MOZ_ASSERT(IsPowerOfTwo(T), "alignment must be a power of two"); + return aAmount + ((T - (aAmount % T)) % T); + } +}; + +template <> +struct AlignUp<0> { + static inline size_t calc(size_t aAmount) { return aAmount; } +}; + +} // namespace layers +} // namespace mozilla + +#ifdef ENABLE_AL_LOGGING +# define AL_LOG(...) printf_stderr("AL: " __VA_ARGS__) +# define AL_LOG_IF(cond, ...) \ + do { \ + if (cond) { \ + printf_stderr("AL: " __VA_ARGS__); \ + } \ + } while (0) +#else +# define AL_LOG(...) +# define AL_LOG_IF(...) +#endif + +#endif // mozilla_gfx_layers_mlgpu_UtilityMLGPU_h diff --git a/gfx/layers/moz.build b/gfx/layers/moz.build new file mode 100755 index 0000000000..f2c206f9a8 --- /dev/null +++ b/gfx/layers/moz.build @@ -0,0 +1,664 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +with Files("**"): + BUG_COMPONENT = ("Core", "Graphics: Layers") + +with Files("apz/**"): + BUG_COMPONENT = ("Core", "Panning and Zooming") + +EXPORTS += [ + "basic/BasicCanvasLayer.h", + "basic/BasicImplData.h", + "basic/BasicLayers.h", + "basic/BasicLayersImpl.h", + "basic/BasicPaintedLayer.h", + "client/ClientCanvasLayer.h", + "client/ClientContainerLayer.h", + "client/ClientLayerManager.h", + "client/ClientPaintedLayer.h", + "client/ClientTiledPaintedLayer.h", + "composite/CompositableHost.h", + "composite/ImageHost.h", + "CompositorTypes.h", + "D3D9SurfaceImage.h", + "FrameMetrics.h", + "GLImages.h", + "GPUVideoImage.h", + "ImageContainer.h", + "ImageLayers.h", + "ImageTypes.h", + "IMFYCbCrImage.h", + "Layers.h", + "LayerScope.h", + "LayerSorter.h", + "LayersTypes.h", + "LayerTreeInvalidation.h", + "LayerUserData.h", + "opengl/OGLShaderConfig.h", + "opengl/OGLShaderProgram.h", + "protobuf/LayerScopePacket.pb.h", + "ReadbackLayer.h", + "TiledLayerBuffer.h", +] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows": + SOURCES += [ + "D3D11ShareHandleImage.cpp", + "D3D11YCbCrImage.cpp", + ] + UNIFIED_SOURCES += [ + "D3D9SurfaceImage.cpp", + "IMFYCbCrImage.cpp", + "TextureDIB.cpp", + ] + EXPORTS.mozilla.layers += [ + "TextureDIB.h", + ] + if CONFIG["MOZ_ENABLE_D3D10_LAYER"]: + EXPORTS.mozilla.layers += [ + "d3d11/CompositorD3D11.h", + "d3d11/DeviceAttachmentsD3D11.h", + "d3d11/DiagnosticsD3D11.h", + "d3d11/HelpersD3D11.h", + "d3d11/MLGDeviceD3D11.h", + "d3d11/ReadbackManagerD3D11.h", + "d3d11/ShaderDefinitionsD3D11.h", + "d3d11/TextureD3D11.h", + ] + UNIFIED_SOURCES += [ + "d3d11/DiagnosticsD3D11.cpp", + "d3d11/MLGDeviceD3D11.cpp", + "d3d11/TextureD3D11.cpp", + ] + SOURCES += [ + "d3d11/CompositorD3D11.cpp", + "d3d11/DeviceAttachmentsD3D11.cpp", + "d3d11/ReadbackManagerD3D11.cpp", + ] + +EXPORTS.gfxipc += [ + "ipc/ShadowLayerUtils.h", + "ipc/SurfaceDescriptor.h", +] + +EXPORTS.mozilla.dom += [ + "apz/util/CheckerboardReportService.h", +] + +EXPORTS.mozilla.gfx += [ + "BuildConstants.h", +] + +EXPORTS.mozilla.layers += [ + "AnimationHelper.h", + "AnimationInfo.h", + "AnimationStorageData.h", + "apz/public/APZInputBridge.h", + "apz/public/APZPublicUtils.h", + "apz/public/APZSampler.h", + "apz/public/APZUpdater.h", + "apz/public/CompositorController.h", + "apz/public/GeckoContentController.h", + "apz/public/GeckoContentControllerTypes.h", + "apz/public/IAPZCTreeManager.h", + "apz/public/MatrixMessage.h", + "apz/public/MetricsSharingController.h", + # exporting things from apz/src is temporary until we extract a + # proper interface for the code there + "apz/src/APZUtils.h", + "apz/src/AsyncDragMetrics.h", + "apz/src/FocusTarget.h", + "apz/src/KeyboardMap.h", + "apz/src/KeyboardScrollAction.h", + "apz/testutil/APZTestData.h", + "apz/util/ActiveElementManager.h", + "apz/util/APZCCallbackHelper.h", + "apz/util/APZEventState.h", + "apz/util/APZThreadUtils.h", + "apz/util/ChromeProcessController.h", + "apz/util/ContentProcessController.h", + "apz/util/DoubleTapToZoom.h", + "apz/util/InputAPZContext.h", + "apz/util/ScrollInputMethods.h", + "apz/util/ScrollLinkedEffectDetector.h", + "apz/util/TouchActionHelper.h", + "apz/util/TouchCounter.h", + "AtomicRefCountedWithFinalize.h", + "AxisPhysicsModel.h", + "AxisPhysicsMSDModel.h", + "basic/BasicCompositor.h", + "basic/MacIOSurfaceTextureHostBasic.h", + "basic/TextureHostBasic.h", + "BSPTree.h", + "BufferTexture.h", + "BuildConstants.h", + "CanvasDrawEventRecorder.h", + "CanvasRenderer.h", + "client/CanvasClient.h", + "client/CompositableClient.h", + "client/ContentClient.h", + "client/GPUVideoTextureClient.h", + "client/ImageClient.h", + "client/MultiTiledContentClient.h", + "client/SingleTiledContentClient.h", + "client/TextureClient.h", + "client/TextureClientPool.h", + "client/TextureClientRecycleAllocator.h", + "client/TextureClientSharedSurface.h", + "client/TextureRecorded.h", + "client/TiledContentClient.h", + "composite/AsyncCompositionManager.h", + "composite/CanvasLayerComposite.h", + "composite/ColorLayerComposite.h", + "composite/ContainerLayerComposite.h", + "composite/ContentHost.h", + "composite/Diagnostics.h", + "composite/FPSCounter.h", + "composite/FrameUniformityData.h", + "composite/GPUVideoTextureHost.h", + "composite/ImageComposite.h", + "composite/ImageHost.h", + "composite/ImageLayerComposite.h", + "composite/LayerManagerComposite.h", + "composite/LayerManagerCompositeUtils.h", + "composite/PaintedLayerComposite.h", + "composite/TextRenderer.h", + "composite/TextureHost.h", + "composite/TiledContentHost.h", + "CompositionRecorder.h", + "Compositor.h", + "CompositorAnimationStorage.h", + "CompositorTypes.h", + "D3D11ShareHandleImage.h", + "D3D11YCbCrImage.h", + "D3D9SurfaceImage.h", + "DirectionUtils.h", + "Effects.h", + "ImageDataSerializer.h", + "ipc/APZChild.h", + "ipc/APZCTreeManagerChild.h", + "ipc/APZCTreeManagerParent.h", + "ipc/APZInputBridgeChild.h", + "ipc/APZInputBridgeParent.h", + "ipc/CanvasChild.h", + "ipc/CanvasThread.h", + "ipc/CanvasTranslator.h", + "ipc/CompositableForwarder.h", + "ipc/CompositableTransactionParent.h", + "ipc/CompositorBridgeChild.h", + "ipc/CompositorBridgeParent.h", + "ipc/CompositorManagerChild.h", + "ipc/CompositorManagerParent.h", + "ipc/CompositorThread.h", + "ipc/CompositorVsyncScheduler.h", + "ipc/CompositorVsyncSchedulerOwner.h", + "ipc/ContentCompositorBridgeParent.h", + "ipc/ImageBridgeChild.h", + "ipc/ImageBridgeParent.h", + "ipc/ISurfaceAllocator.h", + "ipc/KnowsCompositor.h", + "ipc/LayerAnimationUtils.h", + "ipc/LayersMessageUtils.h", + "ipc/LayerTransactionChild.h", + "ipc/LayerTransactionParent.h", + "ipc/LayerTreeOwnerTracker.h", + "ipc/RefCountedShmem.h", + "ipc/RemoteContentController.h", + "ipc/ShadowLayers.h", + "ipc/SharedPlanarYCbCrImage.h", + "ipc/SharedRGBImage.h", + "ipc/SharedSurfacesChild.h", + "ipc/SharedSurfacesMemoryReport.h", + "ipc/SharedSurfacesParent.h", + "ipc/SynchronousTask.h", + "ipc/TextureForwarder.h", + "ipc/UiCompositorControllerChild.h", + "ipc/UiCompositorControllerMessageTypes.h", + "ipc/UiCompositorControllerParent.h", + "ipc/VideoBridgeChild.h", + "ipc/VideoBridgeParent.h", + "ipc/VideoBridgeUtils.h", + "LayerAttributes.h", + "LayerManager.h", + "LayerMetricsWrapper.h", + "LayersHelpers.h", + "LayersTypes.h", + "MemoryPressureObserver.h", + "mlgpu/LayerManagerMLGPU.h", + "mlgpu/LayerMLGPU.h", + "mlgpu/MemoryReportingMLGPU.h", + "mlgpu/MLGDevice.h", + "mlgpu/MLGDeviceTypes.h", + "mlgpu/MLGPUScreenshotGrabber.h", + "mlgpu/ShaderDefinitionsMLGPU.h", + "mlgpu/UtilityMLGPU.h", + "NativeLayer.h", + "OOPCanvasRenderer.h", + "opengl/CompositingRenderTargetOGL.h", + "opengl/CompositorOGL.h", + "opengl/MacIOSurfaceTextureClientOGL.h", + "opengl/MacIOSurfaceTextureHostOGL.h", + "opengl/TextureClientOGL.h", + "opengl/TextureHostOGL.h", + "PaintThread.h", + "PersistentBufferProvider.h", + "ProfilerScreenshots.h", + "RenderTrace.h", + "RepaintRequest.h", + "RotatedBuffer.h", + "SampleTime.h", + "ScreenshotGrabber.h", + "ScrollableLayerGuid.h", + "ShareableCanvasRenderer.h", + "SourceSurfaceSharedData.h", + "SourceSurfaceVolatileData.h", + "SurfacePool.h", + "SyncObject.h", + "TextureSourceProvider.h", + "TextureWrapperImage.h", + "TransactionIdAllocator.h", + "TreeTraversal.h", + "UpdateImageHelper.h", + "wr/AsyncImagePipelineManager.h", + "wr/ClipManager.h", + "wr/DisplayItemCache.h", + "wr/IpcResourceUpdateQueue.h", + "wr/OMTAController.h", + "wr/OMTASampler.h", + "wr/RenderRootStateManager.h", + "wr/RenderRootTypes.h", + "wr/StackingContextHelper.h", + "wr/WebRenderBridgeChild.h", + "wr/WebRenderBridgeParent.h", + "wr/WebRenderCanvasRenderer.h", + "wr/WebRenderCommandBuilder.h", + "wr/WebRenderDrawEventRecorder.h", + "wr/WebRenderImageHost.h", + "wr/WebRenderLayerManager.h", + "wr/WebRenderMessageUtils.h", + "wr/WebRenderScrollData.h", + "wr/WebRenderScrollDataWrapper.h", + "wr/WebRenderTextureHost.h", + "wr/WebRenderUserData.h", + "ZoomConstraints.h", +] + +if CONFIG["MOZ_X11"]: + EXPORTS.mozilla.layers += [ + "basic/TextureClientX11.h", + "basic/X11TextureSourceBasic.h", + "composite/X11TextureHost.h", + "ipc/ShadowLayerUtilsX11.h", + "opengl/X11TextureSourceOGL.h", + ] + SOURCES += [ + "basic/TextureClientX11.cpp", + "basic/X11BasicCompositor.cpp", + "basic/X11TextureSourceBasic.cpp", + "composite/X11TextureHost.cpp", + "ipc/ShadowLayerUtilsX11.cpp", + "opengl/X11TextureSourceOGL.cpp", + ] + +if CONFIG["MOZ_WAYLAND"]: + EXPORTS.mozilla.layers += [ + "DMABUFSurfaceImage.h", + "opengl/DMABUFTextureClientOGL.h", + "opengl/DMABUFTextureHostOGL.h", + ] + SOURCES += [ + "DMABUFSurfaceImage.cpp", + "opengl/DMABUFTextureClientOGL.cpp", + "opengl/DMABUFTextureHostOGL.cpp", + ] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + EXPORTS.mozilla.layers += [ + "NativeLayerCA.h", + "SurfacePoolCA.h", + "TextureSync.h", + ] + EXPORTS += [ + "MacIOSurfaceHelpers.h", + "MacIOSurfaceImage.h", + ] + UNIFIED_SOURCES += [ + "NativeLayerCA.mm", + "SurfacePoolCA.mm", + "TextureSync.cpp", + ] + SOURCES += [ + "ipc/ShadowLayerUtilsMac.cpp", + "MacIOSurfaceHelpers.cpp", + "MacIOSurfaceImage.cpp", + ] + OS_LIBS += [ + "-framework IOSurface", + ] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "android": + EXPORTS.mozilla.layers += [ + "AndroidHardwareBuffer.h", + ] + UNIFIED_SOURCES += [ + "AndroidHardwareBuffer.cpp", + "apz/src/AndroidAPZ.cpp", + "apz/src/AndroidFlingPhysics.cpp", + "apz/src/AndroidVelocityTracker.cpp", + ] + +UNIFIED_SOURCES += [ + "AnimationHelper.cpp", + "AnimationInfo.cpp", + "apz/src/APZCTreeManager.cpp", + "apz/src/APZInputBridge.cpp", + "apz/src/APZPublicUtils.cpp", + "apz/src/APZSampler.cpp", + "apz/src/APZUpdater.cpp", + "apz/src/APZUtils.cpp", + "apz/src/AsyncPanZoomController.cpp", + "apz/src/AutoscrollAnimation.cpp", + "apz/src/Axis.cpp", + "apz/src/CheckerboardEvent.cpp", + "apz/src/DragTracker.cpp", + "apz/src/ExpectedGeckoMetrics.cpp", + "apz/src/FlingAccelerator.cpp", + "apz/src/FocusState.cpp", + "apz/src/FocusTarget.cpp", + "apz/src/GenericScrollAnimation.cpp", + "apz/src/GestureEventListener.cpp", + "apz/src/HitTestingTreeNode.cpp", + "apz/src/InputBlockState.cpp", + "apz/src/InputQueue.cpp", + "apz/src/KeyboardMap.cpp", + "apz/src/KeyboardScrollAction.cpp", + "apz/src/OverscrollHandoffState.cpp", + "apz/src/OvershootDetector.cpp", + "apz/src/PotentialCheckerboardDurationTracker.cpp", + "apz/src/QueuedInput.cpp", + "apz/src/SampledAPZCState.cpp", + "apz/src/SimpleVelocityTracker.cpp", + "apz/src/SmoothMsdScrollAnimation.cpp", + "apz/src/SmoothScrollAnimation.cpp", + "apz/src/WheelScrollAnimation.cpp", + "apz/testutil/APZTestData.cpp", + "apz/util/ActiveElementManager.cpp", + "apz/util/APZCCallbackHelper.cpp", + "apz/util/APZEventState.cpp", + "apz/util/APZThreadUtils.cpp", + "apz/util/CheckerboardReportService.cpp", + "apz/util/ChromeProcessController.cpp", + "apz/util/ContentProcessController.cpp", + "apz/util/DoubleTapToZoom.cpp", + "apz/util/InputAPZContext.cpp", + "apz/util/ScrollLinkedEffectDetector.cpp", + "apz/util/TouchActionHelper.cpp", + "apz/util/TouchCounter.cpp", + "AxisPhysicsModel.cpp", + "AxisPhysicsMSDModel.cpp", + "basic/BasicCanvasLayer.cpp", + "basic/BasicColorLayer.cpp", + "basic/BasicContainerLayer.cpp", + "basic/BasicImages.cpp", + "basic/BasicLayerManager.cpp", + "basic/BasicLayersImpl.cpp", + "basic/BasicPaintedLayer.cpp", + "basic/TextureHostBasic.cpp", + "BSPTree.cpp", + "BufferTexture.cpp", + "CanvasDrawEventRecorder.cpp", + "CanvasRenderer.cpp", + "client/CanvasClient.cpp", + "client/ClientCanvasLayer.cpp", + "client/ClientCanvasRenderer.cpp", + "client/ClientColorLayer.cpp", + "client/ClientContainerLayer.cpp", + "client/ClientImageLayer.cpp", + "client/ClientLayerManager.cpp", + "client/ClientPaintedLayer.cpp", + "client/ClientTiledPaintedLayer.cpp", + "client/CompositableClient.cpp", + "client/ContentClient.cpp", + "client/GPUVideoTextureClient.cpp", + "client/ImageClient.cpp", + "client/MultiTiledContentClient.cpp", + "client/SingleTiledContentClient.cpp", + "client/TextureClientPool.cpp", + "client/TextureClientRecycleAllocator.cpp", + "client/TextureClientSharedSurface.cpp", + "client/TextureRecorded.cpp", + "client/TiledContentClient.cpp", + "composite/AsyncCompositionManager.cpp", + "composite/CanvasLayerComposite.cpp", + "composite/ColorLayerComposite.cpp", + "composite/CompositableHost.cpp", + "composite/ContainerLayerComposite.cpp", + "composite/ContentHost.cpp", + "composite/Diagnostics.cpp", + "composite/FPSCounter.cpp", + "composite/FrameUniformityData.cpp", + "composite/GPUVideoTextureHost.cpp", + "composite/ImageComposite.cpp", + "composite/ImageHost.cpp", + "composite/ImageLayerComposite.cpp", + "composite/LayerManagerComposite.cpp", + "composite/PaintedLayerComposite.cpp", + "composite/TextRenderer.cpp", + "composite/TextureHost.cpp", + "composite/TiledContentHost.cpp", + "CompositionRecorder.cpp", + "Compositor.cpp", + "CompositorAnimationStorage.cpp", + "CompositorTypes.cpp", + "Effects.cpp", + "FrameMetrics.cpp", + "GLImages.cpp", + "ImageDataSerializer.cpp", + "ImageLayers.cpp", + "ipc/APZChild.cpp", + "ipc/APZCTreeManagerChild.cpp", + "ipc/APZCTreeManagerParent.cpp", + "ipc/APZInputBridgeChild.cpp", + "ipc/APZInputBridgeParent.cpp", + "ipc/CanvasChild.cpp", + "ipc/CanvasThread.cpp", + "ipc/CanvasTranslator.cpp", + "ipc/CompositableForwarder.cpp", + "ipc/CompositableTransactionParent.cpp", + "ipc/CompositorBench.cpp", + "ipc/CompositorBridgeChild.cpp", + "ipc/CompositorBridgeParent.cpp", + "ipc/CompositorManagerChild.cpp", + "ipc/CompositorManagerParent.cpp", + "ipc/CompositorThread.cpp", + "ipc/CompositorVsyncScheduler.cpp", + "ipc/ContentCompositorBridgeParent.cpp", + "ipc/ImageBridgeChild.cpp", + "ipc/ImageBridgeParent.cpp", + "ipc/ISurfaceAllocator.cpp", + "ipc/LayerAnimationUtils.cpp", + "ipc/LayerTransactionChild.cpp", + "ipc/LayerTransactionParent.cpp", + "ipc/LayerTreeOwnerTracker.cpp", + "ipc/RefCountedShmem.cpp", + "ipc/RemoteContentController.cpp", + "ipc/ShadowLayers.cpp", + "ipc/SharedPlanarYCbCrImage.cpp", + "ipc/SharedRGBImage.cpp", + "ipc/SharedSurfacesChild.cpp", + "ipc/SharedSurfacesParent.cpp", + "ipc/UiCompositorControllerChild.cpp", + "ipc/UiCompositorControllerParent.cpp", + "ipc/VideoBridgeChild.cpp", + "ipc/VideoBridgeParent.cpp", + "LayerManager.cpp", + "Layers.cpp", + "LayerScope.cpp", + "LayersHelpers.cpp", + "LayerSorter.cpp", + "LayersTypes.cpp", + "LayerTreeInvalidation.cpp", + "MemoryPressureObserver.cpp", + "mlgpu/BufferCache.cpp", + "mlgpu/CanvasLayerMLGPU.cpp", + "mlgpu/ContainerLayerMLGPU.cpp", + "mlgpu/FrameBuilder.cpp", + "mlgpu/ImageLayerMLGPU.cpp", + "mlgpu/LayerManagerMLGPU.cpp", + "mlgpu/LayerMLGPU.cpp", + "mlgpu/MaskOperation.cpp", + "mlgpu/MemoryReportingMLGPU.cpp", + "mlgpu/MLGDevice.cpp", + "mlgpu/MLGPUScreenshotGrabber.cpp", + "mlgpu/PaintedLayerMLGPU.cpp", + "mlgpu/RenderPassMLGPU.cpp", + "mlgpu/RenderViewMLGPU.cpp", + "mlgpu/SharedBufferMLGPU.cpp", + "mlgpu/StagingBuffer.cpp", + "mlgpu/TexturedLayerMLGPU.cpp", + "mlgpu/TextureSourceProviderMLGPU.cpp", + "opengl/CompositingRenderTargetOGL.cpp", + "opengl/CompositorOGL.cpp", + "opengl/GLBlitTextureImageHelper.cpp", + "opengl/OGLShaderProgram.cpp", + "opengl/TextureClientOGL.cpp", + "opengl/TextureHostOGL.cpp", + "PaintThread.cpp", + "ProfilerScreenshots.cpp", + "ReadbackProcessor.cpp", + "RenderTrace.cpp", + "RepaintRequest.cpp", + "RotatedBuffer.cpp", + "SampleTime.cpp", + "ScreenshotGrabber.cpp", + "ScrollableLayerGuid.cpp", + "ShareableCanvasRenderer.cpp", + "SourceSurfaceSharedData.cpp", + "SourceSurfaceVolatileData.cpp", + "SyncObject.cpp", + "TextureSourceProvider.cpp", + "TextureWrapperImage.cpp", + "wr/AsyncImagePipelineManager.cpp", + "wr/ClipManager.cpp", + "wr/DisplayItemCache.cpp", + "wr/IpcResourceUpdateQueue.cpp", + "wr/OMTAController.cpp", + "wr/OMTASampler.cpp", + "wr/RenderRootStateManager.cpp", + "wr/RenderRootTypes.cpp", + "wr/StackingContextHelper.cpp", + "wr/WebRenderBridgeChild.cpp", + "wr/WebRenderBridgeParent.cpp", + "wr/WebRenderCanvasRenderer.cpp", + "wr/WebRenderCommandBuilder.cpp", + "wr/WebRenderDrawEventRecorder.cpp", + "wr/WebRenderImageHost.cpp", + "wr/WebRenderLayerManager.cpp", + "wr/WebRenderScrollData.cpp", + "wr/WebRenderUserData.cpp", + "ZoomConstraints.cpp", + # XXX here are some unified build error. + #'wr/WebRenderTextureHost.cpp' +] + +SOURCES += [ + "basic/BasicCompositor.cpp", + "basic/BasicImageLayer.cpp", + "client/TextureClient.cpp", + "ImageContainer.cpp", + "PersistentBufferProvider.cpp", + "protobuf/LayerScopePacket.pb.cc", + "wr/WebRenderTextureHost.cpp", +] + +DEFINES["GOOGLE_PROTOBUF_NO_RTTI"] = True +DEFINES["GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER"] = True +DEFINES["MOZ_APP_VERSION"] = CONFIG["MOZ_APP_VERSION"] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + SOURCES += [ + "basic/MacIOSurfaceTextureHostBasic.cpp", + "opengl/MacIOSurfaceTextureClientOGL.cpp", + "opengl/MacIOSurfaceTextureHostOGL.cpp", + ] + +IPDL_SOURCES += [ + "ipc/LayersMessages.ipdlh", + "ipc/LayersSurfaces.ipdlh", + "ipc/PAPZ.ipdl", + "ipc/PAPZCTreeManager.ipdl", + "ipc/PAPZInputBridge.ipdl", + "ipc/PCanvas.ipdl", + "ipc/PCompositorBridge.ipdl", + "ipc/PCompositorBridgeTypes.ipdlh", + "ipc/PCompositorManager.ipdl", + "ipc/PImageBridge.ipdl", + "ipc/PLayerTransaction.ipdl", + "ipc/PTexture.ipdl", + "ipc/PUiCompositorController.ipdl", + "ipc/PVideoBridge.ipdl", + "ipc/PWebRenderBridge.ipdl", + "ipc/WebRenderMessages.ipdlh", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows": + GeneratedFile( + "CompositorD3D11Shaders.h", + script="d3d11/genshaders.py", + inputs=["d3d11/shaders.manifest"], + ) + GeneratedFile( + "MLGShaders.h", + script="d3d11/genshaders.py", + inputs=["d3d11/mlgshaders/shaders.manifest"], + ) + +LOCAL_INCLUDES += [ + "/docshell/base", # for nsDocShell.h + "/dom/canvas", # for intertwined WebGL headers + "/layout/base", # for TouchManager.h + "/layout/generic", # for nsTextFrame.h + "/media/libyuv/libyuv/include", # for libyuv.h +] + +FINAL_LIBRARY = "xul" + +if CONFIG["MOZ_DEBUG"]: + DEFINES["D3D_DEBUG_INFO"] = True + +if CONFIG["MOZ_ENABLE_D3D10_LAYER"]: + DEFINES["MOZ_ENABLE_D3D10_LAYER"] = True + +if CONFIG["ENABLE_TESTS"]: + DIRS += ["apz/test/gtest"] + DIRS += ["apz/test/gtest/mvm"] + +MOCHITEST_MANIFESTS += ["apz/test/mochitest/mochitest.ini"] +BROWSER_CHROME_MANIFESTS += ["apz/test/mochitest/browser.ini"] + +CXXFLAGS += CONFIG["MOZ_CAIRO_CFLAGS"] +CXXFLAGS += CONFIG["TK_CFLAGS"] + +LOCAL_INCLUDES += CONFIG["SKIA_INCLUDES"] + +if CONFIG["CC_TYPE"] in ("clang", "gcc"): + CXXFLAGS += ["-Wno-error=shadow"] + # Suppress warnings in third-party code. + CXXFLAGS += ["-Wno-maybe-uninitialized"] + +if CONFIG["MOZ_ENABLE_SKIA"]: + UNIFIED_SOURCES += [ + "composite/PaintCounter.cpp", + ] + +if CONFIG["FUZZING"] and CONFIG["FUZZING_INTERFACES"]: + TEST_DIRS += ["ipc/fuzztest"] + +# Add libFuzzer configuration directives +include("/tools/fuzzing/libfuzzer-config.mozbuild") diff --git a/gfx/layers/opengl/CompositingRenderTargetOGL.cpp b/gfx/layers/opengl/CompositingRenderTargetOGL.cpp new file mode 100644 index 0000000000..9fbc207a6b --- /dev/null +++ b/gfx/layers/opengl/CompositingRenderTargetOGL.cpp @@ -0,0 +1,117 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "CompositingRenderTargetOGL.h" +#include "GLContext.h" +#include "GLReadTexImageHelper.h" +#include "ScopedGLHelpers.h" +#include "mozilla/gfx/2D.h" + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; +using namespace mozilla::gl; + +CompositingRenderTargetOGL::~CompositingRenderTargetOGL() { + if (mGLResourceOwnership == GLResourceOwnership::OWNED_BY_RENDER_TARGET && + mGL && mGL->MakeCurrent()) { + mGL->fDeleteTextures(1, &mTextureHandle); + mGL->fDeleteFramebuffers(1, &mFBO); + } +} + +void CompositingRenderTargetOGL::BindTexture(GLenum aTextureUnit, + GLenum aTextureTarget) { + MOZ_ASSERT(!mNeedInitialization); + MOZ_ASSERT(mTextureHandle != 0); + mGL->fActiveTexture(aTextureUnit); + mGL->fBindTexture(aTextureTarget, mTextureHandle); +} + +void CompositingRenderTargetOGL::BindRenderTarget() { + bool needsClear = false; + + if (mNeedInitialization) { + Initialize(mNeedInitialization->mFBOTextureTarget); + if (mNeedInitialization->mInitMode == INIT_MODE_CLEAR) { + needsClear = true; + mClearOnBind = false; + } + mNeedInitialization = Nothing(); + } else { + GLuint fbo = GetFBO(); + mGL->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, fbo); + GLenum result = mGL->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER); + if (result != LOCAL_GL_FRAMEBUFFER_COMPLETE) { + // The main framebuffer (0) of non-offscreen contexts + // might be backed by a EGLSurface that needs to be renewed. + if (mFBO == 0 && !mGL->IsOffscreen()) { + mGL->RenewSurface(mCompositor->GetWidget()); + result = mGL->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER); + } + if (result != LOCAL_GL_FRAMEBUFFER_COMPLETE) { + nsAutoCString msg; + msg.AppendPrintf( + "Framebuffer not complete -- CheckFramebufferStatus returned 0x%x, " + "GLContext=%p, IsOffscreen()=%d, mFBO=%d, " + "aRect.width=%d, aRect.height=%d", + result, mGL.get(), mGL->IsOffscreen(), mFBO, mSize.width, + mSize.height); + NS_WARNING(msg.get()); + } + } + + needsClear = mClearOnBind; + } + + if (needsClear) { + ScopedGLState scopedScissorTestState(mGL, LOCAL_GL_SCISSOR_TEST, true); + ScopedScissorRect autoScissorRect(mGL, 0, 0, mSize.width, mSize.height); + mGL->fClearColor(0.0, 0.0, 0.0, 0.0); + mGL->fClearDepth(0.0); + mGL->fClear(LOCAL_GL_COLOR_BUFFER_BIT | LOCAL_GL_DEPTH_BUFFER_BIT); + } +} + +GLuint CompositingRenderTargetOGL::GetFBO() const { + MOZ_ASSERT(!mNeedInitialization); + return mFBO == 0 ? mGL->GetDefaultFramebuffer() : mFBO; +} + +#ifdef MOZ_DUMP_PAINTING +already_AddRefed CompositingRenderTargetOGL::Dump( + Compositor* aCompositor) { + MOZ_ASSERT(!mNeedInitialization); + CompositorOGL* compositorOGL = aCompositor->AsCompositorOGL(); + return ReadBackSurface(mGL, mTextureHandle, true, + compositorOGL->GetFBOFormat()); +} +#endif + +void CompositingRenderTargetOGL::Initialize(GLenum aFBOTextureTarget) { + // TODO: call mGL->GetBackbufferFB(), use that + GLuint fbo = mFBO == 0 ? mGL->GetDefaultFramebuffer() : mFBO; + mGL->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, fbo); + mGL->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0, + aFBOTextureTarget, mTextureHandle, 0); + + // Making this call to fCheckFramebufferStatus prevents a crash on + // PowerVR. See bug 695246. + GLenum result = mGL->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER); + if (result != LOCAL_GL_FRAMEBUFFER_COMPLETE) { + nsAutoCString msg; + msg.AppendPrintf( + "Framebuffer not complete -- error 0x%x, aFBOTextureTarget 0x%x, mFBO " + "%d, mTextureHandle %d, aRect.width %d, aRect.height %d", + result, aFBOTextureTarget, mFBO, mTextureHandle, mSize.width, + mSize.height); + NS_ERROR(msg.get()); + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/opengl/CompositingRenderTargetOGL.h b/gfx/layers/opengl/CompositingRenderTargetOGL.h new file mode 100644 index 0000000000..14e7265706 --- /dev/null +++ b/gfx/layers/opengl/CompositingRenderTargetOGL.h @@ -0,0 +1,212 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_COMPOSITINGRENDERTARGETOGL_H +#define MOZILLA_GFX_COMPOSITINGRENDERTARGETOGL_H + +#include "GLContextTypes.h" // for GLContext +#include "GLDefs.h" // for GLenum, LOCAL_GL_FRAMEBUFFER, etc +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/Attributes.h" // for override +#include "mozilla/RefPtr.h" // for RefPtr, already_AddRefed +#include "mozilla/gfx/Point.h" // for IntSize, IntSizeTyped +#include "mozilla/gfx/Types.h" // for SurfaceFormat, etc +#include "mozilla/layers/Compositor.h" // for SurfaceInitMode, etc +#include "mozilla/layers/TextureHost.h" // for CompositingRenderTarget +#include "mozilla/layers/CompositorOGL.h" // for CompositorOGL +#include "mozilla/mozalloc.h" // for operator new +#include "nsAString.h" +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsDebug.h" // for NS_ERROR, NS_WARNING +#include "nsString.h" // for nsAutoCString + +namespace mozilla { +namespace gl { +class BindableTexture; +} // namespace gl +namespace gfx { +class DataSourceSurface; +} // namespace gfx + +namespace layers { + +class TextureSource; + +class CompositingRenderTargetOGL : public CompositingRenderTarget { + typedef mozilla::gl::GLContext GLContext; + + friend class CompositorOGL; + + enum class GLResourceOwnership : uint8_t { + // Framebuffer and texture will be deleted when the RenderTarget is + // destroyed. + OWNED_BY_RENDER_TARGET, + + // Framebuffer and texture are only used by the RenderTarget, but never + // deleted. + EXTERNALLY_OWNED + }; + + struct InitParams { + GLenum mFBOTextureTarget; + SurfaceInitMode mInitMode; + }; + + public: + ~CompositingRenderTargetOGL(); + + const char* Name() const override { return "CompositingRenderTargetOGL"; } + + /** + * Create a render target around the default FBO, for rendering straight to + * the window. + */ + static already_AddRefed CreateForWindow( + CompositorOGL* aCompositor, const gfx::IntSize& aSize) { + RefPtr result = new CompositingRenderTargetOGL( + aCompositor, gfx::IntRect(gfx::IntPoint(), aSize), gfx::IntPoint(), + aSize, GLResourceOwnership::EXTERNALLY_OWNED, 0, 0, Nothing()); + return result.forget(); + } + + static already_AddRefed + CreateForNewFBOAndTakeOwnership(CompositorOGL* aCompositor, GLuint aTexture, + GLuint aFBO, const gfx::IntRect& aRect, + const gfx::IntPoint& aClipSpaceOrigin, + const gfx::IntSize& aPhySize, + GLenum aFBOTextureTarget, + SurfaceInitMode aInit) { + RefPtr result = new CompositingRenderTargetOGL( + aCompositor, aRect, aClipSpaceOrigin, aPhySize, + GLResourceOwnership::OWNED_BY_RENDER_TARGET, aTexture, aFBO, + Some(InitParams{aFBOTextureTarget, aInit})); + return result.forget(); + } + + static already_AddRefed + CreateForExternallyOwnedFBO(CompositorOGL* aCompositor, GLuint aFBO, + const gfx::IntRect& aRect, + const gfx::IntPoint& aClipSpaceOrigin) { + RefPtr result = new CompositingRenderTargetOGL( + aCompositor, aRect, aClipSpaceOrigin, aRect.Size(), + GLResourceOwnership::EXTERNALLY_OWNED, 0, aFBO, Nothing()); + return result.forget(); + } + + void BindTexture(GLenum aTextureUnit, GLenum aTextureTarget); + + /** + * Call when we want to draw into our FBO + */ + void BindRenderTarget(); + + bool IsWindow() { return mFBO == 0; } + + GLuint GetFBO() const; + + GLuint GetTextureHandle() const { + MOZ_ASSERT(!mNeedInitialization); + return mTextureHandle; + } + + // TextureSourceOGL + TextureSourceOGL* AsSourceOGL() override { + // XXX - Bug 900770 + MOZ_ASSERT( + false, + "CompositingRenderTargetOGL should not be used as a TextureSource"); + return nullptr; + } + gfx::IntSize GetSize() const override { return mSize; } + + // The point that DrawGeometry's aClipRect is relative to. Will be (0, 0) for + // root render targets and equal to GetOrigin() for non-root render targets. + gfx::IntPoint GetClipSpaceOrigin() const { return mClipSpaceOrigin; } + + gfx::SurfaceFormat GetFormat() const override { + // XXX - Should it be implemented ? is the above assert true ? + MOZ_ASSERT(false, "Not implemented"); + return gfx::SurfaceFormat::UNKNOWN; + } + + // In render target coordinates, i.e. the same space as GetOrigin(). + // NOT relative to mClipSpaceOrigin! + void SetClipRect(const Maybe& aRect) { mClipRect = aRect; } + const Maybe& GetClipRect() const { return mClipRect; } + +#ifdef MOZ_DUMP_PAINTING + already_AddRefed Dump( + Compositor* aCompositor) override; +#endif + + const gfx::IntSize& GetInitSize() const { return mSize; } + const gfx::IntSize& GetPhysicalSize() const { return mPhySize; } + + protected: + CompositingRenderTargetOGL(CompositorOGL* aCompositor, + const gfx::IntRect& aRect, + const gfx::IntPoint& aClipSpaceOrigin, + const gfx::IntSize& aPhySize, + GLResourceOwnership aGLResourceOwnership, + GLuint aTexure, GLuint aFBO, + const Maybe& aNeedInitialization) + : CompositingRenderTarget(aRect.TopLeft()), + mNeedInitialization(aNeedInitialization), + mSize(aRect.Size()), + mPhySize(aPhySize), + mCompositor(aCompositor), + mGL(aCompositor->gl()), + mClipSpaceOrigin(aClipSpaceOrigin), + mGLResourceOwnership(aGLResourceOwnership), + mTextureHandle(aTexure), + mFBO(aFBO) { + MOZ_ASSERT(mGL); + } + + /** + * Actually do the initialisation. + * We do this lazily so that when we first set this render target on the + * compositor we do not have to re-bind the FBO after unbinding it, or + * alternatively leave the FBO bound after creation. Note that we leave our + * FBO bound, and so calling this method is only suitable when about to use + * this render target. + */ + void Initialize(GLenum aFBOTextureTarget); + + /** + * Some() between construction and Initialize, if initialization was + * requested. + */ + Maybe mNeedInitialization; + + /* + * Users of render target would draw in logical size, but it is + * actually drawn to a surface in physical size. GL surfaces have + * a limitation on their size, a smaller surface would be + * allocated for the render target if the caller requests in a + * size too big. + */ + gfx::IntSize mSize; // Logical size, the expected by callers. + gfx::IntSize mPhySize; // Physical size, the real size of the surface. + + /** + * There is temporary a cycle between the compositor and the render target, + * each having a strong ref to the other. The compositor's reference to + * the target is always cleared at the end of a frame. + */ + RefPtr mCompositor; + RefPtr mGL; + Maybe mClipRect; + gfx::IntPoint mClipSpaceOrigin; + GLResourceOwnership mGLResourceOwnership; + GLuint mTextureHandle; + GLuint mFBO; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* MOZILLA_GFX_SURFACEOGL_H */ diff --git a/gfx/layers/opengl/CompositorOGL.cpp b/gfx/layers/opengl/CompositorOGL.cpp new file mode 100644 index 0000000000..ae32b9dbe3 --- /dev/null +++ b/gfx/layers/opengl/CompositorOGL.cpp @@ -0,0 +1,2403 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "CompositorOGL.h" +#include // for size_t +#include // for uint32_t, uint8_t +#include // for free, malloc +#include "GLContextProvider.h" // for GLContextProvider +#include "GLContext.h" // for GLContext +#include "GLUploadHelpers.h" +#include "Layers.h" // for WriteSnapshotToDumpFile +#include "LayerScope.h" // for LayerScope +#include "gfxCrashReporterUtils.h" // for ScopedGfxFeatureReporter +#include "gfxEnv.h" // for gfxEnv +#include "gfxPlatform.h" // for gfxPlatform +#include "gfxRect.h" // for gfxRect +#include "gfxUtils.h" // for gfxUtils, etc +#include "mozilla/ArrayUtils.h" // for ArrayLength +#include "mozilla/Preferences.h" // for Preferences +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/StaticPrefs_nglayout.h" +#include "mozilla/gfx/BasePoint.h" // for BasePoint +#include "mozilla/gfx/Matrix.h" // for Matrix4x4, Matrix +#include "mozilla/gfx/Triangle.h" // for Triangle +#include "mozilla/gfx/gfxVars.h" // for gfxVars +#include "mozilla/layers/ImageDataSerializer.h" +#include "mozilla/layers/LayerManagerComposite.h" // for LayerComposite, etc +#include "mozilla/layers/NativeLayer.h" +#include "mozilla/layers/CompositingRenderTargetOGL.h" +#include "mozilla/layers/Effects.h" // for EffectChain, TexturedEffect, etc +#include "mozilla/layers/TextureHost.h" // for TextureSource, etc +#include "mozilla/layers/TextureHostOGL.h" // for TextureSourceOGL, etc +#include "mozilla/layers/PTextureParent.h" // for OtherPid() on PTextureParent +#ifdef XP_DARWIN +# include "mozilla/layers/TextureSync.h" // for TextureSync::etc. +#endif +#include "mozilla/mozalloc.h" // for operator delete, etc +#include "nsAppRunner.h" +#include "nsAString.h" +#include "nsClassHashtable.h" +#include "nsIConsoleService.h" // for nsIConsoleService, etc +#include "nsIWidget.h" // for nsIWidget +#include "nsLiteralString.h" // for NS_LITERAL_STRING +#include "nsMathUtils.h" // for NS_roundf +#include "nsRect.h" // for mozilla::gfx::IntRect +#include "nsServiceManagerUtils.h" // for do_GetService +#include "nsString.h" // for nsString, nsAutoCString, etc +#include "OGLShaderProgram.h" // for ShaderProgramOGL, etc +#include "ScopedGLHelpers.h" +#include "GLReadTexImageHelper.h" +#include "GLBlitTextureImageHelper.h" +#include "HeapCopyOfStackArray.h" +#include "GLBlitHelper.h" +#include "mozilla/gfx/Swizzle.h" +#ifdef MOZ_WAYLAND +# include "mozilla/widget/GtkCompositorWidget.h" +#endif +#if MOZ_WIDGET_ANDROID +# include "GLContextEGL.h" +# include "GLLibraryEGL.h" +# include "mozilla/java/GeckoSurfaceTextureWrappers.h" +# include "mozilla/layers/AndroidHardwareBuffer.h" +#endif + +#include "GeckoProfiler.h" + +namespace mozilla { + +using namespace gfx; + +namespace layers { + +using namespace mozilla::gl; + +static const GLuint kCoordinateAttributeIndex = 0; +static const GLuint kTexCoordinateAttributeIndex = 1; + +class AsyncReadbackBufferOGL final : public AsyncReadbackBuffer { + public: + AsyncReadbackBufferOGL(GLContext* aGL, const IntSize& aSize); + + bool MapAndCopyInto(DataSourceSurface* aSurface, + const IntSize& aReadSize) const override; + + void Bind() const { + mGL->fBindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, mBufferHandle); + mGL->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 1); + } + + protected: + virtual ~AsyncReadbackBufferOGL(); + + private: + GLContext* mGL; + GLuint mBufferHandle; +}; + +AsyncReadbackBufferOGL::AsyncReadbackBufferOGL(GLContext* aGL, + const IntSize& aSize) + : AsyncReadbackBuffer(aSize), mGL(aGL), mBufferHandle(0) { + size_t bufferByteCount = mSize.width * mSize.height * 4; + mGL->fGenBuffers(1, &mBufferHandle); + + ScopedPackState scopedPackState(mGL); + Bind(); + mGL->fBufferData(LOCAL_GL_PIXEL_PACK_BUFFER, bufferByteCount, nullptr, + LOCAL_GL_STREAM_READ); +} + +AsyncReadbackBufferOGL::~AsyncReadbackBufferOGL() { + if (mGL && mGL->MakeCurrent()) { + mGL->fDeleteBuffers(1, &mBufferHandle); + } +} + +bool AsyncReadbackBufferOGL::MapAndCopyInto(DataSourceSurface* aSurface, + const IntSize& aReadSize) const { + MOZ_RELEASE_ASSERT(aReadSize <= aSurface->GetSize()); + + if (!mGL || !mGL->MakeCurrent()) { + return false; + } + + ScopedPackState scopedPackState(mGL); + Bind(); + + const uint8_t* srcData = nullptr; + if (mGL->IsSupported(GLFeature::map_buffer_range)) { + srcData = static_cast(mGL->fMapBufferRange( + LOCAL_GL_PIXEL_PACK_BUFFER, 0, aReadSize.height * aReadSize.width * 4, + LOCAL_GL_MAP_READ_BIT)); + } else { + srcData = static_cast( + mGL->fMapBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, LOCAL_GL_READ_ONLY)); + } + + if (!srcData) { + return false; + } + + int32_t srcStride = mSize.width * 4; // Bind() sets an alignment of 1 + DataSourceSurface::ScopedMap map(aSurface, DataSourceSurface::WRITE); + uint8_t* destData = map.GetData(); + int32_t destStride = map.GetStride(); + SurfaceFormat destFormat = aSurface->GetFormat(); + for (int32_t destRow = 0; destRow < aReadSize.height; destRow++) { + // Turn srcData upside down during the copy. + int32_t srcRow = aReadSize.height - 1 - destRow; + const uint8_t* src = &srcData[srcRow * srcStride]; + uint8_t* dest = &destData[destRow * destStride]; + SwizzleData(src, srcStride, SurfaceFormat::R8G8B8A8, dest, destStride, + destFormat, IntSize(aReadSize.width, 1)); + } + + mGL->fUnmapBuffer(LOCAL_GL_PIXEL_PACK_BUFFER); + + return true; +} + +PerUnitTexturePoolOGL::PerUnitTexturePoolOGL(gl::GLContext* aGL) + : mTextureTarget(0), // zero is never a valid texture target + mGL(aGL) {} + +PerUnitTexturePoolOGL::~PerUnitTexturePoolOGL() { DestroyTextures(); } + +static void BindMaskForProgram(ShaderProgramOGL* aProgram, + TextureSourceOGL* aSourceMask, GLenum aTexUnit, + const gfx::Matrix4x4& aTransform) { + MOZ_ASSERT(LOCAL_GL_TEXTURE0 <= aTexUnit && aTexUnit <= LOCAL_GL_TEXTURE31); + aSourceMask->BindTexture(aTexUnit, gfx::SamplingFilter::LINEAR); + aProgram->SetMaskTextureUnit(aTexUnit - LOCAL_GL_TEXTURE0); + aProgram->SetMaskLayerTransform(aTransform); +} + +void CompositorOGL::BindBackdrop(ShaderProgramOGL* aProgram, GLuint aBackdrop, + GLenum aTexUnit) { + MOZ_ASSERT(aBackdrop); + + mGLContext->fActiveTexture(aTexUnit); + mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, aBackdrop); + mGLContext->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER, + LOCAL_GL_LINEAR); + mGLContext->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER, + LOCAL_GL_LINEAR); + aProgram->SetBackdropTextureUnit(aTexUnit - LOCAL_GL_TEXTURE0); +} + +CompositorOGL::CompositorOGL(CompositorBridgeParent* aParent, + widget::CompositorWidget* aWidget, + int aSurfaceWidth, int aSurfaceHeight, + bool aUseExternalSurfaceSize) + : Compositor(aWidget, aParent), + mWidgetSize(-1, -1), + mSurfaceSize(aSurfaceWidth, aSurfaceHeight), + mFBOTextureTarget(0), + mWindowRenderTarget(nullptr), + mQuadVBO(0), + mTriangleVBO(0), + mPreviousFrameDoneSync(nullptr), + mThisFrameDoneSync(nullptr), + mHasBGRA(0), + mUseExternalSurfaceSize(aUseExternalSurfaceSize), + mFrameInProgress(false), + mDestroyed(false), + mViewportSize(0, 0), + mCurrentProgram(nullptr) { + if (aWidget->GetNativeLayerRoot()) { + // We can only render into native layers, our GLContext won't have a usable + // default framebuffer. + mCanRenderToDefaultFramebuffer = false; + } +#ifdef XP_DARWIN + TextureSync::RegisterTextureSourceProvider(this); +#endif + MOZ_COUNT_CTOR(CompositorOGL); +} + +CompositorOGL::~CompositorOGL() { +#ifdef XP_DARWIN + TextureSync::UnregisterTextureSourceProvider(this); +#endif + MOZ_COUNT_DTOR(CompositorOGL); +} + +already_AddRefed CompositorOGL::CreateContext() { + RefPtr context; + + // Used by mock widget to create an offscreen context + nsIWidget* widget = mWidget->RealWidget(); + void* widgetOpenGLContext = + widget ? widget->GetNativeData(NS_NATIVE_OPENGL_CONTEXT) : nullptr; + if (widgetOpenGLContext) { + GLContext* alreadyRefed = reinterpret_cast(widgetOpenGLContext); + return already_AddRefed(alreadyRefed); + } + +#ifdef XP_WIN + if (gfxEnv::LayersPreferEGL()) { + printf_stderr("Trying GL layers...\n"); + context = gl::GLContextProviderEGL::CreateForCompositorWidget( + mWidget, /* aWebRender */ false, /* aForceAccelerated */ false); + } +#endif + + // Allow to create offscreen GL context for main Layer Manager + if (!context && gfxEnv::LayersPreferOffscreen()) { + nsCString discardFailureId; + context = GLContextProvider::CreateHeadless( + {CreateContextFlags::REQUIRE_COMPAT_PROFILE}, &discardFailureId); + if (!context->CreateOffscreenDefaultFb(mSurfaceSize)) { + context = nullptr; + } + } + + if (!context) { + context = gl::GLContextProvider::CreateForCompositorWidget( + mWidget, + /* aWebRender */ false, + gfxVars::RequiresAcceleratedGLContextForCompositorOGL()); + } + + if (!context) { + NS_WARNING("Failed to create CompositorOGL context"); + } + + return context.forget(); +} + +void CompositorOGL::Destroy() { + Compositor::Destroy(); + + if (mTexturePool) { + mTexturePool->Clear(); + mTexturePool = nullptr; + } + +#ifdef XP_DARWIN + mMaybeUnlockBeforeNextComposition.Clear(); +#endif + + if (!mDestroyed) { + mDestroyed = true; + CleanupResources(); + } +} + +void CompositorOGL::CleanupResources() { + if (!mGLContext) return; + + if (mSurfacePoolHandle) { + mSurfacePoolHandle->Pool()->DestroyGLResourcesForContext(mGLContext); + mSurfacePoolHandle = nullptr; + } + + RefPtr ctx = mGLContext->GetSharedContext(); + if (!ctx) { + ctx = mGLContext; + } + + if (!ctx->MakeCurrent()) { + // Leak resources! + mQuadVBO = 0; + mTriangleVBO = 0; + mPreviousFrameDoneSync = nullptr; + mThisFrameDoneSync = nullptr; + mGLContext = nullptr; + mPrograms.clear(); + mNativeLayersReferenceRT = nullptr; + mFullWindowRenderTarget = nullptr; + return; + } + + for (std::map::iterator iter = + mPrograms.begin(); + iter != mPrograms.end(); iter++) { + delete iter->second; + } + mPrograms.clear(); + mNativeLayersReferenceRT = nullptr; + mFullWindowRenderTarget = nullptr; + +#ifdef MOZ_WIDGET_GTK + // TextureSources might hold RefPtr. + // All of them needs to be released to destroy GLContext. + // GLContextGLX has to be destroyed before related gtk window is destroyed. + for (auto textureSource : mRegisteredTextureSources) { + textureSource->DeallocateDeviceData(); + } + mRegisteredTextureSources.clear(); +#endif + + ctx->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, 0); + + if (mQuadVBO) { + ctx->fDeleteBuffers(1, &mQuadVBO); + mQuadVBO = 0; + } + + if (mTriangleVBO) { + ctx->fDeleteBuffers(1, &mTriangleVBO); + mTriangleVBO = 0; + } + + mGLContext->MakeCurrent(); + + if (mPreviousFrameDoneSync) { + mGLContext->fDeleteSync(mPreviousFrameDoneSync); + mPreviousFrameDoneSync = nullptr; + } + + if (mThisFrameDoneSync) { + mGLContext->fDeleteSync(mThisFrameDoneSync); + mThisFrameDoneSync = nullptr; + } + + mBlitTextureImageHelper = nullptr; + + // On the main thread the Widget will be destroyed soon and calling + // MakeCurrent after that could cause a crash (at least with GLX, see bug + // 1059793), unless context is marked as destroyed. There may be some textures + // still alive that will try to call MakeCurrent on the context so let's make + // sure it is marked destroyed now. + mGLContext->MarkDestroyed(); + + mGLContext = nullptr; +} + +bool CompositorOGL::Initialize(nsCString* const out_failureReason) { + ScopedGfxFeatureReporter reporter("GL Layers"); + + // Do not allow double initialization + MOZ_ASSERT(mGLContext == nullptr, "Don't reinitialize CompositorOGL"); + + mGLContext = CreateContext(); + +#ifdef MOZ_WIDGET_ANDROID + if (!mGLContext) { + *out_failureReason = "FEATURE_FAILURE_OPENGL_NO_ANDROID_CONTEXT"; + MOZ_CRASH("We need a context on Android"); + } +#endif + + if (!mGLContext) { + *out_failureReason = "FEATURE_FAILURE_OPENGL_CREATE_CONTEXT"; + return false; + } + + MakeCurrent(); + + mHasBGRA = mGLContext->IsExtensionSupported( + gl::GLContext::EXT_texture_format_BGRA8888) || + mGLContext->IsExtensionSupported(gl::GLContext::EXT_bgra); + + mGLContext->fBlendFuncSeparate(LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA, + LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA); + mGLContext->fEnable(LOCAL_GL_BLEND); + + // initialise a common shader to check that we can actually compile a shader + RefPtr effect = + new EffectSolidColor(DeviceColor(0, 0, 0, 0)); + ShaderConfigOGL config = GetShaderConfigFor(effect); + if (!GetShaderProgramFor(config)) { + *out_failureReason = "FEATURE_FAILURE_OPENGL_COMPILE_SHADER"; + return false; + } + + if (mGLContext->WorkAroundDriverBugs()) { + /** + * We'll test the ability here to bind NPOT textures to a framebuffer, if + * this fails we'll try ARB_texture_rectangle. + */ + + GLenum textureTargets[] = {LOCAL_GL_TEXTURE_2D, LOCAL_GL_NONE}; + + if (!mGLContext->IsGLES()) { + // No TEXTURE_RECTANGLE_ARB available on ES2 + textureTargets[1] = LOCAL_GL_TEXTURE_RECTANGLE_ARB; + } + + mFBOTextureTarget = LOCAL_GL_NONE; + + GLuint testFBO = 0; + mGLContext->fGenFramebuffers(1, &testFBO); + GLuint testTexture = 0; + + for (uint32_t i = 0; i < ArrayLength(textureTargets); i++) { + GLenum target = textureTargets[i]; + if (!target) continue; + + mGLContext->fGenTextures(1, &testTexture); + mGLContext->fBindTexture(target, testTexture); + mGLContext->fTexParameteri(target, LOCAL_GL_TEXTURE_MIN_FILTER, + LOCAL_GL_NEAREST); + mGLContext->fTexParameteri(target, LOCAL_GL_TEXTURE_MAG_FILTER, + LOCAL_GL_NEAREST); + mGLContext->fTexImage2D( + target, 0, LOCAL_GL_RGBA, 5, 3, /* sufficiently NPOT */ + 0, LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, nullptr); + + // unbind this texture, in preparation for binding it to the FBO + mGLContext->fBindTexture(target, 0); + + mGLContext->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, testFBO); + mGLContext->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, + LOCAL_GL_COLOR_ATTACHMENT0, target, + testTexture, 0); + + if (mGLContext->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER) == + LOCAL_GL_FRAMEBUFFER_COMPLETE) { + mFBOTextureTarget = target; + mGLContext->fDeleteTextures(1, &testTexture); + break; + } + + mGLContext->fDeleteTextures(1, &testTexture); + } + + if (testFBO) { + mGLContext->fDeleteFramebuffers(1, &testFBO); + } + + if (mFBOTextureTarget == LOCAL_GL_NONE) { + /* Unable to find a texture target that works with FBOs and NPOT textures + */ + *out_failureReason = "FEATURE_FAILURE_OPENGL_NO_TEXTURE_TARGET"; + return false; + } + } else { + // not trying to work around driver bugs, so TEXTURE_2D should just work + mFBOTextureTarget = LOCAL_GL_TEXTURE_2D; + } + + // back to default framebuffer, to avoid confusion + mGLContext->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, 0); + + if (mFBOTextureTarget == LOCAL_GL_TEXTURE_RECTANGLE_ARB) { + /* If we're using TEXTURE_RECTANGLE, then we must have the ARB + * extension -- the EXT variant does not provide support for + * texture rectangle access inside GLSL (sampler2DRect, + * texture2DRect). + */ + if (!mGLContext->IsExtensionSupported( + gl::GLContext::ARB_texture_rectangle)) { + *out_failureReason = "FEATURE_FAILURE_OPENGL_ARB_EXT"; + return false; + } + } + + // Create a VBO for triangle vertices. + mGLContext->fGenBuffers(1, &mTriangleVBO); + + /* Create a simple quad VBO */ + mGLContext->fGenBuffers(1, &mQuadVBO); + + // 4 quads, with the number of the quad (vertexID) encoded in w. + GLfloat vertices[] = { + 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, + + 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, + 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, + + 0.0f, 0.0f, 0.0f, 2.0f, 1.0f, 0.0f, 0.0f, 2.0f, 0.0f, 1.0f, 0.0f, 2.0f, + 1.0f, 0.0f, 0.0f, 2.0f, 0.0f, 1.0f, 0.0f, 2.0f, 1.0f, 1.0f, 0.0f, 2.0f, + + 0.0f, 0.0f, 0.0f, 3.0f, 1.0f, 0.0f, 0.0f, 3.0f, 0.0f, 1.0f, 0.0f, 3.0f, + 1.0f, 0.0f, 0.0f, 3.0f, 0.0f, 1.0f, 0.0f, 3.0f, 1.0f, 1.0f, 0.0f, 3.0f, + }; + HeapCopyOfStackArray verticesOnHeap(vertices); + + mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mQuadVBO); + mGLContext->fBufferData(LOCAL_GL_ARRAY_BUFFER, verticesOnHeap.ByteLength(), + verticesOnHeap.Data(), LOCAL_GL_STATIC_DRAW); + mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0); + + nsCOMPtr console( + do_GetService(NS_CONSOLESERVICE_CONTRACTID)); + + if (console) { + nsString msg; + msg += nsLiteralString( + u"OpenGL compositor Initialized Succesfully.\nVersion: "); + msg += NS_ConvertUTF8toUTF16(nsDependentCString( + (const char*)mGLContext->fGetString(LOCAL_GL_VERSION))); + msg += u"\nVendor: "_ns; + msg += NS_ConvertUTF8toUTF16(nsDependentCString( + (const char*)mGLContext->fGetString(LOCAL_GL_VENDOR))); + msg += u"\nRenderer: "_ns; + msg += NS_ConvertUTF8toUTF16(nsDependentCString( + (const char*)mGLContext->fGetString(LOCAL_GL_RENDERER))); + msg += u"\nFBO Texture Target: "_ns; + if (mFBOTextureTarget == LOCAL_GL_TEXTURE_2D) + msg += u"TEXTURE_2D"_ns; + else + msg += u"TEXTURE_RECTANGLE"_ns; + console->LogStringMessage(msg.get()); + } + + reporter.SetSuccessful(); + + return true; +} + +/* + * Returns a size that is equal to, or larger than and closest to, + * aSize where both width and height are powers of two. + * If the OpenGL setup is capable of using non-POT textures, + * then it will just return aSize. + */ +static IntSize CalculatePOTSize(const IntSize& aSize, GLContext* gl) { + if (CanUploadNonPowerOfTwo(gl)) return aSize; + + return IntSize(RoundUpPow2(aSize.width), RoundUpPow2(aSize.height)); +} + +gfx::Rect CompositorOGL::GetTextureCoordinates(gfx::Rect textureRect, + TextureSource* aTexture) { + // If the OpenGL setup does not support non-power-of-two textures then the + // texture's width and height will have been increased to the next + // power-of-two (unless already a power of two). In that case we must scale + // the texture coordinates to account for that. + if (!CanUploadNonPowerOfTwo(mGLContext)) { + const IntSize& textureSize = aTexture->GetSize(); + const IntSize potSize = CalculatePOTSize(textureSize, mGLContext); + if (potSize != textureSize) { + const float xScale = (float)textureSize.width / (float)potSize.width; + const float yScale = (float)textureSize.height / (float)potSize.height; + textureRect.Scale(xScale, yScale); + } + } + + return textureRect; +} + +void CompositorOGL::PrepareViewport(CompositingRenderTargetOGL* aRenderTarget) { + MOZ_ASSERT(aRenderTarget); + // Logical surface size. + const gfx::IntSize& size = aRenderTarget->GetSize(); + // Physical surface size. + const gfx::IntSize& phySize = aRenderTarget->GetPhysicalSize(); + + // Set the viewport correctly. + mGLContext->fViewport(mSurfaceOrigin.x, mSurfaceOrigin.y, phySize.width, + phySize.height); + + mViewportSize = size; + + if (!aRenderTarget->HasComplexProjection()) { + // We flip the view matrix around so that everything is right-side up; we're + // drawing directly into the window's back buffer, so this keeps things + // looking correct. + // XXX: We keep track of whether the window size changed, so we could skip + // this update if it hadn't changed since the last call. + + // Matrix to transform (0, 0, aWidth, aHeight) to viewport space (-1.0, 1.0, + // 2, 2) and flip the contents. + Matrix viewMatrix; + viewMatrix.PreTranslate(-1.0, 1.0); + viewMatrix.PreScale(2.0f / float(size.width), 2.0f / float(size.height)); + viewMatrix.PreScale(1.0f, -1.0f); + + MOZ_ASSERT(mCurrentRenderTarget, "No destination"); + + Matrix4x4 matrix3d = Matrix4x4::From2D(viewMatrix); + matrix3d._33 = 0.0f; + mProjMatrix = matrix3d; + mGLContext->fDepthRange(0.0f, 1.0f); + } else { + bool depthEnable; + float zNear, zFar; + aRenderTarget->GetProjection(mProjMatrix, depthEnable, zNear, zFar); + mGLContext->fDepthRange(zNear, zFar); + } +} + +already_AddRefed CompositorOGL::CreateRenderTarget( + const IntRect& aRect, SurfaceInitMode aInit) { + MOZ_ASSERT(!aRect.IsZeroArea(), + "Trying to create a render target of invalid size"); + + if (aRect.IsZeroArea()) { + return nullptr; + } + + if (!gl()) { + // CompositingRenderTargetOGL does not work without a gl context. + return nullptr; + } + + GLuint tex = 0; + GLuint fbo = 0; + IntRect rect = aRect; + IntSize fboSize; + CreateFBOWithTexture(rect, false, 0, &fbo, &tex, &fboSize); + return CompositingRenderTargetOGL::CreateForNewFBOAndTakeOwnership( + this, tex, fbo, aRect, aRect.TopLeft(), aRect.Size(), mFBOTextureTarget, + aInit); +} + +already_AddRefed +CompositorOGL::CreateRenderTargetFromSource( + const IntRect& aRect, const CompositingRenderTarget* aSource, + const IntPoint& aSourcePoint) { + MOZ_ASSERT(!aRect.IsZeroArea(), + "Trying to create a render target of invalid size"); + MOZ_RELEASE_ASSERT(aSource, "Source needs to be non-null"); + + if (aRect.IsZeroArea()) { + return nullptr; + } + + if (!gl()) { + return nullptr; + } + + GLuint tex = 0; + GLuint fbo = 0; + const CompositingRenderTargetOGL* sourceSurface = + static_cast(aSource); + IntRect sourceRect(aSourcePoint, aRect.Size()); + CreateFBOWithTexture(sourceRect, true, sourceSurface->GetFBO(), &fbo, &tex); + + return CompositingRenderTargetOGL::CreateForNewFBOAndTakeOwnership( + this, tex, fbo, aRect, aRect.TopLeft(), sourceRect.Size(), + mFBOTextureTarget, INIT_MODE_NONE); +} + +void CompositorOGL::SetRenderTarget(CompositingRenderTarget* aSurface) { + MOZ_ASSERT(aSurface); + CompositingRenderTargetOGL* surface = + static_cast(aSurface); + if (mCurrentRenderTarget != surface) { + mCurrentRenderTarget = surface; + surface->BindRenderTarget(); + } + + PrepareViewport(mCurrentRenderTarget); +} + +already_AddRefed +CompositorOGL::GetCurrentRenderTarget() const { + return do_AddRef(mCurrentRenderTarget); +} + +already_AddRefed CompositorOGL::GetWindowRenderTarget() + const { + return do_AddRef(mWindowRenderTarget); +} + +already_AddRefed CompositorOGL::CreateAsyncReadbackBuffer( + const IntSize& aSize) { + return MakeAndAddRef(mGLContext, aSize); +} + +bool CompositorOGL::ReadbackRenderTarget(CompositingRenderTarget* aSource, + AsyncReadbackBuffer* aDest) { + IntSize size = aSource->GetSize(); + MOZ_RELEASE_ASSERT(aDest->GetSize() == size); + + RefPtr previousTarget = GetCurrentRenderTarget(); + if (previousTarget != aSource) { + SetRenderTarget(aSource); + } + + ScopedPackState scopedPackState(mGLContext); + static_cast(aDest)->Bind(); + + mGLContext->fReadPixels(0, 0, size.width, size.height, LOCAL_GL_RGBA, + LOCAL_GL_UNSIGNED_BYTE, 0); + + if (previousTarget != aSource) { + SetRenderTarget(previousTarget); + } + return true; +} + +bool CompositorOGL::BlitRenderTarget(CompositingRenderTarget* aSource, + const gfx::IntSize& aSourceSize, + const gfx::IntSize& aDestSize) { + if (!mGLContext->IsSupported(GLFeature::framebuffer_blit)) { + return false; + } + CompositingRenderTargetOGL* source = + static_cast(aSource); + GLuint srcFBO = source->GetFBO(); + GLuint destFBO = mCurrentRenderTarget->GetFBO(); + mGLContext->BlitHelper()->BlitFramebufferToFramebuffer( + srcFBO, destFBO, IntRect(IntPoint(), aSourceSize), + IntRect(IntPoint(), aDestSize), LOCAL_GL_LINEAR); + return true; +} + +static GLenum GetFrameBufferInternalFormat( + GLContext* gl, GLuint aFrameBuffer, + mozilla::widget::CompositorWidget* aWidget) { + if (aFrameBuffer == 0) { // default framebuffer + return aWidget->GetGLFrameBufferFormat(); + } + return LOCAL_GL_RGBA; +} + +void CompositorOGL::ClearRect(const gfx::Rect& aRect) { + // Map aRect to OGL coordinates, origin:bottom-left + GLint y = mViewportSize.height - aRect.YMost(); + + ScopedGLState scopedScissorTestState(mGLContext, LOCAL_GL_SCISSOR_TEST, true); + ScopedScissorRect autoScissorRect(mGLContext, aRect.X(), y, aRect.Width(), + aRect.Height()); + mGLContext->fClearColor(0.0, 0.0, 0.0, 0.0); + mGLContext->fClear(LOCAL_GL_COLOR_BUFFER_BIT | LOCAL_GL_DEPTH_BUFFER_BIT); +} + +already_AddRefed +CompositorOGL::RenderTargetForNativeLayer(NativeLayer* aNativeLayer, + const IntRegion& aInvalidRegion) { + if (aInvalidRegion.IsEmpty()) { + return nullptr; + } + + aNativeLayer->SetSurfaceIsFlipped(true); + + IntRect layerRect = aNativeLayer->GetRect(); + IntRegion invalidRelativeToLayer = + aInvalidRegion.MovedBy(-layerRect.TopLeft()); + Maybe fbo = aNativeLayer->NextSurfaceAsFramebuffer( + gfx::IntRect({}, aNativeLayer->GetSize()), invalidRelativeToLayer, false); + if (!fbo) { + return nullptr; + } + + RefPtr rt = + CompositingRenderTargetOGL::CreateForExternallyOwnedFBO( + this, *fbo, layerRect, IntPoint()); + + // Clip the render target to the invalid rect. This conserves memory bandwidth + // and power. + IntRect invalidRect = aInvalidRegion.GetBounds(); + rt->SetClipRect(invalidRect == layerRect ? Nothing() : Some(invalidRect)); + + return rt.forget(); +} + +Maybe CompositorOGL::BeginFrameForWindow( + const nsIntRegion& aInvalidRegion, const Maybe& aClipRect, + const IntRect& aRenderBounds, const nsIntRegion& aOpaqueRegion) { + MOZ_RELEASE_ASSERT(!mTarget, "mTarget not cleared properly"); + return BeginFrame(aInvalidRegion, aClipRect, aRenderBounds, aOpaqueRegion); +} + +Maybe CompositorOGL::BeginFrameForTarget( + const nsIntRegion& aInvalidRegion, const Maybe& aClipRect, + const IntRect& aRenderBounds, const nsIntRegion& aOpaqueRegion, + DrawTarget* aTarget, const IntRect& aTargetBounds) { + MOZ_RELEASE_ASSERT(!mTarget, "mTarget not cleared properly"); + mTarget = aTarget; // Will be cleared in EndFrame(). + mTargetBounds = aTargetBounds; + Maybe result = + BeginFrame(aInvalidRegion, aClipRect, aRenderBounds, aOpaqueRegion); + if (!result) { + // Composition has been aborted. Reset mTarget. + mTarget = nullptr; + } + return result; +} + +void CompositorOGL::BeginFrameForNativeLayers() { + MakeCurrent(); + mPixelsPerFrame = 0; + mPixelsFilled = 0; + + // Default blend function implements "OVER" + mGLContext->fBlendFuncSeparate(LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA, + LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA); + mGLContext->fEnable(LOCAL_GL_BLEND); + + mFrameInProgress = true; + mShouldInvalidateWindow = NeedToRecreateFullWindowRenderTarget(); + + // Make a 1x1 dummy render target so that GetCurrentRenderTarget() returns + // something non-null even outside of calls to + // Begin/EndRenderingToNativeLayer. + if (!mNativeLayersReferenceRT) { + mNativeLayersReferenceRT = + CreateRenderTarget(IntRect(0, 0, 1, 1), INIT_MODE_CLEAR); + } + SetRenderTarget(mNativeLayersReferenceRT); + mWindowRenderTarget = mFullWindowRenderTarget; +} + +Maybe CompositorOGL::BeginRenderingToNativeLayer( + const nsIntRegion& aInvalidRegion, const Maybe& aClipRect, + const nsIntRegion& aOpaqueRegion, NativeLayer* aNativeLayer) { + MOZ_RELEASE_ASSERT(aNativeLayer); + MOZ_RELEASE_ASSERT(mCurrentRenderTarget == mNativeLayersReferenceRT, + "Please restore the current render target to the one that " + "was in place after the call to BeginFrameForNativeLayers " + "before calling BeginRenderingToNativeLayer."); + + IntRect rect = aNativeLayer->GetRect(); + IntRegion layerInvalid; + if (mShouldInvalidateWindow) { + layerInvalid = rect; + } else { + layerInvalid.And(aInvalidRegion, rect); + } + + RefPtr rt = + RenderTargetForNativeLayer(aNativeLayer, layerInvalid); + if (!rt) { + return Nothing(); + } + SetRenderTarget(rt); + mCurrentNativeLayer = aNativeLayer; + + mGLContext->fClearColor(mClearColor.r, mClearColor.g, mClearColor.b, + mClearColor.a); + if (const Maybe& rtClip = mCurrentRenderTarget->GetClipRect()) { + // We need to apply a scissor rect during the clear. And since clears with + // scissor rects are usually treated differently by the GPU than regular + // clears, let's try to clear as little as possible in order to conserve + // memory bandwidth. + IntRegion clearRegion; + clearRegion.Sub(*rtClip, aOpaqueRegion); + if (!clearRegion.IsEmpty()) { + IntRect clearRect = + clearRegion.GetBounds() - mCurrentRenderTarget->GetOrigin(); + ScopedGLState scopedScissorTestState(mGLContext, LOCAL_GL_SCISSOR_TEST, + true); + ScopedScissorRect autoScissorRect(mGLContext, clearRect.x, + FlipY(clearRect.YMost()), + clearRect.Width(), clearRect.Height()); + mGLContext->fClear(LOCAL_GL_COLOR_BUFFER_BIT | LOCAL_GL_DEPTH_BUFFER_BIT); + } + mPixelsPerFrame += rtClip->Area(); + } else { + mGLContext->fClear(LOCAL_GL_COLOR_BUFFER_BIT | LOCAL_GL_DEPTH_BUFFER_BIT); + mPixelsPerFrame += rect.Area(); + } + + return Some(rect); +} + +void CompositorOGL::NormalDrawingDone() { + // Now is a good time to update mFullWindowRenderTarget. + if (!mCurrentNativeLayer) { + return; + } + + if (!mGLContext->IsSupported(GLFeature::framebuffer_blit)) { + return; + } + + if (!ShouldRecordFrames()) { + // If we are no longer recording a profile, we can drop the render target if + // it exists. + mWindowRenderTarget = nullptr; + mFullWindowRenderTarget = nullptr; + return; + } + + if (NeedToRecreateFullWindowRenderTarget()) { + // We have either (1) just started recording and not yet allocated a + // buffer or (2) are already recording and have resized the window. In + // either case, we need a new render target. + IntRect windowRect(IntPoint(0, 0), + mWidget->GetClientSize().ToUnknownSize()); + RefPtr rt = + CreateRenderTarget(windowRect, INIT_MODE_NONE); + mFullWindowRenderTarget = + static_cast(rt.get()); + mWindowRenderTarget = mFullWindowRenderTarget; + + // Initialize the render target by binding it. + RefPtr previousTarget = mCurrentRenderTarget; + SetRenderTarget(mFullWindowRenderTarget); + SetRenderTarget(previousTarget); + } + + // Copy the appropriate rectangle from the layer to mFullWindowRenderTarget. + RefPtr layerRT = mCurrentRenderTarget; + IntRect copyRect = layerRT->GetClipRect().valueOr(layerRT->GetRect()); + IntRect sourceRect = copyRect - layerRT->GetOrigin(); + sourceRect.y = layerRT->GetSize().height - sourceRect.YMost(); + IntRect destRect = copyRect; + destRect.y = mFullWindowRenderTarget->GetSize().height - destRect.YMost(); + GLuint sourceFBO = layerRT->GetFBO(); + GLuint destFBO = mFullWindowRenderTarget->GetFBO(); + mGLContext->BlitHelper()->BlitFramebufferToFramebuffer( + sourceFBO, destFBO, sourceRect, destRect, LOCAL_GL_NEAREST); +} + +void CompositorOGL::EndRenderingToNativeLayer() { + MOZ_RELEASE_ASSERT(mCurrentNativeLayer, + "EndRenderingToNativeLayer not paired with a call to " + "BeginRenderingToNativeLayer?"); + + if (StaticPrefs::nglayout_debug_widget_update_flashing()) { + float r = float(rand()) / float(RAND_MAX); + float g = float(rand()) / float(RAND_MAX); + float b = float(rand()) / float(RAND_MAX); + EffectChain effectChain; + effectChain.mPrimaryEffect = + new EffectSolidColor(DeviceColor(r, g, b, 0.2f)); + // If we're clipping the render target to the invalid rect, then the + // current render target is still clipped, so just fill the bounds. + IntRect rect = mCurrentRenderTarget->GetRect(); + DrawQuad(Rect(rect), rect - rect.TopLeft(), effectChain, 1.0, Matrix4x4(), + Rect(rect)); + } + + mCurrentRenderTarget->SetClipRect(Nothing()); + SetRenderTarget(mNativeLayersReferenceRT); + + mCurrentNativeLayer->NotifySurfaceReady(); + mCurrentNativeLayer = nullptr; +} + +Maybe CompositorOGL::BeginFrame(const nsIntRegion& aInvalidRegion, + const Maybe& aClipRect, + const IntRect& aRenderBounds, + const nsIntRegion& aOpaqueRegion) { + AUTO_PROFILER_LABEL("CompositorOGL::BeginFrame", GRAPHICS); + + MOZ_ASSERT(!mFrameInProgress, + "frame still in progress (should have called EndFrame"); + + IntRect rect; + if (mUseExternalSurfaceSize) { + rect = IntRect(IntPoint(), mSurfaceSize); + } else { + rect = aRenderBounds; + } + + // We can't draw anything to something with no area + // so just return + if (rect.IsZeroArea()) { + return Nothing(); + } + + // If the widget size changed, we have to force a MakeCurrent + // to make sure that GL sees the updated widget size. + if (mWidgetSize.ToUnknownSize() != rect.Size()) { + MakeCurrent(ForceMakeCurrent); + + mWidgetSize = LayoutDeviceIntSize::FromUnknownSize(rect.Size()); +#ifdef MOZ_WAYLAND + if (mWidget && mWidget->AsX11()) { + mWidget->AsX11()->SetEGLNativeWindowSize(mWidgetSize); + } +#endif + } else { + MakeCurrent(); + } + + mPixelsPerFrame = rect.Area(); + mPixelsFilled = 0; + +#ifdef MOZ_WIDGET_ANDROID + java::GeckoSurfaceTexture::DestroyUnused((int64_t)mGLContext.get()); + mGLContext->MakeCurrent(); // DestroyUnused can change the current context! +#endif + + // Default blend function implements "OVER" + mGLContext->fBlendFuncSeparate(LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA, + LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA); + mGLContext->fEnable(LOCAL_GL_BLEND); + + RefPtr rt; + if (mCanRenderToDefaultFramebuffer) { + rt = CompositingRenderTargetOGL::CreateForWindow(this, rect.Size()); + } else if (mTarget) { + rt = CreateRenderTarget(rect, INIT_MODE_CLEAR); + } else { + MOZ_CRASH("Unexpected call"); + } + + if (!rt) { + return Nothing(); + } + + // We're about to actually draw a frame. + mFrameInProgress = true; + + SetRenderTarget(rt); + mWindowRenderTarget = mCurrentRenderTarget; + +#if defined(MOZ_WIDGET_ANDROID) + if ((mSurfaceOrigin.x > 0) || (mSurfaceOrigin.y > 0)) { + mGLContext->fClearColor( + StaticPrefs::gfx_compositor_override_clear_color_r(), + StaticPrefs::gfx_compositor_override_clear_color_g(), + StaticPrefs::gfx_compositor_override_clear_color_b(), + StaticPrefs::gfx_compositor_override_clear_color_a()); + } else { + mGLContext->fClearColor(mClearColor.r, mClearColor.g, mClearColor.b, + mClearColor.a); + } +#else + mGLContext->fClearColor(mClearColor.r, mClearColor.g, mClearColor.b, + mClearColor.a); +#endif // defined(MOZ_WIDGET_ANDROID) + mGLContext->fClear(LOCAL_GL_COLOR_BUFFER_BIT | LOCAL_GL_DEPTH_BUFFER_BIT); + + for (auto iter = aInvalidRegion.RectIter(); !iter.Done(); iter.Next()) { + const IntRect& r = iter.Get(); + mCurrentFrameInvalidRegion.OrWith( + IntRect(r.X(), FlipY(r.YMost()), r.Width(), r.Height())); + } + + return Some(rect); +} + +void CompositorOGL::CreateFBOWithTexture(const gfx::IntRect& aRect, + bool aCopyFromSource, + GLuint aSourceFrameBuffer, + GLuint* aFBO, GLuint* aTexture, + gfx::IntSize* aAllocSize) { + *aTexture = + CreateTexture(aRect, aCopyFromSource, aSourceFrameBuffer, aAllocSize); + mGLContext->fGenFramebuffers(1, aFBO); +} + +// Should be called after calls to fReadPixels or fCopyTexImage2D, and other +// GL read calls. +static void WorkAroundAppleIntelHD3000GraphicsGLDriverBug(GLContext* aGL) { +#ifdef XP_MACOSX + if (aGL->WorkAroundDriverBugs() && + aGL->Renderer() == GLRenderer::IntelHD3000) { + // Work around a bug in the Apple Intel HD Graphics 3000 driver (bug + // 1586627, filed with Apple as FB7379358). This bug has been found present + // on 10.9.3 and on 10.13.6, so it likely affects all shipped versions of + // this driver. (macOS 10.14 does not support this GPU.) + // The bug manifests as follows: Reading from a framebuffer puts that + // framebuffer into a state such that deleting that framebuffer can break + // other framebuffers in certain cases. More specifically, if you have two + // framebuffers A and B, the following sequence of events breaks subsequent + // drawing to B: + // 1. A becomes "most recently read-from framebuffer". + // 2. B is drawn to. + // 3. A is deleted, and other GL state (such as GL_SCISSOR enabled state) + // is touched. + // 4. B is drawn to again. + // Now all draws to framebuffer B, including the draw from step 4, will + // render at the wrong position and upside down. + // + // When AfterGLReadCall() is called, the currently bound framebuffer is the + // framebuffer that has been read from most recently. So in the presence of + // this bug, deleting this framebuffer has now become dangerous. We work + // around the bug by creating a new short-lived framebuffer, making that new + // framebuffer the most recently read-from framebuffer (using + // glCopyTexImage2D), and then deleting it under controlled circumstances. + // This deletion is not affected by the bug because our deletion call is not + // interleaved with draw calls to another framebuffer and a touching of the + // GL scissor enabled state. + + ScopedTexture texForReading(aGL); + { + // Initialize a 1x1 texture. + ScopedBindTexture autoBindTexForReading(aGL, texForReading); + aGL->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGBA, 1, 1, 0, + LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, nullptr); + aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER, + LOCAL_GL_LINEAR); + aGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER, + LOCAL_GL_LINEAR); + } + // Make a framebuffer around the texture. + ScopedFramebufferForTexture autoFBForReading(aGL, texForReading); + if (autoFBForReading.IsComplete()) { + // "Read" from the framebuffer, by initializing a new texture using + // glCopyTexImage2D. This flips the bad bit on autoFBForReading.FB(). + ScopedBindFramebuffer autoFB(aGL, autoFBForReading.FB()); + ScopedTexture texReadingDest(aGL); + ScopedBindTexture autoBindTexReadingDest(aGL, texReadingDest); + aGL->fCopyTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGBA, 0, 0, 1, 1, + 0); + } + // When autoFBForReading goes out of scope, the "poisoned" framebuffer is + // deleted, and the bad state seems to go away along with it. + } +#endif +} + +GLuint CompositorOGL::CreateTexture(const IntRect& aRect, bool aCopyFromSource, + GLuint aSourceFrameBuffer, + IntSize* aAllocSize) { + // we're about to create a framebuffer backed by textures to use as an + // intermediate surface. What to do if its size (as given by aRect) would + // exceed the maximum texture size supported by the GL? The present code + // chooses the compromise of just clamping the framebuffer's size to the max + // supported size. This gives us a lower resolution rendering of the + // intermediate surface (children layers). See bug 827170 for a discussion. + IntRect clampedRect = aRect; + int32_t maxTexSize = GetMaxTextureSize(); + clampedRect.SetWidth(std::min(clampedRect.Width(), maxTexSize)); + clampedRect.SetHeight(std::min(clampedRect.Height(), maxTexSize)); + + auto clampedRectWidth = clampedRect.Width(); + auto clampedRectHeight = clampedRect.Height(); + + GLuint tex; + + mGLContext->fActiveTexture(LOCAL_GL_TEXTURE0); + mGLContext->fGenTextures(1, &tex); + mGLContext->fBindTexture(mFBOTextureTarget, tex); + + if (aCopyFromSource) { + GLuint curFBO = mCurrentRenderTarget->GetFBO(); + if (curFBO != aSourceFrameBuffer) { + mGLContext->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, aSourceFrameBuffer); + } + + // We're going to create an RGBA temporary fbo. But to + // CopyTexImage() from the current framebuffer, the framebuffer's + // format has to be compatible with the new texture's. So we + // check the format of the framebuffer here and take a slow path + // if it's incompatible. + GLenum format = + GetFrameBufferInternalFormat(gl(), aSourceFrameBuffer, mWidget); + + bool isFormatCompatibleWithRGBA = + gl()->IsGLES() ? (format == LOCAL_GL_RGBA) : true; + + if (isFormatCompatibleWithRGBA) { + mGLContext->fCopyTexImage2D(mFBOTextureTarget, 0, LOCAL_GL_RGBA, + clampedRect.X(), FlipY(clampedRect.YMost()), + clampedRectWidth, clampedRectHeight, 0); + WorkAroundAppleIntelHD3000GraphicsGLDriverBug(mGLContext); + } else { + // Curses, incompatible formats. Take a slow path. + + // RGBA + size_t bufferSize = clampedRectWidth * clampedRectHeight * 4; + auto buf = MakeUnique(bufferSize); + + mGLContext->fReadPixels(clampedRect.X(), clampedRect.Y(), + clampedRectWidth, clampedRectHeight, + LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, buf.get()); + WorkAroundAppleIntelHD3000GraphicsGLDriverBug(mGLContext); + mGLContext->fTexImage2D(mFBOTextureTarget, 0, LOCAL_GL_RGBA, + clampedRectWidth, clampedRectHeight, 0, + LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, buf.get()); + } + + GLenum error = mGLContext->fGetError(); + if (error != LOCAL_GL_NO_ERROR) { + nsAutoCString msg; + msg.AppendPrintf( + "Texture initialization failed! -- error 0x%x, Source %d, Source " + "format %d, RGBA Compat %d", + error, aSourceFrameBuffer, format, isFormatCompatibleWithRGBA); + NS_ERROR(msg.get()); + } + } else { + mGLContext->fTexImage2D(mFBOTextureTarget, 0, LOCAL_GL_RGBA, + clampedRectWidth, clampedRectHeight, 0, + LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, nullptr); + } + mGLContext->fTexParameteri(mFBOTextureTarget, LOCAL_GL_TEXTURE_MIN_FILTER, + LOCAL_GL_LINEAR); + mGLContext->fTexParameteri(mFBOTextureTarget, LOCAL_GL_TEXTURE_MAG_FILTER, + LOCAL_GL_LINEAR); + mGLContext->fTexParameteri(mFBOTextureTarget, LOCAL_GL_TEXTURE_WRAP_S, + LOCAL_GL_CLAMP_TO_EDGE); + mGLContext->fTexParameteri(mFBOTextureTarget, LOCAL_GL_TEXTURE_WRAP_T, + LOCAL_GL_CLAMP_TO_EDGE); + mGLContext->fBindTexture(mFBOTextureTarget, 0); + + if (aAllocSize) { + aAllocSize->width = clampedRectWidth; + aAllocSize->height = clampedRectHeight; + } + + return tex; +} + +ShaderConfigOGL CompositorOGL::GetShaderConfigFor(Effect* aEffect, + TextureSourceOGL* aSourceMask, + gfx::CompositionOp aOp, + bool aColorMatrix, + bool aDEAAEnabled) const { + ShaderConfigOGL config; + + switch (aEffect->mType) { + case EffectTypes::SOLID_COLOR: + config.SetRenderColor(true); + break; + case EffectTypes::YCBCR: { + config.SetYCbCr(true); + EffectYCbCr* effectYCbCr = static_cast(aEffect); + config.SetColorMultiplier( + RescalingFactorForColorDepth(effectYCbCr->mColorDepth)); + config.SetTextureTarget( + effectYCbCr->mTexture->AsSourceOGL()->GetTextureTarget()); + break; + } + case EffectTypes::NV12: + config.SetNV12(true); + if (gl()->IsExtensionSupported(gl::GLContext::ARB_texture_rectangle)) { + config.SetTextureTarget(LOCAL_GL_TEXTURE_RECTANGLE_ARB); + } else { + config.SetTextureTarget(LOCAL_GL_TEXTURE_2D); + } + break; + case EffectTypes::COMPONENT_ALPHA: { + config.SetComponentAlpha(true); + EffectComponentAlpha* effectComponentAlpha = + static_cast(aEffect); + gfx::SurfaceFormat format = effectComponentAlpha->mOnWhite->GetFormat(); + config.SetRBSwap(format == gfx::SurfaceFormat::B8G8R8A8 || + format == gfx::SurfaceFormat::B8G8R8X8); + TextureSourceOGL* source = effectComponentAlpha->mOnWhite->AsSourceOGL(); + config.SetTextureTarget(source->GetTextureTarget()); + break; + } + case EffectTypes::RENDER_TARGET: + config.SetTextureTarget(mFBOTextureTarget); + break; + default: { + MOZ_ASSERT(aEffect->mType == EffectTypes::RGB); + TexturedEffect* texturedEffect = static_cast(aEffect); + TextureSourceOGL* source = texturedEffect->mTexture->AsSourceOGL(); + MOZ_ASSERT_IF( + source->GetTextureTarget() == LOCAL_GL_TEXTURE_EXTERNAL, + source->GetFormat() == gfx::SurfaceFormat::R8G8B8A8 || + source->GetFormat() == gfx::SurfaceFormat::R8G8B8X8 || + source->GetFormat() == gfx::SurfaceFormat::B8G8R8A8 || + source->GetFormat() == gfx::SurfaceFormat::B8G8R8X8 || + source->GetFormat() == gfx::SurfaceFormat::R5G6B5_UINT16); + MOZ_ASSERT_IF( + source->GetTextureTarget() == LOCAL_GL_TEXTURE_RECTANGLE_ARB, + source->GetFormat() == gfx::SurfaceFormat::R8G8B8A8 || + source->GetFormat() == gfx::SurfaceFormat::R8G8B8X8 || + source->GetFormat() == gfx::SurfaceFormat::R5G6B5_UINT16 || + source->GetFormat() == gfx::SurfaceFormat::YUV422); + config = ShaderConfigFromTargetAndFormat(source->GetTextureTarget(), + source->GetFormat()); + if (!texturedEffect->mPremultiplied) { + config.SetNoPremultipliedAlpha(); + } + break; + } + } + config.SetColorMatrix(aColorMatrix); + config.SetMask(!!aSourceMask); + if (aSourceMask) { + config.SetMaskTextureTarget(aSourceMask->GetTextureTarget()); + } + config.SetDEAA(aDEAAEnabled); + config.SetCompositionOp(aOp); + return config; +} + +ShaderProgramOGL* CompositorOGL::GetShaderProgramFor( + const ShaderConfigOGL& aConfig) { + std::map::iterator iter = + mPrograms.find(aConfig); + if (iter != mPrograms.end()) return iter->second; + + ProgramProfileOGL profile = ProgramProfileOGL::GetProfileFor(aConfig); + ShaderProgramOGL* shader = new ShaderProgramOGL(gl(), profile); + if (!shader->Initialize()) { + gfxCriticalError() << "Shader compilation failure, cfg:" + << " features: " << gfx::hexa(aConfig.mFeatures) + << " multiplier: " << aConfig.mMultiplier + << " op: " << aConfig.mCompositionOp; + delete shader; + return nullptr; + } + + mPrograms[aConfig] = shader; + return shader; +} + +void CompositorOGL::ActivateProgram(ShaderProgramOGL* aProg) { + if (mCurrentProgram != aProg) { + gl()->fUseProgram(aProg->GetProgram()); + mCurrentProgram = aProg; + } +} + +void CompositorOGL::ResetProgram() { mCurrentProgram = nullptr; } + +static bool SetBlendMode(GLContext* aGL, gfx::CompositionOp aBlendMode, + bool aIsPremultiplied = true) { + if (BlendOpIsMixBlendMode(aBlendMode)) { + // Mix-blend modes require an extra step (or more) that cannot be expressed + // in the fixed-function blending capabilities of opengl. We handle them + // separately in shaders, and the shaders assume we will use our default + // blend function for compositing (premultiplied OP_OVER). + return false; + } + if (aBlendMode == gfx::CompositionOp::OP_OVER && aIsPremultiplied) { + return false; + } + + GLenum srcBlend; + GLenum dstBlend; + GLenum srcAlphaBlend = LOCAL_GL_ONE; + GLenum dstAlphaBlend = LOCAL_GL_ONE_MINUS_SRC_ALPHA; + + switch (aBlendMode) { + case gfx::CompositionOp::OP_OVER: + MOZ_ASSERT(!aIsPremultiplied); + srcBlend = LOCAL_GL_SRC_ALPHA; + dstBlend = LOCAL_GL_ONE_MINUS_SRC_ALPHA; + break; + case gfx::CompositionOp::OP_SOURCE: + srcBlend = aIsPremultiplied ? LOCAL_GL_ONE : LOCAL_GL_SRC_ALPHA; + dstBlend = LOCAL_GL_ZERO; + srcAlphaBlend = LOCAL_GL_ONE; + dstAlphaBlend = LOCAL_GL_ZERO; + break; + default: + MOZ_ASSERT_UNREACHABLE("Unsupported blend mode!"); + return false; + } + + aGL->fBlendFuncSeparate(srcBlend, dstBlend, srcAlphaBlend, dstAlphaBlend); + return true; +} + +gfx::Point3D CompositorOGL::GetLineCoefficients(const gfx::Point& aPoint1, + const gfx::Point& aPoint2) { + // Return standard coefficients for a line between aPoint1 and aPoint2 + // for standard line equation: + // + // Ax + By + C = 0 + // + // A = (p1.y – p2.y) + // B = (p2.x – p1.x) + // C = (p1.x * p2.y) – (p2.x * p1.y) + + gfx::Point3D coeffecients; + coeffecients.x = aPoint1.y - aPoint2.y; + coeffecients.y = aPoint2.x - aPoint1.x; + coeffecients.z = aPoint1.x * aPoint2.y - aPoint2.x * aPoint1.y; + + coeffecients *= 1.0f / sqrtf(coeffecients.x * coeffecients.x + + coeffecients.y * coeffecients.y); + + // Offset outwards by 0.5 pixel as the edge is considered to be 1 pixel + // wide and included within the interior of the polygon + coeffecients.z += 0.5f; + + return coeffecients; +} + +void CompositorOGL::DrawQuad(const Rect& aRect, const IntRect& aClipRect, + const EffectChain& aEffectChain, Float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::Rect& aVisibleRect) { + AUTO_PROFILER_LABEL("CompositorOGL::DrawQuad", GRAPHICS); + + DrawGeometry(aRect, aRect, aClipRect, aEffectChain, aOpacity, aTransform, + aVisibleRect); +} + +void CompositorOGL::DrawTriangles( + const nsTArray& aTriangles, const gfx::Rect& aRect, + const gfx::IntRect& aClipRect, const EffectChain& aEffectChain, + gfx::Float aOpacity, const gfx::Matrix4x4& aTransform, + const gfx::Rect& aVisibleRect) { + AUTO_PROFILER_LABEL("CompositorOGL::DrawTriangles", GRAPHICS); + + DrawGeometry(aTriangles, aRect, aClipRect, aEffectChain, aOpacity, aTransform, + aVisibleRect); +} + +template +void CompositorOGL::DrawGeometry(const Geometry& aGeometry, + const gfx::Rect& aRect, + const gfx::IntRect& aClipRect, + const EffectChain& aEffectChain, + gfx::Float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::Rect& aVisibleRect) { + MOZ_ASSERT(mFrameInProgress, "frame not started"); + MOZ_ASSERT(mCurrentRenderTarget, "No destination"); + + MakeCurrent(); + + // Convert aClipRect into render target space, and intersect it with the + // render target's clip. + IntRect clipRect = aClipRect + mCurrentRenderTarget->GetClipSpaceOrigin(); + if (Maybe rtClip = mCurrentRenderTarget->GetClipRect()) { + clipRect = clipRect.Intersect(*rtClip); + } + + Rect destRect = aTransform.TransformAndClipBounds( + aRect, Rect(mCurrentRenderTarget->GetRect().Intersect(clipRect))); + if (destRect.IsEmpty()) { + return; + } + + // XXX: This doesn't handle 3D transforms. It also doesn't handled rotated + // quads. Fix me. + mPixelsFilled += destRect.Area(); + + LayerScope::DrawBegin(); + + EffectMask* effectMask; + Rect maskBounds; + if (aEffectChain.mSecondaryEffects[EffectTypes::MASK]) { + effectMask = static_cast( + aEffectChain.mSecondaryEffects[EffectTypes::MASK].get()); + + // We're assuming that the gl backend won't cheat and use NPOT + // textures when glContext says it can't (which seems to happen + // on a mac when you force POT textures) + IntSize maskSize = CalculatePOTSize(effectMask->mSize, mGLContext); + + const gfx::Matrix4x4& maskTransform = effectMask->mMaskTransform; + NS_ASSERTION(maskTransform.Is2D(), + "How did we end up with a 3D transform here?!"); + maskBounds = Rect(Point(), Size(maskSize)); + maskBounds = maskTransform.As2D().TransformBounds(maskBounds); + + clipRect = clipRect.Intersect(RoundedOut(maskBounds)); + } + + // Move clipRect into device space. + IntPoint offset = mCurrentRenderTarget->GetOrigin(); + clipRect -= offset; + + if (!mTarget && mCurrentRenderTarget->IsWindow()) { + clipRect.MoveBy(mSurfaceOrigin.x, -mSurfaceOrigin.y); + } + + ScopedGLState scopedScissorTestState(mGLContext, LOCAL_GL_SCISSOR_TEST, true); + ScopedScissorRect autoScissorRect(mGLContext, clipRect.X(), + FlipY(clipRect.Y() + clipRect.Height()), + clipRect.Width(), clipRect.Height()); + + MaskType maskType; + TextureSourceOGL* sourceMask = nullptr; + gfx::Matrix4x4 maskQuadTransform; + if (aEffectChain.mSecondaryEffects[EffectTypes::MASK]) { + sourceMask = effectMask->mMaskTexture->AsSourceOGL(); + + // NS_ASSERTION(textureMask->IsAlpha(), + // "OpenGL mask layers must be backed by alpha surfaces"); + + maskQuadTransform._11 = 1.0f / maskBounds.Width(); + maskQuadTransform._22 = 1.0f / maskBounds.Height(); + maskQuadTransform._41 = float(-maskBounds.X()) / maskBounds.Width(); + maskQuadTransform._42 = float(-maskBounds.Y()) / maskBounds.Height(); + + maskType = MaskType::Mask; + } else { + maskType = MaskType::MaskNone; + } + + // Determine the color if this is a color shader and fold the opacity into + // the color since color shaders don't have an opacity uniform. + DeviceColor color; + if (aEffectChain.mPrimaryEffect->mType == EffectTypes::SOLID_COLOR) { + EffectSolidColor* effectSolidColor = + static_cast(aEffectChain.mPrimaryEffect.get()); + color = effectSolidColor->mColor; + + Float opacity = aOpacity * color.a; + color.r *= opacity; + color.g *= opacity; + color.b *= opacity; + color.a = opacity; + + // We can fold opacity into the color, so no need to consider it further. + aOpacity = 1.f; + } + + bool createdMixBlendBackdropTexture = false; + GLuint mixBlendBackdrop = 0; + gfx::CompositionOp blendMode = gfx::CompositionOp::OP_OVER; + + if (aEffectChain.mSecondaryEffects[EffectTypes::BLEND_MODE]) { + EffectBlendMode* blendEffect = static_cast( + aEffectChain.mSecondaryEffects[EffectTypes::BLEND_MODE].get()); + blendMode = blendEffect->mBlendMode; + } + + // Only apply DEAA to quads that have been transformed such that aliasing + // could be visible + bool bEnableAA = StaticPrefs::layers_deaa_enabled() && + !aTransform.Is2DIntegerTranslation(); + + bool colorMatrix = aEffectChain.mSecondaryEffects[EffectTypes::COLOR_MATRIX]; + ShaderConfigOGL config = + GetShaderConfigFor(aEffectChain.mPrimaryEffect, sourceMask, blendMode, + colorMatrix, bEnableAA); + + config.SetOpacity(aOpacity != 1.f); + ApplyPrimitiveConfig(config, aGeometry); + + ShaderProgramOGL* program = GetShaderProgramFor(config); + MOZ_DIAGNOSTIC_ASSERT(program); + if (!program) { + return; + } + ActivateProgram(program); + program->SetProjectionMatrix(mProjMatrix); + program->SetLayerTransform(aTransform); + LayerScope::SetLayerTransform(aTransform); + + if (colorMatrix) { + EffectColorMatrix* effectColorMatrix = static_cast( + aEffectChain.mSecondaryEffects[EffectTypes::COLOR_MATRIX].get()); + program->SetColorMatrix(effectColorMatrix->mColorMatrix); + } + + if (BlendOpIsMixBlendMode(blendMode)) { + gfx::Matrix4x4 backdropTransform; + + if (gl()->IsExtensionSupported(GLContext::NV_texture_barrier)) { + // The NV_texture_barrier extension lets us read directly from the + // backbuffer. Let's do that. + // We need to tell OpenGL about this, so that it can make sure everything + // on the GPU is happening in the right order. + gl()->fTextureBarrier(); + mixBlendBackdrop = mCurrentRenderTarget->GetTextureHandle(); + } else { + gfx::IntRect rect = ComputeBackdropCopyRect(aRect, clipRect, aTransform, + &backdropTransform); + mixBlendBackdrop = + CreateTexture(rect, true, mCurrentRenderTarget->GetFBO()); + createdMixBlendBackdropTexture = true; + } + program->SetBackdropTransform(backdropTransform); + } + + program->SetRenderOffset(offset.x, offset.y); + LayerScope::SetRenderOffset(offset.x, offset.y); + + if (aOpacity != 1.f) program->SetLayerOpacity(aOpacity); + + if (config.mFeatures & ENABLE_TEXTURE_RECT) { + TextureSourceOGL* source = nullptr; + if (aEffectChain.mPrimaryEffect->mType == EffectTypes::COMPONENT_ALPHA) { + EffectComponentAlpha* effectComponentAlpha = + static_cast(aEffectChain.mPrimaryEffect.get()); + source = effectComponentAlpha->mOnWhite->AsSourceOGL(); + } else { + TexturedEffect* texturedEffect = + static_cast(aEffectChain.mPrimaryEffect.get()); + source = texturedEffect->mTexture->AsSourceOGL(); + } + // This is used by IOSurface that use 0,0...w,h coordinate rather then + // 0,0..1,1. + program->SetTexCoordMultiplier(source->GetSize().width, + source->GetSize().height); + } + + if (sourceMask && config.mFeatures & ENABLE_MASK_TEXTURE_RECT) { + program->SetMaskCoordMultiplier(sourceMask->GetSize().width, + sourceMask->GetSize().height); + } + + // XXX kip - These calculations could be performed once per layer rather than + // for every tile. This might belong in Compositor.cpp once DEAA + // is implemented for DirectX. + if (bEnableAA) { + // Calculate the transformed vertices of aVisibleRect in screen space + // pixels, mirroring the calculations in the vertex shader + Matrix4x4 flatTransform = aTransform; + flatTransform.PostTranslate(-offset.x, -offset.y, 0.0f); + flatTransform *= mProjMatrix; + + Rect viewportClip = Rect(-1.0f, -1.0f, 2.0f, 2.0f); + size_t edgeCount = 0; + Point3D coefficients[4]; + + Point points[Matrix4x4::kTransformAndClipRectMaxVerts]; + size_t pointCount = + flatTransform.TransformAndClipRect(aVisibleRect, viewportClip, points); + for (size_t i = 0; i < pointCount; i++) { + points[i] = Point((points[i].x * 0.5f + 0.5f) * mViewportSize.width, + (points[i].y * 0.5f + 0.5f) * mViewportSize.height); + } + if (pointCount > 2) { + // Use shoelace formula on a triangle in the clipped quad to determine if + // winding order is reversed. Iterate through the triangles until one is + // found with a non-zero area. + float winding = 0.0f; + size_t wp = 0; + while (winding == 0.0f && wp < pointCount) { + int wp1 = (wp + 1) % pointCount; + int wp2 = (wp + 2) % pointCount; + winding = + (points[wp1].x - points[wp].x) * (points[wp1].y + points[wp].y) + + (points[wp2].x - points[wp1].x) * (points[wp2].y + points[wp1].y) + + (points[wp].x - points[wp2].x) * (points[wp].y + points[wp2].y); + wp++; + } + bool frontFacing = winding >= 0.0f; + + // Calculate the line coefficients used by the DEAA shader to determine + // the sub-pixel coverage of the edge pixels + for (size_t i = 0; i < pointCount; i++) { + const Point& p1 = points[i]; + const Point& p2 = points[(i + 1) % pointCount]; + // Create a DEAA edge for any non-straight lines, to a maximum of 4 + if (p1.x != p2.x && p1.y != p2.y && edgeCount < 4) { + if (frontFacing) { + coefficients[edgeCount++] = GetLineCoefficients(p2, p1); + } else { + coefficients[edgeCount++] = GetLineCoefficients(p1, p2); + } + } + } + } + + // The coefficients that are not needed must not cull any fragments. + // We fill these unused coefficients with a clipping plane that has no + // effect. + for (size_t i = edgeCount; i < 4; i++) { + coefficients[i] = Point3D(0.0f, 1.0f, mViewportSize.height); + } + + // Set uniforms required by DEAA shader + Matrix4x4 transformInverted = aTransform; + transformInverted.Invert(); + program->SetLayerTransformInverse(transformInverted); + program->SetDEAAEdges(coefficients); + program->SetVisibleCenter(aVisibleRect.Center()); + program->SetViewportSize(mViewportSize); + } + + bool didSetBlendMode = false; + + switch (aEffectChain.mPrimaryEffect->mType) { + case EffectTypes::SOLID_COLOR: { + program->SetRenderColor(color); + + if (maskType != MaskType::MaskNone) { + BindMaskForProgram(program, sourceMask, LOCAL_GL_TEXTURE0, + maskQuadTransform); + } + if (mixBlendBackdrop) { + BindBackdrop(program, mixBlendBackdrop, LOCAL_GL_TEXTURE1); + } + + didSetBlendMode = SetBlendMode(gl(), blendMode); + + BindAndDrawGeometry(program, aGeometry); + } break; + + case EffectTypes::RGB: { + TexturedEffect* texturedEffect = + static_cast(aEffectChain.mPrimaryEffect.get()); + TextureSource* source = texturedEffect->mTexture; + + didSetBlendMode = + SetBlendMode(gl(), blendMode, texturedEffect->mPremultiplied); + + gfx::SamplingFilter samplingFilter = texturedEffect->mSamplingFilter; + + source->AsSourceOGL()->BindTexture(LOCAL_GL_TEXTURE0, samplingFilter); + + program->SetTextureUnit(0); + + Matrix4x4 textureTransform = source->AsSourceOGL()->GetTextureTransform(); + program->SetTextureTransform(textureTransform); + + if (maskType != MaskType::MaskNone) { + BindMaskForProgram(program, sourceMask, LOCAL_GL_TEXTURE1, + maskQuadTransform); + } + if (mixBlendBackdrop) { + BindBackdrop(program, mixBlendBackdrop, LOCAL_GL_TEXTURE2); + } + + BindAndDrawGeometryWithTextureRect( + program, aGeometry, texturedEffect->mTextureCoords, source); + source->AsSourceOGL()->MaybeFenceTexture(); + } break; + case EffectTypes::YCBCR: { + EffectYCbCr* effectYCbCr = + static_cast(aEffectChain.mPrimaryEffect.get()); + TextureSource* sourceYCbCr = effectYCbCr->mTexture; + const int Y = 0, Cb = 1, Cr = 2; + TextureSourceOGL* sourceY = sourceYCbCr->GetSubSource(Y)->AsSourceOGL(); + TextureSourceOGL* sourceCb = sourceYCbCr->GetSubSource(Cb)->AsSourceOGL(); + TextureSourceOGL* sourceCr = sourceYCbCr->GetSubSource(Cr)->AsSourceOGL(); + + if (!sourceY || !sourceCb || !sourceCr) { + NS_WARNING("Invalid layer texture."); + return; + } + + sourceY->BindTexture(LOCAL_GL_TEXTURE0, effectYCbCr->mSamplingFilter); + sourceCb->BindTexture(LOCAL_GL_TEXTURE1, effectYCbCr->mSamplingFilter); + sourceCr->BindTexture(LOCAL_GL_TEXTURE2, effectYCbCr->mSamplingFilter); + + if (config.mFeatures & ENABLE_TEXTURE_RECT) { + // This is used by IOSurface that use 0,0...w,h coordinate rather then + // 0,0..1,1. + program->SetCbCrTexCoordMultiplier(sourceCb->GetSize().width, + sourceCb->GetSize().height); + } + + program->SetYCbCrTextureUnits(Y, Cb, Cr); + program->SetTextureTransform(Matrix4x4()); + program->SetYUVColorSpace(effectYCbCr->mYUVColorSpace); + + if (maskType != MaskType::MaskNone) { + BindMaskForProgram(program, sourceMask, LOCAL_GL_TEXTURE3, + maskQuadTransform); + } + if (mixBlendBackdrop) { + BindBackdrop(program, mixBlendBackdrop, LOCAL_GL_TEXTURE4); + } + didSetBlendMode = SetBlendMode(gl(), blendMode); + BindAndDrawGeometryWithTextureRect(program, aGeometry, + effectYCbCr->mTextureCoords, + sourceYCbCr->GetSubSource(Y)); + sourceY->MaybeFenceTexture(); + sourceCb->MaybeFenceTexture(); + sourceCr->MaybeFenceTexture(); + } break; + case EffectTypes::NV12: { + EffectNV12* effectNV12 = + static_cast(aEffectChain.mPrimaryEffect.get()); + TextureSource* sourceNV12 = effectNV12->mTexture; + const int Y = 0, CbCr = 1; + TextureSourceOGL* sourceY = sourceNV12->GetSubSource(Y)->AsSourceOGL(); + TextureSourceOGL* sourceCbCr = + sourceNV12->GetSubSource(CbCr)->AsSourceOGL(); + + if (!sourceY || !sourceCbCr) { + NS_WARNING("Invalid layer texture."); + return; + } + + sourceY->BindTexture(LOCAL_GL_TEXTURE0, effectNV12->mSamplingFilter); + sourceCbCr->BindTexture(LOCAL_GL_TEXTURE1, effectNV12->mSamplingFilter); + + if (config.mFeatures & ENABLE_TEXTURE_RECT) { + // This is used by IOSurface that use 0,0...w,h coordinate rather then + // 0,0..1,1. + program->SetCbCrTexCoordMultiplier(sourceCbCr->GetSize().width, + sourceCbCr->GetSize().height); + } + + program->SetNV12TextureUnits(Y, CbCr); + program->SetTextureTransform(Matrix4x4()); + program->SetYUVColorSpace(effectNV12->mYUVColorSpace); + + if (maskType != MaskType::MaskNone) { + BindMaskForProgram(program, sourceMask, LOCAL_GL_TEXTURE2, + maskQuadTransform); + } + if (mixBlendBackdrop) { + BindBackdrop(program, mixBlendBackdrop, LOCAL_GL_TEXTURE3); + } + didSetBlendMode = SetBlendMode(gl(), blendMode); + BindAndDrawGeometryWithTextureRect(program, aGeometry, + effectNV12->mTextureCoords, + sourceNV12->GetSubSource(Y)); + sourceY->MaybeFenceTexture(); + sourceCbCr->MaybeFenceTexture(); + } break; + case EffectTypes::RENDER_TARGET: { + EffectRenderTarget* effectRenderTarget = + static_cast(aEffectChain.mPrimaryEffect.get()); + RefPtr surface = + static_cast( + effectRenderTarget->mRenderTarget.get()); + + surface->BindTexture(LOCAL_GL_TEXTURE0, mFBOTextureTarget); + + // Drawing is always flipped, but when copying between surfaces we want to + // avoid this, so apply a flip here to cancel the other one out. + Matrix transform; + transform.PreTranslate(0.0, 1.0); + transform.PreScale(1.0f, -1.0f); + program->SetTextureTransform(Matrix4x4::From2D(transform)); + program->SetTextureUnit(0); + + if (maskType != MaskType::MaskNone) { + BindMaskForProgram(program, sourceMask, LOCAL_GL_TEXTURE1, + maskQuadTransform); + } + if (mixBlendBackdrop) { + BindBackdrop(program, mixBlendBackdrop, LOCAL_GL_TEXTURE2); + } + + if (config.mFeatures & ENABLE_TEXTURE_RECT) { + // 2DRect case, get the multiplier right for a sampler2DRect + program->SetTexCoordMultiplier(surface->GetSize().width, + surface->GetSize().height); + } + + // Drawing is always flipped, but when copying between surfaces we want to + // avoid this. Pass true for the flip parameter to introduce a second flip + // that cancels the other one out. + didSetBlendMode = SetBlendMode(gl(), blendMode); + BindAndDrawGeometry(program, aGeometry); + } break; + case EffectTypes::COMPONENT_ALPHA: { + MOZ_ASSERT(LayerManager::LayersComponentAlphaEnabled()); + MOZ_ASSERT(blendMode == gfx::CompositionOp::OP_OVER, + "Can't support blend modes with component alpha!"); + EffectComponentAlpha* effectComponentAlpha = + static_cast(aEffectChain.mPrimaryEffect.get()); + TextureSourceOGL* sourceOnWhite = + effectComponentAlpha->mOnWhite->AsSourceOGL(); + TextureSourceOGL* sourceOnBlack = + effectComponentAlpha->mOnBlack->AsSourceOGL(); + + if (!sourceOnBlack->IsValid() || !sourceOnWhite->IsValid()) { + NS_WARNING("Invalid layer texture for component alpha"); + return; + } + + sourceOnBlack->BindTexture(LOCAL_GL_TEXTURE0, + effectComponentAlpha->mSamplingFilter); + sourceOnWhite->BindTexture(LOCAL_GL_TEXTURE1, + effectComponentAlpha->mSamplingFilter); + + program->SetBlackTextureUnit(0); + program->SetWhiteTextureUnit(1); + program->SetTextureTransform(Matrix4x4()); + + if (maskType != MaskType::MaskNone) { + BindMaskForProgram(program, sourceMask, LOCAL_GL_TEXTURE2, + maskQuadTransform); + } + // Pass 1. + gl()->fBlendFuncSeparate(LOCAL_GL_ZERO, LOCAL_GL_ONE_MINUS_SRC_COLOR, + LOCAL_GL_ONE, LOCAL_GL_ONE); + program->SetTexturePass2(false); + BindAndDrawGeometryWithTextureRect(program, aGeometry, + effectComponentAlpha->mTextureCoords, + effectComponentAlpha->mOnBlack); + + // Pass 2. + gl()->fBlendFuncSeparate(LOCAL_GL_ONE, LOCAL_GL_ONE, LOCAL_GL_ONE, + LOCAL_GL_ONE); + program->SetTexturePass2(true); + BindAndDrawGeometryWithTextureRect(program, aGeometry, + effectComponentAlpha->mTextureCoords, + effectComponentAlpha->mOnBlack); + + mGLContext->fBlendFuncSeparate(LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA, + LOCAL_GL_ONE, + LOCAL_GL_ONE_MINUS_SRC_ALPHA); + + sourceOnBlack->MaybeFenceTexture(); + sourceOnWhite->MaybeFenceTexture(); + } break; + default: + MOZ_ASSERT(false, "Unhandled effect type"); + break; + } + + if (didSetBlendMode) { + gl()->fBlendFuncSeparate(LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA, + LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA); + } + if (createdMixBlendBackdropTexture) { + gl()->fDeleteTextures(1, &mixBlendBackdrop); + } + + // in case rendering has used some other GL context + MakeCurrent(); + + LayerScope::DrawEnd(mGLContext, aEffectChain, aRect.Width(), aRect.Height()); +} + +void CompositorOGL::BindAndDrawGeometry(ShaderProgramOGL* aProgram, + const gfx::Rect& aRect) { + BindAndDrawQuad(aProgram, aRect); +} + +void CompositorOGL::BindAndDrawGeometry( + ShaderProgramOGL* aProgram, + const nsTArray& aTriangles) { + NS_ASSERTION(aProgram->HasInitialized(), + "Shader program not correctly initialized"); + + const nsTArray vertices = + TexturedTrianglesToVertexArray(aTriangles); + + mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mTriangleVBO); + mGLContext->fBufferData(LOCAL_GL_ARRAY_BUFFER, + vertices.Length() * sizeof(TexturedVertex), + vertices.Elements(), LOCAL_GL_STREAM_DRAW); + + const GLsizei stride = 4 * sizeof(GLfloat); + InitializeVAO(kCoordinateAttributeIndex, 2, stride, 0); + InitializeVAO(kTexCoordinateAttributeIndex, 2, stride, 2 * sizeof(GLfloat)); + + mGLContext->fDrawArrays(LOCAL_GL_TRIANGLES, 0, vertices.Length()); + + mGLContext->fDisableVertexAttribArray(kCoordinateAttributeIndex); + mGLContext->fDisableVertexAttribArray(kTexCoordinateAttributeIndex); + mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0); +} + +// |aRect| is the rectangle we want to draw to. We will draw it with +// up to 4 draw commands if necessary to avoid wrapping. +// |aTexCoordRect| is the rectangle from the texture that we want to +// draw using the given program. +// |aTexture| is the texture we are drawing. Its actual size can be +// larger than the rectangle given by |texCoordRect|. +void CompositorOGL::BindAndDrawGeometryWithTextureRect( + ShaderProgramOGL* aProg, const Rect& aRect, const Rect& aTexCoordRect, + TextureSource* aTexture) { + Rect scaledTexCoordRect = GetTextureCoordinates(aTexCoordRect, aTexture); + Rect layerRects[4]; + Rect textureRects[4]; + size_t rects = DecomposeIntoNoRepeatRects(aRect, scaledTexCoordRect, + &layerRects, &textureRects); + + BindAndDrawQuads(aProg, rects, layerRects, textureRects); +} + +void CompositorOGL::BindAndDrawGeometryWithTextureRect( + ShaderProgramOGL* aProg, const nsTArray& aTriangles, + const gfx::Rect& aTexCoordRect, TextureSource* aTexture) { + BindAndDrawGeometry(aProg, aTriangles); +} + +void CompositorOGL::BindAndDrawQuads(ShaderProgramOGL* aProg, int aQuads, + const Rect* aLayerRects, + const Rect* aTextureRects) { + NS_ASSERTION(aProg->HasInitialized(), + "Shader program not correctly initialized"); + + mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mQuadVBO); + InitializeVAO(kCoordinateAttributeIndex, 4, 0, 0); + + aProg->SetLayerRects(aLayerRects); + if (aProg->GetTextureCount() > 0) { + aProg->SetTextureRects(aTextureRects); + } + + // We are using GL_TRIANGLES here because the Mac Intel drivers fail to + // properly process uniform arrays with GL_TRIANGLE_STRIP. Go figure. + mGLContext->fDrawArrays(LOCAL_GL_TRIANGLES, 0, 6 * aQuads); + mGLContext->fDisableVertexAttribArray(kCoordinateAttributeIndex); + mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0); + LayerScope::SetDrawRects(aQuads, aLayerRects, aTextureRects); +} + +void CompositorOGL::InitializeVAO(const GLuint aAttrib, const GLint aComponents, + const GLsizei aStride, const size_t aOffset) { + mGLContext->fVertexAttribPointer(aAttrib, aComponents, LOCAL_GL_FLOAT, + LOCAL_GL_FALSE, aStride, + reinterpret_cast(aOffset)); + mGLContext->fEnableVertexAttribArray(aAttrib); +} + +void CompositorOGL::EndFrame() { + AUTO_PROFILER_LABEL("CompositorOGL::EndFrame", GRAPHICS); + +#ifdef MOZ_DUMP_PAINTING + if (gfxEnv::DumpCompositorTextures()) { + LayoutDeviceIntSize size; + if (mUseExternalSurfaceSize) { + size = LayoutDeviceIntSize(mSurfaceSize.width, mSurfaceSize.height); + } else { + size = mWidget->GetClientSize(); + } + RefPtr target = + gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget( + IntSize(size.width, size.height), SurfaceFormat::B8G8R8A8); + if (target) { + CopyToTarget(target, nsIntPoint(), Matrix()); + WriteSnapshotToDumpFile(this, target); + } + } +#endif + + mFrameInProgress = false; + mShouldInvalidateWindow = false; + + if (mTarget) { + CopyToTarget(mTarget, mTargetBounds.TopLeft(), Matrix()); + mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0); + mTarget = nullptr; + mWindowRenderTarget = nullptr; + mCurrentRenderTarget = nullptr; + Compositor::EndFrame(); + return; + } + + mWindowRenderTarget = nullptr; + mCurrentRenderTarget = nullptr; + + if (mTexturePool) { + mTexturePool->EndFrame(); + } + + InsertFrameDoneSync(); + + mGLContext->SetDamage(mCurrentFrameInvalidRegion); + mGLContext->SwapBuffers(); + mGLContext->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0); + + // Unbind all textures + for (GLuint i = 0; i <= 4; i++) { + mGLContext->fActiveTexture(LOCAL_GL_TEXTURE0 + i); + mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, 0); + if (!mGLContext->IsGLES()) { + mGLContext->fBindTexture(LOCAL_GL_TEXTURE_RECTANGLE_ARB, 0); + } + } + + mCurrentFrameInvalidRegion.SetEmpty(); + + Compositor::EndFrame(); +} + +void CompositorOGL::InsertFrameDoneSync() { +#ifdef XP_MACOSX + // Only do this on macOS. + // On other platforms, SwapBuffers automatically applies back-pressure. + if (mThisFrameDoneSync) { + mGLContext->fDeleteSync(mThisFrameDoneSync); + } + mThisFrameDoneSync = + mGLContext->fFenceSync(LOCAL_GL_SYNC_GPU_COMMANDS_COMPLETE, 0); +#elif defined(MOZ_WIDGET_ANDROID) + const auto& gle = gl::GLContextEGL::Cast(mGLContext); + const auto& egl = gle->mEgl; + + EGLSync sync = nullptr; + if (AndroidHardwareBufferApi::Get()) { + sync = egl->fCreateSync(LOCAL_EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr); + } + if (sync) { + int fenceFd = egl->fDupNativeFenceFDANDROID(sync); + if (fenceFd >= 0) { + mReleaseFenceFd = ipc::FileDescriptor(UniqueFileHandle(fenceFd)); + } + egl->fDestroySync(sync); + sync = nullptr; + } +#endif +} + +void CompositorOGL::WaitForGPU() { + if (mPreviousFrameDoneSync) { + AUTO_PROFILER_LABEL("Waiting for GPU to finish previous frame", GRAPHICS); + mGLContext->fClientWaitSync(mPreviousFrameDoneSync, + LOCAL_GL_SYNC_FLUSH_COMMANDS_BIT, + LOCAL_GL_TIMEOUT_IGNORED); + mGLContext->fDeleteSync(mPreviousFrameDoneSync); + } + mPreviousFrameDoneSync = mThisFrameDoneSync; + mThisFrameDoneSync = nullptr; +} + +ipc::FileDescriptor CompositorOGL::GetReleaseFence() { return mReleaseFenceFd; } + +RefPtr CompositorOGL::GetSurfacePoolHandle() { +#ifdef XP_MACOSX + if (!mSurfacePoolHandle) { + mSurfacePoolHandle = SurfacePool::Create(0)->GetHandleForGL(mGLContext); + } +#endif + return mSurfacePoolHandle; +} + +bool CompositorOGL::NeedToRecreateFullWindowRenderTarget() const { + if (!ShouldRecordFrames()) { + return false; + } + if (!mFullWindowRenderTarget) { + return true; + } + IntSize windowSize = mWidget->GetClientSize().ToUnknownSize(); + return mFullWindowRenderTarget->GetSize() != windowSize; +} + +void CompositorOGL::SetDestinationSurfaceSize(const IntSize& aSize) { + mSurfaceSize.width = aSize.width; + mSurfaceSize.height = aSize.height; +} + +void CompositorOGL::CopyToTarget(DrawTarget* aTarget, + const nsIntPoint& aTopLeft, + const gfx::Matrix& aTransform) { + MOZ_ASSERT(aTarget); + IntRect rect; + if (mUseExternalSurfaceSize) { + rect = IntRect(0, 0, mSurfaceSize.width, mSurfaceSize.height); + } else { + rect = IntRect(0, 0, mWidgetSize.width, mWidgetSize.height); + } + GLint width = rect.Width(); + GLint height = rect.Height(); + + if ((int64_t(width) * int64_t(height) * int64_t(4)) > INT32_MAX) { + NS_ERROR("Widget size too big - integer overflow!"); + return; + } + + RefPtr source = Factory::CreateDataSourceSurface( + rect.Size(), gfx::SurfaceFormat::B8G8R8A8); + if (NS_WARN_IF(!source)) { + return; + } + + ReadPixelsIntoDataSurface(mGLContext, source); + + // Map from GL space to Cairo space and reverse the world transform. + Matrix glToCairoTransform = aTransform; + glToCairoTransform.Invert(); + glToCairoTransform.PreScale(1.0, -1.0); + glToCairoTransform.PreTranslate(0.0, -height); + + glToCairoTransform.PostTranslate(-aTopLeft.x, -aTopLeft.y); + + Matrix oldMatrix = aTarget->GetTransform(); + aTarget->SetTransform(glToCairoTransform); + Rect floatRect = Rect(rect.X(), rect.Y(), width, height); + aTarget->DrawSurface(source, floatRect, floatRect, DrawSurfaceOptions(), + DrawOptions(1.0f, CompositionOp::OP_SOURCE)); + aTarget->SetTransform(oldMatrix); + aTarget->Flush(); +} + +void CompositorOGL::Pause() { +#ifdef MOZ_WIDGET_ANDROID + if (!gl() || gl()->IsDestroyed()) return; + // ReleaseSurface internally calls MakeCurrent + gl()->ReleaseSurface(); +#elif defined(MOZ_WAYLAND) + // ReleaseSurface internally calls MakeCurrent + gl()->ReleaseSurface(); +#endif +} + +bool CompositorOGL::Resume() { +#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_UIKIT) || \ + defined(MOZ_WAYLAND) + if (!gl() || gl()->IsDestroyed()) return false; + + // RenewSurface internally calls MakeCurrent. + return gl()->RenewSurface(GetWidget()); +#endif + return true; +} + +already_AddRefed CompositorOGL::CreateDataTextureSource( + TextureFlags aFlags) { + if (!gl()) { + return nullptr; + } + + return MakeAndAddRef(this, aFlags); +} + +already_AddRefed +CompositorOGL::CreateDataTextureSourceAroundYCbCr(TextureHost* aTexture) { + if (!gl()) { + return nullptr; + } + + BufferTextureHost* bufferTexture = aTexture->AsBufferTextureHost(); + MOZ_ASSERT(bufferTexture); + + if (!bufferTexture) { + return nullptr; + } + + uint8_t* buf = bufferTexture->GetBuffer(); + const BufferDescriptor& buffDesc = bufferTexture->GetBufferDescriptor(); + const YCbCrDescriptor& desc = buffDesc.get_YCbCrDescriptor(); + + RefPtr tempY = + gfx::Factory::CreateWrappingDataSourceSurface( + ImageDataSerializer::GetYChannel(buf, desc), desc.yStride(), + desc.ySize(), SurfaceFormatForColorDepth(desc.colorDepth())); + if (!tempY) { + return nullptr; + } + RefPtr tempCb = + gfx::Factory::CreateWrappingDataSourceSurface( + ImageDataSerializer::GetCbChannel(buf, desc), desc.cbCrStride(), + desc.cbCrSize(), SurfaceFormatForColorDepth(desc.colorDepth())); + if (!tempCb) { + return nullptr; + } + RefPtr tempCr = + gfx::Factory::CreateWrappingDataSourceSurface( + ImageDataSerializer::GetCrChannel(buf, desc), desc.cbCrStride(), + desc.cbCrSize(), SurfaceFormatForColorDepth(desc.colorDepth())); + if (!tempCr) { + return nullptr; + } + + RefPtr srcY = new DirectMapTextureSource(this, tempY); + RefPtr srcU = + new DirectMapTextureSource(this, tempCb); + RefPtr srcV = + new DirectMapTextureSource(this, tempCr); + + srcY->SetNextSibling(srcU); + srcU->SetNextSibling(srcV); + + return srcY.forget(); +} + +#ifdef XP_DARWIN +void CompositorOGL::MaybeUnlockBeforeNextComposition( + TextureHost* aTextureHost) { + auto bufferTexture = aTextureHost->AsBufferTextureHost(); + if (bufferTexture) { + mMaybeUnlockBeforeNextComposition.AppendElement(bufferTexture); + } +} + +void CompositorOGL::TryUnlockTextures() { + nsClassHashtable> + texturesIdsToUnlockByPid; + for (auto& texture : mMaybeUnlockBeforeNextComposition) { + if (texture->IsDirectMap() && texture->CanUnlock()) { + texture->ReadUnlock(); + auto actor = texture->GetIPDLActor(); + if (actor) { + base::ProcessId pid = actor->OtherPid(); + nsTArray* textureIds = + texturesIdsToUnlockByPid.LookupOrAdd(pid); + textureIds->AppendElement(TextureHost::GetTextureSerial(actor)); + } + } + } + mMaybeUnlockBeforeNextComposition.Clear(); + for (auto it = texturesIdsToUnlockByPid.ConstIter(); !it.Done(); it.Next()) { + TextureSync::SetTexturesUnlocked(it.Key(), *it.UserData()); + } +} +#endif + +already_AddRefed +CompositorOGL::CreateDataTextureSourceAround(gfx::DataSourceSurface* aSurface) { + if (!gl()) { + return nullptr; + } + + return MakeAndAddRef(this, aSurface); +} + +bool CompositorOGL::SupportsPartialTextureUpdate() { + return CanUploadSubTextures(mGLContext); +} + +int32_t CompositorOGL::GetMaxTextureSize() const { + MOZ_ASSERT(mGLContext); + GLint texSize = 0; + mGLContext->fGetIntegerv(LOCAL_GL_MAX_TEXTURE_SIZE, &texSize); + MOZ_ASSERT(texSize != 0); + return texSize; +} + +void CompositorOGL::MakeCurrent(MakeCurrentFlags aFlags) { + if (mDestroyed) { + NS_WARNING("Call on destroyed layer manager"); + return; + } + mGLContext->MakeCurrent(aFlags & ForceMakeCurrent); +} + +GLBlitTextureImageHelper* CompositorOGL::BlitTextureImageHelper() { + if (!mBlitTextureImageHelper) { + mBlitTextureImageHelper = MakeUnique(this); + } + + return mBlitTextureImageHelper.get(); +} + +GLuint CompositorOGL::GetTemporaryTexture(GLenum aTarget, GLenum aUnit) { + if (!mTexturePool) { + mTexturePool = new PerUnitTexturePoolOGL(gl()); + } + return mTexturePool->GetTexture(aTarget, aUnit); +} + +bool CompositorOGL::SupportsTextureDirectMapping() { + if (!StaticPrefs::gfx_allow_texture_direct_mapping_AtStartup()) { + return false; + } + + if (mGLContext) { + mGLContext->MakeCurrent(); + return mGLContext->IsExtensionSupported( + gl::GLContext::APPLE_client_storage) && + mGLContext->IsExtensionSupported(gl::GLContext::APPLE_texture_range); + } + + return false; +} + +GLuint PerUnitTexturePoolOGL::GetTexture(GLenum aTarget, GLenum aTextureUnit) { + if (mTextureTarget == 0) { + mTextureTarget = aTarget; + } + MOZ_ASSERT(mTextureTarget == aTarget); + + size_t index = aTextureUnit - LOCAL_GL_TEXTURE0; + // lazily grow the array of temporary textures + if (mTextures.Length() <= index) { + size_t prevLength = mTextures.Length(); + mTextures.SetLength(index + 1); + for (unsigned int i = prevLength; i <= index; ++i) { + mTextures[i] = 0; + } + } + // lazily initialize the temporary textures + if (!mTextures[index]) { + if (!mGL->MakeCurrent()) { + return 0; + } + mGL->fGenTextures(1, &mTextures[index]); + mGL->fBindTexture(aTarget, mTextures[index]); + mGL->fTexParameteri(aTarget, LOCAL_GL_TEXTURE_WRAP_S, + LOCAL_GL_CLAMP_TO_EDGE); + mGL->fTexParameteri(aTarget, LOCAL_GL_TEXTURE_WRAP_T, + LOCAL_GL_CLAMP_TO_EDGE); + } + return mTextures[index]; +} + +void PerUnitTexturePoolOGL::DestroyTextures() { + if (mGL && mGL->MakeCurrent()) { + if (mTextures.Length() > 0) { + mGL->fDeleteTextures(mTextures.Length(), &mTextures[0]); + } + } + mTextures.SetLength(0); +} + +bool CompositorOGL::SupportsLayerGeometry() const { + return StaticPrefs::layers_geometry_opengl_enabled(); +} + +void CompositorOGL::RegisterTextureSource(TextureSource* aTextureSource) { +#ifdef MOZ_WIDGET_GTK + if (mDestroyed) { + return; + } + mRegisteredTextureSources.insert(aTextureSource); +#endif +} + +void CompositorOGL::UnregisterTextureSource(TextureSource* aTextureSource) { +#ifdef MOZ_WIDGET_GTK + if (mDestroyed) { + return; + } + mRegisteredTextureSources.erase(aTextureSource); +#endif +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/opengl/CompositorOGL.h b/gfx/layers/opengl/CompositorOGL.h new file mode 100644 index 0000000000..cfe4698df2 --- /dev/null +++ b/gfx/layers/opengl/CompositorOGL.h @@ -0,0 +1,529 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_COMPOSITOROGL_H +#define MOZILLA_GFX_COMPOSITOROGL_H + +#include +#include +#include + +#include "gfx2DGlue.h" +#include "GLContextTypes.h" // for GLContext, etc +#include "GLDefs.h" // for GLuint, LOCAL_GL_TEXTURE_2D, etc +#include "OGLShaderConfig.h" // for ShaderConfigOGL +#include "Units.h" // for ScreenPoint +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/Attributes.h" // for override, final +#include "mozilla/RefPtr.h" // for already_AddRefed, RefPtr +#include "mozilla/gfx/2D.h" // for DrawTarget +#include "mozilla/gfx/BaseSize.h" // for BaseSize +#include "mozilla/gfx/MatrixFwd.h" // for Matrix4x4 +#include "mozilla/gfx/Point.h" // for IntSize, Point +#include "mozilla/gfx/Rect.h" // for Rect, IntRect +#include "mozilla/gfx/Triangle.h" // for Triangle +#include "mozilla/gfx/Types.h" // for Float, SurfaceFormat, etc +#include "mozilla/ipc/FileDescriptor.h" +#include "mozilla/layers/Compositor.h" // for SurfaceInitMode, Compositor, etc +#include "mozilla/layers/CompositorTypes.h" // for MaskType::MaskType::NumMaskTypes, etc +#include "mozilla/layers/LayersTypes.h" +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsDebug.h" // for NS_ASSERTION, NS_WARNING +#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc +#include "nsTArray.h" // for AutoTArray, nsTArray, etc +#include "nsThreadUtils.h" // for nsRunnable +#include "nsXULAppAPI.h" // for XRE_GetProcessType +#include "nscore.h" // for NS_IMETHOD + +class nsIWidget; + +namespace mozilla { + +namespace layers { + +class CompositingRenderTarget; +class CompositingRenderTargetOGL; +class DataTextureSource; +class ShaderProgramOGL; +class TextureSource; +class TextureSourceOGL; +class BufferTextureHost; +struct Effect; +struct EffectChain; +class GLBlitTextureImageHelper; + +/** + * Interface for pools of temporary gl textures for the compositor. + * The textures are fully owned by the pool, so the latter is responsible + * calling fDeleteTextures accordingly. + * Users of GetTexture receive a texture that is only valid for the duration + * of the current frame. + * This is primarily intended for direct texturing APIs that need to attach + * shared objects (such as an EGLImage) to a gl texture. + */ +class CompositorTexturePoolOGL { + protected: + virtual ~CompositorTexturePoolOGL() = default; + + public: + NS_INLINE_DECL_REFCOUNTING(CompositorTexturePoolOGL) + + virtual void Clear() = 0; + + virtual GLuint GetTexture(GLenum aTarget, GLenum aEnum) = 0; + + virtual void EndFrame() = 0; +}; + +/** + * Agressively reuses textures. One gl texture per texture unit in total. + * So far this hasn't shown the best results on b2g. + */ +class PerUnitTexturePoolOGL : public CompositorTexturePoolOGL { + public: + explicit PerUnitTexturePoolOGL(gl::GLContext* aGL); + virtual ~PerUnitTexturePoolOGL(); + + void Clear() override { DestroyTextures(); } + + GLuint GetTexture(GLenum aTarget, GLenum aUnit) override; + + void EndFrame() override {} + + protected: + void DestroyTextures(); + + GLenum mTextureTarget; + nsTArray mTextures; + RefPtr mGL; +}; + +// If you want to make this class not final, first remove calls to virtual +// methods (Destroy) that are made in the destructor. +class CompositorOGL final : public Compositor { + typedef mozilla::gl::GLContext GLContext; + + friend class CompositingRenderTargetOGL; + + std::map mPrograms; + + public: + CompositorOGL(CompositorBridgeParent* aParent, + widget::CompositorWidget* aWidget, int aSurfaceWidth = -1, + int aSurfaceHeight = -1, bool aUseExternalSurfaceSize = false); + + protected: + virtual ~CompositorOGL(); + + public: + CompositorOGL* AsCompositorOGL() override { return this; } + + already_AddRefed CreateDataTextureSource( + TextureFlags aFlags = TextureFlags::NO_FLAGS) override; + + already_AddRefed CreateDataTextureSourceAroundYCbCr( + TextureHost* aTexture) override; + + already_AddRefed CreateDataTextureSourceAround( + gfx::DataSourceSurface* aSurface) override; + + bool Initialize(nsCString* const out_failureReason) override; + + void Destroy() override; + + TextureFactoryIdentifier GetTextureFactoryIdentifier() override { + TextureFactoryIdentifier result = TextureFactoryIdentifier( + LayersBackend::LAYERS_OPENGL, XRE_GetProcessType(), GetMaxTextureSize(), + SupportsTextureDirectMapping(), false, + mFBOTextureTarget == LOCAL_GL_TEXTURE_2D, + SupportsPartialTextureUpdate()); + return result; + } + + // Returns a render target for the native layer. + // aInvalidRegion is in window coordinates, i.e. in the same space as + // aNativeLayer->GetPosition(). + already_AddRefed RenderTargetForNativeLayer( + NativeLayer* aNativeLayer, const gfx::IntRegion& aInvalidRegion); + + already_AddRefed CreateRenderTarget( + const gfx::IntRect& aRect, SurfaceInitMode aInit) override; + + already_AddRefed CreateRenderTargetFromSource( + const gfx::IntRect& aRect, const CompositingRenderTarget* aSource, + const gfx::IntPoint& aSourcePoint) override; + + void SetRenderTarget(CompositingRenderTarget* aSurface) override; + already_AddRefed GetCurrentRenderTarget() + const override; + already_AddRefed GetWindowRenderTarget() + const override; + + bool ReadbackRenderTarget(CompositingRenderTarget* aSource, + AsyncReadbackBuffer* aDest) override; + + already_AddRefed CreateAsyncReadbackBuffer( + const gfx::IntSize& aSize) override; + + bool BlitRenderTarget(CompositingRenderTarget* aSource, + const gfx::IntSize& aSourceSize, + const gfx::IntSize& aDestSize) override; + + void DrawQuad(const gfx::Rect& aRect, const gfx::IntRect& aClipRect, + const EffectChain& aEffectChain, gfx::Float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::Rect& aVisibleRect) override; + + void DrawTriangles(const nsTArray& aTriangles, + const gfx::Rect& aRect, const gfx::IntRect& aClipRect, + const EffectChain& aEffectChain, gfx::Float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::Rect& aVisibleRect) override; + + bool SupportsLayerGeometry() const override; + + void NormalDrawingDone() override; + + void EndFrame() override; + + void WaitForGPU() override; + + RefPtr GetSurfacePoolHandle() override; + + bool SupportsPartialTextureUpdate() override; + + bool CanUseCanvasLayerForSize(const gfx::IntSize& aSize) override { + if (!mGLContext) return false; + int32_t maxSize = GetMaxTextureSize(); + return aSize <= gfx::IntSize(maxSize, maxSize); + } + + int32_t GetMaxTextureSize() const override; + + /** + * Set the size of the EGL surface we're rendering to, if we're rendering to + * an EGL surface. + */ + void SetDestinationSurfaceSize(const gfx::IntSize& aSize) override; + + void MakeCurrent(MakeCurrentFlags aFlags = 0) override; + +#ifdef MOZ_DUMP_PAINTING + const char* Name() const override { return "OGL"; } +#endif // MOZ_DUMP_PAINTING + + LayersBackend GetBackendType() const override { + return LayersBackend::LAYERS_OPENGL; + } + + void Pause() override; + bool Resume() override; + + GLContext* gl() const { return mGLContext; } + GLContext* GetGLContext() const override { return mGLContext; } + +#ifdef XP_DARWIN + void MaybeUnlockBeforeNextComposition(TextureHost* aTextureHost) override; + void TryUnlockTextures() override; +#endif + + /** + * Clear the program state. This must be called + * before operating on the GLContext directly. */ + void ResetProgram(); + + gfx::SurfaceFormat GetFBOFormat() const { + return gfx::SurfaceFormat::R8G8B8A8; + } + + GLBlitTextureImageHelper* BlitTextureImageHelper(); + + /** + * The compositor provides with temporary textures for use with direct + * textruing. + */ + GLuint GetTemporaryTexture(GLenum aTarget, GLenum aUnit); + + const gfx::IntSize GetDestinationSurfaceSize() const { + return gfx::IntSize(mSurfaceSize.width, mSurfaceSize.height); + } + + /** + * Allow the origin of the surface to be offset so that content does not + * start at (0, 0) on the surface. + */ + void SetSurfaceOrigin(const ScreenIntPoint& aOrigin) { + mSurfaceOrigin = aOrigin; + } + + // Register TextureSource which own device data that have to be deleted before + // destroying this CompositorOGL. + void RegisterTextureSource(TextureSource* aTextureSource); + void UnregisterTextureSource(TextureSource* aTextureSource); + + ipc::FileDescriptor GetReleaseFence(); + + private: + template + void DrawGeometry(const Geometry& aGeometry, const gfx::Rect& aRect, + const gfx::IntRect& aClipRect, + const EffectChain& aEffectChain, gfx::Float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::Rect& aVisibleRect); + + void PrepareViewport(CompositingRenderTargetOGL* aRenderTarget); + + bool SupportsTextureDirectMapping(); + + void InsertFrameDoneSync(); + + bool NeedToRecreateFullWindowRenderTarget() const; + + /** Widget associated with this compositor */ + LayoutDeviceIntSize mWidgetSize; + RefPtr mGLContext; + RefPtr mSurfacePoolHandle; + UniquePtr mBlitTextureImageHelper; + gfx::Matrix4x4 mProjMatrix; + bool mCanRenderToDefaultFramebuffer = true; + +#ifdef XP_DARWIN + nsTArray> mMaybeUnlockBeforeNextComposition; +#endif + + /** The size of the surface we are rendering to */ + gfx::IntSize mSurfaceSize; + + /** The origin of the content on the surface */ + ScreenIntPoint mSurfaceOrigin; + + already_AddRefed CreateContext(); + + /** Texture target to use for FBOs */ + GLenum mFBOTextureTarget; + + /** Currently bound render target */ + RefPtr mCurrentRenderTarget; + + // The 1x1 dummy render target that's the "current" render target between + // BeginFrameForNativeLayers and EndFrame but outside pairs of + // Begin/EndRenderingToNativeLayer. Created on demand. + RefPtr mNativeLayersReferenceRT; + + // The render target that profiler screenshots / frame recording read from. + // This will be the actual window framebuffer when rendering to a window, and + // it will be mFullWindowRenderTarget when rendering to native layers. + RefPtr mWindowRenderTarget; + + // Non-null when using native layers and frame recording is requested. + // EndNormalDrawing() maintains a copy of the entire window contents in this + // render target, by copying from the native layer render targets. + RefPtr mFullWindowRenderTarget; + + /** + * VBO that has some basics in it for a textured quad, including vertex + * coords and texcoords. + */ + GLuint mQuadVBO; + + /** + * VBO that stores dynamic triangle geometry. + */ + GLuint mTriangleVBO; + + // Used to apply back-pressure in WaitForPreviousFrameDoneSync(). + GLsync mPreviousFrameDoneSync; + GLsync mThisFrameDoneSync; + + bool mHasBGRA; + + /** + * When rendering to some EGL surfaces (e.g. on Android), we rely on being + * told about size changes (via SetSurfaceSize) rather than pulling this + * information from the widget. + */ + bool mUseExternalSurfaceSize; + + /** + * Have we had DrawQuad calls since the last frame was rendered? + */ + bool mFrameInProgress; + + // Only true between BeginFromeForNativeLayers and EndFrame, and only if the + // full window render target needed to be recreated in the current frame. + bool mShouldInvalidateWindow = false; + + /* + * Clear aRect on current render target. + */ + void ClearRect(const gfx::Rect& aRect) override; + + /* Start a new frame. + */ + Maybe BeginFrameForWindow( + const nsIntRegion& aInvalidRegion, const Maybe& aClipRect, + const gfx::IntRect& aRenderBounds, + const nsIntRegion& aOpaqueRegion) override; + + Maybe BeginFrameForTarget( + const nsIntRegion& aInvalidRegion, const Maybe& aClipRect, + const gfx::IntRect& aRenderBounds, const nsIntRegion& aOpaqueRegion, + gfx::DrawTarget* aTarget, const gfx::IntRect& aTargetBounds) override; + + void BeginFrameForNativeLayers() override; + + Maybe BeginRenderingToNativeLayer( + const nsIntRegion& aInvalidRegion, const Maybe& aClipRect, + const nsIntRegion& aOpaqueRegion, NativeLayer* aNativeLayer) override; + + void EndRenderingToNativeLayer() override; + + Maybe BeginFrame(const nsIntRegion& aInvalidRegion, + const Maybe& aClipRect, + const gfx::IntRect& aRenderBounds, + const nsIntRegion& aOpaqueRegion); + + ShaderConfigOGL GetShaderConfigFor( + Effect* aEffect, TextureSourceOGL* aSourceMask = nullptr, + gfx::CompositionOp aOp = gfx::CompositionOp::OP_OVER, + bool aColorMatrix = false, bool aDEAAEnabled = false) const; + + ShaderProgramOGL* GetShaderProgramFor(const ShaderConfigOGL& aConfig); + + void ApplyPrimitiveConfig(ShaderConfigOGL& aConfig, const gfx::Rect&) { + aConfig.SetDynamicGeometry(false); + } + + void ApplyPrimitiveConfig(ShaderConfigOGL& aConfig, + const nsTArray&) { + aConfig.SetDynamicGeometry(true); + } + + /** + * Create a FBO backed by a texture. + * Note that the texture target type will be + * of the type returned by FBOTextureTarget; different + * shaders are required to sample from the different + * texture types. + */ + void CreateFBOWithTexture(const gfx::IntRect& aRect, bool aCopyFromSource, + GLuint aSourceFrameBuffer, GLuint* aFBO, + GLuint* aTexture, + gfx::IntSize* aAllocSize = nullptr); + + GLuint CreateTexture(const gfx::IntRect& aRect, bool aCopyFromSource, + GLuint aSourceFrameBuffer, + gfx::IntSize* aAllocSize = nullptr); + + gfx::Point3D GetLineCoefficients(const gfx::Point& aPoint1, + const gfx::Point& aPoint2); + + void ActivateProgram(ShaderProgramOGL* aProg); + + void CleanupResources(); + + void BindAndDrawQuads(ShaderProgramOGL* aProg, int aQuads, + const gfx::Rect* aLayerRect, + const gfx::Rect* aTextureRect); + + void BindAndDrawQuad(ShaderProgramOGL* aProg, const gfx::Rect& aLayerRect, + const gfx::Rect& aTextureRect = gfx::Rect(0.0f, 0.0f, + 1.0f, 1.0f)) { + gfx::Rect layerRects[4]; + gfx::Rect textureRects[4]; + layerRects[0] = aLayerRect; + textureRects[0] = aTextureRect; + BindAndDrawQuads(aProg, 1, layerRects, textureRects); + } + + void BindAndDrawGeometry(ShaderProgramOGL* aProgram, const gfx::Rect& aRect); + + void BindAndDrawGeometry(ShaderProgramOGL* aProgram, + const nsTArray& aTriangles); + + void BindAndDrawGeometryWithTextureRect(ShaderProgramOGL* aProg, + const gfx::Rect& aRect, + const gfx::Rect& aTexCoordRect, + TextureSource* aTexture); + + void BindAndDrawGeometryWithTextureRect( + ShaderProgramOGL* aProg, + const nsTArray& aTriangles, + const gfx::Rect& aTexCoordRect, TextureSource* aTexture); + + void InitializeVAO(const GLuint aAttribIndex, const GLint aComponents, + const GLsizei aStride, const size_t aOffset); + + gfx::Rect GetTextureCoordinates(gfx::Rect textureRect, + TextureSource* aTexture); + + /** + * Bind the texture behind the current render target as the backdrop for a + * mix-blend shader. + */ + void BindBackdrop(ShaderProgramOGL* aProgram, GLuint aBackdrop, + GLenum aTexUnit); + + /** + * Copies the content of the current render target to the set transaction + * target. + */ + void CopyToTarget(gfx::DrawTarget* aTarget, const nsIntPoint& aTopLeft, + const gfx::Matrix& aWorldMatrix); + + /** + * Implements the flipping of the y-axis to convert from layers/compositor + * coordinates to OpenGL coordinates. + * + * Indeed, the only coordinate system that OpenGL knows has the y-axis + * pointing upwards, but the layers/compositor coordinate system has the + * y-axis pointing downwards, for good reason as Web pages are typically + * scrolled downwards. So, some flipping has to take place; FlippedY does it. + */ + GLint FlipY(GLint y) const { return mViewportSize.height - y; } + + // The DrawTarget from BeginFrameForTarget, which EndFrame needs to copy the + // window contents into. + // Only non-null between BeginFrameForTarget and EndFrame. + RefPtr mTarget; + gfx::IntRect mTargetBounds; + + RefPtr mTexturePool; + + // The native layer that we're currently rendering to, if any. + // Non-null only between BeginFrame and EndFrame if BeginFrame has been called + // with a non-null aNativeLayer. + RefPtr mCurrentNativeLayer; + +#ifdef MOZ_WIDGET_GTK + // Hold TextureSources which own device data that have to be deleted before + // destroying this CompositorOGL. + std::unordered_set mRegisteredTextureSources; +#endif + + // FileDescriptor of release fence. + // Release fence is a fence that is used for waiting until usage/composite of + // AHardwareBuffer is ended. The fence is delivered to client side via + // ImageBridge. It is used only on android. + ipc::FileDescriptor mReleaseFenceFd; + + bool mDestroyed; + + /** + * Size of the OpenGL context's primary framebuffer in pixels. Used by + * FlipY for the y-flipping calculation and by the DEAA shader. + */ + gfx::IntSize mViewportSize; + + gfx::IntRegion mCurrentFrameInvalidRegion; + + ShaderProgramOGL* mCurrentProgram; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* MOZILLA_GFX_COMPOSITOROGL_H */ diff --git a/gfx/layers/opengl/DMABUFTextureClientOGL.cpp b/gfx/layers/opengl/DMABUFTextureClientOGL.cpp new file mode 100644 index 0000000000..00dca94b38 --- /dev/null +++ b/gfx/layers/opengl/DMABUFTextureClientOGL.cpp @@ -0,0 +1,116 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DMABUFTextureClientOGL.h" +#include "mozilla/widget/DMABufSurface.h" +#include "gfxPlatform.h" + +namespace mozilla::layers { + +using namespace gfx; + +DMABUFTextureData::DMABUFTextureData(DMABufSurface* aSurface, + BackendType aBackend) + : mSurface(aSurface), mBackend(aBackend) { + MOZ_ASSERT(mSurface); +} + +DMABUFTextureData::~DMABUFTextureData() = default; + +/* static */ DMABUFTextureData* DMABUFTextureData::Create( + const IntSize& aSize, SurfaceFormat aFormat, BackendType aBackend) { + if (aFormat != SurfaceFormat::B8G8R8A8 && + aFormat != SurfaceFormat::B8G8R8X8) { + // TODO + NS_WARNING("DMABUFTextureData::Create() - wrong surface format!"); + return nullptr; + } + + int flags = DMABUF_TEXTURE; + if (aFormat == SurfaceFormat::B8G8R8A8) { + flags |= DMABUF_ALPHA; + } + RefPtr surf = + DMABufSurfaceRGBA::CreateDMABufSurface(aSize.width, aSize.height, flags); + if (!surf) { + NS_WARNING("DMABUFTextureData::Create() failed!"); + return nullptr; + } + return new DMABUFTextureData(surf, aBackend); +} + +TextureData* DMABUFTextureData::CreateSimilar( + LayersIPCChannel* aAllocator, LayersBackend aLayersBackend, + TextureFlags aFlags, TextureAllocationFlags aAllocFlags) const { + return DMABUFTextureData::Create( + gfx::IntSize(mSurface->GetWidth(), mSurface->GetHeight()), + mSurface->GetFormat(), mBackend); +} + +bool DMABUFTextureData::Serialize(SurfaceDescriptor& aOutDescriptor) { + mSurface->Serialize(aOutDescriptor); + return true; +} + +void DMABUFTextureData::FillInfo(TextureData::Info& aInfo) const { + aInfo.size = gfx::IntSize(mSurface->GetWidth(), mSurface->GetHeight()); + aInfo.format = mSurface->GetFormat(); + aInfo.hasIntermediateBuffer = false; + aInfo.hasSynchronization = false; + aInfo.supportsMoz2D = true; + aInfo.canExposeMappedData = false; +} + +bool DMABUFTextureData::Lock(OpenMode) { + auto surf = mSurface->GetAsDMABufSurfaceRGBA(); + MOZ_ASSERT(!surf->IsMapped(), "Already locked?"); + surf->Map(); + return true; +} + +void DMABUFTextureData::Unlock() { + auto surf = mSurface->GetAsDMABufSurfaceRGBA(); + MOZ_ASSERT(surf->IsMapped(), "Already unlocked?"); + surf->Unmap(); +} + +already_AddRefed DMABUFTextureData::GetAsSurface() { + // TODO: Update for debug purposes. + return nullptr; +} + +already_AddRefed DMABUFTextureData::BorrowDrawTarget() { + MOZ_ASSERT(mBackend != BackendType::NONE); + if (mBackend == BackendType::NONE) { + // shouldn't happen, but degrade gracefully + return nullptr; + } + auto surf = mSurface->GetAsDMABufSurfaceRGBA(); + if (!surf->GetMappedRegion()) { + return nullptr; + } + return Factory::CreateDrawTargetForData( + mBackend, (unsigned char*)surf->GetMappedRegion(), + IntSize(surf->GetWidth(), surf->GetHeight()), + surf->GetMappedRegionStride(), surf->GetFormat(), true); +} + +void DMABUFTextureData::Deallocate(LayersIPCChannel*) { mSurface = nullptr; } + +void DMABUFTextureData::Forget(LayersIPCChannel*) { mSurface = nullptr; } + +bool DMABUFTextureData::UpdateFromSurface(gfx::SourceSurface* aSurface) { + RefPtr dt = BorrowDrawTarget(); + if (!dt) { + return false; + } + + dt->CopySurface(aSurface, IntRect(IntPoint(), aSurface->GetSize()), + IntPoint()); + return true; +} + +} // namespace mozilla::layers diff --git a/gfx/layers/opengl/DMABUFTextureClientOGL.h b/gfx/layers/opengl/DMABUFTextureClientOGL.h new file mode 100644 index 0000000000..42ed819b8c --- /dev/null +++ b/gfx/layers/opengl/DMABUFTextureClientOGL.h @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_MACIOSURFACETEXTURECLIENTOGL_H +#define MOZILLA_GFX_MACIOSURFACETEXTURECLIENTOGL_H + +#include "mozilla/layers/TextureClientOGL.h" + +class DMABufSurface; + +namespace mozilla { +namespace layers { + +class DMABUFTextureData : public TextureData { + public: + static DMABUFTextureData* Create(const gfx::IntSize& aSize, + gfx::SurfaceFormat aFormat, + gfx::BackendType aBackend); + + static DMABUFTextureData* Create(DMABufSurface* aSurface, + gfx::BackendType aBackend) { + return new DMABUFTextureData(aSurface, aBackend); + } + + ~DMABUFTextureData(); + + virtual TextureData* CreateSimilar( + LayersIPCChannel* aAllocator, LayersBackend aLayersBackend, + TextureFlags aFlags = TextureFlags::DEFAULT, + TextureAllocationFlags aAllocFlags = ALLOC_DEFAULT) const override; + + void FillInfo(TextureData::Info& aInfo) const override; + + bool Lock(OpenMode) override; + + void Unlock() override; + + already_AddRefed BorrowDrawTarget() override; + + bool Serialize(SurfaceDescriptor& aOutDescriptor) override; + + void Deallocate(LayersIPCChannel*) override; + + void Forget(LayersIPCChannel*) override; + + bool UpdateFromSurface(gfx::SourceSurface* aSurface) override; + + // For debugging purposes only. + already_AddRefed GetAsSurface(); + + protected: + DMABUFTextureData(DMABufSurface* aSurface, gfx::BackendType aBackend); + + RefPtr mSurface; + gfx::BackendType mBackend; +}; + +} // namespace layers +} // namespace mozilla + +#endif // MOZILLA_GFX_MACIOSURFACETEXTURECLIENTOGL_H diff --git a/gfx/layers/opengl/DMABUFTextureHostOGL.cpp b/gfx/layers/opengl/DMABUFTextureHostOGL.cpp new file mode 100644 index 0000000000..9a3ec73cb4 --- /dev/null +++ b/gfx/layers/opengl/DMABUFTextureHostOGL.cpp @@ -0,0 +1,233 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DMABUFTextureHostOGL.h" +#include "mozilla/widget/DMABufSurface.h" +#include "mozilla/webrender/RenderDMABUFTextureHost.h" +#include "mozilla/webrender/RenderThread.h" +#include "mozilla/webrender/WebRenderAPI.h" +#include "GLContextEGL.h" + +namespace mozilla::layers { + +DMABUFTextureHostOGL::DMABUFTextureHostOGL(TextureFlags aFlags, + const SurfaceDescriptor& aDesc) + : TextureHost(aFlags) { + MOZ_COUNT_CTOR(DMABUFTextureHostOGL); + + mSurface = + DMABufSurface::CreateDMABufSurface(aDesc.get_SurfaceDescriptorDMABuf()); +} + +DMABUFTextureHostOGL::~DMABUFTextureHostOGL() { + MOZ_COUNT_DTOR(DMABUFTextureHostOGL); +} + +GLTextureSource* DMABUFTextureHostOGL::CreateTextureSourceForPlane( + size_t aPlane) { + MOZ_ASSERT(mSurface); + + if (!mSurface->GetTexture(aPlane)) { + if (!mSurface->CreateTexture(gl(), aPlane)) { + return nullptr; + } + } + + return new GLTextureSource( + mProvider, mSurface->GetTexture(aPlane), LOCAL_GL_TEXTURE_2D, + gfx::IntSize(mSurface->GetWidth(aPlane), mSurface->GetHeight(aPlane)), + // XXX: This isn't really correct (but isn't used), we should be using the + // format of the individual plane, not of the whole buffer. + mSurface->GetFormatGL()); +} + +bool DMABUFTextureHostOGL::Lock() { + if (!gl() || !gl()->MakeCurrent() || !mSurface) { + return false; + } + + if (!mTextureSource) { + mTextureSource = CreateTextureSourceForPlane(0); + + RefPtr prev = mTextureSource; + for (size_t i = 1; i < mSurface->GetTextureCount(); i++) { + RefPtr next = CreateTextureSourceForPlane(i); + prev->SetNextSibling(next); + prev = next; + } + } + + mSurface->FenceWait(); + return true; +} + +void DMABUFTextureHostOGL::Unlock() {} + +void DMABUFTextureHostOGL::SetTextureSourceProvider( + TextureSourceProvider* aProvider) { + if (!aProvider || !aProvider->GetGLContext()) { + mTextureSource = nullptr; + mProvider = nullptr; + return; + } + + mProvider = aProvider; + + if (mTextureSource) { + mTextureSource->SetTextureSourceProvider(aProvider); + } +} + +gfx::SurfaceFormat DMABUFTextureHostOGL::GetFormat() const { + if (!mSurface) { + return gfx::SurfaceFormat::UNKNOWN; + } + return mSurface->GetFormat(); +} + +gfx::YUVColorSpace DMABUFTextureHostOGL::GetYUVColorSpace() const { + if (!mSurface) { + return gfx::YUVColorSpace::UNKNOWN; + } + return mSurface->GetYUVColorSpace(); +} + +gfx::ColorRange DMABUFTextureHostOGL::GetColorRange() const { + if (!mSurface) { + return gfx::ColorRange::LIMITED; + } + return mSurface->IsFullRange() ? gfx::ColorRange::FULL + : gfx::ColorRange::LIMITED; +} + +uint32_t DMABUFTextureHostOGL::NumSubTextures() { + return mSurface->GetTextureCount(); +} + +gfx::IntSize DMABUFTextureHostOGL::GetSize() const { + if (!mSurface) { + return gfx::IntSize(); + } + return gfx::IntSize(mSurface->GetWidth(), mSurface->GetHeight()); +} + +gl::GLContext* DMABUFTextureHostOGL::gl() const { + return mProvider ? mProvider->GetGLContext() : nullptr; +} + +void DMABUFTextureHostOGL::CreateRenderTexture( + const wr::ExternalImageId& aExternalImageId) { + RefPtr texture = + new wr::RenderDMABUFTextureHost(mSurface); + wr::RenderThread::Get()->RegisterExternalImage(wr::AsUint64(aExternalImageId), + texture.forget()); +} + +void DMABUFTextureHostOGL::PushResourceUpdates( + wr::TransactionBuilder& aResources, ResourceUpdateOp aOp, + const Range& aImageKeys, const wr::ExternalImageId& aExtID) { + MOZ_ASSERT(mSurface); + + auto method = aOp == TextureHost::ADD_IMAGE + ? &wr::TransactionBuilder::AddExternalImage + : &wr::TransactionBuilder::UpdateExternalImage; + auto imageType = + wr::ExternalImageType::TextureHandle(wr::ImageBufferKind::Texture2D); + + switch (mSurface->GetFormat()) { + case gfx::SurfaceFormat::R8G8B8X8: + case gfx::SurfaceFormat::R8G8B8A8: + case gfx::SurfaceFormat::B8G8R8X8: + case gfx::SurfaceFormat::B8G8R8A8: { + MOZ_ASSERT(aImageKeys.length() == 1); + // XXX Add RGBA handling. Temporary hack to avoid crash + // With BGRA format setting, rendering works without problem. + wr::ImageDescriptor descriptor(GetSize(), mSurface->GetFormat()); + (aResources.*method)(aImageKeys[0], descriptor, aExtID, imageType, 0); + break; + } + case gfx::SurfaceFormat::NV12: { + MOZ_ASSERT(aImageKeys.length() == 2); + MOZ_ASSERT(mSurface->GetTextureCount() == 2); + wr::ImageDescriptor descriptor0( + gfx::IntSize(mSurface->GetWidth(0), mSurface->GetHeight(0)), + gfx::SurfaceFormat::A8); + wr::ImageDescriptor descriptor1( + gfx::IntSize(mSurface->GetWidth(1), mSurface->GetHeight(1)), + gfx::SurfaceFormat::R8G8); + (aResources.*method)(aImageKeys[0], descriptor0, aExtID, imageType, 0); + (aResources.*method)(aImageKeys[1], descriptor1, aExtID, imageType, 1); + break; + } + case gfx::SurfaceFormat::YUV: { + MOZ_ASSERT(aImageKeys.length() == 3); + MOZ_ASSERT(mSurface->GetTextureCount() == 3); + wr::ImageDescriptor descriptor0( + gfx::IntSize(mSurface->GetWidth(0), mSurface->GetHeight(0)), + gfx::SurfaceFormat::A8); + wr::ImageDescriptor descriptor1( + gfx::IntSize(mSurface->GetWidth(1), mSurface->GetHeight(1)), + gfx::SurfaceFormat::A8); + (aResources.*method)(aImageKeys[0], descriptor0, aExtID, imageType, 0); + (aResources.*method)(aImageKeys[1], descriptor1, aExtID, imageType, 1); + (aResources.*method)(aImageKeys[2], descriptor1, aExtID, imageType, 2); + break; + } + default: { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + } + } +} + +void DMABUFTextureHostOGL::PushDisplayItems( + wr::DisplayListBuilder& aBuilder, const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, wr::ImageRendering aFilter, + const Range& aImageKeys, PushDisplayItemFlagSet aFlags) { + bool preferCompositorSurface = + aFlags.contains(PushDisplayItemFlag::PREFER_COMPOSITOR_SURFACE); + switch (mSurface->GetFormat()) { + case gfx::SurfaceFormat::R8G8B8X8: + case gfx::SurfaceFormat::R8G8B8A8: + case gfx::SurfaceFormat::B8G8R8A8: + case gfx::SurfaceFormat::B8G8R8X8: { + MOZ_ASSERT(aImageKeys.length() == 1); + aBuilder.PushImage(aBounds, aClip, true, aFilter, aImageKeys[0], + !(mFlags & TextureFlags::NON_PREMULTIPLIED), + wr::ColorF{1.0f, 1.0f, 1.0f, 1.0f}, + preferCompositorSurface); + break; + } + case gfx::SurfaceFormat::NV12: { + MOZ_ASSERT(aImageKeys.length() == 2); + MOZ_ASSERT(mSurface->GetTextureCount() == 2); + // Those images can only be generated at present by the VAAPI H264 decoder + // which only supports 8 bits color depth. + aBuilder.PushNV12Image(aBounds, aClip, true, aImageKeys[0], aImageKeys[1], + wr::ColorDepth::Color8, + wr::ToWrYuvColorSpace(GetYUVColorSpace()), + wr::ToWrColorRange(GetColorRange()), aFilter, + preferCompositorSurface); + break; + } + case gfx::SurfaceFormat::YUV: { + MOZ_ASSERT(aImageKeys.length() == 3); + MOZ_ASSERT(mSurface->GetTextureCount() == 3); + // Those images can only be generated at present by the VAAPI vp8 decoder + // which only supports 8 bits color depth. + aBuilder.PushYCbCrPlanarImage( + aBounds, aClip, true, aImageKeys[0], aImageKeys[1], aImageKeys[2], + wr::ColorDepth::Color8, wr::ToWrYuvColorSpace(GetYUVColorSpace()), + wr::ToWrColorRange(GetColorRange()), aFilter, + preferCompositorSurface); + break; + } + default: { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + } + } +} + +} // namespace mozilla::layers diff --git a/gfx/layers/opengl/DMABUFTextureHostOGL.h b/gfx/layers/opengl/DMABUFTextureHostOGL.h new file mode 100644 index 0000000000..ff87252df6 --- /dev/null +++ b/gfx/layers/opengl/DMABUFTextureHostOGL.h @@ -0,0 +1,81 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_DMABUFTEXTUREHOSTOGL_H +#define MOZILLA_GFX_DMABUFTEXTUREHOSTOGL_H + +#include "mozilla/gfx/2D.h" +#include "mozilla/layers/CompositorOGL.h" +#include "mozilla/layers/TextureHostOGL.h" + +class DMABufSurface; + +namespace mozilla { +namespace layers { + +/** + * A TextureHost for shared class DMABufSurface; + */ +class DMABUFTextureHostOGL : public TextureHost { + public: + DMABUFTextureHostOGL(TextureFlags aFlags, const SurfaceDescriptor& aDesc); + virtual ~DMABUFTextureHostOGL(); + + void SetTextureSourceProvider(TextureSourceProvider* aProvider) override; + + bool Lock() override; + + void Unlock() override; + + gfx::SurfaceFormat GetFormat() const override; + + bool BindTextureSource(CompositableTextureSourceRef& aTexture) override { + aTexture = mTextureSource; + return !!aTexture; + } + + already_AddRefed GetAsSurface() override { + return nullptr; // XXX - implement this (for MOZ_DUMP_PAINTING) + } + + gl::GLContext* gl() const; + + gfx::IntSize GetSize() const override; + +#ifdef MOZ_LAYERS_HAVE_LOG + const char* Name() override { return "DMABUFTextureHostOGL"; } +#endif + uint32_t NumSubTextures() override; + + gfx::YUVColorSpace GetYUVColorSpace() const override; + gfx::ColorRange GetColorRange() const override; + + void CreateRenderTexture( + const wr::ExternalImageId& aExternalImageId) override; + + void PushResourceUpdates(wr::TransactionBuilder& aResources, + ResourceUpdateOp aOp, + const Range& aImageKeys, + const wr::ExternalImageId& aExtID) override; + + void PushDisplayItems(wr::DisplayListBuilder& aBuilder, + const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, wr::ImageRendering aFilter, + const Range& aImageKeys, + PushDisplayItemFlagSet aFlags) override; + + private: + GLTextureSource* CreateTextureSourceForPlane(size_t aPlane); + + protected: + RefPtr mTextureSource; + RefPtr mSurface; +}; + +} // namespace layers +} // namespace mozilla + +#endif // MOZILLA_GFX_DMABUFTEXTUREHOSTOGL_H diff --git a/gfx/layers/opengl/EGLImageHelpers.cpp b/gfx/layers/opengl/EGLImageHelpers.cpp new file mode 100644 index 0000000000..a5abfe449f --- /dev/null +++ b/gfx/layers/opengl/EGLImageHelpers.cpp @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "EGLImageHelpers.h" +#include "GLContext.h" +#include "GLLibraryEGL.h" + +namespace mozilla { +namespace layers { + +using namespace gl; + +EGLImage EGLImageCreateFromNativeBuffer(GLContext* aGL, void* aBuffer, + const gfx::IntSize& aCropSize) { + EGLint attrs[] = { + LOCAL_EGL_IMAGE_PRESERVED, + LOCAL_EGL_TRUE, + LOCAL_EGL_NONE, + LOCAL_EGL_NONE, + }; + + EGLint cropAttrs[] = { + LOCAL_EGL_IMAGE_PRESERVED, + LOCAL_EGL_TRUE, + LOCAL_EGL_IMAGE_CROP_LEFT_ANDROID, + 0, + LOCAL_EGL_IMAGE_CROP_TOP_ANDROID, + 0, + LOCAL_EGL_IMAGE_CROP_RIGHT_ANDROID, + aCropSize.width, + LOCAL_EGL_IMAGE_CROP_BOTTOM_ANDROID, + aCropSize.height, + LOCAL_EGL_NONE, + LOCAL_EGL_NONE, + }; + + auto* egl = gl::GLLibraryEGL::Get(); + bool hasCropRect = (aCropSize.width != 0 && aCropSize.height != 0); + EGLint* usedAttrs = attrs; + if (hasCropRect && + egl->IsExtensionSupported(GLLibraryEGL::EGL_ANDROID_image_crop)) { + usedAttrs = cropAttrs; + } + + return egl->fCreateImage(egl->Display(), EGL_NO_CONTEXT, + LOCAL_EGL_NATIVE_BUFFER_ANDROID, aBuffer, usedAttrs); +} + +void EGLImageDestroy(GLContext* aGL, EGLImage aImage) { + auto* egl = gl::GLLibraryEGL::Get(); + egl->fDestroyImage(egl->Display(), aImage); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/opengl/EGLImageHelpers.h b/gfx/layers/opengl/EGLImageHelpers.h new file mode 100644 index 0000000000..59eb944c0d --- /dev/null +++ b/gfx/layers/opengl/EGLImageHelpers.h @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef EGLIMAGEHELPERS_H_ +#define EGLIMAGEHELPERS_H_ + +#include "mozilla/gfx/Point.h" + +typedef void* EGLImage; + +namespace mozilla { +namespace gl { +class GLContext; +} + +namespace layers { + +EGLImage EGLImageCreateFromNativeBuffer(gl::GLContext* aGL, void* aBuffer, + const gfx::IntSize& aCropSize); +void EGLImageDestroy(gl::GLContext* aGL, EGLImage aImage); + +} // namespace layers +} // namespace mozilla + +#endif // EGLIMAGEHELPERS_H_ diff --git a/gfx/layers/opengl/GLBlitTextureImageHelper.cpp b/gfx/layers/opengl/GLBlitTextureImageHelper.cpp new file mode 100644 index 0000000000..8385c29330 --- /dev/null +++ b/gfx/layers/opengl/GLBlitTextureImageHelper.cpp @@ -0,0 +1,283 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GLBlitTextureImageHelper.h" +#include "GLUploadHelpers.h" +#include "DecomposeIntoNoRepeatTriangles.h" +#include "GLContext.h" +#include "GLTextureImage.h" +#include "ScopedGLHelpers.h" +#include "nsRect.h" +#include "gfx2DGlue.h" +#include "gfxUtils.h" +#include "CompositorOGL.h" +#include "mozilla/gfx/Point.h" + +using namespace mozilla::gl; + +namespace mozilla { +namespace layers { + +GLBlitTextureImageHelper::GLBlitTextureImageHelper(CompositorOGL* aCompositor) + : mCompositor(aCompositor), + mBlitProgram(0), + mBlitFramebuffer(0) + +{} + +GLBlitTextureImageHelper::~GLBlitTextureImageHelper() { + GLContext* gl = mCompositor->gl(); + // Likely used by OGL Layers. + gl->fDeleteProgram(mBlitProgram); + gl->fDeleteFramebuffers(1, &mBlitFramebuffer); +} + +void GLBlitTextureImageHelper::BlitTextureImage(TextureImage* aSrc, + const gfx::IntRect& aSrcRect, + TextureImage* aDst, + const gfx::IntRect& aDstRect) { + GLContext* gl = mCompositor->gl(); + + if (!aSrc || !aDst || aSrcRect.IsEmpty() || aDstRect.IsEmpty()) return; + + int savedFb = 0; + gl->fGetIntegerv(LOCAL_GL_FRAMEBUFFER_BINDING, &savedFb); + + ScopedGLState scopedScissorTestState(gl, LOCAL_GL_SCISSOR_TEST, false); + ScopedGLState scopedBlendState(gl, LOCAL_GL_BLEND, false); + + // 2.0 means scale up by two + float blitScaleX = float(aDstRect.Width()) / float(aSrcRect.Width()); + float blitScaleY = float(aDstRect.Height()) / float(aSrcRect.Height()); + + // We start iterating over all destination tiles + aDst->BeginBigImageIteration(); + do { + // calculate portion of the tile that is going to be painted to + gfx::IntRect dstSubRect; + gfx::IntRect dstTextureRect = aDst->GetTileRect(); + dstSubRect.IntersectRect(aDstRect, dstTextureRect); + + // this tile is not part of the destination rectangle aDstRect + if (dstSubRect.IsEmpty()) continue; + + // (*) transform the rect of this tile into the rectangle defined by + // aSrcRect... + gfx::IntRect dstInSrcRect(dstSubRect); + dstInSrcRect.MoveBy(-aDstRect.TopLeft()); + // ...which might be of different size, hence scale accordingly + dstInSrcRect.ScaleRoundOut(1.0f / blitScaleX, 1.0f / blitScaleY); + dstInSrcRect.MoveBy(aSrcRect.TopLeft()); + + SetBlitFramebufferForDestTexture(aDst->GetTextureID()); + UseBlitProgram(); + + aSrc->BeginBigImageIteration(); + // now iterate over all tiles in the source Image... + do { + // calculate portion of the source tile that is in the source rect + gfx::IntRect srcSubRect; + gfx::IntRect srcTextureRect = aSrc->GetTileRect(); + srcSubRect.IntersectRect(aSrcRect, srcTextureRect); + + // this tile is not part of the source rect + if (srcSubRect.IsEmpty()) { + continue; + } + // calculate intersection of source rect with destination rect + srcSubRect.IntersectRect(srcSubRect, dstInSrcRect); + // this tile does not overlap the current destination tile + if (srcSubRect.IsEmpty()) { + continue; + } + // We now have the intersection of + // the current source tile + // and the desired source rectangle + // and the destination tile + // and the desired destination rectange + // in destination space. + // We need to transform this back into destination space, inverting the + // transform from (*) + gfx::IntRect srcSubInDstRect(srcSubRect); + srcSubInDstRect.MoveBy(-aSrcRect.TopLeft()); + srcSubInDstRect.ScaleRoundOut(blitScaleX, blitScaleY); + srcSubInDstRect.MoveBy(aDstRect.TopLeft()); + + // we transform these rectangles to be relative to the current src and dst + // tiles, respectively + gfx::IntSize srcSize = srcTextureRect.Size(); + gfx::IntSize dstSize = dstTextureRect.Size(); + srcSubRect.MoveBy(-srcTextureRect.X(), -srcTextureRect.Y()); + srcSubInDstRect.MoveBy(-dstTextureRect.X(), -dstTextureRect.Y()); + + float dx0 = + 2.0f * float(srcSubInDstRect.X()) / float(dstSize.width) - 1.0f; + float dy0 = + 2.0f * float(srcSubInDstRect.Y()) / float(dstSize.height) - 1.0f; + float dx1 = + 2.0f * float(srcSubInDstRect.XMost()) / float(dstSize.width) - 1.0f; + float dy1 = + 2.0f * float(srcSubInDstRect.YMost()) / float(dstSize.height) - 1.0f; + ScopedViewportRect autoViewportRect(gl, 0, 0, dstSize.width, + dstSize.height); + + RectTriangles rects; + + gfx::IntSize realTexSize = srcSize; + if (!CanUploadNonPowerOfTwo(gl)) { + realTexSize = gfx::IntSize(RoundUpPow2(srcSize.width), + RoundUpPow2(srcSize.height)); + } + + if (aSrc->GetWrapMode() == LOCAL_GL_REPEAT) { + rects.addRect(/* dest rectangle */ + dx0, dy0, dx1, dy1, + /* tex coords */ + srcSubRect.X() / float(realTexSize.width), + srcSubRect.Y() / float(realTexSize.height), + srcSubRect.XMost() / float(realTexSize.width), + srcSubRect.YMost() / float(realTexSize.height)); + } else { + DecomposeIntoNoRepeatTriangles(srcSubRect, realTexSize, rects); + + // now put the coords into the d[xy]0 .. d[xy]1 coordinate space + // from the 0..1 that it comes out of decompose + nsTArray& coords = rects.vertCoords(); + + for (unsigned int i = 0; i < coords.Length(); ++i) { + coords[i].x = (coords[i].x * (dx1 - dx0)) + dx0; + coords[i].y = (coords[i].y * (dy1 - dy0)) + dy0; + } + } + + ScopedBindTextureUnit autoTexUnit(gl, LOCAL_GL_TEXTURE0); + ScopedBindTexture autoTex(gl, aSrc->GetTextureID()); + ScopedVertexAttribPointer autoAttrib0(gl, 0, 2, LOCAL_GL_FLOAT, + LOCAL_GL_FALSE, 0, 0, + rects.vertCoords().Elements()); + ScopedVertexAttribPointer autoAttrib1(gl, 1, 2, LOCAL_GL_FLOAT, + LOCAL_GL_FALSE, 0, 0, + rects.texCoords().Elements()); + + gl->fDrawArrays(LOCAL_GL_TRIANGLES, 0, rects.elements()); + + } while (aSrc->NextTile()); + } while (aDst->NextTile()); + + // unbind the previous texture from the framebuffer + SetBlitFramebufferForDestTexture(0); + + gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, savedFb); +} + +void GLBlitTextureImageHelper::SetBlitFramebufferForDestTexture( + GLuint aTexture) { + GLContext* gl = mCompositor->gl(); + if (!mBlitFramebuffer) { + gl->fGenFramebuffers(1, &mBlitFramebuffer); + } + + gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mBlitFramebuffer); + gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0, + LOCAL_GL_TEXTURE_2D, aTexture, 0); + + GLenum result = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER); + if (aTexture && (result != LOCAL_GL_FRAMEBUFFER_COMPLETE)) { + nsAutoCString msg; + msg.AppendLiteral("Framebuffer not complete -- error 0x"); + msg.AppendInt(result, 16); + // Note: if you are hitting this, it is likely that + // your texture is not texture complete -- that is, you + // allocated a texture name, but didn't actually define its + // size via a call to TexImage2D. + MOZ_CRASH_UNSAFE(msg.get()); + } +} + +void GLBlitTextureImageHelper::UseBlitProgram() { + // XXX: GLBlitTextureImageHelper doesn't use ShaderProgramOGL + // so we need to Reset the program + mCompositor->ResetProgram(); + + GLContext* gl = mCompositor->gl(); + if (mBlitProgram) { + gl->fUseProgram(mBlitProgram); + return; + } + + mBlitProgram = gl->fCreateProgram(); + + GLuint shaders[2]; + shaders[0] = gl->fCreateShader(LOCAL_GL_VERTEX_SHADER); + shaders[1] = gl->fCreateShader(LOCAL_GL_FRAGMENT_SHADER); + + const char* blitVSSrc = + "attribute vec2 aVertex;" + "attribute vec2 aTexCoord;" + "varying vec2 vTexCoord;" + "void main() {" + " vTexCoord = aTexCoord;" + " gl_Position = vec4(aVertex, 0.0, 1.0);" + "}"; + const char* blitFSSrc = + "#ifdef GL_ES\nprecision mediump float;\n#endif\n" + "uniform sampler2D uSrcTexture;" + "varying vec2 vTexCoord;" + "void main() {" + " gl_FragColor = texture2D(uSrcTexture, vTexCoord);" + "}"; + + gl->fShaderSource(shaders[0], 1, (const GLchar**)&blitVSSrc, nullptr); + gl->fShaderSource(shaders[1], 1, (const GLchar**)&blitFSSrc, nullptr); + + for (int i = 0; i < 2; ++i) { + GLint success, len = 0; + + gl->fCompileShader(shaders[i]); + gl->fGetShaderiv(shaders[i], LOCAL_GL_COMPILE_STATUS, &success); + NS_ASSERTION(success, "Shader compilation failed!"); + + if (!success) { + nsAutoCString log; + gl->fGetShaderiv(shaders[i], LOCAL_GL_INFO_LOG_LENGTH, (GLint*)&len); + log.SetLength(len); + gl->fGetShaderInfoLog(shaders[i], len, (GLint*)&len, + (char*)log.BeginWriting()); + + printf_stderr("Shader %d compilation failed:\n%s\n", i, log.get()); + return; + } + + gl->fAttachShader(mBlitProgram, shaders[i]); + gl->fDeleteShader(shaders[i]); + } + + gl->fBindAttribLocation(mBlitProgram, 0, "aVertex"); + gl->fBindAttribLocation(mBlitProgram, 1, "aTexCoord"); + + gl->fLinkProgram(mBlitProgram); + + GLint success, len = 0; + gl->fGetProgramiv(mBlitProgram, LOCAL_GL_LINK_STATUS, &success); + NS_ASSERTION(success, "Shader linking failed!"); + + if (!success) { + nsAutoCString log; + gl->fGetProgramiv(mBlitProgram, LOCAL_GL_INFO_LOG_LENGTH, (GLint*)&len); + log.SetLength(len); + gl->fGetProgramInfoLog(mBlitProgram, len, (GLint*)&len, + (char*)log.BeginWriting()); + + printf_stderr("Program linking failed:\n%s\n", log.get()); + return; + } + + gl->fUseProgram(mBlitProgram); + gl->fUniform1i(gl->fGetUniformLocation(mBlitProgram, "uSrcTexture"), 0); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/opengl/GLBlitTextureImageHelper.h b/gfx/layers/opengl/GLBlitTextureImageHelper.h new file mode 100644 index 0000000000..e95e19bac3 --- /dev/null +++ b/gfx/layers/opengl/GLBlitTextureImageHelper.h @@ -0,0 +1,70 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GLBLITTEXTUREIMAGEHELPER_H_ +#define GLBLITTEXTUREIMAGEHELPER_H_ + +#include "mozilla/Attributes.h" +#include "GLContextTypes.h" +#include "GLConsts.h" +#include "mozilla/gfx/Rect.h" + +namespace mozilla { +namespace gl { +class GLContext; +class TextureImage; +} // namespace gl +namespace layers { + +class CompositorOGL; + +class GLBlitTextureImageHelper final { + // The GLContext is the sole owner of the GLBlitTextureImageHelper. + CompositorOGL* mCompositor; + + // lazy-initialized things + GLuint mBlitProgram, mBlitFramebuffer; + void UseBlitProgram(); + void SetBlitFramebufferForDestTexture(GLuint aTexture); + + public: + explicit GLBlitTextureImageHelper(CompositorOGL* gl); + ~GLBlitTextureImageHelper(); + + /** + * Copy a rectangle from one TextureImage into another. The + * source and destination are given in integer coordinates, and + * will be converted to texture coordinates. + * + * For the source texture, the wrap modes DO apply -- it's valid + * to use REPEAT or PAD and expect appropriate behaviour if the source + * rectangle extends beyond its bounds. + * + * For the destination texture, the wrap modes DO NOT apply -- the + * destination will be clipped by the bounds of the texture. + * + * Note: calling this function will cause the following OpenGL state + * to be changed: + * + * - current program + * - framebuffer binding + * - viewport + * - blend state (will be enabled at end) + * - scissor state (will be enabled at end) + * - vertex attrib 0 and 1 (pointer and enable state [enable state will + * be disabled at exit]) + * - array buffer binding (will be 0) + * - active texture (will be 0) + * - texture 0 binding + */ + void BlitTextureImage(gl::TextureImage* aSrc, const gfx::IntRect& aSrcRect, + gl::TextureImage* aDst, const gfx::IntRect& aDstRect); +}; + +} // namespace layers +} // namespace mozilla + +#endif // GLBLITTEXTUREIMAGEHELPER_H_ diff --git a/gfx/layers/opengl/MacIOSurfaceTextureClientOGL.cpp b/gfx/layers/opengl/MacIOSurfaceTextureClientOGL.cpp new file mode 100644 index 0000000000..7812a7ae96 --- /dev/null +++ b/gfx/layers/opengl/MacIOSurfaceTextureClientOGL.cpp @@ -0,0 +1,121 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MacIOSurfaceTextureClientOGL.h" +#include "mozilla/gfx/MacIOSurface.h" +#include "MacIOSurfaceHelpers.h" +#include "gfxPlatform.h" + +namespace mozilla { +namespace layers { + +using namespace gfx; + +MacIOSurfaceTextureData::MacIOSurfaceTextureData(MacIOSurface* aSurface, + BackendType aBackend) + : mSurface(aSurface), mBackend(aBackend) { + MOZ_ASSERT(mSurface); +} + +MacIOSurfaceTextureData::~MacIOSurfaceTextureData() = default; + +// static +MacIOSurfaceTextureData* MacIOSurfaceTextureData::Create(MacIOSurface* aSurface, + BackendType aBackend) { + MOZ_ASSERT(aSurface); + if (!aSurface) { + return nullptr; + } + return new MacIOSurfaceTextureData(aSurface, aBackend); +} + +MacIOSurfaceTextureData* MacIOSurfaceTextureData::Create(const IntSize& aSize, + SurfaceFormat aFormat, + BackendType aBackend) { + if (aFormat != SurfaceFormat::B8G8R8A8 && + aFormat != SurfaceFormat::B8G8R8X8) { + return nullptr; + } + + RefPtr surf = MacIOSurface::CreateIOSurface( + aSize.width, aSize.height, 1.0, aFormat == SurfaceFormat::B8G8R8A8); + if (!surf) { + return nullptr; + } + + return Create(surf, aBackend); +} + +bool MacIOSurfaceTextureData::Serialize(SurfaceDescriptor& aOutDescriptor) { + aOutDescriptor = SurfaceDescriptorMacIOSurface( + mSurface->GetIOSurfaceID(), mSurface->GetContentsScaleFactor(), + !mSurface->HasAlpha(), mSurface->GetYUVColorSpace()); + return true; +} + +void MacIOSurfaceTextureData::GetSubDescriptor( + RemoteDecoderVideoSubDescriptor* const aOutDesc) { + *aOutDesc = SurfaceDescriptorMacIOSurface( + mSurface->GetIOSurfaceID(), mSurface->GetContentsScaleFactor(), + !mSurface->HasAlpha(), mSurface->GetYUVColorSpace()); +} + +void MacIOSurfaceTextureData::FillInfo(TextureData::Info& aInfo) const { + aInfo.size = gfx::IntSize(mSurface->GetDevicePixelWidth(), + mSurface->GetDevicePixelHeight()); + aInfo.format = + mSurface->HasAlpha() ? SurfaceFormat::B8G8R8A8 : SurfaceFormat::B8G8R8X8; + aInfo.hasIntermediateBuffer = false; + aInfo.hasSynchronization = false; + aInfo.supportsMoz2D = true; + aInfo.canExposeMappedData = false; +} + +bool MacIOSurfaceTextureData::Lock(OpenMode) { + mSurface->Lock(false); + return true; +} + +void MacIOSurfaceTextureData::Unlock() { mSurface->Unlock(false); } + +already_AddRefed MacIOSurfaceTextureData::GetAsSurface() { + RefPtr surf = CreateSourceSurfaceFromMacIOSurface(mSurface); + return surf->GetDataSurface(); +} + +already_AddRefed MacIOSurfaceTextureData::BorrowDrawTarget() { + MOZ_ASSERT(mBackend != BackendType::NONE); + if (mBackend == BackendType::NONE) { + // shouldn't happen, but degrade gracefully + return nullptr; + } + return Factory::CreateDrawTargetForData( + mBackend, (unsigned char*)mSurface->GetBaseAddress(), + IntSize(mSurface->GetWidth(), mSurface->GetHeight()), + mSurface->GetBytesPerRow(), + mSurface->HasAlpha() ? SurfaceFormat::B8G8R8A8 : SurfaceFormat::B8G8R8X8, + true); +} + +void MacIOSurfaceTextureData::Deallocate(LayersIPCChannel*) { + mSurface = nullptr; +} + +void MacIOSurfaceTextureData::Forget(LayersIPCChannel*) { mSurface = nullptr; } + +bool MacIOSurfaceTextureData::UpdateFromSurface(gfx::SourceSurface* aSurface) { + RefPtr dt = BorrowDrawTarget(); + if (!dt) { + return false; + } + + dt->CopySurface(aSurface, IntRect(IntPoint(), aSurface->GetSize()), + IntPoint()); + return true; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/opengl/MacIOSurfaceTextureClientOGL.h b/gfx/layers/opengl/MacIOSurfaceTextureClientOGL.h new file mode 100644 index 0000000000..f7206e0b1b --- /dev/null +++ b/gfx/layers/opengl/MacIOSurfaceTextureClientOGL.h @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_MACIOSURFACETEXTURECLIENTOGL_H +#define MOZILLA_GFX_MACIOSURFACETEXTURECLIENTOGL_H + +#include "mozilla/layers/TextureClientOGL.h" + +class MacIOSurface; + +namespace mozilla { +namespace layers { + +class MacIOSurfaceTextureData : public TextureData { + public: + static MacIOSurfaceTextureData* Create(MacIOSurface* aSurface, + gfx::BackendType aBackend); + + static MacIOSurfaceTextureData* Create(const gfx::IntSize& aSize, + gfx::SurfaceFormat aFormat, + gfx::BackendType aBackend); + + ~MacIOSurfaceTextureData(); + + void FillInfo(TextureData::Info& aInfo) const override; + + bool Lock(OpenMode) override; + + void Unlock() override; + + already_AddRefed BorrowDrawTarget() override; + + bool Serialize(SurfaceDescriptor& aOutDescriptor) override; + + void GetSubDescriptor( + RemoteDecoderVideoSubDescriptor* const aOutDesc) override; + + void Deallocate(LayersIPCChannel*) override; + + void Forget(LayersIPCChannel*) override; + + bool UpdateFromSurface(gfx::SourceSurface* aSurface) override; + + // For debugging purposes only. + already_AddRefed GetAsSurface(); + + protected: + MacIOSurfaceTextureData(MacIOSurface* aSurface, gfx::BackendType aBackend); + + RefPtr mSurface; + gfx::BackendType mBackend; +}; + +} // namespace layers +} // namespace mozilla + +#endif // MOZILLA_GFX_MACIOSURFACETEXTURECLIENTOGL_H diff --git a/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.cpp b/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.cpp new file mode 100644 index 0000000000..436f24b6ea --- /dev/null +++ b/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.cpp @@ -0,0 +1,268 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MacIOSurfaceTextureHostOGL.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/gfx/MacIOSurface.h" +#include "mozilla/webrender/RenderMacIOSurfaceTextureHost.h" +#include "mozilla/webrender/RenderThread.h" +#include "mozilla/webrender/WebRenderAPI.h" +#include "GLContextCGL.h" + +namespace mozilla { +namespace layers { + +MacIOSurfaceTextureHostOGL::MacIOSurfaceTextureHostOGL( + TextureFlags aFlags, const SurfaceDescriptorMacIOSurface& aDescriptor) + : TextureHost(aFlags) { + MOZ_COUNT_CTOR(MacIOSurfaceTextureHostOGL); + mSurface = MacIOSurface::LookupSurface( + aDescriptor.surfaceId(), aDescriptor.scaleFactor(), + !aDescriptor.isOpaque(), aDescriptor.yUVColorSpace()); +} + +MacIOSurfaceTextureHostOGL::~MacIOSurfaceTextureHostOGL() { + MOZ_COUNT_DTOR(MacIOSurfaceTextureHostOGL); +} + +GLTextureSource* MacIOSurfaceTextureHostOGL::CreateTextureSourceForPlane( + size_t aPlane) { + MOZ_ASSERT(mSurface); + + GLuint textureHandle; + gl::GLContext* gl = mProvider->GetGLContext(); + gl->fGenTextures(1, &textureHandle); + gl->fBindTexture(LOCAL_GL_TEXTURE_RECTANGLE_ARB, textureHandle); + gl->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB, LOCAL_GL_TEXTURE_WRAP_T, + LOCAL_GL_CLAMP_TO_EDGE); + gl->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB, LOCAL_GL_TEXTURE_WRAP_S, + LOCAL_GL_CLAMP_TO_EDGE); + + gfx::SurfaceFormat readFormat = gfx::SurfaceFormat::UNKNOWN; + mSurface->CGLTexImageIOSurface2D( + gl, gl::GLContextCGL::Cast(gl)->GetCGLContext(), aPlane, &readFormat); + // With compositorOGL, we doesn't support the yuv interleaving format yet. + MOZ_ASSERT(readFormat != gfx::SurfaceFormat::YUV422); + + return new GLTextureSource( + mProvider, textureHandle, LOCAL_GL_TEXTURE_RECTANGLE_ARB, + gfx::IntSize(mSurface->GetDevicePixelWidth(aPlane), + mSurface->GetDevicePixelHeight(aPlane)), + readFormat); +} + +bool MacIOSurfaceTextureHostOGL::Lock() { + if (!gl() || !gl()->MakeCurrent() || !mSurface) { + return false; + } + + if (!mTextureSource) { + mTextureSource = CreateTextureSourceForPlane(0); + + RefPtr prev = mTextureSource; + for (size_t i = 1; i < mSurface->GetPlaneCount(); i++) { + RefPtr next = CreateTextureSourceForPlane(i); + prev->SetNextSibling(next); + prev = next; + } + } + return true; +} + +void MacIOSurfaceTextureHostOGL::SetTextureSourceProvider( + TextureSourceProvider* aProvider) { + if (!aProvider || !aProvider->GetGLContext()) { + mTextureSource = nullptr; + mProvider = nullptr; + return; + } + + if (mProvider != aProvider) { + // Cannot share GL texture identifiers across compositors. + mTextureSource = nullptr; + } + + mProvider = aProvider; +} + +gfx::SurfaceFormat MacIOSurfaceTextureHostOGL::GetFormat() const { + if (!mSurface) { + return gfx::SurfaceFormat::UNKNOWN; + } + return mSurface->GetFormat(); +} + +gfx::SurfaceFormat MacIOSurfaceTextureHostOGL::GetReadFormat() const { + if (!mSurface) { + return gfx::SurfaceFormat::UNKNOWN; + } + return mSurface->GetReadFormat(); +} + +gfx::IntSize MacIOSurfaceTextureHostOGL::GetSize() const { + if (!mSurface) { + return gfx::IntSize(); + } + return gfx::IntSize(mSurface->GetDevicePixelWidth(), + mSurface->GetDevicePixelHeight()); +} + +gl::GLContext* MacIOSurfaceTextureHostOGL::gl() const { + return mProvider ? mProvider->GetGLContext() : nullptr; +} + +gfx::YUVColorSpace MacIOSurfaceTextureHostOGL::GetYUVColorSpace() const { + if (!mSurface) { + return gfx::YUVColorSpace::UNKNOWN; + } + return mSurface->GetYUVColorSpace(); +} + +gfx::ColorRange MacIOSurfaceTextureHostOGL::GetColorRange() const { + if (!mSurface) { + return gfx::ColorRange::LIMITED; + } + return mSurface->IsFullRange() ? gfx::ColorRange::FULL + : gfx::ColorRange::LIMITED; +} + +void MacIOSurfaceTextureHostOGL::CreateRenderTexture( + const wr::ExternalImageId& aExternalImageId) { + RefPtr texture = + new wr::RenderMacIOSurfaceTextureHost(GetMacIOSurface()); + + wr::RenderThread::Get()->RegisterExternalImage(wr::AsUint64(aExternalImageId), + texture.forget()); +} + +uint32_t MacIOSurfaceTextureHostOGL::NumSubTextures() { + switch (GetFormat()) { + case gfx::SurfaceFormat::R8G8B8X8: + case gfx::SurfaceFormat::R8G8B8A8: + case gfx::SurfaceFormat::B8G8R8A8: + case gfx::SurfaceFormat::B8G8R8X8: + case gfx::SurfaceFormat::YUV422: { + return 1; + } + case gfx::SurfaceFormat::NV12: { + return 2; + } + default: { + MOZ_ASSERT_UNREACHABLE("unexpected format"); + return 1; + } + } +} + +void MacIOSurfaceTextureHostOGL::PushResourceUpdates( + wr::TransactionBuilder& aResources, ResourceUpdateOp aOp, + const Range& aImageKeys, const wr::ExternalImageId& aExtID) { + MOZ_ASSERT(mSurface); + + auto method = aOp == TextureHost::ADD_IMAGE + ? &wr::TransactionBuilder::AddExternalImage + : &wr::TransactionBuilder::UpdateExternalImage; + auto imageType = + wr::ExternalImageType::TextureHandle(wr::ImageBufferKind::TextureRect); + + switch (GetFormat()) { + case gfx::SurfaceFormat::B8G8R8A8: + case gfx::SurfaceFormat::B8G8R8X8: { + MOZ_ASSERT(aImageKeys.length() == 1); + MOZ_ASSERT(mSurface->GetPlaneCount() == 0); + // The internal pixel format of MacIOSurface is always BGRX or BGRA + // format. + auto format = GetFormat() == gfx::SurfaceFormat::B8G8R8A8 + ? gfx::SurfaceFormat::B8G8R8A8 + : gfx::SurfaceFormat::B8G8R8X8; + wr::ImageDescriptor descriptor(GetSize(), format); + (aResources.*method)(aImageKeys[0], descriptor, aExtID, imageType, 0); + break; + } + case gfx::SurfaceFormat::YUV422: { + // This is the special buffer format. The buffer contents could be a + // converted RGB interleaving data or a YCbCr interleaving data depending + // on the different platform setting. (e.g. It will be RGB at OpenGL 2.1 + // and YCbCr at OpenGL 3.1) + MOZ_ASSERT(aImageKeys.length() == 1); + MOZ_ASSERT(mSurface->GetPlaneCount() == 0); + wr::ImageDescriptor descriptor(GetSize(), gfx::SurfaceFormat::B8G8R8X8); + (aResources.*method)(aImageKeys[0], descriptor, aExtID, imageType, 0); + break; + } + case gfx::SurfaceFormat::NV12: { + MOZ_ASSERT(aImageKeys.length() == 2); + MOZ_ASSERT(mSurface->GetPlaneCount() == 2); + wr::ImageDescriptor descriptor0( + gfx::IntSize(mSurface->GetDevicePixelWidth(0), + mSurface->GetDevicePixelHeight(0)), + gfx::SurfaceFormat::A8); + wr::ImageDescriptor descriptor1( + gfx::IntSize(mSurface->GetDevicePixelWidth(1), + mSurface->GetDevicePixelHeight(1)), + gfx::SurfaceFormat::R8G8); + (aResources.*method)(aImageKeys[0], descriptor0, aExtID, imageType, 0); + (aResources.*method)(aImageKeys[1], descriptor1, aExtID, imageType, 1); + break; + } + default: { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + } + } +} + +void MacIOSurfaceTextureHostOGL::PushDisplayItems( + wr::DisplayListBuilder& aBuilder, const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, wr::ImageRendering aFilter, + const Range& aImageKeys, PushDisplayItemFlagSet aFlags) { + bool preferCompositorSurface = + aFlags.contains(PushDisplayItemFlag::PREFER_COMPOSITOR_SURFACE); + switch (GetFormat()) { + case gfx::SurfaceFormat::B8G8R8A8: + case gfx::SurfaceFormat::B8G8R8X8: { + MOZ_ASSERT(aImageKeys.length() == 1); + MOZ_ASSERT(mSurface->GetPlaneCount() == 0); + // We disable external compositing for RGB surfaces for now until + // we've tested support more thoroughly. Bug 1667917. + aBuilder.PushImage(aBounds, aClip, true, aFilter, aImageKeys[0], + !(mFlags & TextureFlags::NON_PREMULTIPLIED), + wr::ColorF{1.0f, 1.0f, 1.0f, 1.0f}, + preferCompositorSurface, + /* aSupportsExternalCompositing */ false); + break; + } + case gfx::SurfaceFormat::YUV422: { + MOZ_ASSERT(aImageKeys.length() == 1); + MOZ_ASSERT(mSurface->GetPlaneCount() == 0); + // Those images can only be generated at present by the Apple H264 decoder + // which only supports 8 bits color depth. + aBuilder.PushYCbCrInterleavedImage( + aBounds, aClip, true, aImageKeys[0], wr::ColorDepth::Color8, + wr::ToWrYuvColorSpace(GetYUVColorSpace()), + wr::ToWrColorRange(GetColorRange()), aFilter, preferCompositorSurface, + /* aSupportsExternalCompositing */ true); + break; + } + case gfx::SurfaceFormat::NV12: { + MOZ_ASSERT(aImageKeys.length() == 2); + MOZ_ASSERT(mSurface->GetPlaneCount() == 2); + // Those images can only be generated at present by the Apple H264 decoder + // which only supports 8 bits color depth. + aBuilder.PushNV12Image( + aBounds, aClip, true, aImageKeys[0], aImageKeys[1], + wr::ColorDepth::Color8, wr::ToWrYuvColorSpace(GetYUVColorSpace()), + wr::ToWrColorRange(GetColorRange()), aFilter, preferCompositorSurface, + /* aSupportsExternalCompositing */ true); + break; + } + default: { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + } + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.h b/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.h new file mode 100644 index 0000000000..2e4712a45b --- /dev/null +++ b/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.h @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_MACIOSURFACETEXTUREHOSTOGL_H +#define MOZILLA_GFX_MACIOSURFACETEXTUREHOSTOGL_H + +#include "MacIOSurfaceHelpers.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/layers/CompositorOGL.h" +#include "mozilla/layers/TextureHostOGL.h" + +class MacIOSurface; + +namespace mozilla { +namespace layers { + +/** + * A TextureHost for shared MacIOSurface + * + * Most of the logic actually happens in MacIOSurfaceTextureSourceOGL. + */ +class MacIOSurfaceTextureHostOGL : public TextureHost { + public: + MacIOSurfaceTextureHostOGL(TextureFlags aFlags, + const SurfaceDescriptorMacIOSurface& aDescriptor); + virtual ~MacIOSurfaceTextureHostOGL(); + + // MacIOSurfaceTextureSourceOGL doesn't own any GL texture + void DeallocateDeviceData() override {} + + void SetTextureSourceProvider(TextureSourceProvider* aProvider) override; + + bool Lock() override; + + gfx::SurfaceFormat GetFormat() const override; + gfx::SurfaceFormat GetReadFormat() const override; + + bool BindTextureSource(CompositableTextureSourceRef& aTexture) override { + aTexture = mTextureSource; + return !!aTexture; + } + + already_AddRefed GetAsSurface() override { + RefPtr surf = + CreateSourceSurfaceFromMacIOSurface(GetMacIOSurface()); + return surf->GetDataSurface(); + } + + gl::GLContext* gl() const; + + gfx::IntSize GetSize() const override; + +#ifdef MOZ_LAYERS_HAVE_LOG + const char* Name() override { return "MacIOSurfaceTextureHostOGL"; } +#endif + + MacIOSurfaceTextureHostOGL* AsMacIOSurfaceTextureHost() override { + return this; + } + + MacIOSurface* GetMacIOSurface() override { return mSurface; } + + void CreateRenderTexture( + const wr::ExternalImageId& aExternalImageId) override; + + uint32_t NumSubTextures() override; + + void PushResourceUpdates(wr::TransactionBuilder& aResources, + ResourceUpdateOp aOp, + const Range& aImageKeys, + const wr::ExternalImageId& aExtID) override; + + void PushDisplayItems(wr::DisplayListBuilder& aBuilder, + const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, wr::ImageRendering aFilter, + const Range& aImageKeys, + PushDisplayItemFlagSet aFlags) override; + + gfx::YUVColorSpace GetYUVColorSpace() const override; + gfx::ColorRange GetColorRange() const override; + + protected: + GLTextureSource* CreateTextureSourceForPlane(size_t aPlane); + + RefPtr mTextureSource; + RefPtr mSurface; +}; + +} // namespace layers +} // namespace mozilla + +#endif // MOZILLA_GFX_MACIOSURFACETEXTUREHOSTOGL_H diff --git a/gfx/layers/opengl/OGLShaderConfig.h b/gfx/layers/opengl/OGLShaderConfig.h new file mode 100644 index 0000000000..4db9c8c01c --- /dev/null +++ b/gfx/layers/opengl/OGLShaderConfig.h @@ -0,0 +1,267 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_OGLSHADERCONFIG_H +#define GFX_OGLSHADERCONFIG_H + +#include "gfxTypes.h" +#include "ImageTypes.h" +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/gfx/Matrix.h" // for Matrix4x4 +#include "mozilla/gfx/Rect.h" // for Rect +#include "mozilla/gfx/Types.h" +#include "nsDebug.h" // for NS_ASSERTION +#include "nsPoint.h" // for nsIntPoint +#include "nsTArray.h" // for nsTArray +#include "mozilla/layers/CompositorTypes.h" + +namespace mozilla { +namespace layers { + +enum ShaderFeatures { + ENABLE_RENDER_COLOR = 0x01, + ENABLE_TEXTURE_RECT = 0x02, + ENABLE_TEXTURE_EXTERNAL = 0x04, + ENABLE_TEXTURE_YCBCR = 0x08, + ENABLE_TEXTURE_NV12 = 0x10, + ENABLE_TEXTURE_COMPONENT_ALPHA = 0x20, + ENABLE_TEXTURE_NO_ALPHA = 0x40, + ENABLE_TEXTURE_RB_SWAP = 0x80, + ENABLE_OPACITY = 0x100, + ENABLE_BLUR = 0x200, + ENABLE_COLOR_MATRIX = 0x400, + ENABLE_MASK = 0x800, + ENABLE_NO_PREMUL_ALPHA = 0x1000, + ENABLE_DEAA = 0x2000, + ENABLE_DYNAMIC_GEOMETRY = 0x4000, + ENABLE_MASK_TEXTURE_RECT = 0x8000, + ENABLE_TEXTURE_NV12_GA_SWITCH = 0x10000, +}; + +class KnownUniform { + public: + // this needs to be kept in sync with strings in 'AddUniforms' + enum KnownUniformName { + NotAKnownUniform = -1, + + LayerTransform = 0, + LayerTransformInverse, + MaskTransform, + BackdropTransform, + LayerRects, + MatrixProj, + TextureTransform, + TextureRects, + RenderTargetOffset, + LayerOpacity, + Texture, + YTexture, + CbTexture, + CrTexture, + BlackTexture, + WhiteTexture, + MaskTexture, + BackdropTexture, + RenderColor, + TexCoordMultiplier, + CbCrTexCoordMultiplier, + MaskCoordMultiplier, + TexturePass2, + ColorMatrix, + ColorMatrixVector, + BlurRadius, + BlurOffset, + BlurAlpha, + BlurGaussianKernel, + SSEdges, + ViewportSize, + VisibleCenter, + YuvColorMatrix, + YuvOffsetVector, + + KnownUniformCount + }; + + KnownUniform() { + mName = NotAKnownUniform; + mNameString = nullptr; + mLocation = -1; + memset(&mValue, 0, sizeof(mValue)); + } + + bool UpdateUniform(int32_t i1) { + if (mLocation == -1) return false; + if (mValue.i1 != i1) { + mValue.i1 = i1; + return true; + } + return false; + } + + bool UpdateUniform(float f1) { + if (mLocation == -1) return false; + if (mValue.f1 != f1) { + mValue.f1 = f1; + return true; + } + return false; + } + + bool UpdateUniform(float f1, float f2) { + if (mLocation == -1) return false; + if (mValue.f16v[0] != f1 || mValue.f16v[1] != f2) { + mValue.f16v[0] = f1; + mValue.f16v[1] = f2; + return true; + } + return false; + } + + bool UpdateUniform(float f1, float f2, float f3, float f4) { + if (mLocation == -1) return false; + if (mValue.f16v[0] != f1 || mValue.f16v[1] != f2 || mValue.f16v[2] != f3 || + mValue.f16v[3] != f4) { + mValue.f16v[0] = f1; + mValue.f16v[1] = f2; + mValue.f16v[2] = f3; + mValue.f16v[3] = f4; + return true; + } + return false; + } + + bool UpdateUniform(int cnt, const float* fp) { + if (mLocation == -1) return false; + switch (cnt) { + case 1: + case 2: + case 3: + case 4: + case 9: + case 16: + if (memcmp(mValue.f16v, fp, sizeof(float) * cnt) != 0) { + memcpy(mValue.f16v, fp, sizeof(float) * cnt); + return true; + } + return false; + } + + MOZ_ASSERT_UNREACHABLE("cnt must be 1 2 3 4 9 or 16"); + return false; + } + + bool UpdateArrayUniform(int cnt, const float* fp) { + if (mLocation == -1) return false; + if (cnt > 16) { + return false; + } + + if (memcmp(mValue.f16v, fp, sizeof(float) * cnt) != 0) { + memcpy(mValue.f16v, fp, sizeof(float) * cnt); + return true; + } + return false; + } + + bool UpdateArrayUniform(int cnt, const gfx::Point3D* points) { + if (mLocation == -1) return false; + if (cnt > 4) { + return false; + } + + float fp[12]; + float* d = fp; + for (int i = 0; i < cnt; i++) { + // Note: Do not want to make assumptions about .x, .y, .z member packing. + // If gfx::Point3D is updated to make this guarantee, SIMD optimizations + // may be possible + *d++ = points[i].x; + *d++ = points[i].y; + *d++ = points[i].z; + } + + if (memcmp(mValue.f16v, fp, sizeof(float) * cnt * 3) != 0) { + memcpy(mValue.f16v, fp, sizeof(float) * cnt * 3); + return true; + } + return false; + } + + KnownUniformName mName; + const char* mNameString; + int32_t mLocation; + + union { + int i1; + float f1; + float f16v[16]; + } mValue; +}; + +class ShaderConfigOGL { + public: + ShaderConfigOGL() + : mFeatures(0), + mMultiplier(1), + mCompositionOp(gfx::CompositionOp::OP_OVER) {} + + void SetRenderColor(bool aEnabled); + void SetTextureTarget(GLenum aTarget); + void SetMaskTextureTarget(GLenum aTarget); + void SetRBSwap(bool aEnabled); + void SetNoAlpha(bool aEnabled); + void SetOpacity(bool aEnabled); + void SetYCbCr(bool aEnabled); + void SetNV12(bool aEnabled); + void SetComponentAlpha(bool aEnabled); + void SetColorMatrix(bool aEnabled); + void SetBlur(bool aEnabled); + void SetMask(bool aEnabled); + void SetDEAA(bool aEnabled); + void SetCompositionOp(gfx::CompositionOp aOp); + void SetNoPremultipliedAlpha(); + void SetDynamicGeometry(bool aEnabled); + void SetColorMultiplier(uint32_t aMultiplier); + + bool operator<(const ShaderConfigOGL& other) const { + return mFeatures < other.mFeatures || + (mFeatures == other.mFeatures && + (int)mCompositionOp < (int)other.mCompositionOp) || + (mFeatures == other.mFeatures && + (int)mCompositionOp == (int)other.mCompositionOp && + mMultiplier < other.mMultiplier); + } + + public: + void SetFeature(int aBitmask, bool aState) { + if (aState) + mFeatures |= aBitmask; + else + mFeatures &= (~aBitmask); + } + + int mFeatures; + uint32_t mMultiplier; + gfx::CompositionOp mCompositionOp; +}; + +static inline ShaderConfigOGL ShaderConfigFromTargetAndFormat( + GLenum aTarget, gfx::SurfaceFormat aFormat) { + ShaderConfigOGL config; + config.SetTextureTarget(aTarget); + config.SetRBSwap(aFormat == gfx::SurfaceFormat::B8G8R8A8 || + aFormat == gfx::SurfaceFormat::B8G8R8X8); + config.SetNoAlpha(aFormat == gfx::SurfaceFormat::B8G8R8X8 || + aFormat == gfx::SurfaceFormat::R8G8B8X8 || + aFormat == gfx::SurfaceFormat::R5G6B5_UINT16); + return config; +} + +} // namespace layers +} // namespace mozilla + +#endif // GFX_OGLSHADERCONFIG_H diff --git a/gfx/layers/opengl/OGLShaderProgram.cpp b/gfx/layers/opengl/OGLShaderProgram.cpp new file mode 100644 index 0000000000..2bf1f9541d --- /dev/null +++ b/gfx/layers/opengl/OGLShaderProgram.cpp @@ -0,0 +1,1044 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "OGLShaderProgram.h" + +#include // for uint32_t + +#include // for std::ostringstream + +#include "GLContext.h" +#include "Layers.h" +#include "gfxEnv.h" +#include "gfxRect.h" // for gfxRect +#include "gfxUtils.h" +#include "mozilla/DebugOnly.h" // for DebugOnly +#include "mozilla/layers/Compositor.h" // for BlendOpIsMixBlendMode +#include "nsAString.h" +#include "nsString.h" // for nsAutoCString + +namespace mozilla { +namespace layers { + +using std::endl; + +#define GAUSSIAN_KERNEL_HALF_WIDTH 11 +#define GAUSSIAN_KERNEL_STEP 0.2 + +static void AddUniforms(ProgramProfileOGL& aProfile) { + // This needs to be kept in sync with the KnownUniformName enum + static const char* sKnownUniformNames[] = {"uLayerTransform", + "uLayerTransformInverse", + "uMaskTransform", + "uBackdropTransform", + "uLayerRects", + "uMatrixProj", + "uTextureTransform", + "uTextureRects", + "uRenderTargetOffset", + "uLayerOpacity", + "uTexture", + "uYTexture", + "uCbTexture", + "uCrTexture", + "uBlackTexture", + "uWhiteTexture", + "uMaskTexture", + "uBackdropTexture", + "uRenderColor", + "uTexCoordMultiplier", + "uCbCrTexCoordMultiplier", + "uMaskCoordMultiplier", + "uTexturePass2", + "uColorMatrix", + "uColorMatrixVector", + "uBlurRadius", + "uBlurOffset", + "uBlurAlpha", + "uBlurGaussianKernel", + "uSSEdges", + "uViewportSize", + "uVisibleCenter", + "uYuvColorMatrix", + "uYuvOffsetVector", + nullptr}; + + for (int i = 0; sKnownUniformNames[i] != nullptr; ++i) { + aProfile.mUniforms[i].mNameString = sKnownUniformNames[i]; + aProfile.mUniforms[i].mName = (KnownUniform::KnownUniformName)i; + } +} + +void ShaderConfigOGL::SetRenderColor(bool aEnabled) { + SetFeature(ENABLE_RENDER_COLOR, aEnabled); +} + +void ShaderConfigOGL::SetTextureTarget(GLenum aTarget) { + SetFeature(ENABLE_TEXTURE_EXTERNAL | ENABLE_TEXTURE_RECT, false); + switch (aTarget) { + case LOCAL_GL_TEXTURE_EXTERNAL: + SetFeature(ENABLE_TEXTURE_EXTERNAL, true); + break; + case LOCAL_GL_TEXTURE_RECTANGLE_ARB: + SetFeature(ENABLE_TEXTURE_RECT, true); + break; + } +} + +void ShaderConfigOGL::SetMaskTextureTarget(GLenum aTarget) { + if (aTarget == LOCAL_GL_TEXTURE_RECTANGLE_ARB) { + SetFeature(ENABLE_MASK_TEXTURE_RECT, true); + } else { + MOZ_ASSERT(aTarget == LOCAL_GL_TEXTURE_2D); + SetFeature(ENABLE_MASK_TEXTURE_RECT, false); + } +} + +void ShaderConfigOGL::SetRBSwap(bool aEnabled) { + SetFeature(ENABLE_TEXTURE_RB_SWAP, aEnabled); +} + +void ShaderConfigOGL::SetNoAlpha(bool aEnabled) { + SetFeature(ENABLE_TEXTURE_NO_ALPHA, aEnabled); +} + +void ShaderConfigOGL::SetOpacity(bool aEnabled) { + SetFeature(ENABLE_OPACITY, aEnabled); +} + +void ShaderConfigOGL::SetYCbCr(bool aEnabled) { + SetFeature(ENABLE_TEXTURE_YCBCR, aEnabled); + MOZ_ASSERT(!(mFeatures & ENABLE_TEXTURE_NV12)); +} + +void ShaderConfigOGL::SetColorMultiplier(uint32_t aMultiplier) { + MOZ_ASSERT(mFeatures & ENABLE_TEXTURE_YCBCR, + "Multiplier only supported with YCbCr!"); + mMultiplier = aMultiplier; +} + +void ShaderConfigOGL::SetNV12(bool aEnabled) { + SetFeature(ENABLE_TEXTURE_NV12, aEnabled); + MOZ_ASSERT(!(mFeatures & ENABLE_TEXTURE_YCBCR)); +#ifdef MOZ_WAYLAND + SetFeature(ENABLE_TEXTURE_NV12_GA_SWITCH, aEnabled); +#endif +} + +void ShaderConfigOGL::SetComponentAlpha(bool aEnabled) { + SetFeature(ENABLE_TEXTURE_COMPONENT_ALPHA, aEnabled); +} + +void ShaderConfigOGL::SetColorMatrix(bool aEnabled) { + SetFeature(ENABLE_COLOR_MATRIX, aEnabled); +} + +void ShaderConfigOGL::SetBlur(bool aEnabled) { + SetFeature(ENABLE_BLUR, aEnabled); +} + +void ShaderConfigOGL::SetMask(bool aEnabled) { + SetFeature(ENABLE_MASK, aEnabled); +} + +void ShaderConfigOGL::SetNoPremultipliedAlpha() { + SetFeature(ENABLE_NO_PREMUL_ALPHA, true); +} + +void ShaderConfigOGL::SetDEAA(bool aEnabled) { + SetFeature(ENABLE_DEAA, aEnabled); +} + +void ShaderConfigOGL::SetCompositionOp(gfx::CompositionOp aOp) { + mCompositionOp = aOp; +} + +void ShaderConfigOGL::SetDynamicGeometry(bool aEnabled) { + SetFeature(ENABLE_DYNAMIC_GEOMETRY, aEnabled); +} + +/* static */ +ProgramProfileOGL ProgramProfileOGL::GetProfileFor(ShaderConfigOGL aConfig) { + ProgramProfileOGL result; + std::ostringstream fs, vs; + + AddUniforms(result); + + gfx::CompositionOp blendOp = aConfig.mCompositionOp; + + vs << "#ifdef GL_ES" << endl; + vs << "#define EDGE_PRECISION mediump" << endl; + vs << "#else" << endl; + vs << "#define EDGE_PRECISION" << endl; + vs << "#endif" << endl; + vs << "uniform mat4 uMatrixProj;" << endl; + vs << "uniform vec4 uLayerRects[4];" << endl; + vs << "uniform mat4 uLayerTransform;" << endl; + if (aConfig.mFeatures & ENABLE_DEAA) { + vs << "uniform mat4 uLayerTransformInverse;" << endl; + vs << "uniform EDGE_PRECISION vec3 uSSEdges[4];" << endl; + vs << "uniform vec2 uVisibleCenter;" << endl; + vs << "uniform vec2 uViewportSize;" << endl; + } + vs << "uniform vec2 uRenderTargetOffset;" << endl; + + if (!(aConfig.mFeatures & ENABLE_DYNAMIC_GEOMETRY)) { + vs << "attribute vec4 aCoord;" << endl; + } else { + vs << "attribute vec2 aCoord;" << endl; + } + + result.mAttributes.AppendElement(std::pair{"aCoord", 0}); + + if (!(aConfig.mFeatures & ENABLE_RENDER_COLOR)) { + vs << "uniform mat4 uTextureTransform;" << endl; + vs << "uniform vec4 uTextureRects[4];" << endl; + vs << "varying vec2 vTexCoord;" << endl; + + if (aConfig.mFeatures & ENABLE_DYNAMIC_GEOMETRY) { + vs << "attribute vec2 aTexCoord;" << endl; + result.mAttributes.AppendElement( + std::pair{"aTexCoord", 1}); + } + } + + if (BlendOpIsMixBlendMode(blendOp)) { + vs << "uniform mat4 uBackdropTransform;" << endl; + vs << "varying vec2 vBackdropCoord;" << endl; + } + + if (aConfig.mFeatures & ENABLE_MASK) { + vs << "uniform mat4 uMaskTransform;" << endl; + vs << "varying vec3 vMaskCoord;" << endl; + } + + vs << "void main() {" << endl; + + if (aConfig.mFeatures & ENABLE_DYNAMIC_GEOMETRY) { + vs << " vec4 finalPosition = vec4(aCoord.xy, 0.0, 1.0);" << endl; + } else { + vs << " int vertexID = int(aCoord.w);" << endl; + vs << " vec4 layerRect = uLayerRects[vertexID];" << endl; + vs << " vec4 finalPosition = vec4(aCoord.xy * layerRect.zw + " + "layerRect.xy, 0.0, 1.0);" + << endl; + } + + vs << " finalPosition = uLayerTransform * finalPosition;" << endl; + + if (aConfig.mFeatures & ENABLE_DEAA) { + // XXX kip - The DEAA shader could be made simpler if we switch to + // using dynamic vertex buffers instead of sending everything + // in through uniforms. This would enable passing information + // about how to dilate each vertex explicitly and eliminate the + // need to extrapolate this with the sub-pixel coverage + // calculation in the vertex shader. + + // Calculate the screen space position of this vertex, in screen pixels + vs << " vec4 ssPos = finalPosition;" << endl; + vs << " ssPos.xy -= uRenderTargetOffset * finalPosition.w;" << endl; + vs << " ssPos = uMatrixProj * ssPos;" << endl; + vs << " ssPos.xy = ((ssPos.xy/ssPos.w)*0.5+0.5)*uViewportSize;" << endl; + + if (aConfig.mFeatures & ENABLE_MASK || + !(aConfig.mFeatures & ENABLE_RENDER_COLOR)) { + vs << " vec4 coordAdjusted;" << endl; + vs << " coordAdjusted.xy = aCoord.xy;" << endl; + } + + // It is necessary to dilate edges away from uVisibleCenter to ensure that + // fragments with less than 50% sub-pixel coverage will be shaded. + // This offset is applied when the sub-pixel coverage of the vertex is + // less than 100%. Expanding by 0.5 pixels in screen space is sufficient + // to include these pixels. + vs << " if (dot(uSSEdges[0], vec3(ssPos.xy, 1.0)) < 1.5 ||" << endl; + vs << " dot(uSSEdges[1], vec3(ssPos.xy, 1.0)) < 1.5 ||" << endl; + vs << " dot(uSSEdges[2], vec3(ssPos.xy, 1.0)) < 1.5 ||" << endl; + vs << " dot(uSSEdges[3], vec3(ssPos.xy, 1.0)) < 1.5) {" << endl; + // If the shader reaches this branch, then this vertex is on the edge of + // the layer's visible rect and should be dilated away from the center of + // the visible rect. We don't want to hit this for inner facing + // edges between tiles, as the pixels may be covered twice without clipping + // against uSSEdges. If all edges were dilated, it would result in + // artifacts visible within semi-transparent layers with multiple tiles. + vs << " vec4 visibleCenter = uLayerTransform * vec4(uVisibleCenter, " + "0.0, 1.0);" + << endl; + vs << " vec2 dilateDir = finalPosition.xy / finalPosition.w - " + "visibleCenter.xy / visibleCenter.w;" + << endl; + vs << " vec2 offset = sign(dilateDir) * 0.5;" << endl; + vs << " finalPosition.xy += offset * finalPosition.w;" << endl; + if (!(aConfig.mFeatures & ENABLE_RENDER_COLOR)) { + // We must adjust the texture coordinates to compensate for the dilation + vs << " coordAdjusted = uLayerTransformInverse * finalPosition;" + << endl; + vs << " coordAdjusted /= coordAdjusted.w;" << endl; + + if (!(aConfig.mFeatures & ENABLE_DYNAMIC_GEOMETRY)) { + vs << " coordAdjusted.xy -= layerRect.xy;" << endl; + vs << " coordAdjusted.xy /= layerRect.zw;" << endl; + } + } + vs << " }" << endl; + + if (!(aConfig.mFeatures & ENABLE_RENDER_COLOR)) { + if (aConfig.mFeatures & ENABLE_DYNAMIC_GEOMETRY) { + vs << " vTexCoord = (uTextureTransform * vec4(aTexCoord, 0.0, " + "1.0)).xy;" + << endl; + } else { + vs << " vec4 textureRect = uTextureRects[vertexID];" << endl; + vs << " vec2 texCoord = coordAdjusted.xy * textureRect.zw + " + "textureRect.xy;" + << endl; + vs << " vTexCoord = (uTextureTransform * vec4(texCoord, 0.0, 1.0)).xy;" + << endl; + } + } + } else if (!(aConfig.mFeatures & ENABLE_RENDER_COLOR)) { + if (aConfig.mFeatures & ENABLE_DYNAMIC_GEOMETRY) { + vs << " vTexCoord = (uTextureTransform * vec4(aTexCoord, 0.0, 1.0)).xy;" + << endl; + } else { + vs << " vec4 textureRect = uTextureRects[vertexID];" << endl; + vs << " vec2 texCoord = aCoord.xy * textureRect.zw + textureRect.xy;" + << endl; + vs << " vTexCoord = (uTextureTransform * vec4(texCoord, 0.0, 1.0)).xy;" + << endl; + } + } + + if (aConfig.mFeatures & ENABLE_MASK) { + vs << " vMaskCoord.xy = (uMaskTransform * (finalPosition / " + "finalPosition.w)).xy;" + << endl; + // correct for perspective correct interpolation, see comment in D3D11 + // shader + vs << " vMaskCoord.z = 1.0;" << endl; + vs << " vMaskCoord *= finalPosition.w;" << endl; + } + vs << " finalPosition.xy -= uRenderTargetOffset * finalPosition.w;" << endl; + vs << " finalPosition = uMatrixProj * finalPosition;" << endl; + if (BlendOpIsMixBlendMode(blendOp)) { + // Translate from clip space (-1, 1) to (0..1), apply the backdrop + // transform, then invert the y-axis. + vs << " vBackdropCoord.x = (finalPosition.x + 1.0) / 2.0;" << endl; + vs << " vBackdropCoord.y = 1.0 - (finalPosition.y + 1.0) / 2.0;" << endl; + vs << " vBackdropCoord = (uBackdropTransform * vec4(vBackdropCoord.xy, " + "0.0, 1.0)).xy;" + << endl; + vs << " vBackdropCoord.y = 1.0 - vBackdropCoord.y;" << endl; + } + vs << " gl_Position = finalPosition;" << endl; + vs << "}" << endl; + + if (aConfig.mFeatures & ENABLE_TEXTURE_RECT) { + fs << "#extension GL_ARB_texture_rectangle : require" << endl; + } + if (aConfig.mFeatures & ENABLE_TEXTURE_EXTERNAL) { + fs << "#extension GL_OES_EGL_image_external : require" << endl; + } + fs << "#ifdef GL_ES" << endl; + fs << "precision mediump float;" << endl; + fs << "#define COLOR_PRECISION lowp" << endl; + fs << "#define EDGE_PRECISION mediump" << endl; + fs << "#else" << endl; + fs << "#define COLOR_PRECISION" << endl; + fs << "#define EDGE_PRECISION" << endl; + fs << "#endif" << endl; + if (aConfig.mFeatures & ENABLE_RENDER_COLOR) { + fs << "uniform COLOR_PRECISION vec4 uRenderColor;" << endl; + } else { + // for tiling, texcoord can be greater than the lowfp range + fs << "varying vec2 vTexCoord;" << endl; + if (aConfig.mFeatures & ENABLE_BLUR) { + fs << "uniform bool uBlurAlpha;" << endl; + fs << "uniform vec2 uBlurRadius;" << endl; + fs << "uniform vec2 uBlurOffset;" << endl; + fs << "uniform float uBlurGaussianKernel[" << GAUSSIAN_KERNEL_HALF_WIDTH + << "];" << endl; + } + if (aConfig.mFeatures & ENABLE_COLOR_MATRIX) { + fs << "uniform mat4 uColorMatrix;" << endl; + fs << "uniform vec4 uColorMatrixVector;" << endl; + } + if (aConfig.mFeatures & ENABLE_OPACITY) { + fs << "uniform COLOR_PRECISION float uLayerOpacity;" << endl; + } + } + if (BlendOpIsMixBlendMode(blendOp)) { + fs << "varying vec2 vBackdropCoord;" << endl; + } + + const char* sampler2D = "sampler2D"; + const char* texture2D = "texture2D"; + + if (aConfig.mFeatures & ENABLE_TEXTURE_RECT) { + fs << "uniform vec2 uTexCoordMultiplier;" << endl; + if (aConfig.mFeatures & ENABLE_TEXTURE_YCBCR || + aConfig.mFeatures & ENABLE_TEXTURE_NV12) { + fs << "uniform vec2 uCbCrTexCoordMultiplier;" << endl; + } + sampler2D = "sampler2DRect"; + texture2D = "texture2DRect"; + } + + const char* maskSampler2D = "sampler2D"; + const char* maskTexture2D = "texture2D"; + + if (aConfig.mFeatures & ENABLE_MASK && + aConfig.mFeatures & ENABLE_MASK_TEXTURE_RECT) { + fs << "uniform vec2 uMaskCoordMultiplier;" << endl; + maskSampler2D = "sampler2DRect"; + maskTexture2D = "texture2DRect"; + } + + if (aConfig.mFeatures & ENABLE_TEXTURE_EXTERNAL) { + sampler2D = "samplerExternalOES"; + } + + if (aConfig.mFeatures & ENABLE_TEXTURE_YCBCR) { + fs << "uniform " << sampler2D << " uYTexture;" << endl; + fs << "uniform " << sampler2D << " uCbTexture;" << endl; + fs << "uniform " << sampler2D << " uCrTexture;" << endl; + fs << "uniform mat3 uYuvColorMatrix;" << endl; + fs << "uniform vec3 uYuvOffsetVector;" << endl; + } else if (aConfig.mFeatures & ENABLE_TEXTURE_NV12) { + fs << "uniform " << sampler2D << " uYTexture;" << endl; + fs << "uniform " << sampler2D << " uCbTexture;" << endl; + fs << "uniform mat3 uYuvColorMatrix;" << endl; + fs << "uniform vec3 uYuvOffsetVector;" << endl; + } else if (aConfig.mFeatures & ENABLE_TEXTURE_COMPONENT_ALPHA) { + fs << "uniform " << sampler2D << " uBlackTexture;" << endl; + fs << "uniform " << sampler2D << " uWhiteTexture;" << endl; + fs << "uniform bool uTexturePass2;" << endl; + } else { + fs << "uniform " << sampler2D << " uTexture;" << endl; + } + + if (BlendOpIsMixBlendMode(blendOp)) { + // Component alpha should be flattened away inside blend containers. + MOZ_ASSERT(!(aConfig.mFeatures & ENABLE_TEXTURE_COMPONENT_ALPHA)); + + fs << "uniform sampler2D uBackdropTexture;" << endl; + } + + if (aConfig.mFeatures & ENABLE_MASK) { + fs << "varying vec3 vMaskCoord;" << endl; + fs << "uniform " << maskSampler2D << " uMaskTexture;" << endl; + } + + if (aConfig.mFeatures & ENABLE_DEAA) { + fs << "uniform EDGE_PRECISION vec3 uSSEdges[4];" << endl; + } + + if (BlendOpIsMixBlendMode(blendOp)) { + BuildMixBlender(aConfig, fs); + } + + if (!(aConfig.mFeatures & ENABLE_RENDER_COLOR)) { + fs << "vec4 sample(vec2 coord) {" << endl; + fs << " vec4 color;" << endl; + if (aConfig.mFeatures & ENABLE_TEXTURE_YCBCR || + aConfig.mFeatures & ENABLE_TEXTURE_NV12) { + if (aConfig.mFeatures & ENABLE_TEXTURE_YCBCR) { + if (aConfig.mFeatures & ENABLE_TEXTURE_RECT) { + fs << " COLOR_PRECISION float y = " << texture2D + << "(uYTexture, coord * uTexCoordMultiplier).r;" << endl; + fs << " COLOR_PRECISION float cb = " << texture2D + << "(uCbTexture, coord * uCbCrTexCoordMultiplier).r;" << endl; + fs << " COLOR_PRECISION float cr = " << texture2D + << "(uCrTexture, coord * uCbCrTexCoordMultiplier).r;" << endl; + } else { + fs << " COLOR_PRECISION float y = " << texture2D + << "(uYTexture, coord).r;" << endl; + fs << " COLOR_PRECISION float cb = " << texture2D + << "(uCbTexture, coord).r;" << endl; + fs << " COLOR_PRECISION float cr = " << texture2D + << "(uCrTexture, coord).r;" << endl; + } + } else { + if (aConfig.mFeatures & ENABLE_TEXTURE_RECT) { + fs << " COLOR_PRECISION float y = " << texture2D + << "(uYTexture, coord * uTexCoordMultiplier).r;" << endl; + fs << " COLOR_PRECISION float cb = " << texture2D + << "(uCbTexture, coord * uCbCrTexCoordMultiplier).r;" << endl; + if (aConfig.mFeatures & ENABLE_TEXTURE_NV12_GA_SWITCH) { + fs << " COLOR_PRECISION float cr = " << texture2D + << "(uCbTexture, coord * uCbCrTexCoordMultiplier).g;" << endl; + } else { + fs << " COLOR_PRECISION float cr = " << texture2D + << "(uCbTexture, coord * uCbCrTexCoordMultiplier).a;" << endl; + } + } else { + fs << " COLOR_PRECISION float y = " << texture2D + << "(uYTexture, coord).r;" << endl; + fs << " COLOR_PRECISION float cb = " << texture2D + << "(uCbTexture, coord).r;" << endl; + if (aConfig.mFeatures & ENABLE_TEXTURE_NV12_GA_SWITCH) { + fs << " COLOR_PRECISION float cr = " << texture2D + << "(uCbTexture, coord).g;" << endl; + } else { + fs << " COLOR_PRECISION float cr = " << texture2D + << "(uCbTexture, coord).a;" << endl; + } + } + } + fs << " vec3 yuv = vec3(y, cb, cr);" << endl; + if (aConfig.mMultiplier != 1) { + fs << " yuv *= " << aConfig.mMultiplier << ".0;" << endl; + } + fs << " yuv -= uYuvOffsetVector;" << endl; + fs << " color.rgb = uYuvColorMatrix * yuv;" << endl; + fs << " color.a = 1.0;" << endl; + } else if (aConfig.mFeatures & ENABLE_TEXTURE_COMPONENT_ALPHA) { + if (aConfig.mFeatures & ENABLE_TEXTURE_RECT) { + fs << " COLOR_PRECISION vec3 onBlack = " << texture2D + << "(uBlackTexture, coord * uTexCoordMultiplier).rgb;" << endl; + fs << " COLOR_PRECISION vec3 onWhite = " << texture2D + << "(uWhiteTexture, coord * uTexCoordMultiplier).rgb;" << endl; + } else { + fs << " COLOR_PRECISION vec3 onBlack = " << texture2D + << "(uBlackTexture, coord).rgb;" << endl; + fs << " COLOR_PRECISION vec3 onWhite = " << texture2D + << "(uWhiteTexture, coord).rgb;" << endl; + } + fs << " COLOR_PRECISION vec4 alphas = (1.0 - onWhite + onBlack).rgbg;" + << endl; + fs << " if (uTexturePass2)" << endl; + fs << " color = vec4(onBlack, alphas.a);" << endl; + fs << " else" << endl; + fs << " color = alphas;" << endl; + } else { + if (aConfig.mFeatures & ENABLE_TEXTURE_RECT) { + fs << " color = " << texture2D + << "(uTexture, coord * uTexCoordMultiplier);" << endl; + } else { + fs << " color = " << texture2D << "(uTexture, coord);" << endl; + } + } + if (aConfig.mFeatures & ENABLE_TEXTURE_RB_SWAP) { + fs << " color = color.bgra;" << endl; + } + if (aConfig.mFeatures & ENABLE_TEXTURE_NO_ALPHA) { + fs << " color = vec4(color.rgb, 1.0);" << endl; + } + fs << " return color;" << endl; + fs << "}" << endl; + if (aConfig.mFeatures & ENABLE_BLUR) { + fs << "vec4 sampleAtRadius(vec2 coord, float radius) {" << endl; + fs << " coord += uBlurOffset;" << endl; + fs << " coord += radius * uBlurRadius;" << endl; + fs << " if (coord.x < 0. || coord.y < 0. || coord.x > 1. || coord.y > " + "1.)" + << endl; + fs << " return vec4(0, 0, 0, 0);" << endl; + fs << " return sample(coord);" << endl; + fs << "}" << endl; + fs << "vec4 blur(vec4 color, vec2 coord) {" << endl; + fs << " vec4 total = color * uBlurGaussianKernel[0];" << endl; + fs << " for (int i = 1; i < " << GAUSSIAN_KERNEL_HALF_WIDTH << "; ++i) {" + << endl; + fs << " float r = float(i) * " << GAUSSIAN_KERNEL_STEP << ";" << endl; + fs << " float k = uBlurGaussianKernel[i];" << endl; + fs << " total += sampleAtRadius(coord, r) * k;" << endl; + fs << " total += sampleAtRadius(coord, -r) * k;" << endl; + fs << " }" << endl; + fs << " if (uBlurAlpha) {" << endl; + fs << " color *= total.a;" << endl; + fs << " } else {" << endl; + fs << " color = total;" << endl; + fs << " }" << endl; + fs << " return color;" << endl; + fs << "}" << endl; + } + } + fs << "void main() {" << endl; + if (aConfig.mFeatures & ENABLE_RENDER_COLOR) { + fs << " vec4 color = uRenderColor;" << endl; + } else { + fs << " vec4 color = sample(vTexCoord);" << endl; + if (aConfig.mFeatures & ENABLE_BLUR) { + fs << " color = blur(color, vTexCoord);" << endl; + } + if (aConfig.mFeatures & ENABLE_COLOR_MATRIX) { + fs << " color = uColorMatrix * vec4(color.rgb / color.a, color.a) + " + "uColorMatrixVector;" + << endl; + fs << " color.rgb *= color.a;" << endl; + } + if (aConfig.mFeatures & ENABLE_OPACITY) { + fs << " color *= uLayerOpacity;" << endl; + } + } + if (aConfig.mFeatures & ENABLE_DEAA) { + // Calculate the sub-pixel coverage of the pixel and modulate its opacity + // by that amount to perform DEAA. + fs << " vec3 ssPos = vec3(gl_FragCoord.xy, 1.0);" << endl; + fs << " float deaaCoverage = clamp(dot(uSSEdges[0], ssPos), 0.0, 1.0);" + << endl; + fs << " deaaCoverage *= clamp(dot(uSSEdges[1], ssPos), 0.0, 1.0);" << endl; + fs << " deaaCoverage *= clamp(dot(uSSEdges[2], ssPos), 0.0, 1.0);" << endl; + fs << " deaaCoverage *= clamp(dot(uSSEdges[3], ssPos), 0.0, 1.0);" << endl; + fs << " color *= deaaCoverage;" << endl; + } + if (BlendOpIsMixBlendMode(blendOp)) { + fs << " vec4 backdrop = texture2D(uBackdropTexture, vBackdropCoord);" + << endl; + fs << " color = mixAndBlend(backdrop, color);" << endl; + } + if (aConfig.mFeatures & ENABLE_MASK) { + fs << " vec2 maskCoords = vMaskCoord.xy / vMaskCoord.z;" << endl; + if (aConfig.mFeatures & ENABLE_MASK_TEXTURE_RECT) { + fs << " COLOR_PRECISION float mask = " << maskTexture2D + << "(uMaskTexture, maskCoords * uMaskCoordMultiplier).r;" << endl; + } else { + fs << " COLOR_PRECISION float mask = " << maskTexture2D + << "(uMaskTexture, maskCoords).r;" << endl; + } + fs << " color *= mask;" << endl; + } else { + fs << " COLOR_PRECISION float mask = 1.0;" << endl; + fs << " color *= mask;" << endl; + } + fs << " gl_FragColor = color;" << endl; + fs << "}" << endl; + + result.mVertexShaderString = vs.str(); + result.mFragmentShaderString = fs.str(); + + if (aConfig.mFeatures & ENABLE_RENDER_COLOR) { + result.mTextureCount = 0; + } else { + if (aConfig.mFeatures & ENABLE_TEXTURE_YCBCR) { + result.mTextureCount = 3; + } else if (aConfig.mFeatures & ENABLE_TEXTURE_NV12) { + result.mTextureCount = 2; + } else if (aConfig.mFeatures & ENABLE_TEXTURE_COMPONENT_ALPHA) { + result.mTextureCount = 2; + } else { + result.mTextureCount = 1; + } + } + if (aConfig.mFeatures & ENABLE_MASK) { + result.mTextureCount = 1; + } + if (BlendOpIsMixBlendMode(blendOp)) { + result.mTextureCount += 1; + } + + return result; +} + +void ProgramProfileOGL::BuildMixBlender(const ShaderConfigOGL& aConfig, + std::ostringstream& fs) { + // From the "Compositing and Blending Level 1" spec. + // Generate helper functions first. + switch (aConfig.mCompositionOp) { + case gfx::CompositionOp::OP_OVERLAY: + case gfx::CompositionOp::OP_HARD_LIGHT: + // Note: we substitute (2*src-1) into the screen formula below. + fs << "float hardlight(float dest, float src) {" << endl; + fs << " if (src <= 0.5) {" << endl; + fs << " return dest * (2.0 * src);" << endl; + fs << " } else {" << endl; + fs << " return 2.0*dest + 2.0*src - 1.0 - 2.0*dest*src;" << endl; + fs << " }" << endl; + fs << "}" << endl; + break; + case gfx::CompositionOp::OP_COLOR_DODGE: + fs << "float dodge(float dest, float src) {" << endl; + fs << " if (dest == 0.0) {" << endl; + fs << " return 0.0;" << endl; + fs << " } else if (src == 1.0) {" << endl; + fs << " return 1.0;" << endl; + fs << " } else {" << endl; + fs << " return min(1.0, dest / (1.0 - src));" << endl; + fs << " }" << endl; + fs << "}" << endl; + break; + case gfx::CompositionOp::OP_COLOR_BURN: + fs << "float burn(float dest, float src) {" << endl; + fs << " if (dest == 1.0) {" << endl; + fs << " return 1.0;" << endl; + fs << " } else if (src == 0.0) {" << endl; + fs << " return 0.0;" << endl; + fs << " } else {" << endl; + fs << " return 1.0 - min(1.0, (1.0 - dest) / src);" << endl; + fs << " }" << endl; + fs << "}" << endl; + break; + case gfx::CompositionOp::OP_SOFT_LIGHT: + fs << "float darken(float dest) {" << endl; + fs << " if (dest <= 0.25) {" << endl; + fs << " return ((16.0 * dest - 12.0) * dest + 4.0) * dest;" << endl; + fs << " } else {" << endl; + fs << " return sqrt(dest);" << endl; + fs << " }" << endl; + fs << "}" << endl; + fs << "float softlight(float dest, float src) {" << endl; + fs << " if (src <= 0.5) {" << endl; + fs << " return dest - (1.0 - 2.0 * src) * dest * (1.0 - dest);" + << endl; + fs << " } else {" << endl; + fs << " return dest + (2.0 * src - 1.0) * (darken(dest) - dest);" + << endl; + fs << " }" << endl; + fs << "}" << endl; + break; + case gfx::CompositionOp::OP_HUE: + case gfx::CompositionOp::OP_SATURATION: + case gfx::CompositionOp::OP_COLOR: + case gfx::CompositionOp::OP_LUMINOSITY: + fs << "float Lum(vec3 c) {" << endl; + fs << " return dot(vec3(0.3, 0.59, 0.11), c);" << endl; + fs << "}" << endl; + fs << "vec3 ClipColor(vec3 c) {" << endl; + fs << " float L = Lum(c);" << endl; + fs << " float n = min(min(c.r, c.g), c.b);" << endl; + fs << " float x = max(max(c.r, c.g), c.b);" << endl; + fs << " if (n < 0.0) {" << endl; + fs << " c = L + (((c - L) * L) / (L - n));" << endl; + fs << " }" << endl; + fs << " if (x > 1.0) {" << endl; + fs << " c = L + (((c - L) * (1.0 - L)) / (x - L));" << endl; + fs << " }" << endl; + fs << " return c;" << endl; + fs << "}" << endl; + fs << "vec3 SetLum(vec3 c, float L) {" << endl; + fs << " float d = L - Lum(c);" << endl; + fs << " return ClipColor(vec3(" << endl; + fs << " c.r + d," << endl; + fs << " c.g + d," << endl; + fs << " c.b + d));" << endl; + fs << "}" << endl; + fs << "float Sat(vec3 c) {" << endl; + fs << " return max(max(c.r, c.g), c.b) - min(min(c.r, c.g), c.b);" + << endl; + fs << "}" << endl; + + // To use this helper, re-arrange rgb such that r=min, g=mid, and b=max. + fs << "vec3 SetSatInner(vec3 c, float s) {" << endl; + fs << " if (c.b > c.r) {" << endl; + fs << " c.g = (((c.g - c.r) * s) / (c.b - c.r));" << endl; + fs << " c.b = s;" << endl; + fs << " } else {" << endl; + fs << " c.gb = vec2(0.0, 0.0);" << endl; + fs << " }" << endl; + fs << " return vec3(0.0, c.gb);" << endl; + fs << "}" << endl; + + fs << "vec3 SetSat(vec3 c, float s) {" << endl; + fs << " if (c.r <= c.g) {" << endl; + fs << " if (c.g <= c.b) {" << endl; + fs << " c.rgb = SetSatInner(c.rgb, s);" << endl; + fs << " } else if (c.r <= c.b) {" << endl; + fs << " c.rbg = SetSatInner(c.rbg, s);" << endl; + fs << " } else {" << endl; + fs << " c.brg = SetSatInner(c.brg, s);" << endl; + fs << " }" << endl; + fs << " } else if (c.r <= c.b) {" << endl; + fs << " c.grb = SetSatInner(c.grb, s);" << endl; + fs << " } else if (c.g <= c.b) {" << endl; + fs << " c.gbr = SetSatInner(c.gbr, s);" << endl; + fs << " } else {" << endl; + fs << " c.bgr = SetSatInner(c.bgr, s);" << endl; + fs << " }" << endl; + fs << " return c;" << endl; + fs << "}" << endl; + break; + default: + break; + } + + // Generate the main blending helper. + fs << "vec3 blend(vec3 dest, vec3 src) {" << endl; + switch (aConfig.mCompositionOp) { + case gfx::CompositionOp::OP_MULTIPLY: + fs << " return dest * src;" << endl; + break; + case gfx::CompositionOp::OP_SCREEN: + fs << " return dest + src - (dest * src);" << endl; + break; + case gfx::CompositionOp::OP_OVERLAY: + fs << " return vec3(" << endl; + fs << " hardlight(src.r, dest.r)," << endl; + fs << " hardlight(src.g, dest.g)," << endl; + fs << " hardlight(src.b, dest.b));" << endl; + break; + case gfx::CompositionOp::OP_DARKEN: + fs << " return min(dest, src);" << endl; + break; + case gfx::CompositionOp::OP_LIGHTEN: + fs << " return max(dest, src);" << endl; + break; + case gfx::CompositionOp::OP_COLOR_DODGE: + fs << " return vec3(" << endl; + fs << " dodge(dest.r, src.r)," << endl; + fs << " dodge(dest.g, src.g)," << endl; + fs << " dodge(dest.b, src.b));" << endl; + break; + case gfx::CompositionOp::OP_COLOR_BURN: + fs << " return vec3(" << endl; + fs << " burn(dest.r, src.r)," << endl; + fs << " burn(dest.g, src.g)," << endl; + fs << " burn(dest.b, src.b));" << endl; + break; + case gfx::CompositionOp::OP_HARD_LIGHT: + fs << " return vec3(" << endl; + fs << " hardlight(dest.r, src.r)," << endl; + fs << " hardlight(dest.g, src.g)," << endl; + fs << " hardlight(dest.b, src.b));" << endl; + break; + case gfx::CompositionOp::OP_SOFT_LIGHT: + fs << " return vec3(" << endl; + fs << " softlight(dest.r, src.r)," << endl; + fs << " softlight(dest.g, src.g)," << endl; + fs << " softlight(dest.b, src.b));" << endl; + break; + case gfx::CompositionOp::OP_DIFFERENCE: + fs << " return abs(dest - src);" << endl; + break; + case gfx::CompositionOp::OP_EXCLUSION: + fs << " return dest + src - 2.0*dest*src;" << endl; + break; + case gfx::CompositionOp::OP_HUE: + fs << " return SetLum(SetSat(src, Sat(dest)), Lum(dest));" << endl; + break; + case gfx::CompositionOp::OP_SATURATION: + fs << " return SetLum(SetSat(dest, Sat(src)), Lum(dest));" << endl; + break; + case gfx::CompositionOp::OP_COLOR: + fs << " return SetLum(src, Lum(dest));" << endl; + break; + case gfx::CompositionOp::OP_LUMINOSITY: + fs << " return SetLum(dest, Lum(src));" << endl; + break; + default: + MOZ_ASSERT_UNREACHABLE("unknown blend mode"); + } + fs << "}" << endl; + + // Generate the mix-blend function the fragment shader will call. + fs << "vec4 mixAndBlend(vec4 backdrop, vec4 color) {" << endl; + + // Shortcut when the backdrop or source alpha is 0, otherwise we may leak + // Infinity into the blend function and return incorrect results. + fs << " if (backdrop.a == 0.0) {" << endl; + fs << " return color;" << endl; + fs << " }" << endl; + fs << " if (color.a == 0.0) {" << endl; + fs << " return vec4(0.0, 0.0, 0.0, 0.0);" << endl; + fs << " }" << endl; + + // The spec assumes there is no premultiplied alpha. The backdrop is always + // premultiplied, so undo the premultiply. If the source is premultiplied we + // must fix that as well. + fs << " backdrop.rgb /= backdrop.a;" << endl; + if (!(aConfig.mFeatures & ENABLE_NO_PREMUL_ALPHA)) { + fs << " color.rgb /= color.a;" << endl; + } + fs << " vec3 blended = blend(backdrop.rgb, color.rgb);" << endl; + fs << " color.rgb = (1.0 - backdrop.a) * color.rgb + backdrop.a * " + "blended.rgb;" + << endl; + fs << " color.rgb *= color.a;" << endl; + fs << " return color;" << endl; + fs << "}" << endl; +} + +ShaderProgramOGL::ShaderProgramOGL(GLContext* aGL, + const ProgramProfileOGL& aProfile) + : mGL(aGL), mProgram(0), mProfile(aProfile), mProgramState(STATE_NEW) {} + +ShaderProgramOGL::~ShaderProgramOGL() { + if (mProgram <= 0) { + return; + } + + RefPtr ctx = mGL->GetSharedContext(); + if (!ctx) { + ctx = mGL; + } + ctx->MakeCurrent(); + ctx->fDeleteProgram(mProgram); +} + +bool ShaderProgramOGL::Initialize() { + NS_ASSERTION(mProgramState == STATE_NEW, + "Shader program has already been initialised"); + + std::ostringstream vs, fs; + for (uint32_t i = 0; i < mProfile.mDefines.Length(); ++i) { + vs << mProfile.mDefines[i] << endl; + fs << mProfile.mDefines[i] << endl; + } + vs << mProfile.mVertexShaderString << endl; + fs << mProfile.mFragmentShaderString << endl; + + if (!CreateProgram(vs.str().c_str(), fs.str().c_str())) { + mProgramState = STATE_ERROR; + return false; + } + + mProgramState = STATE_OK; + + for (uint32_t i = 0; i < KnownUniform::KnownUniformCount; ++i) { + mProfile.mUniforms[i].mLocation = + mGL->fGetUniformLocation(mProgram, mProfile.mUniforms[i].mNameString); + } + + return true; +} + +GLint ShaderProgramOGL::CreateShader(GLenum aShaderType, + const char* aShaderSource) { + GLint success, len = 0; + + GLint sh = mGL->fCreateShader(aShaderType); + mGL->fShaderSource(sh, 1, (const GLchar**)&aShaderSource, nullptr); + mGL->fCompileShader(sh); + mGL->fGetShaderiv(sh, LOCAL_GL_COMPILE_STATUS, &success); + mGL->fGetShaderiv(sh, LOCAL_GL_INFO_LOG_LENGTH, (GLint*)&len); + /* Even if compiling is successful, there may still be warnings. Print them + * in a debug build. The > 10 is to catch silly compilers that might put + * some whitespace in the log but otherwise leave it empty. + */ + if (!success +#ifdef DEBUG + || (len > 10 && gfxEnv::DebugShaders()) +#endif + ) { + nsAutoCString log; + log.SetLength(len); + mGL->fGetShaderInfoLog(sh, len, (GLint*)&len, (char*)log.BeginWriting()); + log.Truncate(len); + + if (!success) { + printf_stderr("=== SHADER COMPILATION FAILED ===\n"); + } else { + printf_stderr("=== SHADER COMPILATION WARNINGS ===\n"); + } + + printf_stderr("=== Source:\n%s\n", aShaderSource); + printf_stderr("=== Log:\n%s\n", log.get()); + printf_stderr("============\n"); + + if (!success) { + mGL->fDeleteShader(sh); + return 0; + } + } + + return sh; +} + +bool ShaderProgramOGL::CreateProgram(const char* aVertexShaderString, + const char* aFragmentShaderString) { + GLuint vertexShader = + CreateShader(LOCAL_GL_VERTEX_SHADER, aVertexShaderString); + GLuint fragmentShader = + CreateShader(LOCAL_GL_FRAGMENT_SHADER, aFragmentShaderString); + + if (!vertexShader || !fragmentShader) return false; + + GLint result = mGL->fCreateProgram(); + mGL->fAttachShader(result, vertexShader); + mGL->fAttachShader(result, fragmentShader); + + for (std::pair& attribute : mProfile.mAttributes) { + mGL->fBindAttribLocation(result, attribute.second, attribute.first.get()); + } + + mGL->fLinkProgram(result); + + GLint success, len; + mGL->fGetProgramiv(result, LOCAL_GL_LINK_STATUS, &success); + mGL->fGetProgramiv(result, LOCAL_GL_INFO_LOG_LENGTH, (GLint*)&len); + /* Even if linking is successful, there may still be warnings. Print them + * in a debug build. The > 10 is to catch silly compilers that might put + * some whitespace in the log but otherwise leave it empty. + */ + if (!success +#ifdef DEBUG + || (len > 10 && gfxEnv::DebugShaders()) +#endif + ) { + nsAutoCString log; + log.SetLength(len); + mGL->fGetProgramInfoLog(result, len, (GLint*)&len, + (char*)log.BeginWriting()); + + if (!success) { + printf_stderr("=== PROGRAM LINKING FAILED ===\n"); + } else { + printf_stderr("=== PROGRAM LINKING WARNINGS ===\n"); + } + printf_stderr("=== Log:\n%s\n", log.get()); + printf_stderr("============\n"); + } + + // We can mark the shaders for deletion; they're attached to the program + // and will remain attached. + mGL->fDeleteShader(vertexShader); + mGL->fDeleteShader(fragmentShader); + + if (!success) { + mGL->fDeleteProgram(result); + return false; + } + + mProgram = result; + return true; +} + +GLuint ShaderProgramOGL::GetProgram() { + if (mProgramState == STATE_NEW) { + if (!Initialize()) { + NS_WARNING("Shader could not be initialised"); + } + } + MOZ_ASSERT(HasInitialized(), + "Attempting to get a program that's not been initialized!"); + return mProgram; +} + +void ShaderProgramOGL::SetBlurRadius(float aRX, float aRY) { + float f[] = {aRX, aRY}; + SetUniform(KnownUniform::BlurRadius, 2, f); + + float gaussianKernel[GAUSSIAN_KERNEL_HALF_WIDTH]; + float sum = 0.0f; + for (int i = 0; i < GAUSSIAN_KERNEL_HALF_WIDTH; i++) { + float x = i * GAUSSIAN_KERNEL_STEP; + float sigma = 1.0f; + gaussianKernel[i] = + exp(-x * x / (2 * sigma * sigma)) / sqrt(2 * M_PI * sigma * sigma); + sum += gaussianKernel[i] * (i == 0 ? 1 : 2); + } + for (int i = 0; i < GAUSSIAN_KERNEL_HALF_WIDTH; i++) { + gaussianKernel[i] /= sum; + } + SetArrayUniform(KnownUniform::BlurGaussianKernel, GAUSSIAN_KERNEL_HALF_WIDTH, + gaussianKernel); +} + +void ShaderProgramOGL::SetYUVColorSpace(gfx::YUVColorSpace aYUVColorSpace) { + const float* yuvToRgb = + gfxUtils::YuvToRgbMatrix3x3ColumnMajor(aYUVColorSpace); + SetMatrix3fvUniform(KnownUniform::YuvColorMatrix, yuvToRgb); + if (aYUVColorSpace == gfx::YUVColorSpace::Identity) { + const float identity[] = {0.0, 0.0, 0.0}; + SetVec3fvUniform(KnownUniform::YuvOffsetVector, identity); + } else { + const float offset[] = {0.06275, 0.50196, 0.50196}; + SetVec3fvUniform(KnownUniform::YuvOffsetVector, offset); + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/opengl/OGLShaderProgram.h b/gfx/layers/opengl/OGLShaderProgram.h new file mode 100644 index 0000000000..1ae950348b --- /dev/null +++ b/gfx/layers/opengl/OGLShaderProgram.h @@ -0,0 +1,412 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_OGLSHADERPROGRAM_H +#define GFX_OGLSHADERPROGRAM_H + +#include +#include + +#include "GLContext.h" // for fast inlines of glUniform* +#include "OGLShaderConfig.h" + +namespace mozilla { +namespace layers { + +#if defined(DEBUG) +# define CHECK_CURRENT_PROGRAM 1 +# define ASSERT_THIS_PROGRAM \ + do { \ + GLuint currentProgram; \ + mGL->GetUIntegerv(LOCAL_GL_CURRENT_PROGRAM, ¤tProgram); \ + MOZ_ASSERT(currentProgram == mProgram, \ + "SetUniform with wrong program active!"); \ + } while (0) +#else +# define ASSERT_THIS_PROGRAM \ + do { \ + } while (0) +#endif + +/** + * This struct represents the shaders that make up a program and the uniform + * and attribute parmeters that those shaders take. + * It is used by ShaderProgramOGL. + * Use the factory method GetProfileFor to create instances. + */ +struct ProgramProfileOGL { + /** + * Factory method; creates an instance of this class for the given + * ShaderConfigOGL + */ + static ProgramProfileOGL GetProfileFor(ShaderConfigOGL aConfig); + + // the source code for the program's shaders + std::string mVertexShaderString; + std::string mFragmentShaderString; + + // the vertex attributes + CopyableTArray> mAttributes; + + KnownUniform mUniforms[KnownUniform::KnownUniformCount]; + CopyableTArray mDefines; + size_t mTextureCount; + + ProgramProfileOGL() : mTextureCount(0) {} + + private: + static void BuildMixBlender(const ShaderConfigOGL& aConfig, + std::ostringstream& fs); +}; + +/** + * Represents an OGL shader program. The details of a program are represented + * by a ProgramProfileOGL + */ +class ShaderProgramOGL { + public: + typedef mozilla::gl::GLContext GLContext; + + ShaderProgramOGL(GLContext* aGL, const ProgramProfileOGL& aProfile); + + ~ShaderProgramOGL(); + + bool HasInitialized() { + NS_ASSERTION(mProgramState != STATE_OK || mProgram > 0, + "Inconsistent program state"); + return mProgramState == STATE_OK; + } + + GLuint GetProgram(); + + bool Initialize(); + + GLint CreateShader(GLenum aShaderType, const char* aShaderSource); + + /** + * Creates a program and stores its id. + */ + bool CreateProgram(const char* aVertexShaderString, + const char* aFragmentShaderString); + + /** + * The following set of methods set a uniform argument to the shader program. + * Not all uniforms may be set for all programs, and such uses will throw + * an assertion. + */ + void SetLayerTransform(const gfx::Matrix4x4& aMatrix) { + SetMatrixUniform(KnownUniform::LayerTransform, aMatrix); + } + + void SetLayerTransformInverse(const gfx::Matrix4x4& aMatrix) { + SetMatrixUniform(KnownUniform::LayerTransformInverse, aMatrix); + } + + void SetMaskLayerTransform(const gfx::Matrix4x4& aMatrix) { + SetMatrixUniform(KnownUniform::MaskTransform, aMatrix); + } + + void SetBackdropTransform(const gfx::Matrix4x4& aMatrix) { + SetMatrixUniform(KnownUniform::BackdropTransform, aMatrix); + } + + void SetDEAAEdges(const gfx::Point3D* aEdges) { + SetArrayUniform(KnownUniform::SSEdges, 4, aEdges); + } + + void SetViewportSize(const gfx::IntSize& aSize) { + float vals[2] = {(float)aSize.width, (float)aSize.height}; + SetUniform(KnownUniform::ViewportSize, 2, vals); + } + + void SetVisibleCenter(const gfx::Point& aVisibleCenter) { + float vals[2] = {aVisibleCenter.x, aVisibleCenter.y}; + SetUniform(KnownUniform::VisibleCenter, 2, vals); + } + + void SetLayerRects(const gfx::Rect* aRects) { + float vals[16] = { + aRects[0].X(), aRects[0].Y(), aRects[0].Width(), aRects[0].Height(), + aRects[1].X(), aRects[1].Y(), aRects[1].Width(), aRects[1].Height(), + aRects[2].X(), aRects[2].Y(), aRects[2].Width(), aRects[2].Height(), + aRects[3].X(), aRects[3].Y(), aRects[3].Width(), aRects[3].Height()}; + SetUniform(KnownUniform::LayerRects, 16, vals); + } + + void SetProjectionMatrix(const gfx::Matrix4x4& aMatrix) { + SetMatrixUniform(KnownUniform::MatrixProj, aMatrix); + } + + // sets this program's texture transform, if it uses one + void SetTextureTransform(const gfx::Matrix4x4& aMatrix) { + SetMatrixUniform(KnownUniform::TextureTransform, aMatrix); + } + + void SetTextureRects(const gfx::Rect* aRects) { + float vals[16] = { + aRects[0].X(), aRects[0].Y(), aRects[0].Width(), aRects[0].Height(), + aRects[1].X(), aRects[1].Y(), aRects[1].Width(), aRects[1].Height(), + aRects[2].X(), aRects[2].Y(), aRects[2].Width(), aRects[2].Height(), + aRects[3].X(), aRects[3].Y(), aRects[3].Width(), aRects[3].Height()}; + SetUniform(KnownUniform::TextureRects, 16, vals); + } + + void SetRenderOffset(const nsIntPoint& aOffset) { + float vals[4] = {float(aOffset.x), float(aOffset.y)}; + SetUniform(KnownUniform::RenderTargetOffset, 2, vals); + } + + void SetRenderOffset(float aX, float aY) { + float vals[2] = {aX, aY}; + SetUniform(KnownUniform::RenderTargetOffset, 2, vals); + } + + void SetLayerOpacity(float aOpacity) { + SetUniform(KnownUniform::LayerOpacity, aOpacity); + } + + void SetTextureUnit(GLint aUnit) { SetUniform(KnownUniform::Texture, aUnit); } + void SetYTextureUnit(GLint aUnit) { + SetUniform(KnownUniform::YTexture, aUnit); + } + + void SetCbTextureUnit(GLint aUnit) { + SetUniform(KnownUniform::CbTexture, aUnit); + } + + void SetCrTextureUnit(GLint aUnit) { + SetUniform(KnownUniform::CrTexture, aUnit); + } + + void SetYCbCrTextureUnits(GLint aYUnit, GLint aCbUnit, GLint aCrUnit) { + SetUniform(KnownUniform::YTexture, aYUnit); + SetUniform(KnownUniform::CbTexture, aCbUnit); + SetUniform(KnownUniform::CrTexture, aCrUnit); + } + + void SetNV12TextureUnits(GLint aYUnit, GLint aCbCrUnit) { + SetUniform(KnownUniform::YTexture, aYUnit); + SetUniform(KnownUniform::CbTexture, aCbCrUnit); + } + + void SetBlackTextureUnit(GLint aUnit) { + SetUniform(KnownUniform::BlackTexture, aUnit); + } + + void SetWhiteTextureUnit(GLint aUnit) { + SetUniform(KnownUniform::WhiteTexture, aUnit); + } + + void SetMaskTextureUnit(GLint aUnit) { + SetUniform(KnownUniform::MaskTexture, aUnit); + } + + void SetBackdropTextureUnit(GLint aUnit) { + SetUniform(KnownUniform::BackdropTexture, aUnit); + } + + void SetRenderColor(const gfx::DeviceColor& aColor) { + SetUniform(KnownUniform::RenderColor, aColor); + } + + void SetColorMatrix(const gfx::Matrix5x4& aColorMatrix) { + SetMatrixUniform(KnownUniform::ColorMatrix, &aColorMatrix._11); + SetUniform(KnownUniform::ColorMatrixVector, 4, &aColorMatrix._51); + } + + void SetTexCoordMultiplier(float aWidth, float aHeight) { + float f[] = {aWidth, aHeight}; + SetUniform(KnownUniform::TexCoordMultiplier, 2, f); + } + + void SetCbCrTexCoordMultiplier(float aWidth, float aHeight) { + float f[] = {aWidth, aHeight}; + SetUniform(KnownUniform::CbCrTexCoordMultiplier, 2, f); + } + + void SetMaskCoordMultiplier(float aWidth, float aHeight) { + float f[] = {aWidth, aHeight}; + SetUniform(KnownUniform::MaskCoordMultiplier, 2, f); + } + + void SetYUVColorSpace(gfx::YUVColorSpace aYUVColorSpace); + + // Set whether we want the component alpha shader to return the color + // vector (pass 1, false) or the alpha vector (pass2, true). With support + // for multiple render targets we wouldn't need two passes here. + void SetTexturePass2(bool aFlag) { + SetUniform(KnownUniform::TexturePass2, aFlag ? 1 : 0); + } + + void SetBlurRadius(float aRX, float aRY); + + void SetBlurAlpha(float aAlpha) { + SetUniform(KnownUniform::BlurAlpha, aAlpha); + } + + void SetBlurOffset(float aOffsetX, float aOffsetY) { + float f[] = {aOffsetX, aOffsetY}; + SetUniform(KnownUniform::BlurOffset, 2, f); + } + + size_t GetTextureCount() const { return mProfile.mTextureCount; } + + protected: + RefPtr mGL; + // the OpenGL id of the program + GLuint mProgram; + ProgramProfileOGL mProfile; + enum { STATE_NEW, STATE_OK, STATE_ERROR } mProgramState; + +#ifdef CHECK_CURRENT_PROGRAM + static int sCurrentProgramKey; +#endif + + void SetUniform(KnownUniform::KnownUniformName aKnownUniform, + float aFloatValue) { + ASSERT_THIS_PROGRAM; + NS_ASSERTION( + aKnownUniform >= 0 && aKnownUniform < KnownUniform::KnownUniformCount, + "Invalid known uniform"); + + KnownUniform& ku(mProfile.mUniforms[aKnownUniform]); + if (ku.UpdateUniform(aFloatValue)) { + mGL->fUniform1f(ku.mLocation, aFloatValue); + } + } + + void SetUniform(KnownUniform::KnownUniformName aKnownUniform, + const gfx::DeviceColor& aColor) { + ASSERT_THIS_PROGRAM; + NS_ASSERTION( + aKnownUniform >= 0 && aKnownUniform < KnownUniform::KnownUniformCount, + "Invalid known uniform"); + + KnownUniform& ku(mProfile.mUniforms[aKnownUniform]); + if (ku.UpdateUniform(aColor.r, aColor.g, aColor.b, aColor.a)) { + mGL->fUniform4fv(ku.mLocation, 1, ku.mValue.f16v); + } + } + + void SetUniform(KnownUniform::KnownUniformName aKnownUniform, int aLength, + const float* aFloatValues) { + ASSERT_THIS_PROGRAM; + NS_ASSERTION( + aKnownUniform >= 0 && aKnownUniform < KnownUniform::KnownUniformCount, + "Invalid known uniform"); + + KnownUniform& ku(mProfile.mUniforms[aKnownUniform]); + if (ku.UpdateUniform(aLength, aFloatValues)) { + switch (aLength) { + case 1: + mGL->fUniform1fv(ku.mLocation, 1, ku.mValue.f16v); + break; + case 2: + mGL->fUniform2fv(ku.mLocation, 1, ku.mValue.f16v); + break; + case 3: + mGL->fUniform3fv(ku.mLocation, 1, ku.mValue.f16v); + break; + case 4: + mGL->fUniform4fv(ku.mLocation, 1, ku.mValue.f16v); + break; + case 16: + mGL->fUniform4fv(ku.mLocation, 4, ku.mValue.f16v); + break; + default: + MOZ_ASSERT_UNREACHABLE("Bogus aLength param"); + } + } + } + + void SetArrayUniform(KnownUniform::KnownUniformName aKnownUniform, + int aLength, float* aFloatValues) { + ASSERT_THIS_PROGRAM; + NS_ASSERTION( + aKnownUniform >= 0 && aKnownUniform < KnownUniform::KnownUniformCount, + "Invalid known uniform"); + + KnownUniform& ku(mProfile.mUniforms[aKnownUniform]); + if (ku.UpdateArrayUniform(aLength, aFloatValues)) { + mGL->fUniform1fv(ku.mLocation, aLength, ku.mValue.f16v); + } + } + + void SetArrayUniform(KnownUniform::KnownUniformName aKnownUniform, + int aLength, const gfx::Point3D* aPointValues) { + ASSERT_THIS_PROGRAM; + NS_ASSERTION( + aKnownUniform >= 0 && aKnownUniform < KnownUniform::KnownUniformCount, + "Invalid known uniform"); + + KnownUniform& ku(mProfile.mUniforms[aKnownUniform]); + if (ku.UpdateArrayUniform(aLength, aPointValues)) { + mGL->fUniform3fv(ku.mLocation, aLength, ku.mValue.f16v); + } + } + + void SetUniform(KnownUniform::KnownUniformName aKnownUniform, + GLint aIntValue) { + ASSERT_THIS_PROGRAM; + NS_ASSERTION( + aKnownUniform >= 0 && aKnownUniform < KnownUniform::KnownUniformCount, + "Invalid known uniform"); + + KnownUniform& ku(mProfile.mUniforms[aKnownUniform]); + if (ku.UpdateUniform(aIntValue)) { + mGL->fUniform1i(ku.mLocation, aIntValue); + } + } + + void SetMatrixUniform(KnownUniform::KnownUniformName aKnownUniform, + const float* aFloatValues) { + ASSERT_THIS_PROGRAM; + NS_ASSERTION( + aKnownUniform >= 0 && aKnownUniform < KnownUniform::KnownUniformCount, + "Invalid known uniform"); + + KnownUniform& ku(mProfile.mUniforms[aKnownUniform]); + if (ku.UpdateUniform(16, aFloatValues)) { + mGL->fUniformMatrix4fv(ku.mLocation, 1, false, ku.mValue.f16v); + } + } + + void SetMatrix3fvUniform(KnownUniform::KnownUniformName aKnownUniform, + const float* aFloatValues) { + ASSERT_THIS_PROGRAM; + NS_ASSERTION( + aKnownUniform >= 0 && aKnownUniform < KnownUniform::KnownUniformCount, + "Invalid known uniform"); + + KnownUniform& ku(mProfile.mUniforms[aKnownUniform]); + if (ku.UpdateUniform(9, aFloatValues)) { + mGL->fUniformMatrix3fv(ku.mLocation, 1, false, ku.mValue.f16v); + } + } + + void SetVec3fvUniform(KnownUniform::KnownUniformName aKnownUniform, + const float* aFloatValues) { + ASSERT_THIS_PROGRAM; + NS_ASSERTION( + aKnownUniform >= 0 && aKnownUniform < KnownUniform::KnownUniformCount, + "Invalid known uniform"); + + KnownUniform& ku(mProfile.mUniforms[aKnownUniform]); + if (ku.UpdateUniform(3, aFloatValues)) { + mGL->fUniform3fv(ku.mLocation, 1, ku.mValue.f16v); + } + } + + void SetMatrixUniform(KnownUniform::KnownUniformName aKnownUniform, + const gfx::Matrix4x4& aMatrix) { + SetMatrixUniform(aKnownUniform, &aMatrix._11); + } +}; + +} // namespace layers +} // namespace mozilla + +#endif // GFX_OGLSHADERPROGRAM_H diff --git a/gfx/layers/opengl/TextureClientOGL.cpp b/gfx/layers/opengl/TextureClientOGL.cpp new file mode 100644 index 0000000000..7afdedde01 --- /dev/null +++ b/gfx/layers/opengl/TextureClientOGL.cpp @@ -0,0 +1,352 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "GLContext.h" // for GLContext, etc +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/layers/ISurfaceAllocator.h" +#include "mozilla/layers/TextureClientOGL.h" +#include "mozilla/gfx/2D.h" // for Factory +#include "mozilla/gfx/Point.h" // for IntSize +#include "GLLibraryEGL.h" + +#ifdef MOZ_WIDGET_ANDROID +# include +# include +# include +# include +# include "mozilla/ipc/FileDescriptor.h" +# include "mozilla/java/GeckoSurfaceWrappers.h" +# include "mozilla/java/SurfaceAllocatorWrappers.h" +# include "mozilla/layers/AndroidHardwareBuffer.h" +# include "mozilla/UniquePtrExtensions.h" +#endif + +using namespace mozilla::gl; + +namespace mozilla { +namespace layers { + +class CompositableForwarder; + +//////////////////////////////////////////////////////////////////////// +// AndroidSurface + +#ifdef MOZ_WIDGET_ANDROID + +already_AddRefed AndroidSurfaceTextureData::CreateTextureClient( + AndroidSurfaceTextureHandle aHandle, gfx::IntSize aSize, bool aContinuous, + gl::OriginPos aOriginPos, bool aHasAlpha, LayersIPCChannel* aAllocator, + TextureFlags aFlags) { + if (aOriginPos == gl::OriginPos::BottomLeft) { + aFlags |= TextureFlags::ORIGIN_BOTTOM_LEFT; + } + + return TextureClient::CreateWithData( + new AndroidSurfaceTextureData(aHandle, aSize, aContinuous, aHasAlpha), + aFlags, aAllocator); +} + +AndroidSurfaceTextureData::AndroidSurfaceTextureData( + AndroidSurfaceTextureHandle aHandle, gfx::IntSize aSize, bool aContinuous, + bool aHasAlpha) + : mHandle(aHandle), + mSize(aSize), + mContinuous(aContinuous), + mHasAlpha(aHasAlpha) { + MOZ_ASSERT(mHandle); +} + +AndroidSurfaceTextureData::~AndroidSurfaceTextureData() {} + +void AndroidSurfaceTextureData::FillInfo(TextureData::Info& aInfo) const { + aInfo.size = mSize; + aInfo.format = gfx::SurfaceFormat::UNKNOWN; + aInfo.hasIntermediateBuffer = false; + aInfo.hasSynchronization = false; + aInfo.supportsMoz2D = false; + aInfo.canExposeMappedData = false; +} + +bool AndroidSurfaceTextureData::Serialize(SurfaceDescriptor& aOutDescriptor) { + aOutDescriptor = SurfaceTextureDescriptor( + mHandle, mSize, + mHasAlpha ? gfx::SurfaceFormat::R8G8B8A8 : gfx::SurfaceFormat::R8G8B8X8, + mContinuous, false /* do not ignore transform */); + return true; +} + +#endif // MOZ_WIDGET_ANDROID + +//////////////////////////////////////////////////////////////////////// +// AndroidNativeWindow + +#ifdef MOZ_WIDGET_ANDROID + +AndroidNativeWindowTextureData* AndroidNativeWindowTextureData::Create( + gfx::IntSize aSize, gfx::SurfaceFormat aFormat) { + if (aFormat != gfx::SurfaceFormat::R8G8B8A8 && + aFormat != gfx::SurfaceFormat::R8G8B8X8 && + aFormat != gfx::SurfaceFormat::B8G8R8A8 && + aFormat != gfx::SurfaceFormat::B8G8R8X8 && + aFormat != gfx::SurfaceFormat::R5G6B5_UINT16) { + return nullptr; + } + + auto surface = + java::GeckoSurface::LocalRef(java::SurfaceAllocator::AcquireSurface( + aSize.width, aSize.height, true /* single-buffer mode */)); + if (surface) { + return new AndroidNativeWindowTextureData(surface, aSize, aFormat); + } + + return nullptr; +} + +AndroidNativeWindowTextureData::AndroidNativeWindowTextureData( + java::GeckoSurface::Param aSurface, gfx::IntSize aSize, + gfx::SurfaceFormat aFormat) + : mSurface(aSurface), mIsLocked(false), mSize(aSize), mFormat(aFormat) { + mNativeWindow = + ANativeWindow_fromSurface(jni::GetEnvForThread(), mSurface.Get()); + MOZ_ASSERT(mNativeWindow, "Failed to create NativeWindow."); + + // SurfaceTextures don't technically support BGR, but we can just pretend to + // be RGB. + int32_t format = WINDOW_FORMAT_RGBA_8888; + switch (aFormat) { + case gfx::SurfaceFormat::R8G8B8A8: + case gfx::SurfaceFormat::B8G8R8A8: + format = WINDOW_FORMAT_RGBA_8888; + break; + + case gfx::SurfaceFormat::R8G8B8X8: + case gfx::SurfaceFormat::B8G8R8X8: + format = WINDOW_FORMAT_RGBX_8888; + break; + + case gfx::SurfaceFormat::R5G6B5_UINT16: + format = WINDOW_FORMAT_RGB_565; + break; + + default: + MOZ_ASSERT(false, "Unsupported AndroidNativeWindowTextureData format."); + } + + DebugOnly r = ANativeWindow_setBuffersGeometry( + mNativeWindow, mSize.width, mSize.height, format); + MOZ_ASSERT(r == 0, "ANativeWindow_setBuffersGeometry failed."); + + // Ideally here we'd call ANativeWindow_setBuffersTransform() with the + // identity transform, but that is only available on api level >= 26. + // Instead use the SurfaceDescriptor's ignoreTransform flag when serializing. +} + +void AndroidNativeWindowTextureData::FillInfo(TextureData::Info& aInfo) const { + aInfo.size = mSize; + aInfo.format = mFormat; + aInfo.hasIntermediateBuffer = false; + aInfo.hasSynchronization = false; + aInfo.supportsMoz2D = true; + aInfo.canExposeMappedData = false; + aInfo.canConcurrentlyReadLock = false; +} + +bool AndroidNativeWindowTextureData::Serialize( + SurfaceDescriptor& aOutDescriptor) { + aOutDescriptor = SurfaceTextureDescriptor(mSurface->GetHandle(), mSize, + mFormat, false /* not continuous */, + true /* ignore transform */); + return true; +} + +bool AndroidNativeWindowTextureData::Lock(OpenMode) { + // ANativeWindows can only be locked and unlocked a single time, after which + // we must wait until they receive ownership back from the host. + // Therefore we must only actually call ANativeWindow_lock() once per cycle. + if (!mIsLocked) { + int32_t r = ANativeWindow_lock(mNativeWindow, &mBuffer, nullptr); + if (r == -ENOMEM) { + return false; + } else if (r < 0) { + MOZ_CRASH("ANativeWindow_lock failed."); + } + mIsLocked = true; + } + return true; +} + +void AndroidNativeWindowTextureData::Unlock() { + // The TextureClient may want to call Lock again before handing ownership + // to the host, so we cannot call ANativeWindow_unlockAndPost yet. +} + +void AndroidNativeWindowTextureData::Forget(LayersIPCChannel*) { + MOZ_ASSERT(!mIsLocked, + "ANativeWindow should not be released while locked.\n"); + ANativeWindow_release(mNativeWindow); + mNativeWindow = nullptr; + java::SurfaceAllocator::DisposeSurface(mSurface); + mSurface = nullptr; +} + +already_AddRefed +AndroidNativeWindowTextureData::BorrowDrawTarget() { + const int bpp = (mFormat == gfx::SurfaceFormat::R5G6B5_UINT16) ? 2 : 4; + + return gfx::Factory::CreateDrawTargetForData( + gfx::BackendType::SKIA, static_cast(mBuffer.bits), + gfx::IntSize(mBuffer.width, mBuffer.height), mBuffer.stride * bpp, + mFormat, true); +} + +void AndroidNativeWindowTextureData::OnForwardedToHost() { + if (mIsLocked) { + int32_t r = ANativeWindow_unlockAndPost(mNativeWindow); + if (r < 0) { + MOZ_CRASH("ANativeWindow_unlockAndPost failed\n."); + } + mIsLocked = false; + } +} + +AndroidHardwareBufferTextureData* AndroidHardwareBufferTextureData::Create( + gfx::IntSize aSize, gfx::SurfaceFormat aFormat) { + RefPtr buffer = + AndroidHardwareBuffer::Create(aSize, aFormat); + if (!buffer) { + return nullptr; + } + return new AndroidHardwareBufferTextureData(buffer, aSize, aFormat); +} + +AndroidHardwareBufferTextureData::AndroidHardwareBufferTextureData( + AndroidHardwareBuffer* aAndroidHardwareBuffer, gfx::IntSize aSize, + gfx::SurfaceFormat aFormat) + : mAndroidHardwareBuffer(aAndroidHardwareBuffer), + mSize(aSize), + mFormat(aFormat), + mAddress(nullptr), + mIsLocked(false) {} + +AndroidHardwareBufferTextureData::~AndroidHardwareBufferTextureData() {} + +void AndroidHardwareBufferTextureData::FillInfo( + TextureData::Info& aInfo) const { + aInfo.size = mSize; + aInfo.format = mFormat; + aInfo.hasIntermediateBuffer = false; + aInfo.hasSynchronization = true; + aInfo.supportsMoz2D = true; + aInfo.canExposeMappedData = false; + aInfo.canConcurrentlyReadLock = true; +} + +bool AndroidHardwareBufferTextureData::Serialize( + SurfaceDescriptor& aOutDescriptor) { + int fd[2]; + if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fd) != 0) { + aOutDescriptor = SurfaceDescriptorAndroidHardwareBuffer( + ipc::FileDescriptor(), mAndroidHardwareBuffer->mId, mSize, mFormat); + return false; + } + + UniqueFileHandle readerFd(fd[0]); + UniqueFileHandle writerFd(fd[1]); + + // Send the AHardwareBuffer to an AF_UNIX socket. It does not acquire or + // retain a reference to the buffer object. The caller is therefore + // responsible for ensuring that the buffer remains alive through the lifetime + // of this file descriptor. + int ret = mAndroidHardwareBuffer->SendHandleToUnixSocket(writerFd.get()); + if (ret < 0) { + aOutDescriptor = SurfaceDescriptorAndroidHardwareBuffer( + ipc::FileDescriptor(), mAndroidHardwareBuffer->mId, mSize, mFormat); + return false; + } + + aOutDescriptor = SurfaceDescriptorAndroidHardwareBuffer( + ipc::FileDescriptor(readerFd.release()), mAndroidHardwareBuffer->mId, + mSize, mFormat); + return true; +} + +bool AndroidHardwareBufferTextureData::Lock(OpenMode aMode) { + if (!mIsLocked) { + MOZ_ASSERT(!mAddress); + + mAndroidHardwareBuffer->WaitForBufferOwnership(); + + uint64_t usage = 0; + if (aMode & OpenMode::OPEN_READ) { + usage |= AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN; + } + if (aMode & OpenMode::OPEN_WRITE) { + usage |= AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN; + } + + int ret = mAndroidHardwareBuffer->Lock(usage, 0, &mAddress); + if (ret) { + mAddress = nullptr; + return false; + } + mIsLocked = true; + } + return true; +} + +void AndroidHardwareBufferTextureData::Unlock() { + // The TextureClient may want to call Lock again before handing ownership + // to the host, so we cannot call AHardwareBuffer_unlock yet. +} + +void AndroidHardwareBufferTextureData::Forget(LayersIPCChannel*) { + MOZ_ASSERT(!mIsLocked); + mAndroidHardwareBuffer = nullptr; + mAddress = nullptr; +} + +already_AddRefed +AndroidHardwareBufferTextureData::BorrowDrawTarget() { + MOZ_ASSERT(mIsLocked); + + const int bpp = (mFormat == gfx::SurfaceFormat::R5G6B5_UINT16) ? 2 : 4; + + return gfx::Factory::CreateDrawTargetForData( + gfx::BackendType::SKIA, static_cast(mAddress), + gfx::IntSize(mAndroidHardwareBuffer->mSize.width, + mAndroidHardwareBuffer->mSize.height), + mAndroidHardwareBuffer->mStride * bpp, mFormat, true); +} + +void AndroidHardwareBufferTextureData::OnForwardedToHost() { + if (mIsLocked) { + mAndroidHardwareBuffer->Unlock(); + mAddress = nullptr; + mIsLocked = false; + } +} + +TextureFlags AndroidHardwareBufferTextureData::GetTextureFlags() const { + return TextureFlags::WAIT_HOST_USAGE_END; +} + +Maybe AndroidHardwareBufferTextureData::GetBufferId() const { + return Some(mAndroidHardwareBuffer->mId); +} + +mozilla::ipc::FileDescriptor +AndroidHardwareBufferTextureData::GetAcquireFence() { + if (!mAndroidHardwareBuffer) { + return ipc::FileDescriptor(); + } + + return mAndroidHardwareBuffer->GetAcquireFence(); +} + +#endif // MOZ_WIDGET_ANDROID + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/opengl/TextureClientOGL.h b/gfx/layers/opengl/TextureClientOGL.h new file mode 100644 index 0000000000..0a34c1effe --- /dev/null +++ b/gfx/layers/opengl/TextureClientOGL.h @@ -0,0 +1,161 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_TEXTURECLIENTOGL_H +#define MOZILLA_GFX_TEXTURECLIENTOGL_H + +#include "GLContextTypes.h" // for SharedTextureHandle, etc +#include "GLImages.h" +#include "gfxTypes.h" +#include "mozilla/Attributes.h" // for override +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/gfx/Types.h" // for SurfaceFormat +#include "mozilla/layers/CompositorTypes.h" +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor +#include "mozilla/layers/TextureClient.h" // for TextureClient, etc +#ifdef MOZ_WIDGET_ANDROID +# include "AndroidSurfaceTexture.h" +# include "AndroidNativeWindow.h" +# include "mozilla/java/GeckoSurfaceWrappers.h" +# include "mozilla/layers/AndroidHardwareBuffer.h" +#endif + +namespace mozilla { + +namespace gfx { + +class DrawTarget; + +} // namespace gfx + +namespace layers { + +#ifdef MOZ_WIDGET_ANDROID + +class AndroidHardwareBuffer; + +class AndroidSurfaceTextureData : public TextureData { + public: + static already_AddRefed CreateTextureClient( + AndroidSurfaceTextureHandle aHandle, gfx::IntSize aSize, bool aContinuous, + gl::OriginPos aOriginPos, bool aHasAlpha, LayersIPCChannel* aAllocator, + TextureFlags aFlags); + + virtual ~AndroidSurfaceTextureData(); + + void FillInfo(TextureData::Info& aInfo) const override; + + bool Serialize(SurfaceDescriptor& aOutDescriptor) override; + + // Useless functions. + bool Lock(OpenMode) override { return true; } + + void Unlock() override {} + + // Our data is always owned externally. + void Deallocate(LayersIPCChannel*) override {} + + protected: + AndroidSurfaceTextureData(AndroidSurfaceTextureHandle aHandle, + gfx::IntSize aSize, bool aContinuous, + bool aHasAlpha); + + const AndroidSurfaceTextureHandle mHandle; + const gfx::IntSize mSize; + const bool mContinuous; + const bool mHasAlpha; +}; + +class AndroidNativeWindowTextureData : public TextureData { + public: + static AndroidNativeWindowTextureData* Create(gfx::IntSize aSize, + gfx::SurfaceFormat aFormat); + + void FillInfo(TextureData::Info& aInfo) const override; + + bool Serialize(SurfaceDescriptor& aOutDescriptor) override; + + bool Lock(OpenMode) override; + void Unlock() override; + + void Forget(LayersIPCChannel*) override; + void Deallocate(LayersIPCChannel*) override {} + + already_AddRefed BorrowDrawTarget() override; + + void OnForwardedToHost() override; + + protected: + AndroidNativeWindowTextureData(java::GeckoSurface::Param aSurface, + gfx::IntSize aSize, + gfx::SurfaceFormat aFormat); + + private: + java::GeckoSurface::GlobalRef mSurface; + ANativeWindow* mNativeWindow; + ANativeWindow_Buffer mBuffer; + // Keeps track of whether the underlying NativeWindow is actually locked. + bool mIsLocked; + + const gfx::IntSize mSize; + const gfx::SurfaceFormat mFormat; +}; + +class AndroidHardwareBufferTextureData : public TextureData { + public: + static AndroidHardwareBufferTextureData* Create(gfx::IntSize aSize, + gfx::SurfaceFormat aFormat); + + virtual ~AndroidHardwareBufferTextureData(); + + void FillInfo(TextureData::Info& aInfo) const override; + + bool Serialize(SurfaceDescriptor& aOutDescriptor) override; + + bool Lock(OpenMode aMode) override; + void Unlock() override; + + void Forget(LayersIPCChannel*) override; + void Deallocate(LayersIPCChannel*) override {} + + already_AddRefed BorrowDrawTarget() override; + + void OnForwardedToHost() override; + + TextureFlags GetTextureFlags() const override; + + Maybe GetBufferId() const override; + + mozilla::ipc::FileDescriptor GetAcquireFence() override; + + AndroidHardwareBufferTextureData* AsAndroidHardwareBufferTextureData() + override { + return this; + } + + AndroidHardwareBuffer* GetBuffer() { return mAndroidHardwareBuffer; } + + protected: + AndroidHardwareBufferTextureData( + AndroidHardwareBuffer* aAndroidHardwareBuffer, gfx::IntSize aSize, + gfx::SurfaceFormat aFormat); + + RefPtr mAndroidHardwareBuffer; + const gfx::IntSize mSize; + const gfx::SurfaceFormat mFormat; + + void* mAddress; + + // Keeps track of whether the underlying NativeWindow is actually locked. + bool mIsLocked; +}; + +#endif // MOZ_WIDGET_ANDROID + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/opengl/TextureHostOGL.cpp b/gfx/layers/opengl/TextureHostOGL.cpp new file mode 100644 index 0000000000..c4157cdef2 --- /dev/null +++ b/gfx/layers/opengl/TextureHostOGL.cpp @@ -0,0 +1,1277 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TextureHostOGL.h" + +#include "GLContextEGL.h" // for GLContext, etc +#include "GLLibraryEGL.h" // for GLLibraryEGL +#include "GLUploadHelpers.h" +#include "GLReadTexImageHelper.h" +#include "gfx2DGlue.h" // for ContentForFormat, etc +#include "mozilla/gfx/2D.h" // for DataSourceSurface +#include "mozilla/gfx/BaseSize.h" // for BaseSize +#include "mozilla/gfx/Logging.h" // for gfxCriticalError +#include "mozilla/layers/ISurfaceAllocator.h" +#include "mozilla/webrender/RenderEGLImageTextureHost.h" +#include "mozilla/webrender/WebRenderAPI.h" +#include "nsRegion.h" // for nsIntRegion +#include "GfxTexturesReporter.h" // for GfxTexturesReporter +#include "GLBlitTextureImageHelper.h" +#include "GeckoProfiler.h" + +#ifdef XP_MACOSX +# include "mozilla/layers/MacIOSurfaceTextureHostOGL.h" +#endif + +#ifdef MOZ_WIDGET_ANDROID +# include "mozilla/layers/AndroidHardwareBuffer.h" +# include "mozilla/webrender/RenderAndroidHardwareBufferTextureHost.h" +# include "mozilla/webrender/RenderAndroidSurfaceTextureHost.h" +#endif + +#ifdef MOZ_WAYLAND +# include "mozilla/layers/DMABUFTextureHostOGL.h" +#endif + +using namespace mozilla::gl; +using namespace mozilla::gfx; + +namespace mozilla { +namespace layers { + +class Compositor; + +void ApplySamplingFilterToBoundTexture(gl::GLContext* aGL, + gfx::SamplingFilter aSamplingFilter, + GLuint aTarget) { + GLenum filter = + (aSamplingFilter == gfx::SamplingFilter::POINT ? LOCAL_GL_NEAREST + : LOCAL_GL_LINEAR); + + aGL->fTexParameteri(aTarget, LOCAL_GL_TEXTURE_MIN_FILTER, filter); + aGL->fTexParameteri(aTarget, LOCAL_GL_TEXTURE_MAG_FILTER, filter); +} + +already_AddRefed CreateTextureHostOGL( + const SurfaceDescriptor& aDesc, ISurfaceAllocator* aDeallocator, + LayersBackend aBackend, TextureFlags aFlags) { + RefPtr result; + switch (aDesc.type()) { +#ifdef MOZ_WIDGET_ANDROID + case SurfaceDescriptor::TSurfaceTextureDescriptor: { + const SurfaceTextureDescriptor& desc = + aDesc.get_SurfaceTextureDescriptor(); + java::GeckoSurfaceTexture::LocalRef surfaceTexture = + java::GeckoSurfaceTexture::Lookup(desc.handle()); + + result = new SurfaceTextureHost(aFlags, surfaceTexture, desc.size(), + desc.format(), desc.continuous(), + desc.ignoreTransform()); + break; + } + case SurfaceDescriptor::TSurfaceDescriptorAndroidHardwareBuffer: { + const SurfaceDescriptorAndroidHardwareBuffer& desc = + aDesc.get_SurfaceDescriptorAndroidHardwareBuffer(); + result = AndroidHardwareBufferTextureHost::Create(aFlags, desc); + break; + } +#endif + + case SurfaceDescriptor::TEGLImageDescriptor: { + const EGLImageDescriptor& desc = aDesc.get_EGLImageDescriptor(); + result = new EGLImageTextureHost(aFlags, (EGLImage)desc.image(), + (EGLSync)desc.fence(), desc.size(), + desc.hasAlpha()); + break; + } + +#ifdef MOZ_WAYLAND + case SurfaceDescriptor::TSurfaceDescriptorDMABuf: { + result = new DMABUFTextureHostOGL(aFlags, aDesc); + break; + } +#endif + +#ifdef XP_MACOSX + case SurfaceDescriptor::TSurfaceDescriptorMacIOSurface: { + const SurfaceDescriptorMacIOSurface& desc = + aDesc.get_SurfaceDescriptorMacIOSurface(); + result = new MacIOSurfaceTextureHostOGL(aFlags, desc); + break; + } +#endif + + case SurfaceDescriptor::TSurfaceDescriptorSharedGLTexture: { + const auto& desc = aDesc.get_SurfaceDescriptorSharedGLTexture(); + result = + new GLTextureHost(aFlags, desc.texture(), desc.target(), + (GLsync)desc.fence(), desc.size(), desc.hasAlpha()); + break; + } + default: { + MOZ_ASSERT_UNREACHABLE("Unsupported SurfaceDescriptor type"); + break; + } + } + return result.forget(); +} + +static gl::TextureImage::Flags FlagsToGLFlags(TextureFlags aFlags) { + uint32_t result = TextureImage::NoFlags; + + if (aFlags & TextureFlags::USE_NEAREST_FILTER) + result |= TextureImage::UseNearestFilter; + if (aFlags & TextureFlags::ORIGIN_BOTTOM_LEFT) + result |= TextureImage::OriginBottomLeft; + if (aFlags & TextureFlags::DISALLOW_BIGIMAGE) + result |= TextureImage::DisallowBigImage; + + return static_cast(result); +} + +TextureImageTextureSourceOGL::TextureImageTextureSourceOGL( + CompositorOGL* aCompositor, TextureFlags aFlags) + : mGL(aCompositor->gl()), + mCompositor(aCompositor), + mFlags(aFlags), + mIterating(false) { + if (mCompositor) { + mCompositor->RegisterTextureSource(this); + } +} + +TextureImageTextureSourceOGL::~TextureImageTextureSourceOGL() { + DeallocateDeviceData(); +} + +void TextureImageTextureSourceOGL::DeallocateDeviceData() { + mTexImage = nullptr; + mGL = nullptr; + if (mCompositor) { + mCompositor->UnregisterTextureSource(this); + } + SetUpdateSerial(0); +} + +bool TextureImageTextureSourceOGL::Update(gfx::DataSourceSurface* aSurface, + nsIntRegion* aDestRegion, + gfx::IntPoint* aSrcOffset) { + GLContext* gl = mGL; + MOZ_ASSERT(gl); + if (!gl || !gl->MakeCurrent()) { + NS_WARNING( + "trying to update TextureImageTextureSourceOGL without a GLContext"); + return false; + } + if (!aSurface) { + gfxCriticalError() << "Invalid surface for OGL update"; + return false; + } + MOZ_ASSERT(aSurface); + + IntSize size = aSurface->GetSize(); + if (!mTexImage || (mTexImage->GetSize() != size && !aSrcOffset) || + mTexImage->GetContentType() != + gfx::ContentForFormat(aSurface->GetFormat())) { + if (mFlags & TextureFlags::DISALLOW_BIGIMAGE) { + GLint maxTextureSize; + gl->fGetIntegerv(LOCAL_GL_MAX_TEXTURE_SIZE, &maxTextureSize); + if (size.width > maxTextureSize || size.height > maxTextureSize) { + NS_WARNING("Texture exceeds maximum texture size, refusing upload"); + return false; + } + // Explicitly use CreateBasicTextureImage instead of CreateTextureImage, + // because CreateTextureImage might still choose to create a tiled + // texture image. + mTexImage = CreateBasicTextureImage( + gl, size, gfx::ContentForFormat(aSurface->GetFormat()), + LOCAL_GL_CLAMP_TO_EDGE, FlagsToGLFlags(mFlags)); + } else { + // XXX - clarify which size we want to use. IncrementalContentHost will + // require the size of the destination surface to be different from + // the size of aSurface. + // See bug 893300 (tracks the implementation of ContentHost for new + // textures). + mTexImage = CreateTextureImage( + gl, size, gfx::ContentForFormat(aSurface->GetFormat()), + LOCAL_GL_CLAMP_TO_EDGE, FlagsToGLFlags(mFlags), + SurfaceFormatToImageFormat(aSurface->GetFormat())); + } + ClearCachedFilter(); + + if (aDestRegion && !aSrcOffset && + !aDestRegion->IsEqual(gfx::IntRect(0, 0, size.width, size.height))) { + // UpdateFromDataSource will ignore our specified aDestRegion since the + // texture hasn't been allocated with glTexImage2D yet. Call Resize() to + // force the allocation (full size, but no upload), and then we'll only + // upload the pixels we care about below. + mTexImage->Resize(size); + } + } + + return mTexImage->UpdateFromDataSource(aSurface, aDestRegion, aSrcOffset); +} + +void TextureImageTextureSourceOGL::EnsureBuffer(const IntSize& aSize, + gfxContentType aContentType) { + if (!mTexImage || mTexImage->GetSize() != aSize || + mTexImage->GetContentType() != aContentType) { + mTexImage = + CreateTextureImage(mGL, aSize, aContentType, LOCAL_GL_CLAMP_TO_EDGE, + FlagsToGLFlags(mFlags)); + } + mTexImage->Resize(aSize); +} + +void TextureImageTextureSourceOGL::SetTextureSourceProvider( + TextureSourceProvider* aProvider) { + GLContext* newGL = aProvider ? aProvider->GetGLContext() : nullptr; + if (!newGL || mGL != newGL) { + DeallocateDeviceData(); + } + mGL = newGL; + + CompositorOGL* compositor = + aProvider ? aProvider->AsCompositorOGL() : nullptr; + if (mCompositor != compositor) { + if (mCompositor) { + mCompositor->UnregisterTextureSource(this); + } + if (compositor) { + compositor->RegisterTextureSource(this); + } + mCompositor = compositor; + } +} + +gfx::IntSize TextureImageTextureSourceOGL::GetSize() const { + if (mTexImage) { + if (mIterating) { + return mTexImage->GetTileRect().Size(); + } + return mTexImage->GetSize(); + } + NS_WARNING("Trying to query the size of an empty TextureSource."); + return gfx::IntSize(0, 0); +} + +gfx::SurfaceFormat TextureImageTextureSourceOGL::GetFormat() const { + if (mTexImage) { + return mTexImage->GetTextureFormat(); + } + NS_WARNING("Trying to query the format of an empty TextureSource."); + return gfx::SurfaceFormat::UNKNOWN; +} + +gfx::IntRect TextureImageTextureSourceOGL::GetTileRect() { + return mTexImage->GetTileRect(); +} + +void TextureImageTextureSourceOGL::BindTexture( + GLenum aTextureUnit, gfx::SamplingFilter aSamplingFilter) { + MOZ_ASSERT(mTexImage, + "Trying to bind a TextureSource that does not have an underlying " + "GL texture."); + mTexImage->BindTexture(aTextureUnit); + SetSamplingFilter(mGL, aSamplingFilter); +} + +//////////////////////////////////////////////////////////////////////// +// GLTextureSource + +GLTextureSource::GLTextureSource(TextureSourceProvider* aProvider, + GLuint aTextureHandle, GLenum aTarget, + gfx::IntSize aSize, gfx::SurfaceFormat aFormat) + : GLTextureSource(aProvider->GetGLContext(), aTextureHandle, aTarget, aSize, + aFormat) {} + +GLTextureSource::GLTextureSource(GLContext* aGL, GLuint aTextureHandle, + GLenum aTarget, gfx::IntSize aSize, + gfx::SurfaceFormat aFormat) + : mGL(aGL), + mTextureHandle(aTextureHandle), + mTextureTarget(aTarget), + mSize(aSize), + mFormat(aFormat) { + MOZ_COUNT_CTOR(GLTextureSource); +} + +GLTextureSource::~GLTextureSource() { + MOZ_COUNT_DTOR(GLTextureSource); + DeleteTextureHandle(); +} + +void GLTextureSource::DeallocateDeviceData() { DeleteTextureHandle(); } + +void GLTextureSource::DeleteTextureHandle() { + GLContext* gl = this->gl(); + if (mTextureHandle != 0 && gl && gl->MakeCurrent()) { + gl->fDeleteTextures(1, &mTextureHandle); + } + mTextureHandle = 0; +} + +void GLTextureSource::BindTexture(GLenum aTextureUnit, + gfx::SamplingFilter aSamplingFilter) { + MOZ_ASSERT(mTextureHandle != 0); + GLContext* gl = this->gl(); + if (!gl || !gl->MakeCurrent()) { + return; + } + gl->fActiveTexture(aTextureUnit); + gl->fBindTexture(mTextureTarget, mTextureHandle); + ApplySamplingFilterToBoundTexture(gl, aSamplingFilter, mTextureTarget); +} + +void GLTextureSource::SetTextureSourceProvider( + TextureSourceProvider* aProvider) { + GLContext* newGL = aProvider ? aProvider->GetGLContext() : nullptr; + if (!newGL) { + mGL = newGL; + } else if (mGL != newGL) { + gfxCriticalError() + << "GLTextureSource does not support changing compositors"; + } + + if (mNextSibling) { + mNextSibling->SetTextureSourceProvider(aProvider); + } +} + +bool GLTextureSource::IsValid() const { return !!gl() && mTextureHandle != 0; } + +//////////////////////////////////////////////////////////////////////// +// DirectMapTextureSource + +DirectMapTextureSource::DirectMapTextureSource(gl::GLContext* aContext, + gfx::DataSourceSurface* aSurface) + : GLTextureSource(aContext, 0, LOCAL_GL_TEXTURE_RECTANGLE_ARB, + aSurface->GetSize(), aSurface->GetFormat()), + mSync(0) { + MOZ_ASSERT(aSurface); + + UpdateInternal(aSurface, nullptr, nullptr, true); +} + +DirectMapTextureSource::DirectMapTextureSource(TextureSourceProvider* aProvider, + gfx::DataSourceSurface* aSurface) + : DirectMapTextureSource(aProvider->GetGLContext(), aSurface) {} + +DirectMapTextureSource::~DirectMapTextureSource() { + if (!mSync || !gl() || !gl()->MakeCurrent() || gl()->IsDestroyed()) { + return; + } + + gl()->fDeleteSync(mSync); + mSync = 0; +} + +bool DirectMapTextureSource::Update(gfx::DataSourceSurface* aSurface, + nsIntRegion* aDestRegion, + gfx::IntPoint* aSrcOffset) { + if (!aSurface) { + return false; + } + + return UpdateInternal(aSurface, aDestRegion, aSrcOffset, false); +} + +void DirectMapTextureSource::MaybeFenceTexture() { + if (!gl() || !gl()->MakeCurrent() || gl()->IsDestroyed()) { + return; + } + + if (mSync) { + gl()->fDeleteSync(mSync); + } + mSync = gl()->fFenceSync(LOCAL_GL_SYNC_GPU_COMMANDS_COMPLETE, 0); +} + +bool DirectMapTextureSource::Sync(bool aBlocking) { + if (!gl() || !gl()->MakeCurrent() || gl()->IsDestroyed()) { + // We use this function to decide whether we can unlock the texture + // and clean it up. If we return false here and for whatever reason + // the context is absent or invalid, the compositor will keep a + // reference to this texture forever. + return true; + } + + if (!mSync) { + return false; + } + + GLenum waitResult = + gl()->fClientWaitSync(mSync, LOCAL_GL_SYNC_FLUSH_COMMANDS_BIT, + aBlocking ? LOCAL_GL_TIMEOUT_IGNORED : 0); + return waitResult == LOCAL_GL_ALREADY_SIGNALED || + waitResult == LOCAL_GL_CONDITION_SATISFIED; +} + +bool DirectMapTextureSource::UpdateInternal(gfx::DataSourceSurface* aSurface, + nsIntRegion* aDestRegion, + gfx::IntPoint* aSrcOffset, + bool aInit) { + if (!gl() || !gl()->MakeCurrent()) { + return false; + } + + if (aInit) { + gl()->fGenTextures(1, &mTextureHandle); + gl()->fBindTexture(LOCAL_GL_TEXTURE_RECTANGLE_ARB, mTextureHandle); + + gl()->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB, + LOCAL_GL_TEXTURE_STORAGE_HINT_APPLE, + LOCAL_GL_STORAGE_CACHED_APPLE); + gl()->fTextureRangeAPPLE(LOCAL_GL_TEXTURE_RECTANGLE_ARB, + aSurface->Stride() * aSurface->GetSize().height, + aSurface->GetData()); + + gl()->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB, + LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE); + gl()->fTexParameteri(LOCAL_GL_TEXTURE_RECTANGLE_ARB, + LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE); + } + + MOZ_ASSERT(mTextureHandle); + + // APPLE_client_storage + gl()->fPixelStorei(LOCAL_GL_UNPACK_CLIENT_STORAGE_APPLE, LOCAL_GL_TRUE); + + nsIntRegion destRegion = aDestRegion + ? *aDestRegion + : IntRect(0, 0, aSurface->GetSize().width, + aSurface->GetSize().height); + gfx::IntPoint srcPoint = aSrcOffset ? *aSrcOffset : gfx::IntPoint(0, 0); + mFormat = gl::UploadSurfaceToTexture( + gl(), aSurface, destRegion, mTextureHandle, aSurface->GetSize(), nullptr, + aInit, srcPoint, LOCAL_GL_TEXTURE0, LOCAL_GL_TEXTURE_RECTANGLE_ARB); + + if (mSync) { + gl()->fDeleteSync(mSync); + mSync = 0; + } + + gl()->fPixelStorei(LOCAL_GL_UNPACK_CLIENT_STORAGE_APPLE, LOCAL_GL_FALSE); + return true; +} + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// SurfaceTextureHost + +#ifdef MOZ_WIDGET_ANDROID + +SurfaceTextureSource::SurfaceTextureSource( + TextureSourceProvider* aProvider, + mozilla::java::GeckoSurfaceTexture::Ref& aSurfTex, + gfx::SurfaceFormat aFormat, GLenum aTarget, GLenum aWrapMode, + gfx::IntSize aSize, bool aIgnoreTransform) + : mGL(aProvider->GetGLContext()), + mSurfTex(aSurfTex), + mFormat(aFormat), + mTextureTarget(aTarget), + mWrapMode(aWrapMode), + mSize(aSize), + mIgnoreTransform(aIgnoreTransform) {} + +void SurfaceTextureSource::BindTexture(GLenum aTextureUnit, + gfx::SamplingFilter aSamplingFilter) { + MOZ_ASSERT(mSurfTex); + GLContext* gl = this->gl(); + if (!gl || !gl->MakeCurrent()) { + NS_WARNING("Trying to bind a texture without a GLContext"); + return; + } + + gl->fActiveTexture(aTextureUnit); + gl->fBindTexture(mTextureTarget, mSurfTex->GetTexName()); + + ApplySamplingFilterToBoundTexture(gl, aSamplingFilter, mTextureTarget); +} + +void SurfaceTextureSource::SetTextureSourceProvider( + TextureSourceProvider* aProvider) { + GLContext* newGL = aProvider->GetGLContext(); + if (!newGL || mGL != newGL) { + DeallocateDeviceData(); + return; + } + + mGL = newGL; +} + +bool SurfaceTextureSource::IsValid() const { return !!gl(); } + +gfx::Matrix4x4 SurfaceTextureSource::GetTextureTransform() { + MOZ_ASSERT(mSurfTex); + + gfx::Matrix4x4 ret; + + // GetTransformMatrix() returns the transform set by the producer side of + // the SurfaceTexture. We should ignore this if we know the transform should + // be identity but the producer couldn't set it correctly, like is the + // case for AndroidNativeWindowTextureData. + if (!mIgnoreTransform) { + const auto& surf = java::sdk::SurfaceTexture::LocalRef( + java::sdk::SurfaceTexture::Ref::From(mSurfTex)); + AndroidSurfaceTexture::GetTransformMatrix(surf, &ret); + } + + return ret; +} + +void SurfaceTextureSource::DeallocateDeviceData() { mSurfTex = nullptr; } + +//////////////////////////////////////////////////////////////////////// + +SurfaceTextureHost::SurfaceTextureHost( + TextureFlags aFlags, mozilla::java::GeckoSurfaceTexture::Ref& aSurfTex, + gfx::IntSize aSize, gfx::SurfaceFormat aFormat, bool aContinuousUpdate, + bool aIgnoreTransform) + : TextureHost(aFlags), + mSurfTex(aSurfTex), + mSize(aSize), + mFormat(aFormat), + mContinuousUpdate(aContinuousUpdate), + mIgnoreTransform(aIgnoreTransform) { + if (!mSurfTex) { + return; + } + + // Continuous update makes no sense with single buffer mode + MOZ_ASSERT(!mSurfTex->IsSingleBuffer() || !mContinuousUpdate); + + mSurfTex->IncrementUse(); +} + +SurfaceTextureHost::~SurfaceTextureHost() { + if (mSurfTex) { + mSurfTex->DecrementUse(); + mSurfTex = nullptr; + } +} + +void SurfaceTextureHost::PrepareTextureSource( + CompositableTextureSourceRef& aTexture) { + if (!mContinuousUpdate && mSurfTex) { + if (!EnsureAttached()) { + return; + } + + // UpdateTexImage() advances the internal buffer queue, so we only want to + // call this once per transactionwhen we are not in continuous mode (as we + // are here). Otherwise, the SurfaceTexture content will be de-synced from + // the rest of the page in subsequent compositor passes. + mSurfTex->UpdateTexImage(); + } +} + +gl::GLContext* SurfaceTextureHost::gl() const { + return mProvider ? mProvider->GetGLContext() : nullptr; +} + +bool SurfaceTextureHost::EnsureAttached() { + GLContext* gl = this->gl(); + if (!gl || !gl->MakeCurrent()) { + return false; + } + + if (!mSurfTex) { + return false; + } + + if (!mSurfTex->IsAttachedToGLContext((int64_t)gl)) { + GLuint texName; + gl->fGenTextures(1, &texName); + if (NS_FAILED(mSurfTex->AttachToGLContext((int64_t)gl, texName))) { + return false; + } + } + + return true; +} + +bool SurfaceTextureHost::Lock() { + if (!EnsureAttached()) { + return false; + } + + if (mContinuousUpdate) { + mSurfTex->UpdateTexImage(); + } + + if (!mTextureSource) { + GLenum target = + LOCAL_GL_TEXTURE_EXTERNAL; // This is required by SurfaceTexture + GLenum wrapMode = LOCAL_GL_CLAMP_TO_EDGE; + mTextureSource = + new SurfaceTextureSource(mProvider, mSurfTex, mFormat, target, wrapMode, + mSize, mIgnoreTransform); + } + + return true; +} + +void SurfaceTextureHost::SetTextureSourceProvider( + TextureSourceProvider* aProvider) { + if (mProvider != aProvider) { + if (!aProvider || !aProvider->GetGLContext()) { + DeallocateDeviceData(); + return; + } + mProvider = aProvider; + } + + if (mTextureSource) { + mTextureSource->SetTextureSourceProvider(aProvider); + } +} + +void SurfaceTextureHost::NotifyNotUsed() { + if (mSurfTex && mSurfTex->IsSingleBuffer()) { + if (!EnsureAttached()) { + return; + } + mSurfTex->ReleaseTexImage(); + } + + TextureHost::NotifyNotUsed(); +} + +gfx::SurfaceFormat SurfaceTextureHost::GetFormat() const { return mFormat; } + +void SurfaceTextureHost::DeallocateDeviceData() { + if (mTextureSource) { + mTextureSource->DeallocateDeviceData(); + } + + if (mSurfTex) { + mSurfTex->DecrementUse(); + mSurfTex = nullptr; + } +} + +void SurfaceTextureHost::CreateRenderTexture( + const wr::ExternalImageId& aExternalImageId) { + RefPtr texture = + new wr::RenderAndroidSurfaceTextureHost(mSurfTex, mSize, mFormat, + mContinuousUpdate); + wr::RenderThread::Get()->RegisterExternalImage(wr::AsUint64(aExternalImageId), + texture.forget()); +} + +uint32_t SurfaceTextureHost::NumSubTextures() { return mSurfTex ? 1 : 0; } + +void SurfaceTextureHost::PushResourceUpdates( + wr::TransactionBuilder& aResources, ResourceUpdateOp aOp, + const Range& aImageKeys, const wr::ExternalImageId& aExtID) { + auto method = aOp == TextureHost::ADD_IMAGE + ? &wr::TransactionBuilder::AddExternalImage + : &wr::TransactionBuilder::UpdateExternalImage; + auto imageType = wr::ExternalImageType::TextureHandle( + wr::ImageBufferKind::TextureExternal); + + switch (GetFormat()) { + case gfx::SurfaceFormat::R8G8B8X8: + case gfx::SurfaceFormat::R8G8B8A8: { + MOZ_ASSERT(aImageKeys.length() == 1); + + // XXX Add RGBA handling. Temporary hack to avoid crash + // With BGRA format setting, rendering works without problem. + auto format = GetFormat() == gfx::SurfaceFormat::R8G8B8A8 + ? gfx::SurfaceFormat::B8G8R8A8 + : gfx::SurfaceFormat::B8G8R8X8; + wr::ImageDescriptor descriptor(GetSize(), format); + (aResources.*method)(aImageKeys[0], descriptor, aExtID, imageType, 0); + break; + } + default: { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + } + } +} + +void SurfaceTextureHost::PushDisplayItems(wr::DisplayListBuilder& aBuilder, + const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, + wr::ImageRendering aFilter, + const Range& aImageKeys, + PushDisplayItemFlagSet aFlags) { + switch (GetFormat()) { + case gfx::SurfaceFormat::R8G8B8X8: + case gfx::SurfaceFormat::R8G8B8A8: + case gfx::SurfaceFormat::B8G8R8A8: + case gfx::SurfaceFormat::B8G8R8X8: { + MOZ_ASSERT(aImageKeys.length() == 1); + aBuilder.PushImage( + aBounds, aClip, true, aFilter, aImageKeys[0], + !(mFlags & TextureFlags::NON_PREMULTIPLIED), + wr::ColorF{1.0f, 1.0f, 1.0f, 1.0f}, + aFlags.contains(PushDisplayItemFlag::PREFER_COMPOSITOR_SURFACE)); + break; + } + default: { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + } + } +} + +//////////////////////////////////////////////////////////////////////// +// AndroidHardwareBufferTextureHost + +/* static */ +already_AddRefed +AndroidHardwareBufferTextureHost::Create( + TextureFlags aFlags, const SurfaceDescriptorAndroidHardwareBuffer& aDesc) { + RefPtr buffer = + AndroidHardwareBuffer::FromFileDescriptor( + const_cast(aDesc.handle()), aDesc.bufferId(), + aDesc.size(), aDesc.format()); + if (!buffer) { + return nullptr; + } + RefPtr host = + new AndroidHardwareBufferTextureHost(aFlags, buffer); + return host.forget(); +} + +AndroidHardwareBufferTextureHost::AndroidHardwareBufferTextureHost( + TextureFlags aFlags, AndroidHardwareBuffer* aAndroidHardwareBuffer) + : TextureHost(aFlags), + mAndroidHardwareBuffer(aAndroidHardwareBuffer), + mEGLImage(EGL_NO_IMAGE) {} + +AndroidHardwareBufferTextureHost::~AndroidHardwareBufferTextureHost() { + DestroyEGLImage(); +} + +void AndroidHardwareBufferTextureHost::DestroyEGLImage() { + if (mEGLImage && gl()) { + const auto& gle = gl::GLContextEGL::Cast(gl()); + const auto& egl = gle->mEgl; + egl->fDestroyImage(mEGLImage); + mEGLImage = EGL_NO_IMAGE; + } +} + +void AndroidHardwareBufferTextureHost::PrepareTextureSource( + CompositableTextureSourceRef& aTextureSource) { + MOZ_ASSERT(mAndroidHardwareBuffer); + + if (!mAndroidHardwareBuffer) { + mTextureSource = nullptr; + return; + } + + if (mTextureSource) { + // We are already attached to a TextureSource, nothing to do except tell + // the compositable to use it. + aTextureSource = mTextureSource; + return; + } + + if (!gl() || !gl()->MakeCurrent()) { + mTextureSource = nullptr; + return; + } + + if (!mEGLImage) { + // XXX add crop handling for video + // Should only happen the first time. + const auto& gle = gl::GLContextEGL::Cast(gl()); + const auto& egl = gle->mEgl; + + const EGLint attrs[] = { + LOCAL_EGL_IMAGE_PRESERVED, + LOCAL_EGL_TRUE, + LOCAL_EGL_NONE, + LOCAL_EGL_NONE, + }; + + EGLClientBuffer clientBuffer = egl->mLib->fGetNativeClientBufferANDROID( + mAndroidHardwareBuffer->GetNativeBuffer()); + mEGLImage = egl->fCreateImage( + EGL_NO_CONTEXT, LOCAL_EGL_NATIVE_BUFFER_ANDROID, clientBuffer, attrs); + } + + GLenum textureTarget = LOCAL_GL_TEXTURE_EXTERNAL; + GLTextureSource* glSource = + aTextureSource ? aTextureSource->AsSourceOGL()->AsGLTextureSource() + : nullptr; + + bool shouldCreateTextureSource = + !glSource || !glSource->IsValid() || + glSource->NumCompositableRefs() > 1 || + glSource->GetTextureTarget() != textureTarget; + + if (shouldCreateTextureSource) { + GLuint textureHandle; + gl()->fGenTextures(1, &textureHandle); + gl()->fBindTexture(textureTarget, textureHandle); + gl()->fTexParameteri(textureTarget, LOCAL_GL_TEXTURE_WRAP_T, + LOCAL_GL_CLAMP_TO_EDGE); + gl()->fTexParameteri(textureTarget, LOCAL_GL_TEXTURE_WRAP_S, + LOCAL_GL_CLAMP_TO_EDGE); + gl()->fEGLImageTargetTexture2D(textureTarget, mEGLImage); + + mTextureSource = new GLTextureSource(mProvider, textureHandle, + textureTarget, GetSize(), GetFormat()); + aTextureSource = mTextureSource; + } else { + gl()->fBindTexture(textureTarget, glSource->GetTextureHandle()); + gl()->fEGLImageTargetTexture2D(textureTarget, mEGLImage); + glSource->SetSize(GetSize()); + glSource->SetFormat(GetFormat()); + mTextureSource = glSource; + } +} + +bool AndroidHardwareBufferTextureHost::BindTextureSource( + CompositableTextureSourceRef& aTextureSource) { + // This happens at composition time. + + // If mTextureSource is null it means PrepareTextureSource failed. + if (!mTextureSource) { + return false; + } + + // If Prepare didn't fail, we expect our TextureSource to be the same as + // aTextureSource, otherwise it means something has fiddled with the + // TextureSource between Prepare and now. + MOZ_ASSERT(mTextureSource == aTextureSource); + aTextureSource = mTextureSource; + + // XXX Acquire Fence Handling + return true; +} + +gl::GLContext* AndroidHardwareBufferTextureHost::gl() const { + return mProvider ? mProvider->GetGLContext() : nullptr; +} + +bool AndroidHardwareBufferTextureHost::Lock() { + if (!mAndroidHardwareBuffer) { + return false; + } + + auto fenceFd = mAndroidHardwareBuffer->GetAndResetAcquireFence(); + if (fenceFd.IsValid()) { + const auto& gle = gl::GLContextEGL::Cast(gl()); + const auto& egl = gle->mEgl; + + auto rawFD = fenceFd.TakePlatformHandle(); + const EGLint attribs[] = {LOCAL_EGL_SYNC_NATIVE_FENCE_FD_ANDROID, + rawFD.get(), LOCAL_EGL_NONE}; + + EGLSync sync = + egl->fCreateSync(LOCAL_EGL_SYNC_NATIVE_FENCE_ANDROID, attribs); + if (sync) { + // Release fd here, since it is owned by EGLSync + Unused << rawFD.release(); + + if (egl->IsExtensionSupported(gl::EGLExtension::KHR_wait_sync)) { + egl->fWaitSync(sync, 0); + } else { + egl->fClientWaitSync(sync, 0, LOCAL_EGL_FOREVER); + } + egl->fDestroySync(sync); + } else { + gfxCriticalNote << "Failed to create EGLSync from acquire fence fd"; + } + } + + return mTextureSource && mTextureSource->IsValid(); +} + +void AndroidHardwareBufferTextureHost::SetTextureSourceProvider( + TextureSourceProvider* aProvider) { + if (mProvider != aProvider) { + if (!aProvider || !aProvider->GetGLContext()) { + DeallocateDeviceData(); + return; + } + mProvider = aProvider; + } + + if (mTextureSource) { + mTextureSource->SetTextureSourceProvider(aProvider); + } +} + +void AndroidHardwareBufferTextureHost::NotifyNotUsed() { + // XXX Add android fence handling + TextureHost::NotifyNotUsed(); +} + +gfx::SurfaceFormat AndroidHardwareBufferTextureHost::GetFormat() const { + if (mAndroidHardwareBuffer) { + return mAndroidHardwareBuffer->mFormat; + } + return gfx::SurfaceFormat::UNKNOWN; +} + +gfx::IntSize AndroidHardwareBufferTextureHost::GetSize() const { + if (mAndroidHardwareBuffer) { + return mAndroidHardwareBuffer->mSize; + } + return gfx::IntSize(); +} + +void AndroidHardwareBufferTextureHost::DeallocateDeviceData() { + if (mTextureSource) { + mTextureSource = nullptr; + } + DestroyEGLImage(); +} + +void AndroidHardwareBufferTextureHost::SetAcquireFence( + mozilla::ipc::FileDescriptor&& aFenceFd) { + if (!mAndroidHardwareBuffer) { + return; + } + mAndroidHardwareBuffer->SetAcquireFence(std::move(aFenceFd)); +} + +void AndroidHardwareBufferTextureHost::SetReleaseFence( + mozilla::ipc::FileDescriptor&& aFenceFd) { + if (!mAndroidHardwareBuffer) { + return; + } + mAndroidHardwareBuffer->SetReleaseFence(std::move(aFenceFd)); +} + +mozilla::ipc::FileDescriptor +AndroidHardwareBufferTextureHost::GetAndResetReleaseFence() { + if (!mAndroidHardwareBuffer) { + return mozilla::ipc::FileDescriptor(); + } + return mAndroidHardwareBuffer->GetAndResetReleaseFence(); +} + +void AndroidHardwareBufferTextureHost::CreateRenderTexture( + const wr::ExternalImageId& aExternalImageId) { + RefPtr texture = + new wr::RenderAndroidHardwareBufferTextureHost(mAndroidHardwareBuffer); + wr::RenderThread::Get()->RegisterExternalImage(wr::AsUint64(aExternalImageId), + texture.forget()); +} + +uint32_t AndroidHardwareBufferTextureHost::NumSubTextures() { + return mAndroidHardwareBuffer ? 1 : 0; +} + +void AndroidHardwareBufferTextureHost::PushResourceUpdates( + wr::TransactionBuilder& aResources, ResourceUpdateOp aOp, + const Range& aImageKeys, const wr::ExternalImageId& aExtID) { + auto method = aOp == TextureHost::ADD_IMAGE + ? &wr::TransactionBuilder::AddExternalImage + : &wr::TransactionBuilder::UpdateExternalImage; + auto imageType = wr::ExternalImageType::TextureHandle( + wr::ImageBufferKind::TextureExternal); + + switch (GetFormat()) { + case gfx::SurfaceFormat::R8G8B8X8: + case gfx::SurfaceFormat::R8G8B8A8: { + MOZ_ASSERT(aImageKeys.length() == 1); + + // XXX Add RGBA handling. Temporary hack to avoid crash + // With BGRA format setting, rendering works without problem. + auto format = GetFormat() == gfx::SurfaceFormat::R8G8B8A8 + ? gfx::SurfaceFormat::B8G8R8A8 + : gfx::SurfaceFormat::B8G8R8X8; + wr::ImageDescriptor descriptor(GetSize(), format); + (aResources.*method)(aImageKeys[0], descriptor, aExtID, imageType, 0); + break; + } + default: { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + } + } +} + +void AndroidHardwareBufferTextureHost::PushDisplayItems( + wr::DisplayListBuilder& aBuilder, const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, wr::ImageRendering aFilter, + const Range& aImageKeys, PushDisplayItemFlagSet aFlags) { + switch (GetFormat()) { + case gfx::SurfaceFormat::R8G8B8X8: + case gfx::SurfaceFormat::R8G8B8A8: + case gfx::SurfaceFormat::B8G8R8A8: + case gfx::SurfaceFormat::B8G8R8X8: { + MOZ_ASSERT(aImageKeys.length() == 1); + aBuilder.PushImage( + aBounds, aClip, true, aFilter, aImageKeys[0], + !(mFlags & TextureFlags::NON_PREMULTIPLIED), + wr::ColorF{1.0f, 1.0f, 1.0f, 1.0f}, + aFlags.contains(PushDisplayItemFlag::PREFER_COMPOSITOR_SURFACE)); + break; + } + default: { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + } + } +} + +#endif // MOZ_WIDGET_ANDROID + +//////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// EGLImage + +EGLImageTextureSource::EGLImageTextureSource(TextureSourceProvider* aProvider, + EGLImage aImage, + gfx::SurfaceFormat aFormat, + GLenum aTarget, GLenum aWrapMode, + gfx::IntSize aSize) + : mImage(aImage), + mFormat(aFormat), + mTextureTarget(aTarget), + mWrapMode(aWrapMode), + mSize(aSize) { + MOZ_ASSERT(mTextureTarget == LOCAL_GL_TEXTURE_2D || + mTextureTarget == LOCAL_GL_TEXTURE_EXTERNAL); + SetTextureSourceProvider(aProvider); +} + +void EGLImageTextureSource::BindTexture(GLenum aTextureUnit, + gfx::SamplingFilter aSamplingFilter) { + GLContext* gl = this->gl(); + if (!gl || !gl->MakeCurrent()) { + NS_WARNING("Trying to bind a texture without a GLContext"); + return; + } + +#ifdef DEBUG + const bool supportsEglImage = [&]() { + const auto& gle = GLContextEGL::Cast(gl); + const auto& egl = gle->mEgl; + + return egl->HasKHRImageBase() && + egl->IsExtensionSupported(EGLExtension::KHR_gl_texture_2D_image) && + gl->IsExtensionSupported(GLContext::OES_EGL_image); + }(); + MOZ_ASSERT(supportsEglImage, "EGLImage not supported or disabled in runtime"); +#endif + + GLuint tex = mCompositor->GetTemporaryTexture(mTextureTarget, aTextureUnit); + + gl->fActiveTexture(aTextureUnit); + gl->fBindTexture(mTextureTarget, tex); + + gl->fEGLImageTargetTexture2D(mTextureTarget, mImage); + + ApplySamplingFilterToBoundTexture(gl, aSamplingFilter, mTextureTarget); +} + +void EGLImageTextureSource::SetTextureSourceProvider( + TextureSourceProvider* aProvider) { + if (mCompositor == aProvider) { + return; + } + + if (!aProvider) { + mGL = nullptr; + mCompositor = nullptr; + return; + } + + mGL = aProvider->GetGLContext(); + if (Compositor* compositor = aProvider->AsCompositor()) { + mCompositor = compositor->AsCompositorOGL(); + } +} + +bool EGLImageTextureSource::IsValid() const { return !!gl(); } + +gfx::Matrix4x4 EGLImageTextureSource::GetTextureTransform() { + gfx::Matrix4x4 ret; + return ret; +} + +//////////////////////////////////////////////////////////////////////// + +EGLImageTextureHost::EGLImageTextureHost(TextureFlags aFlags, EGLImage aImage, + EGLSync aSync, gfx::IntSize aSize, + bool hasAlpha) + : TextureHost(aFlags), + mImage(aImage), + mSync(aSync), + mSize(aSize), + mHasAlpha(hasAlpha) {} + +EGLImageTextureHost::~EGLImageTextureHost() = default; + +gl::GLContext* EGLImageTextureHost::gl() const { + return mProvider ? mProvider->GetGLContext() : nullptr; +} + +bool EGLImageTextureHost::Lock() { + GLContext* gl = this->gl(); + if (!gl || !gl->MakeCurrent()) { + return false; + } + const auto& gle = GLContextEGL::Cast(gl); + const auto& egl = gle->mEgl; + + EGLint status = LOCAL_EGL_CONDITION_SATISFIED; + + if (mSync) { + MOZ_ASSERT(egl->IsExtensionSupported(EGLExtension::KHR_fence_sync)); + // XXX eglWaitSyncKHR() is better api. Bug 1660434 is going to fix it. + status = egl->fClientWaitSync(mSync, 0, LOCAL_EGL_FOREVER); + } + + if (status != LOCAL_EGL_CONDITION_SATISFIED) { + MOZ_ASSERT( + status != 0, + "ClientWaitSync generated an error. Has mSync already been destroyed?"); + return false; + } + + if (!mTextureSource) { + gfx::SurfaceFormat format = + mHasAlpha ? gfx::SurfaceFormat::R8G8B8A8 : gfx::SurfaceFormat::R8G8B8X8; + GLenum target = gl->GetPreferredEGLImageTextureTarget(); + GLenum wrapMode = LOCAL_GL_CLAMP_TO_EDGE; + mTextureSource = new EGLImageTextureSource(mProvider, mImage, format, + target, wrapMode, mSize); + } + + return true; +} + +void EGLImageTextureHost::Unlock() {} + +void EGLImageTextureHost::SetTextureSourceProvider( + TextureSourceProvider* aProvider) { + if (mProvider != aProvider) { + if (!aProvider || !aProvider->GetGLContext()) { + mProvider = nullptr; + mTextureSource = nullptr; + return; + } + mProvider = aProvider; + } + + if (mTextureSource) { + mTextureSource->SetTextureSourceProvider(aProvider); + } +} + +gfx::SurfaceFormat EGLImageTextureHost::GetFormat() const { + MOZ_ASSERT(mTextureSource); + return mTextureSource ? mTextureSource->GetFormat() + : gfx::SurfaceFormat::UNKNOWN; +} + +void EGLImageTextureHost::CreateRenderTexture( + const wr::ExternalImageId& aExternalImageId) { + RefPtr texture = + new wr::RenderEGLImageTextureHost(mImage, mSync, mSize); + wr::RenderThread::Get()->RegisterExternalImage(wr::AsUint64(aExternalImageId), + texture.forget()); +} + +void EGLImageTextureHost::PushResourceUpdates( + wr::TransactionBuilder& aResources, ResourceUpdateOp aOp, + const Range& aImageKeys, const wr::ExternalImageId& aExtID) { + auto method = aOp == TextureHost::ADD_IMAGE + ? &wr::TransactionBuilder::AddExternalImage + : &wr::TransactionBuilder::UpdateExternalImage; + auto imageType = wr::ExternalImageType::TextureHandle( + wr::ImageBufferKind::TextureExternal); + + gfx::SurfaceFormat format = + mHasAlpha ? gfx::SurfaceFormat::R8G8B8A8 : gfx::SurfaceFormat::R8G8B8X8; + + MOZ_ASSERT(aImageKeys.length() == 1); + // XXX Add RGBA handling. Temporary hack to avoid crash + // With BGRA format setting, rendering works without problem. + auto formatTmp = format == gfx::SurfaceFormat::R8G8B8A8 + ? gfx::SurfaceFormat::B8G8R8A8 + : gfx::SurfaceFormat::B8G8R8X8; + wr::ImageDescriptor descriptor(GetSize(), formatTmp); + (aResources.*method)(aImageKeys[0], descriptor, aExtID, imageType, 0); +} + +void EGLImageTextureHost::PushDisplayItems( + wr::DisplayListBuilder& aBuilder, const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, wr::ImageRendering aFilter, + const Range& aImageKeys, PushDisplayItemFlagSet aFlags) { + MOZ_ASSERT(aImageKeys.length() == 1); + aBuilder.PushImage( + aBounds, aClip, true, aFilter, aImageKeys[0], + !(mFlags & TextureFlags::NON_PREMULTIPLIED), + wr::ColorF{1.0f, 1.0f, 1.0f, 1.0f}, + aFlags.contains(PushDisplayItemFlag::PREFER_COMPOSITOR_SURFACE)); +} + +// + +GLTextureHost::GLTextureHost(TextureFlags aFlags, GLuint aTextureHandle, + GLenum aTarget, GLsync aSync, gfx::IntSize aSize, + bool aHasAlpha) + : TextureHost(aFlags), + mTexture(aTextureHandle), + mTarget(aTarget), + mSync(aSync), + mSize(aSize), + mHasAlpha(aHasAlpha) {} + +GLTextureHost::~GLTextureHost() = default; + +gl::GLContext* GLTextureHost::gl() const { + return mProvider ? mProvider->GetGLContext() : nullptr; +} + +bool GLTextureHost::Lock() { + GLContext* gl = this->gl(); + if (!gl || !gl->MakeCurrent()) { + return false; + } + + if (mSync) { + if (!gl->MakeCurrent()) { + return false; + } + gl->fWaitSync(mSync, 0, LOCAL_GL_TIMEOUT_IGNORED); + gl->fDeleteSync(mSync); + mSync = 0; + } + + if (!mTextureSource) { + gfx::SurfaceFormat format = + mHasAlpha ? gfx::SurfaceFormat::R8G8B8A8 : gfx::SurfaceFormat::R8G8B8X8; + mTextureSource = + new GLTextureSource(mProvider, mTexture, mTarget, mSize, format); + } + + return true; +} + +void GLTextureHost::SetTextureSourceProvider(TextureSourceProvider* aProvider) { + if (mProvider != aProvider) { + if (!aProvider || !aProvider->GetGLContext()) { + mProvider = nullptr; + mTextureSource = nullptr; + return; + } + mProvider = aProvider; + } + + if (mTextureSource) { + mTextureSource->SetTextureSourceProvider(aProvider); + } +} + +gfx::SurfaceFormat GLTextureHost::GetFormat() const { + MOZ_ASSERT(mTextureSource); + return mTextureSource ? mTextureSource->GetFormat() + : gfx::SurfaceFormat::UNKNOWN; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/opengl/TextureHostOGL.h b/gfx/layers/opengl/TextureHostOGL.h new file mode 100644 index 0000000000..70fbfe56a4 --- /dev/null +++ b/gfx/layers/opengl/TextureHostOGL.h @@ -0,0 +1,653 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_TEXTUREOGL_H +#define MOZILLA_GFX_TEXTUREOGL_H + +#include // for size_t +#include // for uint64_t +#include "CompositableHost.h" +#include "GLContextTypes.h" // for GLContext +#include "GLDefs.h" // for GLenum, LOCAL_GL_CLAMP_TO_EDGE, etc +#include "GLTextureImage.h" // for TextureImage +#include "gfxTypes.h" +#include "mozilla/GfxMessageUtils.h" // for gfxContentType +#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc +#include "mozilla/Attributes.h" // for override +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/gfx/Matrix.h" // for Matrix4x4 +#include "mozilla/gfx/Point.h" // for IntSize, IntPoint +#include "mozilla/gfx/Types.h" // for SurfaceFormat, etc +#include "mozilla/layers/CompositorOGL.h" // for CompositorOGL +#include "mozilla/layers/CompositorTypes.h" // for TextureFlags +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor +#include "mozilla/layers/TextureHost.h" // for TextureHost, etc +#include "mozilla/mozalloc.h" // for operator delete, etc +#include "mozilla/webrender/RenderThread.h" +#include "nsCOMPtr.h" // for already_AddRefed +#include "nsDebug.h" // for NS_WARNING +#include "nsISupportsImpl.h" // for TextureImage::Release, etc +#include "nsRegionFwd.h" // for nsIntRegion + +#ifdef MOZ_WIDGET_ANDROID +# include "AndroidSurfaceTexture.h" +# include "mozilla/java/GeckoSurfaceTextureWrappers.h" +#endif + +namespace mozilla { +namespace gfx { +class DataSourceSurface; +} // namespace gfx + +namespace layers { + +class Compositor; +class CompositorOGL; +class AndroidHardwareBuffer; +class SurfaceDescriptorAndroidHardwareBuffer; +class TextureImageTextureSourceOGL; +class GLTextureSource; + +void ApplySamplingFilterToBoundTexture(gl::GLContext* aGL, + gfx::SamplingFilter aSamplingFilter, + GLuint aTarget = LOCAL_GL_TEXTURE_2D); + +already_AddRefed CreateTextureHostOGL( + const SurfaceDescriptor& aDesc, ISurfaceAllocator* aDeallocator, + LayersBackend aBackend, TextureFlags aFlags); + +/* + * TextureHost implementations for the OpenGL backend. + * + * Note that it is important to be careful about the ownership model with + * the OpenGL backend, due to some widget limitation on Linux: before + * the nsBaseWidget associated with our OpenGL context has been completely + * deleted, every resource belonging to the OpenGL context MUST have been + * released. At the moment the teardown sequence happens in the middle of + * the nsBaseWidget's destructor, meaning that at a given moment we must be + * able to easily find and release all the GL resources. + * The point is: be careful about the ownership model and limit the number + * of objects sharing references to GL resources to make the tear down + * sequence as simple as possible. + */ + +/** + * TextureSourceOGL provides the necessary API for CompositorOGL to composite + * a TextureSource. + */ +class TextureSourceOGL { + public: + TextureSourceOGL() + : mCachedSamplingFilter(gfx::SamplingFilter::GOOD), + mHasCachedSamplingFilter(false) {} + + virtual bool IsValid() const = 0; + + virtual void BindTexture(GLenum aTextureUnit, + gfx::SamplingFilter aSamplingFilter) = 0; + + // To be overridden in textures that need this. This method will be called + // when the compositor has used the texture to draw. This allows us to set + // a fence with glFenceSync which we can wait on later to ensure the GPU + // is done with the draw calls using that texture. We would like to be able + // to simply use glFinishObjectAPPLE, but this returns earlier than + // expected with nvidia drivers. + virtual void MaybeFenceTexture() {} + + virtual gfx::IntSize GetSize() const = 0; + + virtual GLenum GetTextureTarget() const { return LOCAL_GL_TEXTURE_2D; } + + virtual gfx::SurfaceFormat GetFormat() const = 0; + + virtual GLenum GetWrapMode() const = 0; + + virtual gfx::Matrix4x4 GetTextureTransform() { return gfx::Matrix4x4(); } + + virtual TextureImageTextureSourceOGL* AsTextureImageTextureSource() { + return nullptr; + } + + virtual GLTextureSource* AsGLTextureSource() { return nullptr; } + + void SetSamplingFilter(gl::GLContext* aGL, + gfx::SamplingFilter aSamplingFilter) { + if (mHasCachedSamplingFilter && mCachedSamplingFilter == aSamplingFilter) { + return; + } + mHasCachedSamplingFilter = true; + mCachedSamplingFilter = aSamplingFilter; + ApplySamplingFilterToBoundTexture(aGL, aSamplingFilter, GetTextureTarget()); + } + + void ClearCachedFilter() { mHasCachedSamplingFilter = false; } + + private: + gfx::SamplingFilter mCachedSamplingFilter; + bool mHasCachedSamplingFilter; +}; + +/** + * A TextureSource backed by a TextureImage. + * + * Depending on the underlying TextureImage, may support texture tiling, so + * make sure to check AsBigImageIterator() and use the texture accordingly. + * + * This TextureSource can be used without a TextureHost and manage it's own + * GL texture(s). + */ +class TextureImageTextureSourceOGL final : public DataTextureSource, + public TextureSourceOGL, + public BigImageIterator { + public: + explicit TextureImageTextureSourceOGL( + CompositorOGL* aCompositor, TextureFlags aFlags = TextureFlags::DEFAULT); + + const char* Name() const override { return "TextureImageTextureSourceOGL"; } + // DataTextureSource + + bool Update(gfx::DataSourceSurface* aSurface, + nsIntRegion* aDestRegion = nullptr, + gfx::IntPoint* aSrcOffset = nullptr) override; + + void EnsureBuffer(const gfx::IntSize& aSize, gfxContentType aContentType); + + TextureImageTextureSourceOGL* AsTextureImageTextureSource() override { + return this; + } + + // TextureSource + + void DeallocateDeviceData() override; + + TextureSourceOGL* AsSourceOGL() override { return this; } + + void BindTexture(GLenum aTextureUnit, + gfx::SamplingFilter aSamplingFilter) override; + + gfx::IntSize GetSize() const override; + + gfx::SurfaceFormat GetFormat() const override; + + bool IsValid() const override { return !!mTexImage; } + + void SetTextureSourceProvider(TextureSourceProvider* aProvider) override; + + GLenum GetWrapMode() const override { return mTexImage->GetWrapMode(); } + + // BigImageIterator + + BigImageIterator* AsBigImageIterator() override { return this; } + + void BeginBigImageIteration() override { + mTexImage->BeginBigImageIteration(); + mIterating = true; + } + + void EndBigImageIteration() override { mIterating = false; } + + gfx::IntRect GetTileRect() override; + + size_t GetTileCount() override { return mTexImage->GetTileCount(); } + + bool NextTile() override { return mTexImage->NextTile(); } + + protected: + ~TextureImageTextureSourceOGL(); + + RefPtr mTexImage; + RefPtr mGL; + RefPtr mCompositor; + TextureFlags mFlags; + bool mIterating; +}; + +/** + * A texture source for GL textures. + * + * It does not own any GL texture, and attaches its shared handle to one of + * the compositor's temporary textures when binding. + * + * The shared texture handle is owned by the TextureHost. + */ +class GLTextureSource : public DataTextureSource, public TextureSourceOGL { + public: + GLTextureSource(TextureSourceProvider* aProvider, GLuint aTextureHandle, + GLenum aTarget, gfx::IntSize aSize, + gfx::SurfaceFormat aFormat); + + GLTextureSource(gl::GLContext* aGL, GLuint aTextureHandle, GLenum aTarget, + gfx::IntSize aSize, gfx::SurfaceFormat aFormat); + + virtual ~GLTextureSource(); + + const char* Name() const override { return "GLTextureSource"; } + + GLTextureSource* AsGLTextureSource() override { return this; } + + TextureSourceOGL* AsSourceOGL() override { return this; } + + void BindTexture(GLenum activetex, + gfx::SamplingFilter aSamplingFilter) override; + + bool IsValid() const override; + + gfx::IntSize GetSize() const override { return mSize; } + + gfx::SurfaceFormat GetFormat() const override { return mFormat; } + + GLenum GetTextureTarget() const override { return mTextureTarget; } + + GLenum GetWrapMode() const override { return LOCAL_GL_CLAMP_TO_EDGE; } + + void DeallocateDeviceData() override; + + void SetTextureSourceProvider(TextureSourceProvider* aProvider) override; + + void SetSize(gfx::IntSize aSize) { mSize = aSize; } + + void SetFormat(gfx::SurfaceFormat aFormat) { mFormat = aFormat; } + + GLuint GetTextureHandle() const { return mTextureHandle; } + + gl::GLContext* gl() const { return mGL; } + + bool Update(gfx::DataSourceSurface* aSurface, + nsIntRegion* aDestRegion = nullptr, + gfx::IntPoint* aSrcOffset = nullptr) override { + return false; + } + + protected: + void DeleteTextureHandle(); + + RefPtr mGL; + RefPtr mCompositor; + GLuint mTextureHandle; + GLenum mTextureTarget; + gfx::IntSize mSize; + gfx::SurfaceFormat mFormat; +}; + +// This texture source try to wrap "aSurface" in ctor for compositor direct +// access. Since we can't know the timing for gpu buffer access, the surface +// should be alive until the ~ClientStorageTextureSource(). And if we try to +// update the surface we mapped before, we need to call Sync() to make sure +// the surface is not used by compositor. +class DirectMapTextureSource : public GLTextureSource { + public: + DirectMapTextureSource(gl::GLContext* aContext, + gfx::DataSourceSurface* aSurface); + DirectMapTextureSource(TextureSourceProvider* aProvider, + gfx::DataSourceSurface* aSurface); + ~DirectMapTextureSource(); + + bool Update(gfx::DataSourceSurface* aSurface, + nsIntRegion* aDestRegion = nullptr, + gfx::IntPoint* aSrcOffset = nullptr) override; + + bool IsDirectMap() override { return true; } + + // If aBlocking is false, check if this texture is no longer being used + // by the GPU - if aBlocking is true, this will block until the GPU is + // done with it. + bool Sync(bool aBlocking) override; + + void MaybeFenceTexture() override; + + private: + bool UpdateInternal(gfx::DataSourceSurface* aSurface, + nsIntRegion* aDestRegion, gfx::IntPoint* aSrcOffset, + bool aInit); + + GLsync mSync; +}; + +class GLTextureHost : public TextureHost { + public: + GLTextureHost(TextureFlags aFlags, GLuint aTextureHandle, GLenum aTarget, + GLsync aSync, gfx::IntSize aSize, bool aHasAlpha); + + virtual ~GLTextureHost(); + + // We don't own anything. + void DeallocateDeviceData() override {} + + void SetTextureSourceProvider(TextureSourceProvider* aProvider) override; + + bool Lock() override; + + void Unlock() override {} + + gfx::SurfaceFormat GetFormat() const override; + + bool BindTextureSource(CompositableTextureSourceRef& aTexture) override { + aTexture = mTextureSource; + return !!aTexture; + } + + already_AddRefed GetAsSurface() override { + return nullptr; // XXX - implement this (for MOZ_DUMP_PAINTING) + } + + gl::GLContext* gl() const; + + gfx::IntSize GetSize() const override { return mSize; } + + const char* Name() override { return "GLTextureHost"; } + + protected: + const GLuint mTexture; + const GLenum mTarget; + GLsync mSync; + const gfx::IntSize mSize; + const bool mHasAlpha; + RefPtr mTextureSource; +}; + +//////////////////////////////////////////////////////////////////////// +// SurfaceTexture + +#ifdef MOZ_WIDGET_ANDROID + +class SurfaceTextureSource : public TextureSource, public TextureSourceOGL { + public: + SurfaceTextureSource(TextureSourceProvider* aProvider, + java::GeckoSurfaceTexture::Ref& aSurfTex, + gfx::SurfaceFormat aFormat, GLenum aTarget, + GLenum aWrapMode, gfx::IntSize aSize, + bool aIgnoreTransform); + + const char* Name() const override { return "SurfaceTextureSource"; } + + TextureSourceOGL* AsSourceOGL() override { return this; } + + void BindTexture(GLenum activetex, + gfx::SamplingFilter aSamplingFilter) override; + + bool IsValid() const override; + + gfx::IntSize GetSize() const override { return mSize; } + + gfx::SurfaceFormat GetFormat() const override { return mFormat; } + + gfx::Matrix4x4 GetTextureTransform() override; + + GLenum GetTextureTarget() const override { return mTextureTarget; } + + GLenum GetWrapMode() const override { return mWrapMode; } + + void DeallocateDeviceData() override; + + void SetTextureSourceProvider(TextureSourceProvider* aProvider) override; + + gl::GLContext* gl() const { return mGL; } + + protected: + RefPtr mGL; + mozilla::java::GeckoSurfaceTexture::GlobalRef mSurfTex; + const gfx::SurfaceFormat mFormat; + const GLenum mTextureTarget; + const GLenum mWrapMode; + const gfx::IntSize mSize; + const bool mIgnoreTransform; +}; + +class SurfaceTextureHost : public TextureHost { + public: + SurfaceTextureHost(TextureFlags aFlags, + mozilla::java::GeckoSurfaceTexture::Ref& aSurfTex, + gfx::IntSize aSize, gfx::SurfaceFormat aFormat, + bool aContinuousUpdate, bool aIgnoreTransform); + + virtual ~SurfaceTextureHost(); + + void PrepareTextureSource(CompositableTextureSourceRef& aTexture) override; + + void DeallocateDeviceData() override; + + void SetTextureSourceProvider(TextureSourceProvider* aProvider) override; + + bool Lock() override; + + gfx::SurfaceFormat GetFormat() const override; + + void NotifyNotUsed() override; + + bool BindTextureSource(CompositableTextureSourceRef& aTexture) override { + aTexture = mTextureSource; + return !!aTexture; + } + + already_AddRefed GetAsSurface() override { + return nullptr; // XXX - implement this (for MOZ_DUMP_PAINTING) + } + + gl::GLContext* gl() const; + + gfx::IntSize GetSize() const override { return mSize; } + + const char* Name() override { return "SurfaceTextureHost"; } + + SurfaceTextureHost* AsSurfaceTextureHost() override { return this; } + + void CreateRenderTexture( + const wr::ExternalImageId& aExternalImageId) override; + + uint32_t NumSubTextures() override; + + void PushResourceUpdates(wr::TransactionBuilder& aResources, + ResourceUpdateOp aOp, + const Range& aImageKeys, + const wr::ExternalImageId& aExtID) override; + + void PushDisplayItems(wr::DisplayListBuilder& aBuilder, + const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, wr::ImageRendering aFilter, + const Range& aImageKeys, + PushDisplayItemFlagSet aFlags) override; + + // gecko does not need deferred deletion with WebRender + // GPU/hardware task end could be checked by android fence. + // SurfaceTexture uses android fence internally, + bool NeedsDeferredDeletion() const override { return false; } + + protected: + bool EnsureAttached(); + + mozilla::java::GeckoSurfaceTexture::GlobalRef mSurfTex; + const gfx::IntSize mSize; + const gfx::SurfaceFormat mFormat; + bool mContinuousUpdate; + const bool mIgnoreTransform; + RefPtr mCompositor; + RefPtr mTextureSource; +}; + +class AndroidHardwareBufferTextureHost : public TextureHost { + public: + static already_AddRefed Create( + TextureFlags aFlags, const SurfaceDescriptorAndroidHardwareBuffer& aDesc); + + AndroidHardwareBufferTextureHost( + TextureFlags aFlags, AndroidHardwareBuffer* aAndroidHardwareBuffer); + + virtual ~AndroidHardwareBufferTextureHost(); + + void PrepareTextureSource( + CompositableTextureSourceRef& aTextureSource) override; + + bool BindTextureSource(CompositableTextureSourceRef& aTexture) override; + + void DeallocateDeviceData() override; + + void SetTextureSourceProvider(TextureSourceProvider* aProvider) override; + + bool Lock() override; + + gfx::SurfaceFormat GetFormat() const override; + + gfx::IntSize GetSize() const override; + + void NotifyNotUsed() override; + + already_AddRefed GetAsSurface() override { + return nullptr; // XXX - implement this (for MOZ_DUMP_PAINTING) + } + + gl::GLContext* gl() const; + + const char* Name() override { return "AndroidHardwareBufferTextureHost"; } + + AndroidHardwareBufferTextureHost* AsAndroidHardwareBufferTextureHost() + override { + return this; + } + + void CreateRenderTexture( + const wr::ExternalImageId& aExternalImageId) override; + + uint32_t NumSubTextures() override; + + void PushResourceUpdates(wr::TransactionBuilder& aResources, + ResourceUpdateOp aOp, + const Range& aImageKeys, + const wr::ExternalImageId& aExtID) override; + + void PushDisplayItems(wr::DisplayListBuilder& aBuilder, + const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, wr::ImageRendering aFilter, + const Range& aImageKeys, + PushDisplayItemFlagSet aFlags) override; + + void SetAcquireFence(mozilla::ipc::FileDescriptor&& aFenceFd) override; + + void SetReleaseFence(mozilla::ipc::FileDescriptor&& aFenceFd) override; + + mozilla::ipc::FileDescriptor GetAndResetReleaseFence() override; + + AndroidHardwareBuffer* GetAndroidHardwareBuffer() const override { + return mAndroidHardwareBuffer; + } + + // gecko does not need deferred deletion with WebRender + // GPU/hardware task end could be checked by android fence. + bool NeedsDeferredDeletion() const override { return false; } + + protected: + void DestroyEGLImage(); + + RefPtr mAndroidHardwareBuffer; + RefPtr mTextureSource; + EGLImage mEGLImage; +}; + +#endif // MOZ_WIDGET_ANDROID + +//////////////////////////////////////////////////////////////////////// +// EGLImage + +class EGLImageTextureSource : public TextureSource, public TextureSourceOGL { + public: + EGLImageTextureSource(TextureSourceProvider* aProvider, EGLImage aImage, + gfx::SurfaceFormat aFormat, GLenum aTarget, + GLenum aWrapMode, gfx::IntSize aSize); + + const char* Name() const override { return "EGLImageTextureSource"; } + + TextureSourceOGL* AsSourceOGL() override { return this; } + + void BindTexture(GLenum activetex, + gfx::SamplingFilter aSamplingFilter) override; + + bool IsValid() const override; + + gfx::IntSize GetSize() const override { return mSize; } + + gfx::SurfaceFormat GetFormat() const override { return mFormat; } + + gfx::Matrix4x4 GetTextureTransform() override; + + GLenum GetTextureTarget() const override { return mTextureTarget; } + + GLenum GetWrapMode() const override { return mWrapMode; } + + // We don't own anything. + void DeallocateDeviceData() override {} + + void SetTextureSourceProvider(TextureSourceProvider* aProvider) override; + + gl::GLContext* gl() const { return mGL; } + + protected: + RefPtr mGL; + RefPtr mCompositor; + const EGLImage mImage; + const gfx::SurfaceFormat mFormat; + const GLenum mTextureTarget; + const GLenum mWrapMode; + const gfx::IntSize mSize; +}; + +class EGLImageTextureHost final : public TextureHost { + public: + EGLImageTextureHost(TextureFlags aFlags, EGLImage aImage, EGLSync aSync, + gfx::IntSize aSize, bool hasAlpha); + + virtual ~EGLImageTextureHost(); + + // We don't own anything. + void DeallocateDeviceData() override {} + + void SetTextureSourceProvider(TextureSourceProvider* aProvider) override; + + bool Lock() override; + + void Unlock() override; + + gfx::SurfaceFormat GetFormat() const override; + + bool BindTextureSource(CompositableTextureSourceRef& aTexture) override { + aTexture = mTextureSource; + return !!aTexture; + } + + already_AddRefed GetAsSurface() override { + return nullptr; // XXX - implement this (for MOZ_DUMP_PAINTING) + } + + gl::GLContext* gl() const; + + gfx::IntSize GetSize() const override { return mSize; } + + const char* Name() override { return "EGLImageTextureHost"; } + + void CreateRenderTexture( + const wr::ExternalImageId& aExternalImageId) override; + + void PushResourceUpdates(wr::TransactionBuilder& aResources, + ResourceUpdateOp aOp, + const Range& aImageKeys, + const wr::ExternalImageId& aExtID) override; + + void PushDisplayItems(wr::DisplayListBuilder& aBuilder, + const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, wr::ImageRendering aFilter, + const Range& aImageKeys, + PushDisplayItemFlagSet aFlags) override; + + protected: + const EGLImage mImage; + const EGLSync mSync; + const gfx::IntSize mSize; + const bool mHasAlpha; + RefPtr mTextureSource; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* MOZILLA_GFX_TEXTUREOGL_H */ diff --git a/gfx/layers/opengl/X11TextureSourceOGL.cpp b/gfx/layers/opengl/X11TextureSourceOGL.cpp new file mode 100644 index 0000000000..d0c9db0fd2 --- /dev/null +++ b/gfx/layers/opengl/X11TextureSourceOGL.cpp @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "X11TextureSourceOGL.h" +#include "gfxXlibSurface.h" +#include "gfx2DGlue.h" +#include "GLContext.h" + +namespace mozilla::layers { + +using namespace mozilla::gfx; + +X11TextureSourceOGL::X11TextureSourceOGL(CompositorOGL* aCompositor, + gfxXlibSurface* aSurface) + : mGL(aCompositor->gl()), + mSurface(aSurface), + mTexture(0), + mUpdated(false) {} + +X11TextureSourceOGL::~X11TextureSourceOGL() { DeallocateDeviceData(); } + +void X11TextureSourceOGL::DeallocateDeviceData() { + if (mTexture) { + if (gl() && gl()->MakeCurrent()) { + gl::sGLXLibrary.ReleaseTexImage(mSurface->XDisplay(), + mSurface->GetGLXPixmap()); + gl()->fDeleteTextures(1, &mTexture); + mTexture = 0; + } + } +} + +void X11TextureSourceOGL::BindTexture(GLenum aTextureUnit, + gfx::SamplingFilter aSamplingFilter) { + gl()->fActiveTexture(aTextureUnit); + + if (!mTexture) { + gl()->fGenTextures(1, &mTexture); + + gl()->fBindTexture(LOCAL_GL_TEXTURE_2D, mTexture); + + gl::sGLXLibrary.BindTexImage(mSurface->XDisplay(), + mSurface->GetGLXPixmap()); + } else { + gl()->fBindTexture(LOCAL_GL_TEXTURE_2D, mTexture); + if (mUpdated) { + gl::sGLXLibrary.UpdateTexImage(mSurface->XDisplay(), + mSurface->GetGLXPixmap()); + mUpdated = false; + } + } + + ApplySamplingFilterToBoundTexture(gl(), aSamplingFilter, LOCAL_GL_TEXTURE_2D); +} + +IntSize X11TextureSourceOGL::GetSize() const { return mSurface->GetSize(); } + +SurfaceFormat X11TextureSourceOGL::GetFormat() const { + gfxContentType type = mSurface->GetContentType(); + return X11TextureSourceOGL::ContentTypeToSurfaceFormat(type); +} + +void X11TextureSourceOGL::SetTextureSourceProvider( + TextureSourceProvider* aProvider) { + gl::GLContext* newGL = aProvider ? aProvider->GetGLContext() : nullptr; + if (mGL != newGL) { + DeallocateDeviceData(); + } + mGL = newGL; +} + +SurfaceFormat X11TextureSourceOGL::ContentTypeToSurfaceFormat( + gfxContentType aType) { + // X11 uses a switched format and the OGL compositor + // doesn't support ALPHA / A8. + switch (aType) { + case gfxContentType::COLOR: + return SurfaceFormat::R8G8B8X8; + case gfxContentType::COLOR_ALPHA: + return SurfaceFormat::R8G8B8A8; + default: + return SurfaceFormat::UNKNOWN; + } +} + +} // namespace mozilla::layers diff --git a/gfx/layers/opengl/X11TextureSourceOGL.h b/gfx/layers/opengl/X11TextureSourceOGL.h new file mode 100644 index 0000000000..34ab8d5638 --- /dev/null +++ b/gfx/layers/opengl/X11TextureSourceOGL.h @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_X11TEXTURESOURCEOGL__H +#define MOZILLA_GFX_X11TEXTURESOURCEOGL__H + +#ifdef MOZ_X11 + +# include "mozilla/layers/CompositorOGL.h" +# include "mozilla/layers/TextureHostOGL.h" +# include "mozilla/layers/X11TextureHost.h" +# include "mozilla/gfx/2D.h" + +namespace mozilla { +namespace layers { + +// TextureSource for Xlib-backed surfaces. +class X11TextureSourceOGL : public TextureSourceOGL, public X11TextureSource { + public: + X11TextureSourceOGL(CompositorOGL* aCompositor, gfxXlibSurface* aSurface); + ~X11TextureSourceOGL(); + + X11TextureSourceOGL* AsSourceOGL() override { return this; } + + bool IsValid() const override { return !!gl(); }; + + void BindTexture(GLenum aTextureUnit, + gfx::SamplingFilter aSamplingFilter) override; + + gfx::IntSize GetSize() const override; + + GLenum GetTextureTarget() const override { return LOCAL_GL_TEXTURE_2D; } + + gfx::SurfaceFormat GetFormat() const override; + + GLenum GetWrapMode() const override { return LOCAL_GL_CLAMP_TO_EDGE; } + + void DeallocateDeviceData() override; + + void SetTextureSourceProvider(TextureSourceProvider* aProvider) override; + + void Updated() override { mUpdated = true; } + + gl::GLContext* gl() const { return mGL; } + + static gfx::SurfaceFormat ContentTypeToSurfaceFormat(gfxContentType aType); + + protected: + RefPtr mGL; + RefPtr mSurface; + RefPtr mSourceSurface; + GLuint mTexture; + bool mUpdated; +}; + +} // namespace layers +} // namespace mozilla + +#endif + +#endif // MOZILLA_GFX_X11TEXTURESOURCEOGL__H diff --git a/gfx/layers/protobuf/LayerScopePacket.pb.cc b/gfx/layers/protobuf/LayerScopePacket.pb.cc new file mode 100644 index 0000000000..35f181d672 --- /dev/null +++ b/gfx/layers/protobuf/LayerScopePacket.pb.cc @@ -0,0 +1,7236 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: LayerScopePacket.proto + +#include "LayerScopePacket.pb.h" + +#include + +#include +#include +#include +#include +// @@protoc_insertion_point(includes) +#include +extern PROTOBUF_INTERNAL_EXPORT_LayerScopePacket_2eproto ::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<0> scc_info_ColorPacket_LayerScopePacket_2eproto; +extern PROTOBUF_INTERNAL_EXPORT_LayerScopePacket_2eproto ::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<1> scc_info_DrawPacket_LayerScopePacket_2eproto; +extern PROTOBUF_INTERNAL_EXPORT_LayerScopePacket_2eproto ::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<0> scc_info_DrawPacket_Rect_LayerScopePacket_2eproto; +extern PROTOBUF_INTERNAL_EXPORT_LayerScopePacket_2eproto ::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<0> scc_info_FramePacket_LayerScopePacket_2eproto; +extern PROTOBUF_INTERNAL_EXPORT_LayerScopePacket_2eproto ::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<1> scc_info_LayersPacket_LayerScopePacket_2eproto; +extern PROTOBUF_INTERNAL_EXPORT_LayerScopePacket_2eproto ::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<5> scc_info_LayersPacket_Layer_LayerScopePacket_2eproto; +extern PROTOBUF_INTERNAL_EXPORT_LayerScopePacket_2eproto ::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<0> scc_info_LayersPacket_Layer_Matrix_LayerScopePacket_2eproto; +extern PROTOBUF_INTERNAL_EXPORT_LayerScopePacket_2eproto ::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<0> scc_info_LayersPacket_Layer_Rect_LayerScopePacket_2eproto; +extern PROTOBUF_INTERNAL_EXPORT_LayerScopePacket_2eproto ::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<1> scc_info_LayersPacket_Layer_Region_LayerScopePacket_2eproto; +extern PROTOBUF_INTERNAL_EXPORT_LayerScopePacket_2eproto ::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<3> scc_info_LayersPacket_Layer_Shadow_LayerScopePacket_2eproto; +extern PROTOBUF_INTERNAL_EXPORT_LayerScopePacket_2eproto ::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<0> scc_info_LayersPacket_Layer_Size_LayerScopePacket_2eproto; +extern PROTOBUF_INTERNAL_EXPORT_LayerScopePacket_2eproto ::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<0> scc_info_MetaPacket_LayerScopePacket_2eproto; +extern PROTOBUF_INTERNAL_EXPORT_LayerScopePacket_2eproto ::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<2> scc_info_TexturePacket_LayerScopePacket_2eproto; +extern PROTOBUF_INTERNAL_EXPORT_LayerScopePacket_2eproto ::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<2> scc_info_TexturePacket_EffectMask_LayerScopePacket_2eproto; +extern PROTOBUF_INTERNAL_EXPORT_LayerScopePacket_2eproto ::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<0> scc_info_TexturePacket_Matrix_LayerScopePacket_2eproto; +extern PROTOBUF_INTERNAL_EXPORT_LayerScopePacket_2eproto ::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<0> scc_info_TexturePacket_Rect_LayerScopePacket_2eproto; +extern PROTOBUF_INTERNAL_EXPORT_LayerScopePacket_2eproto ::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<0> scc_info_TexturePacket_Size_LayerScopePacket_2eproto; +namespace mozilla { +namespace layers { +namespace layerscope { +class FramePacketDefaultTypeInternal { + public: + ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed _instance; +} _FramePacket_default_instance_; +class ColorPacketDefaultTypeInternal { + public: + ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed _instance; +} _ColorPacket_default_instance_; +class TexturePacket_RectDefaultTypeInternal { + public: + ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed _instance; +} _TexturePacket_Rect_default_instance_; +class TexturePacket_SizeDefaultTypeInternal { + public: + ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed _instance; +} _TexturePacket_Size_default_instance_; +class TexturePacket_MatrixDefaultTypeInternal { + public: + ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed _instance; +} _TexturePacket_Matrix_default_instance_; +class TexturePacket_EffectMaskDefaultTypeInternal { + public: + ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed _instance; +} _TexturePacket_EffectMask_default_instance_; +class TexturePacketDefaultTypeInternal { + public: + ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed _instance; +} _TexturePacket_default_instance_; +class LayersPacket_Layer_SizeDefaultTypeInternal { + public: + ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed _instance; +} _LayersPacket_Layer_Size_default_instance_; +class LayersPacket_Layer_RectDefaultTypeInternal { + public: + ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed _instance; +} _LayersPacket_Layer_Rect_default_instance_; +class LayersPacket_Layer_RegionDefaultTypeInternal { + public: + ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed _instance; +} _LayersPacket_Layer_Region_default_instance_; +class LayersPacket_Layer_MatrixDefaultTypeInternal { + public: + ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed _instance; +} _LayersPacket_Layer_Matrix_default_instance_; +class LayersPacket_Layer_ShadowDefaultTypeInternal { + public: + ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed _instance; +} _LayersPacket_Layer_Shadow_default_instance_; +class LayersPacket_LayerDefaultTypeInternal { + public: + ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed _instance; +} _LayersPacket_Layer_default_instance_; +class LayersPacketDefaultTypeInternal { + public: + ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed _instance; +} _LayersPacket_default_instance_; +class MetaPacketDefaultTypeInternal { + public: + ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed _instance; +} _MetaPacket_default_instance_; +class DrawPacket_RectDefaultTypeInternal { + public: + ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed _instance; +} _DrawPacket_Rect_default_instance_; +class DrawPacketDefaultTypeInternal { + public: + ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed _instance; +} _DrawPacket_default_instance_; +class PacketDefaultTypeInternal { + public: + ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed _instance; +} _Packet_default_instance_; +class CommandPacketDefaultTypeInternal { + public: + ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed _instance; +} _CommandPacket_default_instance_; +} // namespace layerscope +} // namespace layers +} // namespace mozilla +static void InitDefaultsscc_info_ColorPacket_LayerScopePacket_2eproto() { + GOOGLE_PROTOBUF_VERIFY_VERSION; + + { + void* ptr = &::mozilla::layers::layerscope::_ColorPacket_default_instance_; + new (ptr) ::mozilla::layers::layerscope::ColorPacket(); + ::PROTOBUF_NAMESPACE_ID::internal::OnShutdownDestroyMessage(ptr); + } + ::mozilla::layers::layerscope::ColorPacket::InitAsDefaultInstance(); +} + +::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<0> scc_info_ColorPacket_LayerScopePacket_2eproto = + {{ATOMIC_VAR_INIT(::PROTOBUF_NAMESPACE_ID::internal::SCCInfoBase::kUninitialized), 0, 0, InitDefaultsscc_info_ColorPacket_LayerScopePacket_2eproto}, {}}; + +static void InitDefaultsscc_info_CommandPacket_LayerScopePacket_2eproto() { + GOOGLE_PROTOBUF_VERIFY_VERSION; + + { + void* ptr = &::mozilla::layers::layerscope::_CommandPacket_default_instance_; + new (ptr) ::mozilla::layers::layerscope::CommandPacket(); + ::PROTOBUF_NAMESPACE_ID::internal::OnShutdownDestroyMessage(ptr); + } + ::mozilla::layers::layerscope::CommandPacket::InitAsDefaultInstance(); +} + +::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<0> scc_info_CommandPacket_LayerScopePacket_2eproto = + {{ATOMIC_VAR_INIT(::PROTOBUF_NAMESPACE_ID::internal::SCCInfoBase::kUninitialized), 0, 0, InitDefaultsscc_info_CommandPacket_LayerScopePacket_2eproto}, {}}; + +static void InitDefaultsscc_info_DrawPacket_LayerScopePacket_2eproto() { + GOOGLE_PROTOBUF_VERIFY_VERSION; + + { + void* ptr = &::mozilla::layers::layerscope::_DrawPacket_default_instance_; + new (ptr) ::mozilla::layers::layerscope::DrawPacket(); + ::PROTOBUF_NAMESPACE_ID::internal::OnShutdownDestroyMessage(ptr); + } + ::mozilla::layers::layerscope::DrawPacket::InitAsDefaultInstance(); +} + +::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<1> scc_info_DrawPacket_LayerScopePacket_2eproto = + {{ATOMIC_VAR_INIT(::PROTOBUF_NAMESPACE_ID::internal::SCCInfoBase::kUninitialized), 1, 0, InitDefaultsscc_info_DrawPacket_LayerScopePacket_2eproto}, { + &scc_info_DrawPacket_Rect_LayerScopePacket_2eproto.base,}}; + +static void InitDefaultsscc_info_DrawPacket_Rect_LayerScopePacket_2eproto() { + GOOGLE_PROTOBUF_VERIFY_VERSION; + + { + void* ptr = &::mozilla::layers::layerscope::_DrawPacket_Rect_default_instance_; + new (ptr) ::mozilla::layers::layerscope::DrawPacket_Rect(); + ::PROTOBUF_NAMESPACE_ID::internal::OnShutdownDestroyMessage(ptr); + } + ::mozilla::layers::layerscope::DrawPacket_Rect::InitAsDefaultInstance(); +} + +::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<0> scc_info_DrawPacket_Rect_LayerScopePacket_2eproto = + {{ATOMIC_VAR_INIT(::PROTOBUF_NAMESPACE_ID::internal::SCCInfoBase::kUninitialized), 0, 0, InitDefaultsscc_info_DrawPacket_Rect_LayerScopePacket_2eproto}, {}}; + +static void InitDefaultsscc_info_FramePacket_LayerScopePacket_2eproto() { + GOOGLE_PROTOBUF_VERIFY_VERSION; + + { + void* ptr = &::mozilla::layers::layerscope::_FramePacket_default_instance_; + new (ptr) ::mozilla::layers::layerscope::FramePacket(); + ::PROTOBUF_NAMESPACE_ID::internal::OnShutdownDestroyMessage(ptr); + } + ::mozilla::layers::layerscope::FramePacket::InitAsDefaultInstance(); +} + +::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<0> scc_info_FramePacket_LayerScopePacket_2eproto = + {{ATOMIC_VAR_INIT(::PROTOBUF_NAMESPACE_ID::internal::SCCInfoBase::kUninitialized), 0, 0, InitDefaultsscc_info_FramePacket_LayerScopePacket_2eproto}, {}}; + +static void InitDefaultsscc_info_LayersPacket_LayerScopePacket_2eproto() { + GOOGLE_PROTOBUF_VERIFY_VERSION; + + { + void* ptr = &::mozilla::layers::layerscope::_LayersPacket_default_instance_; + new (ptr) ::mozilla::layers::layerscope::LayersPacket(); + ::PROTOBUF_NAMESPACE_ID::internal::OnShutdownDestroyMessage(ptr); + } + ::mozilla::layers::layerscope::LayersPacket::InitAsDefaultInstance(); +} + +::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<1> scc_info_LayersPacket_LayerScopePacket_2eproto = + {{ATOMIC_VAR_INIT(::PROTOBUF_NAMESPACE_ID::internal::SCCInfoBase::kUninitialized), 1, 0, InitDefaultsscc_info_LayersPacket_LayerScopePacket_2eproto}, { + &scc_info_LayersPacket_Layer_LayerScopePacket_2eproto.base,}}; + +static void InitDefaultsscc_info_LayersPacket_Layer_LayerScopePacket_2eproto() { + GOOGLE_PROTOBUF_VERIFY_VERSION; + + { + void* ptr = &::mozilla::layers::layerscope::_LayersPacket_Layer_default_instance_; + new (ptr) ::mozilla::layers::layerscope::LayersPacket_Layer(); + ::PROTOBUF_NAMESPACE_ID::internal::OnShutdownDestroyMessage(ptr); + } + ::mozilla::layers::layerscope::LayersPacket_Layer::InitAsDefaultInstance(); +} + +::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<5> scc_info_LayersPacket_Layer_LayerScopePacket_2eproto = + {{ATOMIC_VAR_INIT(::PROTOBUF_NAMESPACE_ID::internal::SCCInfoBase::kUninitialized), 5, 0, InitDefaultsscc_info_LayersPacket_Layer_LayerScopePacket_2eproto}, { + &scc_info_LayersPacket_Layer_Rect_LayerScopePacket_2eproto.base, + &scc_info_LayersPacket_Layer_Matrix_LayerScopePacket_2eproto.base, + &scc_info_LayersPacket_Layer_Region_LayerScopePacket_2eproto.base, + &scc_info_LayersPacket_Layer_Shadow_LayerScopePacket_2eproto.base, + &scc_info_LayersPacket_Layer_Size_LayerScopePacket_2eproto.base,}}; + +static void InitDefaultsscc_info_LayersPacket_Layer_Matrix_LayerScopePacket_2eproto() { + GOOGLE_PROTOBUF_VERIFY_VERSION; + + { + void* ptr = &::mozilla::layers::layerscope::_LayersPacket_Layer_Matrix_default_instance_; + new (ptr) ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix(); + ::PROTOBUF_NAMESPACE_ID::internal::OnShutdownDestroyMessage(ptr); + } + ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix::InitAsDefaultInstance(); +} + +::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<0> scc_info_LayersPacket_Layer_Matrix_LayerScopePacket_2eproto = + {{ATOMIC_VAR_INIT(::PROTOBUF_NAMESPACE_ID::internal::SCCInfoBase::kUninitialized), 0, 0, InitDefaultsscc_info_LayersPacket_Layer_Matrix_LayerScopePacket_2eproto}, {}}; + +static void InitDefaultsscc_info_LayersPacket_Layer_Rect_LayerScopePacket_2eproto() { + GOOGLE_PROTOBUF_VERIFY_VERSION; + + { + void* ptr = &::mozilla::layers::layerscope::_LayersPacket_Layer_Rect_default_instance_; + new (ptr) ::mozilla::layers::layerscope::LayersPacket_Layer_Rect(); + ::PROTOBUF_NAMESPACE_ID::internal::OnShutdownDestroyMessage(ptr); + } + ::mozilla::layers::layerscope::LayersPacket_Layer_Rect::InitAsDefaultInstance(); +} + +::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<0> scc_info_LayersPacket_Layer_Rect_LayerScopePacket_2eproto = + {{ATOMIC_VAR_INIT(::PROTOBUF_NAMESPACE_ID::internal::SCCInfoBase::kUninitialized), 0, 0, InitDefaultsscc_info_LayersPacket_Layer_Rect_LayerScopePacket_2eproto}, {}}; + +static void InitDefaultsscc_info_LayersPacket_Layer_Region_LayerScopePacket_2eproto() { + GOOGLE_PROTOBUF_VERIFY_VERSION; + + { + void* ptr = &::mozilla::layers::layerscope::_LayersPacket_Layer_Region_default_instance_; + new (ptr) ::mozilla::layers::layerscope::LayersPacket_Layer_Region(); + ::PROTOBUF_NAMESPACE_ID::internal::OnShutdownDestroyMessage(ptr); + } + ::mozilla::layers::layerscope::LayersPacket_Layer_Region::InitAsDefaultInstance(); +} + +::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<1> scc_info_LayersPacket_Layer_Region_LayerScopePacket_2eproto = + {{ATOMIC_VAR_INIT(::PROTOBUF_NAMESPACE_ID::internal::SCCInfoBase::kUninitialized), 1, 0, InitDefaultsscc_info_LayersPacket_Layer_Region_LayerScopePacket_2eproto}, { + &scc_info_LayersPacket_Layer_Rect_LayerScopePacket_2eproto.base,}}; + +static void InitDefaultsscc_info_LayersPacket_Layer_Shadow_LayerScopePacket_2eproto() { + GOOGLE_PROTOBUF_VERIFY_VERSION; + + { + void* ptr = &::mozilla::layers::layerscope::_LayersPacket_Layer_Shadow_default_instance_; + new (ptr) ::mozilla::layers::layerscope::LayersPacket_Layer_Shadow(); + ::PROTOBUF_NAMESPACE_ID::internal::OnShutdownDestroyMessage(ptr); + } + ::mozilla::layers::layerscope::LayersPacket_Layer_Shadow::InitAsDefaultInstance(); +} + +::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<3> scc_info_LayersPacket_Layer_Shadow_LayerScopePacket_2eproto = + {{ATOMIC_VAR_INIT(::PROTOBUF_NAMESPACE_ID::internal::SCCInfoBase::kUninitialized), 3, 0, InitDefaultsscc_info_LayersPacket_Layer_Shadow_LayerScopePacket_2eproto}, { + &scc_info_LayersPacket_Layer_Rect_LayerScopePacket_2eproto.base, + &scc_info_LayersPacket_Layer_Matrix_LayerScopePacket_2eproto.base, + &scc_info_LayersPacket_Layer_Region_LayerScopePacket_2eproto.base,}}; + +static void InitDefaultsscc_info_LayersPacket_Layer_Size_LayerScopePacket_2eproto() { + GOOGLE_PROTOBUF_VERIFY_VERSION; + + { + void* ptr = &::mozilla::layers::layerscope::_LayersPacket_Layer_Size_default_instance_; + new (ptr) ::mozilla::layers::layerscope::LayersPacket_Layer_Size(); + ::PROTOBUF_NAMESPACE_ID::internal::OnShutdownDestroyMessage(ptr); + } + ::mozilla::layers::layerscope::LayersPacket_Layer_Size::InitAsDefaultInstance(); +} + +::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<0> scc_info_LayersPacket_Layer_Size_LayerScopePacket_2eproto = + {{ATOMIC_VAR_INIT(::PROTOBUF_NAMESPACE_ID::internal::SCCInfoBase::kUninitialized), 0, 0, InitDefaultsscc_info_LayersPacket_Layer_Size_LayerScopePacket_2eproto}, {}}; + +static void InitDefaultsscc_info_MetaPacket_LayerScopePacket_2eproto() { + GOOGLE_PROTOBUF_VERIFY_VERSION; + + { + void* ptr = &::mozilla::layers::layerscope::_MetaPacket_default_instance_; + new (ptr) ::mozilla::layers::layerscope::MetaPacket(); + ::PROTOBUF_NAMESPACE_ID::internal::OnShutdownDestroyMessage(ptr); + } + ::mozilla::layers::layerscope::MetaPacket::InitAsDefaultInstance(); +} + +::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<0> scc_info_MetaPacket_LayerScopePacket_2eproto = + {{ATOMIC_VAR_INIT(::PROTOBUF_NAMESPACE_ID::internal::SCCInfoBase::kUninitialized), 0, 0, InitDefaultsscc_info_MetaPacket_LayerScopePacket_2eproto}, {}}; + +static void InitDefaultsscc_info_Packet_LayerScopePacket_2eproto() { + GOOGLE_PROTOBUF_VERIFY_VERSION; + + { + void* ptr = &::mozilla::layers::layerscope::_Packet_default_instance_; + new (ptr) ::mozilla::layers::layerscope::Packet(); + ::PROTOBUF_NAMESPACE_ID::internal::OnShutdownDestroyMessage(ptr); + } + ::mozilla::layers::layerscope::Packet::InitAsDefaultInstance(); +} + +::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<6> scc_info_Packet_LayerScopePacket_2eproto = + {{ATOMIC_VAR_INIT(::PROTOBUF_NAMESPACE_ID::internal::SCCInfoBase::kUninitialized), 6, 0, InitDefaultsscc_info_Packet_LayerScopePacket_2eproto}, { + &scc_info_FramePacket_LayerScopePacket_2eproto.base, + &scc_info_ColorPacket_LayerScopePacket_2eproto.base, + &scc_info_TexturePacket_LayerScopePacket_2eproto.base, + &scc_info_LayersPacket_LayerScopePacket_2eproto.base, + &scc_info_MetaPacket_LayerScopePacket_2eproto.base, + &scc_info_DrawPacket_LayerScopePacket_2eproto.base,}}; + +static void InitDefaultsscc_info_TexturePacket_LayerScopePacket_2eproto() { + GOOGLE_PROTOBUF_VERIFY_VERSION; + + { + void* ptr = &::mozilla::layers::layerscope::_TexturePacket_default_instance_; + new (ptr) ::mozilla::layers::layerscope::TexturePacket(); + ::PROTOBUF_NAMESPACE_ID::internal::OnShutdownDestroyMessage(ptr); + } + ::mozilla::layers::layerscope::TexturePacket::InitAsDefaultInstance(); +} + +::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<2> scc_info_TexturePacket_LayerScopePacket_2eproto = + {{ATOMIC_VAR_INIT(::PROTOBUF_NAMESPACE_ID::internal::SCCInfoBase::kUninitialized), 2, 0, InitDefaultsscc_info_TexturePacket_LayerScopePacket_2eproto}, { + &scc_info_TexturePacket_Rect_LayerScopePacket_2eproto.base, + &scc_info_TexturePacket_EffectMask_LayerScopePacket_2eproto.base,}}; + +static void InitDefaultsscc_info_TexturePacket_EffectMask_LayerScopePacket_2eproto() { + GOOGLE_PROTOBUF_VERIFY_VERSION; + + { + void* ptr = &::mozilla::layers::layerscope::_TexturePacket_EffectMask_default_instance_; + new (ptr) ::mozilla::layers::layerscope::TexturePacket_EffectMask(); + ::PROTOBUF_NAMESPACE_ID::internal::OnShutdownDestroyMessage(ptr); + } + ::mozilla::layers::layerscope::TexturePacket_EffectMask::InitAsDefaultInstance(); +} + +::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<2> scc_info_TexturePacket_EffectMask_LayerScopePacket_2eproto = + {{ATOMIC_VAR_INIT(::PROTOBUF_NAMESPACE_ID::internal::SCCInfoBase::kUninitialized), 2, 0, InitDefaultsscc_info_TexturePacket_EffectMask_LayerScopePacket_2eproto}, { + &scc_info_TexturePacket_Size_LayerScopePacket_2eproto.base, + &scc_info_TexturePacket_Matrix_LayerScopePacket_2eproto.base,}}; + +static void InitDefaultsscc_info_TexturePacket_Matrix_LayerScopePacket_2eproto() { + GOOGLE_PROTOBUF_VERIFY_VERSION; + + { + void* ptr = &::mozilla::layers::layerscope::_TexturePacket_Matrix_default_instance_; + new (ptr) ::mozilla::layers::layerscope::TexturePacket_Matrix(); + ::PROTOBUF_NAMESPACE_ID::internal::OnShutdownDestroyMessage(ptr); + } + ::mozilla::layers::layerscope::TexturePacket_Matrix::InitAsDefaultInstance(); +} + +::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<0> scc_info_TexturePacket_Matrix_LayerScopePacket_2eproto = + {{ATOMIC_VAR_INIT(::PROTOBUF_NAMESPACE_ID::internal::SCCInfoBase::kUninitialized), 0, 0, InitDefaultsscc_info_TexturePacket_Matrix_LayerScopePacket_2eproto}, {}}; + +static void InitDefaultsscc_info_TexturePacket_Rect_LayerScopePacket_2eproto() { + GOOGLE_PROTOBUF_VERIFY_VERSION; + + { + void* ptr = &::mozilla::layers::layerscope::_TexturePacket_Rect_default_instance_; + new (ptr) ::mozilla::layers::layerscope::TexturePacket_Rect(); + ::PROTOBUF_NAMESPACE_ID::internal::OnShutdownDestroyMessage(ptr); + } + ::mozilla::layers::layerscope::TexturePacket_Rect::InitAsDefaultInstance(); +} + +::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<0> scc_info_TexturePacket_Rect_LayerScopePacket_2eproto = + {{ATOMIC_VAR_INIT(::PROTOBUF_NAMESPACE_ID::internal::SCCInfoBase::kUninitialized), 0, 0, InitDefaultsscc_info_TexturePacket_Rect_LayerScopePacket_2eproto}, {}}; + +static void InitDefaultsscc_info_TexturePacket_Size_LayerScopePacket_2eproto() { + GOOGLE_PROTOBUF_VERIFY_VERSION; + + { + void* ptr = &::mozilla::layers::layerscope::_TexturePacket_Size_default_instance_; + new (ptr) ::mozilla::layers::layerscope::TexturePacket_Size(); + ::PROTOBUF_NAMESPACE_ID::internal::OnShutdownDestroyMessage(ptr); + } + ::mozilla::layers::layerscope::TexturePacket_Size::InitAsDefaultInstance(); +} + +::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<0> scc_info_TexturePacket_Size_LayerScopePacket_2eproto = + {{ATOMIC_VAR_INIT(::PROTOBUF_NAMESPACE_ID::internal::SCCInfoBase::kUninitialized), 0, 0, InitDefaultsscc_info_TexturePacket_Size_LayerScopePacket_2eproto}, {}}; + +namespace mozilla { +namespace layers { +namespace layerscope { +bool TexturePacket_Filter_IsValid(int value) { + switch (value) { + case 0: + case 1: + case 2: + return true; + default: + return false; + } +} + +static ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed TexturePacket_Filter_strings[3] = {}; + +static const char TexturePacket_Filter_names[] = + "GOOD" + "LINEAR" + "POINT"; + +static const ::PROTOBUF_NAMESPACE_ID::internal::EnumEntry TexturePacket_Filter_entries[] = { + { {TexturePacket_Filter_names + 0, 4}, 0 }, + { {TexturePacket_Filter_names + 4, 6}, 1 }, + { {TexturePacket_Filter_names + 10, 5}, 2 }, +}; + +static const int TexturePacket_Filter_entries_by_number[] = { + 0, // 0 -> GOOD + 1, // 1 -> LINEAR + 2, // 2 -> POINT +}; + +const std::string& TexturePacket_Filter_Name( + TexturePacket_Filter value) { + static const bool dummy = + ::PROTOBUF_NAMESPACE_ID::internal::InitializeEnumStrings( + TexturePacket_Filter_entries, + TexturePacket_Filter_entries_by_number, + 3, TexturePacket_Filter_strings); + (void) dummy; + int idx = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumName( + TexturePacket_Filter_entries, + TexturePacket_Filter_entries_by_number, + 3, value); + return idx == -1 ? ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString() : + TexturePacket_Filter_strings[idx].get(); +} +bool TexturePacket_Filter_Parse( + const std::string& name, TexturePacket_Filter* value) { + int int_value; + bool success = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumValue( + TexturePacket_Filter_entries, 3, name, &int_value); + if (success) { + *value = static_cast(int_value); + } + return success; +} +#if (__cplusplus < 201703) && (!defined(_MSC_VER) || _MSC_VER >= 1900) +constexpr TexturePacket_Filter TexturePacket::GOOD; +constexpr TexturePacket_Filter TexturePacket::LINEAR; +constexpr TexturePacket_Filter TexturePacket::POINT; +constexpr TexturePacket_Filter TexturePacket::Filter_MIN; +constexpr TexturePacket_Filter TexturePacket::Filter_MAX; +constexpr int TexturePacket::Filter_ARRAYSIZE; +#endif // (__cplusplus < 201703) && (!defined(_MSC_VER) || _MSC_VER >= 1900) +bool LayersPacket_Layer_LayerType_IsValid(int value) { + switch (value) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 8: + case 9: + case 10: + return true; + default: + return false; + } +} + +static ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed LayersPacket_Layer_LayerType_strings[10] = {}; + +static const char LayersPacket_Layer_LayerType_names[] = + "CanvasLayer" + "ColorLayer" + "ContainerLayer" + "DisplayItemLayer" + "ImageLayer" + "LayerManager" + "PaintedLayer" + "ReadbackLayer" + "RefLayer" + "UnknownLayer"; + +static const ::PROTOBUF_NAMESPACE_ID::internal::EnumEntry LayersPacket_Layer_LayerType_entries[] = { + { {LayersPacket_Layer_LayerType_names + 0, 11}, 4 }, + { {LayersPacket_Layer_LayerType_names + 11, 10}, 6 }, + { {LayersPacket_Layer_LayerType_names + 21, 14}, 2 }, + { {LayersPacket_Layer_LayerType_names + 35, 16}, 10 }, + { {LayersPacket_Layer_LayerType_names + 51, 10}, 5 }, + { {LayersPacket_Layer_LayerType_names + 61, 12}, 1 }, + { {LayersPacket_Layer_LayerType_names + 73, 12}, 3 }, + { {LayersPacket_Layer_LayerType_names + 85, 13}, 9 }, + { {LayersPacket_Layer_LayerType_names + 98, 8}, 8 }, + { {LayersPacket_Layer_LayerType_names + 106, 12}, 0 }, +}; + +static const int LayersPacket_Layer_LayerType_entries_by_number[] = { + 9, // 0 -> UnknownLayer + 5, // 1 -> LayerManager + 2, // 2 -> ContainerLayer + 6, // 3 -> PaintedLayer + 0, // 4 -> CanvasLayer + 4, // 5 -> ImageLayer + 1, // 6 -> ColorLayer + 8, // 8 -> RefLayer + 7, // 9 -> ReadbackLayer + 3, // 10 -> DisplayItemLayer +}; + +const std::string& LayersPacket_Layer_LayerType_Name( + LayersPacket_Layer_LayerType value) { + static const bool dummy = + ::PROTOBUF_NAMESPACE_ID::internal::InitializeEnumStrings( + LayersPacket_Layer_LayerType_entries, + LayersPacket_Layer_LayerType_entries_by_number, + 10, LayersPacket_Layer_LayerType_strings); + (void) dummy; + int idx = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumName( + LayersPacket_Layer_LayerType_entries, + LayersPacket_Layer_LayerType_entries_by_number, + 10, value); + return idx == -1 ? ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString() : + LayersPacket_Layer_LayerType_strings[idx].get(); +} +bool LayersPacket_Layer_LayerType_Parse( + const std::string& name, LayersPacket_Layer_LayerType* value) { + int int_value; + bool success = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumValue( + LayersPacket_Layer_LayerType_entries, 10, name, &int_value); + if (success) { + *value = static_cast(int_value); + } + return success; +} +#if (__cplusplus < 201703) && (!defined(_MSC_VER) || _MSC_VER >= 1900) +constexpr LayersPacket_Layer_LayerType LayersPacket_Layer::UnknownLayer; +constexpr LayersPacket_Layer_LayerType LayersPacket_Layer::LayerManager; +constexpr LayersPacket_Layer_LayerType LayersPacket_Layer::ContainerLayer; +constexpr LayersPacket_Layer_LayerType LayersPacket_Layer::PaintedLayer; +constexpr LayersPacket_Layer_LayerType LayersPacket_Layer::CanvasLayer; +constexpr LayersPacket_Layer_LayerType LayersPacket_Layer::ImageLayer; +constexpr LayersPacket_Layer_LayerType LayersPacket_Layer::ColorLayer; +constexpr LayersPacket_Layer_LayerType LayersPacket_Layer::RefLayer; +constexpr LayersPacket_Layer_LayerType LayersPacket_Layer::ReadbackLayer; +constexpr LayersPacket_Layer_LayerType LayersPacket_Layer::DisplayItemLayer; +constexpr LayersPacket_Layer_LayerType LayersPacket_Layer::LayerType_MIN; +constexpr LayersPacket_Layer_LayerType LayersPacket_Layer::LayerType_MAX; +constexpr int LayersPacket_Layer::LayerType_ARRAYSIZE; +#endif // (__cplusplus < 201703) && (!defined(_MSC_VER) || _MSC_VER >= 1900) +bool LayersPacket_Layer_ScrollingDirect_IsValid(int value) { + switch (value) { + case 1: + case 2: + return true; + default: + return false; + } +} + +static ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed LayersPacket_Layer_ScrollingDirect_strings[2] = {}; + +static const char LayersPacket_Layer_ScrollingDirect_names[] = + "HORIZONTAL" + "VERTICAL"; + +static const ::PROTOBUF_NAMESPACE_ID::internal::EnumEntry LayersPacket_Layer_ScrollingDirect_entries[] = { + { {LayersPacket_Layer_ScrollingDirect_names + 0, 10}, 2 }, + { {LayersPacket_Layer_ScrollingDirect_names + 10, 8}, 1 }, +}; + +static const int LayersPacket_Layer_ScrollingDirect_entries_by_number[] = { + 1, // 1 -> VERTICAL + 0, // 2 -> HORIZONTAL +}; + +const std::string& LayersPacket_Layer_ScrollingDirect_Name( + LayersPacket_Layer_ScrollingDirect value) { + static const bool dummy = + ::PROTOBUF_NAMESPACE_ID::internal::InitializeEnumStrings( + LayersPacket_Layer_ScrollingDirect_entries, + LayersPacket_Layer_ScrollingDirect_entries_by_number, + 2, LayersPacket_Layer_ScrollingDirect_strings); + (void) dummy; + int idx = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumName( + LayersPacket_Layer_ScrollingDirect_entries, + LayersPacket_Layer_ScrollingDirect_entries_by_number, + 2, value); + return idx == -1 ? ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString() : + LayersPacket_Layer_ScrollingDirect_strings[idx].get(); +} +bool LayersPacket_Layer_ScrollingDirect_Parse( + const std::string& name, LayersPacket_Layer_ScrollingDirect* value) { + int int_value; + bool success = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumValue( + LayersPacket_Layer_ScrollingDirect_entries, 2, name, &int_value); + if (success) { + *value = static_cast(int_value); + } + return success; +} +#if (__cplusplus < 201703) && (!defined(_MSC_VER) || _MSC_VER >= 1900) +constexpr LayersPacket_Layer_ScrollingDirect LayersPacket_Layer::VERTICAL; +constexpr LayersPacket_Layer_ScrollingDirect LayersPacket_Layer::HORIZONTAL; +constexpr LayersPacket_Layer_ScrollingDirect LayersPacket_Layer::ScrollingDirect_MIN; +constexpr LayersPacket_Layer_ScrollingDirect LayersPacket_Layer::ScrollingDirect_MAX; +constexpr int LayersPacket_Layer::ScrollingDirect_ARRAYSIZE; +#endif // (__cplusplus < 201703) && (!defined(_MSC_VER) || _MSC_VER >= 1900) +bool LayersPacket_Layer_Filter_IsValid(int value) { + switch (value) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + return true; + default: + return false; + } +} + +static ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed LayersPacket_Layer_Filter_strings[9] = {}; + +static const char LayersPacket_Layer_Filter_names[] = + "FILTER_BEST" + "FILTER_BILINEAR" + "FILTER_FAST" + "FILTER_GAUSSIAN" + "FILTER_GOOD" + "FILTER_LINEAR" + "FILTER_NEAREST" + "FILTER_POINT" + "FILTER_SENTINEL"; + +static const ::PROTOBUF_NAMESPACE_ID::internal::EnumEntry LayersPacket_Layer_Filter_entries[] = { + { {LayersPacket_Layer_Filter_names + 0, 11}, 2 }, + { {LayersPacket_Layer_Filter_names + 11, 15}, 4 }, + { {LayersPacket_Layer_Filter_names + 26, 11}, 0 }, + { {LayersPacket_Layer_Filter_names + 37, 15}, 5 }, + { {LayersPacket_Layer_Filter_names + 52, 11}, 1 }, + { {LayersPacket_Layer_Filter_names + 63, 13}, 7 }, + { {LayersPacket_Layer_Filter_names + 76, 14}, 3 }, + { {LayersPacket_Layer_Filter_names + 90, 12}, 8 }, + { {LayersPacket_Layer_Filter_names + 102, 15}, 6 }, +}; + +static const int LayersPacket_Layer_Filter_entries_by_number[] = { + 2, // 0 -> FILTER_FAST + 4, // 1 -> FILTER_GOOD + 0, // 2 -> FILTER_BEST + 6, // 3 -> FILTER_NEAREST + 1, // 4 -> FILTER_BILINEAR + 3, // 5 -> FILTER_GAUSSIAN + 8, // 6 -> FILTER_SENTINEL + 5, // 7 -> FILTER_LINEAR + 7, // 8 -> FILTER_POINT +}; + +const std::string& LayersPacket_Layer_Filter_Name( + LayersPacket_Layer_Filter value) { + static const bool dummy = + ::PROTOBUF_NAMESPACE_ID::internal::InitializeEnumStrings( + LayersPacket_Layer_Filter_entries, + LayersPacket_Layer_Filter_entries_by_number, + 9, LayersPacket_Layer_Filter_strings); + (void) dummy; + int idx = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumName( + LayersPacket_Layer_Filter_entries, + LayersPacket_Layer_Filter_entries_by_number, + 9, value); + return idx == -1 ? ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString() : + LayersPacket_Layer_Filter_strings[idx].get(); +} +bool LayersPacket_Layer_Filter_Parse( + const std::string& name, LayersPacket_Layer_Filter* value) { + int int_value; + bool success = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumValue( + LayersPacket_Layer_Filter_entries, 9, name, &int_value); + if (success) { + *value = static_cast(int_value); + } + return success; +} +#if (__cplusplus < 201703) && (!defined(_MSC_VER) || _MSC_VER >= 1900) +constexpr LayersPacket_Layer_Filter LayersPacket_Layer::FILTER_FAST; +constexpr LayersPacket_Layer_Filter LayersPacket_Layer::FILTER_GOOD; +constexpr LayersPacket_Layer_Filter LayersPacket_Layer::FILTER_BEST; +constexpr LayersPacket_Layer_Filter LayersPacket_Layer::FILTER_NEAREST; +constexpr LayersPacket_Layer_Filter LayersPacket_Layer::FILTER_BILINEAR; +constexpr LayersPacket_Layer_Filter LayersPacket_Layer::FILTER_GAUSSIAN; +constexpr LayersPacket_Layer_Filter LayersPacket_Layer::FILTER_SENTINEL; +constexpr LayersPacket_Layer_Filter LayersPacket_Layer::FILTER_LINEAR; +constexpr LayersPacket_Layer_Filter LayersPacket_Layer::FILTER_POINT; +constexpr LayersPacket_Layer_Filter LayersPacket_Layer::Filter_MIN; +constexpr LayersPacket_Layer_Filter LayersPacket_Layer::Filter_MAX; +constexpr int LayersPacket_Layer::Filter_ARRAYSIZE; +#endif // (__cplusplus < 201703) && (!defined(_MSC_VER) || _MSC_VER >= 1900) +bool Packet_DataType_IsValid(int value) { + switch (value) { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + return true; + default: + return false; + } +} + +static ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed Packet_DataType_strings[7] = {}; + +static const char Packet_DataType_names[] = + "COLOR" + "DRAW" + "FRAMEEND" + "FRAMESTART" + "LAYERS" + "META" + "TEXTURE"; + +static const ::PROTOBUF_NAMESPACE_ID::internal::EnumEntry Packet_DataType_entries[] = { + { {Packet_DataType_names + 0, 5}, 3 }, + { {Packet_DataType_names + 5, 4}, 7 }, + { {Packet_DataType_names + 9, 8}, 2 }, + { {Packet_DataType_names + 17, 10}, 1 }, + { {Packet_DataType_names + 27, 6}, 5 }, + { {Packet_DataType_names + 33, 4}, 6 }, + { {Packet_DataType_names + 37, 7}, 4 }, +}; + +static const int Packet_DataType_entries_by_number[] = { + 3, // 1 -> FRAMESTART + 2, // 2 -> FRAMEEND + 0, // 3 -> COLOR + 6, // 4 -> TEXTURE + 4, // 5 -> LAYERS + 5, // 6 -> META + 1, // 7 -> DRAW +}; + +const std::string& Packet_DataType_Name( + Packet_DataType value) { + static const bool dummy = + ::PROTOBUF_NAMESPACE_ID::internal::InitializeEnumStrings( + Packet_DataType_entries, + Packet_DataType_entries_by_number, + 7, Packet_DataType_strings); + (void) dummy; + int idx = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumName( + Packet_DataType_entries, + Packet_DataType_entries_by_number, + 7, value); + return idx == -1 ? ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString() : + Packet_DataType_strings[idx].get(); +} +bool Packet_DataType_Parse( + const std::string& name, Packet_DataType* value) { + int int_value; + bool success = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumValue( + Packet_DataType_entries, 7, name, &int_value); + if (success) { + *value = static_cast(int_value); + } + return success; +} +#if (__cplusplus < 201703) && (!defined(_MSC_VER) || _MSC_VER >= 1900) +constexpr Packet_DataType Packet::FRAMESTART; +constexpr Packet_DataType Packet::FRAMEEND; +constexpr Packet_DataType Packet::COLOR; +constexpr Packet_DataType Packet::TEXTURE; +constexpr Packet_DataType Packet::LAYERS; +constexpr Packet_DataType Packet::META; +constexpr Packet_DataType Packet::DRAW; +constexpr Packet_DataType Packet::DataType_MIN; +constexpr Packet_DataType Packet::DataType_MAX; +constexpr int Packet::DataType_ARRAYSIZE; +#endif // (__cplusplus < 201703) && (!defined(_MSC_VER) || _MSC_VER >= 1900) +bool CommandPacket_CmdType_IsValid(int value) { + switch (value) { + case 0: + case 1: + case 2: + return true; + default: + return false; + } +} + +static ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed CommandPacket_CmdType_strings[3] = {}; + +static const char CommandPacket_CmdType_names[] = + "LAYERS_BUFFER" + "LAYERS_TREE" + "NO_OP"; + +static const ::PROTOBUF_NAMESPACE_ID::internal::EnumEntry CommandPacket_CmdType_entries[] = { + { {CommandPacket_CmdType_names + 0, 13}, 2 }, + { {CommandPacket_CmdType_names + 13, 11}, 1 }, + { {CommandPacket_CmdType_names + 24, 5}, 0 }, +}; + +static const int CommandPacket_CmdType_entries_by_number[] = { + 2, // 0 -> NO_OP + 1, // 1 -> LAYERS_TREE + 0, // 2 -> LAYERS_BUFFER +}; + +const std::string& CommandPacket_CmdType_Name( + CommandPacket_CmdType value) { + static const bool dummy = + ::PROTOBUF_NAMESPACE_ID::internal::InitializeEnumStrings( + CommandPacket_CmdType_entries, + CommandPacket_CmdType_entries_by_number, + 3, CommandPacket_CmdType_strings); + (void) dummy; + int idx = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumName( + CommandPacket_CmdType_entries, + CommandPacket_CmdType_entries_by_number, + 3, value); + return idx == -1 ? ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString() : + CommandPacket_CmdType_strings[idx].get(); +} +bool CommandPacket_CmdType_Parse( + const std::string& name, CommandPacket_CmdType* value) { + int int_value; + bool success = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumValue( + CommandPacket_CmdType_entries, 3, name, &int_value); + if (success) { + *value = static_cast(int_value); + } + return success; +} +#if (__cplusplus < 201703) && (!defined(_MSC_VER) || _MSC_VER >= 1900) +constexpr CommandPacket_CmdType CommandPacket::NO_OP; +constexpr CommandPacket_CmdType CommandPacket::LAYERS_TREE; +constexpr CommandPacket_CmdType CommandPacket::LAYERS_BUFFER; +constexpr CommandPacket_CmdType CommandPacket::CmdType_MIN; +constexpr CommandPacket_CmdType CommandPacket::CmdType_MAX; +constexpr int CommandPacket::CmdType_ARRAYSIZE; +#endif // (__cplusplus < 201703) && (!defined(_MSC_VER) || _MSC_VER >= 1900) + +// =================================================================== + +void FramePacket::InitAsDefaultInstance() { +} +class FramePacket::_Internal { + public: + using HasBits = decltype(std::declval()._has_bits_); + static void set_has_value(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static void set_has_scale(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } +}; + +FramePacket::FramePacket() + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), _internal_metadata_(nullptr) { + SharedCtor(); + // @@protoc_insertion_point(constructor:mozilla.layers.layerscope.FramePacket) +} +FramePacket::FramePacket(const FramePacket& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), + _internal_metadata_(nullptr), + _has_bits_(from._has_bits_) { + _internal_metadata_.MergeFrom(from._internal_metadata_); + ::memcpy(&value_, &from.value_, + static_cast(reinterpret_cast(&scale_) - + reinterpret_cast(&value_)) + sizeof(scale_)); + // @@protoc_insertion_point(copy_constructor:mozilla.layers.layerscope.FramePacket) +} + +void FramePacket::SharedCtor() { + ::memset(&value_, 0, static_cast( + reinterpret_cast(&scale_) - + reinterpret_cast(&value_)) + sizeof(scale_)); +} + +FramePacket::~FramePacket() { + // @@protoc_insertion_point(destructor:mozilla.layers.layerscope.FramePacket) + SharedDtor(); +} + +void FramePacket::SharedDtor() { +} + +void FramePacket::SetCachedSize(int size) const { + _cached_size_.Set(size); +} +const FramePacket& FramePacket::default_instance() { + ::PROTOBUF_NAMESPACE_ID::internal::InitSCC(&::scc_info_FramePacket_LayerScopePacket_2eproto.base); + return *internal_default_instance(); +} + + +void FramePacket::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.layers.layerscope.FramePacket) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + ::memset(&value_, 0, static_cast( + reinterpret_cast(&scale_) - + reinterpret_cast(&value_)) + sizeof(scale_)); + } + _has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* FramePacket::_InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + ::PROTOBUF_NAMESPACE_ID::uint32 tag; + ptr = ::PROTOBUF_NAMESPACE_ID::internal::ReadTag(ptr, &tag); + CHK_(ptr); + switch (tag >> 3) { + // optional uint64 value = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 8)) { + _Internal::set_has_value(&has_bits); + value_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional float scale = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 21)) { + _Internal::set_has_scale(&has_bits); + scale_ = ::PROTOBUF_NAMESPACE_ID::internal::UnalignedLoad(ptr); + ptr += sizeof(float); + } else goto handle_unusual; + continue; + default: { + handle_unusual: + if ((tag & 7) == 4 || tag == 0) { + ctx->SetLastTag(tag); + goto success; + } + ptr = UnknownFieldParse(tag, &_internal_metadata_, ptr, ctx); + CHK_(ptr != nullptr); + continue; + } + } // switch + } // while +success: + _has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto success; +#undef CHK_ +} + +::PROTOBUF_NAMESPACE_ID::uint8* FramePacket::_InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.layers.layerscope.FramePacket) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + // optional uint64 value = 1; + if (cached_has_bits & 0x00000001u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteUInt64ToArray(1, this->_internal_value(), target); + } + + // optional float scale = 2; + if (cached_has_bits & 0x00000002u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteFloatToArray(2, this->_internal_scale(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields().data(), + static_cast(_internal_metadata_.unknown_fields().size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.layers.layerscope.FramePacket) + return target; +} + +size_t FramePacket::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.layers.layerscope.FramePacket) + size_t total_size = 0; + + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + // optional uint64 value = 1; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::UInt64Size( + this->_internal_value()); + } + + // optional float scale = 2; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + 4; + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields().size(); + } + int cached_size = ::PROTOBUF_NAMESPACE_ID::internal::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void FramePacket::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::PROTOBUF_NAMESPACE_ID::internal::DownCast( + &from)); +} + +void FramePacket::MergeFrom(const FramePacket& from) { +// @@protoc_insertion_point(class_specific_merge_from_start:mozilla.layers.layerscope.FramePacket) + GOOGLE_DCHECK_NE(&from, this); + _internal_metadata_.MergeFrom(from._internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + value_ = from.value_; + } + if (cached_has_bits & 0x00000002u) { + scale_ = from.scale_; + } + _has_bits_[0] |= cached_has_bits; + } +} + +void FramePacket::CopyFrom(const FramePacket& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.layers.layerscope.FramePacket) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool FramePacket::IsInitialized() const { + return true; +} + +void FramePacket::InternalSwap(FramePacket* other) { + using std::swap; + _internal_metadata_.Swap(&other->_internal_metadata_); + swap(_has_bits_[0], other->_has_bits_[0]); + swap(value_, other->value_); + swap(scale_, other->scale_); +} + +std::string FramePacket::GetTypeName() const { + return "mozilla.layers.layerscope.FramePacket"; +} + + +// =================================================================== + +void ColorPacket::InitAsDefaultInstance() { +} +class ColorPacket::_Internal { + public: + using HasBits = decltype(std::declval()._has_bits_); + static void set_has_layerref(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static void set_has_width(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static void set_has_height(HasBits* has_bits) { + (*has_bits)[0] |= 4u; + } + static void set_has_color(HasBits* has_bits) { + (*has_bits)[0] |= 8u; + } +}; + +ColorPacket::ColorPacket() + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), _internal_metadata_(nullptr) { + SharedCtor(); + // @@protoc_insertion_point(constructor:mozilla.layers.layerscope.ColorPacket) +} +ColorPacket::ColorPacket(const ColorPacket& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), + _internal_metadata_(nullptr), + _has_bits_(from._has_bits_) { + _internal_metadata_.MergeFrom(from._internal_metadata_); + ::memcpy(&layerref_, &from.layerref_, + static_cast(reinterpret_cast(&color_) - + reinterpret_cast(&layerref_)) + sizeof(color_)); + // @@protoc_insertion_point(copy_constructor:mozilla.layers.layerscope.ColorPacket) +} + +void ColorPacket::SharedCtor() { + ::memset(&layerref_, 0, static_cast( + reinterpret_cast(&color_) - + reinterpret_cast(&layerref_)) + sizeof(color_)); +} + +ColorPacket::~ColorPacket() { + // @@protoc_insertion_point(destructor:mozilla.layers.layerscope.ColorPacket) + SharedDtor(); +} + +void ColorPacket::SharedDtor() { +} + +void ColorPacket::SetCachedSize(int size) const { + _cached_size_.Set(size); +} +const ColorPacket& ColorPacket::default_instance() { + ::PROTOBUF_NAMESPACE_ID::internal::InitSCC(&::scc_info_ColorPacket_LayerScopePacket_2eproto.base); + return *internal_default_instance(); +} + + +void ColorPacket::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.layers.layerscope.ColorPacket) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + if (cached_has_bits & 0x0000000fu) { + ::memset(&layerref_, 0, static_cast( + reinterpret_cast(&color_) - + reinterpret_cast(&layerref_)) + sizeof(color_)); + } + _has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* ColorPacket::_InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + ::PROTOBUF_NAMESPACE_ID::uint32 tag; + ptr = ::PROTOBUF_NAMESPACE_ID::internal::ReadTag(ptr, &tag); + CHK_(ptr); + switch (tag >> 3) { + // required uint64 layerref = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 8)) { + _Internal::set_has_layerref(&has_bits); + layerref_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional uint32 width = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 16)) { + _Internal::set_has_width(&has_bits); + width_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional uint32 height = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 24)) { + _Internal::set_has_height(&has_bits); + height_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional uint32 color = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 32)) { + _Internal::set_has_color(&has_bits); + color_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + default: { + handle_unusual: + if ((tag & 7) == 4 || tag == 0) { + ctx->SetLastTag(tag); + goto success; + } + ptr = UnknownFieldParse(tag, &_internal_metadata_, ptr, ctx); + CHK_(ptr != nullptr); + continue; + } + } // switch + } // while +success: + _has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto success; +#undef CHK_ +} + +::PROTOBUF_NAMESPACE_ID::uint8* ColorPacket::_InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.layers.layerscope.ColorPacket) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + // required uint64 layerref = 1; + if (cached_has_bits & 0x00000001u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteUInt64ToArray(1, this->_internal_layerref(), target); + } + + // optional uint32 width = 2; + if (cached_has_bits & 0x00000002u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteUInt32ToArray(2, this->_internal_width(), target); + } + + // optional uint32 height = 3; + if (cached_has_bits & 0x00000004u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteUInt32ToArray(3, this->_internal_height(), target); + } + + // optional uint32 color = 4; + if (cached_has_bits & 0x00000008u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteUInt32ToArray(4, this->_internal_color(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields().data(), + static_cast(_internal_metadata_.unknown_fields().size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.layers.layerscope.ColorPacket) + return target; +} + +size_t ColorPacket::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.layers.layerscope.ColorPacket) + size_t total_size = 0; + + // required uint64 layerref = 1; + if (_internal_has_layerref()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::UInt64Size( + this->_internal_layerref()); + } + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + if (cached_has_bits & 0x0000000eu) { + // optional uint32 width = 2; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::UInt32Size( + this->_internal_width()); + } + + // optional uint32 height = 3; + if (cached_has_bits & 0x00000004u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::UInt32Size( + this->_internal_height()); + } + + // optional uint32 color = 4; + if (cached_has_bits & 0x00000008u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::UInt32Size( + this->_internal_color()); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields().size(); + } + int cached_size = ::PROTOBUF_NAMESPACE_ID::internal::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void ColorPacket::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::PROTOBUF_NAMESPACE_ID::internal::DownCast( + &from)); +} + +void ColorPacket::MergeFrom(const ColorPacket& from) { +// @@protoc_insertion_point(class_specific_merge_from_start:mozilla.layers.layerscope.ColorPacket) + GOOGLE_DCHECK_NE(&from, this); + _internal_metadata_.MergeFrom(from._internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._has_bits_[0]; + if (cached_has_bits & 0x0000000fu) { + if (cached_has_bits & 0x00000001u) { + layerref_ = from.layerref_; + } + if (cached_has_bits & 0x00000002u) { + width_ = from.width_; + } + if (cached_has_bits & 0x00000004u) { + height_ = from.height_; + } + if (cached_has_bits & 0x00000008u) { + color_ = from.color_; + } + _has_bits_[0] |= cached_has_bits; + } +} + +void ColorPacket::CopyFrom(const ColorPacket& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.layers.layerscope.ColorPacket) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool ColorPacket::IsInitialized() const { + if ((_has_bits_[0] & 0x00000001) != 0x00000001) return false; + return true; +} + +void ColorPacket::InternalSwap(ColorPacket* other) { + using std::swap; + _internal_metadata_.Swap(&other->_internal_metadata_); + swap(_has_bits_[0], other->_has_bits_[0]); + swap(layerref_, other->layerref_); + swap(width_, other->width_); + swap(height_, other->height_); + swap(color_, other->color_); +} + +std::string ColorPacket::GetTypeName() const { + return "mozilla.layers.layerscope.ColorPacket"; +} + + +// =================================================================== + +void TexturePacket_Rect::InitAsDefaultInstance() { +} +class TexturePacket_Rect::_Internal { + public: + using HasBits = decltype(std::declval()._has_bits_); + static void set_has_x(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static void set_has_y(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static void set_has_w(HasBits* has_bits) { + (*has_bits)[0] |= 4u; + } + static void set_has_h(HasBits* has_bits) { + (*has_bits)[0] |= 8u; + } +}; + +TexturePacket_Rect::TexturePacket_Rect() + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), _internal_metadata_(nullptr) { + SharedCtor(); + // @@protoc_insertion_point(constructor:mozilla.layers.layerscope.TexturePacket.Rect) +} +TexturePacket_Rect::TexturePacket_Rect(const TexturePacket_Rect& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), + _internal_metadata_(nullptr), + _has_bits_(from._has_bits_) { + _internal_metadata_.MergeFrom(from._internal_metadata_); + ::memcpy(&x_, &from.x_, + static_cast(reinterpret_cast(&h_) - + reinterpret_cast(&x_)) + sizeof(h_)); + // @@protoc_insertion_point(copy_constructor:mozilla.layers.layerscope.TexturePacket.Rect) +} + +void TexturePacket_Rect::SharedCtor() { + ::memset(&x_, 0, static_cast( + reinterpret_cast(&h_) - + reinterpret_cast(&x_)) + sizeof(h_)); +} + +TexturePacket_Rect::~TexturePacket_Rect() { + // @@protoc_insertion_point(destructor:mozilla.layers.layerscope.TexturePacket.Rect) + SharedDtor(); +} + +void TexturePacket_Rect::SharedDtor() { +} + +void TexturePacket_Rect::SetCachedSize(int size) const { + _cached_size_.Set(size); +} +const TexturePacket_Rect& TexturePacket_Rect::default_instance() { + ::PROTOBUF_NAMESPACE_ID::internal::InitSCC(&::scc_info_TexturePacket_Rect_LayerScopePacket_2eproto.base); + return *internal_default_instance(); +} + + +void TexturePacket_Rect::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.layers.layerscope.TexturePacket.Rect) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + if (cached_has_bits & 0x0000000fu) { + ::memset(&x_, 0, static_cast( + reinterpret_cast(&h_) - + reinterpret_cast(&x_)) + sizeof(h_)); + } + _has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* TexturePacket_Rect::_InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + ::PROTOBUF_NAMESPACE_ID::uint32 tag; + ptr = ::PROTOBUF_NAMESPACE_ID::internal::ReadTag(ptr, &tag); + CHK_(ptr); + switch (tag >> 3) { + // optional float x = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 13)) { + _Internal::set_has_x(&has_bits); + x_ = ::PROTOBUF_NAMESPACE_ID::internal::UnalignedLoad(ptr); + ptr += sizeof(float); + } else goto handle_unusual; + continue; + // optional float y = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 21)) { + _Internal::set_has_y(&has_bits); + y_ = ::PROTOBUF_NAMESPACE_ID::internal::UnalignedLoad(ptr); + ptr += sizeof(float); + } else goto handle_unusual; + continue; + // optional float w = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 29)) { + _Internal::set_has_w(&has_bits); + w_ = ::PROTOBUF_NAMESPACE_ID::internal::UnalignedLoad(ptr); + ptr += sizeof(float); + } else goto handle_unusual; + continue; + // optional float h = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 37)) { + _Internal::set_has_h(&has_bits); + h_ = ::PROTOBUF_NAMESPACE_ID::internal::UnalignedLoad(ptr); + ptr += sizeof(float); + } else goto handle_unusual; + continue; + default: { + handle_unusual: + if ((tag & 7) == 4 || tag == 0) { + ctx->SetLastTag(tag); + goto success; + } + ptr = UnknownFieldParse(tag, &_internal_metadata_, ptr, ctx); + CHK_(ptr != nullptr); + continue; + } + } // switch + } // while +success: + _has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto success; +#undef CHK_ +} + +::PROTOBUF_NAMESPACE_ID::uint8* TexturePacket_Rect::_InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.layers.layerscope.TexturePacket.Rect) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + // optional float x = 1; + if (cached_has_bits & 0x00000001u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteFloatToArray(1, this->_internal_x(), target); + } + + // optional float y = 2; + if (cached_has_bits & 0x00000002u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteFloatToArray(2, this->_internal_y(), target); + } + + // optional float w = 3; + if (cached_has_bits & 0x00000004u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteFloatToArray(3, this->_internal_w(), target); + } + + // optional float h = 4; + if (cached_has_bits & 0x00000008u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteFloatToArray(4, this->_internal_h(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields().data(), + static_cast(_internal_metadata_.unknown_fields().size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.layers.layerscope.TexturePacket.Rect) + return target; +} + +size_t TexturePacket_Rect::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.layers.layerscope.TexturePacket.Rect) + size_t total_size = 0; + + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + if (cached_has_bits & 0x0000000fu) { + // optional float x = 1; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + 4; + } + + // optional float y = 2; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + 4; + } + + // optional float w = 3; + if (cached_has_bits & 0x00000004u) { + total_size += 1 + 4; + } + + // optional float h = 4; + if (cached_has_bits & 0x00000008u) { + total_size += 1 + 4; + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields().size(); + } + int cached_size = ::PROTOBUF_NAMESPACE_ID::internal::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void TexturePacket_Rect::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::PROTOBUF_NAMESPACE_ID::internal::DownCast( + &from)); +} + +void TexturePacket_Rect::MergeFrom(const TexturePacket_Rect& from) { +// @@protoc_insertion_point(class_specific_merge_from_start:mozilla.layers.layerscope.TexturePacket.Rect) + GOOGLE_DCHECK_NE(&from, this); + _internal_metadata_.MergeFrom(from._internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._has_bits_[0]; + if (cached_has_bits & 0x0000000fu) { + if (cached_has_bits & 0x00000001u) { + x_ = from.x_; + } + if (cached_has_bits & 0x00000002u) { + y_ = from.y_; + } + if (cached_has_bits & 0x00000004u) { + w_ = from.w_; + } + if (cached_has_bits & 0x00000008u) { + h_ = from.h_; + } + _has_bits_[0] |= cached_has_bits; + } +} + +void TexturePacket_Rect::CopyFrom(const TexturePacket_Rect& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.layers.layerscope.TexturePacket.Rect) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool TexturePacket_Rect::IsInitialized() const { + return true; +} + +void TexturePacket_Rect::InternalSwap(TexturePacket_Rect* other) { + using std::swap; + _internal_metadata_.Swap(&other->_internal_metadata_); + swap(_has_bits_[0], other->_has_bits_[0]); + swap(x_, other->x_); + swap(y_, other->y_); + swap(w_, other->w_); + swap(h_, other->h_); +} + +std::string TexturePacket_Rect::GetTypeName() const { + return "mozilla.layers.layerscope.TexturePacket.Rect"; +} + + +// =================================================================== + +void TexturePacket_Size::InitAsDefaultInstance() { +} +class TexturePacket_Size::_Internal { + public: + using HasBits = decltype(std::declval()._has_bits_); + static void set_has_w(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static void set_has_h(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } +}; + +TexturePacket_Size::TexturePacket_Size() + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), _internal_metadata_(nullptr) { + SharedCtor(); + // @@protoc_insertion_point(constructor:mozilla.layers.layerscope.TexturePacket.Size) +} +TexturePacket_Size::TexturePacket_Size(const TexturePacket_Size& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), + _internal_metadata_(nullptr), + _has_bits_(from._has_bits_) { + _internal_metadata_.MergeFrom(from._internal_metadata_); + ::memcpy(&w_, &from.w_, + static_cast(reinterpret_cast(&h_) - + reinterpret_cast(&w_)) + sizeof(h_)); + // @@protoc_insertion_point(copy_constructor:mozilla.layers.layerscope.TexturePacket.Size) +} + +void TexturePacket_Size::SharedCtor() { + ::memset(&w_, 0, static_cast( + reinterpret_cast(&h_) - + reinterpret_cast(&w_)) + sizeof(h_)); +} + +TexturePacket_Size::~TexturePacket_Size() { + // @@protoc_insertion_point(destructor:mozilla.layers.layerscope.TexturePacket.Size) + SharedDtor(); +} + +void TexturePacket_Size::SharedDtor() { +} + +void TexturePacket_Size::SetCachedSize(int size) const { + _cached_size_.Set(size); +} +const TexturePacket_Size& TexturePacket_Size::default_instance() { + ::PROTOBUF_NAMESPACE_ID::internal::InitSCC(&::scc_info_TexturePacket_Size_LayerScopePacket_2eproto.base); + return *internal_default_instance(); +} + + +void TexturePacket_Size::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.layers.layerscope.TexturePacket.Size) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + ::memset(&w_, 0, static_cast( + reinterpret_cast(&h_) - + reinterpret_cast(&w_)) + sizeof(h_)); + } + _has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* TexturePacket_Size::_InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + ::PROTOBUF_NAMESPACE_ID::uint32 tag; + ptr = ::PROTOBUF_NAMESPACE_ID::internal::ReadTag(ptr, &tag); + CHK_(ptr); + switch (tag >> 3) { + // optional int32 w = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 8)) { + _Internal::set_has_w(&has_bits); + w_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional int32 h = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 16)) { + _Internal::set_has_h(&has_bits); + h_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + default: { + handle_unusual: + if ((tag & 7) == 4 || tag == 0) { + ctx->SetLastTag(tag); + goto success; + } + ptr = UnknownFieldParse(tag, &_internal_metadata_, ptr, ctx); + CHK_(ptr != nullptr); + continue; + } + } // switch + } // while +success: + _has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto success; +#undef CHK_ +} + +::PROTOBUF_NAMESPACE_ID::uint8* TexturePacket_Size::_InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.layers.layerscope.TexturePacket.Size) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + // optional int32 w = 1; + if (cached_has_bits & 0x00000001u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteInt32ToArray(1, this->_internal_w(), target); + } + + // optional int32 h = 2; + if (cached_has_bits & 0x00000002u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteInt32ToArray(2, this->_internal_h(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields().data(), + static_cast(_internal_metadata_.unknown_fields().size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.layers.layerscope.TexturePacket.Size) + return target; +} + +size_t TexturePacket_Size::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.layers.layerscope.TexturePacket.Size) + size_t total_size = 0; + + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + // optional int32 w = 1; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::Int32Size( + this->_internal_w()); + } + + // optional int32 h = 2; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::Int32Size( + this->_internal_h()); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields().size(); + } + int cached_size = ::PROTOBUF_NAMESPACE_ID::internal::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void TexturePacket_Size::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::PROTOBUF_NAMESPACE_ID::internal::DownCast( + &from)); +} + +void TexturePacket_Size::MergeFrom(const TexturePacket_Size& from) { +// @@protoc_insertion_point(class_specific_merge_from_start:mozilla.layers.layerscope.TexturePacket.Size) + GOOGLE_DCHECK_NE(&from, this); + _internal_metadata_.MergeFrom(from._internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + w_ = from.w_; + } + if (cached_has_bits & 0x00000002u) { + h_ = from.h_; + } + _has_bits_[0] |= cached_has_bits; + } +} + +void TexturePacket_Size::CopyFrom(const TexturePacket_Size& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.layers.layerscope.TexturePacket.Size) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool TexturePacket_Size::IsInitialized() const { + return true; +} + +void TexturePacket_Size::InternalSwap(TexturePacket_Size* other) { + using std::swap; + _internal_metadata_.Swap(&other->_internal_metadata_); + swap(_has_bits_[0], other->_has_bits_[0]); + swap(w_, other->w_); + swap(h_, other->h_); +} + +std::string TexturePacket_Size::GetTypeName() const { + return "mozilla.layers.layerscope.TexturePacket.Size"; +} + + +// =================================================================== + +void TexturePacket_Matrix::InitAsDefaultInstance() { +} +class TexturePacket_Matrix::_Internal { + public: + using HasBits = decltype(std::declval()._has_bits_); + static void set_has_is2d(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static void set_has_isid(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } +}; + +TexturePacket_Matrix::TexturePacket_Matrix() + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), _internal_metadata_(nullptr) { + SharedCtor(); + // @@protoc_insertion_point(constructor:mozilla.layers.layerscope.TexturePacket.Matrix) +} +TexturePacket_Matrix::TexturePacket_Matrix(const TexturePacket_Matrix& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), + _internal_metadata_(nullptr), + _has_bits_(from._has_bits_), + m_(from.m_) { + _internal_metadata_.MergeFrom(from._internal_metadata_); + ::memcpy(&is2d_, &from.is2d_, + static_cast(reinterpret_cast(&isid_) - + reinterpret_cast(&is2d_)) + sizeof(isid_)); + // @@protoc_insertion_point(copy_constructor:mozilla.layers.layerscope.TexturePacket.Matrix) +} + +void TexturePacket_Matrix::SharedCtor() { + ::memset(&is2d_, 0, static_cast( + reinterpret_cast(&isid_) - + reinterpret_cast(&is2d_)) + sizeof(isid_)); +} + +TexturePacket_Matrix::~TexturePacket_Matrix() { + // @@protoc_insertion_point(destructor:mozilla.layers.layerscope.TexturePacket.Matrix) + SharedDtor(); +} + +void TexturePacket_Matrix::SharedDtor() { +} + +void TexturePacket_Matrix::SetCachedSize(int size) const { + _cached_size_.Set(size); +} +const TexturePacket_Matrix& TexturePacket_Matrix::default_instance() { + ::PROTOBUF_NAMESPACE_ID::internal::InitSCC(&::scc_info_TexturePacket_Matrix_LayerScopePacket_2eproto.base); + return *internal_default_instance(); +} + + +void TexturePacket_Matrix::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.layers.layerscope.TexturePacket.Matrix) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + m_.Clear(); + ::memset(&is2d_, 0, static_cast( + reinterpret_cast(&isid_) - + reinterpret_cast(&is2d_)) + sizeof(isid_)); + _has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* TexturePacket_Matrix::_InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + ::PROTOBUF_NAMESPACE_ID::uint32 tag; + ptr = ::PROTOBUF_NAMESPACE_ID::internal::ReadTag(ptr, &tag); + CHK_(ptr); + switch (tag >> 3) { + // optional bool is2D = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 8)) { + _Internal::set_has_is2d(&has_bits); + is2d_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional bool isId = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 16)) { + _Internal::set_has_isid(&has_bits); + isid_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // repeated float m = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 29)) { + ptr -= 1; + do { + ptr += 1; + _internal_add_m(::PROTOBUF_NAMESPACE_ID::internal::UnalignedLoad(ptr)); + ptr += sizeof(float); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<29>(ptr)); + } else if (static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 26) { + ptr = ::PROTOBUF_NAMESPACE_ID::internal::PackedFloatParser(_internal_mutable_m(), ptr, ctx); + CHK_(ptr); + } else goto handle_unusual; + continue; + default: { + handle_unusual: + if ((tag & 7) == 4 || tag == 0) { + ctx->SetLastTag(tag); + goto success; + } + ptr = UnknownFieldParse(tag, &_internal_metadata_, ptr, ctx); + CHK_(ptr != nullptr); + continue; + } + } // switch + } // while +success: + _has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto success; +#undef CHK_ +} + +::PROTOBUF_NAMESPACE_ID::uint8* TexturePacket_Matrix::_InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.layers.layerscope.TexturePacket.Matrix) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + // optional bool is2D = 1; + if (cached_has_bits & 0x00000001u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteBoolToArray(1, this->_internal_is2d(), target); + } + + // optional bool isId = 2; + if (cached_has_bits & 0x00000002u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteBoolToArray(2, this->_internal_isid(), target); + } + + // repeated float m = 3; + for (int i = 0, n = this->_internal_m_size(); i < n; i++) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteFloatToArray(3, this->_internal_m(i), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields().data(), + static_cast(_internal_metadata_.unknown_fields().size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.layers.layerscope.TexturePacket.Matrix) + return target; +} + +size_t TexturePacket_Matrix::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.layers.layerscope.TexturePacket.Matrix) + size_t total_size = 0; + + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // repeated float m = 3; + { + unsigned int count = static_cast(this->_internal_m_size()); + size_t data_size = 4UL * count; + total_size += 1 * + ::PROTOBUF_NAMESPACE_ID::internal::FromIntSize(this->_internal_m_size()); + total_size += data_size; + } + + cached_has_bits = _has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + // optional bool is2D = 1; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + 1; + } + + // optional bool isId = 2; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + 1; + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields().size(); + } + int cached_size = ::PROTOBUF_NAMESPACE_ID::internal::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void TexturePacket_Matrix::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::PROTOBUF_NAMESPACE_ID::internal::DownCast( + &from)); +} + +void TexturePacket_Matrix::MergeFrom(const TexturePacket_Matrix& from) { +// @@protoc_insertion_point(class_specific_merge_from_start:mozilla.layers.layerscope.TexturePacket.Matrix) + GOOGLE_DCHECK_NE(&from, this); + _internal_metadata_.MergeFrom(from._internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + m_.MergeFrom(from.m_); + cached_has_bits = from._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + is2d_ = from.is2d_; + } + if (cached_has_bits & 0x00000002u) { + isid_ = from.isid_; + } + _has_bits_[0] |= cached_has_bits; + } +} + +void TexturePacket_Matrix::CopyFrom(const TexturePacket_Matrix& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.layers.layerscope.TexturePacket.Matrix) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool TexturePacket_Matrix::IsInitialized() const { + return true; +} + +void TexturePacket_Matrix::InternalSwap(TexturePacket_Matrix* other) { + using std::swap; + _internal_metadata_.Swap(&other->_internal_metadata_); + swap(_has_bits_[0], other->_has_bits_[0]); + m_.InternalSwap(&other->m_); + swap(is2d_, other->is2d_); + swap(isid_, other->isid_); +} + +std::string TexturePacket_Matrix::GetTypeName() const { + return "mozilla.layers.layerscope.TexturePacket.Matrix"; +} + + +// =================================================================== + +void TexturePacket_EffectMask::InitAsDefaultInstance() { + ::mozilla::layers::layerscope::_TexturePacket_EffectMask_default_instance_._instance.get_mutable()->msize_ = const_cast< ::mozilla::layers::layerscope::TexturePacket_Size*>( + ::mozilla::layers::layerscope::TexturePacket_Size::internal_default_instance()); + ::mozilla::layers::layerscope::_TexturePacket_EffectMask_default_instance_._instance.get_mutable()->mmasktransform_ = const_cast< ::mozilla::layers::layerscope::TexturePacket_Matrix*>( + ::mozilla::layers::layerscope::TexturePacket_Matrix::internal_default_instance()); +} +class TexturePacket_EffectMask::_Internal { + public: + using HasBits = decltype(std::declval()._has_bits_); + static void set_has_mis3d(HasBits* has_bits) { + (*has_bits)[0] |= 4u; + } + static const ::mozilla::layers::layerscope::TexturePacket_Size& msize(const TexturePacket_EffectMask* msg); + static void set_has_msize(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static const ::mozilla::layers::layerscope::TexturePacket_Matrix& mmasktransform(const TexturePacket_EffectMask* msg); + static void set_has_mmasktransform(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } +}; + +const ::mozilla::layers::layerscope::TexturePacket_Size& +TexturePacket_EffectMask::_Internal::msize(const TexturePacket_EffectMask* msg) { + return *msg->msize_; +} +const ::mozilla::layers::layerscope::TexturePacket_Matrix& +TexturePacket_EffectMask::_Internal::mmasktransform(const TexturePacket_EffectMask* msg) { + return *msg->mmasktransform_; +} +TexturePacket_EffectMask::TexturePacket_EffectMask() + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), _internal_metadata_(nullptr) { + SharedCtor(); + // @@protoc_insertion_point(constructor:mozilla.layers.layerscope.TexturePacket.EffectMask) +} +TexturePacket_EffectMask::TexturePacket_EffectMask(const TexturePacket_EffectMask& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), + _internal_metadata_(nullptr), + _has_bits_(from._has_bits_) { + _internal_metadata_.MergeFrom(from._internal_metadata_); + if (from._internal_has_msize()) { + msize_ = new ::mozilla::layers::layerscope::TexturePacket_Size(*from.msize_); + } else { + msize_ = nullptr; + } + if (from._internal_has_mmasktransform()) { + mmasktransform_ = new ::mozilla::layers::layerscope::TexturePacket_Matrix(*from.mmasktransform_); + } else { + mmasktransform_ = nullptr; + } + mis3d_ = from.mis3d_; + // @@protoc_insertion_point(copy_constructor:mozilla.layers.layerscope.TexturePacket.EffectMask) +} + +void TexturePacket_EffectMask::SharedCtor() { + ::PROTOBUF_NAMESPACE_ID::internal::InitSCC(&scc_info_TexturePacket_EffectMask_LayerScopePacket_2eproto.base); + ::memset(&msize_, 0, static_cast( + reinterpret_cast(&mis3d_) - + reinterpret_cast(&msize_)) + sizeof(mis3d_)); +} + +TexturePacket_EffectMask::~TexturePacket_EffectMask() { + // @@protoc_insertion_point(destructor:mozilla.layers.layerscope.TexturePacket.EffectMask) + SharedDtor(); +} + +void TexturePacket_EffectMask::SharedDtor() { + if (this != internal_default_instance()) delete msize_; + if (this != internal_default_instance()) delete mmasktransform_; +} + +void TexturePacket_EffectMask::SetCachedSize(int size) const { + _cached_size_.Set(size); +} +const TexturePacket_EffectMask& TexturePacket_EffectMask::default_instance() { + ::PROTOBUF_NAMESPACE_ID::internal::InitSCC(&::scc_info_TexturePacket_EffectMask_LayerScopePacket_2eproto.base); + return *internal_default_instance(); +} + + +void TexturePacket_EffectMask::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.layers.layerscope.TexturePacket.EffectMask) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + GOOGLE_DCHECK(msize_ != nullptr); + msize_->Clear(); + } + if (cached_has_bits & 0x00000002u) { + GOOGLE_DCHECK(mmasktransform_ != nullptr); + mmasktransform_->Clear(); + } + } + mis3d_ = false; + _has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* TexturePacket_EffectMask::_InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + ::PROTOBUF_NAMESPACE_ID::uint32 tag; + ptr = ::PROTOBUF_NAMESPACE_ID::internal::ReadTag(ptr, &tag); + CHK_(ptr); + switch (tag >> 3) { + // optional bool mIs3D = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 8)) { + _Internal::set_has_mis3d(&has_bits); + mis3d_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional .mozilla.layers.layerscope.TexturePacket.Size mSize = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 18)) { + ptr = ctx->ParseMessage(_internal_mutable_msize(), ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional .mozilla.layers.layerscope.TexturePacket.Matrix mMaskTransform = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_mmasktransform(), ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + default: { + handle_unusual: + if ((tag & 7) == 4 || tag == 0) { + ctx->SetLastTag(tag); + goto success; + } + ptr = UnknownFieldParse(tag, &_internal_metadata_, ptr, ctx); + CHK_(ptr != nullptr); + continue; + } + } // switch + } // while +success: + _has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto success; +#undef CHK_ +} + +::PROTOBUF_NAMESPACE_ID::uint8* TexturePacket_EffectMask::_InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.layers.layerscope.TexturePacket.EffectMask) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + // optional bool mIs3D = 1; + if (cached_has_bits & 0x00000004u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteBoolToArray(1, this->_internal_mis3d(), target); + } + + // optional .mozilla.layers.layerscope.TexturePacket.Size mSize = 2; + if (cached_has_bits & 0x00000001u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage( + 2, _Internal::msize(this), target, stream); + } + + // optional .mozilla.layers.layerscope.TexturePacket.Matrix mMaskTransform = 3; + if (cached_has_bits & 0x00000002u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage( + 3, _Internal::mmasktransform(this), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields().data(), + static_cast(_internal_metadata_.unknown_fields().size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.layers.layerscope.TexturePacket.EffectMask) + return target; +} + +size_t TexturePacket_EffectMask::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.layers.layerscope.TexturePacket.EffectMask) + size_t total_size = 0; + + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + if (cached_has_bits & 0x00000007u) { + // optional .mozilla.layers.layerscope.TexturePacket.Size mSize = 2; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *msize_); + } + + // optional .mozilla.layers.layerscope.TexturePacket.Matrix mMaskTransform = 3; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *mmasktransform_); + } + + // optional bool mIs3D = 1; + if (cached_has_bits & 0x00000004u) { + total_size += 1 + 1; + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields().size(); + } + int cached_size = ::PROTOBUF_NAMESPACE_ID::internal::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void TexturePacket_EffectMask::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::PROTOBUF_NAMESPACE_ID::internal::DownCast( + &from)); +} + +void TexturePacket_EffectMask::MergeFrom(const TexturePacket_EffectMask& from) { +// @@protoc_insertion_point(class_specific_merge_from_start:mozilla.layers.layerscope.TexturePacket.EffectMask) + GOOGLE_DCHECK_NE(&from, this); + _internal_metadata_.MergeFrom(from._internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._has_bits_[0]; + if (cached_has_bits & 0x00000007u) { + if (cached_has_bits & 0x00000001u) { + _internal_mutable_msize()->::mozilla::layers::layerscope::TexturePacket_Size::MergeFrom(from._internal_msize()); + } + if (cached_has_bits & 0x00000002u) { + _internal_mutable_mmasktransform()->::mozilla::layers::layerscope::TexturePacket_Matrix::MergeFrom(from._internal_mmasktransform()); + } + if (cached_has_bits & 0x00000004u) { + mis3d_ = from.mis3d_; + } + _has_bits_[0] |= cached_has_bits; + } +} + +void TexturePacket_EffectMask::CopyFrom(const TexturePacket_EffectMask& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.layers.layerscope.TexturePacket.EffectMask) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool TexturePacket_EffectMask::IsInitialized() const { + return true; +} + +void TexturePacket_EffectMask::InternalSwap(TexturePacket_EffectMask* other) { + using std::swap; + _internal_metadata_.Swap(&other->_internal_metadata_); + swap(_has_bits_[0], other->_has_bits_[0]); + swap(msize_, other->msize_); + swap(mmasktransform_, other->mmasktransform_); + swap(mis3d_, other->mis3d_); +} + +std::string TexturePacket_EffectMask::GetTypeName() const { + return "mozilla.layers.layerscope.TexturePacket.EffectMask"; +} + + +// =================================================================== + +void TexturePacket::InitAsDefaultInstance() { + ::mozilla::layers::layerscope::_TexturePacket_default_instance_._instance.get_mutable()->mtexturecoords_ = const_cast< ::mozilla::layers::layerscope::TexturePacket_Rect*>( + ::mozilla::layers::layerscope::TexturePacket_Rect::internal_default_instance()); + ::mozilla::layers::layerscope::_TexturePacket_default_instance_._instance.get_mutable()->mask_ = const_cast< ::mozilla::layers::layerscope::TexturePacket_EffectMask*>( + ::mozilla::layers::layerscope::TexturePacket_EffectMask::internal_default_instance()); +} +class TexturePacket::_Internal { + public: + using HasBits = decltype(std::declval()._has_bits_); + static void set_has_layerref(HasBits* has_bits) { + (*has_bits)[0] |= 8u; + } + static void set_has_width(HasBits* has_bits) { + (*has_bits)[0] |= 16u; + } + static void set_has_height(HasBits* has_bits) { + (*has_bits)[0] |= 32u; + } + static void set_has_stride(HasBits* has_bits) { + (*has_bits)[0] |= 64u; + } + static void set_has_name(HasBits* has_bits) { + (*has_bits)[0] |= 128u; + } + static void set_has_target(HasBits* has_bits) { + (*has_bits)[0] |= 256u; + } + static void set_has_dataformat(HasBits* has_bits) { + (*has_bits)[0] |= 512u; + } + static void set_has_glcontext(HasBits* has_bits) { + (*has_bits)[0] |= 1024u; + } + static void set_has_data(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static const ::mozilla::layers::layerscope::TexturePacket_Rect& mtexturecoords(const TexturePacket* msg); + static void set_has_mtexturecoords(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static void set_has_mpremultiplied(HasBits* has_bits) { + (*has_bits)[0] |= 4096u; + } + static void set_has_mfilter(HasBits* has_bits) { + (*has_bits)[0] |= 2048u; + } + static void set_has_ismask(HasBits* has_bits) { + (*has_bits)[0] |= 8192u; + } + static const ::mozilla::layers::layerscope::TexturePacket_EffectMask& mask(const TexturePacket* msg); + static void set_has_mask(HasBits* has_bits) { + (*has_bits)[0] |= 4u; + } +}; + +const ::mozilla::layers::layerscope::TexturePacket_Rect& +TexturePacket::_Internal::mtexturecoords(const TexturePacket* msg) { + return *msg->mtexturecoords_; +} +const ::mozilla::layers::layerscope::TexturePacket_EffectMask& +TexturePacket::_Internal::mask(const TexturePacket* msg) { + return *msg->mask_; +} +TexturePacket::TexturePacket() + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), _internal_metadata_(nullptr) { + SharedCtor(); + // @@protoc_insertion_point(constructor:mozilla.layers.layerscope.TexturePacket) +} +TexturePacket::TexturePacket(const TexturePacket& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), + _internal_metadata_(nullptr), + _has_bits_(from._has_bits_) { + _internal_metadata_.MergeFrom(from._internal_metadata_); + data_.UnsafeSetDefault(&::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited()); + if (from._internal_has_data()) { + data_.AssignWithDefault(&::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited(), from.data_); + } + if (from._internal_has_mtexturecoords()) { + mtexturecoords_ = new ::mozilla::layers::layerscope::TexturePacket_Rect(*from.mtexturecoords_); + } else { + mtexturecoords_ = nullptr; + } + if (from._internal_has_mask()) { + mask_ = new ::mozilla::layers::layerscope::TexturePacket_EffectMask(*from.mask_); + } else { + mask_ = nullptr; + } + ::memcpy(&layerref_, &from.layerref_, + static_cast(reinterpret_cast(&ismask_) - + reinterpret_cast(&layerref_)) + sizeof(ismask_)); + // @@protoc_insertion_point(copy_constructor:mozilla.layers.layerscope.TexturePacket) +} + +void TexturePacket::SharedCtor() { + ::PROTOBUF_NAMESPACE_ID::internal::InitSCC(&scc_info_TexturePacket_LayerScopePacket_2eproto.base); + data_.UnsafeSetDefault(&::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited()); + ::memset(&mtexturecoords_, 0, static_cast( + reinterpret_cast(&ismask_) - + reinterpret_cast(&mtexturecoords_)) + sizeof(ismask_)); +} + +TexturePacket::~TexturePacket() { + // @@protoc_insertion_point(destructor:mozilla.layers.layerscope.TexturePacket) + SharedDtor(); +} + +void TexturePacket::SharedDtor() { + data_.DestroyNoArena(&::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited()); + if (this != internal_default_instance()) delete mtexturecoords_; + if (this != internal_default_instance()) delete mask_; +} + +void TexturePacket::SetCachedSize(int size) const { + _cached_size_.Set(size); +} +const TexturePacket& TexturePacket::default_instance() { + ::PROTOBUF_NAMESPACE_ID::internal::InitSCC(&::scc_info_TexturePacket_LayerScopePacket_2eproto.base); + return *internal_default_instance(); +} + + +void TexturePacket::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.layers.layerscope.TexturePacket) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + if (cached_has_bits & 0x00000007u) { + if (cached_has_bits & 0x00000001u) { + data_.ClearNonDefaultToEmptyNoArena(); + } + if (cached_has_bits & 0x00000002u) { + GOOGLE_DCHECK(mtexturecoords_ != nullptr); + mtexturecoords_->Clear(); + } + if (cached_has_bits & 0x00000004u) { + GOOGLE_DCHECK(mask_ != nullptr); + mask_->Clear(); + } + } + if (cached_has_bits & 0x000000f8u) { + ::memset(&layerref_, 0, static_cast( + reinterpret_cast(&name_) - + reinterpret_cast(&layerref_)) + sizeof(name_)); + } + if (cached_has_bits & 0x00003f00u) { + ::memset(&target_, 0, static_cast( + reinterpret_cast(&ismask_) - + reinterpret_cast(&target_)) + sizeof(ismask_)); + } + _has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* TexturePacket::_InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + ::PROTOBUF_NAMESPACE_ID::uint32 tag; + ptr = ::PROTOBUF_NAMESPACE_ID::internal::ReadTag(ptr, &tag); + CHK_(ptr); + switch (tag >> 3) { + // required uint64 layerref = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 8)) { + _Internal::set_has_layerref(&has_bits); + layerref_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional uint32 width = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 16)) { + _Internal::set_has_width(&has_bits); + width_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional uint32 height = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 24)) { + _Internal::set_has_height(&has_bits); + height_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional uint32 stride = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 32)) { + _Internal::set_has_stride(&has_bits); + stride_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional uint32 name = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 40)) { + _Internal::set_has_name(&has_bits); + name_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional uint32 target = 6; + case 6: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 48)) { + _Internal::set_has_target(&has_bits); + target_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional uint32 dataformat = 7; + case 7: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 56)) { + _Internal::set_has_dataformat(&has_bits); + dataformat_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional uint64 glcontext = 8; + case 8: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 64)) { + _Internal::set_has_glcontext(&has_bits); + glcontext_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional bytes data = 9; + case 9: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 74)) { + auto str = _internal_mutable_data(); + ptr = ::PROTOBUF_NAMESPACE_ID::internal::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional .mozilla.layers.layerscope.TexturePacket.Rect mTextureCoords = 10; + case 10: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 82)) { + ptr = ctx->ParseMessage(_internal_mutable_mtexturecoords(), ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional bool mPremultiplied = 11; + case 11: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 88)) { + _Internal::set_has_mpremultiplied(&has_bits); + mpremultiplied_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional .mozilla.layers.layerscope.TexturePacket.Filter mFilter = 12; + case 12: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 96)) { + ::PROTOBUF_NAMESPACE_ID::uint64 val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::mozilla::layers::layerscope::TexturePacket_Filter_IsValid(val))) { + _internal_set_mfilter(static_cast<::mozilla::layers::layerscope::TexturePacket_Filter>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(12, val, mutable_unknown_fields()); + } + } else goto handle_unusual; + continue; + // optional bool isMask = 20; + case 20: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 160)) { + _Internal::set_has_ismask(&has_bits); + ismask_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional .mozilla.layers.layerscope.TexturePacket.EffectMask mask = 21; + case 21: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 170)) { + ptr = ctx->ParseMessage(_internal_mutable_mask(), ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + default: { + handle_unusual: + if ((tag & 7) == 4 || tag == 0) { + ctx->SetLastTag(tag); + goto success; + } + ptr = UnknownFieldParse(tag, &_internal_metadata_, ptr, ctx); + CHK_(ptr != nullptr); + continue; + } + } // switch + } // while +success: + _has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto success; +#undef CHK_ +} + +::PROTOBUF_NAMESPACE_ID::uint8* TexturePacket::_InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.layers.layerscope.TexturePacket) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + // required uint64 layerref = 1; + if (cached_has_bits & 0x00000008u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteUInt64ToArray(1, this->_internal_layerref(), target); + } + + // optional uint32 width = 2; + if (cached_has_bits & 0x00000010u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteUInt32ToArray(2, this->_internal_width(), target); + } + + // optional uint32 height = 3; + if (cached_has_bits & 0x00000020u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteUInt32ToArray(3, this->_internal_height(), target); + } + + // optional uint32 stride = 4; + if (cached_has_bits & 0x00000040u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteUInt32ToArray(4, this->_internal_stride(), target); + } + + // optional uint32 name = 5; + if (cached_has_bits & 0x00000080u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteUInt32ToArray(5, this->_internal_name(), target); + } + + // optional uint32 target = 6; + if (cached_has_bits & 0x00000100u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteUInt32ToArray(6, this->_internal_target(), target); + } + + // optional uint32 dataformat = 7; + if (cached_has_bits & 0x00000200u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteUInt32ToArray(7, this->_internal_dataformat(), target); + } + + // optional uint64 glcontext = 8; + if (cached_has_bits & 0x00000400u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteUInt64ToArray(8, this->_internal_glcontext(), target); + } + + // optional bytes data = 9; + if (cached_has_bits & 0x00000001u) { + target = stream->WriteBytesMaybeAliased( + 9, this->_internal_data(), target); + } + + // optional .mozilla.layers.layerscope.TexturePacket.Rect mTextureCoords = 10; + if (cached_has_bits & 0x00000002u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage( + 10, _Internal::mtexturecoords(this), target, stream); + } + + // optional bool mPremultiplied = 11; + if (cached_has_bits & 0x00001000u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteBoolToArray(11, this->_internal_mpremultiplied(), target); + } + + // optional .mozilla.layers.layerscope.TexturePacket.Filter mFilter = 12; + if (cached_has_bits & 0x00000800u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteEnumToArray( + 12, this->_internal_mfilter(), target); + } + + // optional bool isMask = 20; + if (cached_has_bits & 0x00002000u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteBoolToArray(20, this->_internal_ismask(), target); + } + + // optional .mozilla.layers.layerscope.TexturePacket.EffectMask mask = 21; + if (cached_has_bits & 0x00000004u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage( + 21, _Internal::mask(this), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields().data(), + static_cast(_internal_metadata_.unknown_fields().size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.layers.layerscope.TexturePacket) + return target; +} + +size_t TexturePacket::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.layers.layerscope.TexturePacket) + size_t total_size = 0; + + // required uint64 layerref = 1; + if (_internal_has_layerref()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::UInt64Size( + this->_internal_layerref()); + } + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + if (cached_has_bits & 0x00000007u) { + // optional bytes data = 9; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + this->_internal_data()); + } + + // optional .mozilla.layers.layerscope.TexturePacket.Rect mTextureCoords = 10; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *mtexturecoords_); + } + + // optional .mozilla.layers.layerscope.TexturePacket.EffectMask mask = 21; + if (cached_has_bits & 0x00000004u) { + total_size += 2 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *mask_); + } + + } + if (cached_has_bits & 0x000000f0u) { + // optional uint32 width = 2; + if (cached_has_bits & 0x00000010u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::UInt32Size( + this->_internal_width()); + } + + // optional uint32 height = 3; + if (cached_has_bits & 0x00000020u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::UInt32Size( + this->_internal_height()); + } + + // optional uint32 stride = 4; + if (cached_has_bits & 0x00000040u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::UInt32Size( + this->_internal_stride()); + } + + // optional uint32 name = 5; + if (cached_has_bits & 0x00000080u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::UInt32Size( + this->_internal_name()); + } + + } + if (cached_has_bits & 0x00003f00u) { + // optional uint32 target = 6; + if (cached_has_bits & 0x00000100u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::UInt32Size( + this->_internal_target()); + } + + // optional uint32 dataformat = 7; + if (cached_has_bits & 0x00000200u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::UInt32Size( + this->_internal_dataformat()); + } + + // optional uint64 glcontext = 8; + if (cached_has_bits & 0x00000400u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::UInt64Size( + this->_internal_glcontext()); + } + + // optional .mozilla.layers.layerscope.TexturePacket.Filter mFilter = 12; + if (cached_has_bits & 0x00000800u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::EnumSize(this->_internal_mfilter()); + } + + // optional bool mPremultiplied = 11; + if (cached_has_bits & 0x00001000u) { + total_size += 1 + 1; + } + + // optional bool isMask = 20; + if (cached_has_bits & 0x00002000u) { + total_size += 2 + 1; + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields().size(); + } + int cached_size = ::PROTOBUF_NAMESPACE_ID::internal::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void TexturePacket::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::PROTOBUF_NAMESPACE_ID::internal::DownCast( + &from)); +} + +void TexturePacket::MergeFrom(const TexturePacket& from) { +// @@protoc_insertion_point(class_specific_merge_from_start:mozilla.layers.layerscope.TexturePacket) + GOOGLE_DCHECK_NE(&from, this); + _internal_metadata_.MergeFrom(from._internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._has_bits_[0]; + if (cached_has_bits & 0x000000ffu) { + if (cached_has_bits & 0x00000001u) { + _has_bits_[0] |= 0x00000001u; + data_.AssignWithDefault(&::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited(), from.data_); + } + if (cached_has_bits & 0x00000002u) { + _internal_mutable_mtexturecoords()->::mozilla::layers::layerscope::TexturePacket_Rect::MergeFrom(from._internal_mtexturecoords()); + } + if (cached_has_bits & 0x00000004u) { + _internal_mutable_mask()->::mozilla::layers::layerscope::TexturePacket_EffectMask::MergeFrom(from._internal_mask()); + } + if (cached_has_bits & 0x00000008u) { + layerref_ = from.layerref_; + } + if (cached_has_bits & 0x00000010u) { + width_ = from.width_; + } + if (cached_has_bits & 0x00000020u) { + height_ = from.height_; + } + if (cached_has_bits & 0x00000040u) { + stride_ = from.stride_; + } + if (cached_has_bits & 0x00000080u) { + name_ = from.name_; + } + _has_bits_[0] |= cached_has_bits; + } + if (cached_has_bits & 0x00003f00u) { + if (cached_has_bits & 0x00000100u) { + target_ = from.target_; + } + if (cached_has_bits & 0x00000200u) { + dataformat_ = from.dataformat_; + } + if (cached_has_bits & 0x00000400u) { + glcontext_ = from.glcontext_; + } + if (cached_has_bits & 0x00000800u) { + mfilter_ = from.mfilter_; + } + if (cached_has_bits & 0x00001000u) { + mpremultiplied_ = from.mpremultiplied_; + } + if (cached_has_bits & 0x00002000u) { + ismask_ = from.ismask_; + } + _has_bits_[0] |= cached_has_bits; + } +} + +void TexturePacket::CopyFrom(const TexturePacket& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.layers.layerscope.TexturePacket) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool TexturePacket::IsInitialized() const { + if ((_has_bits_[0] & 0x00000008) != 0x00000008) return false; + return true; +} + +void TexturePacket::InternalSwap(TexturePacket* other) { + using std::swap; + _internal_metadata_.Swap(&other->_internal_metadata_); + swap(_has_bits_[0], other->_has_bits_[0]); + data_.Swap(&other->data_, &::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited(), + GetArenaNoVirtual()); + swap(mtexturecoords_, other->mtexturecoords_); + swap(mask_, other->mask_); + swap(layerref_, other->layerref_); + swap(width_, other->width_); + swap(height_, other->height_); + swap(stride_, other->stride_); + swap(name_, other->name_); + swap(target_, other->target_); + swap(dataformat_, other->dataformat_); + swap(glcontext_, other->glcontext_); + swap(mfilter_, other->mfilter_); + swap(mpremultiplied_, other->mpremultiplied_); + swap(ismask_, other->ismask_); +} + +std::string TexturePacket::GetTypeName() const { + return "mozilla.layers.layerscope.TexturePacket"; +} + + +// =================================================================== + +void LayersPacket_Layer_Size::InitAsDefaultInstance() { +} +class LayersPacket_Layer_Size::_Internal { + public: + using HasBits = decltype(std::declval()._has_bits_); + static void set_has_w(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static void set_has_h(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } +}; + +LayersPacket_Layer_Size::LayersPacket_Layer_Size() + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), _internal_metadata_(nullptr) { + SharedCtor(); + // @@protoc_insertion_point(constructor:mozilla.layers.layerscope.LayersPacket.Layer.Size) +} +LayersPacket_Layer_Size::LayersPacket_Layer_Size(const LayersPacket_Layer_Size& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), + _internal_metadata_(nullptr), + _has_bits_(from._has_bits_) { + _internal_metadata_.MergeFrom(from._internal_metadata_); + ::memcpy(&w_, &from.w_, + static_cast(reinterpret_cast(&h_) - + reinterpret_cast(&w_)) + sizeof(h_)); + // @@protoc_insertion_point(copy_constructor:mozilla.layers.layerscope.LayersPacket.Layer.Size) +} + +void LayersPacket_Layer_Size::SharedCtor() { + ::memset(&w_, 0, static_cast( + reinterpret_cast(&h_) - + reinterpret_cast(&w_)) + sizeof(h_)); +} + +LayersPacket_Layer_Size::~LayersPacket_Layer_Size() { + // @@protoc_insertion_point(destructor:mozilla.layers.layerscope.LayersPacket.Layer.Size) + SharedDtor(); +} + +void LayersPacket_Layer_Size::SharedDtor() { +} + +void LayersPacket_Layer_Size::SetCachedSize(int size) const { + _cached_size_.Set(size); +} +const LayersPacket_Layer_Size& LayersPacket_Layer_Size::default_instance() { + ::PROTOBUF_NAMESPACE_ID::internal::InitSCC(&::scc_info_LayersPacket_Layer_Size_LayerScopePacket_2eproto.base); + return *internal_default_instance(); +} + + +void LayersPacket_Layer_Size::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.layers.layerscope.LayersPacket.Layer.Size) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + ::memset(&w_, 0, static_cast( + reinterpret_cast(&h_) - + reinterpret_cast(&w_)) + sizeof(h_)); + } + _has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* LayersPacket_Layer_Size::_InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + ::PROTOBUF_NAMESPACE_ID::uint32 tag; + ptr = ::PROTOBUF_NAMESPACE_ID::internal::ReadTag(ptr, &tag); + CHK_(ptr); + switch (tag >> 3) { + // optional int32 w = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 8)) { + _Internal::set_has_w(&has_bits); + w_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional int32 h = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 16)) { + _Internal::set_has_h(&has_bits); + h_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + default: { + handle_unusual: + if ((tag & 7) == 4 || tag == 0) { + ctx->SetLastTag(tag); + goto success; + } + ptr = UnknownFieldParse(tag, &_internal_metadata_, ptr, ctx); + CHK_(ptr != nullptr); + continue; + } + } // switch + } // while +success: + _has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto success; +#undef CHK_ +} + +::PROTOBUF_NAMESPACE_ID::uint8* LayersPacket_Layer_Size::_InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.layers.layerscope.LayersPacket.Layer.Size) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + // optional int32 w = 1; + if (cached_has_bits & 0x00000001u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteInt32ToArray(1, this->_internal_w(), target); + } + + // optional int32 h = 2; + if (cached_has_bits & 0x00000002u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteInt32ToArray(2, this->_internal_h(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields().data(), + static_cast(_internal_metadata_.unknown_fields().size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.layers.layerscope.LayersPacket.Layer.Size) + return target; +} + +size_t LayersPacket_Layer_Size::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.layers.layerscope.LayersPacket.Layer.Size) + size_t total_size = 0; + + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + // optional int32 w = 1; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::Int32Size( + this->_internal_w()); + } + + // optional int32 h = 2; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::Int32Size( + this->_internal_h()); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields().size(); + } + int cached_size = ::PROTOBUF_NAMESPACE_ID::internal::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void LayersPacket_Layer_Size::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::PROTOBUF_NAMESPACE_ID::internal::DownCast( + &from)); +} + +void LayersPacket_Layer_Size::MergeFrom(const LayersPacket_Layer_Size& from) { +// @@protoc_insertion_point(class_specific_merge_from_start:mozilla.layers.layerscope.LayersPacket.Layer.Size) + GOOGLE_DCHECK_NE(&from, this); + _internal_metadata_.MergeFrom(from._internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + w_ = from.w_; + } + if (cached_has_bits & 0x00000002u) { + h_ = from.h_; + } + _has_bits_[0] |= cached_has_bits; + } +} + +void LayersPacket_Layer_Size::CopyFrom(const LayersPacket_Layer_Size& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.layers.layerscope.LayersPacket.Layer.Size) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool LayersPacket_Layer_Size::IsInitialized() const { + return true; +} + +void LayersPacket_Layer_Size::InternalSwap(LayersPacket_Layer_Size* other) { + using std::swap; + _internal_metadata_.Swap(&other->_internal_metadata_); + swap(_has_bits_[0], other->_has_bits_[0]); + swap(w_, other->w_); + swap(h_, other->h_); +} + +std::string LayersPacket_Layer_Size::GetTypeName() const { + return "mozilla.layers.layerscope.LayersPacket.Layer.Size"; +} + + +// =================================================================== + +void LayersPacket_Layer_Rect::InitAsDefaultInstance() { +} +class LayersPacket_Layer_Rect::_Internal { + public: + using HasBits = decltype(std::declval()._has_bits_); + static void set_has_x(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static void set_has_y(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static void set_has_w(HasBits* has_bits) { + (*has_bits)[0] |= 4u; + } + static void set_has_h(HasBits* has_bits) { + (*has_bits)[0] |= 8u; + } +}; + +LayersPacket_Layer_Rect::LayersPacket_Layer_Rect() + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), _internal_metadata_(nullptr) { + SharedCtor(); + // @@protoc_insertion_point(constructor:mozilla.layers.layerscope.LayersPacket.Layer.Rect) +} +LayersPacket_Layer_Rect::LayersPacket_Layer_Rect(const LayersPacket_Layer_Rect& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), + _internal_metadata_(nullptr), + _has_bits_(from._has_bits_) { + _internal_metadata_.MergeFrom(from._internal_metadata_); + ::memcpy(&x_, &from.x_, + static_cast(reinterpret_cast(&h_) - + reinterpret_cast(&x_)) + sizeof(h_)); + // @@protoc_insertion_point(copy_constructor:mozilla.layers.layerscope.LayersPacket.Layer.Rect) +} + +void LayersPacket_Layer_Rect::SharedCtor() { + ::memset(&x_, 0, static_cast( + reinterpret_cast(&h_) - + reinterpret_cast(&x_)) + sizeof(h_)); +} + +LayersPacket_Layer_Rect::~LayersPacket_Layer_Rect() { + // @@protoc_insertion_point(destructor:mozilla.layers.layerscope.LayersPacket.Layer.Rect) + SharedDtor(); +} + +void LayersPacket_Layer_Rect::SharedDtor() { +} + +void LayersPacket_Layer_Rect::SetCachedSize(int size) const { + _cached_size_.Set(size); +} +const LayersPacket_Layer_Rect& LayersPacket_Layer_Rect::default_instance() { + ::PROTOBUF_NAMESPACE_ID::internal::InitSCC(&::scc_info_LayersPacket_Layer_Rect_LayerScopePacket_2eproto.base); + return *internal_default_instance(); +} + + +void LayersPacket_Layer_Rect::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.layers.layerscope.LayersPacket.Layer.Rect) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + if (cached_has_bits & 0x0000000fu) { + ::memset(&x_, 0, static_cast( + reinterpret_cast(&h_) - + reinterpret_cast(&x_)) + sizeof(h_)); + } + _has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* LayersPacket_Layer_Rect::_InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + ::PROTOBUF_NAMESPACE_ID::uint32 tag; + ptr = ::PROTOBUF_NAMESPACE_ID::internal::ReadTag(ptr, &tag); + CHK_(ptr); + switch (tag >> 3) { + // optional int32 x = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 8)) { + _Internal::set_has_x(&has_bits); + x_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional int32 y = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 16)) { + _Internal::set_has_y(&has_bits); + y_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional int32 w = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 24)) { + _Internal::set_has_w(&has_bits); + w_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional int32 h = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 32)) { + _Internal::set_has_h(&has_bits); + h_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + default: { + handle_unusual: + if ((tag & 7) == 4 || tag == 0) { + ctx->SetLastTag(tag); + goto success; + } + ptr = UnknownFieldParse(tag, &_internal_metadata_, ptr, ctx); + CHK_(ptr != nullptr); + continue; + } + } // switch + } // while +success: + _has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto success; +#undef CHK_ +} + +::PROTOBUF_NAMESPACE_ID::uint8* LayersPacket_Layer_Rect::_InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.layers.layerscope.LayersPacket.Layer.Rect) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + // optional int32 x = 1; + if (cached_has_bits & 0x00000001u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteInt32ToArray(1, this->_internal_x(), target); + } + + // optional int32 y = 2; + if (cached_has_bits & 0x00000002u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteInt32ToArray(2, this->_internal_y(), target); + } + + // optional int32 w = 3; + if (cached_has_bits & 0x00000004u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteInt32ToArray(3, this->_internal_w(), target); + } + + // optional int32 h = 4; + if (cached_has_bits & 0x00000008u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteInt32ToArray(4, this->_internal_h(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields().data(), + static_cast(_internal_metadata_.unknown_fields().size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.layers.layerscope.LayersPacket.Layer.Rect) + return target; +} + +size_t LayersPacket_Layer_Rect::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.layers.layerscope.LayersPacket.Layer.Rect) + size_t total_size = 0; + + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + if (cached_has_bits & 0x0000000fu) { + // optional int32 x = 1; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::Int32Size( + this->_internal_x()); + } + + // optional int32 y = 2; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::Int32Size( + this->_internal_y()); + } + + // optional int32 w = 3; + if (cached_has_bits & 0x00000004u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::Int32Size( + this->_internal_w()); + } + + // optional int32 h = 4; + if (cached_has_bits & 0x00000008u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::Int32Size( + this->_internal_h()); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields().size(); + } + int cached_size = ::PROTOBUF_NAMESPACE_ID::internal::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void LayersPacket_Layer_Rect::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::PROTOBUF_NAMESPACE_ID::internal::DownCast( + &from)); +} + +void LayersPacket_Layer_Rect::MergeFrom(const LayersPacket_Layer_Rect& from) { +// @@protoc_insertion_point(class_specific_merge_from_start:mozilla.layers.layerscope.LayersPacket.Layer.Rect) + GOOGLE_DCHECK_NE(&from, this); + _internal_metadata_.MergeFrom(from._internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._has_bits_[0]; + if (cached_has_bits & 0x0000000fu) { + if (cached_has_bits & 0x00000001u) { + x_ = from.x_; + } + if (cached_has_bits & 0x00000002u) { + y_ = from.y_; + } + if (cached_has_bits & 0x00000004u) { + w_ = from.w_; + } + if (cached_has_bits & 0x00000008u) { + h_ = from.h_; + } + _has_bits_[0] |= cached_has_bits; + } +} + +void LayersPacket_Layer_Rect::CopyFrom(const LayersPacket_Layer_Rect& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.layers.layerscope.LayersPacket.Layer.Rect) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool LayersPacket_Layer_Rect::IsInitialized() const { + return true; +} + +void LayersPacket_Layer_Rect::InternalSwap(LayersPacket_Layer_Rect* other) { + using std::swap; + _internal_metadata_.Swap(&other->_internal_metadata_); + swap(_has_bits_[0], other->_has_bits_[0]); + swap(x_, other->x_); + swap(y_, other->y_); + swap(w_, other->w_); + swap(h_, other->h_); +} + +std::string LayersPacket_Layer_Rect::GetTypeName() const { + return "mozilla.layers.layerscope.LayersPacket.Layer.Rect"; +} + + +// =================================================================== + +void LayersPacket_Layer_Region::InitAsDefaultInstance() { +} +class LayersPacket_Layer_Region::_Internal { + public: + using HasBits = decltype(std::declval()._has_bits_); +}; + +LayersPacket_Layer_Region::LayersPacket_Layer_Region() + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), _internal_metadata_(nullptr) { + SharedCtor(); + // @@protoc_insertion_point(constructor:mozilla.layers.layerscope.LayersPacket.Layer.Region) +} +LayersPacket_Layer_Region::LayersPacket_Layer_Region(const LayersPacket_Layer_Region& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), + _internal_metadata_(nullptr), + _has_bits_(from._has_bits_), + r_(from.r_) { + _internal_metadata_.MergeFrom(from._internal_metadata_); + // @@protoc_insertion_point(copy_constructor:mozilla.layers.layerscope.LayersPacket.Layer.Region) +} + +void LayersPacket_Layer_Region::SharedCtor() { + ::PROTOBUF_NAMESPACE_ID::internal::InitSCC(&scc_info_LayersPacket_Layer_Region_LayerScopePacket_2eproto.base); +} + +LayersPacket_Layer_Region::~LayersPacket_Layer_Region() { + // @@protoc_insertion_point(destructor:mozilla.layers.layerscope.LayersPacket.Layer.Region) + SharedDtor(); +} + +void LayersPacket_Layer_Region::SharedDtor() { +} + +void LayersPacket_Layer_Region::SetCachedSize(int size) const { + _cached_size_.Set(size); +} +const LayersPacket_Layer_Region& LayersPacket_Layer_Region::default_instance() { + ::PROTOBUF_NAMESPACE_ID::internal::InitSCC(&::scc_info_LayersPacket_Layer_Region_LayerScopePacket_2eproto.base); + return *internal_default_instance(); +} + + +void LayersPacket_Layer_Region::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.layers.layerscope.LayersPacket.Layer.Region) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + r_.Clear(); + _has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* LayersPacket_Layer_Region::_InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + ::PROTOBUF_NAMESPACE_ID::uint32 tag; + ptr = ::PROTOBUF_NAMESPACE_ID::internal::ReadTag(ptr, &tag); + CHK_(ptr); + switch (tag >> 3) { + // repeated .mozilla.layers.layerscope.LayersPacket.Layer.Rect r = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 10)) { + ptr -= 1; + do { + ptr += 1; + ptr = ctx->ParseMessage(_internal_add_r(), ptr); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<10>(ptr)); + } else goto handle_unusual; + continue; + default: { + handle_unusual: + if ((tag & 7) == 4 || tag == 0) { + ctx->SetLastTag(tag); + goto success; + } + ptr = UnknownFieldParse(tag, &_internal_metadata_, ptr, ctx); + CHK_(ptr != nullptr); + continue; + } + } // switch + } // while +success: + return ptr; +failure: + ptr = nullptr; + goto success; +#undef CHK_ +} + +::PROTOBUF_NAMESPACE_ID::uint8* LayersPacket_Layer_Region::_InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.layers.layerscope.LayersPacket.Layer.Region) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + // repeated .mozilla.layers.layerscope.LayersPacket.Layer.Rect r = 1; + for (unsigned int i = 0, + n = static_cast(this->_internal_r_size()); i < n; i++) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(1, this->_internal_r(i), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields().data(), + static_cast(_internal_metadata_.unknown_fields().size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.layers.layerscope.LayersPacket.Layer.Region) + return target; +} + +size_t LayersPacket_Layer_Region::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.layers.layerscope.LayersPacket.Layer.Region) + size_t total_size = 0; + + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // repeated .mozilla.layers.layerscope.LayersPacket.Layer.Rect r = 1; + total_size += 1UL * this->_internal_r_size(); + for (const auto& msg : this->r_) { + total_size += + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize(msg); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields().size(); + } + int cached_size = ::PROTOBUF_NAMESPACE_ID::internal::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void LayersPacket_Layer_Region::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::PROTOBUF_NAMESPACE_ID::internal::DownCast( + &from)); +} + +void LayersPacket_Layer_Region::MergeFrom(const LayersPacket_Layer_Region& from) { +// @@protoc_insertion_point(class_specific_merge_from_start:mozilla.layers.layerscope.LayersPacket.Layer.Region) + GOOGLE_DCHECK_NE(&from, this); + _internal_metadata_.MergeFrom(from._internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + r_.MergeFrom(from.r_); +} + +void LayersPacket_Layer_Region::CopyFrom(const LayersPacket_Layer_Region& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.layers.layerscope.LayersPacket.Layer.Region) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool LayersPacket_Layer_Region::IsInitialized() const { + return true; +} + +void LayersPacket_Layer_Region::InternalSwap(LayersPacket_Layer_Region* other) { + using std::swap; + _internal_metadata_.Swap(&other->_internal_metadata_); + swap(_has_bits_[0], other->_has_bits_[0]); + r_.InternalSwap(&other->r_); +} + +std::string LayersPacket_Layer_Region::GetTypeName() const { + return "mozilla.layers.layerscope.LayersPacket.Layer.Region"; +} + + +// =================================================================== + +void LayersPacket_Layer_Matrix::InitAsDefaultInstance() { +} +class LayersPacket_Layer_Matrix::_Internal { + public: + using HasBits = decltype(std::declval()._has_bits_); + static void set_has_is2d(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static void set_has_isid(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } +}; + +LayersPacket_Layer_Matrix::LayersPacket_Layer_Matrix() + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), _internal_metadata_(nullptr) { + SharedCtor(); + // @@protoc_insertion_point(constructor:mozilla.layers.layerscope.LayersPacket.Layer.Matrix) +} +LayersPacket_Layer_Matrix::LayersPacket_Layer_Matrix(const LayersPacket_Layer_Matrix& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), + _internal_metadata_(nullptr), + _has_bits_(from._has_bits_), + m_(from.m_) { + _internal_metadata_.MergeFrom(from._internal_metadata_); + ::memcpy(&is2d_, &from.is2d_, + static_cast(reinterpret_cast(&isid_) - + reinterpret_cast(&is2d_)) + sizeof(isid_)); + // @@protoc_insertion_point(copy_constructor:mozilla.layers.layerscope.LayersPacket.Layer.Matrix) +} + +void LayersPacket_Layer_Matrix::SharedCtor() { + ::memset(&is2d_, 0, static_cast( + reinterpret_cast(&isid_) - + reinterpret_cast(&is2d_)) + sizeof(isid_)); +} + +LayersPacket_Layer_Matrix::~LayersPacket_Layer_Matrix() { + // @@protoc_insertion_point(destructor:mozilla.layers.layerscope.LayersPacket.Layer.Matrix) + SharedDtor(); +} + +void LayersPacket_Layer_Matrix::SharedDtor() { +} + +void LayersPacket_Layer_Matrix::SetCachedSize(int size) const { + _cached_size_.Set(size); +} +const LayersPacket_Layer_Matrix& LayersPacket_Layer_Matrix::default_instance() { + ::PROTOBUF_NAMESPACE_ID::internal::InitSCC(&::scc_info_LayersPacket_Layer_Matrix_LayerScopePacket_2eproto.base); + return *internal_default_instance(); +} + + +void LayersPacket_Layer_Matrix::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.layers.layerscope.LayersPacket.Layer.Matrix) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + m_.Clear(); + ::memset(&is2d_, 0, static_cast( + reinterpret_cast(&isid_) - + reinterpret_cast(&is2d_)) + sizeof(isid_)); + _has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* LayersPacket_Layer_Matrix::_InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + ::PROTOBUF_NAMESPACE_ID::uint32 tag; + ptr = ::PROTOBUF_NAMESPACE_ID::internal::ReadTag(ptr, &tag); + CHK_(ptr); + switch (tag >> 3) { + // optional bool is2D = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 8)) { + _Internal::set_has_is2d(&has_bits); + is2d_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional bool isId = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 16)) { + _Internal::set_has_isid(&has_bits); + isid_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // repeated float m = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 29)) { + ptr -= 1; + do { + ptr += 1; + _internal_add_m(::PROTOBUF_NAMESPACE_ID::internal::UnalignedLoad(ptr)); + ptr += sizeof(float); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<29>(ptr)); + } else if (static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 26) { + ptr = ::PROTOBUF_NAMESPACE_ID::internal::PackedFloatParser(_internal_mutable_m(), ptr, ctx); + CHK_(ptr); + } else goto handle_unusual; + continue; + default: { + handle_unusual: + if ((tag & 7) == 4 || tag == 0) { + ctx->SetLastTag(tag); + goto success; + } + ptr = UnknownFieldParse(tag, &_internal_metadata_, ptr, ctx); + CHK_(ptr != nullptr); + continue; + } + } // switch + } // while +success: + _has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto success; +#undef CHK_ +} + +::PROTOBUF_NAMESPACE_ID::uint8* LayersPacket_Layer_Matrix::_InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.layers.layerscope.LayersPacket.Layer.Matrix) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + // optional bool is2D = 1; + if (cached_has_bits & 0x00000001u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteBoolToArray(1, this->_internal_is2d(), target); + } + + // optional bool isId = 2; + if (cached_has_bits & 0x00000002u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteBoolToArray(2, this->_internal_isid(), target); + } + + // repeated float m = 3; + for (int i = 0, n = this->_internal_m_size(); i < n; i++) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteFloatToArray(3, this->_internal_m(i), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields().data(), + static_cast(_internal_metadata_.unknown_fields().size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.layers.layerscope.LayersPacket.Layer.Matrix) + return target; +} + +size_t LayersPacket_Layer_Matrix::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.layers.layerscope.LayersPacket.Layer.Matrix) + size_t total_size = 0; + + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // repeated float m = 3; + { + unsigned int count = static_cast(this->_internal_m_size()); + size_t data_size = 4UL * count; + total_size += 1 * + ::PROTOBUF_NAMESPACE_ID::internal::FromIntSize(this->_internal_m_size()); + total_size += data_size; + } + + cached_has_bits = _has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + // optional bool is2D = 1; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + 1; + } + + // optional bool isId = 2; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + 1; + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields().size(); + } + int cached_size = ::PROTOBUF_NAMESPACE_ID::internal::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void LayersPacket_Layer_Matrix::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::PROTOBUF_NAMESPACE_ID::internal::DownCast( + &from)); +} + +void LayersPacket_Layer_Matrix::MergeFrom(const LayersPacket_Layer_Matrix& from) { +// @@protoc_insertion_point(class_specific_merge_from_start:mozilla.layers.layerscope.LayersPacket.Layer.Matrix) + GOOGLE_DCHECK_NE(&from, this); + _internal_metadata_.MergeFrom(from._internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + m_.MergeFrom(from.m_); + cached_has_bits = from._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + is2d_ = from.is2d_; + } + if (cached_has_bits & 0x00000002u) { + isid_ = from.isid_; + } + _has_bits_[0] |= cached_has_bits; + } +} + +void LayersPacket_Layer_Matrix::CopyFrom(const LayersPacket_Layer_Matrix& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.layers.layerscope.LayersPacket.Layer.Matrix) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool LayersPacket_Layer_Matrix::IsInitialized() const { + return true; +} + +void LayersPacket_Layer_Matrix::InternalSwap(LayersPacket_Layer_Matrix* other) { + using std::swap; + _internal_metadata_.Swap(&other->_internal_metadata_); + swap(_has_bits_[0], other->_has_bits_[0]); + m_.InternalSwap(&other->m_); + swap(is2d_, other->is2d_); + swap(isid_, other->isid_); +} + +std::string LayersPacket_Layer_Matrix::GetTypeName() const { + return "mozilla.layers.layerscope.LayersPacket.Layer.Matrix"; +} + + +// =================================================================== + +void LayersPacket_Layer_Shadow::InitAsDefaultInstance() { + ::mozilla::layers::layerscope::_LayersPacket_Layer_Shadow_default_instance_._instance.get_mutable()->clip_ = const_cast< ::mozilla::layers::layerscope::LayersPacket_Layer_Rect*>( + ::mozilla::layers::layerscope::LayersPacket_Layer_Rect::internal_default_instance()); + ::mozilla::layers::layerscope::_LayersPacket_Layer_Shadow_default_instance_._instance.get_mutable()->transform_ = const_cast< ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix*>( + ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix::internal_default_instance()); + ::mozilla::layers::layerscope::_LayersPacket_Layer_Shadow_default_instance_._instance.get_mutable()->vregion_ = const_cast< ::mozilla::layers::layerscope::LayersPacket_Layer_Region*>( + ::mozilla::layers::layerscope::LayersPacket_Layer_Region::internal_default_instance()); +} +class LayersPacket_Layer_Shadow::_Internal { + public: + using HasBits = decltype(std::declval()._has_bits_); + static const ::mozilla::layers::layerscope::LayersPacket_Layer_Rect& clip(const LayersPacket_Layer_Shadow* msg); + static void set_has_clip(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static const ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix& transform(const LayersPacket_Layer_Shadow* msg); + static void set_has_transform(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& vregion(const LayersPacket_Layer_Shadow* msg); + static void set_has_vregion(HasBits* has_bits) { + (*has_bits)[0] |= 4u; + } +}; + +const ::mozilla::layers::layerscope::LayersPacket_Layer_Rect& +LayersPacket_Layer_Shadow::_Internal::clip(const LayersPacket_Layer_Shadow* msg) { + return *msg->clip_; +} +const ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix& +LayersPacket_Layer_Shadow::_Internal::transform(const LayersPacket_Layer_Shadow* msg) { + return *msg->transform_; +} +const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& +LayersPacket_Layer_Shadow::_Internal::vregion(const LayersPacket_Layer_Shadow* msg) { + return *msg->vregion_; +} +LayersPacket_Layer_Shadow::LayersPacket_Layer_Shadow() + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), _internal_metadata_(nullptr) { + SharedCtor(); + // @@protoc_insertion_point(constructor:mozilla.layers.layerscope.LayersPacket.Layer.Shadow) +} +LayersPacket_Layer_Shadow::LayersPacket_Layer_Shadow(const LayersPacket_Layer_Shadow& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), + _internal_metadata_(nullptr), + _has_bits_(from._has_bits_) { + _internal_metadata_.MergeFrom(from._internal_metadata_); + if (from._internal_has_clip()) { + clip_ = new ::mozilla::layers::layerscope::LayersPacket_Layer_Rect(*from.clip_); + } else { + clip_ = nullptr; + } + if (from._internal_has_transform()) { + transform_ = new ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix(*from.transform_); + } else { + transform_ = nullptr; + } + if (from._internal_has_vregion()) { + vregion_ = new ::mozilla::layers::layerscope::LayersPacket_Layer_Region(*from.vregion_); + } else { + vregion_ = nullptr; + } + // @@protoc_insertion_point(copy_constructor:mozilla.layers.layerscope.LayersPacket.Layer.Shadow) +} + +void LayersPacket_Layer_Shadow::SharedCtor() { + ::PROTOBUF_NAMESPACE_ID::internal::InitSCC(&scc_info_LayersPacket_Layer_Shadow_LayerScopePacket_2eproto.base); + ::memset(&clip_, 0, static_cast( + reinterpret_cast(&vregion_) - + reinterpret_cast(&clip_)) + sizeof(vregion_)); +} + +LayersPacket_Layer_Shadow::~LayersPacket_Layer_Shadow() { + // @@protoc_insertion_point(destructor:mozilla.layers.layerscope.LayersPacket.Layer.Shadow) + SharedDtor(); +} + +void LayersPacket_Layer_Shadow::SharedDtor() { + if (this != internal_default_instance()) delete clip_; + if (this != internal_default_instance()) delete transform_; + if (this != internal_default_instance()) delete vregion_; +} + +void LayersPacket_Layer_Shadow::SetCachedSize(int size) const { + _cached_size_.Set(size); +} +const LayersPacket_Layer_Shadow& LayersPacket_Layer_Shadow::default_instance() { + ::PROTOBUF_NAMESPACE_ID::internal::InitSCC(&::scc_info_LayersPacket_Layer_Shadow_LayerScopePacket_2eproto.base); + return *internal_default_instance(); +} + + +void LayersPacket_Layer_Shadow::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.layers.layerscope.LayersPacket.Layer.Shadow) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + if (cached_has_bits & 0x00000007u) { + if (cached_has_bits & 0x00000001u) { + GOOGLE_DCHECK(clip_ != nullptr); + clip_->Clear(); + } + if (cached_has_bits & 0x00000002u) { + GOOGLE_DCHECK(transform_ != nullptr); + transform_->Clear(); + } + if (cached_has_bits & 0x00000004u) { + GOOGLE_DCHECK(vregion_ != nullptr); + vregion_->Clear(); + } + } + _has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* LayersPacket_Layer_Shadow::_InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + ::PROTOBUF_NAMESPACE_ID::uint32 tag; + ptr = ::PROTOBUF_NAMESPACE_ID::internal::ReadTag(ptr, &tag); + CHK_(ptr); + switch (tag >> 3) { + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Rect clip = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 10)) { + ptr = ctx->ParseMessage(_internal_mutable_clip(), ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Matrix transform = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 18)) { + ptr = ctx->ParseMessage(_internal_mutable_transform(), ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Region vRegion = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_vregion(), ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + default: { + handle_unusual: + if ((tag & 7) == 4 || tag == 0) { + ctx->SetLastTag(tag); + goto success; + } + ptr = UnknownFieldParse(tag, &_internal_metadata_, ptr, ctx); + CHK_(ptr != nullptr); + continue; + } + } // switch + } // while +success: + _has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto success; +#undef CHK_ +} + +::PROTOBUF_NAMESPACE_ID::uint8* LayersPacket_Layer_Shadow::_InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.layers.layerscope.LayersPacket.Layer.Shadow) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Rect clip = 1; + if (cached_has_bits & 0x00000001u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage( + 1, _Internal::clip(this), target, stream); + } + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Matrix transform = 2; + if (cached_has_bits & 0x00000002u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage( + 2, _Internal::transform(this), target, stream); + } + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Region vRegion = 3; + if (cached_has_bits & 0x00000004u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage( + 3, _Internal::vregion(this), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields().data(), + static_cast(_internal_metadata_.unknown_fields().size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.layers.layerscope.LayersPacket.Layer.Shadow) + return target; +} + +size_t LayersPacket_Layer_Shadow::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.layers.layerscope.LayersPacket.Layer.Shadow) + size_t total_size = 0; + + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + if (cached_has_bits & 0x00000007u) { + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Rect clip = 1; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *clip_); + } + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Matrix transform = 2; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *transform_); + } + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Region vRegion = 3; + if (cached_has_bits & 0x00000004u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *vregion_); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields().size(); + } + int cached_size = ::PROTOBUF_NAMESPACE_ID::internal::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void LayersPacket_Layer_Shadow::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::PROTOBUF_NAMESPACE_ID::internal::DownCast( + &from)); +} + +void LayersPacket_Layer_Shadow::MergeFrom(const LayersPacket_Layer_Shadow& from) { +// @@protoc_insertion_point(class_specific_merge_from_start:mozilla.layers.layerscope.LayersPacket.Layer.Shadow) + GOOGLE_DCHECK_NE(&from, this); + _internal_metadata_.MergeFrom(from._internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._has_bits_[0]; + if (cached_has_bits & 0x00000007u) { + if (cached_has_bits & 0x00000001u) { + _internal_mutable_clip()->::mozilla::layers::layerscope::LayersPacket_Layer_Rect::MergeFrom(from._internal_clip()); + } + if (cached_has_bits & 0x00000002u) { + _internal_mutable_transform()->::mozilla::layers::layerscope::LayersPacket_Layer_Matrix::MergeFrom(from._internal_transform()); + } + if (cached_has_bits & 0x00000004u) { + _internal_mutable_vregion()->::mozilla::layers::layerscope::LayersPacket_Layer_Region::MergeFrom(from._internal_vregion()); + } + } +} + +void LayersPacket_Layer_Shadow::CopyFrom(const LayersPacket_Layer_Shadow& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.layers.layerscope.LayersPacket.Layer.Shadow) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool LayersPacket_Layer_Shadow::IsInitialized() const { + return true; +} + +void LayersPacket_Layer_Shadow::InternalSwap(LayersPacket_Layer_Shadow* other) { + using std::swap; + _internal_metadata_.Swap(&other->_internal_metadata_); + swap(_has_bits_[0], other->_has_bits_[0]); + swap(clip_, other->clip_); + swap(transform_, other->transform_); + swap(vregion_, other->vregion_); +} + +std::string LayersPacket_Layer_Shadow::GetTypeName() const { + return "mozilla.layers.layerscope.LayersPacket.Layer.Shadow"; +} + + +// =================================================================== + +void LayersPacket_Layer::InitAsDefaultInstance() { + ::mozilla::layers::layerscope::_LayersPacket_Layer_default_instance_._instance.get_mutable()->clip_ = const_cast< ::mozilla::layers::layerscope::LayersPacket_Layer_Rect*>( + ::mozilla::layers::layerscope::LayersPacket_Layer_Rect::internal_default_instance()); + ::mozilla::layers::layerscope::_LayersPacket_Layer_default_instance_._instance.get_mutable()->transform_ = const_cast< ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix*>( + ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix::internal_default_instance()); + ::mozilla::layers::layerscope::_LayersPacket_Layer_default_instance_._instance.get_mutable()->vregion_ = const_cast< ::mozilla::layers::layerscope::LayersPacket_Layer_Region*>( + ::mozilla::layers::layerscope::LayersPacket_Layer_Region::internal_default_instance()); + ::mozilla::layers::layerscope::_LayersPacket_Layer_default_instance_._instance.get_mutable()->shadow_ = const_cast< ::mozilla::layers::layerscope::LayersPacket_Layer_Shadow*>( + ::mozilla::layers::layerscope::LayersPacket_Layer_Shadow::internal_default_instance()); + ::mozilla::layers::layerscope::_LayersPacket_Layer_default_instance_._instance.get_mutable()->hitregion_ = const_cast< ::mozilla::layers::layerscope::LayersPacket_Layer_Region*>( + ::mozilla::layers::layerscope::LayersPacket_Layer_Region::internal_default_instance()); + ::mozilla::layers::layerscope::_LayersPacket_Layer_default_instance_._instance.get_mutable()->dispatchregion_ = const_cast< ::mozilla::layers::layerscope::LayersPacket_Layer_Region*>( + ::mozilla::layers::layerscope::LayersPacket_Layer_Region::internal_default_instance()); + ::mozilla::layers::layerscope::_LayersPacket_Layer_default_instance_._instance.get_mutable()->noactionregion_ = const_cast< ::mozilla::layers::layerscope::LayersPacket_Layer_Region*>( + ::mozilla::layers::layerscope::LayersPacket_Layer_Region::internal_default_instance()); + ::mozilla::layers::layerscope::_LayersPacket_Layer_default_instance_._instance.get_mutable()->hpanregion_ = const_cast< ::mozilla::layers::layerscope::LayersPacket_Layer_Region*>( + ::mozilla::layers::layerscope::LayersPacket_Layer_Region::internal_default_instance()); + ::mozilla::layers::layerscope::_LayersPacket_Layer_default_instance_._instance.get_mutable()->vpanregion_ = const_cast< ::mozilla::layers::layerscope::LayersPacket_Layer_Region*>( + ::mozilla::layers::layerscope::LayersPacket_Layer_Region::internal_default_instance()); + ::mozilla::layers::layerscope::_LayersPacket_Layer_default_instance_._instance.get_mutable()->valid_ = const_cast< ::mozilla::layers::layerscope::LayersPacket_Layer_Region*>( + ::mozilla::layers::layerscope::LayersPacket_Layer_Region::internal_default_instance()); + ::mozilla::layers::layerscope::_LayersPacket_Layer_default_instance_._instance.get_mutable()->size_ = const_cast< ::mozilla::layers::layerscope::LayersPacket_Layer_Size*>( + ::mozilla::layers::layerscope::LayersPacket_Layer_Size::internal_default_instance()); +} +class LayersPacket_Layer::_Internal { + public: + using HasBits = decltype(std::declval()._has_bits_); + static void set_has_type(HasBits* has_bits) { + (*has_bits)[0] |= 16384u; + } + static void set_has_ptr(HasBits* has_bits) { + (*has_bits)[0] |= 4096u; + } + static void set_has_parentptr(HasBits* has_bits) { + (*has_bits)[0] |= 8192u; + } + static const ::mozilla::layers::layerscope::LayersPacket_Layer_Rect& clip(const LayersPacket_Layer* msg); + static void set_has_clip(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static const ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix& transform(const LayersPacket_Layer* msg); + static void set_has_transform(HasBits* has_bits) { + (*has_bits)[0] |= 4u; + } + static const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& vregion(const LayersPacket_Layer* msg); + static void set_has_vregion(HasBits* has_bits) { + (*has_bits)[0] |= 8u; + } + static const ::mozilla::layers::layerscope::LayersPacket_Layer_Shadow& shadow(const LayersPacket_Layer* msg); + static void set_has_shadow(HasBits* has_bits) { + (*has_bits)[0] |= 16u; + } + static void set_has_opacity(HasBits* has_bits) { + (*has_bits)[0] |= 32768u; + } + static void set_has_copaque(HasBits* has_bits) { + (*has_bits)[0] |= 262144u; + } + static void set_has_calpha(HasBits* has_bits) { + (*has_bits)[0] |= 524288u; + } + static void set_has_direct(HasBits* has_bits) { + (*has_bits)[0] |= 16777216u; + } + static void set_has_barid(HasBits* has_bits) { + (*has_bits)[0] |= 65536u; + } + static void set_has_mask(HasBits* has_bits) { + (*has_bits)[0] |= 131072u; + } + static const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& hitregion(const LayersPacket_Layer* msg); + static void set_has_hitregion(HasBits* has_bits) { + (*has_bits)[0] |= 32u; + } + static const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& dispatchregion(const LayersPacket_Layer* msg); + static void set_has_dispatchregion(HasBits* has_bits) { + (*has_bits)[0] |= 64u; + } + static const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& noactionregion(const LayersPacket_Layer* msg); + static void set_has_noactionregion(HasBits* has_bits) { + (*has_bits)[0] |= 128u; + } + static const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& hpanregion(const LayersPacket_Layer* msg); + static void set_has_hpanregion(HasBits* has_bits) { + (*has_bits)[0] |= 256u; + } + static const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& vpanregion(const LayersPacket_Layer* msg); + static void set_has_vpanregion(HasBits* has_bits) { + (*has_bits)[0] |= 512u; + } + static const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& valid(const LayersPacket_Layer* msg); + static void set_has_valid(HasBits* has_bits) { + (*has_bits)[0] |= 1024u; + } + static void set_has_color(HasBits* has_bits) { + (*has_bits)[0] |= 1048576u; + } + static void set_has_filter(HasBits* has_bits) { + (*has_bits)[0] |= 4194304u; + } + static void set_has_refid(HasBits* has_bits) { + (*has_bits)[0] |= 2097152u; + } + static const ::mozilla::layers::layerscope::LayersPacket_Layer_Size& size(const LayersPacket_Layer* msg); + static void set_has_size(HasBits* has_bits) { + (*has_bits)[0] |= 2048u; + } + static void set_has_displaylistloglength(HasBits* has_bits) { + (*has_bits)[0] |= 8388608u; + } + static void set_has_displaylistlog(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } +}; + +const ::mozilla::layers::layerscope::LayersPacket_Layer_Rect& +LayersPacket_Layer::_Internal::clip(const LayersPacket_Layer* msg) { + return *msg->clip_; +} +const ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix& +LayersPacket_Layer::_Internal::transform(const LayersPacket_Layer* msg) { + return *msg->transform_; +} +const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& +LayersPacket_Layer::_Internal::vregion(const LayersPacket_Layer* msg) { + return *msg->vregion_; +} +const ::mozilla::layers::layerscope::LayersPacket_Layer_Shadow& +LayersPacket_Layer::_Internal::shadow(const LayersPacket_Layer* msg) { + return *msg->shadow_; +} +const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& +LayersPacket_Layer::_Internal::hitregion(const LayersPacket_Layer* msg) { + return *msg->hitregion_; +} +const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& +LayersPacket_Layer::_Internal::dispatchregion(const LayersPacket_Layer* msg) { + return *msg->dispatchregion_; +} +const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& +LayersPacket_Layer::_Internal::noactionregion(const LayersPacket_Layer* msg) { + return *msg->noactionregion_; +} +const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& +LayersPacket_Layer::_Internal::hpanregion(const LayersPacket_Layer* msg) { + return *msg->hpanregion_; +} +const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& +LayersPacket_Layer::_Internal::vpanregion(const LayersPacket_Layer* msg) { + return *msg->vpanregion_; +} +const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& +LayersPacket_Layer::_Internal::valid(const LayersPacket_Layer* msg) { + return *msg->valid_; +} +const ::mozilla::layers::layerscope::LayersPacket_Layer_Size& +LayersPacket_Layer::_Internal::size(const LayersPacket_Layer* msg) { + return *msg->size_; +} +LayersPacket_Layer::LayersPacket_Layer() + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), _internal_metadata_(nullptr) { + SharedCtor(); + // @@protoc_insertion_point(constructor:mozilla.layers.layerscope.LayersPacket.Layer) +} +LayersPacket_Layer::LayersPacket_Layer(const LayersPacket_Layer& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), + _internal_metadata_(nullptr), + _has_bits_(from._has_bits_) { + _internal_metadata_.MergeFrom(from._internal_metadata_); + displaylistlog_.UnsafeSetDefault(&::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited()); + if (from._internal_has_displaylistlog()) { + displaylistlog_.AssignWithDefault(&::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited(), from.displaylistlog_); + } + if (from._internal_has_clip()) { + clip_ = new ::mozilla::layers::layerscope::LayersPacket_Layer_Rect(*from.clip_); + } else { + clip_ = nullptr; + } + if (from._internal_has_transform()) { + transform_ = new ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix(*from.transform_); + } else { + transform_ = nullptr; + } + if (from._internal_has_vregion()) { + vregion_ = new ::mozilla::layers::layerscope::LayersPacket_Layer_Region(*from.vregion_); + } else { + vregion_ = nullptr; + } + if (from._internal_has_shadow()) { + shadow_ = new ::mozilla::layers::layerscope::LayersPacket_Layer_Shadow(*from.shadow_); + } else { + shadow_ = nullptr; + } + if (from._internal_has_hitregion()) { + hitregion_ = new ::mozilla::layers::layerscope::LayersPacket_Layer_Region(*from.hitregion_); + } else { + hitregion_ = nullptr; + } + if (from._internal_has_dispatchregion()) { + dispatchregion_ = new ::mozilla::layers::layerscope::LayersPacket_Layer_Region(*from.dispatchregion_); + } else { + dispatchregion_ = nullptr; + } + if (from._internal_has_noactionregion()) { + noactionregion_ = new ::mozilla::layers::layerscope::LayersPacket_Layer_Region(*from.noactionregion_); + } else { + noactionregion_ = nullptr; + } + if (from._internal_has_hpanregion()) { + hpanregion_ = new ::mozilla::layers::layerscope::LayersPacket_Layer_Region(*from.hpanregion_); + } else { + hpanregion_ = nullptr; + } + if (from._internal_has_vpanregion()) { + vpanregion_ = new ::mozilla::layers::layerscope::LayersPacket_Layer_Region(*from.vpanregion_); + } else { + vpanregion_ = nullptr; + } + if (from._internal_has_valid()) { + valid_ = new ::mozilla::layers::layerscope::LayersPacket_Layer_Region(*from.valid_); + } else { + valid_ = nullptr; + } + if (from._internal_has_size()) { + size_ = new ::mozilla::layers::layerscope::LayersPacket_Layer_Size(*from.size_); + } else { + size_ = nullptr; + } + ::memcpy(&ptr_, &from.ptr_, + static_cast(reinterpret_cast(&direct_) - + reinterpret_cast(&ptr_)) + sizeof(direct_)); + // @@protoc_insertion_point(copy_constructor:mozilla.layers.layerscope.LayersPacket.Layer) +} + +void LayersPacket_Layer::SharedCtor() { + ::PROTOBUF_NAMESPACE_ID::internal::InitSCC(&scc_info_LayersPacket_Layer_LayerScopePacket_2eproto.base); + displaylistlog_.UnsafeSetDefault(&::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited()); + ::memset(&clip_, 0, static_cast( + reinterpret_cast(&displaylistloglength_) - + reinterpret_cast(&clip_)) + sizeof(displaylistloglength_)); + direct_ = 1; +} + +LayersPacket_Layer::~LayersPacket_Layer() { + // @@protoc_insertion_point(destructor:mozilla.layers.layerscope.LayersPacket.Layer) + SharedDtor(); +} + +void LayersPacket_Layer::SharedDtor() { + displaylistlog_.DestroyNoArena(&::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited()); + if (this != internal_default_instance()) delete clip_; + if (this != internal_default_instance()) delete transform_; + if (this != internal_default_instance()) delete vregion_; + if (this != internal_default_instance()) delete shadow_; + if (this != internal_default_instance()) delete hitregion_; + if (this != internal_default_instance()) delete dispatchregion_; + if (this != internal_default_instance()) delete noactionregion_; + if (this != internal_default_instance()) delete hpanregion_; + if (this != internal_default_instance()) delete vpanregion_; + if (this != internal_default_instance()) delete valid_; + if (this != internal_default_instance()) delete size_; +} + +void LayersPacket_Layer::SetCachedSize(int size) const { + _cached_size_.Set(size); +} +const LayersPacket_Layer& LayersPacket_Layer::default_instance() { + ::PROTOBUF_NAMESPACE_ID::internal::InitSCC(&::scc_info_LayersPacket_Layer_LayerScopePacket_2eproto.base); + return *internal_default_instance(); +} + + +void LayersPacket_Layer::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.layers.layerscope.LayersPacket.Layer) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + if (cached_has_bits & 0x000000ffu) { + if (cached_has_bits & 0x00000001u) { + displaylistlog_.ClearNonDefaultToEmptyNoArena(); + } + if (cached_has_bits & 0x00000002u) { + GOOGLE_DCHECK(clip_ != nullptr); + clip_->Clear(); + } + if (cached_has_bits & 0x00000004u) { + GOOGLE_DCHECK(transform_ != nullptr); + transform_->Clear(); + } + if (cached_has_bits & 0x00000008u) { + GOOGLE_DCHECK(vregion_ != nullptr); + vregion_->Clear(); + } + if (cached_has_bits & 0x00000010u) { + GOOGLE_DCHECK(shadow_ != nullptr); + shadow_->Clear(); + } + if (cached_has_bits & 0x00000020u) { + GOOGLE_DCHECK(hitregion_ != nullptr); + hitregion_->Clear(); + } + if (cached_has_bits & 0x00000040u) { + GOOGLE_DCHECK(dispatchregion_ != nullptr); + dispatchregion_->Clear(); + } + if (cached_has_bits & 0x00000080u) { + GOOGLE_DCHECK(noactionregion_ != nullptr); + noactionregion_->Clear(); + } + } + if (cached_has_bits & 0x00000f00u) { + if (cached_has_bits & 0x00000100u) { + GOOGLE_DCHECK(hpanregion_ != nullptr); + hpanregion_->Clear(); + } + if (cached_has_bits & 0x00000200u) { + GOOGLE_DCHECK(vpanregion_ != nullptr); + vpanregion_->Clear(); + } + if (cached_has_bits & 0x00000400u) { + GOOGLE_DCHECK(valid_ != nullptr); + valid_->Clear(); + } + if (cached_has_bits & 0x00000800u) { + GOOGLE_DCHECK(size_ != nullptr); + size_->Clear(); + } + } + if (cached_has_bits & 0x0000f000u) { + ::memset(&ptr_, 0, static_cast( + reinterpret_cast(&opacity_) - + reinterpret_cast(&ptr_)) + sizeof(opacity_)); + } + if (cached_has_bits & 0x00ff0000u) { + ::memset(&barid_, 0, static_cast( + reinterpret_cast(&displaylistloglength_) - + reinterpret_cast(&barid_)) + sizeof(displaylistloglength_)); + } + direct_ = 1; + _has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* LayersPacket_Layer::_InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + ::PROTOBUF_NAMESPACE_ID::uint32 tag; + ptr = ::PROTOBUF_NAMESPACE_ID::internal::ReadTag(ptr, &tag); + CHK_(ptr); + switch (tag >> 3) { + // required .mozilla.layers.layerscope.LayersPacket.Layer.LayerType type = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 8)) { + ::PROTOBUF_NAMESPACE_ID::uint64 val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::mozilla::layers::layerscope::LayersPacket_Layer_LayerType_IsValid(val))) { + _internal_set_type(static_cast<::mozilla::layers::layerscope::LayersPacket_Layer_LayerType>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(1, val, mutable_unknown_fields()); + } + } else goto handle_unusual; + continue; + // required uint64 ptr = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 16)) { + _Internal::set_has_ptr(&has_bits); + ptr_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // required uint64 parentPtr = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 24)) { + _Internal::set_has_parentptr(&has_bits); + parentptr_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Rect clip = 10; + case 10: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 82)) { + ptr = ctx->ParseMessage(_internal_mutable_clip(), ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Matrix transform = 11; + case 11: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 90)) { + ptr = ctx->ParseMessage(_internal_mutable_transform(), ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Region vRegion = 12; + case 12: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 98)) { + ptr = ctx->ParseMessage(_internal_mutable_vregion(), ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Shadow shadow = 13; + case 13: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 106)) { + ptr = ctx->ParseMessage(_internal_mutable_shadow(), ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional float opacity = 14; + case 14: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 117)) { + _Internal::set_has_opacity(&has_bits); + opacity_ = ::PROTOBUF_NAMESPACE_ID::internal::UnalignedLoad(ptr); + ptr += sizeof(float); + } else goto handle_unusual; + continue; + // optional bool cOpaque = 15; + case 15: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 120)) { + _Internal::set_has_copaque(&has_bits); + copaque_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional bool cAlpha = 16; + case 16: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 128)) { + _Internal::set_has_calpha(&has_bits); + calpha_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional .mozilla.layers.layerscope.LayersPacket.Layer.ScrollingDirect direct = 17; + case 17: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 136)) { + ::PROTOBUF_NAMESPACE_ID::uint64 val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::mozilla::layers::layerscope::LayersPacket_Layer_ScrollingDirect_IsValid(val))) { + _internal_set_direct(static_cast<::mozilla::layers::layerscope::LayersPacket_Layer_ScrollingDirect>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(17, val, mutable_unknown_fields()); + } + } else goto handle_unusual; + continue; + // optional uint64 barID = 18; + case 18: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 144)) { + _Internal::set_has_barid(&has_bits); + barid_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional uint64 mask = 19; + case 19: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 152)) { + _Internal::set_has_mask(&has_bits); + mask_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Region hitRegion = 20; + case 20: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 162)) { + ptr = ctx->ParseMessage(_internal_mutable_hitregion(), ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Region dispatchRegion = 21; + case 21: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 170)) { + ptr = ctx->ParseMessage(_internal_mutable_dispatchregion(), ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Region noActionRegion = 22; + case 22: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 178)) { + ptr = ctx->ParseMessage(_internal_mutable_noactionregion(), ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Region hPanRegion = 23; + case 23: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 186)) { + ptr = ctx->ParseMessage(_internal_mutable_hpanregion(), ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Region vPanRegion = 24; + case 24: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 194)) { + ptr = ctx->ParseMessage(_internal_mutable_vpanregion(), ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Region valid = 100; + case 100: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 34)) { + ptr = ctx->ParseMessage(_internal_mutable_valid(), ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional uint32 color = 101; + case 101: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 40)) { + _Internal::set_has_color(&has_bits); + color_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Filter filter = 102; + case 102: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 48)) { + ::PROTOBUF_NAMESPACE_ID::uint64 val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::mozilla::layers::layerscope::LayersPacket_Layer_Filter_IsValid(val))) { + _internal_set_filter(static_cast<::mozilla::layers::layerscope::LayersPacket_Layer_Filter>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(102, val, mutable_unknown_fields()); + } + } else goto handle_unusual; + continue; + // optional uint64 refID = 103; + case 103: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 56)) { + _Internal::set_has_refid(&has_bits); + refid_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Size size = 104; + case 104: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 66)) { + ptr = ctx->ParseMessage(_internal_mutable_size(), ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional uint32 displayListLogLength = 105; + case 105: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 72)) { + _Internal::set_has_displaylistloglength(&has_bits); + displaylistloglength_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional bytes displayListLog = 106; + case 106: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 82)) { + auto str = _internal_mutable_displaylistlog(); + ptr = ::PROTOBUF_NAMESPACE_ID::internal::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + } else goto handle_unusual; + continue; + default: { + handle_unusual: + if ((tag & 7) == 4 || tag == 0) { + ctx->SetLastTag(tag); + goto success; + } + ptr = UnknownFieldParse(tag, &_internal_metadata_, ptr, ctx); + CHK_(ptr != nullptr); + continue; + } + } // switch + } // while +success: + _has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto success; +#undef CHK_ +} + +::PROTOBUF_NAMESPACE_ID::uint8* LayersPacket_Layer::_InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.layers.layerscope.LayersPacket.Layer) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + // required .mozilla.layers.layerscope.LayersPacket.Layer.LayerType type = 1; + if (cached_has_bits & 0x00004000u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteEnumToArray( + 1, this->_internal_type(), target); + } + + // required uint64 ptr = 2; + if (cached_has_bits & 0x00001000u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteUInt64ToArray(2, this->_internal_ptr(), target); + } + + // required uint64 parentPtr = 3; + if (cached_has_bits & 0x00002000u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteUInt64ToArray(3, this->_internal_parentptr(), target); + } + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Rect clip = 10; + if (cached_has_bits & 0x00000002u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage( + 10, _Internal::clip(this), target, stream); + } + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Matrix transform = 11; + if (cached_has_bits & 0x00000004u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage( + 11, _Internal::transform(this), target, stream); + } + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Region vRegion = 12; + if (cached_has_bits & 0x00000008u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage( + 12, _Internal::vregion(this), target, stream); + } + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Shadow shadow = 13; + if (cached_has_bits & 0x00000010u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage( + 13, _Internal::shadow(this), target, stream); + } + + // optional float opacity = 14; + if (cached_has_bits & 0x00008000u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteFloatToArray(14, this->_internal_opacity(), target); + } + + // optional bool cOpaque = 15; + if (cached_has_bits & 0x00040000u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteBoolToArray(15, this->_internal_copaque(), target); + } + + // optional bool cAlpha = 16; + if (cached_has_bits & 0x00080000u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteBoolToArray(16, this->_internal_calpha(), target); + } + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.ScrollingDirect direct = 17; + if (cached_has_bits & 0x01000000u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteEnumToArray( + 17, this->_internal_direct(), target); + } + + // optional uint64 barID = 18; + if (cached_has_bits & 0x00010000u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteUInt64ToArray(18, this->_internal_barid(), target); + } + + // optional uint64 mask = 19; + if (cached_has_bits & 0x00020000u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteUInt64ToArray(19, this->_internal_mask(), target); + } + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Region hitRegion = 20; + if (cached_has_bits & 0x00000020u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage( + 20, _Internal::hitregion(this), target, stream); + } + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Region dispatchRegion = 21; + if (cached_has_bits & 0x00000040u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage( + 21, _Internal::dispatchregion(this), target, stream); + } + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Region noActionRegion = 22; + if (cached_has_bits & 0x00000080u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage( + 22, _Internal::noactionregion(this), target, stream); + } + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Region hPanRegion = 23; + if (cached_has_bits & 0x00000100u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage( + 23, _Internal::hpanregion(this), target, stream); + } + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Region vPanRegion = 24; + if (cached_has_bits & 0x00000200u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage( + 24, _Internal::vpanregion(this), target, stream); + } + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Region valid = 100; + if (cached_has_bits & 0x00000400u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage( + 100, _Internal::valid(this), target, stream); + } + + // optional uint32 color = 101; + if (cached_has_bits & 0x00100000u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteUInt32ToArray(101, this->_internal_color(), target); + } + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Filter filter = 102; + if (cached_has_bits & 0x00400000u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteEnumToArray( + 102, this->_internal_filter(), target); + } + + // optional uint64 refID = 103; + if (cached_has_bits & 0x00200000u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteUInt64ToArray(103, this->_internal_refid(), target); + } + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Size size = 104; + if (cached_has_bits & 0x00000800u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage( + 104, _Internal::size(this), target, stream); + } + + // optional uint32 displayListLogLength = 105; + if (cached_has_bits & 0x00800000u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteUInt32ToArray(105, this->_internal_displaylistloglength(), target); + } + + // optional bytes displayListLog = 106; + if (cached_has_bits & 0x00000001u) { + target = stream->WriteBytesMaybeAliased( + 106, this->_internal_displaylistlog(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields().data(), + static_cast(_internal_metadata_.unknown_fields().size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.layers.layerscope.LayersPacket.Layer) + return target; +} + +size_t LayersPacket_Layer::RequiredFieldsByteSizeFallback() const { +// @@protoc_insertion_point(required_fields_byte_size_fallback_start:mozilla.layers.layerscope.LayersPacket.Layer) + size_t total_size = 0; + + if (_internal_has_ptr()) { + // required uint64 ptr = 2; + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::UInt64Size( + this->_internal_ptr()); + } + + if (_internal_has_parentptr()) { + // required uint64 parentPtr = 3; + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::UInt64Size( + this->_internal_parentptr()); + } + + if (_internal_has_type()) { + // required .mozilla.layers.layerscope.LayersPacket.Layer.LayerType type = 1; + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::EnumSize(this->_internal_type()); + } + + return total_size; +} +size_t LayersPacket_Layer::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.layers.layerscope.LayersPacket.Layer) + size_t total_size = 0; + + if (((_has_bits_[0] & 0x00007000) ^ 0x00007000) == 0) { // All required fields are present. + // required uint64 ptr = 2; + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::UInt64Size( + this->_internal_ptr()); + + // required uint64 parentPtr = 3; + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::UInt64Size( + this->_internal_parentptr()); + + // required .mozilla.layers.layerscope.LayersPacket.Layer.LayerType type = 1; + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::EnumSize(this->_internal_type()); + + } else { + total_size += RequiredFieldsByteSizeFallback(); + } + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + if (cached_has_bits & 0x000000ffu) { + // optional bytes displayListLog = 106; + if (cached_has_bits & 0x00000001u) { + total_size += 2 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize( + this->_internal_displaylistlog()); + } + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Rect clip = 10; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *clip_); + } + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Matrix transform = 11; + if (cached_has_bits & 0x00000004u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *transform_); + } + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Region vRegion = 12; + if (cached_has_bits & 0x00000008u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *vregion_); + } + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Shadow shadow = 13; + if (cached_has_bits & 0x00000010u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *shadow_); + } + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Region hitRegion = 20; + if (cached_has_bits & 0x00000020u) { + total_size += 2 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *hitregion_); + } + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Region dispatchRegion = 21; + if (cached_has_bits & 0x00000040u) { + total_size += 2 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *dispatchregion_); + } + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Region noActionRegion = 22; + if (cached_has_bits & 0x00000080u) { + total_size += 2 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *noactionregion_); + } + + } + if (cached_has_bits & 0x00000f00u) { + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Region hPanRegion = 23; + if (cached_has_bits & 0x00000100u) { + total_size += 2 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *hpanregion_); + } + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Region vPanRegion = 24; + if (cached_has_bits & 0x00000200u) { + total_size += 2 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *vpanregion_); + } + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Region valid = 100; + if (cached_has_bits & 0x00000400u) { + total_size += 2 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *valid_); + } + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Size size = 104; + if (cached_has_bits & 0x00000800u) { + total_size += 2 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *size_); + } + + } + // optional float opacity = 14; + if (cached_has_bits & 0x00008000u) { + total_size += 1 + 4; + } + + if (cached_has_bits & 0x00ff0000u) { + // optional uint64 barID = 18; + if (cached_has_bits & 0x00010000u) { + total_size += 2 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::UInt64Size( + this->_internal_barid()); + } + + // optional uint64 mask = 19; + if (cached_has_bits & 0x00020000u) { + total_size += 2 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::UInt64Size( + this->_internal_mask()); + } + + // optional bool cOpaque = 15; + if (cached_has_bits & 0x00040000u) { + total_size += 1 + 1; + } + + // optional bool cAlpha = 16; + if (cached_has_bits & 0x00080000u) { + total_size += 2 + 1; + } + + // optional uint32 color = 101; + if (cached_has_bits & 0x00100000u) { + total_size += 2 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::UInt32Size( + this->_internal_color()); + } + + // optional uint64 refID = 103; + if (cached_has_bits & 0x00200000u) { + total_size += 2 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::UInt64Size( + this->_internal_refid()); + } + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Filter filter = 102; + if (cached_has_bits & 0x00400000u) { + total_size += 2 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::EnumSize(this->_internal_filter()); + } + + // optional uint32 displayListLogLength = 105; + if (cached_has_bits & 0x00800000u) { + total_size += 2 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::UInt32Size( + this->_internal_displaylistloglength()); + } + + } + // optional .mozilla.layers.layerscope.LayersPacket.Layer.ScrollingDirect direct = 17; + if (cached_has_bits & 0x01000000u) { + total_size += 2 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::EnumSize(this->_internal_direct()); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields().size(); + } + int cached_size = ::PROTOBUF_NAMESPACE_ID::internal::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void LayersPacket_Layer::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::PROTOBUF_NAMESPACE_ID::internal::DownCast( + &from)); +} + +void LayersPacket_Layer::MergeFrom(const LayersPacket_Layer& from) { +// @@protoc_insertion_point(class_specific_merge_from_start:mozilla.layers.layerscope.LayersPacket.Layer) + GOOGLE_DCHECK_NE(&from, this); + _internal_metadata_.MergeFrom(from._internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._has_bits_[0]; + if (cached_has_bits & 0x000000ffu) { + if (cached_has_bits & 0x00000001u) { + _has_bits_[0] |= 0x00000001u; + displaylistlog_.AssignWithDefault(&::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited(), from.displaylistlog_); + } + if (cached_has_bits & 0x00000002u) { + _internal_mutable_clip()->::mozilla::layers::layerscope::LayersPacket_Layer_Rect::MergeFrom(from._internal_clip()); + } + if (cached_has_bits & 0x00000004u) { + _internal_mutable_transform()->::mozilla::layers::layerscope::LayersPacket_Layer_Matrix::MergeFrom(from._internal_transform()); + } + if (cached_has_bits & 0x00000008u) { + _internal_mutable_vregion()->::mozilla::layers::layerscope::LayersPacket_Layer_Region::MergeFrom(from._internal_vregion()); + } + if (cached_has_bits & 0x00000010u) { + _internal_mutable_shadow()->::mozilla::layers::layerscope::LayersPacket_Layer_Shadow::MergeFrom(from._internal_shadow()); + } + if (cached_has_bits & 0x00000020u) { + _internal_mutable_hitregion()->::mozilla::layers::layerscope::LayersPacket_Layer_Region::MergeFrom(from._internal_hitregion()); + } + if (cached_has_bits & 0x00000040u) { + _internal_mutable_dispatchregion()->::mozilla::layers::layerscope::LayersPacket_Layer_Region::MergeFrom(from._internal_dispatchregion()); + } + if (cached_has_bits & 0x00000080u) { + _internal_mutable_noactionregion()->::mozilla::layers::layerscope::LayersPacket_Layer_Region::MergeFrom(from._internal_noactionregion()); + } + } + if (cached_has_bits & 0x0000ff00u) { + if (cached_has_bits & 0x00000100u) { + _internal_mutable_hpanregion()->::mozilla::layers::layerscope::LayersPacket_Layer_Region::MergeFrom(from._internal_hpanregion()); + } + if (cached_has_bits & 0x00000200u) { + _internal_mutable_vpanregion()->::mozilla::layers::layerscope::LayersPacket_Layer_Region::MergeFrom(from._internal_vpanregion()); + } + if (cached_has_bits & 0x00000400u) { + _internal_mutable_valid()->::mozilla::layers::layerscope::LayersPacket_Layer_Region::MergeFrom(from._internal_valid()); + } + if (cached_has_bits & 0x00000800u) { + _internal_mutable_size()->::mozilla::layers::layerscope::LayersPacket_Layer_Size::MergeFrom(from._internal_size()); + } + if (cached_has_bits & 0x00001000u) { + ptr_ = from.ptr_; + } + if (cached_has_bits & 0x00002000u) { + parentptr_ = from.parentptr_; + } + if (cached_has_bits & 0x00004000u) { + type_ = from.type_; + } + if (cached_has_bits & 0x00008000u) { + opacity_ = from.opacity_; + } + _has_bits_[0] |= cached_has_bits; + } + if (cached_has_bits & 0x00ff0000u) { + if (cached_has_bits & 0x00010000u) { + barid_ = from.barid_; + } + if (cached_has_bits & 0x00020000u) { + mask_ = from.mask_; + } + if (cached_has_bits & 0x00040000u) { + copaque_ = from.copaque_; + } + if (cached_has_bits & 0x00080000u) { + calpha_ = from.calpha_; + } + if (cached_has_bits & 0x00100000u) { + color_ = from.color_; + } + if (cached_has_bits & 0x00200000u) { + refid_ = from.refid_; + } + if (cached_has_bits & 0x00400000u) { + filter_ = from.filter_; + } + if (cached_has_bits & 0x00800000u) { + displaylistloglength_ = from.displaylistloglength_; + } + _has_bits_[0] |= cached_has_bits; + } + if (cached_has_bits & 0x01000000u) { + _internal_set_direct(from._internal_direct()); + } +} + +void LayersPacket_Layer::CopyFrom(const LayersPacket_Layer& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.layers.layerscope.LayersPacket.Layer) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool LayersPacket_Layer::IsInitialized() const { + if ((_has_bits_[0] & 0x00007000) != 0x00007000) return false; + return true; +} + +void LayersPacket_Layer::InternalSwap(LayersPacket_Layer* other) { + using std::swap; + _internal_metadata_.Swap(&other->_internal_metadata_); + swap(_has_bits_[0], other->_has_bits_[0]); + displaylistlog_.Swap(&other->displaylistlog_, &::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited(), + GetArenaNoVirtual()); + swap(clip_, other->clip_); + swap(transform_, other->transform_); + swap(vregion_, other->vregion_); + swap(shadow_, other->shadow_); + swap(hitregion_, other->hitregion_); + swap(dispatchregion_, other->dispatchregion_); + swap(noactionregion_, other->noactionregion_); + swap(hpanregion_, other->hpanregion_); + swap(vpanregion_, other->vpanregion_); + swap(valid_, other->valid_); + swap(size_, other->size_); + swap(ptr_, other->ptr_); + swap(parentptr_, other->parentptr_); + swap(type_, other->type_); + swap(opacity_, other->opacity_); + swap(barid_, other->barid_); + swap(mask_, other->mask_); + swap(copaque_, other->copaque_); + swap(calpha_, other->calpha_); + swap(color_, other->color_); + swap(refid_, other->refid_); + swap(filter_, other->filter_); + swap(displaylistloglength_, other->displaylistloglength_); + swap(direct_, other->direct_); +} + +std::string LayersPacket_Layer::GetTypeName() const { + return "mozilla.layers.layerscope.LayersPacket.Layer"; +} + + +// =================================================================== + +void LayersPacket::InitAsDefaultInstance() { +} +class LayersPacket::_Internal { + public: + using HasBits = decltype(std::declval()._has_bits_); +}; + +LayersPacket::LayersPacket() + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), _internal_metadata_(nullptr) { + SharedCtor(); + // @@protoc_insertion_point(constructor:mozilla.layers.layerscope.LayersPacket) +} +LayersPacket::LayersPacket(const LayersPacket& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), + _internal_metadata_(nullptr), + _has_bits_(from._has_bits_), + layer_(from.layer_) { + _internal_metadata_.MergeFrom(from._internal_metadata_); + // @@protoc_insertion_point(copy_constructor:mozilla.layers.layerscope.LayersPacket) +} + +void LayersPacket::SharedCtor() { + ::PROTOBUF_NAMESPACE_ID::internal::InitSCC(&scc_info_LayersPacket_LayerScopePacket_2eproto.base); +} + +LayersPacket::~LayersPacket() { + // @@protoc_insertion_point(destructor:mozilla.layers.layerscope.LayersPacket) + SharedDtor(); +} + +void LayersPacket::SharedDtor() { +} + +void LayersPacket::SetCachedSize(int size) const { + _cached_size_.Set(size); +} +const LayersPacket& LayersPacket::default_instance() { + ::PROTOBUF_NAMESPACE_ID::internal::InitSCC(&::scc_info_LayersPacket_LayerScopePacket_2eproto.base); + return *internal_default_instance(); +} + + +void LayersPacket::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.layers.layerscope.LayersPacket) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + layer_.Clear(); + _has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* LayersPacket::_InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + ::PROTOBUF_NAMESPACE_ID::uint32 tag; + ptr = ::PROTOBUF_NAMESPACE_ID::internal::ReadTag(ptr, &tag); + CHK_(ptr); + switch (tag >> 3) { + // repeated .mozilla.layers.layerscope.LayersPacket.Layer layer = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 10)) { + ptr -= 1; + do { + ptr += 1; + ptr = ctx->ParseMessage(_internal_add_layer(), ptr); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<10>(ptr)); + } else goto handle_unusual; + continue; + default: { + handle_unusual: + if ((tag & 7) == 4 || tag == 0) { + ctx->SetLastTag(tag); + goto success; + } + ptr = UnknownFieldParse(tag, &_internal_metadata_, ptr, ctx); + CHK_(ptr != nullptr); + continue; + } + } // switch + } // while +success: + return ptr; +failure: + ptr = nullptr; + goto success; +#undef CHK_ +} + +::PROTOBUF_NAMESPACE_ID::uint8* LayersPacket::_InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.layers.layerscope.LayersPacket) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + // repeated .mozilla.layers.layerscope.LayersPacket.Layer layer = 1; + for (unsigned int i = 0, + n = static_cast(this->_internal_layer_size()); i < n; i++) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(1, this->_internal_layer(i), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields().data(), + static_cast(_internal_metadata_.unknown_fields().size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.layers.layerscope.LayersPacket) + return target; +} + +size_t LayersPacket::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.layers.layerscope.LayersPacket) + size_t total_size = 0; + + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // repeated .mozilla.layers.layerscope.LayersPacket.Layer layer = 1; + total_size += 1UL * this->_internal_layer_size(); + for (const auto& msg : this->layer_) { + total_size += + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize(msg); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields().size(); + } + int cached_size = ::PROTOBUF_NAMESPACE_ID::internal::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void LayersPacket::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::PROTOBUF_NAMESPACE_ID::internal::DownCast( + &from)); +} + +void LayersPacket::MergeFrom(const LayersPacket& from) { +// @@protoc_insertion_point(class_specific_merge_from_start:mozilla.layers.layerscope.LayersPacket) + GOOGLE_DCHECK_NE(&from, this); + _internal_metadata_.MergeFrom(from._internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + layer_.MergeFrom(from.layer_); +} + +void LayersPacket::CopyFrom(const LayersPacket& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.layers.layerscope.LayersPacket) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool LayersPacket::IsInitialized() const { + if (!::PROTOBUF_NAMESPACE_ID::internal::AllAreInitialized(layer_)) return false; + return true; +} + +void LayersPacket::InternalSwap(LayersPacket* other) { + using std::swap; + _internal_metadata_.Swap(&other->_internal_metadata_); + swap(_has_bits_[0], other->_has_bits_[0]); + layer_.InternalSwap(&other->layer_); +} + +std::string LayersPacket::GetTypeName() const { + return "mozilla.layers.layerscope.LayersPacket"; +} + + +// =================================================================== + +void MetaPacket::InitAsDefaultInstance() { +} +class MetaPacket::_Internal { + public: + using HasBits = decltype(std::declval()._has_bits_); + static void set_has_composedbyhwc(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } +}; + +MetaPacket::MetaPacket() + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), _internal_metadata_(nullptr) { + SharedCtor(); + // @@protoc_insertion_point(constructor:mozilla.layers.layerscope.MetaPacket) +} +MetaPacket::MetaPacket(const MetaPacket& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), + _internal_metadata_(nullptr), + _has_bits_(from._has_bits_) { + _internal_metadata_.MergeFrom(from._internal_metadata_); + composedbyhwc_ = from.composedbyhwc_; + // @@protoc_insertion_point(copy_constructor:mozilla.layers.layerscope.MetaPacket) +} + +void MetaPacket::SharedCtor() { + composedbyhwc_ = false; +} + +MetaPacket::~MetaPacket() { + // @@protoc_insertion_point(destructor:mozilla.layers.layerscope.MetaPacket) + SharedDtor(); +} + +void MetaPacket::SharedDtor() { +} + +void MetaPacket::SetCachedSize(int size) const { + _cached_size_.Set(size); +} +const MetaPacket& MetaPacket::default_instance() { + ::PROTOBUF_NAMESPACE_ID::internal::InitSCC(&::scc_info_MetaPacket_LayerScopePacket_2eproto.base); + return *internal_default_instance(); +} + + +void MetaPacket::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.layers.layerscope.MetaPacket) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + composedbyhwc_ = false; + _has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* MetaPacket::_InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + ::PROTOBUF_NAMESPACE_ID::uint32 tag; + ptr = ::PROTOBUF_NAMESPACE_ID::internal::ReadTag(ptr, &tag); + CHK_(ptr); + switch (tag >> 3) { + // optional bool composedByHwc = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 8)) { + _Internal::set_has_composedbyhwc(&has_bits); + composedbyhwc_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + default: { + handle_unusual: + if ((tag & 7) == 4 || tag == 0) { + ctx->SetLastTag(tag); + goto success; + } + ptr = UnknownFieldParse(tag, &_internal_metadata_, ptr, ctx); + CHK_(ptr != nullptr); + continue; + } + } // switch + } // while +success: + _has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto success; +#undef CHK_ +} + +::PROTOBUF_NAMESPACE_ID::uint8* MetaPacket::_InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.layers.layerscope.MetaPacket) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + // optional bool composedByHwc = 1; + if (cached_has_bits & 0x00000001u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteBoolToArray(1, this->_internal_composedbyhwc(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields().data(), + static_cast(_internal_metadata_.unknown_fields().size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.layers.layerscope.MetaPacket) + return target; +} + +size_t MetaPacket::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.layers.layerscope.MetaPacket) + size_t total_size = 0; + + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // optional bool composedByHwc = 1; + cached_has_bits = _has_bits_[0]; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + 1; + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields().size(); + } + int cached_size = ::PROTOBUF_NAMESPACE_ID::internal::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void MetaPacket::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::PROTOBUF_NAMESPACE_ID::internal::DownCast( + &from)); +} + +void MetaPacket::MergeFrom(const MetaPacket& from) { +// @@protoc_insertion_point(class_specific_merge_from_start:mozilla.layers.layerscope.MetaPacket) + GOOGLE_DCHECK_NE(&from, this); + _internal_metadata_.MergeFrom(from._internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + if (from._internal_has_composedbyhwc()) { + _internal_set_composedbyhwc(from._internal_composedbyhwc()); + } +} + +void MetaPacket::CopyFrom(const MetaPacket& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.layers.layerscope.MetaPacket) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool MetaPacket::IsInitialized() const { + return true; +} + +void MetaPacket::InternalSwap(MetaPacket* other) { + using std::swap; + _internal_metadata_.Swap(&other->_internal_metadata_); + swap(_has_bits_[0], other->_has_bits_[0]); + swap(composedbyhwc_, other->composedbyhwc_); +} + +std::string MetaPacket::GetTypeName() const { + return "mozilla.layers.layerscope.MetaPacket"; +} + + +// =================================================================== + +void DrawPacket_Rect::InitAsDefaultInstance() { +} +class DrawPacket_Rect::_Internal { + public: + using HasBits = decltype(std::declval()._has_bits_); + static void set_has_x(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static void set_has_y(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static void set_has_w(HasBits* has_bits) { + (*has_bits)[0] |= 4u; + } + static void set_has_h(HasBits* has_bits) { + (*has_bits)[0] |= 8u; + } +}; + +DrawPacket_Rect::DrawPacket_Rect() + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), _internal_metadata_(nullptr) { + SharedCtor(); + // @@protoc_insertion_point(constructor:mozilla.layers.layerscope.DrawPacket.Rect) +} +DrawPacket_Rect::DrawPacket_Rect(const DrawPacket_Rect& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), + _internal_metadata_(nullptr), + _has_bits_(from._has_bits_) { + _internal_metadata_.MergeFrom(from._internal_metadata_); + ::memcpy(&x_, &from.x_, + static_cast(reinterpret_cast(&h_) - + reinterpret_cast(&x_)) + sizeof(h_)); + // @@protoc_insertion_point(copy_constructor:mozilla.layers.layerscope.DrawPacket.Rect) +} + +void DrawPacket_Rect::SharedCtor() { + ::memset(&x_, 0, static_cast( + reinterpret_cast(&h_) - + reinterpret_cast(&x_)) + sizeof(h_)); +} + +DrawPacket_Rect::~DrawPacket_Rect() { + // @@protoc_insertion_point(destructor:mozilla.layers.layerscope.DrawPacket.Rect) + SharedDtor(); +} + +void DrawPacket_Rect::SharedDtor() { +} + +void DrawPacket_Rect::SetCachedSize(int size) const { + _cached_size_.Set(size); +} +const DrawPacket_Rect& DrawPacket_Rect::default_instance() { + ::PROTOBUF_NAMESPACE_ID::internal::InitSCC(&::scc_info_DrawPacket_Rect_LayerScopePacket_2eproto.base); + return *internal_default_instance(); +} + + +void DrawPacket_Rect::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.layers.layerscope.DrawPacket.Rect) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + if (cached_has_bits & 0x0000000fu) { + ::memset(&x_, 0, static_cast( + reinterpret_cast(&h_) - + reinterpret_cast(&x_)) + sizeof(h_)); + } + _has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* DrawPacket_Rect::_InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + ::PROTOBUF_NAMESPACE_ID::uint32 tag; + ptr = ::PROTOBUF_NAMESPACE_ID::internal::ReadTag(ptr, &tag); + CHK_(ptr); + switch (tag >> 3) { + // required float x = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 13)) { + _Internal::set_has_x(&has_bits); + x_ = ::PROTOBUF_NAMESPACE_ID::internal::UnalignedLoad(ptr); + ptr += sizeof(float); + } else goto handle_unusual; + continue; + // required float y = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 21)) { + _Internal::set_has_y(&has_bits); + y_ = ::PROTOBUF_NAMESPACE_ID::internal::UnalignedLoad(ptr); + ptr += sizeof(float); + } else goto handle_unusual; + continue; + // required float w = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 29)) { + _Internal::set_has_w(&has_bits); + w_ = ::PROTOBUF_NAMESPACE_ID::internal::UnalignedLoad(ptr); + ptr += sizeof(float); + } else goto handle_unusual; + continue; + // required float h = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 37)) { + _Internal::set_has_h(&has_bits); + h_ = ::PROTOBUF_NAMESPACE_ID::internal::UnalignedLoad(ptr); + ptr += sizeof(float); + } else goto handle_unusual; + continue; + default: { + handle_unusual: + if ((tag & 7) == 4 || tag == 0) { + ctx->SetLastTag(tag); + goto success; + } + ptr = UnknownFieldParse(tag, &_internal_metadata_, ptr, ctx); + CHK_(ptr != nullptr); + continue; + } + } // switch + } // while +success: + _has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto success; +#undef CHK_ +} + +::PROTOBUF_NAMESPACE_ID::uint8* DrawPacket_Rect::_InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.layers.layerscope.DrawPacket.Rect) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + // required float x = 1; + if (cached_has_bits & 0x00000001u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteFloatToArray(1, this->_internal_x(), target); + } + + // required float y = 2; + if (cached_has_bits & 0x00000002u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteFloatToArray(2, this->_internal_y(), target); + } + + // required float w = 3; + if (cached_has_bits & 0x00000004u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteFloatToArray(3, this->_internal_w(), target); + } + + // required float h = 4; + if (cached_has_bits & 0x00000008u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteFloatToArray(4, this->_internal_h(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields().data(), + static_cast(_internal_metadata_.unknown_fields().size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.layers.layerscope.DrawPacket.Rect) + return target; +} + +size_t DrawPacket_Rect::RequiredFieldsByteSizeFallback() const { +// @@protoc_insertion_point(required_fields_byte_size_fallback_start:mozilla.layers.layerscope.DrawPacket.Rect) + size_t total_size = 0; + + if (_internal_has_x()) { + // required float x = 1; + total_size += 1 + 4; + } + + if (_internal_has_y()) { + // required float y = 2; + total_size += 1 + 4; + } + + if (_internal_has_w()) { + // required float w = 3; + total_size += 1 + 4; + } + + if (_internal_has_h()) { + // required float h = 4; + total_size += 1 + 4; + } + + return total_size; +} +size_t DrawPacket_Rect::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.layers.layerscope.DrawPacket.Rect) + size_t total_size = 0; + + if (((_has_bits_[0] & 0x0000000f) ^ 0x0000000f) == 0) { // All required fields are present. + // required float x = 1; + total_size += 1 + 4; + + // required float y = 2; + total_size += 1 + 4; + + // required float w = 3; + total_size += 1 + 4; + + // required float h = 4; + total_size += 1 + 4; + + } else { + total_size += RequiredFieldsByteSizeFallback(); + } + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields().size(); + } + int cached_size = ::PROTOBUF_NAMESPACE_ID::internal::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void DrawPacket_Rect::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::PROTOBUF_NAMESPACE_ID::internal::DownCast( + &from)); +} + +void DrawPacket_Rect::MergeFrom(const DrawPacket_Rect& from) { +// @@protoc_insertion_point(class_specific_merge_from_start:mozilla.layers.layerscope.DrawPacket.Rect) + GOOGLE_DCHECK_NE(&from, this); + _internal_metadata_.MergeFrom(from._internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._has_bits_[0]; + if (cached_has_bits & 0x0000000fu) { + if (cached_has_bits & 0x00000001u) { + x_ = from.x_; + } + if (cached_has_bits & 0x00000002u) { + y_ = from.y_; + } + if (cached_has_bits & 0x00000004u) { + w_ = from.w_; + } + if (cached_has_bits & 0x00000008u) { + h_ = from.h_; + } + _has_bits_[0] |= cached_has_bits; + } +} + +void DrawPacket_Rect::CopyFrom(const DrawPacket_Rect& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.layers.layerscope.DrawPacket.Rect) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool DrawPacket_Rect::IsInitialized() const { + if ((_has_bits_[0] & 0x0000000f) != 0x0000000f) return false; + return true; +} + +void DrawPacket_Rect::InternalSwap(DrawPacket_Rect* other) { + using std::swap; + _internal_metadata_.Swap(&other->_internal_metadata_); + swap(_has_bits_[0], other->_has_bits_[0]); + swap(x_, other->x_); + swap(y_, other->y_); + swap(w_, other->w_); + swap(h_, other->h_); +} + +std::string DrawPacket_Rect::GetTypeName() const { + return "mozilla.layers.layerscope.DrawPacket.Rect"; +} + + +// =================================================================== + +void DrawPacket::InitAsDefaultInstance() { +} +class DrawPacket::_Internal { + public: + using HasBits = decltype(std::declval()._has_bits_); + static void set_has_offsetx(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static void set_has_offsety(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static void set_has_totalrects(HasBits* has_bits) { + (*has_bits)[0] |= 8u; + } + static void set_has_layerref(HasBits* has_bits) { + (*has_bits)[0] |= 4u; + } +}; + +DrawPacket::DrawPacket() + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), _internal_metadata_(nullptr) { + SharedCtor(); + // @@protoc_insertion_point(constructor:mozilla.layers.layerscope.DrawPacket) +} +DrawPacket::DrawPacket(const DrawPacket& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), + _internal_metadata_(nullptr), + _has_bits_(from._has_bits_), + mvmatrix_(from.mvmatrix_), + layerrect_(from.layerrect_), + texids_(from.texids_), + texturerect_(from.texturerect_) { + _internal_metadata_.MergeFrom(from._internal_metadata_); + ::memcpy(&offsetx_, &from.offsetx_, + static_cast(reinterpret_cast(&totalrects_) - + reinterpret_cast(&offsetx_)) + sizeof(totalrects_)); + // @@protoc_insertion_point(copy_constructor:mozilla.layers.layerscope.DrawPacket) +} + +void DrawPacket::SharedCtor() { + ::PROTOBUF_NAMESPACE_ID::internal::InitSCC(&scc_info_DrawPacket_LayerScopePacket_2eproto.base); + ::memset(&offsetx_, 0, static_cast( + reinterpret_cast(&totalrects_) - + reinterpret_cast(&offsetx_)) + sizeof(totalrects_)); +} + +DrawPacket::~DrawPacket() { + // @@protoc_insertion_point(destructor:mozilla.layers.layerscope.DrawPacket) + SharedDtor(); +} + +void DrawPacket::SharedDtor() { +} + +void DrawPacket::SetCachedSize(int size) const { + _cached_size_.Set(size); +} +const DrawPacket& DrawPacket::default_instance() { + ::PROTOBUF_NAMESPACE_ID::internal::InitSCC(&::scc_info_DrawPacket_LayerScopePacket_2eproto.base); + return *internal_default_instance(); +} + + +void DrawPacket::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.layers.layerscope.DrawPacket) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + mvmatrix_.Clear(); + layerrect_.Clear(); + texids_.Clear(); + texturerect_.Clear(); + cached_has_bits = _has_bits_[0]; + if (cached_has_bits & 0x0000000fu) { + ::memset(&offsetx_, 0, static_cast( + reinterpret_cast(&totalrects_) - + reinterpret_cast(&offsetx_)) + sizeof(totalrects_)); + } + _has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* DrawPacket::_InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + ::PROTOBUF_NAMESPACE_ID::uint32 tag; + ptr = ::PROTOBUF_NAMESPACE_ID::internal::ReadTag(ptr, &tag); + CHK_(ptr); + switch (tag >> 3) { + // required float offsetX = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 13)) { + _Internal::set_has_offsetx(&has_bits); + offsetx_ = ::PROTOBUF_NAMESPACE_ID::internal::UnalignedLoad(ptr); + ptr += sizeof(float); + } else goto handle_unusual; + continue; + // required float offsetY = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 21)) { + _Internal::set_has_offsety(&has_bits); + offsety_ = ::PROTOBUF_NAMESPACE_ID::internal::UnalignedLoad(ptr); + ptr += sizeof(float); + } else goto handle_unusual; + continue; + // repeated float mvMatrix = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 29)) { + ptr -= 1; + do { + ptr += 1; + _internal_add_mvmatrix(::PROTOBUF_NAMESPACE_ID::internal::UnalignedLoad(ptr)); + ptr += sizeof(float); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<29>(ptr)); + } else if (static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 26) { + ptr = ::PROTOBUF_NAMESPACE_ID::internal::PackedFloatParser(_internal_mutable_mvmatrix(), ptr, ctx); + CHK_(ptr); + } else goto handle_unusual; + continue; + // required uint32 totalRects = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 32)) { + _Internal::set_has_totalrects(&has_bits); + totalrects_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // repeated .mozilla.layers.layerscope.DrawPacket.Rect layerRect = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 42)) { + ptr -= 1; + do { + ptr += 1; + ptr = ctx->ParseMessage(_internal_add_layerrect(), ptr); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<42>(ptr)); + } else goto handle_unusual; + continue; + // required uint64 layerref = 6; + case 6: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 48)) { + _Internal::set_has_layerref(&has_bits); + layerref_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // repeated uint32 texIDs = 7; + case 7: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 56)) { + ptr -= 1; + do { + ptr += 1; + _internal_add_texids(::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr)); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<56>(ptr)); + } else if (static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 58) { + ptr = ::PROTOBUF_NAMESPACE_ID::internal::PackedUInt32Parser(_internal_mutable_texids(), ptr, ctx); + CHK_(ptr); + } else goto handle_unusual; + continue; + // repeated .mozilla.layers.layerscope.DrawPacket.Rect textureRect = 8; + case 8: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 66)) { + ptr -= 1; + do { + ptr += 1; + ptr = ctx->ParseMessage(_internal_add_texturerect(), ptr); + CHK_(ptr); + if (!ctx->DataAvailable(ptr)) break; + } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<66>(ptr)); + } else goto handle_unusual; + continue; + default: { + handle_unusual: + if ((tag & 7) == 4 || tag == 0) { + ctx->SetLastTag(tag); + goto success; + } + ptr = UnknownFieldParse(tag, &_internal_metadata_, ptr, ctx); + CHK_(ptr != nullptr); + continue; + } + } // switch + } // while +success: + _has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto success; +#undef CHK_ +} + +::PROTOBUF_NAMESPACE_ID::uint8* DrawPacket::_InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.layers.layerscope.DrawPacket) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + // required float offsetX = 1; + if (cached_has_bits & 0x00000001u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteFloatToArray(1, this->_internal_offsetx(), target); + } + + // required float offsetY = 2; + if (cached_has_bits & 0x00000002u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteFloatToArray(2, this->_internal_offsety(), target); + } + + // repeated float mvMatrix = 3; + for (int i = 0, n = this->_internal_mvmatrix_size(); i < n; i++) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteFloatToArray(3, this->_internal_mvmatrix(i), target); + } + + // required uint32 totalRects = 4; + if (cached_has_bits & 0x00000008u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteUInt32ToArray(4, this->_internal_totalrects(), target); + } + + // repeated .mozilla.layers.layerscope.DrawPacket.Rect layerRect = 5; + for (unsigned int i = 0, + n = static_cast(this->_internal_layerrect_size()); i < n; i++) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(5, this->_internal_layerrect(i), target, stream); + } + + // required uint64 layerref = 6; + if (cached_has_bits & 0x00000004u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteUInt64ToArray(6, this->_internal_layerref(), target); + } + + // repeated uint32 texIDs = 7; + for (int i = 0, n = this->_internal_texids_size(); i < n; i++) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteUInt32ToArray(7, this->_internal_texids(i), target); + } + + // repeated .mozilla.layers.layerscope.DrawPacket.Rect textureRect = 8; + for (unsigned int i = 0, + n = static_cast(this->_internal_texturerect_size()); i < n; i++) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(8, this->_internal_texturerect(i), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields().data(), + static_cast(_internal_metadata_.unknown_fields().size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.layers.layerscope.DrawPacket) + return target; +} + +size_t DrawPacket::RequiredFieldsByteSizeFallback() const { +// @@protoc_insertion_point(required_fields_byte_size_fallback_start:mozilla.layers.layerscope.DrawPacket) + size_t total_size = 0; + + if (_internal_has_offsetx()) { + // required float offsetX = 1; + total_size += 1 + 4; + } + + if (_internal_has_offsety()) { + // required float offsetY = 2; + total_size += 1 + 4; + } + + if (_internal_has_layerref()) { + // required uint64 layerref = 6; + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::UInt64Size( + this->_internal_layerref()); + } + + if (_internal_has_totalrects()) { + // required uint32 totalRects = 4; + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::UInt32Size( + this->_internal_totalrects()); + } + + return total_size; +} +size_t DrawPacket::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.layers.layerscope.DrawPacket) + size_t total_size = 0; + + if (((_has_bits_[0] & 0x0000000f) ^ 0x0000000f) == 0) { // All required fields are present. + // required float offsetX = 1; + total_size += 1 + 4; + + // required float offsetY = 2; + total_size += 1 + 4; + + // required uint64 layerref = 6; + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::UInt64Size( + this->_internal_layerref()); + + // required uint32 totalRects = 4; + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::UInt32Size( + this->_internal_totalrects()); + + } else { + total_size += RequiredFieldsByteSizeFallback(); + } + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // repeated float mvMatrix = 3; + { + unsigned int count = static_cast(this->_internal_mvmatrix_size()); + size_t data_size = 4UL * count; + total_size += 1 * + ::PROTOBUF_NAMESPACE_ID::internal::FromIntSize(this->_internal_mvmatrix_size()); + total_size += data_size; + } + + // repeated .mozilla.layers.layerscope.DrawPacket.Rect layerRect = 5; + total_size += 1UL * this->_internal_layerrect_size(); + for (const auto& msg : this->layerrect_) { + total_size += + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize(msg); + } + + // repeated uint32 texIDs = 7; + { + size_t data_size = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + UInt32Size(this->texids_); + total_size += 1 * + ::PROTOBUF_NAMESPACE_ID::internal::FromIntSize(this->_internal_texids_size()); + total_size += data_size; + } + + // repeated .mozilla.layers.layerscope.DrawPacket.Rect textureRect = 8; + total_size += 1UL * this->_internal_texturerect_size(); + for (const auto& msg : this->texturerect_) { + total_size += + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize(msg); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields().size(); + } + int cached_size = ::PROTOBUF_NAMESPACE_ID::internal::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void DrawPacket::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::PROTOBUF_NAMESPACE_ID::internal::DownCast( + &from)); +} + +void DrawPacket::MergeFrom(const DrawPacket& from) { +// @@protoc_insertion_point(class_specific_merge_from_start:mozilla.layers.layerscope.DrawPacket) + GOOGLE_DCHECK_NE(&from, this); + _internal_metadata_.MergeFrom(from._internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + mvmatrix_.MergeFrom(from.mvmatrix_); + layerrect_.MergeFrom(from.layerrect_); + texids_.MergeFrom(from.texids_); + texturerect_.MergeFrom(from.texturerect_); + cached_has_bits = from._has_bits_[0]; + if (cached_has_bits & 0x0000000fu) { + if (cached_has_bits & 0x00000001u) { + offsetx_ = from.offsetx_; + } + if (cached_has_bits & 0x00000002u) { + offsety_ = from.offsety_; + } + if (cached_has_bits & 0x00000004u) { + layerref_ = from.layerref_; + } + if (cached_has_bits & 0x00000008u) { + totalrects_ = from.totalrects_; + } + _has_bits_[0] |= cached_has_bits; + } +} + +void DrawPacket::CopyFrom(const DrawPacket& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.layers.layerscope.DrawPacket) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool DrawPacket::IsInitialized() const { + if ((_has_bits_[0] & 0x0000000f) != 0x0000000f) return false; + if (!::PROTOBUF_NAMESPACE_ID::internal::AllAreInitialized(layerrect_)) return false; + if (!::PROTOBUF_NAMESPACE_ID::internal::AllAreInitialized(texturerect_)) return false; + return true; +} + +void DrawPacket::InternalSwap(DrawPacket* other) { + using std::swap; + _internal_metadata_.Swap(&other->_internal_metadata_); + swap(_has_bits_[0], other->_has_bits_[0]); + mvmatrix_.InternalSwap(&other->mvmatrix_); + layerrect_.InternalSwap(&other->layerrect_); + texids_.InternalSwap(&other->texids_); + texturerect_.InternalSwap(&other->texturerect_); + swap(offsetx_, other->offsetx_); + swap(offsety_, other->offsety_); + swap(layerref_, other->layerref_); + swap(totalrects_, other->totalrects_); +} + +std::string DrawPacket::GetTypeName() const { + return "mozilla.layers.layerscope.DrawPacket"; +} + + +// =================================================================== + +void Packet::InitAsDefaultInstance() { + ::mozilla::layers::layerscope::_Packet_default_instance_._instance.get_mutable()->frame_ = const_cast< ::mozilla::layers::layerscope::FramePacket*>( + ::mozilla::layers::layerscope::FramePacket::internal_default_instance()); + ::mozilla::layers::layerscope::_Packet_default_instance_._instance.get_mutable()->color_ = const_cast< ::mozilla::layers::layerscope::ColorPacket*>( + ::mozilla::layers::layerscope::ColorPacket::internal_default_instance()); + ::mozilla::layers::layerscope::_Packet_default_instance_._instance.get_mutable()->texture_ = const_cast< ::mozilla::layers::layerscope::TexturePacket*>( + ::mozilla::layers::layerscope::TexturePacket::internal_default_instance()); + ::mozilla::layers::layerscope::_Packet_default_instance_._instance.get_mutable()->layers_ = const_cast< ::mozilla::layers::layerscope::LayersPacket*>( + ::mozilla::layers::layerscope::LayersPacket::internal_default_instance()); + ::mozilla::layers::layerscope::_Packet_default_instance_._instance.get_mutable()->meta_ = const_cast< ::mozilla::layers::layerscope::MetaPacket*>( + ::mozilla::layers::layerscope::MetaPacket::internal_default_instance()); + ::mozilla::layers::layerscope::_Packet_default_instance_._instance.get_mutable()->draw_ = const_cast< ::mozilla::layers::layerscope::DrawPacket*>( + ::mozilla::layers::layerscope::DrawPacket::internal_default_instance()); +} +class Packet::_Internal { + public: + using HasBits = decltype(std::declval()._has_bits_); + static void set_has_type(HasBits* has_bits) { + (*has_bits)[0] |= 64u; + } + static const ::mozilla::layers::layerscope::FramePacket& frame(const Packet* msg); + static void set_has_frame(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static const ::mozilla::layers::layerscope::ColorPacket& color(const Packet* msg); + static void set_has_color(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } + static const ::mozilla::layers::layerscope::TexturePacket& texture(const Packet* msg); + static void set_has_texture(HasBits* has_bits) { + (*has_bits)[0] |= 4u; + } + static const ::mozilla::layers::layerscope::LayersPacket& layers(const Packet* msg); + static void set_has_layers(HasBits* has_bits) { + (*has_bits)[0] |= 8u; + } + static const ::mozilla::layers::layerscope::MetaPacket& meta(const Packet* msg); + static void set_has_meta(HasBits* has_bits) { + (*has_bits)[0] |= 16u; + } + static const ::mozilla::layers::layerscope::DrawPacket& draw(const Packet* msg); + static void set_has_draw(HasBits* has_bits) { + (*has_bits)[0] |= 32u; + } +}; + +const ::mozilla::layers::layerscope::FramePacket& +Packet::_Internal::frame(const Packet* msg) { + return *msg->frame_; +} +const ::mozilla::layers::layerscope::ColorPacket& +Packet::_Internal::color(const Packet* msg) { + return *msg->color_; +} +const ::mozilla::layers::layerscope::TexturePacket& +Packet::_Internal::texture(const Packet* msg) { + return *msg->texture_; +} +const ::mozilla::layers::layerscope::LayersPacket& +Packet::_Internal::layers(const Packet* msg) { + return *msg->layers_; +} +const ::mozilla::layers::layerscope::MetaPacket& +Packet::_Internal::meta(const Packet* msg) { + return *msg->meta_; +} +const ::mozilla::layers::layerscope::DrawPacket& +Packet::_Internal::draw(const Packet* msg) { + return *msg->draw_; +} +Packet::Packet() + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), _internal_metadata_(nullptr) { + SharedCtor(); + // @@protoc_insertion_point(constructor:mozilla.layers.layerscope.Packet) +} +Packet::Packet(const Packet& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), + _internal_metadata_(nullptr), + _has_bits_(from._has_bits_) { + _internal_metadata_.MergeFrom(from._internal_metadata_); + if (from._internal_has_frame()) { + frame_ = new ::mozilla::layers::layerscope::FramePacket(*from.frame_); + } else { + frame_ = nullptr; + } + if (from._internal_has_color()) { + color_ = new ::mozilla::layers::layerscope::ColorPacket(*from.color_); + } else { + color_ = nullptr; + } + if (from._internal_has_texture()) { + texture_ = new ::mozilla::layers::layerscope::TexturePacket(*from.texture_); + } else { + texture_ = nullptr; + } + if (from._internal_has_layers()) { + layers_ = new ::mozilla::layers::layerscope::LayersPacket(*from.layers_); + } else { + layers_ = nullptr; + } + if (from._internal_has_meta()) { + meta_ = new ::mozilla::layers::layerscope::MetaPacket(*from.meta_); + } else { + meta_ = nullptr; + } + if (from._internal_has_draw()) { + draw_ = new ::mozilla::layers::layerscope::DrawPacket(*from.draw_); + } else { + draw_ = nullptr; + } + type_ = from.type_; + // @@protoc_insertion_point(copy_constructor:mozilla.layers.layerscope.Packet) +} + +void Packet::SharedCtor() { + ::PROTOBUF_NAMESPACE_ID::internal::InitSCC(&scc_info_Packet_LayerScopePacket_2eproto.base); + ::memset(&frame_, 0, static_cast( + reinterpret_cast(&draw_) - + reinterpret_cast(&frame_)) + sizeof(draw_)); + type_ = 1; +} + +Packet::~Packet() { + // @@protoc_insertion_point(destructor:mozilla.layers.layerscope.Packet) + SharedDtor(); +} + +void Packet::SharedDtor() { + if (this != internal_default_instance()) delete frame_; + if (this != internal_default_instance()) delete color_; + if (this != internal_default_instance()) delete texture_; + if (this != internal_default_instance()) delete layers_; + if (this != internal_default_instance()) delete meta_; + if (this != internal_default_instance()) delete draw_; +} + +void Packet::SetCachedSize(int size) const { + _cached_size_.Set(size); +} +const Packet& Packet::default_instance() { + ::PROTOBUF_NAMESPACE_ID::internal::InitSCC(&::scc_info_Packet_LayerScopePacket_2eproto.base); + return *internal_default_instance(); +} + + +void Packet::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.layers.layerscope.Packet) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + if (cached_has_bits & 0x0000007fu) { + if (cached_has_bits & 0x00000001u) { + GOOGLE_DCHECK(frame_ != nullptr); + frame_->Clear(); + } + if (cached_has_bits & 0x00000002u) { + GOOGLE_DCHECK(color_ != nullptr); + color_->Clear(); + } + if (cached_has_bits & 0x00000004u) { + GOOGLE_DCHECK(texture_ != nullptr); + texture_->Clear(); + } + if (cached_has_bits & 0x00000008u) { + GOOGLE_DCHECK(layers_ != nullptr); + layers_->Clear(); + } + if (cached_has_bits & 0x00000010u) { + GOOGLE_DCHECK(meta_ != nullptr); + meta_->Clear(); + } + if (cached_has_bits & 0x00000020u) { + GOOGLE_DCHECK(draw_ != nullptr); + draw_->Clear(); + } + type_ = 1; + } + _has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* Packet::_InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + ::PROTOBUF_NAMESPACE_ID::uint32 tag; + ptr = ::PROTOBUF_NAMESPACE_ID::internal::ReadTag(ptr, &tag); + CHK_(ptr); + switch (tag >> 3) { + // required .mozilla.layers.layerscope.Packet.DataType type = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 8)) { + ::PROTOBUF_NAMESPACE_ID::uint64 val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::mozilla::layers::layerscope::Packet_DataType_IsValid(val))) { + _internal_set_type(static_cast<::mozilla::layers::layerscope::Packet_DataType>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(1, val, mutable_unknown_fields()); + } + } else goto handle_unusual; + continue; + // optional .mozilla.layers.layerscope.FramePacket frame = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 18)) { + ptr = ctx->ParseMessage(_internal_mutable_frame(), ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional .mozilla.layers.layerscope.ColorPacket color = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 26)) { + ptr = ctx->ParseMessage(_internal_mutable_color(), ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional .mozilla.layers.layerscope.TexturePacket texture = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 34)) { + ptr = ctx->ParseMessage(_internal_mutable_texture(), ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional .mozilla.layers.layerscope.LayersPacket layers = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 42)) { + ptr = ctx->ParseMessage(_internal_mutable_layers(), ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional .mozilla.layers.layerscope.MetaPacket meta = 6; + case 6: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 50)) { + ptr = ctx->ParseMessage(_internal_mutable_meta(), ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + // optional .mozilla.layers.layerscope.DrawPacket draw = 7; + case 7: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 58)) { + ptr = ctx->ParseMessage(_internal_mutable_draw(), ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + default: { + handle_unusual: + if ((tag & 7) == 4 || tag == 0) { + ctx->SetLastTag(tag); + goto success; + } + ptr = UnknownFieldParse(tag, &_internal_metadata_, ptr, ctx); + CHK_(ptr != nullptr); + continue; + } + } // switch + } // while +success: + _has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto success; +#undef CHK_ +} + +::PROTOBUF_NAMESPACE_ID::uint8* Packet::_InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.layers.layerscope.Packet) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + // required .mozilla.layers.layerscope.Packet.DataType type = 1; + if (cached_has_bits & 0x00000040u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteEnumToArray( + 1, this->_internal_type(), target); + } + + // optional .mozilla.layers.layerscope.FramePacket frame = 2; + if (cached_has_bits & 0x00000001u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage( + 2, _Internal::frame(this), target, stream); + } + + // optional .mozilla.layers.layerscope.ColorPacket color = 3; + if (cached_has_bits & 0x00000002u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage( + 3, _Internal::color(this), target, stream); + } + + // optional .mozilla.layers.layerscope.TexturePacket texture = 4; + if (cached_has_bits & 0x00000004u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage( + 4, _Internal::texture(this), target, stream); + } + + // optional .mozilla.layers.layerscope.LayersPacket layers = 5; + if (cached_has_bits & 0x00000008u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage( + 5, _Internal::layers(this), target, stream); + } + + // optional .mozilla.layers.layerscope.MetaPacket meta = 6; + if (cached_has_bits & 0x00000010u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage( + 6, _Internal::meta(this), target, stream); + } + + // optional .mozilla.layers.layerscope.DrawPacket draw = 7; + if (cached_has_bits & 0x00000020u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage( + 7, _Internal::draw(this), target, stream); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields().data(), + static_cast(_internal_metadata_.unknown_fields().size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.layers.layerscope.Packet) + return target; +} + +size_t Packet::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.layers.layerscope.Packet) + size_t total_size = 0; + + // required .mozilla.layers.layerscope.Packet.DataType type = 1; + if (_internal_has_type()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::EnumSize(this->_internal_type()); + } + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + if (cached_has_bits & 0x0000003fu) { + // optional .mozilla.layers.layerscope.FramePacket frame = 2; + if (cached_has_bits & 0x00000001u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *frame_); + } + + // optional .mozilla.layers.layerscope.ColorPacket color = 3; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *color_); + } + + // optional .mozilla.layers.layerscope.TexturePacket texture = 4; + if (cached_has_bits & 0x00000004u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *texture_); + } + + // optional .mozilla.layers.layerscope.LayersPacket layers = 5; + if (cached_has_bits & 0x00000008u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *layers_); + } + + // optional .mozilla.layers.layerscope.MetaPacket meta = 6; + if (cached_has_bits & 0x00000010u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *meta_); + } + + // optional .mozilla.layers.layerscope.DrawPacket draw = 7; + if (cached_has_bits & 0x00000020u) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *draw_); + } + + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields().size(); + } + int cached_size = ::PROTOBUF_NAMESPACE_ID::internal::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void Packet::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::PROTOBUF_NAMESPACE_ID::internal::DownCast( + &from)); +} + +void Packet::MergeFrom(const Packet& from) { +// @@protoc_insertion_point(class_specific_merge_from_start:mozilla.layers.layerscope.Packet) + GOOGLE_DCHECK_NE(&from, this); + _internal_metadata_.MergeFrom(from._internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._has_bits_[0]; + if (cached_has_bits & 0x0000007fu) { + if (cached_has_bits & 0x00000001u) { + _internal_mutable_frame()->::mozilla::layers::layerscope::FramePacket::MergeFrom(from._internal_frame()); + } + if (cached_has_bits & 0x00000002u) { + _internal_mutable_color()->::mozilla::layers::layerscope::ColorPacket::MergeFrom(from._internal_color()); + } + if (cached_has_bits & 0x00000004u) { + _internal_mutable_texture()->::mozilla::layers::layerscope::TexturePacket::MergeFrom(from._internal_texture()); + } + if (cached_has_bits & 0x00000008u) { + _internal_mutable_layers()->::mozilla::layers::layerscope::LayersPacket::MergeFrom(from._internal_layers()); + } + if (cached_has_bits & 0x00000010u) { + _internal_mutable_meta()->::mozilla::layers::layerscope::MetaPacket::MergeFrom(from._internal_meta()); + } + if (cached_has_bits & 0x00000020u) { + _internal_mutable_draw()->::mozilla::layers::layerscope::DrawPacket::MergeFrom(from._internal_draw()); + } + if (cached_has_bits & 0x00000040u) { + type_ = from.type_; + } + _has_bits_[0] |= cached_has_bits; + } +} + +void Packet::CopyFrom(const Packet& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.layers.layerscope.Packet) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool Packet::IsInitialized() const { + if ((_has_bits_[0] & 0x00000040) != 0x00000040) return false; + if (_internal_has_color()) { + if (!color_->IsInitialized()) return false; + } + if (_internal_has_texture()) { + if (!texture_->IsInitialized()) return false; + } + if (_internal_has_layers()) { + if (!layers_->IsInitialized()) return false; + } + if (_internal_has_draw()) { + if (!draw_->IsInitialized()) return false; + } + return true; +} + +void Packet::InternalSwap(Packet* other) { + using std::swap; + _internal_metadata_.Swap(&other->_internal_metadata_); + swap(_has_bits_[0], other->_has_bits_[0]); + swap(frame_, other->frame_); + swap(color_, other->color_); + swap(texture_, other->texture_); + swap(layers_, other->layers_); + swap(meta_, other->meta_); + swap(draw_, other->draw_); + swap(type_, other->type_); +} + +std::string Packet::GetTypeName() const { + return "mozilla.layers.layerscope.Packet"; +} + + +// =================================================================== + +void CommandPacket::InitAsDefaultInstance() { +} +class CommandPacket::_Internal { + public: + using HasBits = decltype(std::declval()._has_bits_); + static void set_has_type(HasBits* has_bits) { + (*has_bits)[0] |= 1u; + } + static void set_has_value(HasBits* has_bits) { + (*has_bits)[0] |= 2u; + } +}; + +CommandPacket::CommandPacket() + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), _internal_metadata_(nullptr) { + SharedCtor(); + // @@protoc_insertion_point(constructor:mozilla.layers.layerscope.CommandPacket) +} +CommandPacket::CommandPacket(const CommandPacket& from) + : ::PROTOBUF_NAMESPACE_ID::MessageLite(), + _internal_metadata_(nullptr), + _has_bits_(from._has_bits_) { + _internal_metadata_.MergeFrom(from._internal_metadata_); + ::memcpy(&type_, &from.type_, + static_cast(reinterpret_cast(&value_) - + reinterpret_cast(&type_)) + sizeof(value_)); + // @@protoc_insertion_point(copy_constructor:mozilla.layers.layerscope.CommandPacket) +} + +void CommandPacket::SharedCtor() { + ::memset(&type_, 0, static_cast( + reinterpret_cast(&value_) - + reinterpret_cast(&type_)) + sizeof(value_)); +} + +CommandPacket::~CommandPacket() { + // @@protoc_insertion_point(destructor:mozilla.layers.layerscope.CommandPacket) + SharedDtor(); +} + +void CommandPacket::SharedDtor() { +} + +void CommandPacket::SetCachedSize(int size) const { + _cached_size_.Set(size); +} +const CommandPacket& CommandPacket::default_instance() { + ::PROTOBUF_NAMESPACE_ID::internal::InitSCC(&::scc_info_CommandPacket_LayerScopePacket_2eproto.base); + return *internal_default_instance(); +} + + +void CommandPacket::Clear() { +// @@protoc_insertion_point(message_clear_start:mozilla.layers.layerscope.CommandPacket) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + ::memset(&type_, 0, static_cast( + reinterpret_cast(&value_) - + reinterpret_cast(&type_)) + sizeof(value_)); + } + _has_bits_.Clear(); + _internal_metadata_.Clear(); +} + +const char* CommandPacket::_InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + _Internal::HasBits has_bits{}; + while (!ctx->Done(&ptr)) { + ::PROTOBUF_NAMESPACE_ID::uint32 tag; + ptr = ::PROTOBUF_NAMESPACE_ID::internal::ReadTag(ptr, &tag); + CHK_(ptr); + switch (tag >> 3) { + // required .mozilla.layers.layerscope.CommandPacket.CmdType type = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 8)) { + ::PROTOBUF_NAMESPACE_ID::uint64 val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + if (PROTOBUF_PREDICT_TRUE(::mozilla::layers::layerscope::CommandPacket_CmdType_IsValid(val))) { + _internal_set_type(static_cast<::mozilla::layers::layerscope::CommandPacket_CmdType>(val)); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(1, val, mutable_unknown_fields()); + } + } else goto handle_unusual; + continue; + // optional bool value = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 16)) { + _Internal::set_has_value(&has_bits); + value_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint(&ptr); + CHK_(ptr); + } else goto handle_unusual; + continue; + default: { + handle_unusual: + if ((tag & 7) == 4 || tag == 0) { + ctx->SetLastTag(tag); + goto success; + } + ptr = UnknownFieldParse(tag, &_internal_metadata_, ptr, ctx); + CHK_(ptr != nullptr); + continue; + } + } // switch + } // while +success: + _has_bits_.Or(has_bits); + return ptr; +failure: + ptr = nullptr; + goto success; +#undef CHK_ +} + +::PROTOBUF_NAMESPACE_ID::uint8* CommandPacket::_InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:mozilla.layers.layerscope.CommandPacket) + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = _has_bits_[0]; + // required .mozilla.layers.layerscope.CommandPacket.CmdType type = 1; + if (cached_has_bits & 0x00000001u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteEnumToArray( + 1, this->_internal_type(), target); + } + + // optional bool value = 2; + if (cached_has_bits & 0x00000002u) { + target = stream->EnsureSpace(target); + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::WriteBoolToArray(2, this->_internal_value(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = stream->WriteRaw(_internal_metadata_.unknown_fields().data(), + static_cast(_internal_metadata_.unknown_fields().size()), target); + } + // @@protoc_insertion_point(serialize_to_array_end:mozilla.layers.layerscope.CommandPacket) + return target; +} + +size_t CommandPacket::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:mozilla.layers.layerscope.CommandPacket) + size_t total_size = 0; + + // required .mozilla.layers.layerscope.CommandPacket.CmdType type = 1; + if (_internal_has_type()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::EnumSize(this->_internal_type()); + } + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // optional bool value = 2; + cached_has_bits = _has_bits_[0]; + if (cached_has_bits & 0x00000002u) { + total_size += 1 + 1; + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + total_size += _internal_metadata_.unknown_fields().size(); + } + int cached_size = ::PROTOBUF_NAMESPACE_ID::internal::ToCachedSize(total_size); + SetCachedSize(cached_size); + return total_size; +} + +void CommandPacket::CheckTypeAndMergeFrom( + const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) { + MergeFrom(*::PROTOBUF_NAMESPACE_ID::internal::DownCast( + &from)); +} + +void CommandPacket::MergeFrom(const CommandPacket& from) { +// @@protoc_insertion_point(class_specific_merge_from_start:mozilla.layers.layerscope.CommandPacket) + GOOGLE_DCHECK_NE(&from, this); + _internal_metadata_.MergeFrom(from._internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + cached_has_bits = from._has_bits_[0]; + if (cached_has_bits & 0x00000003u) { + if (cached_has_bits & 0x00000001u) { + type_ = from.type_; + } + if (cached_has_bits & 0x00000002u) { + value_ = from.value_; + } + _has_bits_[0] |= cached_has_bits; + } +} + +void CommandPacket::CopyFrom(const CommandPacket& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.layers.layerscope.CommandPacket) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool CommandPacket::IsInitialized() const { + if ((_has_bits_[0] & 0x00000001) != 0x00000001) return false; + return true; +} + +void CommandPacket::InternalSwap(CommandPacket* other) { + using std::swap; + _internal_metadata_.Swap(&other->_internal_metadata_); + swap(_has_bits_[0], other->_has_bits_[0]); + swap(type_, other->type_); + swap(value_, other->value_); +} + +std::string CommandPacket::GetTypeName() const { + return "mozilla.layers.layerscope.CommandPacket"; +} + + +// @@protoc_insertion_point(namespace_scope) +} // namespace layerscope +} // namespace layers +} // namespace mozilla +PROTOBUF_NAMESPACE_OPEN +template<> PROTOBUF_NOINLINE ::mozilla::layers::layerscope::FramePacket* Arena::CreateMaybeMessage< ::mozilla::layers::layerscope::FramePacket >(Arena* arena) { + return Arena::CreateInternal< ::mozilla::layers::layerscope::FramePacket >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::layers::layerscope::ColorPacket* Arena::CreateMaybeMessage< ::mozilla::layers::layerscope::ColorPacket >(Arena* arena) { + return Arena::CreateInternal< ::mozilla::layers::layerscope::ColorPacket >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::layers::layerscope::TexturePacket_Rect* Arena::CreateMaybeMessage< ::mozilla::layers::layerscope::TexturePacket_Rect >(Arena* arena) { + return Arena::CreateInternal< ::mozilla::layers::layerscope::TexturePacket_Rect >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::layers::layerscope::TexturePacket_Size* Arena::CreateMaybeMessage< ::mozilla::layers::layerscope::TexturePacket_Size >(Arena* arena) { + return Arena::CreateInternal< ::mozilla::layers::layerscope::TexturePacket_Size >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::layers::layerscope::TexturePacket_Matrix* Arena::CreateMaybeMessage< ::mozilla::layers::layerscope::TexturePacket_Matrix >(Arena* arena) { + return Arena::CreateInternal< ::mozilla::layers::layerscope::TexturePacket_Matrix >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::layers::layerscope::TexturePacket_EffectMask* Arena::CreateMaybeMessage< ::mozilla::layers::layerscope::TexturePacket_EffectMask >(Arena* arena) { + return Arena::CreateInternal< ::mozilla::layers::layerscope::TexturePacket_EffectMask >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::layers::layerscope::TexturePacket* Arena::CreateMaybeMessage< ::mozilla::layers::layerscope::TexturePacket >(Arena* arena) { + return Arena::CreateInternal< ::mozilla::layers::layerscope::TexturePacket >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::layers::layerscope::LayersPacket_Layer_Size* Arena::CreateMaybeMessage< ::mozilla::layers::layerscope::LayersPacket_Layer_Size >(Arena* arena) { + return Arena::CreateInternal< ::mozilla::layers::layerscope::LayersPacket_Layer_Size >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::layers::layerscope::LayersPacket_Layer_Rect* Arena::CreateMaybeMessage< ::mozilla::layers::layerscope::LayersPacket_Layer_Rect >(Arena* arena) { + return Arena::CreateInternal< ::mozilla::layers::layerscope::LayersPacket_Layer_Rect >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::layers::layerscope::LayersPacket_Layer_Region* Arena::CreateMaybeMessage< ::mozilla::layers::layerscope::LayersPacket_Layer_Region >(Arena* arena) { + return Arena::CreateInternal< ::mozilla::layers::layerscope::LayersPacket_Layer_Region >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix* Arena::CreateMaybeMessage< ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix >(Arena* arena) { + return Arena::CreateInternal< ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::layers::layerscope::LayersPacket_Layer_Shadow* Arena::CreateMaybeMessage< ::mozilla::layers::layerscope::LayersPacket_Layer_Shadow >(Arena* arena) { + return Arena::CreateInternal< ::mozilla::layers::layerscope::LayersPacket_Layer_Shadow >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::layers::layerscope::LayersPacket_Layer* Arena::CreateMaybeMessage< ::mozilla::layers::layerscope::LayersPacket_Layer >(Arena* arena) { + return Arena::CreateInternal< ::mozilla::layers::layerscope::LayersPacket_Layer >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::layers::layerscope::LayersPacket* Arena::CreateMaybeMessage< ::mozilla::layers::layerscope::LayersPacket >(Arena* arena) { + return Arena::CreateInternal< ::mozilla::layers::layerscope::LayersPacket >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::layers::layerscope::MetaPacket* Arena::CreateMaybeMessage< ::mozilla::layers::layerscope::MetaPacket >(Arena* arena) { + return Arena::CreateInternal< ::mozilla::layers::layerscope::MetaPacket >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::layers::layerscope::DrawPacket_Rect* Arena::CreateMaybeMessage< ::mozilla::layers::layerscope::DrawPacket_Rect >(Arena* arena) { + return Arena::CreateInternal< ::mozilla::layers::layerscope::DrawPacket_Rect >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::layers::layerscope::DrawPacket* Arena::CreateMaybeMessage< ::mozilla::layers::layerscope::DrawPacket >(Arena* arena) { + return Arena::CreateInternal< ::mozilla::layers::layerscope::DrawPacket >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::layers::layerscope::Packet* Arena::CreateMaybeMessage< ::mozilla::layers::layerscope::Packet >(Arena* arena) { + return Arena::CreateInternal< ::mozilla::layers::layerscope::Packet >(arena); +} +template<> PROTOBUF_NOINLINE ::mozilla::layers::layerscope::CommandPacket* Arena::CreateMaybeMessage< ::mozilla::layers::layerscope::CommandPacket >(Arena* arena) { + return Arena::CreateInternal< ::mozilla::layers::layerscope::CommandPacket >(arena); +} +PROTOBUF_NAMESPACE_CLOSE + +// @@protoc_insertion_point(global_scope) +#include diff --git a/gfx/layers/protobuf/LayerScopePacket.pb.h b/gfx/layers/protobuf/LayerScopePacket.pb.h new file mode 100644 index 0000000000..486c0fbc6c --- /dev/null +++ b/gfx/layers/protobuf/LayerScopePacket.pb.h @@ -0,0 +1,7833 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: LayerScopePacket.proto + +#ifndef GOOGLE_PROTOBUF_INCLUDED_LayerScopePacket_2eproto +#define GOOGLE_PROTOBUF_INCLUDED_LayerScopePacket_2eproto + +#include +#include + +#include +#if PROTOBUF_VERSION < 3011000 +#error This file was generated by a newer version of protoc which is +#error incompatible with your Protocol Buffer headers. Please update +#error your headers. +#endif +#if 3011004 < PROTOBUF_MIN_PROTOC_VERSION +#error This file was generated by an older version of protoc which is +#error incompatible with your Protocol Buffer headers. Please +#error regenerate this file with a newer version of protoc. +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // IWYU pragma: export +#include // IWYU pragma: export +#include +// @@protoc_insertion_point(includes) +#include +#define PROTOBUF_INTERNAL_EXPORT_LayerScopePacket_2eproto +PROTOBUF_NAMESPACE_OPEN +namespace internal { +class AnyMetadata; +} // namespace internal +PROTOBUF_NAMESPACE_CLOSE + +// Internal implementation detail -- do not use these members. +struct TableStruct_LayerScopePacket_2eproto { + static const ::PROTOBUF_NAMESPACE_ID::internal::ParseTableField entries[] + PROTOBUF_SECTION_VARIABLE(protodesc_cold); + static const ::PROTOBUF_NAMESPACE_ID::internal::AuxillaryParseTableField aux[] + PROTOBUF_SECTION_VARIABLE(protodesc_cold); + static const ::PROTOBUF_NAMESPACE_ID::internal::ParseTable schema[19] + PROTOBUF_SECTION_VARIABLE(protodesc_cold); + static const ::PROTOBUF_NAMESPACE_ID::internal::FieldMetadata field_metadata[]; + static const ::PROTOBUF_NAMESPACE_ID::internal::SerializationTable serialization_table[]; + static const ::PROTOBUF_NAMESPACE_ID::uint32 offsets[]; +}; +namespace mozilla { +namespace layers { +namespace layerscope { +class ColorPacket; +class ColorPacketDefaultTypeInternal; +extern ColorPacketDefaultTypeInternal _ColorPacket_default_instance_; +class CommandPacket; +class CommandPacketDefaultTypeInternal; +extern CommandPacketDefaultTypeInternal _CommandPacket_default_instance_; +class DrawPacket; +class DrawPacketDefaultTypeInternal; +extern DrawPacketDefaultTypeInternal _DrawPacket_default_instance_; +class DrawPacket_Rect; +class DrawPacket_RectDefaultTypeInternal; +extern DrawPacket_RectDefaultTypeInternal _DrawPacket_Rect_default_instance_; +class FramePacket; +class FramePacketDefaultTypeInternal; +extern FramePacketDefaultTypeInternal _FramePacket_default_instance_; +class LayersPacket; +class LayersPacketDefaultTypeInternal; +extern LayersPacketDefaultTypeInternal _LayersPacket_default_instance_; +class LayersPacket_Layer; +class LayersPacket_LayerDefaultTypeInternal; +extern LayersPacket_LayerDefaultTypeInternal _LayersPacket_Layer_default_instance_; +class LayersPacket_Layer_Matrix; +class LayersPacket_Layer_MatrixDefaultTypeInternal; +extern LayersPacket_Layer_MatrixDefaultTypeInternal _LayersPacket_Layer_Matrix_default_instance_; +class LayersPacket_Layer_Rect; +class LayersPacket_Layer_RectDefaultTypeInternal; +extern LayersPacket_Layer_RectDefaultTypeInternal _LayersPacket_Layer_Rect_default_instance_; +class LayersPacket_Layer_Region; +class LayersPacket_Layer_RegionDefaultTypeInternal; +extern LayersPacket_Layer_RegionDefaultTypeInternal _LayersPacket_Layer_Region_default_instance_; +class LayersPacket_Layer_Shadow; +class LayersPacket_Layer_ShadowDefaultTypeInternal; +extern LayersPacket_Layer_ShadowDefaultTypeInternal _LayersPacket_Layer_Shadow_default_instance_; +class LayersPacket_Layer_Size; +class LayersPacket_Layer_SizeDefaultTypeInternal; +extern LayersPacket_Layer_SizeDefaultTypeInternal _LayersPacket_Layer_Size_default_instance_; +class MetaPacket; +class MetaPacketDefaultTypeInternal; +extern MetaPacketDefaultTypeInternal _MetaPacket_default_instance_; +class Packet; +class PacketDefaultTypeInternal; +extern PacketDefaultTypeInternal _Packet_default_instance_; +class TexturePacket; +class TexturePacketDefaultTypeInternal; +extern TexturePacketDefaultTypeInternal _TexturePacket_default_instance_; +class TexturePacket_EffectMask; +class TexturePacket_EffectMaskDefaultTypeInternal; +extern TexturePacket_EffectMaskDefaultTypeInternal _TexturePacket_EffectMask_default_instance_; +class TexturePacket_Matrix; +class TexturePacket_MatrixDefaultTypeInternal; +extern TexturePacket_MatrixDefaultTypeInternal _TexturePacket_Matrix_default_instance_; +class TexturePacket_Rect; +class TexturePacket_RectDefaultTypeInternal; +extern TexturePacket_RectDefaultTypeInternal _TexturePacket_Rect_default_instance_; +class TexturePacket_Size; +class TexturePacket_SizeDefaultTypeInternal; +extern TexturePacket_SizeDefaultTypeInternal _TexturePacket_Size_default_instance_; +} // namespace layerscope +} // namespace layers +} // namespace mozilla +PROTOBUF_NAMESPACE_OPEN +template<> ::mozilla::layers::layerscope::ColorPacket* Arena::CreateMaybeMessage<::mozilla::layers::layerscope::ColorPacket>(Arena*); +template<> ::mozilla::layers::layerscope::CommandPacket* Arena::CreateMaybeMessage<::mozilla::layers::layerscope::CommandPacket>(Arena*); +template<> ::mozilla::layers::layerscope::DrawPacket* Arena::CreateMaybeMessage<::mozilla::layers::layerscope::DrawPacket>(Arena*); +template<> ::mozilla::layers::layerscope::DrawPacket_Rect* Arena::CreateMaybeMessage<::mozilla::layers::layerscope::DrawPacket_Rect>(Arena*); +template<> ::mozilla::layers::layerscope::FramePacket* Arena::CreateMaybeMessage<::mozilla::layers::layerscope::FramePacket>(Arena*); +template<> ::mozilla::layers::layerscope::LayersPacket* Arena::CreateMaybeMessage<::mozilla::layers::layerscope::LayersPacket>(Arena*); +template<> ::mozilla::layers::layerscope::LayersPacket_Layer* Arena::CreateMaybeMessage<::mozilla::layers::layerscope::LayersPacket_Layer>(Arena*); +template<> ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix* Arena::CreateMaybeMessage<::mozilla::layers::layerscope::LayersPacket_Layer_Matrix>(Arena*); +template<> ::mozilla::layers::layerscope::LayersPacket_Layer_Rect* Arena::CreateMaybeMessage<::mozilla::layers::layerscope::LayersPacket_Layer_Rect>(Arena*); +template<> ::mozilla::layers::layerscope::LayersPacket_Layer_Region* Arena::CreateMaybeMessage<::mozilla::layers::layerscope::LayersPacket_Layer_Region>(Arena*); +template<> ::mozilla::layers::layerscope::LayersPacket_Layer_Shadow* Arena::CreateMaybeMessage<::mozilla::layers::layerscope::LayersPacket_Layer_Shadow>(Arena*); +template<> ::mozilla::layers::layerscope::LayersPacket_Layer_Size* Arena::CreateMaybeMessage<::mozilla::layers::layerscope::LayersPacket_Layer_Size>(Arena*); +template<> ::mozilla::layers::layerscope::MetaPacket* Arena::CreateMaybeMessage<::mozilla::layers::layerscope::MetaPacket>(Arena*); +template<> ::mozilla::layers::layerscope::Packet* Arena::CreateMaybeMessage<::mozilla::layers::layerscope::Packet>(Arena*); +template<> ::mozilla::layers::layerscope::TexturePacket* Arena::CreateMaybeMessage<::mozilla::layers::layerscope::TexturePacket>(Arena*); +template<> ::mozilla::layers::layerscope::TexturePacket_EffectMask* Arena::CreateMaybeMessage<::mozilla::layers::layerscope::TexturePacket_EffectMask>(Arena*); +template<> ::mozilla::layers::layerscope::TexturePacket_Matrix* Arena::CreateMaybeMessage<::mozilla::layers::layerscope::TexturePacket_Matrix>(Arena*); +template<> ::mozilla::layers::layerscope::TexturePacket_Rect* Arena::CreateMaybeMessage<::mozilla::layers::layerscope::TexturePacket_Rect>(Arena*); +template<> ::mozilla::layers::layerscope::TexturePacket_Size* Arena::CreateMaybeMessage<::mozilla::layers::layerscope::TexturePacket_Size>(Arena*); +PROTOBUF_NAMESPACE_CLOSE +namespace mozilla { +namespace layers { +namespace layerscope { + +enum TexturePacket_Filter : int { + TexturePacket_Filter_GOOD = 0, + TexturePacket_Filter_LINEAR = 1, + TexturePacket_Filter_POINT = 2 +}; +bool TexturePacket_Filter_IsValid(int value); +constexpr TexturePacket_Filter TexturePacket_Filter_Filter_MIN = TexturePacket_Filter_GOOD; +constexpr TexturePacket_Filter TexturePacket_Filter_Filter_MAX = TexturePacket_Filter_POINT; +constexpr int TexturePacket_Filter_Filter_ARRAYSIZE = TexturePacket_Filter_Filter_MAX + 1; + +const std::string& TexturePacket_Filter_Name(TexturePacket_Filter value); +template +inline const std::string& TexturePacket_Filter_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function TexturePacket_Filter_Name."); + return TexturePacket_Filter_Name(static_cast(enum_t_value)); +} +bool TexturePacket_Filter_Parse( + const std::string& name, TexturePacket_Filter* value); +enum LayersPacket_Layer_LayerType : int { + LayersPacket_Layer_LayerType_UnknownLayer = 0, + LayersPacket_Layer_LayerType_LayerManager = 1, + LayersPacket_Layer_LayerType_ContainerLayer = 2, + LayersPacket_Layer_LayerType_PaintedLayer = 3, + LayersPacket_Layer_LayerType_CanvasLayer = 4, + LayersPacket_Layer_LayerType_ImageLayer = 5, + LayersPacket_Layer_LayerType_ColorLayer = 6, + LayersPacket_Layer_LayerType_RefLayer = 8, + LayersPacket_Layer_LayerType_ReadbackLayer = 9, + LayersPacket_Layer_LayerType_DisplayItemLayer = 10 +}; +bool LayersPacket_Layer_LayerType_IsValid(int value); +constexpr LayersPacket_Layer_LayerType LayersPacket_Layer_LayerType_LayerType_MIN = LayersPacket_Layer_LayerType_UnknownLayer; +constexpr LayersPacket_Layer_LayerType LayersPacket_Layer_LayerType_LayerType_MAX = LayersPacket_Layer_LayerType_DisplayItemLayer; +constexpr int LayersPacket_Layer_LayerType_LayerType_ARRAYSIZE = LayersPacket_Layer_LayerType_LayerType_MAX + 1; + +const std::string& LayersPacket_Layer_LayerType_Name(LayersPacket_Layer_LayerType value); +template +inline const std::string& LayersPacket_Layer_LayerType_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function LayersPacket_Layer_LayerType_Name."); + return LayersPacket_Layer_LayerType_Name(static_cast(enum_t_value)); +} +bool LayersPacket_Layer_LayerType_Parse( + const std::string& name, LayersPacket_Layer_LayerType* value); +enum LayersPacket_Layer_ScrollingDirect : int { + LayersPacket_Layer_ScrollingDirect_VERTICAL = 1, + LayersPacket_Layer_ScrollingDirect_HORIZONTAL = 2 +}; +bool LayersPacket_Layer_ScrollingDirect_IsValid(int value); +constexpr LayersPacket_Layer_ScrollingDirect LayersPacket_Layer_ScrollingDirect_ScrollingDirect_MIN = LayersPacket_Layer_ScrollingDirect_VERTICAL; +constexpr LayersPacket_Layer_ScrollingDirect LayersPacket_Layer_ScrollingDirect_ScrollingDirect_MAX = LayersPacket_Layer_ScrollingDirect_HORIZONTAL; +constexpr int LayersPacket_Layer_ScrollingDirect_ScrollingDirect_ARRAYSIZE = LayersPacket_Layer_ScrollingDirect_ScrollingDirect_MAX + 1; + +const std::string& LayersPacket_Layer_ScrollingDirect_Name(LayersPacket_Layer_ScrollingDirect value); +template +inline const std::string& LayersPacket_Layer_ScrollingDirect_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function LayersPacket_Layer_ScrollingDirect_Name."); + return LayersPacket_Layer_ScrollingDirect_Name(static_cast(enum_t_value)); +} +bool LayersPacket_Layer_ScrollingDirect_Parse( + const std::string& name, LayersPacket_Layer_ScrollingDirect* value); +enum LayersPacket_Layer_Filter : int { + LayersPacket_Layer_Filter_FILTER_FAST = 0, + LayersPacket_Layer_Filter_FILTER_GOOD = 1, + LayersPacket_Layer_Filter_FILTER_BEST = 2, + LayersPacket_Layer_Filter_FILTER_NEAREST = 3, + LayersPacket_Layer_Filter_FILTER_BILINEAR = 4, + LayersPacket_Layer_Filter_FILTER_GAUSSIAN = 5, + LayersPacket_Layer_Filter_FILTER_SENTINEL = 6, + LayersPacket_Layer_Filter_FILTER_LINEAR = 7, + LayersPacket_Layer_Filter_FILTER_POINT = 8 +}; +bool LayersPacket_Layer_Filter_IsValid(int value); +constexpr LayersPacket_Layer_Filter LayersPacket_Layer_Filter_Filter_MIN = LayersPacket_Layer_Filter_FILTER_FAST; +constexpr LayersPacket_Layer_Filter LayersPacket_Layer_Filter_Filter_MAX = LayersPacket_Layer_Filter_FILTER_POINT; +constexpr int LayersPacket_Layer_Filter_Filter_ARRAYSIZE = LayersPacket_Layer_Filter_Filter_MAX + 1; + +const std::string& LayersPacket_Layer_Filter_Name(LayersPacket_Layer_Filter value); +template +inline const std::string& LayersPacket_Layer_Filter_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function LayersPacket_Layer_Filter_Name."); + return LayersPacket_Layer_Filter_Name(static_cast(enum_t_value)); +} +bool LayersPacket_Layer_Filter_Parse( + const std::string& name, LayersPacket_Layer_Filter* value); +enum Packet_DataType : int { + Packet_DataType_FRAMESTART = 1, + Packet_DataType_FRAMEEND = 2, + Packet_DataType_COLOR = 3, + Packet_DataType_TEXTURE = 4, + Packet_DataType_LAYERS = 5, + Packet_DataType_META = 6, + Packet_DataType_DRAW = 7 +}; +bool Packet_DataType_IsValid(int value); +constexpr Packet_DataType Packet_DataType_DataType_MIN = Packet_DataType_FRAMESTART; +constexpr Packet_DataType Packet_DataType_DataType_MAX = Packet_DataType_DRAW; +constexpr int Packet_DataType_DataType_ARRAYSIZE = Packet_DataType_DataType_MAX + 1; + +const std::string& Packet_DataType_Name(Packet_DataType value); +template +inline const std::string& Packet_DataType_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function Packet_DataType_Name."); + return Packet_DataType_Name(static_cast(enum_t_value)); +} +bool Packet_DataType_Parse( + const std::string& name, Packet_DataType* value); +enum CommandPacket_CmdType : int { + CommandPacket_CmdType_NO_OP = 0, + CommandPacket_CmdType_LAYERS_TREE = 1, + CommandPacket_CmdType_LAYERS_BUFFER = 2 +}; +bool CommandPacket_CmdType_IsValid(int value); +constexpr CommandPacket_CmdType CommandPacket_CmdType_CmdType_MIN = CommandPacket_CmdType_NO_OP; +constexpr CommandPacket_CmdType CommandPacket_CmdType_CmdType_MAX = CommandPacket_CmdType_LAYERS_BUFFER; +constexpr int CommandPacket_CmdType_CmdType_ARRAYSIZE = CommandPacket_CmdType_CmdType_MAX + 1; + +const std::string& CommandPacket_CmdType_Name(CommandPacket_CmdType value); +template +inline const std::string& CommandPacket_CmdType_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function CommandPacket_CmdType_Name."); + return CommandPacket_CmdType_Name(static_cast(enum_t_value)); +} +bool CommandPacket_CmdType_Parse( + const std::string& name, CommandPacket_CmdType* value); +// =================================================================== + +class FramePacket : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.layers.layerscope.FramePacket) */ { + public: + FramePacket(); + virtual ~FramePacket(); + + FramePacket(const FramePacket& from); + FramePacket(FramePacket&& from) noexcept + : FramePacket() { + *this = ::std::move(from); + } + + inline FramePacket& operator=(const FramePacket& from) { + CopyFrom(from); + return *this; + } + inline FramePacket& operator=(FramePacket&& from) noexcept { + if (GetArenaNoVirtual() == from.GetArenaNoVirtual()) { + if (this != &from) InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const FramePacket& default_instance(); + + static void InitAsDefaultInstance(); // FOR INTERNAL USE ONLY + static inline const FramePacket* internal_default_instance() { + return reinterpret_cast( + &_FramePacket_default_instance_); + } + static constexpr int kIndexInFileMessages = + 0; + + friend void swap(FramePacket& a, FramePacket& b) { + a.Swap(&b); + } + inline void Swap(FramePacket* other) { + if (other == this) return; + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + inline FramePacket* New() const final { + return CreateMaybeMessage(nullptr); + } + + FramePacket* New(::PROTOBUF_NAMESPACE_ID::Arena* arena) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) + final; + void CopyFrom(const FramePacket& from); + void MergeFrom(const FramePacket& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + ::PROTOBUF_NAMESPACE_ID::uint8* _InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + void DiscardUnknownFields(); + int GetCachedSize() const final { return _cached_size_.Get(); } + + private: + inline void SharedCtor(); + inline void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(FramePacket* other); + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.layers.layerscope.FramePacket"; + } + private: + inline ::PROTOBUF_NAMESPACE_ID::Arena* GetArenaNoVirtual() const { + return nullptr; + } + inline void* MaybeArenaPtr() const { + return nullptr; + } + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kValueFieldNumber = 1, + kScaleFieldNumber = 2, + }; + // optional uint64 value = 1; + bool has_value() const; + private: + bool _internal_has_value() const; + public: + void clear_value(); + ::PROTOBUF_NAMESPACE_ID::uint64 value() const; + void set_value(::PROTOBUF_NAMESPACE_ID::uint64 value); + private: + ::PROTOBUF_NAMESPACE_ID::uint64 _internal_value() const; + void _internal_set_value(::PROTOBUF_NAMESPACE_ID::uint64 value); + public: + + // optional float scale = 2; + bool has_scale() const; + private: + bool _internal_has_scale() const; + public: + void clear_scale(); + float scale() const; + void set_scale(float value); + private: + float _internal_scale() const; + void _internal_set_scale(float value); + public: + + // @@protoc_insertion_point(class_scope:mozilla.layers.layerscope.FramePacket) + private: + class _Internal; + + ::PROTOBUF_NAMESPACE_ID::internal::InternalMetadataWithArenaLite _internal_metadata_; + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::uint64 value_; + float scale_; + friend struct ::TableStruct_LayerScopePacket_2eproto; +}; +// ------------------------------------------------------------------- + +class ColorPacket : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.layers.layerscope.ColorPacket) */ { + public: + ColorPacket(); + virtual ~ColorPacket(); + + ColorPacket(const ColorPacket& from); + ColorPacket(ColorPacket&& from) noexcept + : ColorPacket() { + *this = ::std::move(from); + } + + inline ColorPacket& operator=(const ColorPacket& from) { + CopyFrom(from); + return *this; + } + inline ColorPacket& operator=(ColorPacket&& from) noexcept { + if (GetArenaNoVirtual() == from.GetArenaNoVirtual()) { + if (this != &from) InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const ColorPacket& default_instance(); + + static void InitAsDefaultInstance(); // FOR INTERNAL USE ONLY + static inline const ColorPacket* internal_default_instance() { + return reinterpret_cast( + &_ColorPacket_default_instance_); + } + static constexpr int kIndexInFileMessages = + 1; + + friend void swap(ColorPacket& a, ColorPacket& b) { + a.Swap(&b); + } + inline void Swap(ColorPacket* other) { + if (other == this) return; + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + inline ColorPacket* New() const final { + return CreateMaybeMessage(nullptr); + } + + ColorPacket* New(::PROTOBUF_NAMESPACE_ID::Arena* arena) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) + final; + void CopyFrom(const ColorPacket& from); + void MergeFrom(const ColorPacket& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + ::PROTOBUF_NAMESPACE_ID::uint8* _InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + void DiscardUnknownFields(); + int GetCachedSize() const final { return _cached_size_.Get(); } + + private: + inline void SharedCtor(); + inline void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(ColorPacket* other); + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.layers.layerscope.ColorPacket"; + } + private: + inline ::PROTOBUF_NAMESPACE_ID::Arena* GetArenaNoVirtual() const { + return nullptr; + } + inline void* MaybeArenaPtr() const { + return nullptr; + } + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kLayerrefFieldNumber = 1, + kWidthFieldNumber = 2, + kHeightFieldNumber = 3, + kColorFieldNumber = 4, + }; + // required uint64 layerref = 1; + bool has_layerref() const; + private: + bool _internal_has_layerref() const; + public: + void clear_layerref(); + ::PROTOBUF_NAMESPACE_ID::uint64 layerref() const; + void set_layerref(::PROTOBUF_NAMESPACE_ID::uint64 value); + private: + ::PROTOBUF_NAMESPACE_ID::uint64 _internal_layerref() const; + void _internal_set_layerref(::PROTOBUF_NAMESPACE_ID::uint64 value); + public: + + // optional uint32 width = 2; + bool has_width() const; + private: + bool _internal_has_width() const; + public: + void clear_width(); + ::PROTOBUF_NAMESPACE_ID::uint32 width() const; + void set_width(::PROTOBUF_NAMESPACE_ID::uint32 value); + private: + ::PROTOBUF_NAMESPACE_ID::uint32 _internal_width() const; + void _internal_set_width(::PROTOBUF_NAMESPACE_ID::uint32 value); + public: + + // optional uint32 height = 3; + bool has_height() const; + private: + bool _internal_has_height() const; + public: + void clear_height(); + ::PROTOBUF_NAMESPACE_ID::uint32 height() const; + void set_height(::PROTOBUF_NAMESPACE_ID::uint32 value); + private: + ::PROTOBUF_NAMESPACE_ID::uint32 _internal_height() const; + void _internal_set_height(::PROTOBUF_NAMESPACE_ID::uint32 value); + public: + + // optional uint32 color = 4; + bool has_color() const; + private: + bool _internal_has_color() const; + public: + void clear_color(); + ::PROTOBUF_NAMESPACE_ID::uint32 color() const; + void set_color(::PROTOBUF_NAMESPACE_ID::uint32 value); + private: + ::PROTOBUF_NAMESPACE_ID::uint32 _internal_color() const; + void _internal_set_color(::PROTOBUF_NAMESPACE_ID::uint32 value); + public: + + // @@protoc_insertion_point(class_scope:mozilla.layers.layerscope.ColorPacket) + private: + class _Internal; + + ::PROTOBUF_NAMESPACE_ID::internal::InternalMetadataWithArenaLite _internal_metadata_; + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::uint64 layerref_; + ::PROTOBUF_NAMESPACE_ID::uint32 width_; + ::PROTOBUF_NAMESPACE_ID::uint32 height_; + ::PROTOBUF_NAMESPACE_ID::uint32 color_; + friend struct ::TableStruct_LayerScopePacket_2eproto; +}; +// ------------------------------------------------------------------- + +class TexturePacket_Rect : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.layers.layerscope.TexturePacket.Rect) */ { + public: + TexturePacket_Rect(); + virtual ~TexturePacket_Rect(); + + TexturePacket_Rect(const TexturePacket_Rect& from); + TexturePacket_Rect(TexturePacket_Rect&& from) noexcept + : TexturePacket_Rect() { + *this = ::std::move(from); + } + + inline TexturePacket_Rect& operator=(const TexturePacket_Rect& from) { + CopyFrom(from); + return *this; + } + inline TexturePacket_Rect& operator=(TexturePacket_Rect&& from) noexcept { + if (GetArenaNoVirtual() == from.GetArenaNoVirtual()) { + if (this != &from) InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const TexturePacket_Rect& default_instance(); + + static void InitAsDefaultInstance(); // FOR INTERNAL USE ONLY + static inline const TexturePacket_Rect* internal_default_instance() { + return reinterpret_cast( + &_TexturePacket_Rect_default_instance_); + } + static constexpr int kIndexInFileMessages = + 2; + + friend void swap(TexturePacket_Rect& a, TexturePacket_Rect& b) { + a.Swap(&b); + } + inline void Swap(TexturePacket_Rect* other) { + if (other == this) return; + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + inline TexturePacket_Rect* New() const final { + return CreateMaybeMessage(nullptr); + } + + TexturePacket_Rect* New(::PROTOBUF_NAMESPACE_ID::Arena* arena) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) + final; + void CopyFrom(const TexturePacket_Rect& from); + void MergeFrom(const TexturePacket_Rect& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + ::PROTOBUF_NAMESPACE_ID::uint8* _InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + void DiscardUnknownFields(); + int GetCachedSize() const final { return _cached_size_.Get(); } + + private: + inline void SharedCtor(); + inline void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(TexturePacket_Rect* other); + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.layers.layerscope.TexturePacket.Rect"; + } + private: + inline ::PROTOBUF_NAMESPACE_ID::Arena* GetArenaNoVirtual() const { + return nullptr; + } + inline void* MaybeArenaPtr() const { + return nullptr; + } + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kXFieldNumber = 1, + kYFieldNumber = 2, + kWFieldNumber = 3, + kHFieldNumber = 4, + }; + // optional float x = 1; + bool has_x() const; + private: + bool _internal_has_x() const; + public: + void clear_x(); + float x() const; + void set_x(float value); + private: + float _internal_x() const; + void _internal_set_x(float value); + public: + + // optional float y = 2; + bool has_y() const; + private: + bool _internal_has_y() const; + public: + void clear_y(); + float y() const; + void set_y(float value); + private: + float _internal_y() const; + void _internal_set_y(float value); + public: + + // optional float w = 3; + bool has_w() const; + private: + bool _internal_has_w() const; + public: + void clear_w(); + float w() const; + void set_w(float value); + private: + float _internal_w() const; + void _internal_set_w(float value); + public: + + // optional float h = 4; + bool has_h() const; + private: + bool _internal_has_h() const; + public: + void clear_h(); + float h() const; + void set_h(float value); + private: + float _internal_h() const; + void _internal_set_h(float value); + public: + + // @@protoc_insertion_point(class_scope:mozilla.layers.layerscope.TexturePacket.Rect) + private: + class _Internal; + + ::PROTOBUF_NAMESPACE_ID::internal::InternalMetadataWithArenaLite _internal_metadata_; + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + float x_; + float y_; + float w_; + float h_; + friend struct ::TableStruct_LayerScopePacket_2eproto; +}; +// ------------------------------------------------------------------- + +class TexturePacket_Size : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.layers.layerscope.TexturePacket.Size) */ { + public: + TexturePacket_Size(); + virtual ~TexturePacket_Size(); + + TexturePacket_Size(const TexturePacket_Size& from); + TexturePacket_Size(TexturePacket_Size&& from) noexcept + : TexturePacket_Size() { + *this = ::std::move(from); + } + + inline TexturePacket_Size& operator=(const TexturePacket_Size& from) { + CopyFrom(from); + return *this; + } + inline TexturePacket_Size& operator=(TexturePacket_Size&& from) noexcept { + if (GetArenaNoVirtual() == from.GetArenaNoVirtual()) { + if (this != &from) InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const TexturePacket_Size& default_instance(); + + static void InitAsDefaultInstance(); // FOR INTERNAL USE ONLY + static inline const TexturePacket_Size* internal_default_instance() { + return reinterpret_cast( + &_TexturePacket_Size_default_instance_); + } + static constexpr int kIndexInFileMessages = + 3; + + friend void swap(TexturePacket_Size& a, TexturePacket_Size& b) { + a.Swap(&b); + } + inline void Swap(TexturePacket_Size* other) { + if (other == this) return; + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + inline TexturePacket_Size* New() const final { + return CreateMaybeMessage(nullptr); + } + + TexturePacket_Size* New(::PROTOBUF_NAMESPACE_ID::Arena* arena) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) + final; + void CopyFrom(const TexturePacket_Size& from); + void MergeFrom(const TexturePacket_Size& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + ::PROTOBUF_NAMESPACE_ID::uint8* _InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + void DiscardUnknownFields(); + int GetCachedSize() const final { return _cached_size_.Get(); } + + private: + inline void SharedCtor(); + inline void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(TexturePacket_Size* other); + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.layers.layerscope.TexturePacket.Size"; + } + private: + inline ::PROTOBUF_NAMESPACE_ID::Arena* GetArenaNoVirtual() const { + return nullptr; + } + inline void* MaybeArenaPtr() const { + return nullptr; + } + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kWFieldNumber = 1, + kHFieldNumber = 2, + }; + // optional int32 w = 1; + bool has_w() const; + private: + bool _internal_has_w() const; + public: + void clear_w(); + ::PROTOBUF_NAMESPACE_ID::int32 w() const; + void set_w(::PROTOBUF_NAMESPACE_ID::int32 value); + private: + ::PROTOBUF_NAMESPACE_ID::int32 _internal_w() const; + void _internal_set_w(::PROTOBUF_NAMESPACE_ID::int32 value); + public: + + // optional int32 h = 2; + bool has_h() const; + private: + bool _internal_has_h() const; + public: + void clear_h(); + ::PROTOBUF_NAMESPACE_ID::int32 h() const; + void set_h(::PROTOBUF_NAMESPACE_ID::int32 value); + private: + ::PROTOBUF_NAMESPACE_ID::int32 _internal_h() const; + void _internal_set_h(::PROTOBUF_NAMESPACE_ID::int32 value); + public: + + // @@protoc_insertion_point(class_scope:mozilla.layers.layerscope.TexturePacket.Size) + private: + class _Internal; + + ::PROTOBUF_NAMESPACE_ID::internal::InternalMetadataWithArenaLite _internal_metadata_; + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::int32 w_; + ::PROTOBUF_NAMESPACE_ID::int32 h_; + friend struct ::TableStruct_LayerScopePacket_2eproto; +}; +// ------------------------------------------------------------------- + +class TexturePacket_Matrix : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.layers.layerscope.TexturePacket.Matrix) */ { + public: + TexturePacket_Matrix(); + virtual ~TexturePacket_Matrix(); + + TexturePacket_Matrix(const TexturePacket_Matrix& from); + TexturePacket_Matrix(TexturePacket_Matrix&& from) noexcept + : TexturePacket_Matrix() { + *this = ::std::move(from); + } + + inline TexturePacket_Matrix& operator=(const TexturePacket_Matrix& from) { + CopyFrom(from); + return *this; + } + inline TexturePacket_Matrix& operator=(TexturePacket_Matrix&& from) noexcept { + if (GetArenaNoVirtual() == from.GetArenaNoVirtual()) { + if (this != &from) InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const TexturePacket_Matrix& default_instance(); + + static void InitAsDefaultInstance(); // FOR INTERNAL USE ONLY + static inline const TexturePacket_Matrix* internal_default_instance() { + return reinterpret_cast( + &_TexturePacket_Matrix_default_instance_); + } + static constexpr int kIndexInFileMessages = + 4; + + friend void swap(TexturePacket_Matrix& a, TexturePacket_Matrix& b) { + a.Swap(&b); + } + inline void Swap(TexturePacket_Matrix* other) { + if (other == this) return; + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + inline TexturePacket_Matrix* New() const final { + return CreateMaybeMessage(nullptr); + } + + TexturePacket_Matrix* New(::PROTOBUF_NAMESPACE_ID::Arena* arena) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) + final; + void CopyFrom(const TexturePacket_Matrix& from); + void MergeFrom(const TexturePacket_Matrix& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + ::PROTOBUF_NAMESPACE_ID::uint8* _InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + void DiscardUnknownFields(); + int GetCachedSize() const final { return _cached_size_.Get(); } + + private: + inline void SharedCtor(); + inline void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(TexturePacket_Matrix* other); + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.layers.layerscope.TexturePacket.Matrix"; + } + private: + inline ::PROTOBUF_NAMESPACE_ID::Arena* GetArenaNoVirtual() const { + return nullptr; + } + inline void* MaybeArenaPtr() const { + return nullptr; + } + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kMFieldNumber = 3, + kIs2DFieldNumber = 1, + kIsIdFieldNumber = 2, + }; + // repeated float m = 3; + int m_size() const; + private: + int _internal_m_size() const; + public: + void clear_m(); + private: + float _internal_m(int index) const; + const ::PROTOBUF_NAMESPACE_ID::RepeatedField< float >& + _internal_m() const; + void _internal_add_m(float value); + ::PROTOBUF_NAMESPACE_ID::RepeatedField< float >* + _internal_mutable_m(); + public: + float m(int index) const; + void set_m(int index, float value); + void add_m(float value); + const ::PROTOBUF_NAMESPACE_ID::RepeatedField< float >& + m() const; + ::PROTOBUF_NAMESPACE_ID::RepeatedField< float >* + mutable_m(); + + // optional bool is2D = 1; + bool has_is2d() const; + private: + bool _internal_has_is2d() const; + public: + void clear_is2d(); + bool is2d() const; + void set_is2d(bool value); + private: + bool _internal_is2d() const; + void _internal_set_is2d(bool value); + public: + + // optional bool isId = 2; + bool has_isid() const; + private: + bool _internal_has_isid() const; + public: + void clear_isid(); + bool isid() const; + void set_isid(bool value); + private: + bool _internal_isid() const; + void _internal_set_isid(bool value); + public: + + // @@protoc_insertion_point(class_scope:mozilla.layers.layerscope.TexturePacket.Matrix) + private: + class _Internal; + + ::PROTOBUF_NAMESPACE_ID::internal::InternalMetadataWithArenaLite _internal_metadata_; + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::RepeatedField< float > m_; + bool is2d_; + bool isid_; + friend struct ::TableStruct_LayerScopePacket_2eproto; +}; +// ------------------------------------------------------------------- + +class TexturePacket_EffectMask : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.layers.layerscope.TexturePacket.EffectMask) */ { + public: + TexturePacket_EffectMask(); + virtual ~TexturePacket_EffectMask(); + + TexturePacket_EffectMask(const TexturePacket_EffectMask& from); + TexturePacket_EffectMask(TexturePacket_EffectMask&& from) noexcept + : TexturePacket_EffectMask() { + *this = ::std::move(from); + } + + inline TexturePacket_EffectMask& operator=(const TexturePacket_EffectMask& from) { + CopyFrom(from); + return *this; + } + inline TexturePacket_EffectMask& operator=(TexturePacket_EffectMask&& from) noexcept { + if (GetArenaNoVirtual() == from.GetArenaNoVirtual()) { + if (this != &from) InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const TexturePacket_EffectMask& default_instance(); + + static void InitAsDefaultInstance(); // FOR INTERNAL USE ONLY + static inline const TexturePacket_EffectMask* internal_default_instance() { + return reinterpret_cast( + &_TexturePacket_EffectMask_default_instance_); + } + static constexpr int kIndexInFileMessages = + 5; + + friend void swap(TexturePacket_EffectMask& a, TexturePacket_EffectMask& b) { + a.Swap(&b); + } + inline void Swap(TexturePacket_EffectMask* other) { + if (other == this) return; + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + inline TexturePacket_EffectMask* New() const final { + return CreateMaybeMessage(nullptr); + } + + TexturePacket_EffectMask* New(::PROTOBUF_NAMESPACE_ID::Arena* arena) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) + final; + void CopyFrom(const TexturePacket_EffectMask& from); + void MergeFrom(const TexturePacket_EffectMask& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + ::PROTOBUF_NAMESPACE_ID::uint8* _InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + void DiscardUnknownFields(); + int GetCachedSize() const final { return _cached_size_.Get(); } + + private: + inline void SharedCtor(); + inline void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(TexturePacket_EffectMask* other); + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.layers.layerscope.TexturePacket.EffectMask"; + } + private: + inline ::PROTOBUF_NAMESPACE_ID::Arena* GetArenaNoVirtual() const { + return nullptr; + } + inline void* MaybeArenaPtr() const { + return nullptr; + } + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kMSizeFieldNumber = 2, + kMMaskTransformFieldNumber = 3, + kMIs3DFieldNumber = 1, + }; + // optional .mozilla.layers.layerscope.TexturePacket.Size mSize = 2; + bool has_msize() const; + private: + bool _internal_has_msize() const; + public: + void clear_msize(); + const ::mozilla::layers::layerscope::TexturePacket_Size& msize() const; + ::mozilla::layers::layerscope::TexturePacket_Size* release_msize(); + ::mozilla::layers::layerscope::TexturePacket_Size* mutable_msize(); + void set_allocated_msize(::mozilla::layers::layerscope::TexturePacket_Size* msize); + private: + const ::mozilla::layers::layerscope::TexturePacket_Size& _internal_msize() const; + ::mozilla::layers::layerscope::TexturePacket_Size* _internal_mutable_msize(); + public: + + // optional .mozilla.layers.layerscope.TexturePacket.Matrix mMaskTransform = 3; + bool has_mmasktransform() const; + private: + bool _internal_has_mmasktransform() const; + public: + void clear_mmasktransform(); + const ::mozilla::layers::layerscope::TexturePacket_Matrix& mmasktransform() const; + ::mozilla::layers::layerscope::TexturePacket_Matrix* release_mmasktransform(); + ::mozilla::layers::layerscope::TexturePacket_Matrix* mutable_mmasktransform(); + void set_allocated_mmasktransform(::mozilla::layers::layerscope::TexturePacket_Matrix* mmasktransform); + private: + const ::mozilla::layers::layerscope::TexturePacket_Matrix& _internal_mmasktransform() const; + ::mozilla::layers::layerscope::TexturePacket_Matrix* _internal_mutable_mmasktransform(); + public: + + // optional bool mIs3D = 1; + bool has_mis3d() const; + private: + bool _internal_has_mis3d() const; + public: + void clear_mis3d(); + bool mis3d() const; + void set_mis3d(bool value); + private: + bool _internal_mis3d() const; + void _internal_set_mis3d(bool value); + public: + + // @@protoc_insertion_point(class_scope:mozilla.layers.layerscope.TexturePacket.EffectMask) + private: + class _Internal; + + ::PROTOBUF_NAMESPACE_ID::internal::InternalMetadataWithArenaLite _internal_metadata_; + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::mozilla::layers::layerscope::TexturePacket_Size* msize_; + ::mozilla::layers::layerscope::TexturePacket_Matrix* mmasktransform_; + bool mis3d_; + friend struct ::TableStruct_LayerScopePacket_2eproto; +}; +// ------------------------------------------------------------------- + +class TexturePacket : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.layers.layerscope.TexturePacket) */ { + public: + TexturePacket(); + virtual ~TexturePacket(); + + TexturePacket(const TexturePacket& from); + TexturePacket(TexturePacket&& from) noexcept + : TexturePacket() { + *this = ::std::move(from); + } + + inline TexturePacket& operator=(const TexturePacket& from) { + CopyFrom(from); + return *this; + } + inline TexturePacket& operator=(TexturePacket&& from) noexcept { + if (GetArenaNoVirtual() == from.GetArenaNoVirtual()) { + if (this != &from) InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const TexturePacket& default_instance(); + + static void InitAsDefaultInstance(); // FOR INTERNAL USE ONLY + static inline const TexturePacket* internal_default_instance() { + return reinterpret_cast( + &_TexturePacket_default_instance_); + } + static constexpr int kIndexInFileMessages = + 6; + + friend void swap(TexturePacket& a, TexturePacket& b) { + a.Swap(&b); + } + inline void Swap(TexturePacket* other) { + if (other == this) return; + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + inline TexturePacket* New() const final { + return CreateMaybeMessage(nullptr); + } + + TexturePacket* New(::PROTOBUF_NAMESPACE_ID::Arena* arena) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) + final; + void CopyFrom(const TexturePacket& from); + void MergeFrom(const TexturePacket& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + ::PROTOBUF_NAMESPACE_ID::uint8* _InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + void DiscardUnknownFields(); + int GetCachedSize() const final { return _cached_size_.Get(); } + + private: + inline void SharedCtor(); + inline void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(TexturePacket* other); + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.layers.layerscope.TexturePacket"; + } + private: + inline ::PROTOBUF_NAMESPACE_ID::Arena* GetArenaNoVirtual() const { + return nullptr; + } + inline void* MaybeArenaPtr() const { + return nullptr; + } + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + typedef TexturePacket_Rect Rect; + typedef TexturePacket_Size Size; + typedef TexturePacket_Matrix Matrix; + typedef TexturePacket_EffectMask EffectMask; + + typedef TexturePacket_Filter Filter; + static constexpr Filter GOOD = + TexturePacket_Filter_GOOD; + static constexpr Filter LINEAR = + TexturePacket_Filter_LINEAR; + static constexpr Filter POINT = + TexturePacket_Filter_POINT; + static inline bool Filter_IsValid(int value) { + return TexturePacket_Filter_IsValid(value); + } + static constexpr Filter Filter_MIN = + TexturePacket_Filter_Filter_MIN; + static constexpr Filter Filter_MAX = + TexturePacket_Filter_Filter_MAX; + static constexpr int Filter_ARRAYSIZE = + TexturePacket_Filter_Filter_ARRAYSIZE; + template + static inline const std::string& Filter_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function Filter_Name."); + return TexturePacket_Filter_Name(enum_t_value); + } + static inline bool Filter_Parse(const std::string& name, + Filter* value) { + return TexturePacket_Filter_Parse(name, value); + } + + // accessors ------------------------------------------------------- + + enum : int { + kDataFieldNumber = 9, + kMTextureCoordsFieldNumber = 10, + kMaskFieldNumber = 21, + kLayerrefFieldNumber = 1, + kWidthFieldNumber = 2, + kHeightFieldNumber = 3, + kStrideFieldNumber = 4, + kNameFieldNumber = 5, + kTargetFieldNumber = 6, + kDataformatFieldNumber = 7, + kGlcontextFieldNumber = 8, + kMFilterFieldNumber = 12, + kMPremultipliedFieldNumber = 11, + kIsMaskFieldNumber = 20, + }; + // optional bytes data = 9; + bool has_data() const; + private: + bool _internal_has_data() const; + public: + void clear_data(); + const std::string& data() const; + void set_data(const std::string& value); + void set_data(std::string&& value); + void set_data(const char* value); + void set_data(const void* value, size_t size); + std::string* mutable_data(); + std::string* release_data(); + void set_allocated_data(std::string* data); + private: + const std::string& _internal_data() const; + void _internal_set_data(const std::string& value); + std::string* _internal_mutable_data(); + public: + + // optional .mozilla.layers.layerscope.TexturePacket.Rect mTextureCoords = 10; + bool has_mtexturecoords() const; + private: + bool _internal_has_mtexturecoords() const; + public: + void clear_mtexturecoords(); + const ::mozilla::layers::layerscope::TexturePacket_Rect& mtexturecoords() const; + ::mozilla::layers::layerscope::TexturePacket_Rect* release_mtexturecoords(); + ::mozilla::layers::layerscope::TexturePacket_Rect* mutable_mtexturecoords(); + void set_allocated_mtexturecoords(::mozilla::layers::layerscope::TexturePacket_Rect* mtexturecoords); + private: + const ::mozilla::layers::layerscope::TexturePacket_Rect& _internal_mtexturecoords() const; + ::mozilla::layers::layerscope::TexturePacket_Rect* _internal_mutable_mtexturecoords(); + public: + + // optional .mozilla.layers.layerscope.TexturePacket.EffectMask mask = 21; + bool has_mask() const; + private: + bool _internal_has_mask() const; + public: + void clear_mask(); + const ::mozilla::layers::layerscope::TexturePacket_EffectMask& mask() const; + ::mozilla::layers::layerscope::TexturePacket_EffectMask* release_mask(); + ::mozilla::layers::layerscope::TexturePacket_EffectMask* mutable_mask(); + void set_allocated_mask(::mozilla::layers::layerscope::TexturePacket_EffectMask* mask); + private: + const ::mozilla::layers::layerscope::TexturePacket_EffectMask& _internal_mask() const; + ::mozilla::layers::layerscope::TexturePacket_EffectMask* _internal_mutable_mask(); + public: + + // required uint64 layerref = 1; + bool has_layerref() const; + private: + bool _internal_has_layerref() const; + public: + void clear_layerref(); + ::PROTOBUF_NAMESPACE_ID::uint64 layerref() const; + void set_layerref(::PROTOBUF_NAMESPACE_ID::uint64 value); + private: + ::PROTOBUF_NAMESPACE_ID::uint64 _internal_layerref() const; + void _internal_set_layerref(::PROTOBUF_NAMESPACE_ID::uint64 value); + public: + + // optional uint32 width = 2; + bool has_width() const; + private: + bool _internal_has_width() const; + public: + void clear_width(); + ::PROTOBUF_NAMESPACE_ID::uint32 width() const; + void set_width(::PROTOBUF_NAMESPACE_ID::uint32 value); + private: + ::PROTOBUF_NAMESPACE_ID::uint32 _internal_width() const; + void _internal_set_width(::PROTOBUF_NAMESPACE_ID::uint32 value); + public: + + // optional uint32 height = 3; + bool has_height() const; + private: + bool _internal_has_height() const; + public: + void clear_height(); + ::PROTOBUF_NAMESPACE_ID::uint32 height() const; + void set_height(::PROTOBUF_NAMESPACE_ID::uint32 value); + private: + ::PROTOBUF_NAMESPACE_ID::uint32 _internal_height() const; + void _internal_set_height(::PROTOBUF_NAMESPACE_ID::uint32 value); + public: + + // optional uint32 stride = 4; + bool has_stride() const; + private: + bool _internal_has_stride() const; + public: + void clear_stride(); + ::PROTOBUF_NAMESPACE_ID::uint32 stride() const; + void set_stride(::PROTOBUF_NAMESPACE_ID::uint32 value); + private: + ::PROTOBUF_NAMESPACE_ID::uint32 _internal_stride() const; + void _internal_set_stride(::PROTOBUF_NAMESPACE_ID::uint32 value); + public: + + // optional uint32 name = 5; + bool has_name() const; + private: + bool _internal_has_name() const; + public: + void clear_name(); + ::PROTOBUF_NAMESPACE_ID::uint32 name() const; + void set_name(::PROTOBUF_NAMESPACE_ID::uint32 value); + private: + ::PROTOBUF_NAMESPACE_ID::uint32 _internal_name() const; + void _internal_set_name(::PROTOBUF_NAMESPACE_ID::uint32 value); + public: + + // optional uint32 target = 6; + bool has_target() const; + private: + bool _internal_has_target() const; + public: + void clear_target(); + ::PROTOBUF_NAMESPACE_ID::uint32 target() const; + void set_target(::PROTOBUF_NAMESPACE_ID::uint32 value); + private: + ::PROTOBUF_NAMESPACE_ID::uint32 _internal_target() const; + void _internal_set_target(::PROTOBUF_NAMESPACE_ID::uint32 value); + public: + + // optional uint32 dataformat = 7; + bool has_dataformat() const; + private: + bool _internal_has_dataformat() const; + public: + void clear_dataformat(); + ::PROTOBUF_NAMESPACE_ID::uint32 dataformat() const; + void set_dataformat(::PROTOBUF_NAMESPACE_ID::uint32 value); + private: + ::PROTOBUF_NAMESPACE_ID::uint32 _internal_dataformat() const; + void _internal_set_dataformat(::PROTOBUF_NAMESPACE_ID::uint32 value); + public: + + // optional uint64 glcontext = 8; + bool has_glcontext() const; + private: + bool _internal_has_glcontext() const; + public: + void clear_glcontext(); + ::PROTOBUF_NAMESPACE_ID::uint64 glcontext() const; + void set_glcontext(::PROTOBUF_NAMESPACE_ID::uint64 value); + private: + ::PROTOBUF_NAMESPACE_ID::uint64 _internal_glcontext() const; + void _internal_set_glcontext(::PROTOBUF_NAMESPACE_ID::uint64 value); + public: + + // optional .mozilla.layers.layerscope.TexturePacket.Filter mFilter = 12; + bool has_mfilter() const; + private: + bool _internal_has_mfilter() const; + public: + void clear_mfilter(); + ::mozilla::layers::layerscope::TexturePacket_Filter mfilter() const; + void set_mfilter(::mozilla::layers::layerscope::TexturePacket_Filter value); + private: + ::mozilla::layers::layerscope::TexturePacket_Filter _internal_mfilter() const; + void _internal_set_mfilter(::mozilla::layers::layerscope::TexturePacket_Filter value); + public: + + // optional bool mPremultiplied = 11; + bool has_mpremultiplied() const; + private: + bool _internal_has_mpremultiplied() const; + public: + void clear_mpremultiplied(); + bool mpremultiplied() const; + void set_mpremultiplied(bool value); + private: + bool _internal_mpremultiplied() const; + void _internal_set_mpremultiplied(bool value); + public: + + // optional bool isMask = 20; + bool has_ismask() const; + private: + bool _internal_has_ismask() const; + public: + void clear_ismask(); + bool ismask() const; + void set_ismask(bool value); + private: + bool _internal_ismask() const; + void _internal_set_ismask(bool value); + public: + + // @@protoc_insertion_point(class_scope:mozilla.layers.layerscope.TexturePacket) + private: + class _Internal; + + ::PROTOBUF_NAMESPACE_ID::internal::InternalMetadataWithArenaLite _internal_metadata_; + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr data_; + ::mozilla::layers::layerscope::TexturePacket_Rect* mtexturecoords_; + ::mozilla::layers::layerscope::TexturePacket_EffectMask* mask_; + ::PROTOBUF_NAMESPACE_ID::uint64 layerref_; + ::PROTOBUF_NAMESPACE_ID::uint32 width_; + ::PROTOBUF_NAMESPACE_ID::uint32 height_; + ::PROTOBUF_NAMESPACE_ID::uint32 stride_; + ::PROTOBUF_NAMESPACE_ID::uint32 name_; + ::PROTOBUF_NAMESPACE_ID::uint32 target_; + ::PROTOBUF_NAMESPACE_ID::uint32 dataformat_; + ::PROTOBUF_NAMESPACE_ID::uint64 glcontext_; + int mfilter_; + bool mpremultiplied_; + bool ismask_; + friend struct ::TableStruct_LayerScopePacket_2eproto; +}; +// ------------------------------------------------------------------- + +class LayersPacket_Layer_Size : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.layers.layerscope.LayersPacket.Layer.Size) */ { + public: + LayersPacket_Layer_Size(); + virtual ~LayersPacket_Layer_Size(); + + LayersPacket_Layer_Size(const LayersPacket_Layer_Size& from); + LayersPacket_Layer_Size(LayersPacket_Layer_Size&& from) noexcept + : LayersPacket_Layer_Size() { + *this = ::std::move(from); + } + + inline LayersPacket_Layer_Size& operator=(const LayersPacket_Layer_Size& from) { + CopyFrom(from); + return *this; + } + inline LayersPacket_Layer_Size& operator=(LayersPacket_Layer_Size&& from) noexcept { + if (GetArenaNoVirtual() == from.GetArenaNoVirtual()) { + if (this != &from) InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const LayersPacket_Layer_Size& default_instance(); + + static void InitAsDefaultInstance(); // FOR INTERNAL USE ONLY + static inline const LayersPacket_Layer_Size* internal_default_instance() { + return reinterpret_cast( + &_LayersPacket_Layer_Size_default_instance_); + } + static constexpr int kIndexInFileMessages = + 7; + + friend void swap(LayersPacket_Layer_Size& a, LayersPacket_Layer_Size& b) { + a.Swap(&b); + } + inline void Swap(LayersPacket_Layer_Size* other) { + if (other == this) return; + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + inline LayersPacket_Layer_Size* New() const final { + return CreateMaybeMessage(nullptr); + } + + LayersPacket_Layer_Size* New(::PROTOBUF_NAMESPACE_ID::Arena* arena) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) + final; + void CopyFrom(const LayersPacket_Layer_Size& from); + void MergeFrom(const LayersPacket_Layer_Size& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + ::PROTOBUF_NAMESPACE_ID::uint8* _InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + void DiscardUnknownFields(); + int GetCachedSize() const final { return _cached_size_.Get(); } + + private: + inline void SharedCtor(); + inline void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(LayersPacket_Layer_Size* other); + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.layers.layerscope.LayersPacket.Layer.Size"; + } + private: + inline ::PROTOBUF_NAMESPACE_ID::Arena* GetArenaNoVirtual() const { + return nullptr; + } + inline void* MaybeArenaPtr() const { + return nullptr; + } + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kWFieldNumber = 1, + kHFieldNumber = 2, + }; + // optional int32 w = 1; + bool has_w() const; + private: + bool _internal_has_w() const; + public: + void clear_w(); + ::PROTOBUF_NAMESPACE_ID::int32 w() const; + void set_w(::PROTOBUF_NAMESPACE_ID::int32 value); + private: + ::PROTOBUF_NAMESPACE_ID::int32 _internal_w() const; + void _internal_set_w(::PROTOBUF_NAMESPACE_ID::int32 value); + public: + + // optional int32 h = 2; + bool has_h() const; + private: + bool _internal_has_h() const; + public: + void clear_h(); + ::PROTOBUF_NAMESPACE_ID::int32 h() const; + void set_h(::PROTOBUF_NAMESPACE_ID::int32 value); + private: + ::PROTOBUF_NAMESPACE_ID::int32 _internal_h() const; + void _internal_set_h(::PROTOBUF_NAMESPACE_ID::int32 value); + public: + + // @@protoc_insertion_point(class_scope:mozilla.layers.layerscope.LayersPacket.Layer.Size) + private: + class _Internal; + + ::PROTOBUF_NAMESPACE_ID::internal::InternalMetadataWithArenaLite _internal_metadata_; + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::int32 w_; + ::PROTOBUF_NAMESPACE_ID::int32 h_; + friend struct ::TableStruct_LayerScopePacket_2eproto; +}; +// ------------------------------------------------------------------- + +class LayersPacket_Layer_Rect : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.layers.layerscope.LayersPacket.Layer.Rect) */ { + public: + LayersPacket_Layer_Rect(); + virtual ~LayersPacket_Layer_Rect(); + + LayersPacket_Layer_Rect(const LayersPacket_Layer_Rect& from); + LayersPacket_Layer_Rect(LayersPacket_Layer_Rect&& from) noexcept + : LayersPacket_Layer_Rect() { + *this = ::std::move(from); + } + + inline LayersPacket_Layer_Rect& operator=(const LayersPacket_Layer_Rect& from) { + CopyFrom(from); + return *this; + } + inline LayersPacket_Layer_Rect& operator=(LayersPacket_Layer_Rect&& from) noexcept { + if (GetArenaNoVirtual() == from.GetArenaNoVirtual()) { + if (this != &from) InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const LayersPacket_Layer_Rect& default_instance(); + + static void InitAsDefaultInstance(); // FOR INTERNAL USE ONLY + static inline const LayersPacket_Layer_Rect* internal_default_instance() { + return reinterpret_cast( + &_LayersPacket_Layer_Rect_default_instance_); + } + static constexpr int kIndexInFileMessages = + 8; + + friend void swap(LayersPacket_Layer_Rect& a, LayersPacket_Layer_Rect& b) { + a.Swap(&b); + } + inline void Swap(LayersPacket_Layer_Rect* other) { + if (other == this) return; + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + inline LayersPacket_Layer_Rect* New() const final { + return CreateMaybeMessage(nullptr); + } + + LayersPacket_Layer_Rect* New(::PROTOBUF_NAMESPACE_ID::Arena* arena) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) + final; + void CopyFrom(const LayersPacket_Layer_Rect& from); + void MergeFrom(const LayersPacket_Layer_Rect& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + ::PROTOBUF_NAMESPACE_ID::uint8* _InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + void DiscardUnknownFields(); + int GetCachedSize() const final { return _cached_size_.Get(); } + + private: + inline void SharedCtor(); + inline void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(LayersPacket_Layer_Rect* other); + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.layers.layerscope.LayersPacket.Layer.Rect"; + } + private: + inline ::PROTOBUF_NAMESPACE_ID::Arena* GetArenaNoVirtual() const { + return nullptr; + } + inline void* MaybeArenaPtr() const { + return nullptr; + } + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kXFieldNumber = 1, + kYFieldNumber = 2, + kWFieldNumber = 3, + kHFieldNumber = 4, + }; + // optional int32 x = 1; + bool has_x() const; + private: + bool _internal_has_x() const; + public: + void clear_x(); + ::PROTOBUF_NAMESPACE_ID::int32 x() const; + void set_x(::PROTOBUF_NAMESPACE_ID::int32 value); + private: + ::PROTOBUF_NAMESPACE_ID::int32 _internal_x() const; + void _internal_set_x(::PROTOBUF_NAMESPACE_ID::int32 value); + public: + + // optional int32 y = 2; + bool has_y() const; + private: + bool _internal_has_y() const; + public: + void clear_y(); + ::PROTOBUF_NAMESPACE_ID::int32 y() const; + void set_y(::PROTOBUF_NAMESPACE_ID::int32 value); + private: + ::PROTOBUF_NAMESPACE_ID::int32 _internal_y() const; + void _internal_set_y(::PROTOBUF_NAMESPACE_ID::int32 value); + public: + + // optional int32 w = 3; + bool has_w() const; + private: + bool _internal_has_w() const; + public: + void clear_w(); + ::PROTOBUF_NAMESPACE_ID::int32 w() const; + void set_w(::PROTOBUF_NAMESPACE_ID::int32 value); + private: + ::PROTOBUF_NAMESPACE_ID::int32 _internal_w() const; + void _internal_set_w(::PROTOBUF_NAMESPACE_ID::int32 value); + public: + + // optional int32 h = 4; + bool has_h() const; + private: + bool _internal_has_h() const; + public: + void clear_h(); + ::PROTOBUF_NAMESPACE_ID::int32 h() const; + void set_h(::PROTOBUF_NAMESPACE_ID::int32 value); + private: + ::PROTOBUF_NAMESPACE_ID::int32 _internal_h() const; + void _internal_set_h(::PROTOBUF_NAMESPACE_ID::int32 value); + public: + + // @@protoc_insertion_point(class_scope:mozilla.layers.layerscope.LayersPacket.Layer.Rect) + private: + class _Internal; + + ::PROTOBUF_NAMESPACE_ID::internal::InternalMetadataWithArenaLite _internal_metadata_; + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::int32 x_; + ::PROTOBUF_NAMESPACE_ID::int32 y_; + ::PROTOBUF_NAMESPACE_ID::int32 w_; + ::PROTOBUF_NAMESPACE_ID::int32 h_; + friend struct ::TableStruct_LayerScopePacket_2eproto; +}; +// ------------------------------------------------------------------- + +class LayersPacket_Layer_Region : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.layers.layerscope.LayersPacket.Layer.Region) */ { + public: + LayersPacket_Layer_Region(); + virtual ~LayersPacket_Layer_Region(); + + LayersPacket_Layer_Region(const LayersPacket_Layer_Region& from); + LayersPacket_Layer_Region(LayersPacket_Layer_Region&& from) noexcept + : LayersPacket_Layer_Region() { + *this = ::std::move(from); + } + + inline LayersPacket_Layer_Region& operator=(const LayersPacket_Layer_Region& from) { + CopyFrom(from); + return *this; + } + inline LayersPacket_Layer_Region& operator=(LayersPacket_Layer_Region&& from) noexcept { + if (GetArenaNoVirtual() == from.GetArenaNoVirtual()) { + if (this != &from) InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const LayersPacket_Layer_Region& default_instance(); + + static void InitAsDefaultInstance(); // FOR INTERNAL USE ONLY + static inline const LayersPacket_Layer_Region* internal_default_instance() { + return reinterpret_cast( + &_LayersPacket_Layer_Region_default_instance_); + } + static constexpr int kIndexInFileMessages = + 9; + + friend void swap(LayersPacket_Layer_Region& a, LayersPacket_Layer_Region& b) { + a.Swap(&b); + } + inline void Swap(LayersPacket_Layer_Region* other) { + if (other == this) return; + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + inline LayersPacket_Layer_Region* New() const final { + return CreateMaybeMessage(nullptr); + } + + LayersPacket_Layer_Region* New(::PROTOBUF_NAMESPACE_ID::Arena* arena) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) + final; + void CopyFrom(const LayersPacket_Layer_Region& from); + void MergeFrom(const LayersPacket_Layer_Region& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + ::PROTOBUF_NAMESPACE_ID::uint8* _InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + void DiscardUnknownFields(); + int GetCachedSize() const final { return _cached_size_.Get(); } + + private: + inline void SharedCtor(); + inline void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(LayersPacket_Layer_Region* other); + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.layers.layerscope.LayersPacket.Layer.Region"; + } + private: + inline ::PROTOBUF_NAMESPACE_ID::Arena* GetArenaNoVirtual() const { + return nullptr; + } + inline void* MaybeArenaPtr() const { + return nullptr; + } + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kRFieldNumber = 1, + }; + // repeated .mozilla.layers.layerscope.LayersPacket.Layer.Rect r = 1; + int r_size() const; + private: + int _internal_r_size() const; + public: + void clear_r(); + ::mozilla::layers::layerscope::LayersPacket_Layer_Rect* mutable_r(int index); + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::layers::layerscope::LayersPacket_Layer_Rect >* + mutable_r(); + private: + const ::mozilla::layers::layerscope::LayersPacket_Layer_Rect& _internal_r(int index) const; + ::mozilla::layers::layerscope::LayersPacket_Layer_Rect* _internal_add_r(); + public: + const ::mozilla::layers::layerscope::LayersPacket_Layer_Rect& r(int index) const; + ::mozilla::layers::layerscope::LayersPacket_Layer_Rect* add_r(); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::layers::layerscope::LayersPacket_Layer_Rect >& + r() const; + + // @@protoc_insertion_point(class_scope:mozilla.layers.layerscope.LayersPacket.Layer.Region) + private: + class _Internal; + + ::PROTOBUF_NAMESPACE_ID::internal::InternalMetadataWithArenaLite _internal_metadata_; + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::layers::layerscope::LayersPacket_Layer_Rect > r_; + friend struct ::TableStruct_LayerScopePacket_2eproto; +}; +// ------------------------------------------------------------------- + +class LayersPacket_Layer_Matrix : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.layers.layerscope.LayersPacket.Layer.Matrix) */ { + public: + LayersPacket_Layer_Matrix(); + virtual ~LayersPacket_Layer_Matrix(); + + LayersPacket_Layer_Matrix(const LayersPacket_Layer_Matrix& from); + LayersPacket_Layer_Matrix(LayersPacket_Layer_Matrix&& from) noexcept + : LayersPacket_Layer_Matrix() { + *this = ::std::move(from); + } + + inline LayersPacket_Layer_Matrix& operator=(const LayersPacket_Layer_Matrix& from) { + CopyFrom(from); + return *this; + } + inline LayersPacket_Layer_Matrix& operator=(LayersPacket_Layer_Matrix&& from) noexcept { + if (GetArenaNoVirtual() == from.GetArenaNoVirtual()) { + if (this != &from) InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const LayersPacket_Layer_Matrix& default_instance(); + + static void InitAsDefaultInstance(); // FOR INTERNAL USE ONLY + static inline const LayersPacket_Layer_Matrix* internal_default_instance() { + return reinterpret_cast( + &_LayersPacket_Layer_Matrix_default_instance_); + } + static constexpr int kIndexInFileMessages = + 10; + + friend void swap(LayersPacket_Layer_Matrix& a, LayersPacket_Layer_Matrix& b) { + a.Swap(&b); + } + inline void Swap(LayersPacket_Layer_Matrix* other) { + if (other == this) return; + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + inline LayersPacket_Layer_Matrix* New() const final { + return CreateMaybeMessage(nullptr); + } + + LayersPacket_Layer_Matrix* New(::PROTOBUF_NAMESPACE_ID::Arena* arena) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) + final; + void CopyFrom(const LayersPacket_Layer_Matrix& from); + void MergeFrom(const LayersPacket_Layer_Matrix& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + ::PROTOBUF_NAMESPACE_ID::uint8* _InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + void DiscardUnknownFields(); + int GetCachedSize() const final { return _cached_size_.Get(); } + + private: + inline void SharedCtor(); + inline void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(LayersPacket_Layer_Matrix* other); + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.layers.layerscope.LayersPacket.Layer.Matrix"; + } + private: + inline ::PROTOBUF_NAMESPACE_ID::Arena* GetArenaNoVirtual() const { + return nullptr; + } + inline void* MaybeArenaPtr() const { + return nullptr; + } + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kMFieldNumber = 3, + kIs2DFieldNumber = 1, + kIsIdFieldNumber = 2, + }; + // repeated float m = 3; + int m_size() const; + private: + int _internal_m_size() const; + public: + void clear_m(); + private: + float _internal_m(int index) const; + const ::PROTOBUF_NAMESPACE_ID::RepeatedField< float >& + _internal_m() const; + void _internal_add_m(float value); + ::PROTOBUF_NAMESPACE_ID::RepeatedField< float >* + _internal_mutable_m(); + public: + float m(int index) const; + void set_m(int index, float value); + void add_m(float value); + const ::PROTOBUF_NAMESPACE_ID::RepeatedField< float >& + m() const; + ::PROTOBUF_NAMESPACE_ID::RepeatedField< float >* + mutable_m(); + + // optional bool is2D = 1; + bool has_is2d() const; + private: + bool _internal_has_is2d() const; + public: + void clear_is2d(); + bool is2d() const; + void set_is2d(bool value); + private: + bool _internal_is2d() const; + void _internal_set_is2d(bool value); + public: + + // optional bool isId = 2; + bool has_isid() const; + private: + bool _internal_has_isid() const; + public: + void clear_isid(); + bool isid() const; + void set_isid(bool value); + private: + bool _internal_isid() const; + void _internal_set_isid(bool value); + public: + + // @@protoc_insertion_point(class_scope:mozilla.layers.layerscope.LayersPacket.Layer.Matrix) + private: + class _Internal; + + ::PROTOBUF_NAMESPACE_ID::internal::InternalMetadataWithArenaLite _internal_metadata_; + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::RepeatedField< float > m_; + bool is2d_; + bool isid_; + friend struct ::TableStruct_LayerScopePacket_2eproto; +}; +// ------------------------------------------------------------------- + +class LayersPacket_Layer_Shadow : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.layers.layerscope.LayersPacket.Layer.Shadow) */ { + public: + LayersPacket_Layer_Shadow(); + virtual ~LayersPacket_Layer_Shadow(); + + LayersPacket_Layer_Shadow(const LayersPacket_Layer_Shadow& from); + LayersPacket_Layer_Shadow(LayersPacket_Layer_Shadow&& from) noexcept + : LayersPacket_Layer_Shadow() { + *this = ::std::move(from); + } + + inline LayersPacket_Layer_Shadow& operator=(const LayersPacket_Layer_Shadow& from) { + CopyFrom(from); + return *this; + } + inline LayersPacket_Layer_Shadow& operator=(LayersPacket_Layer_Shadow&& from) noexcept { + if (GetArenaNoVirtual() == from.GetArenaNoVirtual()) { + if (this != &from) InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const LayersPacket_Layer_Shadow& default_instance(); + + static void InitAsDefaultInstance(); // FOR INTERNAL USE ONLY + static inline const LayersPacket_Layer_Shadow* internal_default_instance() { + return reinterpret_cast( + &_LayersPacket_Layer_Shadow_default_instance_); + } + static constexpr int kIndexInFileMessages = + 11; + + friend void swap(LayersPacket_Layer_Shadow& a, LayersPacket_Layer_Shadow& b) { + a.Swap(&b); + } + inline void Swap(LayersPacket_Layer_Shadow* other) { + if (other == this) return; + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + inline LayersPacket_Layer_Shadow* New() const final { + return CreateMaybeMessage(nullptr); + } + + LayersPacket_Layer_Shadow* New(::PROTOBUF_NAMESPACE_ID::Arena* arena) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) + final; + void CopyFrom(const LayersPacket_Layer_Shadow& from); + void MergeFrom(const LayersPacket_Layer_Shadow& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + ::PROTOBUF_NAMESPACE_ID::uint8* _InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + void DiscardUnknownFields(); + int GetCachedSize() const final { return _cached_size_.Get(); } + + private: + inline void SharedCtor(); + inline void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(LayersPacket_Layer_Shadow* other); + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.layers.layerscope.LayersPacket.Layer.Shadow"; + } + private: + inline ::PROTOBUF_NAMESPACE_ID::Arena* GetArenaNoVirtual() const { + return nullptr; + } + inline void* MaybeArenaPtr() const { + return nullptr; + } + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kClipFieldNumber = 1, + kTransformFieldNumber = 2, + kVRegionFieldNumber = 3, + }; + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Rect clip = 1; + bool has_clip() const; + private: + bool _internal_has_clip() const; + public: + void clear_clip(); + const ::mozilla::layers::layerscope::LayersPacket_Layer_Rect& clip() const; + ::mozilla::layers::layerscope::LayersPacket_Layer_Rect* release_clip(); + ::mozilla::layers::layerscope::LayersPacket_Layer_Rect* mutable_clip(); + void set_allocated_clip(::mozilla::layers::layerscope::LayersPacket_Layer_Rect* clip); + private: + const ::mozilla::layers::layerscope::LayersPacket_Layer_Rect& _internal_clip() const; + ::mozilla::layers::layerscope::LayersPacket_Layer_Rect* _internal_mutable_clip(); + public: + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Matrix transform = 2; + bool has_transform() const; + private: + bool _internal_has_transform() const; + public: + void clear_transform(); + const ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix& transform() const; + ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix* release_transform(); + ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix* mutable_transform(); + void set_allocated_transform(::mozilla::layers::layerscope::LayersPacket_Layer_Matrix* transform); + private: + const ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix& _internal_transform() const; + ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix* _internal_mutable_transform(); + public: + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Region vRegion = 3; + bool has_vregion() const; + private: + bool _internal_has_vregion() const; + public: + void clear_vregion(); + const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& vregion() const; + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* release_vregion(); + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* mutable_vregion(); + void set_allocated_vregion(::mozilla::layers::layerscope::LayersPacket_Layer_Region* vregion); + private: + const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& _internal_vregion() const; + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* _internal_mutable_vregion(); + public: + + // @@protoc_insertion_point(class_scope:mozilla.layers.layerscope.LayersPacket.Layer.Shadow) + private: + class _Internal; + + ::PROTOBUF_NAMESPACE_ID::internal::InternalMetadataWithArenaLite _internal_metadata_; + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::mozilla::layers::layerscope::LayersPacket_Layer_Rect* clip_; + ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix* transform_; + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* vregion_; + friend struct ::TableStruct_LayerScopePacket_2eproto; +}; +// ------------------------------------------------------------------- + +class LayersPacket_Layer : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.layers.layerscope.LayersPacket.Layer) */ { + public: + LayersPacket_Layer(); + virtual ~LayersPacket_Layer(); + + LayersPacket_Layer(const LayersPacket_Layer& from); + LayersPacket_Layer(LayersPacket_Layer&& from) noexcept + : LayersPacket_Layer() { + *this = ::std::move(from); + } + + inline LayersPacket_Layer& operator=(const LayersPacket_Layer& from) { + CopyFrom(from); + return *this; + } + inline LayersPacket_Layer& operator=(LayersPacket_Layer&& from) noexcept { + if (GetArenaNoVirtual() == from.GetArenaNoVirtual()) { + if (this != &from) InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const LayersPacket_Layer& default_instance(); + + static void InitAsDefaultInstance(); // FOR INTERNAL USE ONLY + static inline const LayersPacket_Layer* internal_default_instance() { + return reinterpret_cast( + &_LayersPacket_Layer_default_instance_); + } + static constexpr int kIndexInFileMessages = + 12; + + friend void swap(LayersPacket_Layer& a, LayersPacket_Layer& b) { + a.Swap(&b); + } + inline void Swap(LayersPacket_Layer* other) { + if (other == this) return; + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + inline LayersPacket_Layer* New() const final { + return CreateMaybeMessage(nullptr); + } + + LayersPacket_Layer* New(::PROTOBUF_NAMESPACE_ID::Arena* arena) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) + final; + void CopyFrom(const LayersPacket_Layer& from); + void MergeFrom(const LayersPacket_Layer& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + ::PROTOBUF_NAMESPACE_ID::uint8* _InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + void DiscardUnknownFields(); + int GetCachedSize() const final { return _cached_size_.Get(); } + + private: + inline void SharedCtor(); + inline void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(LayersPacket_Layer* other); + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.layers.layerscope.LayersPacket.Layer"; + } + private: + inline ::PROTOBUF_NAMESPACE_ID::Arena* GetArenaNoVirtual() const { + return nullptr; + } + inline void* MaybeArenaPtr() const { + return nullptr; + } + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + typedef LayersPacket_Layer_Size Size; + typedef LayersPacket_Layer_Rect Rect; + typedef LayersPacket_Layer_Region Region; + typedef LayersPacket_Layer_Matrix Matrix; + typedef LayersPacket_Layer_Shadow Shadow; + + typedef LayersPacket_Layer_LayerType LayerType; + static constexpr LayerType UnknownLayer = + LayersPacket_Layer_LayerType_UnknownLayer; + static constexpr LayerType LayerManager = + LayersPacket_Layer_LayerType_LayerManager; + static constexpr LayerType ContainerLayer = + LayersPacket_Layer_LayerType_ContainerLayer; + static constexpr LayerType PaintedLayer = + LayersPacket_Layer_LayerType_PaintedLayer; + static constexpr LayerType CanvasLayer = + LayersPacket_Layer_LayerType_CanvasLayer; + static constexpr LayerType ImageLayer = + LayersPacket_Layer_LayerType_ImageLayer; + static constexpr LayerType ColorLayer = + LayersPacket_Layer_LayerType_ColorLayer; + static constexpr LayerType RefLayer = + LayersPacket_Layer_LayerType_RefLayer; + static constexpr LayerType ReadbackLayer = + LayersPacket_Layer_LayerType_ReadbackLayer; + static constexpr LayerType DisplayItemLayer = + LayersPacket_Layer_LayerType_DisplayItemLayer; + static inline bool LayerType_IsValid(int value) { + return LayersPacket_Layer_LayerType_IsValid(value); + } + static constexpr LayerType LayerType_MIN = + LayersPacket_Layer_LayerType_LayerType_MIN; + static constexpr LayerType LayerType_MAX = + LayersPacket_Layer_LayerType_LayerType_MAX; + static constexpr int LayerType_ARRAYSIZE = + LayersPacket_Layer_LayerType_LayerType_ARRAYSIZE; + template + static inline const std::string& LayerType_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function LayerType_Name."); + return LayersPacket_Layer_LayerType_Name(enum_t_value); + } + static inline bool LayerType_Parse(const std::string& name, + LayerType* value) { + return LayersPacket_Layer_LayerType_Parse(name, value); + } + + typedef LayersPacket_Layer_ScrollingDirect ScrollingDirect; + static constexpr ScrollingDirect VERTICAL = + LayersPacket_Layer_ScrollingDirect_VERTICAL; + static constexpr ScrollingDirect HORIZONTAL = + LayersPacket_Layer_ScrollingDirect_HORIZONTAL; + static inline bool ScrollingDirect_IsValid(int value) { + return LayersPacket_Layer_ScrollingDirect_IsValid(value); + } + static constexpr ScrollingDirect ScrollingDirect_MIN = + LayersPacket_Layer_ScrollingDirect_ScrollingDirect_MIN; + static constexpr ScrollingDirect ScrollingDirect_MAX = + LayersPacket_Layer_ScrollingDirect_ScrollingDirect_MAX; + static constexpr int ScrollingDirect_ARRAYSIZE = + LayersPacket_Layer_ScrollingDirect_ScrollingDirect_ARRAYSIZE; + template + static inline const std::string& ScrollingDirect_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function ScrollingDirect_Name."); + return LayersPacket_Layer_ScrollingDirect_Name(enum_t_value); + } + static inline bool ScrollingDirect_Parse(const std::string& name, + ScrollingDirect* value) { + return LayersPacket_Layer_ScrollingDirect_Parse(name, value); + } + + typedef LayersPacket_Layer_Filter Filter; + static constexpr Filter FILTER_FAST = + LayersPacket_Layer_Filter_FILTER_FAST; + static constexpr Filter FILTER_GOOD = + LayersPacket_Layer_Filter_FILTER_GOOD; + static constexpr Filter FILTER_BEST = + LayersPacket_Layer_Filter_FILTER_BEST; + static constexpr Filter FILTER_NEAREST = + LayersPacket_Layer_Filter_FILTER_NEAREST; + static constexpr Filter FILTER_BILINEAR = + LayersPacket_Layer_Filter_FILTER_BILINEAR; + static constexpr Filter FILTER_GAUSSIAN = + LayersPacket_Layer_Filter_FILTER_GAUSSIAN; + static constexpr Filter FILTER_SENTINEL = + LayersPacket_Layer_Filter_FILTER_SENTINEL; + static constexpr Filter FILTER_LINEAR = + LayersPacket_Layer_Filter_FILTER_LINEAR; + static constexpr Filter FILTER_POINT = + LayersPacket_Layer_Filter_FILTER_POINT; + static inline bool Filter_IsValid(int value) { + return LayersPacket_Layer_Filter_IsValid(value); + } + static constexpr Filter Filter_MIN = + LayersPacket_Layer_Filter_Filter_MIN; + static constexpr Filter Filter_MAX = + LayersPacket_Layer_Filter_Filter_MAX; + static constexpr int Filter_ARRAYSIZE = + LayersPacket_Layer_Filter_Filter_ARRAYSIZE; + template + static inline const std::string& Filter_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function Filter_Name."); + return LayersPacket_Layer_Filter_Name(enum_t_value); + } + static inline bool Filter_Parse(const std::string& name, + Filter* value) { + return LayersPacket_Layer_Filter_Parse(name, value); + } + + // accessors ------------------------------------------------------- + + enum : int { + kDisplayListLogFieldNumber = 106, + kClipFieldNumber = 10, + kTransformFieldNumber = 11, + kVRegionFieldNumber = 12, + kShadowFieldNumber = 13, + kHitRegionFieldNumber = 20, + kDispatchRegionFieldNumber = 21, + kNoActionRegionFieldNumber = 22, + kHPanRegionFieldNumber = 23, + kVPanRegionFieldNumber = 24, + kValidFieldNumber = 100, + kSizeFieldNumber = 104, + kPtrFieldNumber = 2, + kParentPtrFieldNumber = 3, + kTypeFieldNumber = 1, + kOpacityFieldNumber = 14, + kBarIDFieldNumber = 18, + kMaskFieldNumber = 19, + kCOpaqueFieldNumber = 15, + kCAlphaFieldNumber = 16, + kColorFieldNumber = 101, + kRefIDFieldNumber = 103, + kFilterFieldNumber = 102, + kDisplayListLogLengthFieldNumber = 105, + kDirectFieldNumber = 17, + }; + // optional bytes displayListLog = 106; + bool has_displaylistlog() const; + private: + bool _internal_has_displaylistlog() const; + public: + void clear_displaylistlog(); + const std::string& displaylistlog() const; + void set_displaylistlog(const std::string& value); + void set_displaylistlog(std::string&& value); + void set_displaylistlog(const char* value); + void set_displaylistlog(const void* value, size_t size); + std::string* mutable_displaylistlog(); + std::string* release_displaylistlog(); + void set_allocated_displaylistlog(std::string* displaylistlog); + private: + const std::string& _internal_displaylistlog() const; + void _internal_set_displaylistlog(const std::string& value); + std::string* _internal_mutable_displaylistlog(); + public: + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Rect clip = 10; + bool has_clip() const; + private: + bool _internal_has_clip() const; + public: + void clear_clip(); + const ::mozilla::layers::layerscope::LayersPacket_Layer_Rect& clip() const; + ::mozilla::layers::layerscope::LayersPacket_Layer_Rect* release_clip(); + ::mozilla::layers::layerscope::LayersPacket_Layer_Rect* mutable_clip(); + void set_allocated_clip(::mozilla::layers::layerscope::LayersPacket_Layer_Rect* clip); + private: + const ::mozilla::layers::layerscope::LayersPacket_Layer_Rect& _internal_clip() const; + ::mozilla::layers::layerscope::LayersPacket_Layer_Rect* _internal_mutable_clip(); + public: + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Matrix transform = 11; + bool has_transform() const; + private: + bool _internal_has_transform() const; + public: + void clear_transform(); + const ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix& transform() const; + ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix* release_transform(); + ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix* mutable_transform(); + void set_allocated_transform(::mozilla::layers::layerscope::LayersPacket_Layer_Matrix* transform); + private: + const ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix& _internal_transform() const; + ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix* _internal_mutable_transform(); + public: + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Region vRegion = 12; + bool has_vregion() const; + private: + bool _internal_has_vregion() const; + public: + void clear_vregion(); + const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& vregion() const; + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* release_vregion(); + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* mutable_vregion(); + void set_allocated_vregion(::mozilla::layers::layerscope::LayersPacket_Layer_Region* vregion); + private: + const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& _internal_vregion() const; + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* _internal_mutable_vregion(); + public: + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Shadow shadow = 13; + bool has_shadow() const; + private: + bool _internal_has_shadow() const; + public: + void clear_shadow(); + const ::mozilla::layers::layerscope::LayersPacket_Layer_Shadow& shadow() const; + ::mozilla::layers::layerscope::LayersPacket_Layer_Shadow* release_shadow(); + ::mozilla::layers::layerscope::LayersPacket_Layer_Shadow* mutable_shadow(); + void set_allocated_shadow(::mozilla::layers::layerscope::LayersPacket_Layer_Shadow* shadow); + private: + const ::mozilla::layers::layerscope::LayersPacket_Layer_Shadow& _internal_shadow() const; + ::mozilla::layers::layerscope::LayersPacket_Layer_Shadow* _internal_mutable_shadow(); + public: + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Region hitRegion = 20; + bool has_hitregion() const; + private: + bool _internal_has_hitregion() const; + public: + void clear_hitregion(); + const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& hitregion() const; + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* release_hitregion(); + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* mutable_hitregion(); + void set_allocated_hitregion(::mozilla::layers::layerscope::LayersPacket_Layer_Region* hitregion); + private: + const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& _internal_hitregion() const; + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* _internal_mutable_hitregion(); + public: + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Region dispatchRegion = 21; + bool has_dispatchregion() const; + private: + bool _internal_has_dispatchregion() const; + public: + void clear_dispatchregion(); + const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& dispatchregion() const; + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* release_dispatchregion(); + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* mutable_dispatchregion(); + void set_allocated_dispatchregion(::mozilla::layers::layerscope::LayersPacket_Layer_Region* dispatchregion); + private: + const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& _internal_dispatchregion() const; + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* _internal_mutable_dispatchregion(); + public: + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Region noActionRegion = 22; + bool has_noactionregion() const; + private: + bool _internal_has_noactionregion() const; + public: + void clear_noactionregion(); + const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& noactionregion() const; + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* release_noactionregion(); + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* mutable_noactionregion(); + void set_allocated_noactionregion(::mozilla::layers::layerscope::LayersPacket_Layer_Region* noactionregion); + private: + const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& _internal_noactionregion() const; + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* _internal_mutable_noactionregion(); + public: + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Region hPanRegion = 23; + bool has_hpanregion() const; + private: + bool _internal_has_hpanregion() const; + public: + void clear_hpanregion(); + const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& hpanregion() const; + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* release_hpanregion(); + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* mutable_hpanregion(); + void set_allocated_hpanregion(::mozilla::layers::layerscope::LayersPacket_Layer_Region* hpanregion); + private: + const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& _internal_hpanregion() const; + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* _internal_mutable_hpanregion(); + public: + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Region vPanRegion = 24; + bool has_vpanregion() const; + private: + bool _internal_has_vpanregion() const; + public: + void clear_vpanregion(); + const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& vpanregion() const; + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* release_vpanregion(); + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* mutable_vpanregion(); + void set_allocated_vpanregion(::mozilla::layers::layerscope::LayersPacket_Layer_Region* vpanregion); + private: + const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& _internal_vpanregion() const; + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* _internal_mutable_vpanregion(); + public: + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Region valid = 100; + bool has_valid() const; + private: + bool _internal_has_valid() const; + public: + void clear_valid(); + const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& valid() const; + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* release_valid(); + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* mutable_valid(); + void set_allocated_valid(::mozilla::layers::layerscope::LayersPacket_Layer_Region* valid); + private: + const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& _internal_valid() const; + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* _internal_mutable_valid(); + public: + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Size size = 104; + bool has_size() const; + private: + bool _internal_has_size() const; + public: + void clear_size(); + const ::mozilla::layers::layerscope::LayersPacket_Layer_Size& size() const; + ::mozilla::layers::layerscope::LayersPacket_Layer_Size* release_size(); + ::mozilla::layers::layerscope::LayersPacket_Layer_Size* mutable_size(); + void set_allocated_size(::mozilla::layers::layerscope::LayersPacket_Layer_Size* size); + private: + const ::mozilla::layers::layerscope::LayersPacket_Layer_Size& _internal_size() const; + ::mozilla::layers::layerscope::LayersPacket_Layer_Size* _internal_mutable_size(); + public: + + // required uint64 ptr = 2; + bool has_ptr() const; + private: + bool _internal_has_ptr() const; + public: + void clear_ptr(); + ::PROTOBUF_NAMESPACE_ID::uint64 ptr() const; + void set_ptr(::PROTOBUF_NAMESPACE_ID::uint64 value); + private: + ::PROTOBUF_NAMESPACE_ID::uint64 _internal_ptr() const; + void _internal_set_ptr(::PROTOBUF_NAMESPACE_ID::uint64 value); + public: + + // required uint64 parentPtr = 3; + bool has_parentptr() const; + private: + bool _internal_has_parentptr() const; + public: + void clear_parentptr(); + ::PROTOBUF_NAMESPACE_ID::uint64 parentptr() const; + void set_parentptr(::PROTOBUF_NAMESPACE_ID::uint64 value); + private: + ::PROTOBUF_NAMESPACE_ID::uint64 _internal_parentptr() const; + void _internal_set_parentptr(::PROTOBUF_NAMESPACE_ID::uint64 value); + public: + + // required .mozilla.layers.layerscope.LayersPacket.Layer.LayerType type = 1; + bool has_type() const; + private: + bool _internal_has_type() const; + public: + void clear_type(); + ::mozilla::layers::layerscope::LayersPacket_Layer_LayerType type() const; + void set_type(::mozilla::layers::layerscope::LayersPacket_Layer_LayerType value); + private: + ::mozilla::layers::layerscope::LayersPacket_Layer_LayerType _internal_type() const; + void _internal_set_type(::mozilla::layers::layerscope::LayersPacket_Layer_LayerType value); + public: + + // optional float opacity = 14; + bool has_opacity() const; + private: + bool _internal_has_opacity() const; + public: + void clear_opacity(); + float opacity() const; + void set_opacity(float value); + private: + float _internal_opacity() const; + void _internal_set_opacity(float value); + public: + + // optional uint64 barID = 18; + bool has_barid() const; + private: + bool _internal_has_barid() const; + public: + void clear_barid(); + ::PROTOBUF_NAMESPACE_ID::uint64 barid() const; + void set_barid(::PROTOBUF_NAMESPACE_ID::uint64 value); + private: + ::PROTOBUF_NAMESPACE_ID::uint64 _internal_barid() const; + void _internal_set_barid(::PROTOBUF_NAMESPACE_ID::uint64 value); + public: + + // optional uint64 mask = 19; + bool has_mask() const; + private: + bool _internal_has_mask() const; + public: + void clear_mask(); + ::PROTOBUF_NAMESPACE_ID::uint64 mask() const; + void set_mask(::PROTOBUF_NAMESPACE_ID::uint64 value); + private: + ::PROTOBUF_NAMESPACE_ID::uint64 _internal_mask() const; + void _internal_set_mask(::PROTOBUF_NAMESPACE_ID::uint64 value); + public: + + // optional bool cOpaque = 15; + bool has_copaque() const; + private: + bool _internal_has_copaque() const; + public: + void clear_copaque(); + bool copaque() const; + void set_copaque(bool value); + private: + bool _internal_copaque() const; + void _internal_set_copaque(bool value); + public: + + // optional bool cAlpha = 16; + bool has_calpha() const; + private: + bool _internal_has_calpha() const; + public: + void clear_calpha(); + bool calpha() const; + void set_calpha(bool value); + private: + bool _internal_calpha() const; + void _internal_set_calpha(bool value); + public: + + // optional uint32 color = 101; + bool has_color() const; + private: + bool _internal_has_color() const; + public: + void clear_color(); + ::PROTOBUF_NAMESPACE_ID::uint32 color() const; + void set_color(::PROTOBUF_NAMESPACE_ID::uint32 value); + private: + ::PROTOBUF_NAMESPACE_ID::uint32 _internal_color() const; + void _internal_set_color(::PROTOBUF_NAMESPACE_ID::uint32 value); + public: + + // optional uint64 refID = 103; + bool has_refid() const; + private: + bool _internal_has_refid() const; + public: + void clear_refid(); + ::PROTOBUF_NAMESPACE_ID::uint64 refid() const; + void set_refid(::PROTOBUF_NAMESPACE_ID::uint64 value); + private: + ::PROTOBUF_NAMESPACE_ID::uint64 _internal_refid() const; + void _internal_set_refid(::PROTOBUF_NAMESPACE_ID::uint64 value); + public: + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.Filter filter = 102; + bool has_filter() const; + private: + bool _internal_has_filter() const; + public: + void clear_filter(); + ::mozilla::layers::layerscope::LayersPacket_Layer_Filter filter() const; + void set_filter(::mozilla::layers::layerscope::LayersPacket_Layer_Filter value); + private: + ::mozilla::layers::layerscope::LayersPacket_Layer_Filter _internal_filter() const; + void _internal_set_filter(::mozilla::layers::layerscope::LayersPacket_Layer_Filter value); + public: + + // optional uint32 displayListLogLength = 105; + bool has_displaylistloglength() const; + private: + bool _internal_has_displaylistloglength() const; + public: + void clear_displaylistloglength(); + ::PROTOBUF_NAMESPACE_ID::uint32 displaylistloglength() const; + void set_displaylistloglength(::PROTOBUF_NAMESPACE_ID::uint32 value); + private: + ::PROTOBUF_NAMESPACE_ID::uint32 _internal_displaylistloglength() const; + void _internal_set_displaylistloglength(::PROTOBUF_NAMESPACE_ID::uint32 value); + public: + + // optional .mozilla.layers.layerscope.LayersPacket.Layer.ScrollingDirect direct = 17; + bool has_direct() const; + private: + bool _internal_has_direct() const; + public: + void clear_direct(); + ::mozilla::layers::layerscope::LayersPacket_Layer_ScrollingDirect direct() const; + void set_direct(::mozilla::layers::layerscope::LayersPacket_Layer_ScrollingDirect value); + private: + ::mozilla::layers::layerscope::LayersPacket_Layer_ScrollingDirect _internal_direct() const; + void _internal_set_direct(::mozilla::layers::layerscope::LayersPacket_Layer_ScrollingDirect value); + public: + + // @@protoc_insertion_point(class_scope:mozilla.layers.layerscope.LayersPacket.Layer) + private: + class _Internal; + + // helper for ByteSizeLong() + size_t RequiredFieldsByteSizeFallback() const; + + ::PROTOBUF_NAMESPACE_ID::internal::InternalMetadataWithArenaLite _internal_metadata_; + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr displaylistlog_; + ::mozilla::layers::layerscope::LayersPacket_Layer_Rect* clip_; + ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix* transform_; + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* vregion_; + ::mozilla::layers::layerscope::LayersPacket_Layer_Shadow* shadow_; + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* hitregion_; + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* dispatchregion_; + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* noactionregion_; + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* hpanregion_; + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* vpanregion_; + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* valid_; + ::mozilla::layers::layerscope::LayersPacket_Layer_Size* size_; + ::PROTOBUF_NAMESPACE_ID::uint64 ptr_; + ::PROTOBUF_NAMESPACE_ID::uint64 parentptr_; + int type_; + float opacity_; + ::PROTOBUF_NAMESPACE_ID::uint64 barid_; + ::PROTOBUF_NAMESPACE_ID::uint64 mask_; + bool copaque_; + bool calpha_; + ::PROTOBUF_NAMESPACE_ID::uint32 color_; + ::PROTOBUF_NAMESPACE_ID::uint64 refid_; + int filter_; + ::PROTOBUF_NAMESPACE_ID::uint32 displaylistloglength_; + int direct_; + friend struct ::TableStruct_LayerScopePacket_2eproto; +}; +// ------------------------------------------------------------------- + +class LayersPacket : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.layers.layerscope.LayersPacket) */ { + public: + LayersPacket(); + virtual ~LayersPacket(); + + LayersPacket(const LayersPacket& from); + LayersPacket(LayersPacket&& from) noexcept + : LayersPacket() { + *this = ::std::move(from); + } + + inline LayersPacket& operator=(const LayersPacket& from) { + CopyFrom(from); + return *this; + } + inline LayersPacket& operator=(LayersPacket&& from) noexcept { + if (GetArenaNoVirtual() == from.GetArenaNoVirtual()) { + if (this != &from) InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const LayersPacket& default_instance(); + + static void InitAsDefaultInstance(); // FOR INTERNAL USE ONLY + static inline const LayersPacket* internal_default_instance() { + return reinterpret_cast( + &_LayersPacket_default_instance_); + } + static constexpr int kIndexInFileMessages = + 13; + + friend void swap(LayersPacket& a, LayersPacket& b) { + a.Swap(&b); + } + inline void Swap(LayersPacket* other) { + if (other == this) return; + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + inline LayersPacket* New() const final { + return CreateMaybeMessage(nullptr); + } + + LayersPacket* New(::PROTOBUF_NAMESPACE_ID::Arena* arena) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) + final; + void CopyFrom(const LayersPacket& from); + void MergeFrom(const LayersPacket& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + ::PROTOBUF_NAMESPACE_ID::uint8* _InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + void DiscardUnknownFields(); + int GetCachedSize() const final { return _cached_size_.Get(); } + + private: + inline void SharedCtor(); + inline void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(LayersPacket* other); + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.layers.layerscope.LayersPacket"; + } + private: + inline ::PROTOBUF_NAMESPACE_ID::Arena* GetArenaNoVirtual() const { + return nullptr; + } + inline void* MaybeArenaPtr() const { + return nullptr; + } + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + typedef LayersPacket_Layer Layer; + + // accessors ------------------------------------------------------- + + enum : int { + kLayerFieldNumber = 1, + }; + // repeated .mozilla.layers.layerscope.LayersPacket.Layer layer = 1; + int layer_size() const; + private: + int _internal_layer_size() const; + public: + void clear_layer(); + ::mozilla::layers::layerscope::LayersPacket_Layer* mutable_layer(int index); + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::layers::layerscope::LayersPacket_Layer >* + mutable_layer(); + private: + const ::mozilla::layers::layerscope::LayersPacket_Layer& _internal_layer(int index) const; + ::mozilla::layers::layerscope::LayersPacket_Layer* _internal_add_layer(); + public: + const ::mozilla::layers::layerscope::LayersPacket_Layer& layer(int index) const; + ::mozilla::layers::layerscope::LayersPacket_Layer* add_layer(); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::layers::layerscope::LayersPacket_Layer >& + layer() const; + + // @@protoc_insertion_point(class_scope:mozilla.layers.layerscope.LayersPacket) + private: + class _Internal; + + ::PROTOBUF_NAMESPACE_ID::internal::InternalMetadataWithArenaLite _internal_metadata_; + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::layers::layerscope::LayersPacket_Layer > layer_; + friend struct ::TableStruct_LayerScopePacket_2eproto; +}; +// ------------------------------------------------------------------- + +class MetaPacket : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.layers.layerscope.MetaPacket) */ { + public: + MetaPacket(); + virtual ~MetaPacket(); + + MetaPacket(const MetaPacket& from); + MetaPacket(MetaPacket&& from) noexcept + : MetaPacket() { + *this = ::std::move(from); + } + + inline MetaPacket& operator=(const MetaPacket& from) { + CopyFrom(from); + return *this; + } + inline MetaPacket& operator=(MetaPacket&& from) noexcept { + if (GetArenaNoVirtual() == from.GetArenaNoVirtual()) { + if (this != &from) InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const MetaPacket& default_instance(); + + static void InitAsDefaultInstance(); // FOR INTERNAL USE ONLY + static inline const MetaPacket* internal_default_instance() { + return reinterpret_cast( + &_MetaPacket_default_instance_); + } + static constexpr int kIndexInFileMessages = + 14; + + friend void swap(MetaPacket& a, MetaPacket& b) { + a.Swap(&b); + } + inline void Swap(MetaPacket* other) { + if (other == this) return; + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + inline MetaPacket* New() const final { + return CreateMaybeMessage(nullptr); + } + + MetaPacket* New(::PROTOBUF_NAMESPACE_ID::Arena* arena) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) + final; + void CopyFrom(const MetaPacket& from); + void MergeFrom(const MetaPacket& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + ::PROTOBUF_NAMESPACE_ID::uint8* _InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + void DiscardUnknownFields(); + int GetCachedSize() const final { return _cached_size_.Get(); } + + private: + inline void SharedCtor(); + inline void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(MetaPacket* other); + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.layers.layerscope.MetaPacket"; + } + private: + inline ::PROTOBUF_NAMESPACE_ID::Arena* GetArenaNoVirtual() const { + return nullptr; + } + inline void* MaybeArenaPtr() const { + return nullptr; + } + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kComposedByHwcFieldNumber = 1, + }; + // optional bool composedByHwc = 1; + bool has_composedbyhwc() const; + private: + bool _internal_has_composedbyhwc() const; + public: + void clear_composedbyhwc(); + bool composedbyhwc() const; + void set_composedbyhwc(bool value); + private: + bool _internal_composedbyhwc() const; + void _internal_set_composedbyhwc(bool value); + public: + + // @@protoc_insertion_point(class_scope:mozilla.layers.layerscope.MetaPacket) + private: + class _Internal; + + ::PROTOBUF_NAMESPACE_ID::internal::InternalMetadataWithArenaLite _internal_metadata_; + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + bool composedbyhwc_; + friend struct ::TableStruct_LayerScopePacket_2eproto; +}; +// ------------------------------------------------------------------- + +class DrawPacket_Rect : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.layers.layerscope.DrawPacket.Rect) */ { + public: + DrawPacket_Rect(); + virtual ~DrawPacket_Rect(); + + DrawPacket_Rect(const DrawPacket_Rect& from); + DrawPacket_Rect(DrawPacket_Rect&& from) noexcept + : DrawPacket_Rect() { + *this = ::std::move(from); + } + + inline DrawPacket_Rect& operator=(const DrawPacket_Rect& from) { + CopyFrom(from); + return *this; + } + inline DrawPacket_Rect& operator=(DrawPacket_Rect&& from) noexcept { + if (GetArenaNoVirtual() == from.GetArenaNoVirtual()) { + if (this != &from) InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const DrawPacket_Rect& default_instance(); + + static void InitAsDefaultInstance(); // FOR INTERNAL USE ONLY + static inline const DrawPacket_Rect* internal_default_instance() { + return reinterpret_cast( + &_DrawPacket_Rect_default_instance_); + } + static constexpr int kIndexInFileMessages = + 15; + + friend void swap(DrawPacket_Rect& a, DrawPacket_Rect& b) { + a.Swap(&b); + } + inline void Swap(DrawPacket_Rect* other) { + if (other == this) return; + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + inline DrawPacket_Rect* New() const final { + return CreateMaybeMessage(nullptr); + } + + DrawPacket_Rect* New(::PROTOBUF_NAMESPACE_ID::Arena* arena) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) + final; + void CopyFrom(const DrawPacket_Rect& from); + void MergeFrom(const DrawPacket_Rect& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + ::PROTOBUF_NAMESPACE_ID::uint8* _InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + void DiscardUnknownFields(); + int GetCachedSize() const final { return _cached_size_.Get(); } + + private: + inline void SharedCtor(); + inline void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(DrawPacket_Rect* other); + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.layers.layerscope.DrawPacket.Rect"; + } + private: + inline ::PROTOBUF_NAMESPACE_ID::Arena* GetArenaNoVirtual() const { + return nullptr; + } + inline void* MaybeArenaPtr() const { + return nullptr; + } + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kXFieldNumber = 1, + kYFieldNumber = 2, + kWFieldNumber = 3, + kHFieldNumber = 4, + }; + // required float x = 1; + bool has_x() const; + private: + bool _internal_has_x() const; + public: + void clear_x(); + float x() const; + void set_x(float value); + private: + float _internal_x() const; + void _internal_set_x(float value); + public: + + // required float y = 2; + bool has_y() const; + private: + bool _internal_has_y() const; + public: + void clear_y(); + float y() const; + void set_y(float value); + private: + float _internal_y() const; + void _internal_set_y(float value); + public: + + // required float w = 3; + bool has_w() const; + private: + bool _internal_has_w() const; + public: + void clear_w(); + float w() const; + void set_w(float value); + private: + float _internal_w() const; + void _internal_set_w(float value); + public: + + // required float h = 4; + bool has_h() const; + private: + bool _internal_has_h() const; + public: + void clear_h(); + float h() const; + void set_h(float value); + private: + float _internal_h() const; + void _internal_set_h(float value); + public: + + // @@protoc_insertion_point(class_scope:mozilla.layers.layerscope.DrawPacket.Rect) + private: + class _Internal; + + // helper for ByteSizeLong() + size_t RequiredFieldsByteSizeFallback() const; + + ::PROTOBUF_NAMESPACE_ID::internal::InternalMetadataWithArenaLite _internal_metadata_; + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + float x_; + float y_; + float w_; + float h_; + friend struct ::TableStruct_LayerScopePacket_2eproto; +}; +// ------------------------------------------------------------------- + +class DrawPacket : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.layers.layerscope.DrawPacket) */ { + public: + DrawPacket(); + virtual ~DrawPacket(); + + DrawPacket(const DrawPacket& from); + DrawPacket(DrawPacket&& from) noexcept + : DrawPacket() { + *this = ::std::move(from); + } + + inline DrawPacket& operator=(const DrawPacket& from) { + CopyFrom(from); + return *this; + } + inline DrawPacket& operator=(DrawPacket&& from) noexcept { + if (GetArenaNoVirtual() == from.GetArenaNoVirtual()) { + if (this != &from) InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const DrawPacket& default_instance(); + + static void InitAsDefaultInstance(); // FOR INTERNAL USE ONLY + static inline const DrawPacket* internal_default_instance() { + return reinterpret_cast( + &_DrawPacket_default_instance_); + } + static constexpr int kIndexInFileMessages = + 16; + + friend void swap(DrawPacket& a, DrawPacket& b) { + a.Swap(&b); + } + inline void Swap(DrawPacket* other) { + if (other == this) return; + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + inline DrawPacket* New() const final { + return CreateMaybeMessage(nullptr); + } + + DrawPacket* New(::PROTOBUF_NAMESPACE_ID::Arena* arena) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) + final; + void CopyFrom(const DrawPacket& from); + void MergeFrom(const DrawPacket& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + ::PROTOBUF_NAMESPACE_ID::uint8* _InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + void DiscardUnknownFields(); + int GetCachedSize() const final { return _cached_size_.Get(); } + + private: + inline void SharedCtor(); + inline void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(DrawPacket* other); + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.layers.layerscope.DrawPacket"; + } + private: + inline ::PROTOBUF_NAMESPACE_ID::Arena* GetArenaNoVirtual() const { + return nullptr; + } + inline void* MaybeArenaPtr() const { + return nullptr; + } + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + typedef DrawPacket_Rect Rect; + + // accessors ------------------------------------------------------- + + enum : int { + kMvMatrixFieldNumber = 3, + kLayerRectFieldNumber = 5, + kTexIDsFieldNumber = 7, + kTextureRectFieldNumber = 8, + kOffsetXFieldNumber = 1, + kOffsetYFieldNumber = 2, + kLayerrefFieldNumber = 6, + kTotalRectsFieldNumber = 4, + }; + // repeated float mvMatrix = 3; + int mvmatrix_size() const; + private: + int _internal_mvmatrix_size() const; + public: + void clear_mvmatrix(); + private: + float _internal_mvmatrix(int index) const; + const ::PROTOBUF_NAMESPACE_ID::RepeatedField< float >& + _internal_mvmatrix() const; + void _internal_add_mvmatrix(float value); + ::PROTOBUF_NAMESPACE_ID::RepeatedField< float >* + _internal_mutable_mvmatrix(); + public: + float mvmatrix(int index) const; + void set_mvmatrix(int index, float value); + void add_mvmatrix(float value); + const ::PROTOBUF_NAMESPACE_ID::RepeatedField< float >& + mvmatrix() const; + ::PROTOBUF_NAMESPACE_ID::RepeatedField< float >* + mutable_mvmatrix(); + + // repeated .mozilla.layers.layerscope.DrawPacket.Rect layerRect = 5; + int layerrect_size() const; + private: + int _internal_layerrect_size() const; + public: + void clear_layerrect(); + ::mozilla::layers::layerscope::DrawPacket_Rect* mutable_layerrect(int index); + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::layers::layerscope::DrawPacket_Rect >* + mutable_layerrect(); + private: + const ::mozilla::layers::layerscope::DrawPacket_Rect& _internal_layerrect(int index) const; + ::mozilla::layers::layerscope::DrawPacket_Rect* _internal_add_layerrect(); + public: + const ::mozilla::layers::layerscope::DrawPacket_Rect& layerrect(int index) const; + ::mozilla::layers::layerscope::DrawPacket_Rect* add_layerrect(); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::layers::layerscope::DrawPacket_Rect >& + layerrect() const; + + // repeated uint32 texIDs = 7; + int texids_size() const; + private: + int _internal_texids_size() const; + public: + void clear_texids(); + private: + ::PROTOBUF_NAMESPACE_ID::uint32 _internal_texids(int index) const; + const ::PROTOBUF_NAMESPACE_ID::RepeatedField< ::PROTOBUF_NAMESPACE_ID::uint32 >& + _internal_texids() const; + void _internal_add_texids(::PROTOBUF_NAMESPACE_ID::uint32 value); + ::PROTOBUF_NAMESPACE_ID::RepeatedField< ::PROTOBUF_NAMESPACE_ID::uint32 >* + _internal_mutable_texids(); + public: + ::PROTOBUF_NAMESPACE_ID::uint32 texids(int index) const; + void set_texids(int index, ::PROTOBUF_NAMESPACE_ID::uint32 value); + void add_texids(::PROTOBUF_NAMESPACE_ID::uint32 value); + const ::PROTOBUF_NAMESPACE_ID::RepeatedField< ::PROTOBUF_NAMESPACE_ID::uint32 >& + texids() const; + ::PROTOBUF_NAMESPACE_ID::RepeatedField< ::PROTOBUF_NAMESPACE_ID::uint32 >* + mutable_texids(); + + // repeated .mozilla.layers.layerscope.DrawPacket.Rect textureRect = 8; + int texturerect_size() const; + private: + int _internal_texturerect_size() const; + public: + void clear_texturerect(); + ::mozilla::layers::layerscope::DrawPacket_Rect* mutable_texturerect(int index); + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::layers::layerscope::DrawPacket_Rect >* + mutable_texturerect(); + private: + const ::mozilla::layers::layerscope::DrawPacket_Rect& _internal_texturerect(int index) const; + ::mozilla::layers::layerscope::DrawPacket_Rect* _internal_add_texturerect(); + public: + const ::mozilla::layers::layerscope::DrawPacket_Rect& texturerect(int index) const; + ::mozilla::layers::layerscope::DrawPacket_Rect* add_texturerect(); + const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::layers::layerscope::DrawPacket_Rect >& + texturerect() const; + + // required float offsetX = 1; + bool has_offsetx() const; + private: + bool _internal_has_offsetx() const; + public: + void clear_offsetx(); + float offsetx() const; + void set_offsetx(float value); + private: + float _internal_offsetx() const; + void _internal_set_offsetx(float value); + public: + + // required float offsetY = 2; + bool has_offsety() const; + private: + bool _internal_has_offsety() const; + public: + void clear_offsety(); + float offsety() const; + void set_offsety(float value); + private: + float _internal_offsety() const; + void _internal_set_offsety(float value); + public: + + // required uint64 layerref = 6; + bool has_layerref() const; + private: + bool _internal_has_layerref() const; + public: + void clear_layerref(); + ::PROTOBUF_NAMESPACE_ID::uint64 layerref() const; + void set_layerref(::PROTOBUF_NAMESPACE_ID::uint64 value); + private: + ::PROTOBUF_NAMESPACE_ID::uint64 _internal_layerref() const; + void _internal_set_layerref(::PROTOBUF_NAMESPACE_ID::uint64 value); + public: + + // required uint32 totalRects = 4; + bool has_totalrects() const; + private: + bool _internal_has_totalrects() const; + public: + void clear_totalrects(); + ::PROTOBUF_NAMESPACE_ID::uint32 totalrects() const; + void set_totalrects(::PROTOBUF_NAMESPACE_ID::uint32 value); + private: + ::PROTOBUF_NAMESPACE_ID::uint32 _internal_totalrects() const; + void _internal_set_totalrects(::PROTOBUF_NAMESPACE_ID::uint32 value); + public: + + // @@protoc_insertion_point(class_scope:mozilla.layers.layerscope.DrawPacket) + private: + class _Internal; + + // helper for ByteSizeLong() + size_t RequiredFieldsByteSizeFallback() const; + + ::PROTOBUF_NAMESPACE_ID::internal::InternalMetadataWithArenaLite _internal_metadata_; + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::PROTOBUF_NAMESPACE_ID::RepeatedField< float > mvmatrix_; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::layers::layerscope::DrawPacket_Rect > layerrect_; + ::PROTOBUF_NAMESPACE_ID::RepeatedField< ::PROTOBUF_NAMESPACE_ID::uint32 > texids_; + ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::layers::layerscope::DrawPacket_Rect > texturerect_; + float offsetx_; + float offsety_; + ::PROTOBUF_NAMESPACE_ID::uint64 layerref_; + ::PROTOBUF_NAMESPACE_ID::uint32 totalrects_; + friend struct ::TableStruct_LayerScopePacket_2eproto; +}; +// ------------------------------------------------------------------- + +class Packet : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.layers.layerscope.Packet) */ { + public: + Packet(); + virtual ~Packet(); + + Packet(const Packet& from); + Packet(Packet&& from) noexcept + : Packet() { + *this = ::std::move(from); + } + + inline Packet& operator=(const Packet& from) { + CopyFrom(from); + return *this; + } + inline Packet& operator=(Packet&& from) noexcept { + if (GetArenaNoVirtual() == from.GetArenaNoVirtual()) { + if (this != &from) InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const Packet& default_instance(); + + static void InitAsDefaultInstance(); // FOR INTERNAL USE ONLY + static inline const Packet* internal_default_instance() { + return reinterpret_cast( + &_Packet_default_instance_); + } + static constexpr int kIndexInFileMessages = + 17; + + friend void swap(Packet& a, Packet& b) { + a.Swap(&b); + } + inline void Swap(Packet* other) { + if (other == this) return; + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + inline Packet* New() const final { + return CreateMaybeMessage(nullptr); + } + + Packet* New(::PROTOBUF_NAMESPACE_ID::Arena* arena) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) + final; + void CopyFrom(const Packet& from); + void MergeFrom(const Packet& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + ::PROTOBUF_NAMESPACE_ID::uint8* _InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + void DiscardUnknownFields(); + int GetCachedSize() const final { return _cached_size_.Get(); } + + private: + inline void SharedCtor(); + inline void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(Packet* other); + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.layers.layerscope.Packet"; + } + private: + inline ::PROTOBUF_NAMESPACE_ID::Arena* GetArenaNoVirtual() const { + return nullptr; + } + inline void* MaybeArenaPtr() const { + return nullptr; + } + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + typedef Packet_DataType DataType; + static constexpr DataType FRAMESTART = + Packet_DataType_FRAMESTART; + static constexpr DataType FRAMEEND = + Packet_DataType_FRAMEEND; + static constexpr DataType COLOR = + Packet_DataType_COLOR; + static constexpr DataType TEXTURE = + Packet_DataType_TEXTURE; + static constexpr DataType LAYERS = + Packet_DataType_LAYERS; + static constexpr DataType META = + Packet_DataType_META; + static constexpr DataType DRAW = + Packet_DataType_DRAW; + static inline bool DataType_IsValid(int value) { + return Packet_DataType_IsValid(value); + } + static constexpr DataType DataType_MIN = + Packet_DataType_DataType_MIN; + static constexpr DataType DataType_MAX = + Packet_DataType_DataType_MAX; + static constexpr int DataType_ARRAYSIZE = + Packet_DataType_DataType_ARRAYSIZE; + template + static inline const std::string& DataType_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function DataType_Name."); + return Packet_DataType_Name(enum_t_value); + } + static inline bool DataType_Parse(const std::string& name, + DataType* value) { + return Packet_DataType_Parse(name, value); + } + + // accessors ------------------------------------------------------- + + enum : int { + kFrameFieldNumber = 2, + kColorFieldNumber = 3, + kTextureFieldNumber = 4, + kLayersFieldNumber = 5, + kMetaFieldNumber = 6, + kDrawFieldNumber = 7, + kTypeFieldNumber = 1, + }; + // optional .mozilla.layers.layerscope.FramePacket frame = 2; + bool has_frame() const; + private: + bool _internal_has_frame() const; + public: + void clear_frame(); + const ::mozilla::layers::layerscope::FramePacket& frame() const; + ::mozilla::layers::layerscope::FramePacket* release_frame(); + ::mozilla::layers::layerscope::FramePacket* mutable_frame(); + void set_allocated_frame(::mozilla::layers::layerscope::FramePacket* frame); + private: + const ::mozilla::layers::layerscope::FramePacket& _internal_frame() const; + ::mozilla::layers::layerscope::FramePacket* _internal_mutable_frame(); + public: + + // optional .mozilla.layers.layerscope.ColorPacket color = 3; + bool has_color() const; + private: + bool _internal_has_color() const; + public: + void clear_color(); + const ::mozilla::layers::layerscope::ColorPacket& color() const; + ::mozilla::layers::layerscope::ColorPacket* release_color(); + ::mozilla::layers::layerscope::ColorPacket* mutable_color(); + void set_allocated_color(::mozilla::layers::layerscope::ColorPacket* color); + private: + const ::mozilla::layers::layerscope::ColorPacket& _internal_color() const; + ::mozilla::layers::layerscope::ColorPacket* _internal_mutable_color(); + public: + + // optional .mozilla.layers.layerscope.TexturePacket texture = 4; + bool has_texture() const; + private: + bool _internal_has_texture() const; + public: + void clear_texture(); + const ::mozilla::layers::layerscope::TexturePacket& texture() const; + ::mozilla::layers::layerscope::TexturePacket* release_texture(); + ::mozilla::layers::layerscope::TexturePacket* mutable_texture(); + void set_allocated_texture(::mozilla::layers::layerscope::TexturePacket* texture); + private: + const ::mozilla::layers::layerscope::TexturePacket& _internal_texture() const; + ::mozilla::layers::layerscope::TexturePacket* _internal_mutable_texture(); + public: + + // optional .mozilla.layers.layerscope.LayersPacket layers = 5; + bool has_layers() const; + private: + bool _internal_has_layers() const; + public: + void clear_layers(); + const ::mozilla::layers::layerscope::LayersPacket& layers() const; + ::mozilla::layers::layerscope::LayersPacket* release_layers(); + ::mozilla::layers::layerscope::LayersPacket* mutable_layers(); + void set_allocated_layers(::mozilla::layers::layerscope::LayersPacket* layers); + private: + const ::mozilla::layers::layerscope::LayersPacket& _internal_layers() const; + ::mozilla::layers::layerscope::LayersPacket* _internal_mutable_layers(); + public: + + // optional .mozilla.layers.layerscope.MetaPacket meta = 6; + bool has_meta() const; + private: + bool _internal_has_meta() const; + public: + void clear_meta(); + const ::mozilla::layers::layerscope::MetaPacket& meta() const; + ::mozilla::layers::layerscope::MetaPacket* release_meta(); + ::mozilla::layers::layerscope::MetaPacket* mutable_meta(); + void set_allocated_meta(::mozilla::layers::layerscope::MetaPacket* meta); + private: + const ::mozilla::layers::layerscope::MetaPacket& _internal_meta() const; + ::mozilla::layers::layerscope::MetaPacket* _internal_mutable_meta(); + public: + + // optional .mozilla.layers.layerscope.DrawPacket draw = 7; + bool has_draw() const; + private: + bool _internal_has_draw() const; + public: + void clear_draw(); + const ::mozilla::layers::layerscope::DrawPacket& draw() const; + ::mozilla::layers::layerscope::DrawPacket* release_draw(); + ::mozilla::layers::layerscope::DrawPacket* mutable_draw(); + void set_allocated_draw(::mozilla::layers::layerscope::DrawPacket* draw); + private: + const ::mozilla::layers::layerscope::DrawPacket& _internal_draw() const; + ::mozilla::layers::layerscope::DrawPacket* _internal_mutable_draw(); + public: + + // required .mozilla.layers.layerscope.Packet.DataType type = 1; + bool has_type() const; + private: + bool _internal_has_type() const; + public: + void clear_type(); + ::mozilla::layers::layerscope::Packet_DataType type() const; + void set_type(::mozilla::layers::layerscope::Packet_DataType value); + private: + ::mozilla::layers::layerscope::Packet_DataType _internal_type() const; + void _internal_set_type(::mozilla::layers::layerscope::Packet_DataType value); + public: + + // @@protoc_insertion_point(class_scope:mozilla.layers.layerscope.Packet) + private: + class _Internal; + + ::PROTOBUF_NAMESPACE_ID::internal::InternalMetadataWithArenaLite _internal_metadata_; + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + ::mozilla::layers::layerscope::FramePacket* frame_; + ::mozilla::layers::layerscope::ColorPacket* color_; + ::mozilla::layers::layerscope::TexturePacket* texture_; + ::mozilla::layers::layerscope::LayersPacket* layers_; + ::mozilla::layers::layerscope::MetaPacket* meta_; + ::mozilla::layers::layerscope::DrawPacket* draw_; + int type_; + friend struct ::TableStruct_LayerScopePacket_2eproto; +}; +// ------------------------------------------------------------------- + +class CommandPacket : + public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.layers.layerscope.CommandPacket) */ { + public: + CommandPacket(); + virtual ~CommandPacket(); + + CommandPacket(const CommandPacket& from); + CommandPacket(CommandPacket&& from) noexcept + : CommandPacket() { + *this = ::std::move(from); + } + + inline CommandPacket& operator=(const CommandPacket& from) { + CopyFrom(from); + return *this; + } + inline CommandPacket& operator=(CommandPacket&& from) noexcept { + if (GetArenaNoVirtual() == from.GetArenaNoVirtual()) { + if (this != &from) InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + inline const std::string& unknown_fields() const { + return _internal_metadata_.unknown_fields(); + } + inline std::string* mutable_unknown_fields() { + return _internal_metadata_.mutable_unknown_fields(); + } + + static const CommandPacket& default_instance(); + + static void InitAsDefaultInstance(); // FOR INTERNAL USE ONLY + static inline const CommandPacket* internal_default_instance() { + return reinterpret_cast( + &_CommandPacket_default_instance_); + } + static constexpr int kIndexInFileMessages = + 18; + + friend void swap(CommandPacket& a, CommandPacket& b) { + a.Swap(&b); + } + inline void Swap(CommandPacket* other) { + if (other == this) return; + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + inline CommandPacket* New() const final { + return CreateMaybeMessage(nullptr); + } + + CommandPacket* New(::PROTOBUF_NAMESPACE_ID::Arena* arena) const final { + return CreateMaybeMessage(arena); + } + void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) + final; + void CopyFrom(const CommandPacket& from); + void MergeFrom(const CommandPacket& from); + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + ::PROTOBUF_NAMESPACE_ID::uint8* _InternalSerialize( + ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + void DiscardUnknownFields(); + int GetCachedSize() const final { return _cached_size_.Get(); } + + private: + inline void SharedCtor(); + inline void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(CommandPacket* other); + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "mozilla.layers.layerscope.CommandPacket"; + } + private: + inline ::PROTOBUF_NAMESPACE_ID::Arena* GetArenaNoVirtual() const { + return nullptr; + } + inline void* MaybeArenaPtr() const { + return nullptr; + } + public: + + std::string GetTypeName() const final; + + // nested types ---------------------------------------------------- + + typedef CommandPacket_CmdType CmdType; + static constexpr CmdType NO_OP = + CommandPacket_CmdType_NO_OP; + static constexpr CmdType LAYERS_TREE = + CommandPacket_CmdType_LAYERS_TREE; + static constexpr CmdType LAYERS_BUFFER = + CommandPacket_CmdType_LAYERS_BUFFER; + static inline bool CmdType_IsValid(int value) { + return CommandPacket_CmdType_IsValid(value); + } + static constexpr CmdType CmdType_MIN = + CommandPacket_CmdType_CmdType_MIN; + static constexpr CmdType CmdType_MAX = + CommandPacket_CmdType_CmdType_MAX; + static constexpr int CmdType_ARRAYSIZE = + CommandPacket_CmdType_CmdType_ARRAYSIZE; + template + static inline const std::string& CmdType_Name(T enum_t_value) { + static_assert(::std::is_same::value || + ::std::is_integral::value, + "Incorrect type passed to function CmdType_Name."); + return CommandPacket_CmdType_Name(enum_t_value); + } + static inline bool CmdType_Parse(const std::string& name, + CmdType* value) { + return CommandPacket_CmdType_Parse(name, value); + } + + // accessors ------------------------------------------------------- + + enum : int { + kTypeFieldNumber = 1, + kValueFieldNumber = 2, + }; + // required .mozilla.layers.layerscope.CommandPacket.CmdType type = 1; + bool has_type() const; + private: + bool _internal_has_type() const; + public: + void clear_type(); + ::mozilla::layers::layerscope::CommandPacket_CmdType type() const; + void set_type(::mozilla::layers::layerscope::CommandPacket_CmdType value); + private: + ::mozilla::layers::layerscope::CommandPacket_CmdType _internal_type() const; + void _internal_set_type(::mozilla::layers::layerscope::CommandPacket_CmdType value); + public: + + // optional bool value = 2; + bool has_value() const; + private: + bool _internal_has_value() const; + public: + void clear_value(); + bool value() const; + void set_value(bool value); + private: + bool _internal_value() const; + void _internal_set_value(bool value); + public: + + // @@protoc_insertion_point(class_scope:mozilla.layers.layerscope.CommandPacket) + private: + class _Internal; + + ::PROTOBUF_NAMESPACE_ID::internal::InternalMetadataWithArenaLite _internal_metadata_; + ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + int type_; + bool value_; + friend struct ::TableStruct_LayerScopePacket_2eproto; +}; +// =================================================================== + + +// =================================================================== + +#ifdef __GNUC__ + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wstrict-aliasing" +#endif // __GNUC__ +// FramePacket + +// optional uint64 value = 1; +inline bool FramePacket::_internal_has_value() const { + bool value = (_has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool FramePacket::has_value() const { + return _internal_has_value(); +} +inline void FramePacket::clear_value() { + value_ = PROTOBUF_ULONGLONG(0); + _has_bits_[0] &= ~0x00000001u; +} +inline ::PROTOBUF_NAMESPACE_ID::uint64 FramePacket::_internal_value() const { + return value_; +} +inline ::PROTOBUF_NAMESPACE_ID::uint64 FramePacket::value() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.FramePacket.value) + return _internal_value(); +} +inline void FramePacket::_internal_set_value(::PROTOBUF_NAMESPACE_ID::uint64 value) { + _has_bits_[0] |= 0x00000001u; + value_ = value; +} +inline void FramePacket::set_value(::PROTOBUF_NAMESPACE_ID::uint64 value) { + _internal_set_value(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.FramePacket.value) +} + +// optional float scale = 2; +inline bool FramePacket::_internal_has_scale() const { + bool value = (_has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool FramePacket::has_scale() const { + return _internal_has_scale(); +} +inline void FramePacket::clear_scale() { + scale_ = 0; + _has_bits_[0] &= ~0x00000002u; +} +inline float FramePacket::_internal_scale() const { + return scale_; +} +inline float FramePacket::scale() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.FramePacket.scale) + return _internal_scale(); +} +inline void FramePacket::_internal_set_scale(float value) { + _has_bits_[0] |= 0x00000002u; + scale_ = value; +} +inline void FramePacket::set_scale(float value) { + _internal_set_scale(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.FramePacket.scale) +} + +// ------------------------------------------------------------------- + +// ColorPacket + +// required uint64 layerref = 1; +inline bool ColorPacket::_internal_has_layerref() const { + bool value = (_has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool ColorPacket::has_layerref() const { + return _internal_has_layerref(); +} +inline void ColorPacket::clear_layerref() { + layerref_ = PROTOBUF_ULONGLONG(0); + _has_bits_[0] &= ~0x00000001u; +} +inline ::PROTOBUF_NAMESPACE_ID::uint64 ColorPacket::_internal_layerref() const { + return layerref_; +} +inline ::PROTOBUF_NAMESPACE_ID::uint64 ColorPacket::layerref() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.ColorPacket.layerref) + return _internal_layerref(); +} +inline void ColorPacket::_internal_set_layerref(::PROTOBUF_NAMESPACE_ID::uint64 value) { + _has_bits_[0] |= 0x00000001u; + layerref_ = value; +} +inline void ColorPacket::set_layerref(::PROTOBUF_NAMESPACE_ID::uint64 value) { + _internal_set_layerref(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.ColorPacket.layerref) +} + +// optional uint32 width = 2; +inline bool ColorPacket::_internal_has_width() const { + bool value = (_has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool ColorPacket::has_width() const { + return _internal_has_width(); +} +inline void ColorPacket::clear_width() { + width_ = 0u; + _has_bits_[0] &= ~0x00000002u; +} +inline ::PROTOBUF_NAMESPACE_ID::uint32 ColorPacket::_internal_width() const { + return width_; +} +inline ::PROTOBUF_NAMESPACE_ID::uint32 ColorPacket::width() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.ColorPacket.width) + return _internal_width(); +} +inline void ColorPacket::_internal_set_width(::PROTOBUF_NAMESPACE_ID::uint32 value) { + _has_bits_[0] |= 0x00000002u; + width_ = value; +} +inline void ColorPacket::set_width(::PROTOBUF_NAMESPACE_ID::uint32 value) { + _internal_set_width(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.ColorPacket.width) +} + +// optional uint32 height = 3; +inline bool ColorPacket::_internal_has_height() const { + bool value = (_has_bits_[0] & 0x00000004u) != 0; + return value; +} +inline bool ColorPacket::has_height() const { + return _internal_has_height(); +} +inline void ColorPacket::clear_height() { + height_ = 0u; + _has_bits_[0] &= ~0x00000004u; +} +inline ::PROTOBUF_NAMESPACE_ID::uint32 ColorPacket::_internal_height() const { + return height_; +} +inline ::PROTOBUF_NAMESPACE_ID::uint32 ColorPacket::height() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.ColorPacket.height) + return _internal_height(); +} +inline void ColorPacket::_internal_set_height(::PROTOBUF_NAMESPACE_ID::uint32 value) { + _has_bits_[0] |= 0x00000004u; + height_ = value; +} +inline void ColorPacket::set_height(::PROTOBUF_NAMESPACE_ID::uint32 value) { + _internal_set_height(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.ColorPacket.height) +} + +// optional uint32 color = 4; +inline bool ColorPacket::_internal_has_color() const { + bool value = (_has_bits_[0] & 0x00000008u) != 0; + return value; +} +inline bool ColorPacket::has_color() const { + return _internal_has_color(); +} +inline void ColorPacket::clear_color() { + color_ = 0u; + _has_bits_[0] &= ~0x00000008u; +} +inline ::PROTOBUF_NAMESPACE_ID::uint32 ColorPacket::_internal_color() const { + return color_; +} +inline ::PROTOBUF_NAMESPACE_ID::uint32 ColorPacket::color() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.ColorPacket.color) + return _internal_color(); +} +inline void ColorPacket::_internal_set_color(::PROTOBUF_NAMESPACE_ID::uint32 value) { + _has_bits_[0] |= 0x00000008u; + color_ = value; +} +inline void ColorPacket::set_color(::PROTOBUF_NAMESPACE_ID::uint32 value) { + _internal_set_color(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.ColorPacket.color) +} + +// ------------------------------------------------------------------- + +// TexturePacket_Rect + +// optional float x = 1; +inline bool TexturePacket_Rect::_internal_has_x() const { + bool value = (_has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool TexturePacket_Rect::has_x() const { + return _internal_has_x(); +} +inline void TexturePacket_Rect::clear_x() { + x_ = 0; + _has_bits_[0] &= ~0x00000001u; +} +inline float TexturePacket_Rect::_internal_x() const { + return x_; +} +inline float TexturePacket_Rect::x() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.TexturePacket.Rect.x) + return _internal_x(); +} +inline void TexturePacket_Rect::_internal_set_x(float value) { + _has_bits_[0] |= 0x00000001u; + x_ = value; +} +inline void TexturePacket_Rect::set_x(float value) { + _internal_set_x(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.TexturePacket.Rect.x) +} + +// optional float y = 2; +inline bool TexturePacket_Rect::_internal_has_y() const { + bool value = (_has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool TexturePacket_Rect::has_y() const { + return _internal_has_y(); +} +inline void TexturePacket_Rect::clear_y() { + y_ = 0; + _has_bits_[0] &= ~0x00000002u; +} +inline float TexturePacket_Rect::_internal_y() const { + return y_; +} +inline float TexturePacket_Rect::y() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.TexturePacket.Rect.y) + return _internal_y(); +} +inline void TexturePacket_Rect::_internal_set_y(float value) { + _has_bits_[0] |= 0x00000002u; + y_ = value; +} +inline void TexturePacket_Rect::set_y(float value) { + _internal_set_y(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.TexturePacket.Rect.y) +} + +// optional float w = 3; +inline bool TexturePacket_Rect::_internal_has_w() const { + bool value = (_has_bits_[0] & 0x00000004u) != 0; + return value; +} +inline bool TexturePacket_Rect::has_w() const { + return _internal_has_w(); +} +inline void TexturePacket_Rect::clear_w() { + w_ = 0; + _has_bits_[0] &= ~0x00000004u; +} +inline float TexturePacket_Rect::_internal_w() const { + return w_; +} +inline float TexturePacket_Rect::w() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.TexturePacket.Rect.w) + return _internal_w(); +} +inline void TexturePacket_Rect::_internal_set_w(float value) { + _has_bits_[0] |= 0x00000004u; + w_ = value; +} +inline void TexturePacket_Rect::set_w(float value) { + _internal_set_w(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.TexturePacket.Rect.w) +} + +// optional float h = 4; +inline bool TexturePacket_Rect::_internal_has_h() const { + bool value = (_has_bits_[0] & 0x00000008u) != 0; + return value; +} +inline bool TexturePacket_Rect::has_h() const { + return _internal_has_h(); +} +inline void TexturePacket_Rect::clear_h() { + h_ = 0; + _has_bits_[0] &= ~0x00000008u; +} +inline float TexturePacket_Rect::_internal_h() const { + return h_; +} +inline float TexturePacket_Rect::h() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.TexturePacket.Rect.h) + return _internal_h(); +} +inline void TexturePacket_Rect::_internal_set_h(float value) { + _has_bits_[0] |= 0x00000008u; + h_ = value; +} +inline void TexturePacket_Rect::set_h(float value) { + _internal_set_h(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.TexturePacket.Rect.h) +} + +// ------------------------------------------------------------------- + +// TexturePacket_Size + +// optional int32 w = 1; +inline bool TexturePacket_Size::_internal_has_w() const { + bool value = (_has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool TexturePacket_Size::has_w() const { + return _internal_has_w(); +} +inline void TexturePacket_Size::clear_w() { + w_ = 0; + _has_bits_[0] &= ~0x00000001u; +} +inline ::PROTOBUF_NAMESPACE_ID::int32 TexturePacket_Size::_internal_w() const { + return w_; +} +inline ::PROTOBUF_NAMESPACE_ID::int32 TexturePacket_Size::w() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.TexturePacket.Size.w) + return _internal_w(); +} +inline void TexturePacket_Size::_internal_set_w(::PROTOBUF_NAMESPACE_ID::int32 value) { + _has_bits_[0] |= 0x00000001u; + w_ = value; +} +inline void TexturePacket_Size::set_w(::PROTOBUF_NAMESPACE_ID::int32 value) { + _internal_set_w(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.TexturePacket.Size.w) +} + +// optional int32 h = 2; +inline bool TexturePacket_Size::_internal_has_h() const { + bool value = (_has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool TexturePacket_Size::has_h() const { + return _internal_has_h(); +} +inline void TexturePacket_Size::clear_h() { + h_ = 0; + _has_bits_[0] &= ~0x00000002u; +} +inline ::PROTOBUF_NAMESPACE_ID::int32 TexturePacket_Size::_internal_h() const { + return h_; +} +inline ::PROTOBUF_NAMESPACE_ID::int32 TexturePacket_Size::h() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.TexturePacket.Size.h) + return _internal_h(); +} +inline void TexturePacket_Size::_internal_set_h(::PROTOBUF_NAMESPACE_ID::int32 value) { + _has_bits_[0] |= 0x00000002u; + h_ = value; +} +inline void TexturePacket_Size::set_h(::PROTOBUF_NAMESPACE_ID::int32 value) { + _internal_set_h(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.TexturePacket.Size.h) +} + +// ------------------------------------------------------------------- + +// TexturePacket_Matrix + +// optional bool is2D = 1; +inline bool TexturePacket_Matrix::_internal_has_is2d() const { + bool value = (_has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool TexturePacket_Matrix::has_is2d() const { + return _internal_has_is2d(); +} +inline void TexturePacket_Matrix::clear_is2d() { + is2d_ = false; + _has_bits_[0] &= ~0x00000001u; +} +inline bool TexturePacket_Matrix::_internal_is2d() const { + return is2d_; +} +inline bool TexturePacket_Matrix::is2d() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.TexturePacket.Matrix.is2D) + return _internal_is2d(); +} +inline void TexturePacket_Matrix::_internal_set_is2d(bool value) { + _has_bits_[0] |= 0x00000001u; + is2d_ = value; +} +inline void TexturePacket_Matrix::set_is2d(bool value) { + _internal_set_is2d(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.TexturePacket.Matrix.is2D) +} + +// optional bool isId = 2; +inline bool TexturePacket_Matrix::_internal_has_isid() const { + bool value = (_has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool TexturePacket_Matrix::has_isid() const { + return _internal_has_isid(); +} +inline void TexturePacket_Matrix::clear_isid() { + isid_ = false; + _has_bits_[0] &= ~0x00000002u; +} +inline bool TexturePacket_Matrix::_internal_isid() const { + return isid_; +} +inline bool TexturePacket_Matrix::isid() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.TexturePacket.Matrix.isId) + return _internal_isid(); +} +inline void TexturePacket_Matrix::_internal_set_isid(bool value) { + _has_bits_[0] |= 0x00000002u; + isid_ = value; +} +inline void TexturePacket_Matrix::set_isid(bool value) { + _internal_set_isid(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.TexturePacket.Matrix.isId) +} + +// repeated float m = 3; +inline int TexturePacket_Matrix::_internal_m_size() const { + return m_.size(); +} +inline int TexturePacket_Matrix::m_size() const { + return _internal_m_size(); +} +inline void TexturePacket_Matrix::clear_m() { + m_.Clear(); +} +inline float TexturePacket_Matrix::_internal_m(int index) const { + return m_.Get(index); +} +inline float TexturePacket_Matrix::m(int index) const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.TexturePacket.Matrix.m) + return _internal_m(index); +} +inline void TexturePacket_Matrix::set_m(int index, float value) { + m_.Set(index, value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.TexturePacket.Matrix.m) +} +inline void TexturePacket_Matrix::_internal_add_m(float value) { + m_.Add(value); +} +inline void TexturePacket_Matrix::add_m(float value) { + _internal_add_m(value); + // @@protoc_insertion_point(field_add:mozilla.layers.layerscope.TexturePacket.Matrix.m) +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedField< float >& +TexturePacket_Matrix::_internal_m() const { + return m_; +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedField< float >& +TexturePacket_Matrix::m() const { + // @@protoc_insertion_point(field_list:mozilla.layers.layerscope.TexturePacket.Matrix.m) + return _internal_m(); +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedField< float >* +TexturePacket_Matrix::_internal_mutable_m() { + return &m_; +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedField< float >* +TexturePacket_Matrix::mutable_m() { + // @@protoc_insertion_point(field_mutable_list:mozilla.layers.layerscope.TexturePacket.Matrix.m) + return _internal_mutable_m(); +} + +// ------------------------------------------------------------------- + +// TexturePacket_EffectMask + +// optional bool mIs3D = 1; +inline bool TexturePacket_EffectMask::_internal_has_mis3d() const { + bool value = (_has_bits_[0] & 0x00000004u) != 0; + return value; +} +inline bool TexturePacket_EffectMask::has_mis3d() const { + return _internal_has_mis3d(); +} +inline void TexturePacket_EffectMask::clear_mis3d() { + mis3d_ = false; + _has_bits_[0] &= ~0x00000004u; +} +inline bool TexturePacket_EffectMask::_internal_mis3d() const { + return mis3d_; +} +inline bool TexturePacket_EffectMask::mis3d() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.TexturePacket.EffectMask.mIs3D) + return _internal_mis3d(); +} +inline void TexturePacket_EffectMask::_internal_set_mis3d(bool value) { + _has_bits_[0] |= 0x00000004u; + mis3d_ = value; +} +inline void TexturePacket_EffectMask::set_mis3d(bool value) { + _internal_set_mis3d(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.TexturePacket.EffectMask.mIs3D) +} + +// optional .mozilla.layers.layerscope.TexturePacket.Size mSize = 2; +inline bool TexturePacket_EffectMask::_internal_has_msize() const { + bool value = (_has_bits_[0] & 0x00000001u) != 0; + PROTOBUF_ASSUME(!value || msize_ != nullptr); + return value; +} +inline bool TexturePacket_EffectMask::has_msize() const { + return _internal_has_msize(); +} +inline void TexturePacket_EffectMask::clear_msize() { + if (msize_ != nullptr) msize_->Clear(); + _has_bits_[0] &= ~0x00000001u; +} +inline const ::mozilla::layers::layerscope::TexturePacket_Size& TexturePacket_EffectMask::_internal_msize() const { + const ::mozilla::layers::layerscope::TexturePacket_Size* p = msize_; + return p != nullptr ? *p : *reinterpret_cast( + &::mozilla::layers::layerscope::_TexturePacket_Size_default_instance_); +} +inline const ::mozilla::layers::layerscope::TexturePacket_Size& TexturePacket_EffectMask::msize() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.TexturePacket.EffectMask.mSize) + return _internal_msize(); +} +inline ::mozilla::layers::layerscope::TexturePacket_Size* TexturePacket_EffectMask::release_msize() { + // @@protoc_insertion_point(field_release:mozilla.layers.layerscope.TexturePacket.EffectMask.mSize) + _has_bits_[0] &= ~0x00000001u; + ::mozilla::layers::layerscope::TexturePacket_Size* temp = msize_; + msize_ = nullptr; + return temp; +} +inline ::mozilla::layers::layerscope::TexturePacket_Size* TexturePacket_EffectMask::_internal_mutable_msize() { + _has_bits_[0] |= 0x00000001u; + if (msize_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::layers::layerscope::TexturePacket_Size>(GetArenaNoVirtual()); + msize_ = p; + } + return msize_; +} +inline ::mozilla::layers::layerscope::TexturePacket_Size* TexturePacket_EffectMask::mutable_msize() { + // @@protoc_insertion_point(field_mutable:mozilla.layers.layerscope.TexturePacket.EffectMask.mSize) + return _internal_mutable_msize(); +} +inline void TexturePacket_EffectMask::set_allocated_msize(::mozilla::layers::layerscope::TexturePacket_Size* msize) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaNoVirtual(); + if (message_arena == nullptr) { + delete msize_; + } + if (msize) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = nullptr; + if (message_arena != submessage_arena) { + msize = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, msize, submessage_arena); + } + _has_bits_[0] |= 0x00000001u; + } else { + _has_bits_[0] &= ~0x00000001u; + } + msize_ = msize; + // @@protoc_insertion_point(field_set_allocated:mozilla.layers.layerscope.TexturePacket.EffectMask.mSize) +} + +// optional .mozilla.layers.layerscope.TexturePacket.Matrix mMaskTransform = 3; +inline bool TexturePacket_EffectMask::_internal_has_mmasktransform() const { + bool value = (_has_bits_[0] & 0x00000002u) != 0; + PROTOBUF_ASSUME(!value || mmasktransform_ != nullptr); + return value; +} +inline bool TexturePacket_EffectMask::has_mmasktransform() const { + return _internal_has_mmasktransform(); +} +inline void TexturePacket_EffectMask::clear_mmasktransform() { + if (mmasktransform_ != nullptr) mmasktransform_->Clear(); + _has_bits_[0] &= ~0x00000002u; +} +inline const ::mozilla::layers::layerscope::TexturePacket_Matrix& TexturePacket_EffectMask::_internal_mmasktransform() const { + const ::mozilla::layers::layerscope::TexturePacket_Matrix* p = mmasktransform_; + return p != nullptr ? *p : *reinterpret_cast( + &::mozilla::layers::layerscope::_TexturePacket_Matrix_default_instance_); +} +inline const ::mozilla::layers::layerscope::TexturePacket_Matrix& TexturePacket_EffectMask::mmasktransform() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.TexturePacket.EffectMask.mMaskTransform) + return _internal_mmasktransform(); +} +inline ::mozilla::layers::layerscope::TexturePacket_Matrix* TexturePacket_EffectMask::release_mmasktransform() { + // @@protoc_insertion_point(field_release:mozilla.layers.layerscope.TexturePacket.EffectMask.mMaskTransform) + _has_bits_[0] &= ~0x00000002u; + ::mozilla::layers::layerscope::TexturePacket_Matrix* temp = mmasktransform_; + mmasktransform_ = nullptr; + return temp; +} +inline ::mozilla::layers::layerscope::TexturePacket_Matrix* TexturePacket_EffectMask::_internal_mutable_mmasktransform() { + _has_bits_[0] |= 0x00000002u; + if (mmasktransform_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::layers::layerscope::TexturePacket_Matrix>(GetArenaNoVirtual()); + mmasktransform_ = p; + } + return mmasktransform_; +} +inline ::mozilla::layers::layerscope::TexturePacket_Matrix* TexturePacket_EffectMask::mutable_mmasktransform() { + // @@protoc_insertion_point(field_mutable:mozilla.layers.layerscope.TexturePacket.EffectMask.mMaskTransform) + return _internal_mutable_mmasktransform(); +} +inline void TexturePacket_EffectMask::set_allocated_mmasktransform(::mozilla::layers::layerscope::TexturePacket_Matrix* mmasktransform) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaNoVirtual(); + if (message_arena == nullptr) { + delete mmasktransform_; + } + if (mmasktransform) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = nullptr; + if (message_arena != submessage_arena) { + mmasktransform = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, mmasktransform, submessage_arena); + } + _has_bits_[0] |= 0x00000002u; + } else { + _has_bits_[0] &= ~0x00000002u; + } + mmasktransform_ = mmasktransform; + // @@protoc_insertion_point(field_set_allocated:mozilla.layers.layerscope.TexturePacket.EffectMask.mMaskTransform) +} + +// ------------------------------------------------------------------- + +// TexturePacket + +// required uint64 layerref = 1; +inline bool TexturePacket::_internal_has_layerref() const { + bool value = (_has_bits_[0] & 0x00000008u) != 0; + return value; +} +inline bool TexturePacket::has_layerref() const { + return _internal_has_layerref(); +} +inline void TexturePacket::clear_layerref() { + layerref_ = PROTOBUF_ULONGLONG(0); + _has_bits_[0] &= ~0x00000008u; +} +inline ::PROTOBUF_NAMESPACE_ID::uint64 TexturePacket::_internal_layerref() const { + return layerref_; +} +inline ::PROTOBUF_NAMESPACE_ID::uint64 TexturePacket::layerref() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.TexturePacket.layerref) + return _internal_layerref(); +} +inline void TexturePacket::_internal_set_layerref(::PROTOBUF_NAMESPACE_ID::uint64 value) { + _has_bits_[0] |= 0x00000008u; + layerref_ = value; +} +inline void TexturePacket::set_layerref(::PROTOBUF_NAMESPACE_ID::uint64 value) { + _internal_set_layerref(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.TexturePacket.layerref) +} + +// optional uint32 width = 2; +inline bool TexturePacket::_internal_has_width() const { + bool value = (_has_bits_[0] & 0x00000010u) != 0; + return value; +} +inline bool TexturePacket::has_width() const { + return _internal_has_width(); +} +inline void TexturePacket::clear_width() { + width_ = 0u; + _has_bits_[0] &= ~0x00000010u; +} +inline ::PROTOBUF_NAMESPACE_ID::uint32 TexturePacket::_internal_width() const { + return width_; +} +inline ::PROTOBUF_NAMESPACE_ID::uint32 TexturePacket::width() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.TexturePacket.width) + return _internal_width(); +} +inline void TexturePacket::_internal_set_width(::PROTOBUF_NAMESPACE_ID::uint32 value) { + _has_bits_[0] |= 0x00000010u; + width_ = value; +} +inline void TexturePacket::set_width(::PROTOBUF_NAMESPACE_ID::uint32 value) { + _internal_set_width(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.TexturePacket.width) +} + +// optional uint32 height = 3; +inline bool TexturePacket::_internal_has_height() const { + bool value = (_has_bits_[0] & 0x00000020u) != 0; + return value; +} +inline bool TexturePacket::has_height() const { + return _internal_has_height(); +} +inline void TexturePacket::clear_height() { + height_ = 0u; + _has_bits_[0] &= ~0x00000020u; +} +inline ::PROTOBUF_NAMESPACE_ID::uint32 TexturePacket::_internal_height() const { + return height_; +} +inline ::PROTOBUF_NAMESPACE_ID::uint32 TexturePacket::height() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.TexturePacket.height) + return _internal_height(); +} +inline void TexturePacket::_internal_set_height(::PROTOBUF_NAMESPACE_ID::uint32 value) { + _has_bits_[0] |= 0x00000020u; + height_ = value; +} +inline void TexturePacket::set_height(::PROTOBUF_NAMESPACE_ID::uint32 value) { + _internal_set_height(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.TexturePacket.height) +} + +// optional uint32 stride = 4; +inline bool TexturePacket::_internal_has_stride() const { + bool value = (_has_bits_[0] & 0x00000040u) != 0; + return value; +} +inline bool TexturePacket::has_stride() const { + return _internal_has_stride(); +} +inline void TexturePacket::clear_stride() { + stride_ = 0u; + _has_bits_[0] &= ~0x00000040u; +} +inline ::PROTOBUF_NAMESPACE_ID::uint32 TexturePacket::_internal_stride() const { + return stride_; +} +inline ::PROTOBUF_NAMESPACE_ID::uint32 TexturePacket::stride() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.TexturePacket.stride) + return _internal_stride(); +} +inline void TexturePacket::_internal_set_stride(::PROTOBUF_NAMESPACE_ID::uint32 value) { + _has_bits_[0] |= 0x00000040u; + stride_ = value; +} +inline void TexturePacket::set_stride(::PROTOBUF_NAMESPACE_ID::uint32 value) { + _internal_set_stride(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.TexturePacket.stride) +} + +// optional uint32 name = 5; +inline bool TexturePacket::_internal_has_name() const { + bool value = (_has_bits_[0] & 0x00000080u) != 0; + return value; +} +inline bool TexturePacket::has_name() const { + return _internal_has_name(); +} +inline void TexturePacket::clear_name() { + name_ = 0u; + _has_bits_[0] &= ~0x00000080u; +} +inline ::PROTOBUF_NAMESPACE_ID::uint32 TexturePacket::_internal_name() const { + return name_; +} +inline ::PROTOBUF_NAMESPACE_ID::uint32 TexturePacket::name() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.TexturePacket.name) + return _internal_name(); +} +inline void TexturePacket::_internal_set_name(::PROTOBUF_NAMESPACE_ID::uint32 value) { + _has_bits_[0] |= 0x00000080u; + name_ = value; +} +inline void TexturePacket::set_name(::PROTOBUF_NAMESPACE_ID::uint32 value) { + _internal_set_name(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.TexturePacket.name) +} + +// optional uint32 target = 6; +inline bool TexturePacket::_internal_has_target() const { + bool value = (_has_bits_[0] & 0x00000100u) != 0; + return value; +} +inline bool TexturePacket::has_target() const { + return _internal_has_target(); +} +inline void TexturePacket::clear_target() { + target_ = 0u; + _has_bits_[0] &= ~0x00000100u; +} +inline ::PROTOBUF_NAMESPACE_ID::uint32 TexturePacket::_internal_target() const { + return target_; +} +inline ::PROTOBUF_NAMESPACE_ID::uint32 TexturePacket::target() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.TexturePacket.target) + return _internal_target(); +} +inline void TexturePacket::_internal_set_target(::PROTOBUF_NAMESPACE_ID::uint32 value) { + _has_bits_[0] |= 0x00000100u; + target_ = value; +} +inline void TexturePacket::set_target(::PROTOBUF_NAMESPACE_ID::uint32 value) { + _internal_set_target(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.TexturePacket.target) +} + +// optional uint32 dataformat = 7; +inline bool TexturePacket::_internal_has_dataformat() const { + bool value = (_has_bits_[0] & 0x00000200u) != 0; + return value; +} +inline bool TexturePacket::has_dataformat() const { + return _internal_has_dataformat(); +} +inline void TexturePacket::clear_dataformat() { + dataformat_ = 0u; + _has_bits_[0] &= ~0x00000200u; +} +inline ::PROTOBUF_NAMESPACE_ID::uint32 TexturePacket::_internal_dataformat() const { + return dataformat_; +} +inline ::PROTOBUF_NAMESPACE_ID::uint32 TexturePacket::dataformat() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.TexturePacket.dataformat) + return _internal_dataformat(); +} +inline void TexturePacket::_internal_set_dataformat(::PROTOBUF_NAMESPACE_ID::uint32 value) { + _has_bits_[0] |= 0x00000200u; + dataformat_ = value; +} +inline void TexturePacket::set_dataformat(::PROTOBUF_NAMESPACE_ID::uint32 value) { + _internal_set_dataformat(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.TexturePacket.dataformat) +} + +// optional uint64 glcontext = 8; +inline bool TexturePacket::_internal_has_glcontext() const { + bool value = (_has_bits_[0] & 0x00000400u) != 0; + return value; +} +inline bool TexturePacket::has_glcontext() const { + return _internal_has_glcontext(); +} +inline void TexturePacket::clear_glcontext() { + glcontext_ = PROTOBUF_ULONGLONG(0); + _has_bits_[0] &= ~0x00000400u; +} +inline ::PROTOBUF_NAMESPACE_ID::uint64 TexturePacket::_internal_glcontext() const { + return glcontext_; +} +inline ::PROTOBUF_NAMESPACE_ID::uint64 TexturePacket::glcontext() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.TexturePacket.glcontext) + return _internal_glcontext(); +} +inline void TexturePacket::_internal_set_glcontext(::PROTOBUF_NAMESPACE_ID::uint64 value) { + _has_bits_[0] |= 0x00000400u; + glcontext_ = value; +} +inline void TexturePacket::set_glcontext(::PROTOBUF_NAMESPACE_ID::uint64 value) { + _internal_set_glcontext(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.TexturePacket.glcontext) +} + +// optional bytes data = 9; +inline bool TexturePacket::_internal_has_data() const { + bool value = (_has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool TexturePacket::has_data() const { + return _internal_has_data(); +} +inline void TexturePacket::clear_data() { + data_.ClearToEmptyNoArena(&::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited()); + _has_bits_[0] &= ~0x00000001u; +} +inline const std::string& TexturePacket::data() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.TexturePacket.data) + return _internal_data(); +} +inline void TexturePacket::set_data(const std::string& value) { + _internal_set_data(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.TexturePacket.data) +} +inline std::string* TexturePacket::mutable_data() { + // @@protoc_insertion_point(field_mutable:mozilla.layers.layerscope.TexturePacket.data) + return _internal_mutable_data(); +} +inline const std::string& TexturePacket::_internal_data() const { + return data_.GetNoArena(); +} +inline void TexturePacket::_internal_set_data(const std::string& value) { + _has_bits_[0] |= 0x00000001u; + data_.SetNoArena(&::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited(), value); +} +inline void TexturePacket::set_data(std::string&& value) { + _has_bits_[0] |= 0x00000001u; + data_.SetNoArena( + &::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited(), ::std::move(value)); + // @@protoc_insertion_point(field_set_rvalue:mozilla.layers.layerscope.TexturePacket.data) +} +inline void TexturePacket::set_data(const char* value) { + GOOGLE_DCHECK(value != nullptr); + _has_bits_[0] |= 0x00000001u; + data_.SetNoArena(&::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited(), ::std::string(value)); + // @@protoc_insertion_point(field_set_char:mozilla.layers.layerscope.TexturePacket.data) +} +inline void TexturePacket::set_data(const void* value, size_t size) { + _has_bits_[0] |= 0x00000001u; + data_.SetNoArena(&::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited(), + ::std::string(reinterpret_cast(value), size)); + // @@protoc_insertion_point(field_set_pointer:mozilla.layers.layerscope.TexturePacket.data) +} +inline std::string* TexturePacket::_internal_mutable_data() { + _has_bits_[0] |= 0x00000001u; + return data_.MutableNoArena(&::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited()); +} +inline std::string* TexturePacket::release_data() { + // @@protoc_insertion_point(field_release:mozilla.layers.layerscope.TexturePacket.data) + if (!_internal_has_data()) { + return nullptr; + } + _has_bits_[0] &= ~0x00000001u; + return data_.ReleaseNonDefaultNoArena(&::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited()); +} +inline void TexturePacket::set_allocated_data(std::string* data) { + if (data != nullptr) { + _has_bits_[0] |= 0x00000001u; + } else { + _has_bits_[0] &= ~0x00000001u; + } + data_.SetAllocatedNoArena(&::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited(), data); + // @@protoc_insertion_point(field_set_allocated:mozilla.layers.layerscope.TexturePacket.data) +} + +// optional .mozilla.layers.layerscope.TexturePacket.Rect mTextureCoords = 10; +inline bool TexturePacket::_internal_has_mtexturecoords() const { + bool value = (_has_bits_[0] & 0x00000002u) != 0; + PROTOBUF_ASSUME(!value || mtexturecoords_ != nullptr); + return value; +} +inline bool TexturePacket::has_mtexturecoords() const { + return _internal_has_mtexturecoords(); +} +inline void TexturePacket::clear_mtexturecoords() { + if (mtexturecoords_ != nullptr) mtexturecoords_->Clear(); + _has_bits_[0] &= ~0x00000002u; +} +inline const ::mozilla::layers::layerscope::TexturePacket_Rect& TexturePacket::_internal_mtexturecoords() const { + const ::mozilla::layers::layerscope::TexturePacket_Rect* p = mtexturecoords_; + return p != nullptr ? *p : *reinterpret_cast( + &::mozilla::layers::layerscope::_TexturePacket_Rect_default_instance_); +} +inline const ::mozilla::layers::layerscope::TexturePacket_Rect& TexturePacket::mtexturecoords() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.TexturePacket.mTextureCoords) + return _internal_mtexturecoords(); +} +inline ::mozilla::layers::layerscope::TexturePacket_Rect* TexturePacket::release_mtexturecoords() { + // @@protoc_insertion_point(field_release:mozilla.layers.layerscope.TexturePacket.mTextureCoords) + _has_bits_[0] &= ~0x00000002u; + ::mozilla::layers::layerscope::TexturePacket_Rect* temp = mtexturecoords_; + mtexturecoords_ = nullptr; + return temp; +} +inline ::mozilla::layers::layerscope::TexturePacket_Rect* TexturePacket::_internal_mutable_mtexturecoords() { + _has_bits_[0] |= 0x00000002u; + if (mtexturecoords_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::layers::layerscope::TexturePacket_Rect>(GetArenaNoVirtual()); + mtexturecoords_ = p; + } + return mtexturecoords_; +} +inline ::mozilla::layers::layerscope::TexturePacket_Rect* TexturePacket::mutable_mtexturecoords() { + // @@protoc_insertion_point(field_mutable:mozilla.layers.layerscope.TexturePacket.mTextureCoords) + return _internal_mutable_mtexturecoords(); +} +inline void TexturePacket::set_allocated_mtexturecoords(::mozilla::layers::layerscope::TexturePacket_Rect* mtexturecoords) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaNoVirtual(); + if (message_arena == nullptr) { + delete mtexturecoords_; + } + if (mtexturecoords) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = nullptr; + if (message_arena != submessage_arena) { + mtexturecoords = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, mtexturecoords, submessage_arena); + } + _has_bits_[0] |= 0x00000002u; + } else { + _has_bits_[0] &= ~0x00000002u; + } + mtexturecoords_ = mtexturecoords; + // @@protoc_insertion_point(field_set_allocated:mozilla.layers.layerscope.TexturePacket.mTextureCoords) +} + +// optional bool mPremultiplied = 11; +inline bool TexturePacket::_internal_has_mpremultiplied() const { + bool value = (_has_bits_[0] & 0x00001000u) != 0; + return value; +} +inline bool TexturePacket::has_mpremultiplied() const { + return _internal_has_mpremultiplied(); +} +inline void TexturePacket::clear_mpremultiplied() { + mpremultiplied_ = false; + _has_bits_[0] &= ~0x00001000u; +} +inline bool TexturePacket::_internal_mpremultiplied() const { + return mpremultiplied_; +} +inline bool TexturePacket::mpremultiplied() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.TexturePacket.mPremultiplied) + return _internal_mpremultiplied(); +} +inline void TexturePacket::_internal_set_mpremultiplied(bool value) { + _has_bits_[0] |= 0x00001000u; + mpremultiplied_ = value; +} +inline void TexturePacket::set_mpremultiplied(bool value) { + _internal_set_mpremultiplied(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.TexturePacket.mPremultiplied) +} + +// optional .mozilla.layers.layerscope.TexturePacket.Filter mFilter = 12; +inline bool TexturePacket::_internal_has_mfilter() const { + bool value = (_has_bits_[0] & 0x00000800u) != 0; + return value; +} +inline bool TexturePacket::has_mfilter() const { + return _internal_has_mfilter(); +} +inline void TexturePacket::clear_mfilter() { + mfilter_ = 0; + _has_bits_[0] &= ~0x00000800u; +} +inline ::mozilla::layers::layerscope::TexturePacket_Filter TexturePacket::_internal_mfilter() const { + return static_cast< ::mozilla::layers::layerscope::TexturePacket_Filter >(mfilter_); +} +inline ::mozilla::layers::layerscope::TexturePacket_Filter TexturePacket::mfilter() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.TexturePacket.mFilter) + return _internal_mfilter(); +} +inline void TexturePacket::_internal_set_mfilter(::mozilla::layers::layerscope::TexturePacket_Filter value) { + assert(::mozilla::layers::layerscope::TexturePacket_Filter_IsValid(value)); + _has_bits_[0] |= 0x00000800u; + mfilter_ = value; +} +inline void TexturePacket::set_mfilter(::mozilla::layers::layerscope::TexturePacket_Filter value) { + _internal_set_mfilter(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.TexturePacket.mFilter) +} + +// optional bool isMask = 20; +inline bool TexturePacket::_internal_has_ismask() const { + bool value = (_has_bits_[0] & 0x00002000u) != 0; + return value; +} +inline bool TexturePacket::has_ismask() const { + return _internal_has_ismask(); +} +inline void TexturePacket::clear_ismask() { + ismask_ = false; + _has_bits_[0] &= ~0x00002000u; +} +inline bool TexturePacket::_internal_ismask() const { + return ismask_; +} +inline bool TexturePacket::ismask() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.TexturePacket.isMask) + return _internal_ismask(); +} +inline void TexturePacket::_internal_set_ismask(bool value) { + _has_bits_[0] |= 0x00002000u; + ismask_ = value; +} +inline void TexturePacket::set_ismask(bool value) { + _internal_set_ismask(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.TexturePacket.isMask) +} + +// optional .mozilla.layers.layerscope.TexturePacket.EffectMask mask = 21; +inline bool TexturePacket::_internal_has_mask() const { + bool value = (_has_bits_[0] & 0x00000004u) != 0; + PROTOBUF_ASSUME(!value || mask_ != nullptr); + return value; +} +inline bool TexturePacket::has_mask() const { + return _internal_has_mask(); +} +inline void TexturePacket::clear_mask() { + if (mask_ != nullptr) mask_->Clear(); + _has_bits_[0] &= ~0x00000004u; +} +inline const ::mozilla::layers::layerscope::TexturePacket_EffectMask& TexturePacket::_internal_mask() const { + const ::mozilla::layers::layerscope::TexturePacket_EffectMask* p = mask_; + return p != nullptr ? *p : *reinterpret_cast( + &::mozilla::layers::layerscope::_TexturePacket_EffectMask_default_instance_); +} +inline const ::mozilla::layers::layerscope::TexturePacket_EffectMask& TexturePacket::mask() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.TexturePacket.mask) + return _internal_mask(); +} +inline ::mozilla::layers::layerscope::TexturePacket_EffectMask* TexturePacket::release_mask() { + // @@protoc_insertion_point(field_release:mozilla.layers.layerscope.TexturePacket.mask) + _has_bits_[0] &= ~0x00000004u; + ::mozilla::layers::layerscope::TexturePacket_EffectMask* temp = mask_; + mask_ = nullptr; + return temp; +} +inline ::mozilla::layers::layerscope::TexturePacket_EffectMask* TexturePacket::_internal_mutable_mask() { + _has_bits_[0] |= 0x00000004u; + if (mask_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::layers::layerscope::TexturePacket_EffectMask>(GetArenaNoVirtual()); + mask_ = p; + } + return mask_; +} +inline ::mozilla::layers::layerscope::TexturePacket_EffectMask* TexturePacket::mutable_mask() { + // @@protoc_insertion_point(field_mutable:mozilla.layers.layerscope.TexturePacket.mask) + return _internal_mutable_mask(); +} +inline void TexturePacket::set_allocated_mask(::mozilla::layers::layerscope::TexturePacket_EffectMask* mask) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaNoVirtual(); + if (message_arena == nullptr) { + delete mask_; + } + if (mask) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = nullptr; + if (message_arena != submessage_arena) { + mask = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, mask, submessage_arena); + } + _has_bits_[0] |= 0x00000004u; + } else { + _has_bits_[0] &= ~0x00000004u; + } + mask_ = mask; + // @@protoc_insertion_point(field_set_allocated:mozilla.layers.layerscope.TexturePacket.mask) +} + +// ------------------------------------------------------------------- + +// LayersPacket_Layer_Size + +// optional int32 w = 1; +inline bool LayersPacket_Layer_Size::_internal_has_w() const { + bool value = (_has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool LayersPacket_Layer_Size::has_w() const { + return _internal_has_w(); +} +inline void LayersPacket_Layer_Size::clear_w() { + w_ = 0; + _has_bits_[0] &= ~0x00000001u; +} +inline ::PROTOBUF_NAMESPACE_ID::int32 LayersPacket_Layer_Size::_internal_w() const { + return w_; +} +inline ::PROTOBUF_NAMESPACE_ID::int32 LayersPacket_Layer_Size::w() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.Size.w) + return _internal_w(); +} +inline void LayersPacket_Layer_Size::_internal_set_w(::PROTOBUF_NAMESPACE_ID::int32 value) { + _has_bits_[0] |= 0x00000001u; + w_ = value; +} +inline void LayersPacket_Layer_Size::set_w(::PROTOBUF_NAMESPACE_ID::int32 value) { + _internal_set_w(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.LayersPacket.Layer.Size.w) +} + +// optional int32 h = 2; +inline bool LayersPacket_Layer_Size::_internal_has_h() const { + bool value = (_has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool LayersPacket_Layer_Size::has_h() const { + return _internal_has_h(); +} +inline void LayersPacket_Layer_Size::clear_h() { + h_ = 0; + _has_bits_[0] &= ~0x00000002u; +} +inline ::PROTOBUF_NAMESPACE_ID::int32 LayersPacket_Layer_Size::_internal_h() const { + return h_; +} +inline ::PROTOBUF_NAMESPACE_ID::int32 LayersPacket_Layer_Size::h() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.Size.h) + return _internal_h(); +} +inline void LayersPacket_Layer_Size::_internal_set_h(::PROTOBUF_NAMESPACE_ID::int32 value) { + _has_bits_[0] |= 0x00000002u; + h_ = value; +} +inline void LayersPacket_Layer_Size::set_h(::PROTOBUF_NAMESPACE_ID::int32 value) { + _internal_set_h(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.LayersPacket.Layer.Size.h) +} + +// ------------------------------------------------------------------- + +// LayersPacket_Layer_Rect + +// optional int32 x = 1; +inline bool LayersPacket_Layer_Rect::_internal_has_x() const { + bool value = (_has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool LayersPacket_Layer_Rect::has_x() const { + return _internal_has_x(); +} +inline void LayersPacket_Layer_Rect::clear_x() { + x_ = 0; + _has_bits_[0] &= ~0x00000001u; +} +inline ::PROTOBUF_NAMESPACE_ID::int32 LayersPacket_Layer_Rect::_internal_x() const { + return x_; +} +inline ::PROTOBUF_NAMESPACE_ID::int32 LayersPacket_Layer_Rect::x() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.Rect.x) + return _internal_x(); +} +inline void LayersPacket_Layer_Rect::_internal_set_x(::PROTOBUF_NAMESPACE_ID::int32 value) { + _has_bits_[0] |= 0x00000001u; + x_ = value; +} +inline void LayersPacket_Layer_Rect::set_x(::PROTOBUF_NAMESPACE_ID::int32 value) { + _internal_set_x(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.LayersPacket.Layer.Rect.x) +} + +// optional int32 y = 2; +inline bool LayersPacket_Layer_Rect::_internal_has_y() const { + bool value = (_has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool LayersPacket_Layer_Rect::has_y() const { + return _internal_has_y(); +} +inline void LayersPacket_Layer_Rect::clear_y() { + y_ = 0; + _has_bits_[0] &= ~0x00000002u; +} +inline ::PROTOBUF_NAMESPACE_ID::int32 LayersPacket_Layer_Rect::_internal_y() const { + return y_; +} +inline ::PROTOBUF_NAMESPACE_ID::int32 LayersPacket_Layer_Rect::y() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.Rect.y) + return _internal_y(); +} +inline void LayersPacket_Layer_Rect::_internal_set_y(::PROTOBUF_NAMESPACE_ID::int32 value) { + _has_bits_[0] |= 0x00000002u; + y_ = value; +} +inline void LayersPacket_Layer_Rect::set_y(::PROTOBUF_NAMESPACE_ID::int32 value) { + _internal_set_y(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.LayersPacket.Layer.Rect.y) +} + +// optional int32 w = 3; +inline bool LayersPacket_Layer_Rect::_internal_has_w() const { + bool value = (_has_bits_[0] & 0x00000004u) != 0; + return value; +} +inline bool LayersPacket_Layer_Rect::has_w() const { + return _internal_has_w(); +} +inline void LayersPacket_Layer_Rect::clear_w() { + w_ = 0; + _has_bits_[0] &= ~0x00000004u; +} +inline ::PROTOBUF_NAMESPACE_ID::int32 LayersPacket_Layer_Rect::_internal_w() const { + return w_; +} +inline ::PROTOBUF_NAMESPACE_ID::int32 LayersPacket_Layer_Rect::w() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.Rect.w) + return _internal_w(); +} +inline void LayersPacket_Layer_Rect::_internal_set_w(::PROTOBUF_NAMESPACE_ID::int32 value) { + _has_bits_[0] |= 0x00000004u; + w_ = value; +} +inline void LayersPacket_Layer_Rect::set_w(::PROTOBUF_NAMESPACE_ID::int32 value) { + _internal_set_w(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.LayersPacket.Layer.Rect.w) +} + +// optional int32 h = 4; +inline bool LayersPacket_Layer_Rect::_internal_has_h() const { + bool value = (_has_bits_[0] & 0x00000008u) != 0; + return value; +} +inline bool LayersPacket_Layer_Rect::has_h() const { + return _internal_has_h(); +} +inline void LayersPacket_Layer_Rect::clear_h() { + h_ = 0; + _has_bits_[0] &= ~0x00000008u; +} +inline ::PROTOBUF_NAMESPACE_ID::int32 LayersPacket_Layer_Rect::_internal_h() const { + return h_; +} +inline ::PROTOBUF_NAMESPACE_ID::int32 LayersPacket_Layer_Rect::h() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.Rect.h) + return _internal_h(); +} +inline void LayersPacket_Layer_Rect::_internal_set_h(::PROTOBUF_NAMESPACE_ID::int32 value) { + _has_bits_[0] |= 0x00000008u; + h_ = value; +} +inline void LayersPacket_Layer_Rect::set_h(::PROTOBUF_NAMESPACE_ID::int32 value) { + _internal_set_h(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.LayersPacket.Layer.Rect.h) +} + +// ------------------------------------------------------------------- + +// LayersPacket_Layer_Region + +// repeated .mozilla.layers.layerscope.LayersPacket.Layer.Rect r = 1; +inline int LayersPacket_Layer_Region::_internal_r_size() const { + return r_.size(); +} +inline int LayersPacket_Layer_Region::r_size() const { + return _internal_r_size(); +} +inline void LayersPacket_Layer_Region::clear_r() { + r_.Clear(); +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Rect* LayersPacket_Layer_Region::mutable_r(int index) { + // @@protoc_insertion_point(field_mutable:mozilla.layers.layerscope.LayersPacket.Layer.Region.r) + return r_.Mutable(index); +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::layers::layerscope::LayersPacket_Layer_Rect >* +LayersPacket_Layer_Region::mutable_r() { + // @@protoc_insertion_point(field_mutable_list:mozilla.layers.layerscope.LayersPacket.Layer.Region.r) + return &r_; +} +inline const ::mozilla::layers::layerscope::LayersPacket_Layer_Rect& LayersPacket_Layer_Region::_internal_r(int index) const { + return r_.Get(index); +} +inline const ::mozilla::layers::layerscope::LayersPacket_Layer_Rect& LayersPacket_Layer_Region::r(int index) const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.Region.r) + return _internal_r(index); +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Rect* LayersPacket_Layer_Region::_internal_add_r() { + return r_.Add(); +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Rect* LayersPacket_Layer_Region::add_r() { + // @@protoc_insertion_point(field_add:mozilla.layers.layerscope.LayersPacket.Layer.Region.r) + return _internal_add_r(); +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::layers::layerscope::LayersPacket_Layer_Rect >& +LayersPacket_Layer_Region::r() const { + // @@protoc_insertion_point(field_list:mozilla.layers.layerscope.LayersPacket.Layer.Region.r) + return r_; +} + +// ------------------------------------------------------------------- + +// LayersPacket_Layer_Matrix + +// optional bool is2D = 1; +inline bool LayersPacket_Layer_Matrix::_internal_has_is2d() const { + bool value = (_has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool LayersPacket_Layer_Matrix::has_is2d() const { + return _internal_has_is2d(); +} +inline void LayersPacket_Layer_Matrix::clear_is2d() { + is2d_ = false; + _has_bits_[0] &= ~0x00000001u; +} +inline bool LayersPacket_Layer_Matrix::_internal_is2d() const { + return is2d_; +} +inline bool LayersPacket_Layer_Matrix::is2d() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.Matrix.is2D) + return _internal_is2d(); +} +inline void LayersPacket_Layer_Matrix::_internal_set_is2d(bool value) { + _has_bits_[0] |= 0x00000001u; + is2d_ = value; +} +inline void LayersPacket_Layer_Matrix::set_is2d(bool value) { + _internal_set_is2d(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.LayersPacket.Layer.Matrix.is2D) +} + +// optional bool isId = 2; +inline bool LayersPacket_Layer_Matrix::_internal_has_isid() const { + bool value = (_has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool LayersPacket_Layer_Matrix::has_isid() const { + return _internal_has_isid(); +} +inline void LayersPacket_Layer_Matrix::clear_isid() { + isid_ = false; + _has_bits_[0] &= ~0x00000002u; +} +inline bool LayersPacket_Layer_Matrix::_internal_isid() const { + return isid_; +} +inline bool LayersPacket_Layer_Matrix::isid() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.Matrix.isId) + return _internal_isid(); +} +inline void LayersPacket_Layer_Matrix::_internal_set_isid(bool value) { + _has_bits_[0] |= 0x00000002u; + isid_ = value; +} +inline void LayersPacket_Layer_Matrix::set_isid(bool value) { + _internal_set_isid(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.LayersPacket.Layer.Matrix.isId) +} + +// repeated float m = 3; +inline int LayersPacket_Layer_Matrix::_internal_m_size() const { + return m_.size(); +} +inline int LayersPacket_Layer_Matrix::m_size() const { + return _internal_m_size(); +} +inline void LayersPacket_Layer_Matrix::clear_m() { + m_.Clear(); +} +inline float LayersPacket_Layer_Matrix::_internal_m(int index) const { + return m_.Get(index); +} +inline float LayersPacket_Layer_Matrix::m(int index) const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.Matrix.m) + return _internal_m(index); +} +inline void LayersPacket_Layer_Matrix::set_m(int index, float value) { + m_.Set(index, value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.LayersPacket.Layer.Matrix.m) +} +inline void LayersPacket_Layer_Matrix::_internal_add_m(float value) { + m_.Add(value); +} +inline void LayersPacket_Layer_Matrix::add_m(float value) { + _internal_add_m(value); + // @@protoc_insertion_point(field_add:mozilla.layers.layerscope.LayersPacket.Layer.Matrix.m) +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedField< float >& +LayersPacket_Layer_Matrix::_internal_m() const { + return m_; +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedField< float >& +LayersPacket_Layer_Matrix::m() const { + // @@protoc_insertion_point(field_list:mozilla.layers.layerscope.LayersPacket.Layer.Matrix.m) + return _internal_m(); +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedField< float >* +LayersPacket_Layer_Matrix::_internal_mutable_m() { + return &m_; +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedField< float >* +LayersPacket_Layer_Matrix::mutable_m() { + // @@protoc_insertion_point(field_mutable_list:mozilla.layers.layerscope.LayersPacket.Layer.Matrix.m) + return _internal_mutable_m(); +} + +// ------------------------------------------------------------------- + +// LayersPacket_Layer_Shadow + +// optional .mozilla.layers.layerscope.LayersPacket.Layer.Rect clip = 1; +inline bool LayersPacket_Layer_Shadow::_internal_has_clip() const { + bool value = (_has_bits_[0] & 0x00000001u) != 0; + PROTOBUF_ASSUME(!value || clip_ != nullptr); + return value; +} +inline bool LayersPacket_Layer_Shadow::has_clip() const { + return _internal_has_clip(); +} +inline void LayersPacket_Layer_Shadow::clear_clip() { + if (clip_ != nullptr) clip_->Clear(); + _has_bits_[0] &= ~0x00000001u; +} +inline const ::mozilla::layers::layerscope::LayersPacket_Layer_Rect& LayersPacket_Layer_Shadow::_internal_clip() const { + const ::mozilla::layers::layerscope::LayersPacket_Layer_Rect* p = clip_; + return p != nullptr ? *p : *reinterpret_cast( + &::mozilla::layers::layerscope::_LayersPacket_Layer_Rect_default_instance_); +} +inline const ::mozilla::layers::layerscope::LayersPacket_Layer_Rect& LayersPacket_Layer_Shadow::clip() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.Shadow.clip) + return _internal_clip(); +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Rect* LayersPacket_Layer_Shadow::release_clip() { + // @@protoc_insertion_point(field_release:mozilla.layers.layerscope.LayersPacket.Layer.Shadow.clip) + _has_bits_[0] &= ~0x00000001u; + ::mozilla::layers::layerscope::LayersPacket_Layer_Rect* temp = clip_; + clip_ = nullptr; + return temp; +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Rect* LayersPacket_Layer_Shadow::_internal_mutable_clip() { + _has_bits_[0] |= 0x00000001u; + if (clip_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::layers::layerscope::LayersPacket_Layer_Rect>(GetArenaNoVirtual()); + clip_ = p; + } + return clip_; +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Rect* LayersPacket_Layer_Shadow::mutable_clip() { + // @@protoc_insertion_point(field_mutable:mozilla.layers.layerscope.LayersPacket.Layer.Shadow.clip) + return _internal_mutable_clip(); +} +inline void LayersPacket_Layer_Shadow::set_allocated_clip(::mozilla::layers::layerscope::LayersPacket_Layer_Rect* clip) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaNoVirtual(); + if (message_arena == nullptr) { + delete clip_; + } + if (clip) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = nullptr; + if (message_arena != submessage_arena) { + clip = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, clip, submessage_arena); + } + _has_bits_[0] |= 0x00000001u; + } else { + _has_bits_[0] &= ~0x00000001u; + } + clip_ = clip; + // @@protoc_insertion_point(field_set_allocated:mozilla.layers.layerscope.LayersPacket.Layer.Shadow.clip) +} + +// optional .mozilla.layers.layerscope.LayersPacket.Layer.Matrix transform = 2; +inline bool LayersPacket_Layer_Shadow::_internal_has_transform() const { + bool value = (_has_bits_[0] & 0x00000002u) != 0; + PROTOBUF_ASSUME(!value || transform_ != nullptr); + return value; +} +inline bool LayersPacket_Layer_Shadow::has_transform() const { + return _internal_has_transform(); +} +inline void LayersPacket_Layer_Shadow::clear_transform() { + if (transform_ != nullptr) transform_->Clear(); + _has_bits_[0] &= ~0x00000002u; +} +inline const ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix& LayersPacket_Layer_Shadow::_internal_transform() const { + const ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix* p = transform_; + return p != nullptr ? *p : *reinterpret_cast( + &::mozilla::layers::layerscope::_LayersPacket_Layer_Matrix_default_instance_); +} +inline const ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix& LayersPacket_Layer_Shadow::transform() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.Shadow.transform) + return _internal_transform(); +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix* LayersPacket_Layer_Shadow::release_transform() { + // @@protoc_insertion_point(field_release:mozilla.layers.layerscope.LayersPacket.Layer.Shadow.transform) + _has_bits_[0] &= ~0x00000002u; + ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix* temp = transform_; + transform_ = nullptr; + return temp; +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix* LayersPacket_Layer_Shadow::_internal_mutable_transform() { + _has_bits_[0] |= 0x00000002u; + if (transform_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::layers::layerscope::LayersPacket_Layer_Matrix>(GetArenaNoVirtual()); + transform_ = p; + } + return transform_; +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix* LayersPacket_Layer_Shadow::mutable_transform() { + // @@protoc_insertion_point(field_mutable:mozilla.layers.layerscope.LayersPacket.Layer.Shadow.transform) + return _internal_mutable_transform(); +} +inline void LayersPacket_Layer_Shadow::set_allocated_transform(::mozilla::layers::layerscope::LayersPacket_Layer_Matrix* transform) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaNoVirtual(); + if (message_arena == nullptr) { + delete transform_; + } + if (transform) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = nullptr; + if (message_arena != submessage_arena) { + transform = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, transform, submessage_arena); + } + _has_bits_[0] |= 0x00000002u; + } else { + _has_bits_[0] &= ~0x00000002u; + } + transform_ = transform; + // @@protoc_insertion_point(field_set_allocated:mozilla.layers.layerscope.LayersPacket.Layer.Shadow.transform) +} + +// optional .mozilla.layers.layerscope.LayersPacket.Layer.Region vRegion = 3; +inline bool LayersPacket_Layer_Shadow::_internal_has_vregion() const { + bool value = (_has_bits_[0] & 0x00000004u) != 0; + PROTOBUF_ASSUME(!value || vregion_ != nullptr); + return value; +} +inline bool LayersPacket_Layer_Shadow::has_vregion() const { + return _internal_has_vregion(); +} +inline void LayersPacket_Layer_Shadow::clear_vregion() { + if (vregion_ != nullptr) vregion_->Clear(); + _has_bits_[0] &= ~0x00000004u; +} +inline const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& LayersPacket_Layer_Shadow::_internal_vregion() const { + const ::mozilla::layers::layerscope::LayersPacket_Layer_Region* p = vregion_; + return p != nullptr ? *p : *reinterpret_cast( + &::mozilla::layers::layerscope::_LayersPacket_Layer_Region_default_instance_); +} +inline const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& LayersPacket_Layer_Shadow::vregion() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.Shadow.vRegion) + return _internal_vregion(); +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Region* LayersPacket_Layer_Shadow::release_vregion() { + // @@protoc_insertion_point(field_release:mozilla.layers.layerscope.LayersPacket.Layer.Shadow.vRegion) + _has_bits_[0] &= ~0x00000004u; + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* temp = vregion_; + vregion_ = nullptr; + return temp; +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Region* LayersPacket_Layer_Shadow::_internal_mutable_vregion() { + _has_bits_[0] |= 0x00000004u; + if (vregion_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::layers::layerscope::LayersPacket_Layer_Region>(GetArenaNoVirtual()); + vregion_ = p; + } + return vregion_; +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Region* LayersPacket_Layer_Shadow::mutable_vregion() { + // @@protoc_insertion_point(field_mutable:mozilla.layers.layerscope.LayersPacket.Layer.Shadow.vRegion) + return _internal_mutable_vregion(); +} +inline void LayersPacket_Layer_Shadow::set_allocated_vregion(::mozilla::layers::layerscope::LayersPacket_Layer_Region* vregion) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaNoVirtual(); + if (message_arena == nullptr) { + delete vregion_; + } + if (vregion) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = nullptr; + if (message_arena != submessage_arena) { + vregion = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, vregion, submessage_arena); + } + _has_bits_[0] |= 0x00000004u; + } else { + _has_bits_[0] &= ~0x00000004u; + } + vregion_ = vregion; + // @@protoc_insertion_point(field_set_allocated:mozilla.layers.layerscope.LayersPacket.Layer.Shadow.vRegion) +} + +// ------------------------------------------------------------------- + +// LayersPacket_Layer + +// required .mozilla.layers.layerscope.LayersPacket.Layer.LayerType type = 1; +inline bool LayersPacket_Layer::_internal_has_type() const { + bool value = (_has_bits_[0] & 0x00004000u) != 0; + return value; +} +inline bool LayersPacket_Layer::has_type() const { + return _internal_has_type(); +} +inline void LayersPacket_Layer::clear_type() { + type_ = 0; + _has_bits_[0] &= ~0x00004000u; +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_LayerType LayersPacket_Layer::_internal_type() const { + return static_cast< ::mozilla::layers::layerscope::LayersPacket_Layer_LayerType >(type_); +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_LayerType LayersPacket_Layer::type() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.type) + return _internal_type(); +} +inline void LayersPacket_Layer::_internal_set_type(::mozilla::layers::layerscope::LayersPacket_Layer_LayerType value) { + assert(::mozilla::layers::layerscope::LayersPacket_Layer_LayerType_IsValid(value)); + _has_bits_[0] |= 0x00004000u; + type_ = value; +} +inline void LayersPacket_Layer::set_type(::mozilla::layers::layerscope::LayersPacket_Layer_LayerType value) { + _internal_set_type(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.LayersPacket.Layer.type) +} + +// required uint64 ptr = 2; +inline bool LayersPacket_Layer::_internal_has_ptr() const { + bool value = (_has_bits_[0] & 0x00001000u) != 0; + return value; +} +inline bool LayersPacket_Layer::has_ptr() const { + return _internal_has_ptr(); +} +inline void LayersPacket_Layer::clear_ptr() { + ptr_ = PROTOBUF_ULONGLONG(0); + _has_bits_[0] &= ~0x00001000u; +} +inline ::PROTOBUF_NAMESPACE_ID::uint64 LayersPacket_Layer::_internal_ptr() const { + return ptr_; +} +inline ::PROTOBUF_NAMESPACE_ID::uint64 LayersPacket_Layer::ptr() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.ptr) + return _internal_ptr(); +} +inline void LayersPacket_Layer::_internal_set_ptr(::PROTOBUF_NAMESPACE_ID::uint64 value) { + _has_bits_[0] |= 0x00001000u; + ptr_ = value; +} +inline void LayersPacket_Layer::set_ptr(::PROTOBUF_NAMESPACE_ID::uint64 value) { + _internal_set_ptr(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.LayersPacket.Layer.ptr) +} + +// required uint64 parentPtr = 3; +inline bool LayersPacket_Layer::_internal_has_parentptr() const { + bool value = (_has_bits_[0] & 0x00002000u) != 0; + return value; +} +inline bool LayersPacket_Layer::has_parentptr() const { + return _internal_has_parentptr(); +} +inline void LayersPacket_Layer::clear_parentptr() { + parentptr_ = PROTOBUF_ULONGLONG(0); + _has_bits_[0] &= ~0x00002000u; +} +inline ::PROTOBUF_NAMESPACE_ID::uint64 LayersPacket_Layer::_internal_parentptr() const { + return parentptr_; +} +inline ::PROTOBUF_NAMESPACE_ID::uint64 LayersPacket_Layer::parentptr() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.parentPtr) + return _internal_parentptr(); +} +inline void LayersPacket_Layer::_internal_set_parentptr(::PROTOBUF_NAMESPACE_ID::uint64 value) { + _has_bits_[0] |= 0x00002000u; + parentptr_ = value; +} +inline void LayersPacket_Layer::set_parentptr(::PROTOBUF_NAMESPACE_ID::uint64 value) { + _internal_set_parentptr(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.LayersPacket.Layer.parentPtr) +} + +// optional .mozilla.layers.layerscope.LayersPacket.Layer.Rect clip = 10; +inline bool LayersPacket_Layer::_internal_has_clip() const { + bool value = (_has_bits_[0] & 0x00000002u) != 0; + PROTOBUF_ASSUME(!value || clip_ != nullptr); + return value; +} +inline bool LayersPacket_Layer::has_clip() const { + return _internal_has_clip(); +} +inline void LayersPacket_Layer::clear_clip() { + if (clip_ != nullptr) clip_->Clear(); + _has_bits_[0] &= ~0x00000002u; +} +inline const ::mozilla::layers::layerscope::LayersPacket_Layer_Rect& LayersPacket_Layer::_internal_clip() const { + const ::mozilla::layers::layerscope::LayersPacket_Layer_Rect* p = clip_; + return p != nullptr ? *p : *reinterpret_cast( + &::mozilla::layers::layerscope::_LayersPacket_Layer_Rect_default_instance_); +} +inline const ::mozilla::layers::layerscope::LayersPacket_Layer_Rect& LayersPacket_Layer::clip() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.clip) + return _internal_clip(); +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Rect* LayersPacket_Layer::release_clip() { + // @@protoc_insertion_point(field_release:mozilla.layers.layerscope.LayersPacket.Layer.clip) + _has_bits_[0] &= ~0x00000002u; + ::mozilla::layers::layerscope::LayersPacket_Layer_Rect* temp = clip_; + clip_ = nullptr; + return temp; +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Rect* LayersPacket_Layer::_internal_mutable_clip() { + _has_bits_[0] |= 0x00000002u; + if (clip_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::layers::layerscope::LayersPacket_Layer_Rect>(GetArenaNoVirtual()); + clip_ = p; + } + return clip_; +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Rect* LayersPacket_Layer::mutable_clip() { + // @@protoc_insertion_point(field_mutable:mozilla.layers.layerscope.LayersPacket.Layer.clip) + return _internal_mutable_clip(); +} +inline void LayersPacket_Layer::set_allocated_clip(::mozilla::layers::layerscope::LayersPacket_Layer_Rect* clip) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaNoVirtual(); + if (message_arena == nullptr) { + delete clip_; + } + if (clip) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = nullptr; + if (message_arena != submessage_arena) { + clip = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, clip, submessage_arena); + } + _has_bits_[0] |= 0x00000002u; + } else { + _has_bits_[0] &= ~0x00000002u; + } + clip_ = clip; + // @@protoc_insertion_point(field_set_allocated:mozilla.layers.layerscope.LayersPacket.Layer.clip) +} + +// optional .mozilla.layers.layerscope.LayersPacket.Layer.Matrix transform = 11; +inline bool LayersPacket_Layer::_internal_has_transform() const { + bool value = (_has_bits_[0] & 0x00000004u) != 0; + PROTOBUF_ASSUME(!value || transform_ != nullptr); + return value; +} +inline bool LayersPacket_Layer::has_transform() const { + return _internal_has_transform(); +} +inline void LayersPacket_Layer::clear_transform() { + if (transform_ != nullptr) transform_->Clear(); + _has_bits_[0] &= ~0x00000004u; +} +inline const ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix& LayersPacket_Layer::_internal_transform() const { + const ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix* p = transform_; + return p != nullptr ? *p : *reinterpret_cast( + &::mozilla::layers::layerscope::_LayersPacket_Layer_Matrix_default_instance_); +} +inline const ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix& LayersPacket_Layer::transform() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.transform) + return _internal_transform(); +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix* LayersPacket_Layer::release_transform() { + // @@protoc_insertion_point(field_release:mozilla.layers.layerscope.LayersPacket.Layer.transform) + _has_bits_[0] &= ~0x00000004u; + ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix* temp = transform_; + transform_ = nullptr; + return temp; +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix* LayersPacket_Layer::_internal_mutable_transform() { + _has_bits_[0] |= 0x00000004u; + if (transform_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::layers::layerscope::LayersPacket_Layer_Matrix>(GetArenaNoVirtual()); + transform_ = p; + } + return transform_; +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Matrix* LayersPacket_Layer::mutable_transform() { + // @@protoc_insertion_point(field_mutable:mozilla.layers.layerscope.LayersPacket.Layer.transform) + return _internal_mutable_transform(); +} +inline void LayersPacket_Layer::set_allocated_transform(::mozilla::layers::layerscope::LayersPacket_Layer_Matrix* transform) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaNoVirtual(); + if (message_arena == nullptr) { + delete transform_; + } + if (transform) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = nullptr; + if (message_arena != submessage_arena) { + transform = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, transform, submessage_arena); + } + _has_bits_[0] |= 0x00000004u; + } else { + _has_bits_[0] &= ~0x00000004u; + } + transform_ = transform; + // @@protoc_insertion_point(field_set_allocated:mozilla.layers.layerscope.LayersPacket.Layer.transform) +} + +// optional .mozilla.layers.layerscope.LayersPacket.Layer.Region vRegion = 12; +inline bool LayersPacket_Layer::_internal_has_vregion() const { + bool value = (_has_bits_[0] & 0x00000008u) != 0; + PROTOBUF_ASSUME(!value || vregion_ != nullptr); + return value; +} +inline bool LayersPacket_Layer::has_vregion() const { + return _internal_has_vregion(); +} +inline void LayersPacket_Layer::clear_vregion() { + if (vregion_ != nullptr) vregion_->Clear(); + _has_bits_[0] &= ~0x00000008u; +} +inline const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& LayersPacket_Layer::_internal_vregion() const { + const ::mozilla::layers::layerscope::LayersPacket_Layer_Region* p = vregion_; + return p != nullptr ? *p : *reinterpret_cast( + &::mozilla::layers::layerscope::_LayersPacket_Layer_Region_default_instance_); +} +inline const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& LayersPacket_Layer::vregion() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.vRegion) + return _internal_vregion(); +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Region* LayersPacket_Layer::release_vregion() { + // @@protoc_insertion_point(field_release:mozilla.layers.layerscope.LayersPacket.Layer.vRegion) + _has_bits_[0] &= ~0x00000008u; + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* temp = vregion_; + vregion_ = nullptr; + return temp; +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Region* LayersPacket_Layer::_internal_mutable_vregion() { + _has_bits_[0] |= 0x00000008u; + if (vregion_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::layers::layerscope::LayersPacket_Layer_Region>(GetArenaNoVirtual()); + vregion_ = p; + } + return vregion_; +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Region* LayersPacket_Layer::mutable_vregion() { + // @@protoc_insertion_point(field_mutable:mozilla.layers.layerscope.LayersPacket.Layer.vRegion) + return _internal_mutable_vregion(); +} +inline void LayersPacket_Layer::set_allocated_vregion(::mozilla::layers::layerscope::LayersPacket_Layer_Region* vregion) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaNoVirtual(); + if (message_arena == nullptr) { + delete vregion_; + } + if (vregion) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = nullptr; + if (message_arena != submessage_arena) { + vregion = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, vregion, submessage_arena); + } + _has_bits_[0] |= 0x00000008u; + } else { + _has_bits_[0] &= ~0x00000008u; + } + vregion_ = vregion; + // @@protoc_insertion_point(field_set_allocated:mozilla.layers.layerscope.LayersPacket.Layer.vRegion) +} + +// optional .mozilla.layers.layerscope.LayersPacket.Layer.Shadow shadow = 13; +inline bool LayersPacket_Layer::_internal_has_shadow() const { + bool value = (_has_bits_[0] & 0x00000010u) != 0; + PROTOBUF_ASSUME(!value || shadow_ != nullptr); + return value; +} +inline bool LayersPacket_Layer::has_shadow() const { + return _internal_has_shadow(); +} +inline void LayersPacket_Layer::clear_shadow() { + if (shadow_ != nullptr) shadow_->Clear(); + _has_bits_[0] &= ~0x00000010u; +} +inline const ::mozilla::layers::layerscope::LayersPacket_Layer_Shadow& LayersPacket_Layer::_internal_shadow() const { + const ::mozilla::layers::layerscope::LayersPacket_Layer_Shadow* p = shadow_; + return p != nullptr ? *p : *reinterpret_cast( + &::mozilla::layers::layerscope::_LayersPacket_Layer_Shadow_default_instance_); +} +inline const ::mozilla::layers::layerscope::LayersPacket_Layer_Shadow& LayersPacket_Layer::shadow() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.shadow) + return _internal_shadow(); +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Shadow* LayersPacket_Layer::release_shadow() { + // @@protoc_insertion_point(field_release:mozilla.layers.layerscope.LayersPacket.Layer.shadow) + _has_bits_[0] &= ~0x00000010u; + ::mozilla::layers::layerscope::LayersPacket_Layer_Shadow* temp = shadow_; + shadow_ = nullptr; + return temp; +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Shadow* LayersPacket_Layer::_internal_mutable_shadow() { + _has_bits_[0] |= 0x00000010u; + if (shadow_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::layers::layerscope::LayersPacket_Layer_Shadow>(GetArenaNoVirtual()); + shadow_ = p; + } + return shadow_; +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Shadow* LayersPacket_Layer::mutable_shadow() { + // @@protoc_insertion_point(field_mutable:mozilla.layers.layerscope.LayersPacket.Layer.shadow) + return _internal_mutable_shadow(); +} +inline void LayersPacket_Layer::set_allocated_shadow(::mozilla::layers::layerscope::LayersPacket_Layer_Shadow* shadow) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaNoVirtual(); + if (message_arena == nullptr) { + delete shadow_; + } + if (shadow) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = nullptr; + if (message_arena != submessage_arena) { + shadow = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, shadow, submessage_arena); + } + _has_bits_[0] |= 0x00000010u; + } else { + _has_bits_[0] &= ~0x00000010u; + } + shadow_ = shadow; + // @@protoc_insertion_point(field_set_allocated:mozilla.layers.layerscope.LayersPacket.Layer.shadow) +} + +// optional float opacity = 14; +inline bool LayersPacket_Layer::_internal_has_opacity() const { + bool value = (_has_bits_[0] & 0x00008000u) != 0; + return value; +} +inline bool LayersPacket_Layer::has_opacity() const { + return _internal_has_opacity(); +} +inline void LayersPacket_Layer::clear_opacity() { + opacity_ = 0; + _has_bits_[0] &= ~0x00008000u; +} +inline float LayersPacket_Layer::_internal_opacity() const { + return opacity_; +} +inline float LayersPacket_Layer::opacity() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.opacity) + return _internal_opacity(); +} +inline void LayersPacket_Layer::_internal_set_opacity(float value) { + _has_bits_[0] |= 0x00008000u; + opacity_ = value; +} +inline void LayersPacket_Layer::set_opacity(float value) { + _internal_set_opacity(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.LayersPacket.Layer.opacity) +} + +// optional bool cOpaque = 15; +inline bool LayersPacket_Layer::_internal_has_copaque() const { + bool value = (_has_bits_[0] & 0x00040000u) != 0; + return value; +} +inline bool LayersPacket_Layer::has_copaque() const { + return _internal_has_copaque(); +} +inline void LayersPacket_Layer::clear_copaque() { + copaque_ = false; + _has_bits_[0] &= ~0x00040000u; +} +inline bool LayersPacket_Layer::_internal_copaque() const { + return copaque_; +} +inline bool LayersPacket_Layer::copaque() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.cOpaque) + return _internal_copaque(); +} +inline void LayersPacket_Layer::_internal_set_copaque(bool value) { + _has_bits_[0] |= 0x00040000u; + copaque_ = value; +} +inline void LayersPacket_Layer::set_copaque(bool value) { + _internal_set_copaque(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.LayersPacket.Layer.cOpaque) +} + +// optional bool cAlpha = 16; +inline bool LayersPacket_Layer::_internal_has_calpha() const { + bool value = (_has_bits_[0] & 0x00080000u) != 0; + return value; +} +inline bool LayersPacket_Layer::has_calpha() const { + return _internal_has_calpha(); +} +inline void LayersPacket_Layer::clear_calpha() { + calpha_ = false; + _has_bits_[0] &= ~0x00080000u; +} +inline bool LayersPacket_Layer::_internal_calpha() const { + return calpha_; +} +inline bool LayersPacket_Layer::calpha() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.cAlpha) + return _internal_calpha(); +} +inline void LayersPacket_Layer::_internal_set_calpha(bool value) { + _has_bits_[0] |= 0x00080000u; + calpha_ = value; +} +inline void LayersPacket_Layer::set_calpha(bool value) { + _internal_set_calpha(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.LayersPacket.Layer.cAlpha) +} + +// optional .mozilla.layers.layerscope.LayersPacket.Layer.ScrollingDirect direct = 17; +inline bool LayersPacket_Layer::_internal_has_direct() const { + bool value = (_has_bits_[0] & 0x01000000u) != 0; + return value; +} +inline bool LayersPacket_Layer::has_direct() const { + return _internal_has_direct(); +} +inline void LayersPacket_Layer::clear_direct() { + direct_ = 1; + _has_bits_[0] &= ~0x01000000u; +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_ScrollingDirect LayersPacket_Layer::_internal_direct() const { + return static_cast< ::mozilla::layers::layerscope::LayersPacket_Layer_ScrollingDirect >(direct_); +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_ScrollingDirect LayersPacket_Layer::direct() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.direct) + return _internal_direct(); +} +inline void LayersPacket_Layer::_internal_set_direct(::mozilla::layers::layerscope::LayersPacket_Layer_ScrollingDirect value) { + assert(::mozilla::layers::layerscope::LayersPacket_Layer_ScrollingDirect_IsValid(value)); + _has_bits_[0] |= 0x01000000u; + direct_ = value; +} +inline void LayersPacket_Layer::set_direct(::mozilla::layers::layerscope::LayersPacket_Layer_ScrollingDirect value) { + _internal_set_direct(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.LayersPacket.Layer.direct) +} + +// optional uint64 barID = 18; +inline bool LayersPacket_Layer::_internal_has_barid() const { + bool value = (_has_bits_[0] & 0x00010000u) != 0; + return value; +} +inline bool LayersPacket_Layer::has_barid() const { + return _internal_has_barid(); +} +inline void LayersPacket_Layer::clear_barid() { + barid_ = PROTOBUF_ULONGLONG(0); + _has_bits_[0] &= ~0x00010000u; +} +inline ::PROTOBUF_NAMESPACE_ID::uint64 LayersPacket_Layer::_internal_barid() const { + return barid_; +} +inline ::PROTOBUF_NAMESPACE_ID::uint64 LayersPacket_Layer::barid() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.barID) + return _internal_barid(); +} +inline void LayersPacket_Layer::_internal_set_barid(::PROTOBUF_NAMESPACE_ID::uint64 value) { + _has_bits_[0] |= 0x00010000u; + barid_ = value; +} +inline void LayersPacket_Layer::set_barid(::PROTOBUF_NAMESPACE_ID::uint64 value) { + _internal_set_barid(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.LayersPacket.Layer.barID) +} + +// optional uint64 mask = 19; +inline bool LayersPacket_Layer::_internal_has_mask() const { + bool value = (_has_bits_[0] & 0x00020000u) != 0; + return value; +} +inline bool LayersPacket_Layer::has_mask() const { + return _internal_has_mask(); +} +inline void LayersPacket_Layer::clear_mask() { + mask_ = PROTOBUF_ULONGLONG(0); + _has_bits_[0] &= ~0x00020000u; +} +inline ::PROTOBUF_NAMESPACE_ID::uint64 LayersPacket_Layer::_internal_mask() const { + return mask_; +} +inline ::PROTOBUF_NAMESPACE_ID::uint64 LayersPacket_Layer::mask() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.mask) + return _internal_mask(); +} +inline void LayersPacket_Layer::_internal_set_mask(::PROTOBUF_NAMESPACE_ID::uint64 value) { + _has_bits_[0] |= 0x00020000u; + mask_ = value; +} +inline void LayersPacket_Layer::set_mask(::PROTOBUF_NAMESPACE_ID::uint64 value) { + _internal_set_mask(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.LayersPacket.Layer.mask) +} + +// optional .mozilla.layers.layerscope.LayersPacket.Layer.Region hitRegion = 20; +inline bool LayersPacket_Layer::_internal_has_hitregion() const { + bool value = (_has_bits_[0] & 0x00000020u) != 0; + PROTOBUF_ASSUME(!value || hitregion_ != nullptr); + return value; +} +inline bool LayersPacket_Layer::has_hitregion() const { + return _internal_has_hitregion(); +} +inline void LayersPacket_Layer::clear_hitregion() { + if (hitregion_ != nullptr) hitregion_->Clear(); + _has_bits_[0] &= ~0x00000020u; +} +inline const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& LayersPacket_Layer::_internal_hitregion() const { + const ::mozilla::layers::layerscope::LayersPacket_Layer_Region* p = hitregion_; + return p != nullptr ? *p : *reinterpret_cast( + &::mozilla::layers::layerscope::_LayersPacket_Layer_Region_default_instance_); +} +inline const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& LayersPacket_Layer::hitregion() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.hitRegion) + return _internal_hitregion(); +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Region* LayersPacket_Layer::release_hitregion() { + // @@protoc_insertion_point(field_release:mozilla.layers.layerscope.LayersPacket.Layer.hitRegion) + _has_bits_[0] &= ~0x00000020u; + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* temp = hitregion_; + hitregion_ = nullptr; + return temp; +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Region* LayersPacket_Layer::_internal_mutable_hitregion() { + _has_bits_[0] |= 0x00000020u; + if (hitregion_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::layers::layerscope::LayersPacket_Layer_Region>(GetArenaNoVirtual()); + hitregion_ = p; + } + return hitregion_; +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Region* LayersPacket_Layer::mutable_hitregion() { + // @@protoc_insertion_point(field_mutable:mozilla.layers.layerscope.LayersPacket.Layer.hitRegion) + return _internal_mutable_hitregion(); +} +inline void LayersPacket_Layer::set_allocated_hitregion(::mozilla::layers::layerscope::LayersPacket_Layer_Region* hitregion) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaNoVirtual(); + if (message_arena == nullptr) { + delete hitregion_; + } + if (hitregion) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = nullptr; + if (message_arena != submessage_arena) { + hitregion = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, hitregion, submessage_arena); + } + _has_bits_[0] |= 0x00000020u; + } else { + _has_bits_[0] &= ~0x00000020u; + } + hitregion_ = hitregion; + // @@protoc_insertion_point(field_set_allocated:mozilla.layers.layerscope.LayersPacket.Layer.hitRegion) +} + +// optional .mozilla.layers.layerscope.LayersPacket.Layer.Region dispatchRegion = 21; +inline bool LayersPacket_Layer::_internal_has_dispatchregion() const { + bool value = (_has_bits_[0] & 0x00000040u) != 0; + PROTOBUF_ASSUME(!value || dispatchregion_ != nullptr); + return value; +} +inline bool LayersPacket_Layer::has_dispatchregion() const { + return _internal_has_dispatchregion(); +} +inline void LayersPacket_Layer::clear_dispatchregion() { + if (dispatchregion_ != nullptr) dispatchregion_->Clear(); + _has_bits_[0] &= ~0x00000040u; +} +inline const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& LayersPacket_Layer::_internal_dispatchregion() const { + const ::mozilla::layers::layerscope::LayersPacket_Layer_Region* p = dispatchregion_; + return p != nullptr ? *p : *reinterpret_cast( + &::mozilla::layers::layerscope::_LayersPacket_Layer_Region_default_instance_); +} +inline const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& LayersPacket_Layer::dispatchregion() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.dispatchRegion) + return _internal_dispatchregion(); +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Region* LayersPacket_Layer::release_dispatchregion() { + // @@protoc_insertion_point(field_release:mozilla.layers.layerscope.LayersPacket.Layer.dispatchRegion) + _has_bits_[0] &= ~0x00000040u; + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* temp = dispatchregion_; + dispatchregion_ = nullptr; + return temp; +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Region* LayersPacket_Layer::_internal_mutable_dispatchregion() { + _has_bits_[0] |= 0x00000040u; + if (dispatchregion_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::layers::layerscope::LayersPacket_Layer_Region>(GetArenaNoVirtual()); + dispatchregion_ = p; + } + return dispatchregion_; +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Region* LayersPacket_Layer::mutable_dispatchregion() { + // @@protoc_insertion_point(field_mutable:mozilla.layers.layerscope.LayersPacket.Layer.dispatchRegion) + return _internal_mutable_dispatchregion(); +} +inline void LayersPacket_Layer::set_allocated_dispatchregion(::mozilla::layers::layerscope::LayersPacket_Layer_Region* dispatchregion) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaNoVirtual(); + if (message_arena == nullptr) { + delete dispatchregion_; + } + if (dispatchregion) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = nullptr; + if (message_arena != submessage_arena) { + dispatchregion = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, dispatchregion, submessage_arena); + } + _has_bits_[0] |= 0x00000040u; + } else { + _has_bits_[0] &= ~0x00000040u; + } + dispatchregion_ = dispatchregion; + // @@protoc_insertion_point(field_set_allocated:mozilla.layers.layerscope.LayersPacket.Layer.dispatchRegion) +} + +// optional .mozilla.layers.layerscope.LayersPacket.Layer.Region noActionRegion = 22; +inline bool LayersPacket_Layer::_internal_has_noactionregion() const { + bool value = (_has_bits_[0] & 0x00000080u) != 0; + PROTOBUF_ASSUME(!value || noactionregion_ != nullptr); + return value; +} +inline bool LayersPacket_Layer::has_noactionregion() const { + return _internal_has_noactionregion(); +} +inline void LayersPacket_Layer::clear_noactionregion() { + if (noactionregion_ != nullptr) noactionregion_->Clear(); + _has_bits_[0] &= ~0x00000080u; +} +inline const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& LayersPacket_Layer::_internal_noactionregion() const { + const ::mozilla::layers::layerscope::LayersPacket_Layer_Region* p = noactionregion_; + return p != nullptr ? *p : *reinterpret_cast( + &::mozilla::layers::layerscope::_LayersPacket_Layer_Region_default_instance_); +} +inline const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& LayersPacket_Layer::noactionregion() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.noActionRegion) + return _internal_noactionregion(); +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Region* LayersPacket_Layer::release_noactionregion() { + // @@protoc_insertion_point(field_release:mozilla.layers.layerscope.LayersPacket.Layer.noActionRegion) + _has_bits_[0] &= ~0x00000080u; + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* temp = noactionregion_; + noactionregion_ = nullptr; + return temp; +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Region* LayersPacket_Layer::_internal_mutable_noactionregion() { + _has_bits_[0] |= 0x00000080u; + if (noactionregion_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::layers::layerscope::LayersPacket_Layer_Region>(GetArenaNoVirtual()); + noactionregion_ = p; + } + return noactionregion_; +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Region* LayersPacket_Layer::mutable_noactionregion() { + // @@protoc_insertion_point(field_mutable:mozilla.layers.layerscope.LayersPacket.Layer.noActionRegion) + return _internal_mutable_noactionregion(); +} +inline void LayersPacket_Layer::set_allocated_noactionregion(::mozilla::layers::layerscope::LayersPacket_Layer_Region* noactionregion) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaNoVirtual(); + if (message_arena == nullptr) { + delete noactionregion_; + } + if (noactionregion) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = nullptr; + if (message_arena != submessage_arena) { + noactionregion = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, noactionregion, submessage_arena); + } + _has_bits_[0] |= 0x00000080u; + } else { + _has_bits_[0] &= ~0x00000080u; + } + noactionregion_ = noactionregion; + // @@protoc_insertion_point(field_set_allocated:mozilla.layers.layerscope.LayersPacket.Layer.noActionRegion) +} + +// optional .mozilla.layers.layerscope.LayersPacket.Layer.Region hPanRegion = 23; +inline bool LayersPacket_Layer::_internal_has_hpanregion() const { + bool value = (_has_bits_[0] & 0x00000100u) != 0; + PROTOBUF_ASSUME(!value || hpanregion_ != nullptr); + return value; +} +inline bool LayersPacket_Layer::has_hpanregion() const { + return _internal_has_hpanregion(); +} +inline void LayersPacket_Layer::clear_hpanregion() { + if (hpanregion_ != nullptr) hpanregion_->Clear(); + _has_bits_[0] &= ~0x00000100u; +} +inline const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& LayersPacket_Layer::_internal_hpanregion() const { + const ::mozilla::layers::layerscope::LayersPacket_Layer_Region* p = hpanregion_; + return p != nullptr ? *p : *reinterpret_cast( + &::mozilla::layers::layerscope::_LayersPacket_Layer_Region_default_instance_); +} +inline const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& LayersPacket_Layer::hpanregion() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.hPanRegion) + return _internal_hpanregion(); +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Region* LayersPacket_Layer::release_hpanregion() { + // @@protoc_insertion_point(field_release:mozilla.layers.layerscope.LayersPacket.Layer.hPanRegion) + _has_bits_[0] &= ~0x00000100u; + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* temp = hpanregion_; + hpanregion_ = nullptr; + return temp; +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Region* LayersPacket_Layer::_internal_mutable_hpanregion() { + _has_bits_[0] |= 0x00000100u; + if (hpanregion_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::layers::layerscope::LayersPacket_Layer_Region>(GetArenaNoVirtual()); + hpanregion_ = p; + } + return hpanregion_; +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Region* LayersPacket_Layer::mutable_hpanregion() { + // @@protoc_insertion_point(field_mutable:mozilla.layers.layerscope.LayersPacket.Layer.hPanRegion) + return _internal_mutable_hpanregion(); +} +inline void LayersPacket_Layer::set_allocated_hpanregion(::mozilla::layers::layerscope::LayersPacket_Layer_Region* hpanregion) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaNoVirtual(); + if (message_arena == nullptr) { + delete hpanregion_; + } + if (hpanregion) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = nullptr; + if (message_arena != submessage_arena) { + hpanregion = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, hpanregion, submessage_arena); + } + _has_bits_[0] |= 0x00000100u; + } else { + _has_bits_[0] &= ~0x00000100u; + } + hpanregion_ = hpanregion; + // @@protoc_insertion_point(field_set_allocated:mozilla.layers.layerscope.LayersPacket.Layer.hPanRegion) +} + +// optional .mozilla.layers.layerscope.LayersPacket.Layer.Region vPanRegion = 24; +inline bool LayersPacket_Layer::_internal_has_vpanregion() const { + bool value = (_has_bits_[0] & 0x00000200u) != 0; + PROTOBUF_ASSUME(!value || vpanregion_ != nullptr); + return value; +} +inline bool LayersPacket_Layer::has_vpanregion() const { + return _internal_has_vpanregion(); +} +inline void LayersPacket_Layer::clear_vpanregion() { + if (vpanregion_ != nullptr) vpanregion_->Clear(); + _has_bits_[0] &= ~0x00000200u; +} +inline const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& LayersPacket_Layer::_internal_vpanregion() const { + const ::mozilla::layers::layerscope::LayersPacket_Layer_Region* p = vpanregion_; + return p != nullptr ? *p : *reinterpret_cast( + &::mozilla::layers::layerscope::_LayersPacket_Layer_Region_default_instance_); +} +inline const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& LayersPacket_Layer::vpanregion() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.vPanRegion) + return _internal_vpanregion(); +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Region* LayersPacket_Layer::release_vpanregion() { + // @@protoc_insertion_point(field_release:mozilla.layers.layerscope.LayersPacket.Layer.vPanRegion) + _has_bits_[0] &= ~0x00000200u; + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* temp = vpanregion_; + vpanregion_ = nullptr; + return temp; +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Region* LayersPacket_Layer::_internal_mutable_vpanregion() { + _has_bits_[0] |= 0x00000200u; + if (vpanregion_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::layers::layerscope::LayersPacket_Layer_Region>(GetArenaNoVirtual()); + vpanregion_ = p; + } + return vpanregion_; +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Region* LayersPacket_Layer::mutable_vpanregion() { + // @@protoc_insertion_point(field_mutable:mozilla.layers.layerscope.LayersPacket.Layer.vPanRegion) + return _internal_mutable_vpanregion(); +} +inline void LayersPacket_Layer::set_allocated_vpanregion(::mozilla::layers::layerscope::LayersPacket_Layer_Region* vpanregion) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaNoVirtual(); + if (message_arena == nullptr) { + delete vpanregion_; + } + if (vpanregion) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = nullptr; + if (message_arena != submessage_arena) { + vpanregion = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, vpanregion, submessage_arena); + } + _has_bits_[0] |= 0x00000200u; + } else { + _has_bits_[0] &= ~0x00000200u; + } + vpanregion_ = vpanregion; + // @@protoc_insertion_point(field_set_allocated:mozilla.layers.layerscope.LayersPacket.Layer.vPanRegion) +} + +// optional .mozilla.layers.layerscope.LayersPacket.Layer.Region valid = 100; +inline bool LayersPacket_Layer::_internal_has_valid() const { + bool value = (_has_bits_[0] & 0x00000400u) != 0; + PROTOBUF_ASSUME(!value || valid_ != nullptr); + return value; +} +inline bool LayersPacket_Layer::has_valid() const { + return _internal_has_valid(); +} +inline void LayersPacket_Layer::clear_valid() { + if (valid_ != nullptr) valid_->Clear(); + _has_bits_[0] &= ~0x00000400u; +} +inline const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& LayersPacket_Layer::_internal_valid() const { + const ::mozilla::layers::layerscope::LayersPacket_Layer_Region* p = valid_; + return p != nullptr ? *p : *reinterpret_cast( + &::mozilla::layers::layerscope::_LayersPacket_Layer_Region_default_instance_); +} +inline const ::mozilla::layers::layerscope::LayersPacket_Layer_Region& LayersPacket_Layer::valid() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.valid) + return _internal_valid(); +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Region* LayersPacket_Layer::release_valid() { + // @@protoc_insertion_point(field_release:mozilla.layers.layerscope.LayersPacket.Layer.valid) + _has_bits_[0] &= ~0x00000400u; + ::mozilla::layers::layerscope::LayersPacket_Layer_Region* temp = valid_; + valid_ = nullptr; + return temp; +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Region* LayersPacket_Layer::_internal_mutable_valid() { + _has_bits_[0] |= 0x00000400u; + if (valid_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::layers::layerscope::LayersPacket_Layer_Region>(GetArenaNoVirtual()); + valid_ = p; + } + return valid_; +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Region* LayersPacket_Layer::mutable_valid() { + // @@protoc_insertion_point(field_mutable:mozilla.layers.layerscope.LayersPacket.Layer.valid) + return _internal_mutable_valid(); +} +inline void LayersPacket_Layer::set_allocated_valid(::mozilla::layers::layerscope::LayersPacket_Layer_Region* valid) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaNoVirtual(); + if (message_arena == nullptr) { + delete valid_; + } + if (valid) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = nullptr; + if (message_arena != submessage_arena) { + valid = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, valid, submessage_arena); + } + _has_bits_[0] |= 0x00000400u; + } else { + _has_bits_[0] &= ~0x00000400u; + } + valid_ = valid; + // @@protoc_insertion_point(field_set_allocated:mozilla.layers.layerscope.LayersPacket.Layer.valid) +} + +// optional uint32 color = 101; +inline bool LayersPacket_Layer::_internal_has_color() const { + bool value = (_has_bits_[0] & 0x00100000u) != 0; + return value; +} +inline bool LayersPacket_Layer::has_color() const { + return _internal_has_color(); +} +inline void LayersPacket_Layer::clear_color() { + color_ = 0u; + _has_bits_[0] &= ~0x00100000u; +} +inline ::PROTOBUF_NAMESPACE_ID::uint32 LayersPacket_Layer::_internal_color() const { + return color_; +} +inline ::PROTOBUF_NAMESPACE_ID::uint32 LayersPacket_Layer::color() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.color) + return _internal_color(); +} +inline void LayersPacket_Layer::_internal_set_color(::PROTOBUF_NAMESPACE_ID::uint32 value) { + _has_bits_[0] |= 0x00100000u; + color_ = value; +} +inline void LayersPacket_Layer::set_color(::PROTOBUF_NAMESPACE_ID::uint32 value) { + _internal_set_color(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.LayersPacket.Layer.color) +} + +// optional .mozilla.layers.layerscope.LayersPacket.Layer.Filter filter = 102; +inline bool LayersPacket_Layer::_internal_has_filter() const { + bool value = (_has_bits_[0] & 0x00400000u) != 0; + return value; +} +inline bool LayersPacket_Layer::has_filter() const { + return _internal_has_filter(); +} +inline void LayersPacket_Layer::clear_filter() { + filter_ = 0; + _has_bits_[0] &= ~0x00400000u; +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Filter LayersPacket_Layer::_internal_filter() const { + return static_cast< ::mozilla::layers::layerscope::LayersPacket_Layer_Filter >(filter_); +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Filter LayersPacket_Layer::filter() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.filter) + return _internal_filter(); +} +inline void LayersPacket_Layer::_internal_set_filter(::mozilla::layers::layerscope::LayersPacket_Layer_Filter value) { + assert(::mozilla::layers::layerscope::LayersPacket_Layer_Filter_IsValid(value)); + _has_bits_[0] |= 0x00400000u; + filter_ = value; +} +inline void LayersPacket_Layer::set_filter(::mozilla::layers::layerscope::LayersPacket_Layer_Filter value) { + _internal_set_filter(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.LayersPacket.Layer.filter) +} + +// optional uint64 refID = 103; +inline bool LayersPacket_Layer::_internal_has_refid() const { + bool value = (_has_bits_[0] & 0x00200000u) != 0; + return value; +} +inline bool LayersPacket_Layer::has_refid() const { + return _internal_has_refid(); +} +inline void LayersPacket_Layer::clear_refid() { + refid_ = PROTOBUF_ULONGLONG(0); + _has_bits_[0] &= ~0x00200000u; +} +inline ::PROTOBUF_NAMESPACE_ID::uint64 LayersPacket_Layer::_internal_refid() const { + return refid_; +} +inline ::PROTOBUF_NAMESPACE_ID::uint64 LayersPacket_Layer::refid() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.refID) + return _internal_refid(); +} +inline void LayersPacket_Layer::_internal_set_refid(::PROTOBUF_NAMESPACE_ID::uint64 value) { + _has_bits_[0] |= 0x00200000u; + refid_ = value; +} +inline void LayersPacket_Layer::set_refid(::PROTOBUF_NAMESPACE_ID::uint64 value) { + _internal_set_refid(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.LayersPacket.Layer.refID) +} + +// optional .mozilla.layers.layerscope.LayersPacket.Layer.Size size = 104; +inline bool LayersPacket_Layer::_internal_has_size() const { + bool value = (_has_bits_[0] & 0x00000800u) != 0; + PROTOBUF_ASSUME(!value || size_ != nullptr); + return value; +} +inline bool LayersPacket_Layer::has_size() const { + return _internal_has_size(); +} +inline void LayersPacket_Layer::clear_size() { + if (size_ != nullptr) size_->Clear(); + _has_bits_[0] &= ~0x00000800u; +} +inline const ::mozilla::layers::layerscope::LayersPacket_Layer_Size& LayersPacket_Layer::_internal_size() const { + const ::mozilla::layers::layerscope::LayersPacket_Layer_Size* p = size_; + return p != nullptr ? *p : *reinterpret_cast( + &::mozilla::layers::layerscope::_LayersPacket_Layer_Size_default_instance_); +} +inline const ::mozilla::layers::layerscope::LayersPacket_Layer_Size& LayersPacket_Layer::size() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.size) + return _internal_size(); +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Size* LayersPacket_Layer::release_size() { + // @@protoc_insertion_point(field_release:mozilla.layers.layerscope.LayersPacket.Layer.size) + _has_bits_[0] &= ~0x00000800u; + ::mozilla::layers::layerscope::LayersPacket_Layer_Size* temp = size_; + size_ = nullptr; + return temp; +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Size* LayersPacket_Layer::_internal_mutable_size() { + _has_bits_[0] |= 0x00000800u; + if (size_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::layers::layerscope::LayersPacket_Layer_Size>(GetArenaNoVirtual()); + size_ = p; + } + return size_; +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer_Size* LayersPacket_Layer::mutable_size() { + // @@protoc_insertion_point(field_mutable:mozilla.layers.layerscope.LayersPacket.Layer.size) + return _internal_mutable_size(); +} +inline void LayersPacket_Layer::set_allocated_size(::mozilla::layers::layerscope::LayersPacket_Layer_Size* size) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaNoVirtual(); + if (message_arena == nullptr) { + delete size_; + } + if (size) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = nullptr; + if (message_arena != submessage_arena) { + size = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, size, submessage_arena); + } + _has_bits_[0] |= 0x00000800u; + } else { + _has_bits_[0] &= ~0x00000800u; + } + size_ = size; + // @@protoc_insertion_point(field_set_allocated:mozilla.layers.layerscope.LayersPacket.Layer.size) +} + +// optional uint32 displayListLogLength = 105; +inline bool LayersPacket_Layer::_internal_has_displaylistloglength() const { + bool value = (_has_bits_[0] & 0x00800000u) != 0; + return value; +} +inline bool LayersPacket_Layer::has_displaylistloglength() const { + return _internal_has_displaylistloglength(); +} +inline void LayersPacket_Layer::clear_displaylistloglength() { + displaylistloglength_ = 0u; + _has_bits_[0] &= ~0x00800000u; +} +inline ::PROTOBUF_NAMESPACE_ID::uint32 LayersPacket_Layer::_internal_displaylistloglength() const { + return displaylistloglength_; +} +inline ::PROTOBUF_NAMESPACE_ID::uint32 LayersPacket_Layer::displaylistloglength() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.displayListLogLength) + return _internal_displaylistloglength(); +} +inline void LayersPacket_Layer::_internal_set_displaylistloglength(::PROTOBUF_NAMESPACE_ID::uint32 value) { + _has_bits_[0] |= 0x00800000u; + displaylistloglength_ = value; +} +inline void LayersPacket_Layer::set_displaylistloglength(::PROTOBUF_NAMESPACE_ID::uint32 value) { + _internal_set_displaylistloglength(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.LayersPacket.Layer.displayListLogLength) +} + +// optional bytes displayListLog = 106; +inline bool LayersPacket_Layer::_internal_has_displaylistlog() const { + bool value = (_has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool LayersPacket_Layer::has_displaylistlog() const { + return _internal_has_displaylistlog(); +} +inline void LayersPacket_Layer::clear_displaylistlog() { + displaylistlog_.ClearToEmptyNoArena(&::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited()); + _has_bits_[0] &= ~0x00000001u; +} +inline const std::string& LayersPacket_Layer::displaylistlog() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.Layer.displayListLog) + return _internal_displaylistlog(); +} +inline void LayersPacket_Layer::set_displaylistlog(const std::string& value) { + _internal_set_displaylistlog(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.LayersPacket.Layer.displayListLog) +} +inline std::string* LayersPacket_Layer::mutable_displaylistlog() { + // @@protoc_insertion_point(field_mutable:mozilla.layers.layerscope.LayersPacket.Layer.displayListLog) + return _internal_mutable_displaylistlog(); +} +inline const std::string& LayersPacket_Layer::_internal_displaylistlog() const { + return displaylistlog_.GetNoArena(); +} +inline void LayersPacket_Layer::_internal_set_displaylistlog(const std::string& value) { + _has_bits_[0] |= 0x00000001u; + displaylistlog_.SetNoArena(&::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited(), value); +} +inline void LayersPacket_Layer::set_displaylistlog(std::string&& value) { + _has_bits_[0] |= 0x00000001u; + displaylistlog_.SetNoArena( + &::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited(), ::std::move(value)); + // @@protoc_insertion_point(field_set_rvalue:mozilla.layers.layerscope.LayersPacket.Layer.displayListLog) +} +inline void LayersPacket_Layer::set_displaylistlog(const char* value) { + GOOGLE_DCHECK(value != nullptr); + _has_bits_[0] |= 0x00000001u; + displaylistlog_.SetNoArena(&::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited(), ::std::string(value)); + // @@protoc_insertion_point(field_set_char:mozilla.layers.layerscope.LayersPacket.Layer.displayListLog) +} +inline void LayersPacket_Layer::set_displaylistlog(const void* value, size_t size) { + _has_bits_[0] |= 0x00000001u; + displaylistlog_.SetNoArena(&::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited(), + ::std::string(reinterpret_cast(value), size)); + // @@protoc_insertion_point(field_set_pointer:mozilla.layers.layerscope.LayersPacket.Layer.displayListLog) +} +inline std::string* LayersPacket_Layer::_internal_mutable_displaylistlog() { + _has_bits_[0] |= 0x00000001u; + return displaylistlog_.MutableNoArena(&::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited()); +} +inline std::string* LayersPacket_Layer::release_displaylistlog() { + // @@protoc_insertion_point(field_release:mozilla.layers.layerscope.LayersPacket.Layer.displayListLog) + if (!_internal_has_displaylistlog()) { + return nullptr; + } + _has_bits_[0] &= ~0x00000001u; + return displaylistlog_.ReleaseNonDefaultNoArena(&::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited()); +} +inline void LayersPacket_Layer::set_allocated_displaylistlog(std::string* displaylistlog) { + if (displaylistlog != nullptr) { + _has_bits_[0] |= 0x00000001u; + } else { + _has_bits_[0] &= ~0x00000001u; + } + displaylistlog_.SetAllocatedNoArena(&::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited(), displaylistlog); + // @@protoc_insertion_point(field_set_allocated:mozilla.layers.layerscope.LayersPacket.Layer.displayListLog) +} + +// ------------------------------------------------------------------- + +// LayersPacket + +// repeated .mozilla.layers.layerscope.LayersPacket.Layer layer = 1; +inline int LayersPacket::_internal_layer_size() const { + return layer_.size(); +} +inline int LayersPacket::layer_size() const { + return _internal_layer_size(); +} +inline void LayersPacket::clear_layer() { + layer_.Clear(); +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer* LayersPacket::mutable_layer(int index) { + // @@protoc_insertion_point(field_mutable:mozilla.layers.layerscope.LayersPacket.layer) + return layer_.Mutable(index); +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::layers::layerscope::LayersPacket_Layer >* +LayersPacket::mutable_layer() { + // @@protoc_insertion_point(field_mutable_list:mozilla.layers.layerscope.LayersPacket.layer) + return &layer_; +} +inline const ::mozilla::layers::layerscope::LayersPacket_Layer& LayersPacket::_internal_layer(int index) const { + return layer_.Get(index); +} +inline const ::mozilla::layers::layerscope::LayersPacket_Layer& LayersPacket::layer(int index) const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.LayersPacket.layer) + return _internal_layer(index); +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer* LayersPacket::_internal_add_layer() { + return layer_.Add(); +} +inline ::mozilla::layers::layerscope::LayersPacket_Layer* LayersPacket::add_layer() { + // @@protoc_insertion_point(field_add:mozilla.layers.layerscope.LayersPacket.layer) + return _internal_add_layer(); +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::layers::layerscope::LayersPacket_Layer >& +LayersPacket::layer() const { + // @@protoc_insertion_point(field_list:mozilla.layers.layerscope.LayersPacket.layer) + return layer_; +} + +// ------------------------------------------------------------------- + +// MetaPacket + +// optional bool composedByHwc = 1; +inline bool MetaPacket::_internal_has_composedbyhwc() const { + bool value = (_has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool MetaPacket::has_composedbyhwc() const { + return _internal_has_composedbyhwc(); +} +inline void MetaPacket::clear_composedbyhwc() { + composedbyhwc_ = false; + _has_bits_[0] &= ~0x00000001u; +} +inline bool MetaPacket::_internal_composedbyhwc() const { + return composedbyhwc_; +} +inline bool MetaPacket::composedbyhwc() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.MetaPacket.composedByHwc) + return _internal_composedbyhwc(); +} +inline void MetaPacket::_internal_set_composedbyhwc(bool value) { + _has_bits_[0] |= 0x00000001u; + composedbyhwc_ = value; +} +inline void MetaPacket::set_composedbyhwc(bool value) { + _internal_set_composedbyhwc(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.MetaPacket.composedByHwc) +} + +// ------------------------------------------------------------------- + +// DrawPacket_Rect + +// required float x = 1; +inline bool DrawPacket_Rect::_internal_has_x() const { + bool value = (_has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool DrawPacket_Rect::has_x() const { + return _internal_has_x(); +} +inline void DrawPacket_Rect::clear_x() { + x_ = 0; + _has_bits_[0] &= ~0x00000001u; +} +inline float DrawPacket_Rect::_internal_x() const { + return x_; +} +inline float DrawPacket_Rect::x() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.DrawPacket.Rect.x) + return _internal_x(); +} +inline void DrawPacket_Rect::_internal_set_x(float value) { + _has_bits_[0] |= 0x00000001u; + x_ = value; +} +inline void DrawPacket_Rect::set_x(float value) { + _internal_set_x(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.DrawPacket.Rect.x) +} + +// required float y = 2; +inline bool DrawPacket_Rect::_internal_has_y() const { + bool value = (_has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool DrawPacket_Rect::has_y() const { + return _internal_has_y(); +} +inline void DrawPacket_Rect::clear_y() { + y_ = 0; + _has_bits_[0] &= ~0x00000002u; +} +inline float DrawPacket_Rect::_internal_y() const { + return y_; +} +inline float DrawPacket_Rect::y() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.DrawPacket.Rect.y) + return _internal_y(); +} +inline void DrawPacket_Rect::_internal_set_y(float value) { + _has_bits_[0] |= 0x00000002u; + y_ = value; +} +inline void DrawPacket_Rect::set_y(float value) { + _internal_set_y(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.DrawPacket.Rect.y) +} + +// required float w = 3; +inline bool DrawPacket_Rect::_internal_has_w() const { + bool value = (_has_bits_[0] & 0x00000004u) != 0; + return value; +} +inline bool DrawPacket_Rect::has_w() const { + return _internal_has_w(); +} +inline void DrawPacket_Rect::clear_w() { + w_ = 0; + _has_bits_[0] &= ~0x00000004u; +} +inline float DrawPacket_Rect::_internal_w() const { + return w_; +} +inline float DrawPacket_Rect::w() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.DrawPacket.Rect.w) + return _internal_w(); +} +inline void DrawPacket_Rect::_internal_set_w(float value) { + _has_bits_[0] |= 0x00000004u; + w_ = value; +} +inline void DrawPacket_Rect::set_w(float value) { + _internal_set_w(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.DrawPacket.Rect.w) +} + +// required float h = 4; +inline bool DrawPacket_Rect::_internal_has_h() const { + bool value = (_has_bits_[0] & 0x00000008u) != 0; + return value; +} +inline bool DrawPacket_Rect::has_h() const { + return _internal_has_h(); +} +inline void DrawPacket_Rect::clear_h() { + h_ = 0; + _has_bits_[0] &= ~0x00000008u; +} +inline float DrawPacket_Rect::_internal_h() const { + return h_; +} +inline float DrawPacket_Rect::h() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.DrawPacket.Rect.h) + return _internal_h(); +} +inline void DrawPacket_Rect::_internal_set_h(float value) { + _has_bits_[0] |= 0x00000008u; + h_ = value; +} +inline void DrawPacket_Rect::set_h(float value) { + _internal_set_h(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.DrawPacket.Rect.h) +} + +// ------------------------------------------------------------------- + +// DrawPacket + +// required float offsetX = 1; +inline bool DrawPacket::_internal_has_offsetx() const { + bool value = (_has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool DrawPacket::has_offsetx() const { + return _internal_has_offsetx(); +} +inline void DrawPacket::clear_offsetx() { + offsetx_ = 0; + _has_bits_[0] &= ~0x00000001u; +} +inline float DrawPacket::_internal_offsetx() const { + return offsetx_; +} +inline float DrawPacket::offsetx() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.DrawPacket.offsetX) + return _internal_offsetx(); +} +inline void DrawPacket::_internal_set_offsetx(float value) { + _has_bits_[0] |= 0x00000001u; + offsetx_ = value; +} +inline void DrawPacket::set_offsetx(float value) { + _internal_set_offsetx(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.DrawPacket.offsetX) +} + +// required float offsetY = 2; +inline bool DrawPacket::_internal_has_offsety() const { + bool value = (_has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool DrawPacket::has_offsety() const { + return _internal_has_offsety(); +} +inline void DrawPacket::clear_offsety() { + offsety_ = 0; + _has_bits_[0] &= ~0x00000002u; +} +inline float DrawPacket::_internal_offsety() const { + return offsety_; +} +inline float DrawPacket::offsety() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.DrawPacket.offsetY) + return _internal_offsety(); +} +inline void DrawPacket::_internal_set_offsety(float value) { + _has_bits_[0] |= 0x00000002u; + offsety_ = value; +} +inline void DrawPacket::set_offsety(float value) { + _internal_set_offsety(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.DrawPacket.offsetY) +} + +// repeated float mvMatrix = 3; +inline int DrawPacket::_internal_mvmatrix_size() const { + return mvmatrix_.size(); +} +inline int DrawPacket::mvmatrix_size() const { + return _internal_mvmatrix_size(); +} +inline void DrawPacket::clear_mvmatrix() { + mvmatrix_.Clear(); +} +inline float DrawPacket::_internal_mvmatrix(int index) const { + return mvmatrix_.Get(index); +} +inline float DrawPacket::mvmatrix(int index) const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.DrawPacket.mvMatrix) + return _internal_mvmatrix(index); +} +inline void DrawPacket::set_mvmatrix(int index, float value) { + mvmatrix_.Set(index, value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.DrawPacket.mvMatrix) +} +inline void DrawPacket::_internal_add_mvmatrix(float value) { + mvmatrix_.Add(value); +} +inline void DrawPacket::add_mvmatrix(float value) { + _internal_add_mvmatrix(value); + // @@protoc_insertion_point(field_add:mozilla.layers.layerscope.DrawPacket.mvMatrix) +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedField< float >& +DrawPacket::_internal_mvmatrix() const { + return mvmatrix_; +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedField< float >& +DrawPacket::mvmatrix() const { + // @@protoc_insertion_point(field_list:mozilla.layers.layerscope.DrawPacket.mvMatrix) + return _internal_mvmatrix(); +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedField< float >* +DrawPacket::_internal_mutable_mvmatrix() { + return &mvmatrix_; +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedField< float >* +DrawPacket::mutable_mvmatrix() { + // @@protoc_insertion_point(field_mutable_list:mozilla.layers.layerscope.DrawPacket.mvMatrix) + return _internal_mutable_mvmatrix(); +} + +// required uint32 totalRects = 4; +inline bool DrawPacket::_internal_has_totalrects() const { + bool value = (_has_bits_[0] & 0x00000008u) != 0; + return value; +} +inline bool DrawPacket::has_totalrects() const { + return _internal_has_totalrects(); +} +inline void DrawPacket::clear_totalrects() { + totalrects_ = 0u; + _has_bits_[0] &= ~0x00000008u; +} +inline ::PROTOBUF_NAMESPACE_ID::uint32 DrawPacket::_internal_totalrects() const { + return totalrects_; +} +inline ::PROTOBUF_NAMESPACE_ID::uint32 DrawPacket::totalrects() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.DrawPacket.totalRects) + return _internal_totalrects(); +} +inline void DrawPacket::_internal_set_totalrects(::PROTOBUF_NAMESPACE_ID::uint32 value) { + _has_bits_[0] |= 0x00000008u; + totalrects_ = value; +} +inline void DrawPacket::set_totalrects(::PROTOBUF_NAMESPACE_ID::uint32 value) { + _internal_set_totalrects(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.DrawPacket.totalRects) +} + +// repeated .mozilla.layers.layerscope.DrawPacket.Rect layerRect = 5; +inline int DrawPacket::_internal_layerrect_size() const { + return layerrect_.size(); +} +inline int DrawPacket::layerrect_size() const { + return _internal_layerrect_size(); +} +inline void DrawPacket::clear_layerrect() { + layerrect_.Clear(); +} +inline ::mozilla::layers::layerscope::DrawPacket_Rect* DrawPacket::mutable_layerrect(int index) { + // @@protoc_insertion_point(field_mutable:mozilla.layers.layerscope.DrawPacket.layerRect) + return layerrect_.Mutable(index); +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::layers::layerscope::DrawPacket_Rect >* +DrawPacket::mutable_layerrect() { + // @@protoc_insertion_point(field_mutable_list:mozilla.layers.layerscope.DrawPacket.layerRect) + return &layerrect_; +} +inline const ::mozilla::layers::layerscope::DrawPacket_Rect& DrawPacket::_internal_layerrect(int index) const { + return layerrect_.Get(index); +} +inline const ::mozilla::layers::layerscope::DrawPacket_Rect& DrawPacket::layerrect(int index) const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.DrawPacket.layerRect) + return _internal_layerrect(index); +} +inline ::mozilla::layers::layerscope::DrawPacket_Rect* DrawPacket::_internal_add_layerrect() { + return layerrect_.Add(); +} +inline ::mozilla::layers::layerscope::DrawPacket_Rect* DrawPacket::add_layerrect() { + // @@protoc_insertion_point(field_add:mozilla.layers.layerscope.DrawPacket.layerRect) + return _internal_add_layerrect(); +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::layers::layerscope::DrawPacket_Rect >& +DrawPacket::layerrect() const { + // @@protoc_insertion_point(field_list:mozilla.layers.layerscope.DrawPacket.layerRect) + return layerrect_; +} + +// required uint64 layerref = 6; +inline bool DrawPacket::_internal_has_layerref() const { + bool value = (_has_bits_[0] & 0x00000004u) != 0; + return value; +} +inline bool DrawPacket::has_layerref() const { + return _internal_has_layerref(); +} +inline void DrawPacket::clear_layerref() { + layerref_ = PROTOBUF_ULONGLONG(0); + _has_bits_[0] &= ~0x00000004u; +} +inline ::PROTOBUF_NAMESPACE_ID::uint64 DrawPacket::_internal_layerref() const { + return layerref_; +} +inline ::PROTOBUF_NAMESPACE_ID::uint64 DrawPacket::layerref() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.DrawPacket.layerref) + return _internal_layerref(); +} +inline void DrawPacket::_internal_set_layerref(::PROTOBUF_NAMESPACE_ID::uint64 value) { + _has_bits_[0] |= 0x00000004u; + layerref_ = value; +} +inline void DrawPacket::set_layerref(::PROTOBUF_NAMESPACE_ID::uint64 value) { + _internal_set_layerref(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.DrawPacket.layerref) +} + +// repeated uint32 texIDs = 7; +inline int DrawPacket::_internal_texids_size() const { + return texids_.size(); +} +inline int DrawPacket::texids_size() const { + return _internal_texids_size(); +} +inline void DrawPacket::clear_texids() { + texids_.Clear(); +} +inline ::PROTOBUF_NAMESPACE_ID::uint32 DrawPacket::_internal_texids(int index) const { + return texids_.Get(index); +} +inline ::PROTOBUF_NAMESPACE_ID::uint32 DrawPacket::texids(int index) const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.DrawPacket.texIDs) + return _internal_texids(index); +} +inline void DrawPacket::set_texids(int index, ::PROTOBUF_NAMESPACE_ID::uint32 value) { + texids_.Set(index, value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.DrawPacket.texIDs) +} +inline void DrawPacket::_internal_add_texids(::PROTOBUF_NAMESPACE_ID::uint32 value) { + texids_.Add(value); +} +inline void DrawPacket::add_texids(::PROTOBUF_NAMESPACE_ID::uint32 value) { + _internal_add_texids(value); + // @@protoc_insertion_point(field_add:mozilla.layers.layerscope.DrawPacket.texIDs) +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedField< ::PROTOBUF_NAMESPACE_ID::uint32 >& +DrawPacket::_internal_texids() const { + return texids_; +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedField< ::PROTOBUF_NAMESPACE_ID::uint32 >& +DrawPacket::texids() const { + // @@protoc_insertion_point(field_list:mozilla.layers.layerscope.DrawPacket.texIDs) + return _internal_texids(); +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedField< ::PROTOBUF_NAMESPACE_ID::uint32 >* +DrawPacket::_internal_mutable_texids() { + return &texids_; +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedField< ::PROTOBUF_NAMESPACE_ID::uint32 >* +DrawPacket::mutable_texids() { + // @@protoc_insertion_point(field_mutable_list:mozilla.layers.layerscope.DrawPacket.texIDs) + return _internal_mutable_texids(); +} + +// repeated .mozilla.layers.layerscope.DrawPacket.Rect textureRect = 8; +inline int DrawPacket::_internal_texturerect_size() const { + return texturerect_.size(); +} +inline int DrawPacket::texturerect_size() const { + return _internal_texturerect_size(); +} +inline void DrawPacket::clear_texturerect() { + texturerect_.Clear(); +} +inline ::mozilla::layers::layerscope::DrawPacket_Rect* DrawPacket::mutable_texturerect(int index) { + // @@protoc_insertion_point(field_mutable:mozilla.layers.layerscope.DrawPacket.textureRect) + return texturerect_.Mutable(index); +} +inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::layers::layerscope::DrawPacket_Rect >* +DrawPacket::mutable_texturerect() { + // @@protoc_insertion_point(field_mutable_list:mozilla.layers.layerscope.DrawPacket.textureRect) + return &texturerect_; +} +inline const ::mozilla::layers::layerscope::DrawPacket_Rect& DrawPacket::_internal_texturerect(int index) const { + return texturerect_.Get(index); +} +inline const ::mozilla::layers::layerscope::DrawPacket_Rect& DrawPacket::texturerect(int index) const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.DrawPacket.textureRect) + return _internal_texturerect(index); +} +inline ::mozilla::layers::layerscope::DrawPacket_Rect* DrawPacket::_internal_add_texturerect() { + return texturerect_.Add(); +} +inline ::mozilla::layers::layerscope::DrawPacket_Rect* DrawPacket::add_texturerect() { + // @@protoc_insertion_point(field_add:mozilla.layers.layerscope.DrawPacket.textureRect) + return _internal_add_texturerect(); +} +inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::layers::layerscope::DrawPacket_Rect >& +DrawPacket::texturerect() const { + // @@protoc_insertion_point(field_list:mozilla.layers.layerscope.DrawPacket.textureRect) + return texturerect_; +} + +// ------------------------------------------------------------------- + +// Packet + +// required .mozilla.layers.layerscope.Packet.DataType type = 1; +inline bool Packet::_internal_has_type() const { + bool value = (_has_bits_[0] & 0x00000040u) != 0; + return value; +} +inline bool Packet::has_type() const { + return _internal_has_type(); +} +inline void Packet::clear_type() { + type_ = 1; + _has_bits_[0] &= ~0x00000040u; +} +inline ::mozilla::layers::layerscope::Packet_DataType Packet::_internal_type() const { + return static_cast< ::mozilla::layers::layerscope::Packet_DataType >(type_); +} +inline ::mozilla::layers::layerscope::Packet_DataType Packet::type() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.Packet.type) + return _internal_type(); +} +inline void Packet::_internal_set_type(::mozilla::layers::layerscope::Packet_DataType value) { + assert(::mozilla::layers::layerscope::Packet_DataType_IsValid(value)); + _has_bits_[0] |= 0x00000040u; + type_ = value; +} +inline void Packet::set_type(::mozilla::layers::layerscope::Packet_DataType value) { + _internal_set_type(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.Packet.type) +} + +// optional .mozilla.layers.layerscope.FramePacket frame = 2; +inline bool Packet::_internal_has_frame() const { + bool value = (_has_bits_[0] & 0x00000001u) != 0; + PROTOBUF_ASSUME(!value || frame_ != nullptr); + return value; +} +inline bool Packet::has_frame() const { + return _internal_has_frame(); +} +inline void Packet::clear_frame() { + if (frame_ != nullptr) frame_->Clear(); + _has_bits_[0] &= ~0x00000001u; +} +inline const ::mozilla::layers::layerscope::FramePacket& Packet::_internal_frame() const { + const ::mozilla::layers::layerscope::FramePacket* p = frame_; + return p != nullptr ? *p : *reinterpret_cast( + &::mozilla::layers::layerscope::_FramePacket_default_instance_); +} +inline const ::mozilla::layers::layerscope::FramePacket& Packet::frame() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.Packet.frame) + return _internal_frame(); +} +inline ::mozilla::layers::layerscope::FramePacket* Packet::release_frame() { + // @@protoc_insertion_point(field_release:mozilla.layers.layerscope.Packet.frame) + _has_bits_[0] &= ~0x00000001u; + ::mozilla::layers::layerscope::FramePacket* temp = frame_; + frame_ = nullptr; + return temp; +} +inline ::mozilla::layers::layerscope::FramePacket* Packet::_internal_mutable_frame() { + _has_bits_[0] |= 0x00000001u; + if (frame_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::layers::layerscope::FramePacket>(GetArenaNoVirtual()); + frame_ = p; + } + return frame_; +} +inline ::mozilla::layers::layerscope::FramePacket* Packet::mutable_frame() { + // @@protoc_insertion_point(field_mutable:mozilla.layers.layerscope.Packet.frame) + return _internal_mutable_frame(); +} +inline void Packet::set_allocated_frame(::mozilla::layers::layerscope::FramePacket* frame) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaNoVirtual(); + if (message_arena == nullptr) { + delete frame_; + } + if (frame) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = nullptr; + if (message_arena != submessage_arena) { + frame = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, frame, submessage_arena); + } + _has_bits_[0] |= 0x00000001u; + } else { + _has_bits_[0] &= ~0x00000001u; + } + frame_ = frame; + // @@protoc_insertion_point(field_set_allocated:mozilla.layers.layerscope.Packet.frame) +} + +// optional .mozilla.layers.layerscope.ColorPacket color = 3; +inline bool Packet::_internal_has_color() const { + bool value = (_has_bits_[0] & 0x00000002u) != 0; + PROTOBUF_ASSUME(!value || color_ != nullptr); + return value; +} +inline bool Packet::has_color() const { + return _internal_has_color(); +} +inline void Packet::clear_color() { + if (color_ != nullptr) color_->Clear(); + _has_bits_[0] &= ~0x00000002u; +} +inline const ::mozilla::layers::layerscope::ColorPacket& Packet::_internal_color() const { + const ::mozilla::layers::layerscope::ColorPacket* p = color_; + return p != nullptr ? *p : *reinterpret_cast( + &::mozilla::layers::layerscope::_ColorPacket_default_instance_); +} +inline const ::mozilla::layers::layerscope::ColorPacket& Packet::color() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.Packet.color) + return _internal_color(); +} +inline ::mozilla::layers::layerscope::ColorPacket* Packet::release_color() { + // @@protoc_insertion_point(field_release:mozilla.layers.layerscope.Packet.color) + _has_bits_[0] &= ~0x00000002u; + ::mozilla::layers::layerscope::ColorPacket* temp = color_; + color_ = nullptr; + return temp; +} +inline ::mozilla::layers::layerscope::ColorPacket* Packet::_internal_mutable_color() { + _has_bits_[0] |= 0x00000002u; + if (color_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::layers::layerscope::ColorPacket>(GetArenaNoVirtual()); + color_ = p; + } + return color_; +} +inline ::mozilla::layers::layerscope::ColorPacket* Packet::mutable_color() { + // @@protoc_insertion_point(field_mutable:mozilla.layers.layerscope.Packet.color) + return _internal_mutable_color(); +} +inline void Packet::set_allocated_color(::mozilla::layers::layerscope::ColorPacket* color) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaNoVirtual(); + if (message_arena == nullptr) { + delete color_; + } + if (color) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = nullptr; + if (message_arena != submessage_arena) { + color = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, color, submessage_arena); + } + _has_bits_[0] |= 0x00000002u; + } else { + _has_bits_[0] &= ~0x00000002u; + } + color_ = color; + // @@protoc_insertion_point(field_set_allocated:mozilla.layers.layerscope.Packet.color) +} + +// optional .mozilla.layers.layerscope.TexturePacket texture = 4; +inline bool Packet::_internal_has_texture() const { + bool value = (_has_bits_[0] & 0x00000004u) != 0; + PROTOBUF_ASSUME(!value || texture_ != nullptr); + return value; +} +inline bool Packet::has_texture() const { + return _internal_has_texture(); +} +inline void Packet::clear_texture() { + if (texture_ != nullptr) texture_->Clear(); + _has_bits_[0] &= ~0x00000004u; +} +inline const ::mozilla::layers::layerscope::TexturePacket& Packet::_internal_texture() const { + const ::mozilla::layers::layerscope::TexturePacket* p = texture_; + return p != nullptr ? *p : *reinterpret_cast( + &::mozilla::layers::layerscope::_TexturePacket_default_instance_); +} +inline const ::mozilla::layers::layerscope::TexturePacket& Packet::texture() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.Packet.texture) + return _internal_texture(); +} +inline ::mozilla::layers::layerscope::TexturePacket* Packet::release_texture() { + // @@protoc_insertion_point(field_release:mozilla.layers.layerscope.Packet.texture) + _has_bits_[0] &= ~0x00000004u; + ::mozilla::layers::layerscope::TexturePacket* temp = texture_; + texture_ = nullptr; + return temp; +} +inline ::mozilla::layers::layerscope::TexturePacket* Packet::_internal_mutable_texture() { + _has_bits_[0] |= 0x00000004u; + if (texture_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::layers::layerscope::TexturePacket>(GetArenaNoVirtual()); + texture_ = p; + } + return texture_; +} +inline ::mozilla::layers::layerscope::TexturePacket* Packet::mutable_texture() { + // @@protoc_insertion_point(field_mutable:mozilla.layers.layerscope.Packet.texture) + return _internal_mutable_texture(); +} +inline void Packet::set_allocated_texture(::mozilla::layers::layerscope::TexturePacket* texture) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaNoVirtual(); + if (message_arena == nullptr) { + delete texture_; + } + if (texture) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = nullptr; + if (message_arena != submessage_arena) { + texture = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, texture, submessage_arena); + } + _has_bits_[0] |= 0x00000004u; + } else { + _has_bits_[0] &= ~0x00000004u; + } + texture_ = texture; + // @@protoc_insertion_point(field_set_allocated:mozilla.layers.layerscope.Packet.texture) +} + +// optional .mozilla.layers.layerscope.LayersPacket layers = 5; +inline bool Packet::_internal_has_layers() const { + bool value = (_has_bits_[0] & 0x00000008u) != 0; + PROTOBUF_ASSUME(!value || layers_ != nullptr); + return value; +} +inline bool Packet::has_layers() const { + return _internal_has_layers(); +} +inline void Packet::clear_layers() { + if (layers_ != nullptr) layers_->Clear(); + _has_bits_[0] &= ~0x00000008u; +} +inline const ::mozilla::layers::layerscope::LayersPacket& Packet::_internal_layers() const { + const ::mozilla::layers::layerscope::LayersPacket* p = layers_; + return p != nullptr ? *p : *reinterpret_cast( + &::mozilla::layers::layerscope::_LayersPacket_default_instance_); +} +inline const ::mozilla::layers::layerscope::LayersPacket& Packet::layers() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.Packet.layers) + return _internal_layers(); +} +inline ::mozilla::layers::layerscope::LayersPacket* Packet::release_layers() { + // @@protoc_insertion_point(field_release:mozilla.layers.layerscope.Packet.layers) + _has_bits_[0] &= ~0x00000008u; + ::mozilla::layers::layerscope::LayersPacket* temp = layers_; + layers_ = nullptr; + return temp; +} +inline ::mozilla::layers::layerscope::LayersPacket* Packet::_internal_mutable_layers() { + _has_bits_[0] |= 0x00000008u; + if (layers_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::layers::layerscope::LayersPacket>(GetArenaNoVirtual()); + layers_ = p; + } + return layers_; +} +inline ::mozilla::layers::layerscope::LayersPacket* Packet::mutable_layers() { + // @@protoc_insertion_point(field_mutable:mozilla.layers.layerscope.Packet.layers) + return _internal_mutable_layers(); +} +inline void Packet::set_allocated_layers(::mozilla::layers::layerscope::LayersPacket* layers) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaNoVirtual(); + if (message_arena == nullptr) { + delete layers_; + } + if (layers) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = nullptr; + if (message_arena != submessage_arena) { + layers = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, layers, submessage_arena); + } + _has_bits_[0] |= 0x00000008u; + } else { + _has_bits_[0] &= ~0x00000008u; + } + layers_ = layers; + // @@protoc_insertion_point(field_set_allocated:mozilla.layers.layerscope.Packet.layers) +} + +// optional .mozilla.layers.layerscope.MetaPacket meta = 6; +inline bool Packet::_internal_has_meta() const { + bool value = (_has_bits_[0] & 0x00000010u) != 0; + PROTOBUF_ASSUME(!value || meta_ != nullptr); + return value; +} +inline bool Packet::has_meta() const { + return _internal_has_meta(); +} +inline void Packet::clear_meta() { + if (meta_ != nullptr) meta_->Clear(); + _has_bits_[0] &= ~0x00000010u; +} +inline const ::mozilla::layers::layerscope::MetaPacket& Packet::_internal_meta() const { + const ::mozilla::layers::layerscope::MetaPacket* p = meta_; + return p != nullptr ? *p : *reinterpret_cast( + &::mozilla::layers::layerscope::_MetaPacket_default_instance_); +} +inline const ::mozilla::layers::layerscope::MetaPacket& Packet::meta() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.Packet.meta) + return _internal_meta(); +} +inline ::mozilla::layers::layerscope::MetaPacket* Packet::release_meta() { + // @@protoc_insertion_point(field_release:mozilla.layers.layerscope.Packet.meta) + _has_bits_[0] &= ~0x00000010u; + ::mozilla::layers::layerscope::MetaPacket* temp = meta_; + meta_ = nullptr; + return temp; +} +inline ::mozilla::layers::layerscope::MetaPacket* Packet::_internal_mutable_meta() { + _has_bits_[0] |= 0x00000010u; + if (meta_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::layers::layerscope::MetaPacket>(GetArenaNoVirtual()); + meta_ = p; + } + return meta_; +} +inline ::mozilla::layers::layerscope::MetaPacket* Packet::mutable_meta() { + // @@protoc_insertion_point(field_mutable:mozilla.layers.layerscope.Packet.meta) + return _internal_mutable_meta(); +} +inline void Packet::set_allocated_meta(::mozilla::layers::layerscope::MetaPacket* meta) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaNoVirtual(); + if (message_arena == nullptr) { + delete meta_; + } + if (meta) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = nullptr; + if (message_arena != submessage_arena) { + meta = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, meta, submessage_arena); + } + _has_bits_[0] |= 0x00000010u; + } else { + _has_bits_[0] &= ~0x00000010u; + } + meta_ = meta; + // @@protoc_insertion_point(field_set_allocated:mozilla.layers.layerscope.Packet.meta) +} + +// optional .mozilla.layers.layerscope.DrawPacket draw = 7; +inline bool Packet::_internal_has_draw() const { + bool value = (_has_bits_[0] & 0x00000020u) != 0; + PROTOBUF_ASSUME(!value || draw_ != nullptr); + return value; +} +inline bool Packet::has_draw() const { + return _internal_has_draw(); +} +inline void Packet::clear_draw() { + if (draw_ != nullptr) draw_->Clear(); + _has_bits_[0] &= ~0x00000020u; +} +inline const ::mozilla::layers::layerscope::DrawPacket& Packet::_internal_draw() const { + const ::mozilla::layers::layerscope::DrawPacket* p = draw_; + return p != nullptr ? *p : *reinterpret_cast( + &::mozilla::layers::layerscope::_DrawPacket_default_instance_); +} +inline const ::mozilla::layers::layerscope::DrawPacket& Packet::draw() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.Packet.draw) + return _internal_draw(); +} +inline ::mozilla::layers::layerscope::DrawPacket* Packet::release_draw() { + // @@protoc_insertion_point(field_release:mozilla.layers.layerscope.Packet.draw) + _has_bits_[0] &= ~0x00000020u; + ::mozilla::layers::layerscope::DrawPacket* temp = draw_; + draw_ = nullptr; + return temp; +} +inline ::mozilla::layers::layerscope::DrawPacket* Packet::_internal_mutable_draw() { + _has_bits_[0] |= 0x00000020u; + if (draw_ == nullptr) { + auto* p = CreateMaybeMessage<::mozilla::layers::layerscope::DrawPacket>(GetArenaNoVirtual()); + draw_ = p; + } + return draw_; +} +inline ::mozilla::layers::layerscope::DrawPacket* Packet::mutable_draw() { + // @@protoc_insertion_point(field_mutable:mozilla.layers.layerscope.Packet.draw) + return _internal_mutable_draw(); +} +inline void Packet::set_allocated_draw(::mozilla::layers::layerscope::DrawPacket* draw) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaNoVirtual(); + if (message_arena == nullptr) { + delete draw_; + } + if (draw) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = nullptr; + if (message_arena != submessage_arena) { + draw = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, draw, submessage_arena); + } + _has_bits_[0] |= 0x00000020u; + } else { + _has_bits_[0] &= ~0x00000020u; + } + draw_ = draw; + // @@protoc_insertion_point(field_set_allocated:mozilla.layers.layerscope.Packet.draw) +} + +// ------------------------------------------------------------------- + +// CommandPacket + +// required .mozilla.layers.layerscope.CommandPacket.CmdType type = 1; +inline bool CommandPacket::_internal_has_type() const { + bool value = (_has_bits_[0] & 0x00000001u) != 0; + return value; +} +inline bool CommandPacket::has_type() const { + return _internal_has_type(); +} +inline void CommandPacket::clear_type() { + type_ = 0; + _has_bits_[0] &= ~0x00000001u; +} +inline ::mozilla::layers::layerscope::CommandPacket_CmdType CommandPacket::_internal_type() const { + return static_cast< ::mozilla::layers::layerscope::CommandPacket_CmdType >(type_); +} +inline ::mozilla::layers::layerscope::CommandPacket_CmdType CommandPacket::type() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.CommandPacket.type) + return _internal_type(); +} +inline void CommandPacket::_internal_set_type(::mozilla::layers::layerscope::CommandPacket_CmdType value) { + assert(::mozilla::layers::layerscope::CommandPacket_CmdType_IsValid(value)); + _has_bits_[0] |= 0x00000001u; + type_ = value; +} +inline void CommandPacket::set_type(::mozilla::layers::layerscope::CommandPacket_CmdType value) { + _internal_set_type(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.CommandPacket.type) +} + +// optional bool value = 2; +inline bool CommandPacket::_internal_has_value() const { + bool value = (_has_bits_[0] & 0x00000002u) != 0; + return value; +} +inline bool CommandPacket::has_value() const { + return _internal_has_value(); +} +inline void CommandPacket::clear_value() { + value_ = false; + _has_bits_[0] &= ~0x00000002u; +} +inline bool CommandPacket::_internal_value() const { + return value_; +} +inline bool CommandPacket::value() const { + // @@protoc_insertion_point(field_get:mozilla.layers.layerscope.CommandPacket.value) + return _internal_value(); +} +inline void CommandPacket::_internal_set_value(bool value) { + _has_bits_[0] |= 0x00000002u; + value_ = value; +} +inline void CommandPacket::set_value(bool value) { + _internal_set_value(value); + // @@protoc_insertion_point(field_set:mozilla.layers.layerscope.CommandPacket.value) +} + +#ifdef __GNUC__ + #pragma GCC diagnostic pop +#endif // __GNUC__ +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + + +// @@protoc_insertion_point(namespace_scope) + +} // namespace layerscope +} // namespace layers +} // namespace mozilla + +PROTOBUF_NAMESPACE_OPEN + +template <> struct is_proto_enum< ::mozilla::layers::layerscope::TexturePacket_Filter> : ::std::true_type {}; +template <> struct is_proto_enum< ::mozilla::layers::layerscope::LayersPacket_Layer_LayerType> : ::std::true_type {}; +template <> struct is_proto_enum< ::mozilla::layers::layerscope::LayersPacket_Layer_ScrollingDirect> : ::std::true_type {}; +template <> struct is_proto_enum< ::mozilla::layers::layerscope::LayersPacket_Layer_Filter> : ::std::true_type {}; +template <> struct is_proto_enum< ::mozilla::layers::layerscope::Packet_DataType> : ::std::true_type {}; +template <> struct is_proto_enum< ::mozilla::layers::layerscope::CommandPacket_CmdType> : ::std::true_type {}; + +PROTOBUF_NAMESPACE_CLOSE + +// @@protoc_insertion_point(global_scope) + +#include +#endif // GOOGLE_PROTOBUF_INCLUDED_GOOGLE_PROTOBUF_INCLUDED_LayerScopePacket_2eproto diff --git a/gfx/layers/protobuf/LayerScopePacket.proto b/gfx/layers/protobuf/LayerScopePacket.proto new file mode 100644 index 0000000000..a871aa8594 --- /dev/null +++ b/gfx/layers/protobuf/LayerScopePacket.proto @@ -0,0 +1,221 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package mozilla.layers.layerscope; + +// =============================== +// Server to Client messages +// =============================== +message FramePacket { + optional uint64 value = 1; + optional float scale = 2; +} + +message ColorPacket { + required uint64 layerref = 1; + optional uint32 width = 2; + optional uint32 height = 3; + optional uint32 color = 4; +} + +message TexturePacket { + enum Filter { + GOOD = 0; + LINEAR = 1; + POINT = 2; + } + message Rect { + optional float x = 1; + optional float y = 2; + optional float w = 3; + optional float h = 4; + } + message Size { + optional int32 w = 1; + optional int32 h = 2; + } + message Matrix { + optional bool is2D = 1; + optional bool isId = 2; + repeated float m = 3; + } + message EffectMask { + optional bool mIs3D = 1; + optional Size mSize = 2; + optional Matrix mMaskTransform = 3; + } + + // Basic info + required uint64 layerref = 1; + optional uint32 width = 2; + optional uint32 height = 3; + optional uint32 stride = 4; + optional uint32 name = 5; + optional uint32 target = 6; + optional uint32 dataformat = 7; + optional uint64 glcontext = 8; + optional bytes data = 9; + + // TextureEffect attributes + optional Rect mTextureCoords = 10; + optional bool mPremultiplied = 11; + optional Filter mFilter = 12; + + // Mask attributes + optional bool isMask = 20; + optional EffectMask mask = 21; +} + +message LayersPacket { + message Layer { + enum LayerType { + UnknownLayer = 0; + LayerManager = 1; + ContainerLayer = 2; + PaintedLayer = 3; + CanvasLayer = 4; + ImageLayer = 5; + ColorLayer = 6; + RefLayer = 8; + ReadbackLayer = 9; + DisplayItemLayer = 10; + } + enum ScrollingDirect { + VERTICAL = 1; + HORIZONTAL = 2; + } + enum Filter { + FILTER_FAST = 0; // deprecated + FILTER_GOOD = 1; + FILTER_BEST = 2; // deprecated + FILTER_NEAREST = 3; //deprecated + FILTER_BILINEAR = 4; //deprecated + FILTER_GAUSSIAN = 5; //deprecated + FILTER_SENTINEL = 6; //deprecated + FILTER_LINEAR = 7; + FILTER_POINT = 8; + } + message Size { + optional int32 w = 1; + optional int32 h = 2; + } + message Rect { + optional int32 x = 1; + optional int32 y = 2; + optional int32 w = 3; + optional int32 h = 4; + } + message Region { + repeated Rect r = 1; + } + message Matrix { + optional bool is2D = 1; + optional bool isId = 2; + repeated float m = 3; + } + message Shadow { + optional Rect clip = 1; + optional Matrix transform = 2; + optional Region vRegion = 3; + } + + // Basic info + // Note: Parent's pointer is used to recontruct the layer tree + required LayerType type = 1; + required uint64 ptr = 2; + required uint64 parentPtr = 3; + + // Common info (10 to 99) + optional Rect clip = 10; + optional Matrix transform = 11; + optional Region vRegion = 12; // visible region + optional Shadow shadow = 13; // shadow info + optional float opacity = 14; + optional bool cOpaque = 15; // content opaque + optional bool cAlpha = 16; // component alpha + optional ScrollingDirect direct = 17; + optional uint64 barID = 18; + optional uint64 mask = 19; // mask layer + optional Region hitRegion = 20; + optional Region dispatchRegion = 21; + optional Region noActionRegion = 22; + optional Region hPanRegion = 23; + optional Region vPanRegion = 24; + + // Specific info (100 to max) + // Painted Layer + optional Region valid = 100; + // Color Layer + optional uint32 color = 101; + // Canvas & Image Layer + optional Filter filter = 102; + // Ref Layer + optional uint64 refID = 103; + // Readback Layer + optional Size size = 104; + optional uint32 displayListLogLength = 105; + optional bytes displayListLog = 106; + } + repeated Layer layer = 1; +} + +message MetaPacket { + optional bool composedByHwc = 1; +} + +message DrawPacket { + message Rect { + required float x = 1; + required float y = 2; + required float w = 3; + required float h = 4; + } + + required float offsetX = 1; + required float offsetY = 2; + repeated float mvMatrix = 3; + required uint32 totalRects = 4; + repeated Rect layerRect = 5; + required uint64 layerref = 6; + repeated uint32 texIDs = 7; + repeated Rect textureRect = 8; +} + +// We only need to use this Packet. +// Other packet definitions are just type defines +message Packet { + enum DataType { + FRAMESTART = 1; + FRAMEEND = 2; + COLOR = 3; + TEXTURE = 4; + LAYERS = 5; + META = 6; + DRAW = 7; + } + required DataType type = 1; + + optional FramePacket frame = 2; + optional ColorPacket color = 3; + optional TexturePacket texture = 4; + optional LayersPacket layers = 5; + optional MetaPacket meta = 6; + optional DrawPacket draw = 7; +} + + +// =============================== +// Client to Server messages +// =============================== +message CommandPacket { + enum CmdType { + NO_OP = 0; + LAYERS_TREE = 1; + LAYERS_BUFFER = 2; + } + required CmdType type = 1; + optional bool value = 2; +} diff --git a/gfx/layers/wr/AsyncImagePipelineManager.cpp b/gfx/layers/wr/AsyncImagePipelineManager.cpp new file mode 100644 index 0000000000..c9848e6d7c --- /dev/null +++ b/gfx/layers/wr/AsyncImagePipelineManager.cpp @@ -0,0 +1,746 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AsyncImagePipelineManager.h" + +#include +#include + +#include "CompositableHost.h" +#include "gfxEnv.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/layers/SharedSurfacesParent.h" +#include "mozilla/layers/WebRenderImageHost.h" +#include "mozilla/layers/WebRenderTextureHost.h" +#include "mozilla/webrender/RenderThread.h" +#include "mozilla/webrender/WebRenderAPI.h" +#include "mozilla/webrender/WebRenderTypes.h" + +#ifdef MOZ_WIDGET_ANDROID +# include "mozilla/layers/TextureHostOGL.h" +#endif + +namespace mozilla { +namespace layers { + +AsyncImagePipelineManager::ForwardingExternalImage::~ForwardingExternalImage() { + DebugOnly released = SharedSurfacesParent::Release(mImageId); + MOZ_ASSERT(released); +} + +AsyncImagePipelineManager::AsyncImagePipeline::AsyncImagePipeline() + : mInitialised(false), + mIsChanged(false), + mUseExternalImage(false), + mRotation(VideoInfo::Rotation::kDegree_0), + mFilter(wr::ImageRendering::Auto), + mMixBlendMode(wr::MixBlendMode::Normal) {} + +AsyncImagePipelineManager::AsyncImagePipelineManager( + RefPtr&& aApi, bool aUseCompositorWnd) + : mApi(aApi), + mUseCompositorWnd(aUseCompositorWnd), + mIdNamespace(mApi->GetNamespace()), + mUseTripleBuffering(mApi->GetUseTripleBuffering()), + mResourceId(0), + mAsyncImageEpoch{0}, + mWillGenerateFrame(false), + mDestroyed(false), +#ifdef XP_WIN + mUseWebRenderDCompVideoOverlayWin( + gfx::gfxVars::UseWebRenderDCompVideoOverlayWin()), +#endif + mRenderSubmittedUpdatesLock("SubmittedUpdatesLock"), + mLastCompletedFrameId(0) { + MOZ_COUNT_CTOR(AsyncImagePipelineManager); +} + +AsyncImagePipelineManager::~AsyncImagePipelineManager() { + MOZ_COUNT_DTOR(AsyncImagePipelineManager); +} + +void AsyncImagePipelineManager::Destroy() { + MOZ_ASSERT(!mDestroyed); + mApi = nullptr; + mPipelineTexturesHolders.Clear(); + mDestroyed = true; +} + +/* static */ +wr::ExternalImageId AsyncImagePipelineManager::GetNextExternalImageId() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + static uint64_t sResourceId = 0; + + ++sResourceId; + // Upper 32bit(namespace) needs to be 0. + // Namespace other than 0 might be used by others. + MOZ_RELEASE_ASSERT(sResourceId != UINT32_MAX); + return wr::ToExternalImageId(sResourceId); +} + +void AsyncImagePipelineManager::SetWillGenerateFrame() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + + mWillGenerateFrame = true; +} + +bool AsyncImagePipelineManager::GetAndResetWillGenerateFrame() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + + bool ret = mWillGenerateFrame; + mWillGenerateFrame = false; + return ret; +} + +void AsyncImagePipelineManager::AddPipeline(const wr::PipelineId& aPipelineId, + WebRenderBridgeParent* aWrBridge) { + if (mDestroyed) { + return; + } + uint64_t id = wr::AsUint64(aPipelineId); + + PipelineTexturesHolder* holder = + mPipelineTexturesHolders.Get(wr::AsUint64(aPipelineId)); + if (holder) { + // This could happen during tab move between different windows. + // Previously removed holder could be still alive for waiting destroyed. + MOZ_ASSERT(holder->mDestroyedEpoch.isSome()); + holder->mDestroyedEpoch = Nothing(); // Revive holder + holder->mWrBridge = aWrBridge; + return; + } + holder = new PipelineTexturesHolder(); + holder->mWrBridge = aWrBridge; + mPipelineTexturesHolders.Put(id, holder); +} + +void AsyncImagePipelineManager::RemovePipeline( + const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch) { + if (mDestroyed) { + return; + } + + PipelineTexturesHolder* holder = + mPipelineTexturesHolders.Get(wr::AsUint64(aPipelineId)); + MOZ_ASSERT(holder); + if (!holder) { + return; + } + holder->mWrBridge = nullptr; + holder->mDestroyedEpoch = Some(aEpoch); +} + +WebRenderBridgeParent* AsyncImagePipelineManager::GetWrBridge( + const wr::PipelineId& aPipelineId) { + if (mDestroyed) { + return nullptr; + } + + PipelineTexturesHolder* holder = + mPipelineTexturesHolders.Get(wr::AsUint64(aPipelineId)); + if (!holder) { + return nullptr; + } + if (holder->mWrBridge) { + MOZ_ASSERT(holder->mDestroyedEpoch.isNothing()); + return holder->mWrBridge; + } + + return nullptr; +} + +void AsyncImagePipelineManager::AddAsyncImagePipeline( + const wr::PipelineId& aPipelineId, WebRenderImageHost* aImageHost) { + if (mDestroyed) { + return; + } + MOZ_ASSERT(aImageHost); + uint64_t id = wr::AsUint64(aPipelineId); + + MOZ_ASSERT(!mAsyncImagePipelines.Get(id)); + AsyncImagePipeline* holder = new AsyncImagePipeline(); + holder->mImageHost = aImageHost; + mAsyncImagePipelines.Put(id, holder); + AddPipeline(aPipelineId, /* aWrBridge */ nullptr); +} + +void AsyncImagePipelineManager::RemoveAsyncImagePipeline( + const wr::PipelineId& aPipelineId, wr::TransactionBuilder& aTxn) { + if (mDestroyed) { + return; + } + + uint64_t id = wr::AsUint64(aPipelineId); + if (auto entry = mAsyncImagePipelines.Lookup(id)) { + const auto& holder = entry.Data(); + wr::Epoch epoch = GetNextImageEpoch(); + aTxn.ClearDisplayList(epoch, aPipelineId); + for (wr::ImageKey key : holder->mKeys) { + aTxn.DeleteImage(key); + } + entry.Remove(); + RemovePipeline(aPipelineId, epoch); + } +} + +void AsyncImagePipelineManager::UpdateAsyncImagePipeline( + const wr::PipelineId& aPipelineId, const LayoutDeviceRect& aScBounds, + const VideoInfo::Rotation aRotation, const wr::ImageRendering& aFilter, + const wr::MixBlendMode& aMixBlendMode) { + if (mDestroyed) { + return; + } + AsyncImagePipeline* pipeline = + mAsyncImagePipelines.Get(wr::AsUint64(aPipelineId)); + if (!pipeline) { + return; + } + pipeline->mInitialised = true; + pipeline->Update(aScBounds, aRotation, aFilter, aMixBlendMode); +} + +Maybe AsyncImagePipelineManager::UpdateImageKeys( + const wr::Epoch& aEpoch, const wr::PipelineId& aPipelineId, + AsyncImagePipeline* aPipeline, nsTArray& aKeys, + wr::TransactionBuilder& aSceneBuilderTxn, + wr::TransactionBuilder& aMaybeFastTxn) { + MOZ_ASSERT(aKeys.IsEmpty()); + MOZ_ASSERT(aPipeline); + + TextureHost* texture = + aPipeline->mImageHost->GetAsTextureHostForComposite(this); + TextureHost* previousTexture = aPipeline->mCurrentTexture.get(); + + if (texture == previousTexture) { + // The texture has not changed, just reuse previous ImageKeys. + aKeys = aPipeline->mKeys.Clone(); + return Nothing(); + } + + if (!texture || texture->NumSubTextures() == 0) { + // We don't have a new texture or texture does not have SubTextures, there + // isn't much we can do. + aKeys = aPipeline->mKeys.Clone(); + return Nothing(); + } + + aPipeline->mCurrentTexture = texture; + + WebRenderTextureHost* wrTexture = texture->AsWebRenderTextureHost(); + MOZ_ASSERT(wrTexture); + if (!wrTexture) { + gfxCriticalNote << "WebRenderTextureHost is not used"; + } + + bool useExternalImage = !!wrTexture; + aPipeline->mUseExternalImage = useExternalImage; + + // The non-external image code path falls back to converting the texture into + // an rgb image. + auto numKeys = useExternalImage ? texture->NumSubTextures() : 1; + MOZ_ASSERT(numKeys > 0); + + // If we already had a texture and the format hasn't changed, better to reuse + // the image keys than create new ones. + bool canUpdate = !!previousTexture && + previousTexture->GetSize() == texture->GetSize() && + previousTexture->GetFormat() == texture->GetFormat() && + previousTexture->NeedsYFlip() == texture->NeedsYFlip() && + previousTexture->SupportsExternalCompositing() == + texture->SupportsExternalCompositing() && + aPipeline->mKeys.Length() == numKeys; + + if (!canUpdate) { + for (auto key : aPipeline->mKeys) { + // Destroy ImageKeys on transaction of scene builder thread, since + // DisplayList is updated on SceneBuilder thread. It prevents too early + // ImageKey deletion. + aSceneBuilderTxn.DeleteImage(key); + } + aPipeline->mKeys.Clear(); + for (uint32_t i = 0; i < numKeys; ++i) { + aPipeline->mKeys.AppendElement(GenerateImageKey()); + } + } + + aKeys = aPipeline->mKeys.Clone(); + + auto op = canUpdate ? TextureHost::UPDATE_IMAGE : TextureHost::ADD_IMAGE; + + if (!useExternalImage) { + return UpdateWithoutExternalImage(texture, aKeys[0], op, aMaybeFastTxn); + } + + wrTexture->MaybeNotifyForUse(aMaybeFastTxn); + + Range keys(&aKeys[0], aKeys.Length()); + auto externalImageKey = wrTexture->GetExternalImageKey(); + wrTexture->PushResourceUpdates(aMaybeFastTxn, op, keys, externalImageKey); + + return Some(op); +} + +Maybe +AsyncImagePipelineManager::UpdateWithoutExternalImage( + TextureHost* aTexture, wr::ImageKey aKey, TextureHost::ResourceUpdateOp aOp, + wr::TransactionBuilder& aTxn) { + MOZ_ASSERT(aTexture); + + RefPtr dSurf = aTexture->GetAsSurface(); + if (!dSurf) { + NS_ERROR("TextureHost does not return DataSourceSurface"); + return Nothing(); + } + gfx::DataSourceSurface::MappedSurface map; + if (!dSurf->Map(gfx::DataSourceSurface::MapType::READ, &map)) { + NS_ERROR("DataSourceSurface failed to map"); + return Nothing(); + } + + gfx::IntSize size = dSurf->GetSize(); + wr::ImageDescriptor descriptor(size, map.mStride, dSurf->GetFormat()); + + // Costly copy right here... + wr::Vec bytes; + bytes.PushBytes(Range(map.mData, size.height * map.mStride)); + + if (aOp == TextureHost::UPDATE_IMAGE) { + aTxn.UpdateImageBuffer(aKey, descriptor, bytes); + } else { + aTxn.AddImage(aKey, descriptor, bytes); + } + + dSurf->Unmap(); + + return Some(aOp); +} + +void AsyncImagePipelineManager::ApplyAsyncImagesOfImageBridge( + wr::TransactionBuilder& aSceneBuilderTxn, + wr::TransactionBuilder& aFastTxn) { + if (mDestroyed || mAsyncImagePipelines.Count() == 0) { + return; + } + +#ifdef XP_WIN + // UseWebRenderDCompVideoOverlayWin() could be changed from true to false, + // when DCompVideoOverlay task is failed. In this case, DisplayItems need to + // be re-pushed to WebRender for disabling video overlay. + bool isChanged = mUseWebRenderDCompVideoOverlayWin != + gfx::gfxVars::UseWebRenderDCompVideoOverlayWin(); + if (isChanged) { + mUseWebRenderDCompVideoOverlayWin = + gfx::gfxVars::UseWebRenderDCompVideoOverlayWin(); + } +#endif + + wr::Epoch epoch = GetNextImageEpoch(); + + // We use a pipeline with a very small display list for each video element. + // Update each of them if needed. + for (auto iter = mAsyncImagePipelines.Iter(); !iter.Done(); iter.Next()) { + wr::PipelineId pipelineId = wr::AsPipelineId(iter.Key()); + AsyncImagePipeline* pipeline = iter.UserData(); + +#ifdef XP_WIN + if (isChanged) { + pipeline->mIsChanged = true; + } +#endif + + // If aync image pipeline does not use ImageBridge, do not need to apply. + if (!pipeline->mImageHost->GetAsyncRef()) { + continue; + } + ApplyAsyncImageForPipeline(epoch, pipelineId, pipeline, aSceneBuilderTxn, + aFastTxn); + } +} + +wr::WrRotation ToWrRotation(VideoInfo::Rotation aRotation) { + switch (aRotation) { + case VideoInfo::Rotation::kDegree_0: + return wr::WrRotation::Degree0; + case VideoInfo::Rotation::kDegree_90: + return wr::WrRotation::Degree90; + case VideoInfo::Rotation::kDegree_180: + return wr::WrRotation::Degree180; + case VideoInfo::Rotation::kDegree_270: + return wr::WrRotation::Degree270; + } + return wr::WrRotation::Degree0; +} + +void AsyncImagePipelineManager::ApplyAsyncImageForPipeline( + const wr::Epoch& aEpoch, const wr::PipelineId& aPipelineId, + AsyncImagePipeline* aPipeline, wr::TransactionBuilder& aSceneBuilderTxn, + wr::TransactionBuilder& aMaybeFastTxn) { + nsTArray keys; + auto op = UpdateImageKeys(aEpoch, aPipelineId, aPipeline, keys, + aSceneBuilderTxn, aMaybeFastTxn); + + bool updateDisplayList = + aPipeline->mInitialised && + (aPipeline->mIsChanged || op == Some(TextureHost::ADD_IMAGE)) && + !!aPipeline->mCurrentTexture; + + if (!updateDisplayList) { + // We don't need to update the display list, either because we can't or + // because the previous one is still up to date. We may, however, have + // updated some resources. + + // Use transaction of scene builder thread to notify epoch. + // It is for making epoch update consistent. + aSceneBuilderTxn.UpdateEpoch(aPipelineId, aEpoch); + if (aPipeline->mCurrentTexture) { + HoldExternalImage(aPipelineId, aEpoch, aPipeline->mCurrentTexture); + } + return; + } + + aPipeline->mIsChanged = false; + + wr::DisplayListBuilder builder(aPipelineId); + + float opacity = 1.0f; + wr::StackingContextParams params; + params.opacity = &opacity; + params.mix_blend_mode = aPipeline->mMixBlendMode; + + wr::WrComputedTransformData computedTransform; + computedTransform.vertical_flip = + aPipeline->mCurrentTexture && aPipeline->mCurrentTexture->NeedsYFlip(); + computedTransform.scale_from = { + float(aPipeline->mCurrentTexture->GetSize().width), + float(aPipeline->mCurrentTexture->GetSize().height)}; + computedTransform.rotation = ToWrRotation(aPipeline->mRotation); + params.computed_transform = &computedTransform; + + Maybe referenceFrameId = builder.PushStackingContext( + params, wr::ToLayoutRect(aPipeline->mScBounds), + // This is fine to do unconditionally because we only push images here. + wr::RasterSpace::Screen()); + + Maybe spaceAndClipChainHelper; + if (referenceFrameId) { + spaceAndClipChainHelper.emplace(builder, referenceFrameId.ref()); + } + + if (aPipeline->mCurrentTexture && !keys.IsEmpty()) { + LayoutDeviceRect rect(0, 0, aPipeline->mCurrentTexture->GetSize().width, + aPipeline->mCurrentTexture->GetSize().height); + + if (aPipeline->mUseExternalImage) { + MOZ_ASSERT(aPipeline->mCurrentTexture->AsWebRenderTextureHost()); + Range range_keys(&keys[0], keys.Length()); + TextureHost::PushDisplayItemFlagSet flags; + if (IsOpaque(aPipeline->mCurrentTexture->GetFormat()) || + bool(aPipeline->mCurrentTexture->GetFlags() & + TextureFlags::IS_OPAQUE)) { + flags += TextureHost::PushDisplayItemFlag::PREFER_COMPOSITOR_SURFACE; + } + if (mApi->SupportsExternalBufferTextures()) { + flags += + TextureHost::PushDisplayItemFlag::SUPPORTS_EXTERNAL_BUFFER_TEXTURES; + } + aPipeline->mCurrentTexture->PushDisplayItems( + builder, wr::ToLayoutRect(rect), wr::ToLayoutRect(rect), + aPipeline->mFilter, range_keys, flags); + HoldExternalImage(aPipelineId, aEpoch, aPipeline->mCurrentTexture); + } else { + MOZ_ASSERT(keys.Length() == 1); + builder.PushImage(wr::ToLayoutRect(rect), wr::ToLayoutRect(rect), true, + aPipeline->mFilter, keys[0]); + } + } + + spaceAndClipChainHelper.reset(); + builder.PopStackingContext(referenceFrameId.isSome()); + + wr::BuiltDisplayList dl; + builder.Finalize(dl); + aSceneBuilderTxn.SetDisplayList(gfx::DeviceColor(0.f, 0.f, 0.f, 0.f), aEpoch, + wr::ToLayoutSize(aPipeline->mScBounds.Size()), + aPipelineId, dl.dl_desc, dl.dl); +} + +void AsyncImagePipelineManager::ApplyAsyncImageForPipeline( + const wr::PipelineId& aPipelineId, wr::TransactionBuilder& aTxn, + wr::TransactionBuilder& aTxnForImageBridge) { + AsyncImagePipeline* pipeline = + mAsyncImagePipelines.Get(wr::AsUint64(aPipelineId)); + if (!pipeline) { + return; + } + wr::TransactionBuilder fastTxn(/* aUseSceneBuilderThread */ false); + wr::AutoTransactionSender sender(mApi, &fastTxn); + + // Transaction for async image pipeline that uses ImageBridge always need to + // be non low priority. + auto& sceneBuilderTxn = + pipeline->mImageHost->GetAsyncRef() ? aTxnForImageBridge : aTxn; + + // Use transaction of using non scene builder thread when ImageHost uses + // ImageBridge. ApplyAsyncImagesOfImageBridge() handles transaction of adding + // and updating wr::ImageKeys of ImageHosts that uses ImageBridge. Then + // AsyncImagePipelineManager always needs to use non scene builder thread + // transaction for adding and updating wr::ImageKeys of ImageHosts that uses + // ImageBridge. Otherwise, ordering of wr::ImageKeys updating in webrender + // becomes inconsistent. + auto& maybeFastTxn = pipeline->mImageHost->GetAsyncRef() ? fastTxn : aTxn; + + wr::Epoch epoch = GetNextImageEpoch(); + + ApplyAsyncImageForPipeline(epoch, aPipelineId, pipeline, sceneBuilderTxn, + maybeFastTxn); +} + +void AsyncImagePipelineManager::SetEmptyDisplayList( + const wr::PipelineId& aPipelineId, wr::TransactionBuilder& aTxn, + wr::TransactionBuilder& aTxnForImageBridge) { + AsyncImagePipeline* pipeline = + mAsyncImagePipelines.Get(wr::AsUint64(aPipelineId)); + if (!pipeline) { + return; + } + + // Transaction for async image pipeline that uses ImageBridge always need to + // be non low priority. + auto& txn = pipeline->mImageHost->GetAsyncRef() ? aTxnForImageBridge : aTxn; + + wr::Epoch epoch = GetNextImageEpoch(); + wr::DisplayListBuilder builder(aPipelineId); + + wr::BuiltDisplayList dl; + builder.Finalize(dl); + txn.SetDisplayList(gfx::DeviceColor(0.f, 0.f, 0.f, 0.f), epoch, + wr::ToLayoutSize(pipeline->mScBounds.Size()), aPipelineId, + dl.dl_desc, dl.dl); +} + +void AsyncImagePipelineManager::HoldExternalImage( + const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch, + TextureHost* aTexture) { + if (mDestroyed) { + return; + } + MOZ_ASSERT(aTexture); + + PipelineTexturesHolder* holder = + mPipelineTexturesHolders.Get(wr::AsUint64(aPipelineId)); + MOZ_ASSERT(holder); + if (!holder) { + return; + } + if (aTexture->NeedsDeferredDeletion()) { + // Hold WebRenderTextureHost until rendering completed. + holder->mTextureHostsUntilRenderCompleted.emplace_back( + MakeUnique(aEpoch, aTexture)); + } else { + // Hold WebRenderTextureHost until submitted for rendering. + holder->mTextureHostsUntilRenderSubmitted.emplace_back(aEpoch, aTexture); + } +} + +void AsyncImagePipelineManager::HoldExternalImage( + const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch, + const wr::ExternalImageId& aImageId) { + if (mDestroyed) { + SharedSurfacesParent::Release(aImageId); + return; + } + + PipelineTexturesHolder* holder = + mPipelineTexturesHolders.Get(wr::AsUint64(aPipelineId)); + MOZ_ASSERT(holder); + if (!holder) { + SharedSurfacesParent::Release(aImageId); + return; + } + + holder->mExternalImages.emplace_back( + MakeUnique(aEpoch, aImageId)); +} + +void AsyncImagePipelineManager::NotifyPipelinesUpdated( + RefPtr aInfo, + wr::RenderedFrameId aLatestFrameId, + wr::RenderedFrameId aLastCompletedFrameId, ipc::FileDescriptor&& aFenceFd) { + MOZ_ASSERT(wr::RenderThread::IsInRenderThread()); + MOZ_ASSERT(mLastCompletedFrameId <= aLastCompletedFrameId.mId); + MOZ_ASSERT(aLatestFrameId.IsValid()); + + mLastCompletedFrameId = aLastCompletedFrameId.mId; + + { + // We need to lock for mRenderSubmittedUpdates because it can be accessed + // on the compositor thread. + MutexAutoLock lock(mRenderSubmittedUpdatesLock); + + // Move the pending updates into the submitted ones. + mRenderSubmittedUpdates.emplace_back( + aLatestFrameId, + WebRenderPipelineInfoHolder(std::move(aInfo), std::move(aFenceFd))); + } + + // Queue a runnable on the compositor thread to process the updates. + // This will also call CheckForTextureHostsNotUsedByGPU. + layers::CompositorThread()->Dispatch( + NewRunnableMethod("ProcessPipelineUpdates", this, + &AsyncImagePipelineManager::ProcessPipelineUpdates)); +} + +void AsyncImagePipelineManager::ProcessPipelineUpdates() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + + if (mDestroyed) { + return; + } + + std::vector> + submittedUpdates; + { + // We need to lock for mRenderSubmittedUpdates because it can be accessed on + // the compositor thread. + MutexAutoLock lock(mRenderSubmittedUpdatesLock); + mRenderSubmittedUpdates.swap(submittedUpdates); + } + + // submittedUpdates is a vector of RenderedFrameIds paired with vectors of + // WebRenderPipelineInfo. + for (auto update : submittedUpdates) { + auto& holder = update.second; + const auto& info = holder.mInfo->Raw(); + + mReleaseFenceFd = std::move(holder.mFenceFd); + + for (auto& epoch : info.epochs) { + ProcessPipelineRendered(epoch.pipeline_id, epoch.epoch, update.first); + } + for (auto& removedPipeline : info.removed_pipelines) { + ProcessPipelineRemoved(removedPipeline, update.first); + } + } + CheckForTextureHostsNotUsedByGPU(); +} + +void AsyncImagePipelineManager::ProcessPipelineRendered( + const wr::PipelineId& aPipelineId, const wr::Epoch& aEpoch, + wr::RenderedFrameId aRenderedFrameId) { + if (auto entry = mPipelineTexturesHolders.Lookup(wr::AsUint64(aPipelineId))) { + const auto& holder = entry.Data(); + // For TextureHosts that can be released on render submission, using aEpoch + // find the first that we can't release and then release all prior to that. + auto firstSubmittedHostToKeep = std::find_if( + holder->mTextureHostsUntilRenderSubmitted.begin(), + holder->mTextureHostsUntilRenderSubmitted.end(), + [&aEpoch](const auto& entry) { return aEpoch <= entry.mEpoch; }); +#ifdef MOZ_WIDGET_ANDROID + // Set release fence if TextureHost owns AndroidHardwareBuffer. + // The TextureHost handled by mTextureHostsUntilRenderSubmitted instead of + // mTextureHostsUntilRenderCompleted, since android fence could be used + // to wait until its end of usage by GPU. + for (auto it = holder->mTextureHostsUntilRenderSubmitted.begin(); + it != firstSubmittedHostToKeep; ++it) { + const auto& entry = it; + if (entry->mTexture->GetAndroidHardwareBuffer()) { + ipc::FileDescriptor fenceFd = mReleaseFenceFd; + entry->mTexture->SetReleaseFence(std::move(fenceFd)); + } + } +#endif + holder->mTextureHostsUntilRenderSubmitted.erase( + holder->mTextureHostsUntilRenderSubmitted.begin(), + firstSubmittedHostToKeep); + + // For TextureHosts that need to wait until render completed, find the first + // that is later than aEpoch and then move all prior to that to + // mTexturesInUseByGPU paired with aRenderedFrameId. These will be released + // once rendering has completed for aRenderedFrameId. + auto firstCompletedHostToKeep = std::find_if( + holder->mTextureHostsUntilRenderCompleted.begin(), + holder->mTextureHostsUntilRenderCompleted.end(), + [&aEpoch](const auto& entry) { return aEpoch <= entry->mEpoch; }); + if (firstCompletedHostToKeep != + holder->mTextureHostsUntilRenderCompleted.begin()) { + std::vector> hostsUntilCompleted( + std::make_move_iterator( + holder->mTextureHostsUntilRenderCompleted.begin()), + std::make_move_iterator(firstCompletedHostToKeep)); + mTexturesInUseByGPU.emplace_back(aRenderedFrameId, + std::move(hostsUntilCompleted)); + holder->mTextureHostsUntilRenderCompleted.erase( + holder->mTextureHostsUntilRenderCompleted.begin(), + firstCompletedHostToKeep); + } + + // Using aEpoch, find the first external image that we can't release and + // then release all prior to that. + auto firstImageToKeep = std::find_if( + holder->mExternalImages.begin(), holder->mExternalImages.end(), + [&aEpoch](const auto& entry) { return aEpoch <= entry->mEpoch; }); + holder->mExternalImages.erase(holder->mExternalImages.begin(), + firstImageToKeep); + } +} + +void AsyncImagePipelineManager::ProcessPipelineRemoved( + const wr::RemovedPipeline& aRemovedPipeline, + wr::RenderedFrameId aRenderedFrameId) { + if (mDestroyed) { + return; + } + if (auto entry = mPipelineTexturesHolders.Lookup( + wr::AsUint64(aRemovedPipeline.pipeline_id))) { + const auto& holder = entry.Data(); + if (holder->mDestroyedEpoch.isSome()) { + if (!holder->mTextureHostsUntilRenderCompleted.empty()) { + // Move all TextureHosts that must be held until render completed to + // mTexturesInUseByGPU paired with aRenderedFrameId. + mTexturesInUseByGPU.emplace_back( + aRenderedFrameId, + std::move(holder->mTextureHostsUntilRenderCompleted)); + } + + // Remove Pipeline releasing all remaining TextureHosts and external + // images. + entry.Remove(); + } + + // If mDestroyedEpoch contains nothing it means we reused the same pipeline + // id (probably because we moved the tab to another window). In this case we + // need to keep the holder. + } +} + +void AsyncImagePipelineManager::CheckForTextureHostsNotUsedByGPU() { + uint64_t lastCompletedFrameId = mLastCompletedFrameId; + + // Find first entry after mLastCompletedFrameId and release all prior ones. + auto firstTexturesToKeep = + std::find_if(mTexturesInUseByGPU.begin(), mTexturesInUseByGPU.end(), + [lastCompletedFrameId](const auto& entry) { + return lastCompletedFrameId < entry.first.mId; + }); + mTexturesInUseByGPU.erase(mTexturesInUseByGPU.begin(), firstTexturesToKeep); +} + +wr::Epoch AsyncImagePipelineManager::GetNextImageEpoch() { + mAsyncImageEpoch.mHandle++; + return mAsyncImageEpoch; +} + +AsyncImagePipelineManager::WebRenderPipelineInfoHolder:: + WebRenderPipelineInfoHolder(RefPtr&& aInfo, + ipc::FileDescriptor&& aFenceFd) + : mInfo(aInfo), mFenceFd(aFenceFd) {} + +AsyncImagePipelineManager::WebRenderPipelineInfoHolder:: + ~WebRenderPipelineInfoHolder() = default; + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/wr/AsyncImagePipelineManager.h b/gfx/layers/wr/AsyncImagePipelineManager.h new file mode 100644 index 0000000000..0d41949482 --- /dev/null +++ b/gfx/layers/wr/AsyncImagePipelineManager.h @@ -0,0 +1,276 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_WEBRENDERCOMPOSITABLE_HOLDER_H +#define MOZILLA_GFX_WEBRENDERCOMPOSITABLE_HOLDER_H + +#include + +#include "CompositableHost.h" +#include "mozilla/gfx/Point.h" +#include "mozilla/ipc/FileDescriptor.h" +#include "mozilla/layers/TextureHost.h" +#include "mozilla/Maybe.h" +#include "mozilla/webrender/WebRenderAPI.h" +#include "mozilla/webrender/WebRenderTypes.h" +#include "nsClassHashtable.h" + +namespace mozilla { + +namespace wr { +class DisplayListBuilder; +class WebRenderAPI; +class WebRenderPipelineInfo; +} // namespace wr + +namespace layers { + +class CompositableHost; +class CompositorVsyncScheduler; +class WebRenderImageHost; +class WebRenderTextureHost; + +class AsyncImagePipelineManager final { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AsyncImagePipelineManager) + + explicit AsyncImagePipelineManager(RefPtr&& aApi, + bool aUseCompositorWnd); + + protected: + ~AsyncImagePipelineManager(); + + public: + void Destroy(); + + bool UseCompositorWnd() const { return mUseCompositorWnd; } + + void AddPipeline(const wr::PipelineId& aPipelineId, + WebRenderBridgeParent* aWrBridge); + void RemovePipeline(const wr::PipelineId& aPipelineId, + const wr::Epoch& aEpoch); + WebRenderBridgeParent* GetWrBridge(const wr::PipelineId& aPipelineId); + + void HoldExternalImage(const wr::PipelineId& aPipelineId, + const wr::Epoch& aEpoch, TextureHost* aTexture); + void HoldExternalImage(const wr::PipelineId& aPipelineId, + const wr::Epoch& aEpoch, + const wr::ExternalImageId& aImageId); + + // This is called from the Renderer thread to notify this class about the + // pipelines in the most recently completed update. + // @param aInfo PipelineInfo for the update + // @param aLatestFrameId RenderedFrameId if a frame has been submitted for + // rendering, invalid if not + // @param aLastCompletedFrameId RenderedFrameId for the last completed frame + void NotifyPipelinesUpdated(RefPtr aInfo, + wr::RenderedFrameId aLatestFrameId, + wr::RenderedFrameId aLastCompletedFrameId, + ipc::FileDescriptor&& aFenceFd); + + // This is run on the compositor thread to process mRenderSubmittedUpdates. We + // make this public because we need to invoke it from other places. + void ProcessPipelineUpdates(); + + TimeStamp GetCompositionTime() const { return mCompositionTime; } + CompositionOpportunityId GetCompositionOpportunityId() const { + return mCompositionOpportunityId; + } + + void SetCompositionInfo(TimeStamp aTimeStamp, + CompositionOpportunityId aCompositionOpportunityId) { + mCompositionTime = aTimeStamp; + mCompositionOpportunityId = aCompositionOpportunityId; + if (!mCompositionTime.IsNull() && !mCompositeUntilTime.IsNull() && + mCompositionTime >= mCompositeUntilTime) { + mCompositeUntilTime = TimeStamp(); + } + } + void CompositeUntil(TimeStamp aTimeStamp) { + if (mCompositeUntilTime.IsNull() || mCompositeUntilTime < aTimeStamp) { + mCompositeUntilTime = aTimeStamp; + } + } + TimeStamp GetCompositeUntilTime() const { return mCompositeUntilTime; } + + void AddAsyncImagePipeline(const wr::PipelineId& aPipelineId, + WebRenderImageHost* aImageHost); + void RemoveAsyncImagePipeline(const wr::PipelineId& aPipelineId, + wr::TransactionBuilder& aTxn); + + void UpdateAsyncImagePipeline(const wr::PipelineId& aPipelineId, + const LayoutDeviceRect& aScBounds, + VideoInfo::Rotation aRotation, + const wr::ImageRendering& aFilter, + const wr::MixBlendMode& aMixBlendMode); + void ApplyAsyncImagesOfImageBridge(wr::TransactionBuilder& aSceneBuilderTxn, + wr::TransactionBuilder& aFastTxn); + void ApplyAsyncImageForPipeline(const wr::PipelineId& aPipelineId, + wr::TransactionBuilder& aTxn, + wr::TransactionBuilder& aTxnForImageBridge); + + void SetEmptyDisplayList(const wr::PipelineId& aPipelineId, + wr::TransactionBuilder& aTxn, + wr::TransactionBuilder& aTxnForImageBridge); + + void AppendImageCompositeNotification( + const ImageCompositeNotificationInfo& aNotification) { + mImageCompositeNotifications.AppendElement(aNotification); + } + + void FlushImageNotifications( + nsTArray* aNotifications) { + aNotifications->AppendElements(std::move(mImageCompositeNotifications)); + } + + void SetWillGenerateFrame(); + bool GetAndResetWillGenerateFrame(); + + static wr::ExternalImageId GetNextExternalImageId(); + + private: + void ProcessPipelineRendered(const wr::PipelineId& aPipelineId, + const wr::Epoch& aEpoch, + wr::RenderedFrameId aRenderedFrameId); + void ProcessPipelineRemoved(const wr::RemovedPipeline& aRemovedPipeline, + wr::RenderedFrameId aRenderedFrameId); + + wr::Epoch GetNextImageEpoch(); + uint32_t GetNextResourceId() { return ++mResourceId; } + wr::IdNamespace GetNamespace() { return mIdNamespace; } + wr::ImageKey GenerateImageKey() { + wr::ImageKey key; + key.mNamespace = GetNamespace(); + key.mHandle = GetNextResourceId(); + return key; + } + + struct ForwardingTextureHost { + ForwardingTextureHost(const wr::Epoch& aEpoch, TextureHost* aTexture) + : mEpoch(aEpoch), mTexture(aTexture) {} + wr::Epoch mEpoch; + CompositableTextureHostRef mTexture; + }; + + struct ForwardingExternalImage { + ForwardingExternalImage(const wr::Epoch& aEpoch, + const wr::ExternalImageId& aImageId) + : mEpoch(aEpoch), mImageId(aImageId) {} + ~ForwardingExternalImage(); + wr::Epoch mEpoch; + wr::ExternalImageId mImageId; + }; + + struct PipelineTexturesHolder { + // Holds forwarding WebRenderTextureHosts. + std::vector mTextureHostsUntilRenderSubmitted; + // TextureHosts that must be held until rendering has completed. UniquePtr + // is used to make the entries movable, ideally ForwardingTextureHost would + // be fully movable. + std::vector> + mTextureHostsUntilRenderCompleted; + std::vector> mExternalImages; + Maybe mDestroyedEpoch; + WebRenderBridgeParent* MOZ_NON_OWNING_REF mWrBridge = nullptr; + }; + + struct AsyncImagePipeline { + AsyncImagePipeline(); + void Update(const LayoutDeviceRect& aScBounds, + VideoInfo::Rotation aRotation, + const wr::ImageRendering& aFilter, + const wr::MixBlendMode& aMixBlendMode) { + mIsChanged |= !mScBounds.IsEqualEdges(aScBounds) || + mRotation != aRotation || mFilter != aFilter || + mMixBlendMode != aMixBlendMode; + mScBounds = aScBounds; + mRotation = aRotation; + mFilter = aFilter; + mMixBlendMode = aMixBlendMode; + } + + bool mInitialised; + bool mIsChanged; + bool mUseExternalImage; + LayoutDeviceRect mScBounds; + VideoInfo::Rotation mRotation; + wr::ImageRendering mFilter; + wr::MixBlendMode mMixBlendMode; + RefPtr mImageHost; + CompositableTextureHostRef mCurrentTexture; + nsTArray mKeys; + }; + + void ApplyAsyncImageForPipeline(const wr::Epoch& aEpoch, + const wr::PipelineId& aPipelineId, + AsyncImagePipeline* aPipeline, + wr::TransactionBuilder& aSceneBuilderTxn, + wr::TransactionBuilder& aMaybeFastTxn); + Maybe UpdateImageKeys( + const wr::Epoch& aEpoch, const wr::PipelineId& aPipelineId, + AsyncImagePipeline* aPipeline, nsTArray& aKeys, + wr::TransactionBuilder& aSceneBuilderTxn, + wr::TransactionBuilder& aMaybeFastTxn); + Maybe UpdateWithoutExternalImage( + TextureHost* aTexture, wr::ImageKey aKey, TextureHost::ResourceUpdateOp, + wr::TransactionBuilder& aTxn); + + void CheckForTextureHostsNotUsedByGPU(); + + RefPtr mApi; + bool mUseCompositorWnd; + + const wr::IdNamespace mIdNamespace; + const bool mUseTripleBuffering; + uint32_t mResourceId; + + nsClassHashtable + mPipelineTexturesHolders; + nsClassHashtable mAsyncImagePipelines; + wr::Epoch mAsyncImageEpoch; + bool mWillGenerateFrame; + bool mDestroyed; + +#ifdef XP_WIN + bool mUseWebRenderDCompVideoOverlayWin; +#endif + + // Render time for the current composition. + TimeStamp mCompositionTime; + + // CompositionOpportunityId of the current composition. + CompositionOpportunityId mCompositionOpportunityId; + + // When nonnull, during rendering, some compositable indicated that it will + // change its rendering at this time. In order not to miss it, we composite + // on every vsync until this time occurs (this is the latest such time). + TimeStamp mCompositeUntilTime; + + nsTArray mImageCompositeNotifications; + + struct WebRenderPipelineInfoHolder { + WebRenderPipelineInfoHolder(RefPtr&& aInfo, + ipc::FileDescriptor&& aFenceFd); + ~WebRenderPipelineInfoHolder(); + RefPtr mInfo; + ipc::FileDescriptor mFenceFd; + }; + + std::vector> + mRenderSubmittedUpdates; + Mutex mRenderSubmittedUpdatesLock; + + Atomic mLastCompletedFrameId; + std::vector>>> + mTexturesInUseByGPU; + ipc::FileDescriptor mReleaseFenceFd; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* MOZILLA_GFX_WEBRENDERCOMPOSITABLE_HOLDER_H */ diff --git a/gfx/layers/wr/ClipManager.cpp b/gfx/layers/wr/ClipManager.cpp new file mode 100644 index 0000000000..c1347d4210 --- /dev/null +++ b/gfx/layers/wr/ClipManager.cpp @@ -0,0 +1,424 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/ClipManager.h" + +#include "DisplayItemClipChain.h" +#include "FrameMetrics.h" +#include "mozilla/layers/StackingContextHelper.h" +#include "mozilla/layers/WebRenderLayerManager.h" +#include "mozilla/webrender/WebRenderAPI.h" +#include "nsDisplayList.h" +#include "nsStyleStructInlines.h" +#include "UnitTransforms.h" + +// clang-format off +#define CLIP_LOG(...) +//#define CLIP_LOG(...) printf_stderr("CLIP: " __VA_ARGS__) +//#define CLIP_LOG(...) if (XRE_IsContentProcess()) printf_stderr("CLIP: " __VA_ARGS__) +// clang-format on + +namespace mozilla { +namespace layers { + +ClipManager::ClipManager() : mManager(nullptr), mBuilder(nullptr) {} + +void ClipManager::BeginBuild(WebRenderLayerManager* aManager, + wr::DisplayListBuilder& aBuilder) { + MOZ_ASSERT(!mManager); + mManager = aManager; + MOZ_ASSERT(!mBuilder); + mBuilder = &aBuilder; + MOZ_ASSERT(mCacheStack.empty()); + mCacheStack.emplace(); + MOZ_ASSERT(mASROverride.empty()); + MOZ_ASSERT(mItemClipStack.empty()); +} + +void ClipManager::EndBuild() { + mBuilder = nullptr; + mManager = nullptr; + mCacheStack.pop(); + MOZ_ASSERT(mCacheStack.empty()); + MOZ_ASSERT(mASROverride.empty()); + MOZ_ASSERT(mItemClipStack.empty()); +} + +void ClipManager::BeginList(const StackingContextHelper& aStackingContext) { + if (aStackingContext.AffectsClipPositioning()) { + if (aStackingContext.ReferenceFrameId()) { + PushOverrideForASR( + mItemClipStack.empty() ? nullptr : mItemClipStack.top().mASR, + aStackingContext.ReferenceFrameId().ref()); + } else { + // Start a new cache + mCacheStack.emplace(); + } + } + + ItemClips clips(nullptr, nullptr, false); + if (!mItemClipStack.empty()) { + clips.CopyOutputsFrom(mItemClipStack.top()); + } + + if (aStackingContext.ReferenceFrameId()) { + clips.mScrollId = aStackingContext.ReferenceFrameId().ref(); + } + + mItemClipStack.push(clips); +} + +void ClipManager::EndList(const StackingContextHelper& aStackingContext) { + MOZ_ASSERT(!mItemClipStack.empty()); + mBuilder->SetClipChainLeaf(Nothing()); + mItemClipStack.pop(); + + if (aStackingContext.AffectsClipPositioning()) { + if (aStackingContext.ReferenceFrameId()) { + PopOverrideForASR(mItemClipStack.empty() ? nullptr + : mItemClipStack.top().mASR); + } else { + MOZ_ASSERT(!mCacheStack.empty()); + mCacheStack.pop(); + } + } +} + +void ClipManager::PushOverrideForASR(const ActiveScrolledRoot* aASR, + const wr::WrSpatialId& aSpatialId) { + Maybe spaceAndClip = GetScrollLayer(aASR); + MOZ_ASSERT(spaceAndClip.isSome()); + + CLIP_LOG("Pushing %p override %zu -> %s\n", aASR, spaceAndClip->space.id, + Stringify(aSpatialId.id).c_str()); + + auto it = + mASROverride.insert({spaceAndClip->space, std::stack()}); + it.first->second.push(aSpatialId); + + // Start a new cache + mCacheStack.emplace(); +} + +void ClipManager::PopOverrideForASR(const ActiveScrolledRoot* aASR) { + MOZ_ASSERT(!mCacheStack.empty()); + mCacheStack.pop(); + + Maybe spaceAndClip = GetScrollLayer(aASR); + MOZ_ASSERT(spaceAndClip.isSome()); + + auto it = mASROverride.find(spaceAndClip->space); + CLIP_LOG("Popping %p override %zu -> %s\n", aASR, spaceAndClip->space.id, + Stringify(it->second.top().id).c_str()); + + it->second.pop(); + if (it->second.empty()) { + mASROverride.erase(it); + } +} + +wr::WrSpatialId ClipManager::SpatialIdAfterOverride( + const wr::WrSpatialId& aSpatialId) { + auto it = mASROverride.find(aSpatialId); + if (it == mASROverride.end()) { + return aSpatialId; + } + MOZ_ASSERT(!it->second.empty()); + CLIP_LOG("Overriding %zu with %s\n", aSpatialId.id, + Stringify(it->second.top().id).c_str()); + + return it->second.top(); +} + +wr::WrSpaceAndClipChain ClipManager::SwitchItem(nsDisplayItem* aItem) { + const DisplayItemClipChain* clip = aItem->GetClipChain(); + const ActiveScrolledRoot* asr = aItem->GetActiveScrolledRoot(); + CLIP_LOG("processing item %p (%s) asr %p\n", aItem, + DisplayItemTypeName(aItem->GetType()), asr); + + DisplayItemType type = aItem->GetType(); + if (type == DisplayItemType::TYPE_STICKY_POSITION) { + // For sticky position items, the ASR is computed differently depending + // on whether the item has a fixed descendant or not. But for WebRender + // purposes we always want to use the ASR that would have been used if it + // didn't have fixed descendants, which is stored as the "container ASR" on + // the sticky item. + nsDisplayStickyPosition* sticky = + static_cast(aItem); + asr = sticky->GetContainerASR(); + + // If the leafmost clip for the sticky item is just the displayport clip, + // then skip it. This allows sticky items to remain visible even if the + // rest of the content in the enclosing scrollframe is checkerboarding. + if (sticky->IsClippedToDisplayPort() && clip && clip->mASR == asr) { + clip = clip->mParent; + } + } + + // In most cases we can combine the leaf of the clip chain with the clip rect + // of the display item. This reduces the number of clip items, which avoids + // some overhead further down the pipeline. + bool separateLeaf = false; + if (clip && clip->mASR == asr && clip->mClip.GetRoundedRectCount() == 0) { + // Container display items are not currently supported because the clip + // rect of a stacking context is not handled the same as normal display + // items. + separateLeaf = aItem->GetChildren() == nullptr; + } + + ItemClips clips(asr, clip, separateLeaf); + MOZ_ASSERT(!mItemClipStack.empty()); + if (clips.HasSameInputs(mItemClipStack.top())) { + // Early-exit because if the clips are the same as aItem's previous sibling, + // then we don't need to do do the work of popping the old stuff and then + // pushing it right back on for the new item. Note that if aItem doesn't + // have a previous sibling, that means BeginList would have been called + // just before this, which will have pushed a ItemClips(nullptr, nullptr) + // onto mItemClipStack, so the HasSameInputs check should return false. + CLIP_LOG("\tearly-exit for %p\n", aItem); + return mItemClipStack.top().GetSpaceAndClipChain(); + } + + // Pop aItem's previous sibling's stuff from mBuilder in preparation for + // pushing aItem's stuff. + mItemClipStack.pop(); + + // Zoom display items report their bounds etc using the parent document's + // APD because zoom items act as a conversion layer between the two different + // APDs. + int32_t auPerDevPixel; + if (type == DisplayItemType::TYPE_ZOOM) { + auPerDevPixel = + static_cast(aItem)->GetParentAppUnitsPerDevPixel(); + } else { + auPerDevPixel = aItem->Frame()->PresContext()->AppUnitsPerDevPixel(); + } + + // If the leaf of the clip chain is going to be merged with the display item's + // clip rect, then we should create a clip chain id from the leaf's parent. + if (separateLeaf) { + CLIP_LOG("\tseparate leaf detected, ignoring the last clip\n"); + clip = clip->mParent; + } + + // There are two ASR chains here that we need to be fully defined. One is the + // ASR chain pointed to by |asr|. The other is the + // ASR chain pointed to by clip->mASR. We pick the leafmost + // of these two chains because that one will include the other. Calling + // DefineScrollLayers with this leafmost ASR will recursively define all the + // ASRs that we care about for this item, but will not actually push + // anything onto the WR stack. + const ActiveScrolledRoot* leafmostASR = asr; + if (clip) { + leafmostASR = ActiveScrolledRoot::PickDescendant(leafmostASR, clip->mASR); + } + Maybe leafmostId = DefineScrollLayers(leafmostASR, aItem); + Unused << leafmostId; + + // Define all the clips in the item's clip chain, and obtain a clip chain id + // for it. + clips.mClipChainId = DefineClipChain(clip, auPerDevPixel); + + Maybe spaceAndClip = GetScrollLayer(asr); + MOZ_ASSERT(spaceAndClip.isSome()); + clips.mScrollId = SpatialIdAfterOverride(spaceAndClip->space); + CLIP_LOG("\tassigning %d -> %d\n", (int)spaceAndClip->space.id, + (int)clips.mScrollId.id); + + // Now that we have the scroll id and a clip id for the item, push it onto + // the WR stack. + clips.UpdateSeparateLeaf(*mBuilder, auPerDevPixel); + auto spaceAndClipChain = clips.GetSpaceAndClipChain(); + mItemClipStack.push(clips); + + CLIP_LOG("done setup for %p\n", aItem); + return spaceAndClipChain; +} + +Maybe ClipManager::GetScrollLayer( + const ActiveScrolledRoot* aASR) { + for (const ActiveScrolledRoot* asr = aASR; asr; asr = asr->mParent) { + Maybe spaceAndClip = + mBuilder->GetScrollIdForDefinedScrollLayer(asr->GetViewId()); + if (spaceAndClip) { + return spaceAndClip; + } + + // If this ASR doesn't have a scroll ID, then we should check its ancestor. + // There may not be one defined because the ASR may not be scrollable or we + // failed to get the scroll metadata. + } + + Maybe spaceAndClip = + mBuilder->GetScrollIdForDefinedScrollLayer( + ScrollableLayerGuid::NULL_SCROLL_ID); + MOZ_ASSERT(spaceAndClip.isSome()); + return spaceAndClip; +} + +Maybe ClipManager::DefineScrollLayers( + const ActiveScrolledRoot* aASR, nsDisplayItem* aItem) { + if (!aASR) { + // Recursion base case + return Nothing(); + } + ScrollableLayerGuid::ViewID viewId = aASR->GetViewId(); + Maybe spaceAndClip = + mBuilder->GetScrollIdForDefinedScrollLayer(viewId); + if (spaceAndClip) { + // If we've already defined this scroll layer before, we can early-exit + return spaceAndClip; + } + // Recurse to define the ancestors + Maybe ancestorSpaceAndClip = + DefineScrollLayers(aASR->mParent, aItem); + + Maybe metadata = + aASR->mScrollableFrame->ComputeScrollMetadata( + mManager, aItem->ReferenceFrame(), Nothing(), nullptr); + if (!metadata) { + MOZ_ASSERT_UNREACHABLE("Expected scroll metadata to be available!"); + return ancestorSpaceAndClip; + } + + FrameMetrics& metrics = metadata->GetMetrics(); + if (!metrics.IsScrollable()) { + // This item is a scrolling no-op, skip over it in the ASR chain. + return ancestorSpaceAndClip; + } + + nsIScrollableFrame* scrollableFrame = aASR->mScrollableFrame; + nsIFrame* scrollFrame = do_QueryFrame(scrollableFrame); + nsPoint offset = scrollFrame->GetOffsetToCrossDoc(aItem->ReferenceFrame()); + float auPerDevPixel = aItem->Frame()->PresContext()->AppUnitsPerDevPixel(); + nsRect scrollPort = scrollableFrame->GetScrollPortRect() + offset; + LayoutDeviceRect clipBounds = + LayoutDeviceRect::FromAppUnits(scrollPort, auPerDevPixel); + + // The content rect that we hand to PushScrollLayer should be relative to + // the same origin as the clipBounds that we hand to PushScrollLayer - + // that is, both of them should be relative to the stacking context `aSc`. + // However, when we get the scrollable rect from the FrameMetrics, the + // origin has nothing to do with the position of the frame but instead + // represents the minimum allowed scroll offset of the scrollable content. + // While APZ uses this to clamp the scroll position, we don't need to send + // this to WebRender at all. Instead, we take the position from the + // composition bounds. + LayoutDeviceRect contentRect = + metrics.GetExpandedScrollableRect() * metrics.GetDevPixelsPerCSSPixel(); + contentRect.MoveTo(clipBounds.TopLeft()); + + Maybe parent = ancestorSpaceAndClip; + if (parent) { + parent->space = SpatialIdAfterOverride(parent->space); + } + // The external scroll offset is accumulated into the local space positions of + // display items inside WR, so that the elements hash (intern) to the same + // content ID for quick comparisons. To avoid invalidations when the + // auPerDevPixel is not a round value, round here directly from app units. + // This guarantees we won't introduce any inaccuracy in the external scroll + // offset passed to WR. + LayoutDevicePoint scrollOffset = LayoutDevicePoint::FromAppUnitsRounded( + scrollableFrame->GetScrollPosition(), auPerDevPixel); + + return Some(mBuilder->DefineScrollLayer( + viewId, parent, wr::ToLayoutRect(contentRect), + wr::ToLayoutRect(clipBounds), wr::ToLayoutPoint(scrollOffset))); +} + +Maybe ClipManager::DefineClipChain( + const DisplayItemClipChain* aChain, int32_t aAppUnitsPerDevPixel) { + AutoTArray clipIds; + // Iterate through the clips in the current item's clip chain, define them + // in WR, and put their IDs into |clipIds|. + for (const DisplayItemClipChain* chain = aChain; chain; + chain = chain->mParent) { + ClipIdMap& cache = mCacheStack.top(); + auto it = cache.find(chain); + if (it != cache.end()) { + // Found it in the currently-active cache, so just use the id we have for + // it. + CLIP_LOG("cache[%p] => %zu\n", chain, it->second.id); + clipIds.AppendElement(it->second); + continue; + } + if (!chain->mClip.HasClip()) { + // This item in the chain is a no-op, skip over it + continue; + } + + LayoutDeviceRect clip = LayoutDeviceRect::FromAppUnits( + chain->mClip.GetClipRect(), aAppUnitsPerDevPixel); + nsTArray wrRoundedRects; + chain->mClip.ToComplexClipRegions(aAppUnitsPerDevPixel, wrRoundedRects); + + Maybe spaceAndClip = GetScrollLayer(chain->mASR); + // Before calling DefineClipChain we defined the ASRs by calling + // DefineScrollLayers, so we must have a scrollId here. + MOZ_ASSERT(spaceAndClip.isSome()); + + // Define the clip + spaceAndClip->space = SpatialIdAfterOverride(spaceAndClip->space); + wr::WrClipId clipId = mBuilder->DefineClip( + spaceAndClip, wr::ToLayoutRect(clip), &wrRoundedRects); + clipIds.AppendElement(clipId); + cache[chain] = clipId; + CLIP_LOG("cache[%p] <= %zu\n", chain, clipId.id); + } + + if (clipIds.IsEmpty()) { + return Nothing(); + } + + return Some(mBuilder->DefineClipChain(clipIds)); +} + +ClipManager::~ClipManager() { + MOZ_ASSERT(!mBuilder); + MOZ_ASSERT(mCacheStack.empty()); + MOZ_ASSERT(mItemClipStack.empty()); +} + +ClipManager::ItemClips::ItemClips(const ActiveScrolledRoot* aASR, + const DisplayItemClipChain* aChain, + bool aSeparateLeaf) + : mASR(aASR), mChain(aChain), mSeparateLeaf(aSeparateLeaf) { + mScrollId = wr::wr_root_scroll_node_id(); +} + +void ClipManager::ItemClips::UpdateSeparateLeaf( + wr::DisplayListBuilder& aBuilder, int32_t aAppUnitsPerDevPixel) { + Maybe clipLeaf; + if (mSeparateLeaf) { + MOZ_ASSERT(mChain); + clipLeaf.emplace(wr::ToLayoutRect(LayoutDeviceRect::FromAppUnits( + mChain->mClip.GetClipRect(), aAppUnitsPerDevPixel))); + } + + aBuilder.SetClipChainLeaf(clipLeaf); +} + +bool ClipManager::ItemClips::HasSameInputs(const ItemClips& aOther) { + return mASR == aOther.mASR && mChain == aOther.mChain && + mSeparateLeaf == aOther.mSeparateLeaf; +} + +void ClipManager::ItemClips::CopyOutputsFrom(const ItemClips& aOther) { + mScrollId = aOther.mScrollId; + mClipChainId = aOther.mClipChainId; +} + +wr::WrSpaceAndClipChain ClipManager::ItemClips::GetSpaceAndClipChain() const { + auto spaceAndClipChain = wr::RootScrollNodeWithChain(); + spaceAndClipChain.space = mScrollId; + if (mClipChainId) { + spaceAndClipChain.clip_chain = mClipChainId->id; + } + return spaceAndClipChain; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/wr/ClipManager.h b/gfx/layers/wr/ClipManager.h new file mode 100644 index 0000000000..c1bec5aa6c --- /dev/null +++ b/gfx/layers/wr/ClipManager.h @@ -0,0 +1,150 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_CLIPMANAGER_H +#define GFX_CLIPMANAGER_H + +#include +#include + +#include "mozilla/Attributes.h" +#include "mozilla/webrender/WebRenderAPI.h" + +class nsDisplayItem; + +namespace mozilla { + +struct ActiveScrolledRoot; +struct DisplayItemClipChain; + +namespace wr { +class DisplayListBuilder; +} + +namespace layers { + +class StackingContextHelper; +class WebRenderLayerManager; + +/** + * This class manages creating and assigning scroll layers and clips in + * WebRender based on the gecko display list. It has a few public functions that + * are intended to be invoked while traversing the Gecko display list, and it + * uses the ASR and clip information from the display list to create the + * necessary clip state in WebRender. + * + * The structure of the clip state in WebRender ends up quite similar to how + * it is in Gecko. For each ASR in Gecko, we create a scroll layer (i.e. a + * scrolling clip) in WebRender; these form a tree structure similar to the + * ASR tree structure. Ancestors of scroll layers are always other scroll + * layers, or the root scroll node. + * The DisplayItemClipChain list of clips from the gecko display list is + * converted to a WR clip chain and pushed on the stack prior to creating + * any WR commands for that item, and is popped afterwards. In addition, + * the WR clip chain has a parent pointer, which points to the clip chain for + * any enclosing stacking context. This again results in a strucuture very + * similar to that in Gecko, where the clips from container display items get + * applied to the contained display items. + */ +class ClipManager { + public: + ClipManager(); + + void BeginBuild(WebRenderLayerManager* aManager, + wr::DisplayListBuilder& aBuilder); + void EndBuild(); + + void BeginList(const StackingContextHelper& aStackingContext); + void EndList(const StackingContextHelper& aStackingContext); + + wr::WrSpaceAndClipChain SwitchItem(nsDisplayItem* aItem); + ~ClipManager(); + + void PushOverrideForASR(const ActiveScrolledRoot* aASR, + const wr::WrSpatialId& aSpatialId); + void PopOverrideForASR(const ActiveScrolledRoot* aASR); + + private: + wr::WrSpatialId SpatialIdAfterOverride(const wr::WrSpatialId& aSpatialId); + + Maybe GetScrollLayer(const ActiveScrolledRoot* aASR); + + Maybe DefineScrollLayers(const ActiveScrolledRoot* aASR, + nsDisplayItem* aItem); + + Maybe DefineClipChain(const DisplayItemClipChain* aChain, + int32_t aAppUnitsPerDevPixel); + + WebRenderLayerManager* MOZ_NON_OWNING_REF mManager; + wr::DisplayListBuilder* mBuilder; + + // Stack of clip caches. Each cache contains a map from gecko + // DisplayItemClipChain objects to webrender WrClipIds, which allows us to + // avoid redefining identical clips in WR. However, the gecko + // DisplayItemClipChain items get deduplicated quite aggressively, without + // regard to things like the enclosing reference frame or mask. On the WR + // side, we cannot deduplicate clips that aggressively. So what we do is + // any time we enter a new reference frame (for example) we create a new clip + // cache on mCacheStack. This ensures we continue caching stuff within a given + // reference frame, but disallow caching stuff across reference frames. In + // general we need to do this anytime PushOverrideForASR is called, as that is + // called for the same set of conditions for which we cannot deduplicate + // clips. + typedef std::unordered_map + ClipIdMap; + std::stack mCacheStack; + + // A map that holds the cache overrides created by (a) "out of band" clips, + // i.e. clips that are generated by display items but that ClipManager + // doesn't know about and (b) stacking contexts that affect clip positioning. + // These are called "cache overrides" because while we're inside these things, + // we cannot use the ASR from the gecko display list as-is. Fundamentally this + // results from a mismatch between the ASR+clip items on the gecko side and + // the ClipScrollTree on the WR side; the WR side incorporates things like + // transforms and stacking context origins while the gecko side manages those + // differently. + // Any time ClipManager wants to define a new clip as a child of ASR X, it + // should first check the cache overrides to see if there is a cache override + // item ((a) or (b) above) that is already a child of X, and then define that + // clip as a child of Y instead. This map stores X -> Y, which allows + // ClipManager to do the necessary lookup. Note that there theoretically might + // be multiple different "Y" clips (in case of nested cache overrides), which + // is why we need a stack. + std::unordered_map> mASROverride; + + // This holds some clip state for a single nsDisplayItem + struct ItemClips { + ItemClips(const ActiveScrolledRoot* aASR, + const DisplayItemClipChain* aChain, bool aSeparateLeaf); + + // These are the "inputs" - they come from the nsDisplayItem + const ActiveScrolledRoot* mASR; + const DisplayItemClipChain* mChain; + bool mSeparateLeaf; + + // These are the "outputs" - they are pushed to WR as needed + wr::WrSpatialId mScrollId; + Maybe mClipChainId; + + void UpdateSeparateLeaf(wr::DisplayListBuilder& aBuilder, + int32_t aAppUnitsPerDevPixel); + bool HasSameInputs(const ItemClips& aOther); + void CopyOutputsFrom(const ItemClips& aOther); + wr::WrSpaceAndClipChain GetSpaceAndClipChain() const; + }; + + // A stack of ItemClips corresponding to the nsDisplayItem ancestry. Each + // time we recurse into a nsDisplayItem's child list, this stack size + // increases by one. The topmost item on the stack is for the display item + // we are currently processing and items deeper on the stack are for that + // display item's ancestors. + std::stack mItemClipStack; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/wr/DisplayItemCache.cpp b/gfx/layers/wr/DisplayItemCache.cpp new file mode 100644 index 0000000000..212a0449eb --- /dev/null +++ b/gfx/layers/wr/DisplayItemCache.cpp @@ -0,0 +1,197 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DisplayItemCache.h" +#include "nsDisplayList.h" + +namespace mozilla { +namespace layers { + +DisplayItemCache::DisplayItemCache() + : mDisplayList(nullptr), + mMaximumSize(0), + mPipelineId{}, + mCaching(false), + mInvalid(false), + mSuppressed(false) {} + +void DisplayItemCache::SetDisplayList(nsDisplayListBuilder* aBuilder, + nsDisplayList* aList) { + if (!IsEnabled()) { + return; + } + + MOZ_ASSERT(aBuilder); + MOZ_ASSERT(aList); + + const bool listChanged = mDisplayList != aList; + const bool partialBuild = !aBuilder->PartialBuildFailed(); + + if (listChanged && partialBuild) { + // If the display list changed during a partial update, it means that + // |SetDisplayList()| has missed one rebuilt display list. + mDisplayList = nullptr; + return; + } + + if (listChanged || !partialBuild) { + // The display list has been changed or rebuilt. + mDisplayList = aList; + mInvalid = true; + } + + UpdateState(); +} + +void DisplayItemCache::SetPipelineId(const wr::PipelineId& aPipelineId) { + mInvalid = mInvalid || !(mPipelineId == aPipelineId); + mPipelineId = aPipelineId; +} + +void DisplayItemCache::UpdateState() { + // |mCaching == true| if: + // 1) |SetDisplayList()| is called with a fully rebuilt display list + // followed by + // 2a) |SetDisplayList()| is called with a partially updated display list + // or + // 2b) |SkipWaitingForPartialDisplayList()| is called + mCaching = !mInvalid; + +#if 0 + Stats().Print(); + Stats().Reset(); +#endif + + if (IsEmpty()) { + // The cache is empty so nothing needs to be updated. + mInvalid = false; + return; + } + + // Clear the cache if the current state is invalid. + if (mInvalid) { + Clear(); + } else { + FreeUnusedSlots(); + } + + mInvalid = false; +} + +void DisplayItemCache::Clear() { + memset(mSlots.Elements(), 0, mSlots.Length() * sizeof(Slot)); + mFreeSlots.ClearAndRetainStorage(); + + for (size_t i = 0; i < CurrentSize(); ++i) { + mFreeSlots.AppendElement(i); + } +} + +Maybe DisplayItemCache::GetNextFreeSlot() { + if (mFreeSlots.IsEmpty() && !GrowIfPossible()) { + return Nothing(); + } + + return Some(mFreeSlots.PopLastElement()); +} + +bool DisplayItemCache::GrowIfPossible() { + if (IsFull()) { + return false; + } + + const auto currentSize = CurrentSize(); + MOZ_ASSERT(currentSize <= mMaximumSize); + + // New slots are added one by one, which is amortized O(1) time complexity due + // to underlying storage implementation. + mSlots.AppendElement(); + mFreeSlots.AppendElement(currentSize); + return true; +} + +void DisplayItemCache::FreeUnusedSlots() { + for (size_t i = 0; i < CurrentSize(); ++i) { + auto& slot = mSlots[i]; + + if (!slot.mUsed && slot.mOccupied) { + // This entry contained a cached item, but was not used. + slot.mOccupied = false; + mFreeSlots.AppendElement(i); + } + + slot.mUsed = false; + } +} + +void DisplayItemCache::SetCapacity(const size_t aInitialSize, + const size_t aMaximumSize) { + mMaximumSize = aMaximumSize; + mSlots.SetLength(aInitialSize); + mFreeSlots.SetCapacity(aMaximumSize); + Clear(); +} + +Maybe DisplayItemCache::AssignSlot(nsPaintedDisplayItem* aItem) { + if (!mCaching || !aItem->CanBeReused() || !aItem->CanBeCached()) { + return Nothing(); + } + + auto& slot = aItem->CacheIndex(); + + if (!slot) { + slot = GetNextFreeSlot(); + if (!slot) { + // The item does not fit in the cache. + return Nothing(); + } + } + + MOZ_ASSERT(slot && CurrentSize() > *slot); + return slot; +} + +void DisplayItemCache::MarkSlotOccupied( + uint16_t aSlotIndex, const wr::WrSpaceAndClipChain& aSpaceAndClip) { + // Caching of the item succeeded, update the slot state. + auto& slot = mSlots[aSlotIndex]; + MOZ_ASSERT(!slot.mOccupied); + slot.mOccupied = true; + MOZ_ASSERT(!slot.mUsed); + slot.mUsed = true; + slot.mSpaceAndClip = aSpaceAndClip; +} + +Maybe DisplayItemCache::CanReuseItem( + nsPaintedDisplayItem* aItem, const wr::WrSpaceAndClipChain& aSpaceAndClip) { + auto& slotIndex = aItem->CacheIndex(); + if (!slotIndex) { + return Nothing(); + } + + MOZ_ASSERT(slotIndex && CurrentSize() > *slotIndex); + + auto& slot = mSlots[*slotIndex]; + if (!slot.mOccupied) { + // The display item has a stale cache slot. Recache the item. + return Nothing(); + } + + if (!(aSpaceAndClip == slot.mSpaceAndClip)) { + // Spatial id and clip id can change between display lists, if items that + // generate them change their order. + slot.mOccupied = false; + aItem->SetCantBeCached(); + slotIndex = Nothing(); + } else { + slot.mUsed = true; + } + + return slotIndex; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/wr/DisplayItemCache.h b/gfx/layers/wr/DisplayItemCache.h new file mode 100644 index 0000000000..e7a1b83e81 --- /dev/null +++ b/gfx/layers/wr/DisplayItemCache.h @@ -0,0 +1,210 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_DISPLAY_ITEM_CACHE_H +#define GFX_DISPLAY_ITEM_CACHE_H + +#include "mozilla/webrender/WebRenderAPI.h" +#include "nsTArray.h" + +class nsDisplayList; +class nsDisplayListBuilder; +class nsPaintedDisplayItem; + +namespace mozilla { + +namespace wr { +class DisplayListBuilder; +} // namespace wr + +namespace layers { + +class CacheStats { + public: + CacheStats() = default; + + void Reset() { mCached = mReused = mTotal = 0; } + + void Print() { + static uint64_t avgC = 1; + static uint64_t avgR = 1; + static uint64_t avgT = 1; + + avgC += mCached; + avgR += mReused; + avgT += mTotal; + + printf("Cached: %zu (avg: %f), Reused: %zu (avg: %f), Total: %zu\n", + mCached, (double)avgC / (double)avgT, mReused, + (double)avgR / (double)avgT, mTotal); + } + + void AddCached() { mCached++; } + void AddReused() { mReused++; } + void AddTotal() { mTotal++; } + + private: + size_t mCached = 0; + size_t mReused = 0; + size_t mTotal = 0; +}; + +/** + * DisplayItemCache keeps track of which Gecko display items have already had + * their respective WebRender display items sent to WebRender backend. + * + * Ideally creating the WR display items for a Gecko display item would not + * depend on any external state. However currently pipeline id, clip id, and + * spatial id can change between display lists, even if the Gecko display items + * have not. This state is tracked by DisplayItemCache. + */ +class DisplayItemCache final { + public: + DisplayItemCache(); + + /** + * Clears the cache. + */ + void Clear(); + + /** + * Sets the initial and max cache size to given |aInitialSize| and |aMaxSize|. + */ + void SetCapacity(const size_t aInitialSize, const size_t aMaximumSize); + + /** + * Sets the display list used by the cache. + */ + void SetDisplayList(nsDisplayListBuilder* aBuilder, nsDisplayList* aList); + + /** + * Sets the pipeline id used by the cache. + */ + void SetPipelineId(const wr::PipelineId& aPipelineId); + + /** + * Enables caching immediately if the cache is valid, and display list is set. + */ + void SkipWaitingForPartialDisplayList() { + mCaching = mDisplayList && !mInvalid; + } + + /** + * Returns true if display item caching is enabled, otherwise false. + */ + bool IsEnabled() const { return !mSuppressed && mMaximumSize > 0; } + + /** + * Suppress display item caching. This doesn't clear any existing cached + * items or change the underlying capacity, it just makes IsEnabled() return + * false. It is not meant to be flipped in the middle of a display list build, + * but rather set before the display list build starts to suppress use of the + * cache for that display list build. + */ + bool SetSuppressed(bool aSuppressed) { + if (aSuppressed == mSuppressed) { + return mSuppressed; + } + mSuppressed = aSuppressed; + return !mSuppressed; + } + + /** + * Returns true if there are no cached items, otherwise false. + */ + bool IsEmpty() const { return mFreeSlots.Length() == CurrentSize(); } + + /** + * Returns true if the cache has reached the maximum size, otherwise false. + */ + bool IsFull() const { + return mFreeSlots.IsEmpty() && CurrentSize() == mMaximumSize; + } + + /** + * Returns the current cache size. + */ + size_t CurrentSize() const { return mSlots.Length(); } + + /** + * If there are free slots in the cache, assigns a cache slot to the given + * display item |aItem| and returns it. Otherwise returns Nothing(). + */ + Maybe AssignSlot(nsPaintedDisplayItem* aItem); + + /** + * Marks the slot with the given |slotIndex| occupied and used. + * Also stores the current space and clipchain |aSpaceAndClip|. + */ + void MarkSlotOccupied(uint16_t slotIndex, + const wr::WrSpaceAndClipChain& aSpaceAndClip); + + /** + * Returns the slot index of the the given display item |aItem|, if the item + * can be reused. The current space and clipchain |aSpaceAndClip| is used to + * check whether the cached item is still valid. + * If the item cannot be reused, returns Nothing(). + */ + Maybe CanReuseItem(nsPaintedDisplayItem* aItem, + const wr::WrSpaceAndClipChain& aSpaceAndClip); + + CacheStats& Stats() { return mCacheStats; } + + private: + struct Slot { + Slot() : mSpaceAndClip{}, mOccupied(false), mUsed(false) {} + + wr::WrSpaceAndClipChain mSpaceAndClip; + bool mOccupied; + bool mUsed; + }; + + void FreeUnusedSlots(); + Maybe GetNextFreeSlot(); + bool GrowIfPossible(); + void UpdateState(); + + // The lifetime of display lists exceed the lifetime of DisplayItemCache. + // This pointer stores the address of the display list that is using this + // cache, and it is only used for pointer comparisons. + nsDisplayList* mDisplayList; + + size_t mMaximumSize; + nsTArray mSlots; + nsTArray mFreeSlots; + + wr::PipelineId mPipelineId; + bool mCaching; + bool mInvalid; + bool mSuppressed; + + CacheStats mCacheStats; +}; + +class MOZ_RAII AutoDisplayItemCacheSuppressor { + public: + explicit AutoDisplayItemCacheSuppressor(DisplayItemCache* aCache) + : mCache(aCache) { + mWasSuppressed = mCache->SetSuppressed(true); + } + + // Note that this restores the original state rather than unconditionally + // unsuppressing the cache for future-proofing/robustification. Currently + // we only ever use this RAII in one non-recursive function, but we might + // decide to expand its usage to other scenarios and end up with nested + // suppressions, in which case restoring the state back to what we found it + // is better. + ~AutoDisplayItemCacheSuppressor() { mCache->SetSuppressed(mWasSuppressed); } + + private: + DisplayItemCache* mCache; + bool mWasSuppressed; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_DISPLAY_ITEM_CACHE_H */ diff --git a/gfx/layers/wr/IpcResourceUpdateQueue.cpp b/gfx/layers/wr/IpcResourceUpdateQueue.cpp new file mode 100644 index 0000000000..d69d5269e4 --- /dev/null +++ b/gfx/layers/wr/IpcResourceUpdateQueue.cpp @@ -0,0 +1,498 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "IpcResourceUpdateQueue.h" +#include +#include +#include "mozilla/Maybe.h" +#include "mozilla/ipc/SharedMemory.h" +#include "mozilla/layers/PTextureChild.h" +#include "mozilla/layers/WebRenderBridgeChild.h" + +namespace mozilla { +namespace wr { + +using namespace mozilla::layers; + +ShmSegmentsWriter::ShmSegmentsWriter(layers::WebRenderBridgeChild* aAllocator, + size_t aChunkSize) + : mShmAllocator(aAllocator), mCursor(0), mChunkSize(aChunkSize) { + MOZ_ASSERT(mShmAllocator); +} + +ShmSegmentsWriter::~ShmSegmentsWriter() { Clear(); } + +ShmSegmentsWriter::ShmSegmentsWriter(ShmSegmentsWriter&& aOther) noexcept + : mSmallAllocs(std::move(aOther.mSmallAllocs)), + mLargeAllocs(std::move(aOther.mLargeAllocs)), + mShmAllocator(aOther.mShmAllocator), + mCursor(aOther.mCursor), + mChunkSize(aOther.mChunkSize) { + aOther.mCursor = 0; +} + +ShmSegmentsWriter& ShmSegmentsWriter::operator=( + ShmSegmentsWriter&& aOther) noexcept { + MOZ_ASSERT(IsEmpty(), "Will forget existing updates!"); + Clear(); + mSmallAllocs = std::move(aOther.mSmallAllocs); + mLargeAllocs = std::move(aOther.mLargeAllocs); + mShmAllocator = aOther.mShmAllocator; + mCursor = aOther.mCursor; + mChunkSize = aOther.mChunkSize; + aOther.mCursor = 0; + return *this; +} + +layers::OffsetRange ShmSegmentsWriter::Write(Range aBytes) { + const size_t start = mCursor; + const size_t length = aBytes.length(); + + if (length >= mChunkSize * 4) { + auto range = AllocLargeChunk(length); + if (range.length()) { + // Allocation was successful + uint8_t* dstPtr = mLargeAllocs.LastElement().get(); + memcpy(dstPtr, aBytes.begin().get(), length); + } + return range; + } + + int remainingBytesToCopy = length; + + size_t srcCursor = 0; + size_t dstCursor = mCursor; + size_t currAllocLen = mSmallAllocs.Length(); + + while (remainingBytesToCopy > 0) { + if (dstCursor >= mSmallAllocs.Length() * mChunkSize) { + if (!AllocChunk()) { + // Allocation failed, so roll back to the state at the start of this + // Write() call and abort. + while (mSmallAllocs.Length() > currAllocLen) { + RefCountedShmem shm = mSmallAllocs.PopLastElement(); + RefCountedShm::Dealloc(mShmAllocator, shm); + } + MOZ_ASSERT(mSmallAllocs.Length() == currAllocLen); + return layers::OffsetRange(0, start, 0); + } + // Allocation succeeded, so dstCursor should now be pointing to + // something inside the allocation buffer + MOZ_ASSERT(dstCursor < (mSmallAllocs.Length() * mChunkSize)); + } + + const size_t dstMaxOffset = mChunkSize * mSmallAllocs.Length(); + const size_t dstBaseOffset = mChunkSize * (mSmallAllocs.Length() - 1); + + MOZ_ASSERT(dstCursor >= dstBaseOffset); + MOZ_ASSERT(dstCursor <= dstMaxOffset); + + size_t availableRange = dstMaxOffset - dstCursor; + size_t copyRange = std::min(availableRange, remainingBytesToCopy); + + uint8_t* srcPtr = &aBytes[srcCursor]; + uint8_t* dstPtr = RefCountedShm::GetBytes(mSmallAllocs.LastElement()) + + (dstCursor - dstBaseOffset); + + memcpy(dstPtr, srcPtr, copyRange); + + srcCursor += copyRange; + dstCursor += copyRange; + remainingBytesToCopy -= copyRange; + + // sanity check + MOZ_ASSERT(remainingBytesToCopy >= 0); + } + + mCursor += length; + + return layers::OffsetRange(0, start, length); +} + +bool ShmSegmentsWriter::AllocChunk() { + RefCountedShmem shm; + if (!mShmAllocator->AllocResourceShmem(mChunkSize, shm)) { + gfxCriticalNote << "ShmSegmentsWriter failed to allocate chunk #" + << mSmallAllocs.Length(); + MOZ_ASSERT(false, "ShmSegmentsWriter fails to allocate chunk"); + return false; + } + RefCountedShm::AddRef(shm); + mSmallAllocs.AppendElement(shm); + return true; +} + +layers::OffsetRange ShmSegmentsWriter::AllocLargeChunk(size_t aSize) { + ipc::Shmem shm; + auto shmType = ipc::SharedMemory::SharedMemoryType::TYPE_BASIC; + if (!mShmAllocator->AllocShmem(aSize, shmType, &shm)) { + gfxCriticalNote + << "ShmSegmentsWriter failed to allocate large chunk of size " << aSize; + MOZ_ASSERT(false, "ShmSegmentsWriter fails to allocate large chunk"); + return layers::OffsetRange(0, 0, 0); + } + mLargeAllocs.AppendElement(shm); + + return layers::OffsetRange(mLargeAllocs.Length(), 0, aSize); +} + +void ShmSegmentsWriter::Flush(nsTArray& aSmallAllocs, + nsTArray& aLargeAllocs) { + MOZ_ASSERT(aSmallAllocs.IsEmpty()); + MOZ_ASSERT(aLargeAllocs.IsEmpty()); + aSmallAllocs = std::move(mSmallAllocs); + aLargeAllocs = std::move(mLargeAllocs); + mCursor = 0; +} + +bool ShmSegmentsWriter::IsEmpty() const { return mCursor == 0; } + +void ShmSegmentsWriter::Clear() { + if (mShmAllocator) { + IpcResourceUpdateQueue::ReleaseShmems(mShmAllocator, mSmallAllocs); + IpcResourceUpdateQueue::ReleaseShmems(mShmAllocator, mLargeAllocs); + } + mCursor = 0; +} + +ShmSegmentsReader::ShmSegmentsReader( + const nsTArray& aSmallShmems, + const nsTArray& aLargeShmems) + : mSmallAllocs(aSmallShmems), mLargeAllocs(aLargeShmems), mChunkSize(0) { + if (mSmallAllocs.IsEmpty()) { + return; + } + + mChunkSize = RefCountedShm::GetSize(mSmallAllocs[0]); + + // Check that all shmems are readable and have the same size. If anything + // isn't right, set mChunkSize to zero which signifies that the reader is + // in an invalid state and Read calls will return false; + for (const auto& shm : mSmallAllocs) { + if (!RefCountedShm::IsValid(shm) || + RefCountedShm::GetSize(shm) != mChunkSize || + RefCountedShm::GetBytes(shm) == nullptr) { + mChunkSize = 0; + return; + } + } + + for (const auto& shm : mLargeAllocs) { + if (!shm.IsReadable() || shm.get() == nullptr) { + mChunkSize = 0; + return; + } + } +} + +bool ShmSegmentsReader::ReadLarge(const layers::OffsetRange& aRange, + wr::Vec& aInto) { + // source = zero is for small allocs. + MOZ_RELEASE_ASSERT(aRange.source() != 0); + if (aRange.source() > mLargeAllocs.Length()) { + return false; + } + size_t id = aRange.source() - 1; + const ipc::Shmem& shm = mLargeAllocs[id]; + if (shm.Size() < aRange.length()) { + return false; + } + + uint8_t* srcPtr = shm.get(); + aInto.PushBytes(Range(srcPtr, aRange.length())); + + return true; +} + +bool ShmSegmentsReader::Read(const layers::OffsetRange& aRange, + wr::Vec& aInto) { + if (aRange.length() == 0) { + return true; + } + + if (aRange.source() != 0) { + return ReadLarge(aRange, aInto); + } + + if (mChunkSize == 0) { + return false; + } + + if (aRange.start() + aRange.length() > mChunkSize * mSmallAllocs.Length()) { + return false; + } + + size_t initialLength = aInto.Length(); + + size_t srcCursor = aRange.start(); + size_t remainingBytesToCopy = aRange.length(); + while (remainingBytesToCopy > 0) { + const size_t shm_idx = srcCursor / mChunkSize; + const size_t ptrOffset = srcCursor % mChunkSize; + const size_t copyRange = + std::min(remainingBytesToCopy, mChunkSize - ptrOffset); + uint8_t* srcPtr = + RefCountedShm::GetBytes(mSmallAllocs[shm_idx]) + ptrOffset; + + aInto.PushBytes(Range(srcPtr, copyRange)); + + srcCursor += copyRange; + remainingBytesToCopy -= copyRange; + } + + return aInto.Length() - initialLength == aRange.length(); +} + +Maybe> ShmSegmentsReader::GetReadPointerLarge( + const layers::OffsetRange& aRange) { + // source = zero is for small allocs. + MOZ_RELEASE_ASSERT(aRange.source() != 0); + if (aRange.source() > mLargeAllocs.Length()) { + return Nothing(); + } + size_t id = aRange.source() - 1; + const ipc::Shmem& shm = mLargeAllocs[id]; + if (shm.Size() < aRange.length()) { + return Nothing(); + } + + uint8_t* srcPtr = shm.get(); + return Some(Range(srcPtr, aRange.length())); +} + +Maybe> ShmSegmentsReader::GetReadPointer( + const layers::OffsetRange& aRange) { + if (aRange.length() == 0) { + return Some(Range()); + } + + if (aRange.source() != 0) { + return GetReadPointerLarge(aRange); + } + + if (mChunkSize == 0 || + aRange.start() + aRange.length() > mChunkSize * mSmallAllocs.Length()) { + return Nothing(); + } + + size_t srcCursor = aRange.start(); + size_t remainingBytesToCopy = aRange.length(); + const size_t shm_idx = srcCursor / mChunkSize; + const size_t ptrOffset = srcCursor % mChunkSize; + // Return nothing if we can't return a pointer to the full range + if (mChunkSize - ptrOffset < remainingBytesToCopy) { + return Nothing(); + } + uint8_t* srcPtr = RefCountedShm::GetBytes(mSmallAllocs[shm_idx]) + ptrOffset; + return Some(Range(srcPtr, remainingBytesToCopy)); +} + +IpcResourceUpdateQueue::IpcResourceUpdateQueue( + layers::WebRenderBridgeChild* aAllocator, size_t aChunkSize) + : mWriter(aAllocator, aChunkSize) {} + +IpcResourceUpdateQueue::IpcResourceUpdateQueue( + IpcResourceUpdateQueue&& aOther) noexcept + : mWriter(std::move(aOther.mWriter)), + mUpdates(std::move(aOther.mUpdates)) {} + +IpcResourceUpdateQueue& IpcResourceUpdateQueue::operator=( + IpcResourceUpdateQueue&& aOther) noexcept { + MOZ_ASSERT(IsEmpty(), "Will forget existing updates!"); + mWriter = std::move(aOther.mWriter); + mUpdates = std::move(aOther.mUpdates); + return *this; +} + +void IpcResourceUpdateQueue::ReplaceResources(IpcResourceUpdateQueue&& aOther) { + MOZ_ASSERT(IsEmpty(), "Will forget existing updates!"); + mWriter = std::move(aOther.mWriter); + mUpdates = std::move(aOther.mUpdates); +} + +bool IpcResourceUpdateQueue::AddImage(ImageKey key, + const ImageDescriptor& aDescriptor, + Range aBytes) { + auto bytes = mWriter.Write(aBytes); + if (!bytes.length()) { + return false; + } + mUpdates.AppendElement(layers::OpAddImage(aDescriptor, bytes, 0, key)); + return true; +} + +bool IpcResourceUpdateQueue::AddBlobImage(BlobImageKey key, + const ImageDescriptor& aDescriptor, + Range aBytes, + ImageIntRect aVisibleRect) { + MOZ_RELEASE_ASSERT(aDescriptor.width > 0 && aDescriptor.height > 0); + auto bytes = mWriter.Write(aBytes); + if (!bytes.length()) { + return false; + } + mUpdates.AppendElement( + layers::OpAddBlobImage(aDescriptor, bytes, aVisibleRect, 0, key)); + return true; +} + +void IpcResourceUpdateQueue::AddPrivateExternalImage( + wr::ExternalImageId aExtId, wr::ImageKey aKey, wr::ImageDescriptor aDesc) { + mUpdates.AppendElement( + layers::OpAddPrivateExternalImage(aExtId, aKey, aDesc)); +} + +void IpcResourceUpdateQueue::AddSharedExternalImage(wr::ExternalImageId aExtId, + wr::ImageKey aKey) { + mUpdates.AppendElement(layers::OpAddSharedExternalImage(aExtId, aKey)); +} + +void IpcResourceUpdateQueue::PushExternalImageForTexture( + wr::ExternalImageId aExtId, wr::ImageKey aKey, + layers::TextureClient* aTexture, bool aIsUpdate) { + MOZ_ASSERT(aTexture); + MOZ_ASSERT(aTexture->GetIPDLActor()); + MOZ_RELEASE_ASSERT(aTexture->GetIPDLActor()->GetIPCChannel() == + mWriter.WrBridge()->GetIPCChannel()); + mUpdates.AppendElement(layers::OpPushExternalImageForTexture( + aExtId, aKey, nullptr, aTexture->GetIPDLActor(), aIsUpdate)); +} + +bool IpcResourceUpdateQueue::UpdateImageBuffer( + ImageKey aKey, const ImageDescriptor& aDescriptor, Range aBytes) { + auto bytes = mWriter.Write(aBytes); + if (!bytes.length()) { + return false; + } + mUpdates.AppendElement(layers::OpUpdateImage(aDescriptor, bytes, aKey)); + return true; +} + +bool IpcResourceUpdateQueue::UpdateBlobImage(BlobImageKey aKey, + const ImageDescriptor& aDescriptor, + Range aBytes, + ImageIntRect aVisibleRect, + ImageIntRect aDirtyRect) { + MOZ_ASSERT(aVisibleRect.width > 0 && aVisibleRect.height > 0); + + auto bytes = mWriter.Write(aBytes); + if (!bytes.length()) { + return false; + } + mUpdates.AppendElement(layers::OpUpdateBlobImage(aDescriptor, bytes, aKey, + aVisibleRect, aDirtyRect)); + return true; +} + +void IpcResourceUpdateQueue::UpdatePrivateExternalImage( + wr::ExternalImageId aExtId, wr::ImageKey aKey, + const wr::ImageDescriptor& aDesc, ImageIntRect aDirtyRect) { + mUpdates.AppendElement( + layers::OpUpdatePrivateExternalImage(aExtId, aKey, aDesc, aDirtyRect)); +} + +void IpcResourceUpdateQueue::UpdateSharedExternalImage( + wr::ExternalImageId aExtId, wr::ImageKey aKey, ImageIntRect aDirtyRect) { + mUpdates.AppendElement( + layers::OpUpdateSharedExternalImage(aExtId, aKey, aDirtyRect)); +} + +void IpcResourceUpdateQueue::SetBlobImageVisibleArea( + wr::BlobImageKey aKey, const ImageIntRect& aArea) { + mUpdates.AppendElement(layers::OpSetBlobImageVisibleArea(aArea, aKey)); +} + +void IpcResourceUpdateQueue::DeleteImage(ImageKey aKey) { + mUpdates.AppendElement(layers::OpDeleteImage(aKey)); +} + +void IpcResourceUpdateQueue::DeleteBlobImage(BlobImageKey aKey) { + mUpdates.AppendElement(layers::OpDeleteBlobImage(aKey)); +} + +bool IpcResourceUpdateQueue::AddRawFont(wr::FontKey aKey, Range aBytes, + uint32_t aIndex) { + auto bytes = mWriter.Write(aBytes); + if (!bytes.length()) { + return false; + } + mUpdates.AppendElement(layers::OpAddRawFont(bytes, aIndex, aKey)); + return true; +} + +bool IpcResourceUpdateQueue::AddFontDescriptor(wr::FontKey aKey, + Range aBytes, + uint32_t aIndex) { + auto bytes = mWriter.Write(aBytes); + if (!bytes.length()) { + return false; + } + mUpdates.AppendElement(layers::OpAddFontDescriptor(bytes, aIndex, aKey)); + return true; +} + +void IpcResourceUpdateQueue::DeleteFont(wr::FontKey aKey) { + mUpdates.AppendElement(layers::OpDeleteFont(aKey)); +} + +void IpcResourceUpdateQueue::AddFontInstance( + wr::FontInstanceKey aKey, wr::FontKey aFontKey, float aGlyphSize, + const wr::FontInstanceOptions* aOptions, + const wr::FontInstancePlatformOptions* aPlatformOptions, + Range aVariations) { + auto bytes = mWriter.WriteAsBytes(aVariations); + mUpdates.AppendElement(layers::OpAddFontInstance( + aOptions ? Some(*aOptions) : Nothing(), + aPlatformOptions ? Some(*aPlatformOptions) : Nothing(), bytes, aKey, + aFontKey, aGlyphSize)); +} + +void IpcResourceUpdateQueue::DeleteFontInstance(wr::FontInstanceKey aKey) { + mUpdates.AppendElement(layers::OpDeleteFontInstance(aKey)); +} + +void IpcResourceUpdateQueue::Flush( + nsTArray& aUpdates, + nsTArray& aSmallAllocs, + nsTArray& aLargeAllocs) { + aUpdates = std::move(mUpdates); + mWriter.Flush(aSmallAllocs, aLargeAllocs); +} + +bool IpcResourceUpdateQueue::IsEmpty() const { + if (mUpdates.Length() == 0) { + MOZ_ASSERT(mWriter.IsEmpty()); + return true; + } + return false; +} + +void IpcResourceUpdateQueue::Clear() { + mWriter.Clear(); + mUpdates.Clear(); +} + +// static +void IpcResourceUpdateQueue::ReleaseShmems( + ipc::IProtocol* aShmAllocator, nsTArray& aShms) { + for (auto& shm : aShms) { + if (RefCountedShm::IsValid(shm) && RefCountedShm::Release(shm) == 0) { + RefCountedShm::Dealloc(aShmAllocator, shm); + } + } + aShms.Clear(); +} + +// static +void IpcResourceUpdateQueue::ReleaseShmems(ipc::IProtocol* aShmAllocator, + nsTArray& aShms) { + for (auto& shm : aShms) { + aShmAllocator->DeallocShmem(shm); + } + aShms.Clear(); +} + +} // namespace wr +} // namespace mozilla diff --git a/gfx/layers/wr/IpcResourceUpdateQueue.h b/gfx/layers/wr/IpcResourceUpdateQueue.h new file mode 100644 index 0000000000..0b3f890cbe --- /dev/null +++ b/gfx/layers/wr/IpcResourceUpdateQueue.h @@ -0,0 +1,201 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_WR_IPCRESOURCEUPDATEQUEUE_H +#define GFX_WR_IPCRESOURCEUPDATEQUEUE_H + +#include "mozilla/layers/WebRenderMessages.h" +#include "mozilla/layers/RefCountedShmem.h" +#include "mozilla/layers/TextureClient.h" +#include "mozilla/webrender/WebRenderTypes.h" + +namespace mozilla { +namespace ipc { +class IShmemAllocator; +} +namespace layers { +class TextureClient; +class WebRenderBridgeChild; +} // namespace layers + +namespace wr { + +/// ShmSegmentsWriter pushes bytes in a sequence of fixed size shmems for small +/// allocations and creates dedicated shmems for large allocations. +class ShmSegmentsWriter { + public: + ShmSegmentsWriter(layers::WebRenderBridgeChild* aAllocator, + size_t aChunkSize); + ~ShmSegmentsWriter(); + + ShmSegmentsWriter(ShmSegmentsWriter&& aOther) noexcept; + ShmSegmentsWriter& operator=(ShmSegmentsWriter&& aOther) noexcept; + + ShmSegmentsWriter(const ShmSegmentsWriter& aOther) = delete; + ShmSegmentsWriter& operator=(const ShmSegmentsWriter& aOther) = delete; + + layers::OffsetRange Write(Range aBytes); + + template + layers::OffsetRange WriteAsBytes(Range aValues) { + return Write(Range((uint8_t*)aValues.begin().get(), + aValues.length() * sizeof(T))); + } + + void Flush(nsTArray& aSmallAllocs, + nsTArray& aLargeAllocs); + + void Clear(); + bool IsEmpty() const; + + layers::WebRenderBridgeChild* WrBridge() const { return mShmAllocator; } + size_t ChunkSize() const { return mChunkSize; } + + protected: + bool AllocChunk(); + layers::OffsetRange AllocLargeChunk(size_t aSize); + + nsTArray mSmallAllocs; + nsTArray mLargeAllocs; + layers::WebRenderBridgeChild* mShmAllocator; + size_t mCursor; + size_t mChunkSize; +}; + +class ShmSegmentsReader { + public: + ShmSegmentsReader(const nsTArray& aSmallShmems, + const nsTArray& aLargeShmems); + + bool Read(const layers::OffsetRange& aRange, wr::Vec& aInto); + + // Get a read pointer, if possible, directly into the shm. If the range has + // been broken up into multiple chunks that can't be represented by a single + // range, nothing will be returned to indicate failure. + Maybe> GetReadPointer(const layers::OffsetRange& aRange); + + // Get a read pointer, if possible, directly into the shm. Otherwise, copy + // it into the Vec and return a pointer to that contiguous memory instead. + // If all fails, return nothing. + Maybe> GetReadPointerOrCopy(const layers::OffsetRange& aRange, + wr::Vec& aInto) { + if (Maybe> ptr = GetReadPointer(aRange)) { + return ptr; + } else { + size_t initialLength = aInto.Length(); + if (Read(aRange, aInto)) { + return Some(Range(aInto.Data() + initialLength, + aInto.Length() - initialLength)); + } else { + return Nothing(); + } + } + } + + protected: + bool ReadLarge(const layers::OffsetRange& aRange, wr::Vec& aInto); + + Maybe> GetReadPointerLarge(const layers::OffsetRange& aRange); + + const nsTArray& mSmallAllocs; + const nsTArray& mLargeAllocs; + size_t mChunkSize; +}; + +class IpcResourceUpdateQueue { + public: + // Because we are using shmems, the size should be a multiple of the page + // size. Each shmem has two guard pages, and the minimum shmem size (at least + // one Windows) is 64k which is already quite large for a lot of the resources + // we use here. The RefCountedShmem type used to allocate the chunks keeps a + // 16 bytes header in the buffer which we account for here as well. So we pick + // 64k - 2 * 4k - 16 = 57328 bytes as the default alloc size. + explicit IpcResourceUpdateQueue(layers::WebRenderBridgeChild* aAllocator, + size_t aChunkSize = 57328); + + IpcResourceUpdateQueue(IpcResourceUpdateQueue&& aOther) noexcept; + IpcResourceUpdateQueue& operator=(IpcResourceUpdateQueue&& aOther) noexcept; + + IpcResourceUpdateQueue(const IpcResourceUpdateQueue& aOther) = delete; + IpcResourceUpdateQueue& operator=(const IpcResourceUpdateQueue& aOther) = + delete; + + // Moves over everything but the subqueues + void ReplaceResources(IpcResourceUpdateQueue&& aOther); + + bool AddImage(wr::ImageKey aKey, const ImageDescriptor& aDescriptor, + Range aBytes); + + bool AddBlobImage(wr::BlobImageKey aKey, const ImageDescriptor& aDescriptor, + Range aBytes, ImageIntRect aVisibleRect); + + void AddPrivateExternalImage(wr::ExternalImageId aExtId, wr::ImageKey aKey, + wr::ImageDescriptor aDesc); + + void AddSharedExternalImage(wr::ExternalImageId aExtId, wr::ImageKey aKey); + + void PushExternalImageForTexture(wr::ExternalImageId aExtId, + wr::ImageKey aKey, + layers::TextureClient* aTexture, + bool aIsUpdate); + + bool UpdateImageBuffer(wr::ImageKey aKey, const ImageDescriptor& aDescriptor, + Range aBytes); + + bool UpdateBlobImage(wr::BlobImageKey aKey, + const ImageDescriptor& aDescriptor, + Range aBytes, ImageIntRect aVisibleRect, + ImageIntRect aDirtyRect); + + void UpdatePrivateExternalImage(wr::ExternalImageId aExtId, wr::ImageKey aKey, + const wr::ImageDescriptor& aDesc, + ImageIntRect aDirtyRect); + void UpdateSharedExternalImage(ExternalImageId aExtID, ImageKey aKey, + ImageIntRect aDirtyRect); + + void SetBlobImageVisibleArea(BlobImageKey aKey, const ImageIntRect& aArea); + + void DeleteImage(wr::ImageKey aKey); + + void DeleteBlobImage(wr::BlobImageKey aKey); + + bool AddRawFont(wr::FontKey aKey, Range aBytes, uint32_t aIndex); + + bool AddFontDescriptor(wr::FontKey aKey, Range aBytes, + uint32_t aIndex); + + void DeleteFont(wr::FontKey aKey); + + void AddFontInstance(wr::FontInstanceKey aKey, wr::FontKey aFontKey, + float aGlyphSize, + const wr::FontInstanceOptions* aOptions, + const wr::FontInstancePlatformOptions* aPlatformOptions, + Range aVariations); + + void DeleteFontInstance(wr::FontInstanceKey aKey); + + void Clear(); + + void Flush(nsTArray& aUpdates, + nsTArray& aSmallAllocs, + nsTArray& aLargeAllocs); + + bool IsEmpty() const; + + static void ReleaseShmems(mozilla::ipc::IProtocol*, + nsTArray& aShms); + static void ReleaseShmems(mozilla::ipc::IProtocol*, + nsTArray& aShms); + + protected: + ShmSegmentsWriter mWriter; + nsTArray mUpdates; +}; + +} // namespace wr +} // namespace mozilla + +#endif diff --git a/gfx/layers/wr/OMTAController.cpp b/gfx/layers/wr/OMTAController.cpp new file mode 100644 index 0000000000..a928b2a15a --- /dev/null +++ b/gfx/layers/wr/OMTAController.cpp @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/OMTAController.h" + +#include "mozilla/layers/CompositorBridgeParent.h" +#include "mozilla/layers/CompositorThread.h" + +namespace mozilla { +namespace layers { + +void OMTAController::NotifyJankedAnimations( + JankedAnimations&& aJankedAnimations) const { + if (StaticPrefs::layout_animation_prerender_partial_jank()) { + return; + } + + if (!CompositorThread()) { + return; + } + + if (!CompositorThread()->IsOnCurrentThread()) { + CompositorThread()->Dispatch(NewRunnableMethod( + "layers::OMTAController::NotifyJankedAnimations", this, + &OMTAController::NotifyJankedAnimations, std::move(aJankedAnimations))); + return; + } + + if (CompositorBridgeParent* bridge = + CompositorBridgeParent::GetCompositorBridgeParentFromLayersId( + mRootLayersId)) { + bridge->NotifyJankedAnimations(aJankedAnimations); + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/wr/OMTAController.h b/gfx/layers/wr/OMTAController.h new file mode 100644 index 0000000000..3119650267 --- /dev/null +++ b/gfx/layers/wr/OMTAController.h @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_OMTAController_h +#define mozilla_layers_OMTAController_h + +#include + +#include "mozilla/layers/LayersTypes.h" // for LayersId +#include "nsISerialEventTarget.h" +#include "nsISupportsImpl.h" +#include "nsTArrayForwardDeclare.h" + +namespace mozilla { +namespace layers { + +/** + * This class just delegates the jank animations notification to the compositor + * thread from the sampler thread. + */ +class OMTAController final { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(OMTAController) + + public: + explicit OMTAController(LayersId aRootLayersId) + : mRootLayersId(aRootLayersId) {} + + using JankedAnimations = + std::unordered_map, LayersId::HashFn>; + void NotifyJankedAnimations(JankedAnimations&& aJankedAnimations) const; + + private: + ~OMTAController() = default; + + LayersId mRootLayersId; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_OMTAController_h diff --git a/gfx/layers/wr/OMTASampler.cpp b/gfx/layers/wr/OMTASampler.cpp new file mode 100644 index 0000000000..f2979cf97b --- /dev/null +++ b/gfx/layers/wr/OMTASampler.cpp @@ -0,0 +1,243 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/OMTASampler.h" + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/layers/CompositorAnimationStorage.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/layers/OMTAController.h" +#include "mozilla/layers/SynchronousTask.h" +#include "mozilla/layers/WebRenderBridgeParent.h" +#include "mozilla/webrender/WebRenderAPI.h" + +namespace mozilla { +namespace layers { + +StaticMutex OMTASampler::sWindowIdLock; +StaticAutoPtr>> + OMTASampler::sWindowIdMap; + +OMTASampler::OMTASampler(const RefPtr& aAnimStorage, + LayersId aRootLayersId) + : mAnimStorage(aAnimStorage), + mStorageLock("OMTASampler::mStorageLock"), + mThreadIdLock("OMTASampler::mThreadIdLock"), + mSampleTimeLock("OMTASampler::mSampleTimeLock") { + mController = new OMTAController(aRootLayersId); +} + +void OMTASampler::Destroy() { + StaticMutexAutoLock lock(sWindowIdLock); + if (mWindowId) { + MOZ_ASSERT(sWindowIdMap); + sWindowIdMap->erase(wr::AsUint64(*mWindowId)); + } +} + +void OMTASampler::SetWebRenderWindowId(const wr::WrWindowId& aWindowId) { + StaticMutexAutoLock lock(sWindowIdLock); + MOZ_ASSERT(!mWindowId); + mWindowId = Some(aWindowId); + if (!sWindowIdMap) { + sWindowIdMap = new std::unordered_map>(); + NS_DispatchToMainThread( + NS_NewRunnableFunction("OMTASampler::ClearOnShutdown", + [] { ClearOnShutdown(&sWindowIdMap); })); + } + (*sWindowIdMap)[wr::AsUint64(aWindowId)] = this; +} + +/*static*/ +void OMTASampler::SetSamplerThread(const wr::WrWindowId& aWindowId) { + if (RefPtr sampler = GetSampler(aWindowId)) { + MutexAutoLock lock(sampler->mThreadIdLock); + sampler->mSamplerThreadId = Some(PlatformThread::CurrentId()); + } +} + +/*static*/ +void OMTASampler::Sample(const wr::WrWindowId& aWindowId, + wr::Transaction* aTransaction) { + if (RefPtr sampler = GetSampler(aWindowId)) { + wr::TransactionWrapper txn(aTransaction); + sampler->Sample(txn); + } +} + +void OMTASampler::SetSampleTime(const TimeStamp& aSampleTime) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + + const bool hasAnimations = HasAnimations(); + + MutexAutoLock lock(mSampleTimeLock); + + // Reset the previous time stamp if we don't already have any running + // animations to avoid using the time which is far behind for newly + // started animations. + mPreviousSampleTime = hasAnimations ? std::move(mSampleTime) : TimeStamp(); + mSampleTime = aSampleTime; +} + +void OMTASampler::ResetPreviousSampleTime() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + MutexAutoLock lock(mSampleTimeLock); + + mPreviousSampleTime = TimeStamp(); +} + +void OMTASampler::Sample(wr::TransactionWrapper& aTxn) { + MOZ_ASSERT(IsSamplerThread()); + + TimeStamp sampleTime; + TimeStamp previousSampleTime; + { // scope lock + MutexAutoLock lock(mSampleTimeLock); + + // If mSampleTime is null we're in a startup phase where the + // WebRenderBridgeParent hasn't yet provided us with a sample time. + // If we're that early there probably aren't any OMTA animations happening + // anyway, so using Timestamp::Now() should be fine. + sampleTime = mSampleTime.IsNull() ? TimeStamp::Now() : mSampleTime; + previousSampleTime = mPreviousSampleTime; + } + + WrAnimations animations = SampleAnimations(previousSampleTime, sampleTime); + + // We do this even if the arrays are empty, because it will clear out any + // previous properties store on the WR side, which is desirable. + aTxn.UpdateDynamicProperties(animations.mOpacityArrays, + animations.mTransformArrays, + animations.mColorArrays); +} + +WrAnimations OMTASampler::SampleAnimations(const TimeStamp& aPreviousSampleTime, + const TimeStamp& aSampleTime) { + MOZ_ASSERT(IsSamplerThread()); + + MutexAutoLock lock(mStorageLock); + + mAnimStorage->SampleAnimations(mController, aPreviousSampleTime, aSampleTime); + + return mAnimStorage->CollectWebRenderAnimations(); +} + +OMTAValue OMTASampler::GetOMTAValue(const uint64_t& aId) const { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + MutexAutoLock lock(mStorageLock); + + return mAnimStorage->GetOMTAValue(aId); +} + +void OMTASampler::SampleForTesting(const Maybe& aTestingSampleTime) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + + TimeStamp sampleTime; + TimeStamp previousSampleTime; + { // scope lock + MutexAutoLock timeLock(mSampleTimeLock); + if (aTestingSampleTime) { + // If we are on testing refresh mode, use the testing time stamp for both + // of the previous sample time and the current sample time since unlike + // normal refresh mode, the testing mode animations on the compositor are + // synchronously composed, so we don't need to worry about the time gap + // between the main thread and compositor thread. + sampleTime = *aTestingSampleTime; + previousSampleTime = *aTestingSampleTime; + } else { + sampleTime = mSampleTime; + previousSampleTime = mPreviousSampleTime; + } + } + + MutexAutoLock storageLock(mStorageLock); + mAnimStorage->SampleAnimations(mController, previousSampleTime, sampleTime); +} + +void OMTASampler::SetAnimations( + uint64_t aId, const LayersId& aLayersId, + const nsTArray& aAnimations) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + MutexAutoLock lock(mStorageLock); + + mAnimStorage->SetAnimations(aId, aLayersId, aAnimations); +} + +bool OMTASampler::HasAnimations() const { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + MutexAutoLock lock(mStorageLock); + + return mAnimStorage->HasAnimations(); +} + +void OMTASampler::ClearActiveAnimations( + std::unordered_map& aActiveAnimations) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + MutexAutoLock lock(mStorageLock); + for (const auto& id : aActiveAnimations) { + mAnimStorage->ClearById(id.first); + } +} + +void OMTASampler::RemoveEpochDataPriorTo( + std::queue& aCompositorAnimationsToDelete, + std::unordered_map& aActiveAnimations, + const wr::WrEpoch& aRenderedEpoch) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + MutexAutoLock lock(mStorageLock); + + while (!aCompositorAnimationsToDelete.empty()) { + if (aRenderedEpoch < aCompositorAnimationsToDelete.front().mEpoch) { + break; + } + for (uint64_t id : aCompositorAnimationsToDelete.front().mIds) { + const auto activeAnim = aActiveAnimations.find(id); + if (activeAnim == aActiveAnimations.end()) { + NS_ERROR("Tried to delete invalid animation"); + continue; + } + // Check if animation delete request is still valid. + if (activeAnim->second <= aCompositorAnimationsToDelete.front().mEpoch) { + mAnimStorage->ClearById(id); + aActiveAnimations.erase(activeAnim); + } + } + aCompositorAnimationsToDelete.pop(); + } +} + +bool OMTASampler::IsSamplerThread() const { + MutexAutoLock lock(mThreadIdLock); + return mSamplerThreadId && PlatformThread::CurrentId() == *mSamplerThreadId; +} + +/*static*/ +already_AddRefed OMTASampler::GetSampler( + const wr::WrWindowId& aWindowId) { + RefPtr sampler; + StaticMutexAutoLock lock(sWindowIdLock); + if (sWindowIdMap) { + auto it = sWindowIdMap->find(wr::AsUint64(aWindowId)); + if (it != sWindowIdMap->end()) { + sampler = it->second; + } + } + return sampler.forget(); +} + +} // namespace layers +} // namespace mozilla + +void omta_register_sampler(mozilla::wr::WrWindowId aWindowId) { + mozilla::layers::OMTASampler::SetSamplerThread(aWindowId); +} + +void omta_sample(mozilla::wr::WrWindowId aWindowId, + mozilla::wr::Transaction* aTransaction) { + mozilla::layers::OMTASampler::Sample(aWindowId, aTransaction); +} + +void omta_deregister_sampler(mozilla::wr::WrWindowId aWindowId) {} diff --git a/gfx/layers/wr/OMTASampler.h b/gfx/layers/wr/OMTASampler.h new file mode 100644 index 0000000000..860a84acbd --- /dev/null +++ b/gfx/layers/wr/OMTASampler.h @@ -0,0 +1,151 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_OMTASampler_h +#define mozilla_layers_OMTASampler_h + +#include +#include + +#include "base/platform_thread.h" // for PlatformThreadId +#include "mozilla/layers/OMTAController.h" // for OMTAController +#include "mozilla/Maybe.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/webrender/WebRenderTypes.h" // For WrWindowId, WrEpoch, etc. + +namespace mozilla { + +class TimeStamp; + +namespace wr { +struct Transaction; +class TransactionWrapper; +} // namespace wr + +namespace layers { +class Animation; +class CompositorAnimationStorage; +class OMTAValue; +struct CompositorAnimationIdsForEpoch; +struct LayersId; +struct WrAnimations; + +/** + * This interface exposes OMTA methods related to "sampling" (i.e. calculating + * animating values) and "". All sampling methods should be called on the + * sampler thread, all some of them should be called on the compositor thread. + */ +class OMTASampler final { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(OMTASampler) + + public: + OMTASampler(const RefPtr& aAnimStorage, + LayersId aRootLayersId); + + // Whoever creates this sampler is responsible for calling Destroy() on it + // before releasing the owning refptr. + void Destroy(); + + void SetWebRenderWindowId(const wr::WrWindowId& aWindowId); + + /** + * This function is invoked from rust on the render backend thread when it + * is created. It effectively tells the OMTASampler "the current thread is + * the sampler thread for this window id" and allows OMTASampler to remember + * which thread it is. + */ + static void SetSamplerThread(const wr::WrWindowId& aWindowId); + + static void Sample(const wr::WrWindowId& aWindowId, wr::Transaction* aTxn); + + /** + * Sample all animations, called on the sampler thread. + */ + void Sample(wr::TransactionWrapper& aTxn); + + /** + * These funtions get called on the the compositor thread. + */ + void SetSampleTime(const TimeStamp& aSampleTime); + void ResetPreviousSampleTime(); + void SetAnimations(uint64_t aId, const LayersId& aLayersId, + const nsTArray& aAnimations); + bool HasAnimations() const; + + /** + * Clear AnimatedValues and Animations data, called on the compositor + * thread. + */ + void ClearActiveAnimations( + std::unordered_map& aActiveAnimations); + void RemoveEpochDataPriorTo( + std::queue& aCompositorAnimationsToDelete, + std::unordered_map& aActiveAnimations, + const wr::Epoch& aRenderedEpoch); + + // Those two methods are for testing called on the compositor thread. + OMTAValue GetOMTAValue(const uint64_t& aId) const; + /** + * There are two possibilities when this function gets called, either 1) in + * testing refesh driver mode or 2) in normal refresh driver mode. In the case + * of 2) |aTestingSampleTime| should be Nothing() so that we can use + * |mPreviousSampleTime| and |mSampleTime| for sampling animations. + */ + void SampleForTesting(const Maybe& aTestingSampleTime); + + /** + * Returns true if currently on the "sampler thread". + */ + bool IsSamplerThread() const; + + protected: + ~OMTASampler() = default; + + static already_AddRefed GetSampler( + const wr::WrWindowId& aWindowId); + + private: + WrAnimations SampleAnimations(const TimeStamp& aPreviousSampleTime, + const TimeStamp& aSampleTime); + + RefPtr mController; + // Can only be accessed or modified while holding mStorageLock. + RefPtr mAnimStorage; + mutable Mutex mStorageLock; + + // Used to manage the mapping from a WR window id to OMTASampler. These are + // only used if WebRender is enabled. Both sWindowIdMap and mWindowId should + // only be used while holding the sWindowIdLock. Note that we use a + // StaticAutoPtr wrapper on sWindowIdMap to avoid a static initializer for the + // unordered_map. This also avoids the initializer/memory allocation in cases + // where we're not using WebRender. + static StaticMutex sWindowIdLock; + static StaticAutoPtr>> + sWindowIdMap; + Maybe mWindowId; + + // Lock used to protected mSamplerThreadId + mutable Mutex mThreadIdLock; + // If WebRender is enabled, this holds the thread id of the render backend + // thread (which is the sampler thread) for the compositor associated with + // this OMTASampler instance. + Maybe mSamplerThreadId; + + Mutex mSampleTimeLock; + // Can only be accessed or modified while holding mSampleTimeLock. + TimeStamp mSampleTime; + // Same as |mSampleTime|, can only be accessed or modified while holding + // mSampleTimeLock. + // We basically use this time stamp instead of |mSampleTime| to make + // animations more in sync with other animations on the main thread. + TimeStamp mPreviousSampleTime; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_OMTASampler_h diff --git a/gfx/layers/wr/RenderRootStateManager.cpp b/gfx/layers/wr/RenderRootStateManager.cpp new file mode 100644 index 0000000000..0168725eef --- /dev/null +++ b/gfx/layers/wr/RenderRootStateManager.cpp @@ -0,0 +1,213 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/RenderRootStateManager.h" + +#include "mozilla/layers/WebRenderBridgeChild.h" +#include "mozilla/layers/WebRenderLayerManager.h" + +namespace mozilla { +namespace layers { + +// RenderRootStateManager shares its ref count with the WebRenderLayerManager +// that created it. You can think of the two classes as being one unit, except +// there are multiple RenderRootStateManagers per WebRenderLayerManager. Since +// we need to reference the WebRenderLayerManager and it needs to reference us, +// this avoids us needing to involve the cycle collector. +void RenderRootStateManager::AddRef() { mLayerManager->AddRef(); } + +void RenderRootStateManager::Release() { mLayerManager->Release(); } + +WebRenderBridgeChild* RenderRootStateManager::WrBridge() const { + return mLayerManager->WrBridge(); +} + +WebRenderCommandBuilder& RenderRootStateManager::CommandBuilder() { + return mLayerManager->CommandBuilder(); +} + +RenderRootStateManager::WebRenderUserDataRefTable* +RenderRootStateManager::GetWebRenderUserDataTable() { + return mLayerManager->GetWebRenderUserDataTable(); +} + +wr::IpcResourceUpdateQueue& RenderRootStateManager::AsyncResourceUpdates() { + MOZ_ASSERT(NS_IsMainThread()); + + if (!mAsyncResourceUpdates) { + mAsyncResourceUpdates.emplace(WrBridge()); + + RefPtr task = NewRunnableMethod( + "RenderRootStateManager::FlushAsyncResourceUpdates", this, + &RenderRootStateManager::FlushAsyncResourceUpdates); + NS_DispatchToMainThread(task.forget()); + } + + return mAsyncResourceUpdates.ref(); +} + +void RenderRootStateManager::Destroy() { + ClearAsyncAnimations(); + + if (WrBridge()) { + // Just clear ImageKeys, they are deleted during WebRenderAPI destruction. + DiscardLocalImages(); + // CompositorAnimations are cleared by WebRenderBridgeParent. + mDiscardedCompositorAnimationsIds.Clear(); + } + + mActiveCompositorAnimationIds.clear(); + + mDestroyed = true; +} + +void RenderRootStateManager::FlushAsyncResourceUpdates() { + MOZ_ASSERT(NS_IsMainThread()); + + if (!mAsyncResourceUpdates) { + return; + } + + if (!IsDestroyed() && WrBridge()) { + WrBridge()->UpdateResources(mAsyncResourceUpdates.ref()); + } + + mAsyncResourceUpdates.reset(); +} + +void RenderRootStateManager::AddImageKeyForDiscard(wr::ImageKey key) { + mImageKeysToDelete.AppendElement(key); +} + +void RenderRootStateManager::AddBlobImageKeyForDiscard(wr::BlobImageKey key) { + mBlobImageKeysToDelete.AppendElement(key); +} + +void RenderRootStateManager::DiscardImagesInTransaction( + wr::IpcResourceUpdateQueue& aResources) { + for (const auto& key : mImageKeysToDelete) { + aResources.DeleteImage(key); + } + for (const auto& key : mBlobImageKeysToDelete) { + aResources.DeleteBlobImage(key); + } + mImageKeysToDelete.Clear(); + mBlobImageKeysToDelete.Clear(); +} + +void RenderRootStateManager::DiscardLocalImages() { + // Removes images but doesn't tell the parent side about them + // This is useful in empty / failed transactions where we created + // image keys but didn't tell the parent about them yet. + mImageKeysToDelete.Clear(); + mBlobImageKeysToDelete.Clear(); +} + +void RenderRootStateManager::ClearCachedResources() { + mActiveCompositorAnimationIds.clear(); + mDiscardedCompositorAnimationsIds.Clear(); +} + +void RenderRootStateManager::AddActiveCompositorAnimationId(uint64_t aId) { + // In layers-free mode we track the active compositor animation ids on the + // client side so that we don't try to discard the same animation id multiple + // times. We could just ignore the multiple-discard on the parent side, but + // checking on the content side reduces IPC traffic. + mActiveCompositorAnimationIds.insert(aId); +} + +void RenderRootStateManager::AddCompositorAnimationsIdForDiscard(uint64_t aId) { + if (mActiveCompositorAnimationIds.erase(aId)) { + // For layers-free ensure we don't try to discard an animation id that + // wasn't active. We also remove it from mActiveCompositorAnimationIds so we + // don't discard it again unless it gets re-activated. + mDiscardedCompositorAnimationsIds.AppendElement(aId); + } +} + +void RenderRootStateManager::DiscardCompositorAnimations() { + if (WrBridge()->IPCOpen() && !mDiscardedCompositorAnimationsIds.IsEmpty()) { + WrBridge()->SendDeleteCompositorAnimations( + mDiscardedCompositorAnimationsIds); + } + mDiscardedCompositorAnimationsIds.Clear(); +} + +void RenderRootStateManager::RegisterAsyncAnimation( + const wr::ImageKey& aKey, SharedSurfacesAnimation* aAnimation) { + mAsyncAnimations.insert(std::make_pair(wr::AsUint64(aKey), aAnimation)); +} + +void RenderRootStateManager::DeregisterAsyncAnimation( + const wr::ImageKey& aKey) { + mAsyncAnimations.erase(wr::AsUint64(aKey)); +} + +void RenderRootStateManager::ClearAsyncAnimations() { + for (const auto& i : mAsyncAnimations) { + i.second->Invalidate(this); + } + mAsyncAnimations.clear(); +} + +void RenderRootStateManager::WrReleasedImages( + const nsTArray& aPairs) { + // A SharedSurfaceAnimation object's lifetime is tied to its owning + // ImageContainer. When the ImageContainer is released, + // SharedSurfaceAnimation::Destroy is called which should ensure it is removed + // from the layer manager. Whenever the namespace for the + // WebRenderLayerManager itself is invalidated (e.g. we changed windows, or + // were destroyed ourselves), we callback into the SharedSurfaceAnimation + // object to remove its image key for us and any bound surfaces. If, for any + // reason, we somehow missed an WrReleasedImages call before the animation + // was bound to the layer manager, it will free those associated surfaces on + // the next ReleasePreviousFrame call. + for (const auto& pair : aPairs) { + auto i = mAsyncAnimations.find(wr::AsUint64(pair.key)); + if (i != mAsyncAnimations.end()) { + i->second->ReleasePreviousFrame(this, pair.id); + } + } +} + +void RenderRootStateManager::AddWebRenderParentCommand( + const WebRenderParentCommand& aCmd) { + WrBridge()->AddWebRenderParentCommand(aCmd); +} +void RenderRootStateManager::UpdateResources( + wr::IpcResourceUpdateQueue& aResources) { + WrBridge()->UpdateResources(aResources); +} +void RenderRootStateManager::AddPipelineIdForAsyncCompositable( + const wr::PipelineId& aPipelineId, const CompositableHandle& aHandle) { + WrBridge()->AddPipelineIdForAsyncCompositable(aPipelineId, aHandle); +} +void RenderRootStateManager::AddPipelineIdForCompositable( + const wr::PipelineId& aPipelineId, const CompositableHandle& aHandle) { + WrBridge()->AddPipelineIdForCompositable(aPipelineId, aHandle); +} +void RenderRootStateManager::RemovePipelineIdForCompositable( + const wr::PipelineId& aPipelineId) { + WrBridge()->RemovePipelineIdForCompositable(aPipelineId); +} +/// Release TextureClient that is bounded to ImageKey. +/// It is used for recycling TextureClient. +void RenderRootStateManager::ReleaseTextureOfImage(const wr::ImageKey& aKey) { + WrBridge()->ReleaseTextureOfImage(aKey); +} + +Maybe RenderRootStateManager::GetFontKeyForScaledFont( + gfx::ScaledFont* aScaledFont, wr::IpcResourceUpdateQueue* aResources) { + return WrBridge()->GetFontKeyForScaledFont(aScaledFont, aResources); +} + +Maybe RenderRootStateManager::GetFontKeyForUnscaledFont( + gfx::UnscaledFont* aUnscaledFont, wr::IpcResourceUpdateQueue* aResources) { + return WrBridge()->GetFontKeyForUnscaledFont(aUnscaledFont, aResources); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/wr/RenderRootStateManager.h b/gfx/layers/wr/RenderRootStateManager.h new file mode 100644 index 0000000000..befe169271 --- /dev/null +++ b/gfx/layers/wr/RenderRootStateManager.h @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_RENDERROOTSTATEMANAGER_H +#define GFX_RENDERROOTSTATEMANAGER_H + +#include "mozilla/webrender/WebRenderAPI.h" + +#include "mozilla/layers/IpcResourceUpdateQueue.h" +#include "mozilla/layers/SharedSurfacesChild.h" +#include "mozilla/layers/WebRenderCommandBuilder.h" + +namespace mozilla { + +namespace layers { + +class RenderRootStateManager { + typedef nsTHashtable> + WebRenderUserDataRefTable; + + public: + void AddRef(); + void Release(); + + RenderRootStateManager() : mLayerManager(nullptr), mDestroyed(false) {} + + void Destroy(); + bool IsDestroyed() { return mDestroyed; } + wr::IpcResourceUpdateQueue& AsyncResourceUpdates(); + WebRenderBridgeChild* WrBridge() const; + WebRenderCommandBuilder& CommandBuilder(); + WebRenderUserDataRefTable* GetWebRenderUserDataTable(); + WebRenderLayerManager* LayerManager() { return mLayerManager; } + + void AddImageKeyForDiscard(wr::ImageKey key); + void AddBlobImageKeyForDiscard(wr::BlobImageKey key); + void DiscardImagesInTransaction(wr::IpcResourceUpdateQueue& aResources); + void DiscardLocalImages(); + + void ClearCachedResources(); + + // Methods to manage the compositor animation ids. Active animations are still + // going, and when they end we discard them and remove them from the active + // list. + void AddActiveCompositorAnimationId(uint64_t aId); + void AddCompositorAnimationsIdForDiscard(uint64_t aId); + void DiscardCompositorAnimations(); + + void RegisterAsyncAnimation(const wr::ImageKey& aKey, + SharedSurfacesAnimation* aAnimation); + void DeregisterAsyncAnimation(const wr::ImageKey& aKey); + void ClearAsyncAnimations(); + void WrReleasedImages(const nsTArray& aPairs); + + void AddWebRenderParentCommand(const WebRenderParentCommand& aCmd); + void UpdateResources(wr::IpcResourceUpdateQueue& aResources); + void AddPipelineIdForAsyncCompositable(const wr::PipelineId& aPipelineId, + const CompositableHandle& aHandlee); + void AddPipelineIdForCompositable(const wr::PipelineId& aPipelineId, + const CompositableHandle& aHandlee); + void RemovePipelineIdForCompositable(const wr::PipelineId& aPipelineId); + /// Release TextureClient that is bounded to ImageKey. + /// It is used for recycling TextureClient. + void ReleaseTextureOfImage(const wr::ImageKey& aKey); + Maybe GetFontKeyForScaledFont( + gfx::ScaledFont* aScaledFont, + wr::IpcResourceUpdateQueue* aResources = nullptr); + Maybe GetFontKeyForUnscaledFont( + gfx::UnscaledFont* aUnscaledFont, + wr::IpcResourceUpdateQueue* aResources = nullptr); + + void FlushAsyncResourceUpdates(); + + private: + WebRenderLayerManager* mLayerManager; + Maybe mAsyncResourceUpdates; + nsTArray mImageKeysToDelete; + nsTArray mBlobImageKeysToDelete; + std::unordered_map> + mAsyncAnimations; + + // Set of compositor animation ids for which there are active animations (as + // of the last transaction) on the compositor side. + std::unordered_set mActiveCompositorAnimationIds; + // Compositor animation ids for animations that are done now and that we want + // the compositor to discard information for. + nsTArray mDiscardedCompositorAnimationsIds; + + bool mDestroyed; + + friend class WebRenderLayerManager; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_RENDERROOTSTATEMANAGER_H */ diff --git a/gfx/layers/wr/RenderRootTypes.cpp b/gfx/layers/wr/RenderRootTypes.cpp new file mode 100644 index 0000000000..e9c964e023 --- /dev/null +++ b/gfx/layers/wr/RenderRootTypes.cpp @@ -0,0 +1,108 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "RenderRootTypes.h" +#include "mozilla/layers/WebRenderMessageUtils.h" +#include "mozilla/layers/WebRenderBridgeChild.h" + +namespace mozilla { +namespace ipc { + +void IPDLParamTraits::Write( + IPC::Message* aMsg, IProtocol* aActor, paramType&& aParam) { + WriteIPDLParam(aMsg, aActor, aParam.mIdNamespace); + WriteIPDLParam(aMsg, aActor, aParam.mRect); + WriteIPDLParam(aMsg, aActor, aParam.mCommands); + WriteIPDLParam(aMsg, aActor, std::move(aParam.mDL)); + WriteIPDLParam(aMsg, aActor, aParam.mDLDesc); + WriteIPDLParam(aMsg, aActor, aParam.mRemotePipelineIds); + WriteIPDLParam(aMsg, aActor, aParam.mResourceUpdates); + WriteIPDLParam(aMsg, aActor, aParam.mSmallShmems); + WriteIPDLParam(aMsg, aActor, std::move(aParam.mLargeShmems)); + WriteIPDLParam(aMsg, aActor, aParam.mScrollData); +} + +bool IPDLParamTraits::Read( + const IPC::Message* aMsg, PickleIterator* aIter, IProtocol* aActor, + paramType* aResult) { + if (ReadIPDLParam(aMsg, aIter, aActor, &aResult->mIdNamespace) && + ReadIPDLParam(aMsg, aIter, aActor, &aResult->mRect) && + ReadIPDLParam(aMsg, aIter, aActor, &aResult->mCommands) && + ReadIPDLParam(aMsg, aIter, aActor, &aResult->mDL) && + ReadIPDLParam(aMsg, aIter, aActor, &aResult->mDLDesc) && + ReadIPDLParam(aMsg, aIter, aActor, &aResult->mRemotePipelineIds) && + ReadIPDLParam(aMsg, aIter, aActor, &aResult->mResourceUpdates) && + ReadIPDLParam(aMsg, aIter, aActor, &aResult->mSmallShmems) && + ReadIPDLParam(aMsg, aIter, aActor, &aResult->mLargeShmems) && + ReadIPDLParam(aMsg, aIter, aActor, &aResult->mScrollData)) { + return true; + } + return false; +} + +void WriteScrollUpdates(IPC::Message* aMsg, IProtocol* aActor, + layers::ScrollUpdatesMap& aParam) { + // ICK: we need to manually serialize this map because + // nsDataHashTable doesn't support it (and other maps cause other issues) + WriteIPDLParam(aMsg, aActor, aParam.Count()); + for (auto it = aParam.Iter(); !it.Done(); it.Next()) { + WriteIPDLParam(aMsg, aActor, it.Key()); + WriteIPDLParam(aMsg, aActor, it.Data()); + } +} + +bool ReadScrollUpdates(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, layers::ScrollUpdatesMap* aResult) { + // Manually deserialize mScrollUpdates as a stream of K,V pairs + uint32_t count; + if (!ReadIPDLParam(aMsg, aIter, aActor, &count)) { + return false; + } + + layers::ScrollUpdatesMap map(count); + for (size_t i = 0; i < count; ++i) { + layers::ScrollableLayerGuid::ViewID key; + nsTArray data; + if (!ReadIPDLParam(aMsg, aIter, aActor, &key) || + !ReadIPDLParam(aMsg, aIter, aActor, &data)) { + return false; + } + map.Put(key, std::move(data)); + } + + MOZ_RELEASE_ASSERT(map.Count() == count); + *aResult = std::move(map); + return true; +} + +void IPDLParamTraits::Write( + IPC::Message* aMsg, IProtocol* aActor, paramType&& aParam) { + WriteIPDLParam(aMsg, aActor, aParam.mIdNamespace); + WriteIPDLParam(aMsg, aActor, aParam.mCommands); + WriteIPDLParam(aMsg, aActor, aParam.mResourceUpdates); + WriteIPDLParam(aMsg, aActor, aParam.mSmallShmems); + WriteIPDLParam(aMsg, aActor, std::move(aParam.mLargeShmems)); + WriteScrollUpdates(aMsg, aActor, aParam.mScrollUpdates); + WriteIPDLParam(aMsg, aActor, aParam.mPaintSequenceNumber); +} + +bool IPDLParamTraits::Read( + const IPC::Message* aMsg, PickleIterator* aIter, IProtocol* aActor, + paramType* aResult) { + if (ReadIPDLParam(aMsg, aIter, aActor, &aResult->mIdNamespace) && + ReadIPDLParam(aMsg, aIter, aActor, &aResult->mCommands) && + ReadIPDLParam(aMsg, aIter, aActor, &aResult->mResourceUpdates) && + ReadIPDLParam(aMsg, aIter, aActor, &aResult->mSmallShmems) && + ReadIPDLParam(aMsg, aIter, aActor, &aResult->mLargeShmems) && + ReadScrollUpdates(aMsg, aIter, aActor, &aResult->mScrollUpdates) && + ReadIPDLParam(aMsg, aIter, aActor, &aResult->mPaintSequenceNumber)) { + return true; + } + return false; +} + +} // namespace ipc +} // namespace mozilla diff --git a/gfx/layers/wr/RenderRootTypes.h b/gfx/layers/wr/RenderRootTypes.h new file mode 100644 index 0000000000..1f112bba92 --- /dev/null +++ b/gfx/layers/wr/RenderRootTypes.h @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_RENDERROOTTYPES_H +#define GFX_RENDERROOTTYPES_H + +#include "mozilla/webrender/WebRenderAPI.h" +#include "mozilla/webrender/WebRenderTypes.h" +#include "mozilla/layers/WebRenderMessages.h" +#include "mozilla/layers/WebRenderScrollData.h" + +namespace mozilla { + +namespace layers { + +struct DisplayListData { + wr::IdNamespace mIdNamespace; + LayoutDeviceRect mRect; + nsTArray mCommands; + Maybe mDL; + wr::BuiltDisplayListDescriptor mDLDesc; + nsTArray mRemotePipelineIds; + nsTArray mResourceUpdates; + nsTArray mSmallShmems; + nsTArray mLargeShmems; + Maybe mScrollData; +}; + +struct TransactionData { + wr::IdNamespace mIdNamespace; + nsTArray mCommands; + nsTArray mResourceUpdates; + nsTArray mSmallShmems; + nsTArray mLargeShmems; + ScrollUpdatesMap mScrollUpdates; + uint32_t mPaintSequenceNumber; +}; + +typedef Maybe MaybeTransactionData; + +} // namespace layers + +namespace ipc { + +template <> +struct IPDLParamTraits { + typedef mozilla::layers::DisplayListData paramType; + + static void Write(IPC::Message* aMsg, IProtocol* aActor, paramType&& aParam); + + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, paramType* aResult); +}; + +template <> +struct IPDLParamTraits { + typedef mozilla::layers::TransactionData paramType; + + static void Write(IPC::Message* aMsg, IProtocol* aActor, paramType&& aParam); + + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, paramType* aResult); +}; + +} // namespace ipc +} // namespace mozilla + +#endif /* GFX_RENDERROOTTYPES_H */ diff --git a/gfx/layers/wr/StackingContextHelper.cpp b/gfx/layers/wr/StackingContextHelper.cpp new file mode 100644 index 0000000000..15645d2539 --- /dev/null +++ b/gfx/layers/wr/StackingContextHelper.cpp @@ -0,0 +1,150 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/StackingContextHelper.h" + +#include "mozilla/PresShell.h" +#include "UnitTransforms.h" +#include "nsDisplayList.h" + +namespace mozilla { +namespace layers { + +StackingContextHelper::StackingContextHelper() + : mBuilder(nullptr), + mScale(1.0f, 1.0f), + mAffectsClipPositioning(false), + mRasterizeLocally(false) { + // mOrigin remains at 0,0 +} + +StackingContextHelper::StackingContextHelper( + const StackingContextHelper& aParentSC, const ActiveScrolledRoot* aAsr, + nsIFrame* aContainerFrame, nsDisplayItem* aContainerItem, + wr::DisplayListBuilder& aBuilder, const wr::StackingContextParams& aParams, + const LayoutDeviceRect& aBounds) + : mBuilder(&aBuilder), + mScale(1.0f, 1.0f), + mDeferredTransformItem(aParams.mDeferredTransformItem), + mRasterizeLocally(aParams.mRasterizeLocally || + aParentSC.mRasterizeLocally) { + mOrigin = aParentSC.mOrigin + aBounds.TopLeft(); + // Compute scale for fallback rendering. We don't try to guess a scale for 3d + // transformed items + + if (aParams.mBoundTransform) { + gfx::Matrix transform2d; + bool canDraw2D = aParams.mBoundTransform->CanDraw2D(&transform2d); + if (canDraw2D && + aParams.reference_frame_kind != wr::WrReferenceFrameKind::Perspective && + !aContainerFrame->Combines3DTransformWithAncestors()) { + mInheritedTransform = transform2d * aParentSC.mInheritedTransform; + + int32_t apd = aContainerFrame->PresContext()->AppUnitsPerDevPixel(); + nsRect r = LayoutDevicePixel::ToAppUnits(aBounds, apd); + mScale = FrameLayerBuilder::ChooseScale( + aContainerFrame, aContainerItem, r, aParentSC.mScale.width, + aParentSC.mScale.height, mInheritedTransform, + /* aCanDraw2D = */ true); + } else { + mScale = gfx::Size(1.0f, 1.0f); + mInheritedTransform = gfx::Matrix::Scaling(1.f, 1.f); + } + + if (aParams.mAnimated) { + mSnappingSurfaceTransform = + gfx::Matrix::Scaling(mScale.width, mScale.height); + } else { + mSnappingSurfaceTransform = + transform2d * aParentSC.mSnappingSurfaceTransform; + } + + } else if (aParams.reference_frame_kind == wr::WrReferenceFrameKind::Zoom && + aContainerItem && + aContainerItem->GetType() == DisplayItemType::TYPE_ASYNC_ZOOM && + aContainerItem->Frame()) { + double resolution = aContainerItem->Frame()->PresShell()->GetResolution(); + gfx::Matrix transform = gfx::Matrix::Scaling(resolution, resolution); + + mInheritedTransform = transform * aParentSC.mInheritedTransform; + mScale = resolution * aParentSC.mScale; + + MOZ_ASSERT(!aParams.mAnimated); + mSnappingSurfaceTransform = transform * aParentSC.mSnappingSurfaceTransform; + + } else { + mInheritedTransform = aParentSC.mInheritedTransform; + mScale = aParentSC.mScale; + } + + auto rasterSpace = + mRasterizeLocally + ? wr::RasterSpace::Local(std::max(mScale.width, mScale.height)) + : wr::RasterSpace::Screen(); + + MOZ_ASSERT(!aParams.clip.IsNone()); + mReferenceFrameId = mBuilder->PushStackingContext( + aParams, wr::ToLayoutRect(aBounds), rasterSpace); + + if (mReferenceFrameId) { + mSpaceAndClipChainHelper.emplace(aBuilder, mReferenceFrameId.ref()); + } + + mAffectsClipPositioning = + mReferenceFrameId.isSome() || (aBounds.TopLeft() != LayoutDevicePoint()); + + // If the parent stacking context has a deferred transform item, inherit it + // into this stacking context, as long as the ASR hasn't changed. Refer to + // the comments on StackingContextHelper::mDeferredTransformItem for an + // explanation of what goes in these fields. + if (aParentSC.mDeferredTransformItem && + aAsr == (*aParentSC.mDeferredTransformItem)->GetActiveScrolledRoot()) { + if (mDeferredTransformItem) { + // If we are deferring another transform, put the combined transform from + // all the ancestor deferred items into mDeferredAncestorTransform + mDeferredAncestorTransform = aParentSC.GetDeferredTransformMatrix(); + } else { + // We are not deferring another transform, so we can just inherit the + // parent stacking context's deferred data without any modification. + mDeferredTransformItem = aParentSC.mDeferredTransformItem; + mDeferredAncestorTransform = aParentSC.mDeferredAncestorTransform; + } + } +} + +StackingContextHelper::~StackingContextHelper() { + if (mBuilder) { + mSpaceAndClipChainHelper.reset(); + mBuilder->PopStackingContext(mReferenceFrameId.isSome()); + } +} + +const Maybe& +StackingContextHelper::GetDeferredTransformItem() const { + return mDeferredTransformItem; +} + +Maybe StackingContextHelper::GetDeferredTransformMatrix() + const { + if (mDeferredTransformItem) { + // See the comments on StackingContextHelper::mDeferredTransformItem for + // an explanation of what's stored in mDeferredTransformItem and + // mDeferredAncestorTransform. Here we need to return the combined transform + // transform from all the deferred ancestors, including + // mDeferredTransformItem. + gfx::Matrix4x4 result = + (*mDeferredTransformItem)->GetTransform().GetMatrix(); + if (mDeferredAncestorTransform) { + result = result * *mDeferredAncestorTransform; + } + return Some(result); + } else { + return Nothing(); + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/wr/StackingContextHelper.h b/gfx/layers/wr/StackingContextHelper.h new file mode 100644 index 0000000000..bd9e576197 --- /dev/null +++ b/gfx/layers/wr/StackingContextHelper.h @@ -0,0 +1,130 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_STACKINGCONTEXTHELPER_H +#define GFX_STACKINGCONTEXTHELPER_H + +#include "mozilla/Attributes.h" +#include "mozilla/gfx/MatrixFwd.h" +#include "mozilla/webrender/WebRenderAPI.h" +#include "mozilla/webrender/WebRenderTypes.h" +#include "Units.h" + +class nsDisplayTransform; + +namespace mozilla { + +struct ActiveScrolledRoot; + +namespace layers { + +/** + * This is a helper class that pushes/pops a stacking context, and manages + * some of the coordinate space transformations needed. + */ +class MOZ_RAII StackingContextHelper { + public: + StackingContextHelper(const StackingContextHelper& aParentSC, + const ActiveScrolledRoot* aAsr, + nsIFrame* aContainerFrame, + nsDisplayItem* aContainerItem, + wr::DisplayListBuilder& aBuilder, + const wr::StackingContextParams& aParams, + const LayoutDeviceRect& aBounds = LayoutDeviceRect()); + + // This version of the constructor should only be used at the root level + // of the tree, so that we have a StackingContextHelper to pass down into + // the RenderLayer traversal, but don't actually want it to push a stacking + // context on the display list builder. + StackingContextHelper(); + + // Pops the stacking context, if one was pushed during the constructor. + ~StackingContextHelper(); + + // Export the inherited scale + gfx::Size GetInheritedScale() const { return mScale; } + + const gfx::Matrix& GetInheritedTransform() const { + return mInheritedTransform; + } + + const gfx::Matrix& GetSnappingSurfaceTransform() const { + return mSnappingSurfaceTransform; + } + + const Maybe& GetDeferredTransformItem() const; + Maybe GetDeferredTransformMatrix() const; + + bool AffectsClipPositioning() const { return mAffectsClipPositioning; } + Maybe ReferenceFrameId() const { return mReferenceFrameId; } + + const LayoutDevicePoint& GetOrigin() const { return mOrigin; } + + private: + wr::DisplayListBuilder* mBuilder; + gfx::Size mScale; + gfx::Matrix mInheritedTransform; + LayoutDevicePoint mOrigin; + + // The "snapping surface" defines the space that we want to snap in. + // You can think of it as the nearest physical surface. + // Animated transforms create a new snapping surface, so that changes to their + // transform don't affect the snapping of their contents. Non-animated + // transforms do *not* create a new snapping surface, so that for example the + // existence of a non-animated identity transform does not affect snapping. + gfx::Matrix mSnappingSurfaceTransform; + bool mAffectsClipPositioning; + Maybe mReferenceFrameId; + Maybe mSpaceAndClipChainHelper; + + // The deferred transform item is used when building the WebRenderScrollData + // structure. The backstory is that APZ needs to know about transforms that + // apply to the different APZC instances. Prior to bug 1423370, we would do + // this by creating a new WebRenderLayerScrollData for each nsDisplayTransform + // item we encountered. However, this was unnecessarily expensive because it + // turned out a lot of nsDisplayTransform items didn't have new ASRs defined + // as descendants, so we'd create the WebRenderLayerScrollData and send it + // over to APZ even though the transform information was not needed in that + // case. + // + // In bug 1423370 and friends, this was optimized by "deferring" a + // nsDisplayTransform item when we encountered it during display list + // traversal. If we found a descendant of that transform item that had a + // new ASR or otherwise was "relevant to APZ", we would then pluck the + // transform matrix off the deferred item and put it on the + // WebRenderLayerScrollData instance created for that APZ-relevant descendant. + // + // One complication with this is if there are multiple nsDisplayTransform + // items in the ancestor chain for the APZ-relevant item. As we traverse the + // display list, we will defer the outermost nsDisplayTransform item, and when + // we encounter the next one we will need to merge it with the already- + // deferred one somehow. What we do in this case is have + // mDeferredTransformItem always point to the "innermost" deferred transform + // item (i.e. the closest ancestor nsDisplayTransform item of the item that + // created this StackingContextHelper). And then we use + // mDeferredAncestorTransform to store the product of all the other transforms + // that were deferred. As a result, there is an invariant here that if + // mDeferredTransformItem is Nothing(), mDeferredAncestorTransform will also + // be Nothing(). Note that we can only do this if the nsDisplayTransform items + // share the same ASR. If we are processing an nsDisplayTransform item with a + // different ASR than the previously-deferred item, we assume that the + // previously-deferred transform will get sent to APZ as part of a separate + // WebRenderLayerScrollData item, and so we don't need to bother with any + // merging. (The merging probably wouldn't even make sense because the + // coordinate spaces might be different in the face of async scrolling). This + // behaviour of forcing a WebRenderLayerScrollData item to be generated when + // the ASR changes is implemented in + // WebRenderCommandBuilder::CreateWebRenderCommandsFromDisplayList. + Maybe mDeferredTransformItem; + Maybe mDeferredAncestorTransform; + + bool mRasterizeLocally; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_STACKINGCONTEXTHELPER_H */ diff --git a/gfx/layers/wr/WebRenderBridgeChild.cpp b/gfx/layers/wr/WebRenderBridgeChild.cpp new file mode 100644 index 0000000000..61a31e6f3d --- /dev/null +++ b/gfx/layers/wr/WebRenderBridgeChild.cpp @@ -0,0 +1,615 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/WebRenderBridgeChild.h" + +#include "gfxPlatform.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/layers/CompositableClient.h" +#include "mozilla/layers/CompositorBridgeChild.h" +#include "mozilla/layers/ImageDataSerializer.h" +#include "mozilla/layers/IpcResourceUpdateQueue.h" +#include "mozilla/layers/StackingContextHelper.h" +#include "mozilla/layers/PTextureChild.h" +#include "mozilla/layers/WebRenderLayerManager.h" +#include "mozilla/webrender/WebRenderAPI.h" + +namespace mozilla { +namespace layers { + +using namespace mozilla::gfx; + +WebRenderBridgeChild::WebRenderBridgeChild(const wr::PipelineId& aPipelineId) + : mIsInTransaction(false), + mIsInClearCachedResources(false), + mIdNamespace{0}, + mResourceId(0), + mPipelineId(aPipelineId), + mManager(nullptr), + mIPCOpen(false), + mDestroyed(false), + mSentDisplayList(false), + mFontKeysDeleted(0), + mFontInstanceKeysDeleted(0) {} + +WebRenderBridgeChild::~WebRenderBridgeChild() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mDestroyed); +} + +void WebRenderBridgeChild::Destroy(bool aIsSync) { + if (!IPCOpen()) { + return; + } + + DoDestroy(); + + if (aIsSync) { + SendShutdownSync(); + } else { + SendShutdown(); + } +} + +void WebRenderBridgeChild::ActorDestroy(ActorDestroyReason why) { DoDestroy(); } + +void WebRenderBridgeChild::DoDestroy() { + if (RefCountedShm::IsValid(mResourceShm) && + RefCountedShm::Release(mResourceShm) == 0) { + RefCountedShm::Dealloc(this, mResourceShm); + mResourceShm = RefCountedShmem(); + } + + // mDestroyed is used to prevent calling Send__delete__() twice. + // When this function is called from CompositorBridgeChild::Destroy(). + // mActiveResourceTracker is not cleared here, since it is + // used by PersistentBufferProviderShared. + mDestroyed = true; + mManager = nullptr; +} + +void WebRenderBridgeChild::AddWebRenderParentCommand( + const WebRenderParentCommand& aCmd) { + mParentCommands.AppendElement(aCmd); +} + +void WebRenderBridgeChild::BeginTransaction() { + MOZ_ASSERT(!mDestroyed); + + UpdateFwdTransactionId(); + mIsInTransaction = true; +} + +void WebRenderBridgeChild::UpdateResources( + wr::IpcResourceUpdateQueue& aResources) { + if (!IPCOpen()) { + aResources.Clear(); + return; + } + + if (aResources.IsEmpty()) { + return; + } + + nsTArray resourceUpdates; + nsTArray smallShmems; + nsTArray largeShmems; + aResources.Flush(resourceUpdates, smallShmems, largeShmems); + + this->SendUpdateResources(mIdNamespace, resourceUpdates, smallShmems, + std::move(largeShmems)); +} + +bool WebRenderBridgeChild::EndTransaction( + DisplayListData&& aDisplayListData, TransactionId aTransactionId, + bool aContainsSVGGroup, const mozilla::VsyncId& aVsyncId, + const mozilla::TimeStamp& aVsyncStartTime, + const mozilla::TimeStamp& aRefreshStartTime, + const mozilla::TimeStamp& aTxnStartTime, const nsCString& aTxnURL) { + MOZ_ASSERT(!mDestroyed); + MOZ_ASSERT(mIsInTransaction); + + TimeStamp fwdTime = TimeStamp::Now(); + + aDisplayListData.mCommands = std::move(mParentCommands); + aDisplayListData.mIdNamespace = mIdNamespace; + + nsTArray payloads; + if (mManager) { + mManager->TakeCompositionPayloads(payloads); + } + + mSentDisplayList = true; + bool ret = this->SendSetDisplayList( + std::move(aDisplayListData), mDestroyedActors, GetFwdTransactionId(), + aTransactionId, aContainsSVGGroup, aVsyncId, aVsyncStartTime, + aRefreshStartTime, aTxnStartTime, aTxnURL, fwdTime, payloads); + + // With multiple render roots, we may not have sent all of our + // mParentCommands, so go ahead and go through our mParentCommands and ensure + // they get sent. + ProcessWebRenderParentCommands(); + mDestroyedActors.Clear(); + mIsInTransaction = false; + + return ret; +} + +void WebRenderBridgeChild::EndEmptyTransaction( + const FocusTarget& aFocusTarget, Maybe&& aTransactionData, + TransactionId aTransactionId, const mozilla::VsyncId& aVsyncId, + const mozilla::TimeStamp& aVsyncStartTime, + const mozilla::TimeStamp& aRefreshStartTime, + const mozilla::TimeStamp& aTxnStartTime, const nsCString& aTxnURL) { + MOZ_ASSERT(!mDestroyed); + MOZ_ASSERT(mIsInTransaction); + + TimeStamp fwdTime = TimeStamp::Now(); + + if (aTransactionData) { + aTransactionData->mCommands = std::move(mParentCommands); + } + + nsTArray payloads; + if (mManager) { + mManager->TakeCompositionPayloads(payloads); + } + + this->SendEmptyTransaction( + aFocusTarget, std::move(aTransactionData), mDestroyedActors, + GetFwdTransactionId(), aTransactionId, aVsyncId, aVsyncStartTime, + aRefreshStartTime, aTxnStartTime, aTxnURL, fwdTime, payloads); + + // With multiple render roots, we may not have sent all of our + // mParentCommands, so go ahead and go through our mParentCommands and ensure + // they get sent. + ProcessWebRenderParentCommands(); + mDestroyedActors.Clear(); + mIsInTransaction = false; +} + +void WebRenderBridgeChild::ProcessWebRenderParentCommands() { + MOZ_ASSERT(!mDestroyed); + + if (!mParentCommands.IsEmpty()) { + this->SendParentCommands(mParentCommands); + mParentCommands.Clear(); + } +} + +void WebRenderBridgeChild::AddPipelineIdForAsyncCompositable( + const wr::PipelineId& aPipelineId, const CompositableHandle& aHandle) { + AddWebRenderParentCommand( + OpAddPipelineIdForCompositable(aPipelineId, aHandle, /* isAsync */ true)); +} + +void WebRenderBridgeChild::AddPipelineIdForCompositable( + const wr::PipelineId& aPipelineId, const CompositableHandle& aHandle) { + AddWebRenderParentCommand(OpAddPipelineIdForCompositable( + aPipelineId, aHandle, /* isAsync */ false)); +} + +void WebRenderBridgeChild::RemovePipelineIdForCompositable( + const wr::PipelineId& aPipelineId) { + AddWebRenderParentCommand(OpRemovePipelineIdForCompositable(aPipelineId)); +} + +wr::ExternalImageId WebRenderBridgeChild::GetNextExternalImageId() { + wr::MaybeExternalImageId id = + GetCompositorBridgeChild()->GetNextExternalImageId(); + MOZ_RELEASE_ASSERT(id.isSome()); + return id.value(); +} + +void WebRenderBridgeChild::ReleaseTextureOfImage(const wr::ImageKey& aKey) { + AddWebRenderParentCommand(OpReleaseTextureOfImage(aKey)); +} + +struct FontFileDataSink { + wr::FontKey* mFontKey; + WebRenderBridgeChild* mWrBridge; + wr::IpcResourceUpdateQueue* mResources; +}; + +static void WriteFontFileData(const uint8_t* aData, uint32_t aLength, + uint32_t aIndex, void* aBaton) { + FontFileDataSink* sink = static_cast(aBaton); + + *sink->mFontKey = sink->mWrBridge->GetNextFontKey(); + + sink->mResources->AddRawFont( + *sink->mFontKey, Range(const_cast(aData), aLength), + aIndex); +} + +static void WriteFontDescriptor(const uint8_t* aData, uint32_t aLength, + uint32_t aIndex, void* aBaton) { + FontFileDataSink* sink = static_cast(aBaton); + + *sink->mFontKey = sink->mWrBridge->GetNextFontKey(); + + sink->mResources->AddFontDescriptor( + *sink->mFontKey, Range(const_cast(aData), aLength), + aIndex); +} + +void WebRenderBridgeChild::PushGlyphs( + wr::DisplayListBuilder& aBuilder, Range aGlyphs, + gfx::ScaledFont* aFont, const wr::ColorF& aColor, + const StackingContextHelper& aSc, const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, bool aBackfaceVisible, + const wr::GlyphOptions* aGlyphOptions) { + MOZ_ASSERT(aFont); + + Maybe key = GetFontKeyForScaledFont(aFont); + MOZ_ASSERT(key.isSome()); + + if (key.isSome()) { + aBuilder.PushText(aBounds, aClip, aBackfaceVisible, aColor, key.value(), + aGlyphs, aGlyphOptions); + } +} + +Maybe WebRenderBridgeChild::GetFontKeyForScaledFont( + gfx::ScaledFont* aScaledFont, wr::IpcResourceUpdateQueue* aResources) { + MOZ_ASSERT(!mDestroyed); + MOZ_ASSERT(aScaledFont); + MOZ_ASSERT(aScaledFont->CanSerialize()); + + wr::FontInstanceKey instanceKey = {wr::IdNamespace{0}, 0}; + if (mFontInstanceKeys.Get(aScaledFont, &instanceKey)) { + return Some(instanceKey); + } + + Maybe resources = + aResources ? Nothing() : Some(wr::IpcResourceUpdateQueue(this)); + aResources = resources.ptrOr(aResources); + + Maybe fontKey = + GetFontKeyForUnscaledFont(aScaledFont->GetUnscaledFont(), aResources); + if (fontKey.isNothing()) { + return Nothing(); + } + + instanceKey = GetNextFontInstanceKey(); + + Maybe options; + Maybe platformOptions; + std::vector variations; + aScaledFont->GetWRFontInstanceOptions(&options, &platformOptions, + &variations); + + aResources->AddFontInstance( + instanceKey, fontKey.value(), aScaledFont->GetSize(), + options.ptrOr(nullptr), platformOptions.ptrOr(nullptr), + Range(variations.data(), variations.size())); + if (resources.isSome()) { + UpdateResources(resources.ref()); + } + + mFontInstanceKeys.Put(aScaledFont, instanceKey); + + return Some(instanceKey); +} + +Maybe WebRenderBridgeChild::GetFontKeyForUnscaledFont( + gfx::UnscaledFont* aUnscaled, wr::IpcResourceUpdateQueue* aResources) { + MOZ_ASSERT(!mDestroyed); + + wr::FontKey fontKey = {wr::IdNamespace{0}, 0}; + if (!mFontKeys.Get(aUnscaled, &fontKey)) { + Maybe resources = + aResources ? Nothing() : Some(wr::IpcResourceUpdateQueue(this)); + + FontFileDataSink sink = {&fontKey, this, resources.ptrOr(aResources)}; + // First try to retrieve a descriptor for the font, as this is much cheaper + // to send over IPC than the full raw font data. If this is not possible, + // then and only then fall back to getting the raw font file data. If that + // fails, then the only thing left to do is signal failure by returning a + // null font key. + if (!aUnscaled->GetFontDescriptor(WriteFontDescriptor, &sink) && + !aUnscaled->GetFontFileData(WriteFontFileData, &sink)) { + return Nothing(); + } + + if (resources.isSome()) { + UpdateResources(resources.ref()); + } + + mFontKeys.Put(aUnscaled, fontKey); + } + + return Some(fontKey); +} + +void WebRenderBridgeChild::RemoveExpiredFontKeys( + wr::IpcResourceUpdateQueue& aResourceUpdates) { + uint32_t counter = gfx::ScaledFont::DeletionCounter(); + if (mFontInstanceKeysDeleted != counter) { + mFontInstanceKeysDeleted = counter; + for (auto iter = mFontInstanceKeys.Iter(); !iter.Done(); iter.Next()) { + if (!iter.Key()) { + aResourceUpdates.DeleteFontInstance(iter.Data()); + iter.Remove(); + } + } + } + counter = gfx::UnscaledFont::DeletionCounter(); + if (mFontKeysDeleted != counter) { + mFontKeysDeleted = counter; + for (auto iter = mFontKeys.Iter(); !iter.Done(); iter.Next()) { + if (!iter.Key()) { + aResourceUpdates.DeleteFont(iter.Data()); + iter.Remove(); + } + } + } +} + +CompositorBridgeChild* WebRenderBridgeChild::GetCompositorBridgeChild() { + if (!IPCOpen()) { + return nullptr; + } + return static_cast(Manager()); +} + +TextureForwarder* WebRenderBridgeChild::GetTextureForwarder() { + return static_cast(GetCompositorBridgeChild()); +} + +LayersIPCActor* WebRenderBridgeChild::GetLayersIPCActor() { + return static_cast(GetCompositorBridgeChild()); +} + +void WebRenderBridgeChild::SyncWithCompositor() { + if (!IPCOpen()) { + return; + } + SendSyncWithCompositor(); +} + +void WebRenderBridgeChild::Connect(CompositableClient* aCompositable, + ImageContainer* aImageContainer) { + MOZ_ASSERT(!mDestroyed); + MOZ_ASSERT(aCompositable); + + static uint64_t sNextID = 1; + uint64_t id = sNextID++; + + mCompositables.Put(id, aCompositable); + + CompositableHandle handle(id); + aCompositable->InitIPDL(handle); + SendNewCompositable(handle, aCompositable->GetTextureInfo()); +} + +void WebRenderBridgeChild::UseTiledLayerBuffer( + CompositableClient* aCompositable, + const SurfaceDescriptorTiles& aTiledDescriptor) {} + +void WebRenderBridgeChild::UpdateTextureRegion( + CompositableClient* aCompositable, + const ThebesBufferData& aThebesBufferData, + const nsIntRegion& aUpdatedRegion) {} + +bool WebRenderBridgeChild::AddOpDestroy(const OpDestroy& aOp) { + if (!mIsInTransaction) { + return false; + } + + mDestroyedActors.AppendElement(aOp); + return true; +} + +void WebRenderBridgeChild::ReleaseCompositable( + const CompositableHandle& aHandle) { + if (!IPCOpen()) { + // This can happen if the IPC connection was torn down, because, e.g. + // the GPU process died. + return; + } + if (!DestroyInTransaction(aHandle)) { + SendReleaseCompositable(aHandle); + } + mCompositables.Remove(aHandle.Value()); +} + +bool WebRenderBridgeChild::DestroyInTransaction(PTextureChild* aTexture) { + return AddOpDestroy(OpDestroy(aTexture)); +} + +bool WebRenderBridgeChild::DestroyInTransaction( + const CompositableHandle& aHandle) { + return AddOpDestroy(OpDestroy(aHandle)); +} + +void WebRenderBridgeChild::RemoveTextureFromCompositable( + CompositableClient* aCompositable, TextureClient* aTexture) { + MOZ_ASSERT(aCompositable); + MOZ_ASSERT(aTexture); + MOZ_ASSERT(aTexture->GetIPDLActor()); + MOZ_RELEASE_ASSERT(aTexture->GetIPDLActor()->GetIPCChannel() == + GetIPCChannel()); + if (!aCompositable->IsConnected() || !aTexture->GetIPDLActor()) { + // We don't have an actor anymore, don't try to use it! + return; + } + + AddWebRenderParentCommand(CompositableOperation( + aCompositable->GetIPCHandle(), + OpRemoveTexture(nullptr, aTexture->GetIPDLActor()))); +} + +void WebRenderBridgeChild::UseTextures( + CompositableClient* aCompositable, + const nsTArray& aTextures) { + MOZ_ASSERT(aCompositable); + + if (!aCompositable->IsConnected()) { + return; + } + + AutoTArray textures; + + for (auto& t : aTextures) { + MOZ_ASSERT(t.mTextureClient); + MOZ_ASSERT(t.mTextureClient->GetIPDLActor()); + MOZ_RELEASE_ASSERT(t.mTextureClient->GetIPDLActor()->GetIPCChannel() == + GetIPCChannel()); + bool readLocked = t.mTextureClient->OnForwardedToHost(); + + textures.AppendElement( + TimedTexture(nullptr, t.mTextureClient->GetIPDLActor(), t.mTimeStamp, + t.mPictureRect, t.mFrameID, t.mProducerID, readLocked)); + GetCompositorBridgeChild()->HoldUntilCompositableRefReleasedIfNecessary( + t.mTextureClient); + + auto fenceFd = t.mTextureClient->GetInternalData()->GetAcquireFence(); + if (fenceFd.IsValid()) { + AddWebRenderParentCommand(CompositableOperation( + aCompositable->GetIPCHandle(), + OpDeliverAcquireFence(nullptr, t.mTextureClient->GetIPDLActor(), + fenceFd))); + } + } + AddWebRenderParentCommand(CompositableOperation(aCompositable->GetIPCHandle(), + OpUseTexture(textures))); +} + +void WebRenderBridgeChild::UseComponentAlphaTextures( + CompositableClient* aCompositable, TextureClient* aClientOnBlack, + TextureClient* aClientOnWhite) {} + +void WebRenderBridgeChild::UpdateFwdTransactionId() { + GetCompositorBridgeChild()->UpdateFwdTransactionId(); +} + +uint64_t WebRenderBridgeChild::GetFwdTransactionId() { + return GetCompositorBridgeChild()->GetFwdTransactionId(); +} + +bool WebRenderBridgeChild::InForwarderThread() { return NS_IsMainThread(); } + +mozilla::ipc::IPCResult WebRenderBridgeChild::RecvWrUpdated( + const wr::IdNamespace& aNewIdNamespace, + const TextureFactoryIdentifier& textureFactoryIdentifier) { + if (mManager) { + mManager->WrUpdated(); + } + IdentifyTextureHost(textureFactoryIdentifier); + // Update mIdNamespace to identify obsolete keys and messages by + // WebRenderBridgeParent. Since usage of invalid keys could cause crash in + // webrender. + mIdNamespace = aNewIdNamespace; + // Just clear FontInstaceKeys/FontKeys, they are removed during WebRenderAPI + // destruction. + mFontInstanceKeys.Clear(); + mFontKeys.Clear(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeChild::RecvWrReleasedImages( + nsTArray&& aPairs) { + if (mManager) { + mManager->WrReleasedImages(aPairs); + } + return IPC_OK(); +} + +void WebRenderBridgeChild::BeginClearCachedResources() { + mSentDisplayList = false; + mIsInClearCachedResources = true; + // Clear display list and animtaions at parent side before clearing cached + // resources on client side. It prevents to clear resources before clearing + // display list at parent side. + SendClearCachedResources(); +} + +void WebRenderBridgeChild::EndClearCachedResources() { + if (!IPCOpen()) { + mIsInClearCachedResources = false; + return; + } + ProcessWebRenderParentCommands(); + mIsInClearCachedResources = false; +} + +void WebRenderBridgeChild::SetWebRenderLayerManager( + WebRenderLayerManager* aManager) { + MOZ_ASSERT(aManager && !mManager); + mManager = aManager; + + MOZ_ASSERT(NS_IsMainThread() || !XRE_IsContentProcess()); + mActiveResourceTracker = + MakeUnique(1000, "CompositableForwarder", nullptr); +} + +ipc::IShmemAllocator* WebRenderBridgeChild::GetShmemAllocator() { + if (!IPCOpen()) { + return nullptr; + } + return static_cast(Manager()); +} + +RefPtr WebRenderBridgeChild::GetForMedia() { + MOZ_ASSERT(NS_IsMainThread()); + + // Ensure devices initialization for video playback. The devices are lazily + // initialized with WebRender to reduce memory usage. + gfxPlatform::GetPlatform()->EnsureDevicesInitialized(); + + return MakeAndAddRef( + GetTextureFactoryIdentifier()); +} + +bool WebRenderBridgeChild::AllocResourceShmem(size_t aSize, + RefCountedShmem& aShm) { + // We keep a single shmem around to reuse later if it is reference count has + // dropped back to 1 (the reference held by the WebRenderBridgeChild). + + // If the cached shmem exists, has the correct size and isn't held by anything + // other than us, recycle it. + bool alreadyAllocated = RefCountedShm::IsValid(mResourceShm); + if (alreadyAllocated) { + if (RefCountedShm::GetSize(mResourceShm) == aSize && + RefCountedShm::GetReferenceCount(mResourceShm) <= 1) { + MOZ_ASSERT(RefCountedShm::GetReferenceCount(mResourceShm) == 1); + aShm = mResourceShm; + return true; + } + } + + // If there was no cached shmem or we couldn't recycle it, alloc a new one. + if (!RefCountedShm::Alloc(this, aSize, aShm)) { + return false; + } + + // Now that we have a valid shmem, put it in the cache if we don't have one + // yet. + if (!alreadyAllocated) { + mResourceShm = aShm; + RefCountedShm::AddRef(aShm); + } + + return true; +} + +void WebRenderBridgeChild::DeallocResourceShmem(RefCountedShmem& aShm) { + if (!RefCountedShm::IsValid(aShm)) { + return; + } + MOZ_ASSERT(RefCountedShm::GetReferenceCount(aShm) == 0); + + RefCountedShm::Dealloc(this, aShm); +} + +void WebRenderBridgeChild::Capture() { this->SendCapture(); } +void WebRenderBridgeChild::ToggleCaptureSequence() { + this->SendToggleCaptureSequence(); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/wr/WebRenderBridgeChild.h b/gfx/layers/wr/WebRenderBridgeChild.h new file mode 100644 index 0000000000..672cd68055 --- /dev/null +++ b/gfx/layers/wr/WebRenderBridgeChild.h @@ -0,0 +1,262 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_WebRenderBridgeChild_h +#define mozilla_layers_WebRenderBridgeChild_h + +#include "mozilla/layers/CompositableForwarder.h" +#include "mozilla/layers/PWebRenderBridgeChild.h" + +namespace mozilla { + +namespace widget { +class CompositorWidget; +} + +namespace wr { +class DisplayListBuilder; +class ResourceUpdateQueue; +class IpcResourceUpdateQueue; +} // namespace wr + +namespace layers { + +class CompositableClient; +class CompositorBridgeChild; +class StackingContextHelper; +class TextureForwarder; +class WebRenderLayerManager; + +template +class ThreadSafeWeakPtrHashKey : public PLDHashEntryHdr { + public: + typedef RefPtr KeyType; + typedef const T* KeyTypePointer; + + explicit ThreadSafeWeakPtrHashKey(KeyTypePointer aKey) + : mKey(do_AddRef(const_cast(aKey))) {} + + KeyType GetKey() const { return do_AddRef(mKey); } + bool KeyEquals(KeyTypePointer aKey) const { return mKey == aKey; } + + static KeyTypePointer KeyToPointer(const KeyType& aKey) { return aKey.get(); } + static PLDHashNumber HashKey(KeyTypePointer aKey) { + return NS_PTR_TO_UINT32(aKey) >> 2; + } + enum { ALLOW_MEMMOVE = true }; + + private: + ThreadSafeWeakPtr mKey; +}; + +typedef ThreadSafeWeakPtrHashKey UnscaledFontHashKey; +typedef ThreadSafeWeakPtrHashKey ScaledFontHashKey; + +class WebRenderBridgeChild final : public PWebRenderBridgeChild, + public CompositableForwarder { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebRenderBridgeChild, override) + + friend class PWebRenderBridgeChild; + + public: + explicit WebRenderBridgeChild(const wr::PipelineId& aPipelineId); + + void AddWebRenderParentCommand(const WebRenderParentCommand& aCmd); + bool HasWebRenderParentCommands() { return !mParentCommands.IsEmpty(); } + + void UpdateResources(wr::IpcResourceUpdateQueue& aResources); + void BeginTransaction(); + bool EndTransaction(DisplayListData&& aDisplayListData, + TransactionId aTransactionId, bool aContainsSVGroup, + const mozilla::VsyncId& aVsyncId, + const mozilla::TimeStamp& aVsyncStartTime, + const mozilla::TimeStamp& aRefreshStartTime, + const mozilla::TimeStamp& aTxnStartTime, + const nsCString& aTxtURL); + void EndEmptyTransaction(const FocusTarget& aFocusTarget, + Maybe&& aTransactionData, + TransactionId aTransactionId, + const mozilla::VsyncId& aVsyncId, + const mozilla::TimeStamp& aVsyncStartTime, + const mozilla::TimeStamp& aRefreshStartTime, + const mozilla::TimeStamp& aTxnStartTime, + const nsCString& aTxtURL); + void ProcessWebRenderParentCommands(); + + CompositorBridgeChild* GetCompositorBridgeChild(); + + wr::PipelineId GetPipeline() { return mPipelineId; } + + // KnowsCompositor + TextureForwarder* GetTextureForwarder() override; + LayersIPCActor* GetLayersIPCActor() override; + void SyncWithCompositor() override; + ActiveResourceTracker* GetActiveResourceTracker() override { + return mActiveResourceTracker.get(); + } + + void AddPipelineIdForAsyncCompositable(const wr::PipelineId& aPipelineId, + const CompositableHandle& aHandlee); + void AddPipelineIdForCompositable(const wr::PipelineId& aPipelineId, + const CompositableHandle& aHandlee); + void RemovePipelineIdForCompositable(const wr::PipelineId& aPipelineId); + + /// Release TextureClient that is bounded to ImageKey. + /// It is used for recycling TextureClient. + void ReleaseTextureOfImage(const wr::ImageKey& aKey); + + /** + * Clean this up, finishing with SendShutDown() which will cause __delete__ + * to be sent from the parent side. + */ + void Destroy(bool aIsSync); + bool IPCOpen() const { return mIPCOpen && !mDestroyed; } + bool GetSentDisplayList() const { return mSentDisplayList; } + bool IsDestroyed() const { return mDestroyed; } + + uint32_t GetNextResourceId() { return ++mResourceId; } + wr::IdNamespace GetNamespace() { return mIdNamespace; } + void SetNamespace(wr::IdNamespace aIdNamespace) { + mIdNamespace = aIdNamespace; + } + + wr::FontKey GetNextFontKey() { + return wr::FontKey{GetNamespace(), GetNextResourceId()}; + } + + wr::FontInstanceKey GetNextFontInstanceKey() { + return wr::FontInstanceKey{GetNamespace(), GetNextResourceId()}; + } + + wr::WrImageKey GetNextImageKey() { + return wr::WrImageKey{GetNamespace(), GetNextResourceId()}; + } + + void PushGlyphs(wr::DisplayListBuilder& aBuilder, + Range aGlyphs, + gfx::ScaledFont* aFont, const wr::ColorF& aColor, + const StackingContextHelper& aSc, + const wr::LayoutRect& aBounds, const wr::LayoutRect& aClip, + bool aBackfaceVisible, + const wr::GlyphOptions* aGlyphOptions = nullptr); + + Maybe GetFontKeyForScaledFont( + gfx::ScaledFont* aScaledFont, + wr::IpcResourceUpdateQueue* aResources = nullptr); + Maybe GetFontKeyForUnscaledFont( + gfx::UnscaledFont* aUnscaledFont, + wr::IpcResourceUpdateQueue* aResources = nullptr); + void RemoveExpiredFontKeys(wr::IpcResourceUpdateQueue& aResources); + + void BeginClearCachedResources(); + void EndClearCachedResources(); + + void SetWebRenderLayerManager(WebRenderLayerManager* aManager); + + mozilla::ipc::IShmemAllocator* GetShmemAllocator(); + + bool IsThreadSafe() const override { return false; } + + RefPtr GetForMedia() override; + + /// Alloc a specific type of shmem that is intended for use in + /// IpcResourceUpdateQueue only, and cache at most one of them, + /// when called multiple times. + /// + /// Do not use this for anything else. + bool AllocResourceShmem(size_t aSize, RefCountedShmem& aShm); + /// Dealloc shared memory that was allocated with AllocResourceShmem. + /// + /// Do not use this for anything else. + void DeallocResourceShmem(RefCountedShmem& aShm); + + void Capture(); + void ToggleCaptureSequence(); + + private: + friend class CompositorBridgeChild; + + ~WebRenderBridgeChild(); + + wr::ExternalImageId GetNextExternalImageId(); + + // CompositableForwarder + void Connect(CompositableClient* aCompositable, + ImageContainer* aImageContainer = nullptr) override; + void UseTiledLayerBuffer( + CompositableClient* aCompositable, + const SurfaceDescriptorTiles& aTiledDescriptor) override; + void UpdateTextureRegion(CompositableClient* aCompositable, + const ThebesBufferData& aThebesBufferData, + const nsIntRegion& aUpdatedRegion) override; + void ReleaseCompositable(const CompositableHandle& aHandle) override; + bool DestroyInTransaction(PTextureChild* aTexture) override; + bool DestroyInTransaction(const CompositableHandle& aHandle); + void RemoveTextureFromCompositable(CompositableClient* aCompositable, + TextureClient* aTexture) override; + void UseTextures(CompositableClient* aCompositable, + const nsTArray& aTextures) override; + void UseComponentAlphaTextures(CompositableClient* aCompositable, + TextureClient* aClientOnBlack, + TextureClient* aClientOnWhite) override; + void UpdateFwdTransactionId() override; + uint64_t GetFwdTransactionId() override; + bool InForwarderThread() override; + + void ActorDestroy(ActorDestroyReason why) override; + + void DoDestroy(); + + mozilla::ipc::IPCResult RecvWrUpdated( + const wr::IdNamespace& aNewIdNamespace, + const TextureFactoryIdentifier& textureFactoryIdentifier); + mozilla::ipc::IPCResult RecvWrReleasedImages( + nsTArray&& aPairs); + + void AddIPDLReference() { + MOZ_ASSERT(mIPCOpen == false); + mIPCOpen = true; + AddRef(); + } + void ReleaseIPDLReference() { + MOZ_ASSERT(mIPCOpen == true); + mIPCOpen = false; + Release(); + } + + bool AddOpDestroy(const OpDestroy& aOp); + + nsTArray mDestroyedActors; + nsTArray mParentCommands; + nsDataHashtable mCompositables; + bool mIsInTransaction; + bool mIsInClearCachedResources; + wr::IdNamespace mIdNamespace; + uint32_t mResourceId; + wr::PipelineId mPipelineId; + WebRenderLayerManager* mManager; + + bool mIPCOpen; + bool mDestroyed; + // True iff we have called SendSetDisplayList and haven't called + // SendClearCachedResources since that call. + bool mSentDisplayList; + + uint32_t mFontKeysDeleted; + nsDataHashtable mFontKeys; + + uint32_t mFontInstanceKeysDeleted; + nsDataHashtable mFontInstanceKeys; + + UniquePtr mActiveResourceTracker; + + RefCountedShmem mResourceShm; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_WebRenderBridgeChild_h diff --git a/gfx/layers/wr/WebRenderBridgeParent.cpp b/gfx/layers/wr/WebRenderBridgeParent.cpp new file mode 100644 index 0000000000..1484cc9976 --- /dev/null +++ b/gfx/layers/wr/WebRenderBridgeParent.cpp @@ -0,0 +1,2596 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/WebRenderBridgeParent.h" + +#include "CompositableHost.h" +#include "gfxEnv.h" +#include "gfxPlatform.h" +#include "gfxOTSUtils.h" +#include "GeckoProfiler.h" +#include "GLContext.h" +#include "GLContextProvider.h" +#include "GLLibraryLoader.h" +#include "Layers.h" +#include "nsExceptionHandler.h" +#include "mozilla/Range.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/layers/AnimationHelper.h" +#include "mozilla/layers/APZSampler.h" +#include "mozilla/layers/APZUpdater.h" +#include "mozilla/layers/Compositor.h" +#include "mozilla/layers/CompositorBridgeParent.h" +#include "mozilla/layers/CompositorAnimationStorage.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/layers/CompositorVsyncScheduler.h" +#include "mozilla/layers/ImageBridgeParent.h" +#include "mozilla/layers/ImageDataSerializer.h" +#include "mozilla/layers/IpcResourceUpdateQueue.h" +#include "mozilla/layers/OMTASampler.h" +#include "mozilla/layers/SharedSurfacesParent.h" +#include "mozilla/layers/TextureHost.h" +#include "mozilla/layers/AsyncImagePipelineManager.h" +#include "mozilla/layers/UiCompositorControllerParent.h" +#include "mozilla/layers/WebRenderImageHost.h" +#include "mozilla/layers/WebRenderTextureHost.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Unused.h" +#include "mozilla/webrender/RenderThread.h" +#include "mozilla/widget/CompositorWidget.h" + +#ifdef XP_WIN +# include "mozilla/widget/WinCompositorWidget.h" +#endif + +#ifdef MOZ_GECKO_PROFILER +# include "mozilla/ProfilerMarkerTypes.h" +#endif + +bool is_in_main_thread() { return NS_IsMainThread(); } + +bool is_in_compositor_thread() { + return mozilla::layers::CompositorThreadHolder::IsInCompositorThread(); +} + +bool is_in_render_thread() { + return mozilla::wr::RenderThread::IsInRenderThread(); +} + +void gecko_profiler_start_marker(const char* name) { + PROFILER_MARKER(mozilla::ProfilerString8View::WrapNullTerminatedString(name), + GRAPHICS, mozilla::MarkerTiming::IntervalStart(), Tracing, + "WebRender"); +} + +void gecko_profiler_end_marker(const char* name) { + PROFILER_MARKER(mozilla::ProfilerString8View::WrapNullTerminatedString(name), + GRAPHICS, mozilla::MarkerTiming::IntervalEnd(), Tracing, + "WebRender"); +} + +void gecko_profiler_event_marker(const char* name) { + PROFILER_MARKER(mozilla::ProfilerString8View::WrapNullTerminatedString(name), + GRAPHICS, {}, Tracing, "WebRender"); +} + +void gecko_profiler_add_text_marker(const char* name, const char* text_bytes, + size_t text_len, uint64_t microseconds) { +#ifdef MOZ_GECKO_PROFILER + if (profiler_thread_is_being_profiled()) { + auto now = mozilla::TimeStamp::NowUnfuzzed(); + auto start = now - mozilla::TimeDuration::FromMicroseconds(microseconds); + PROFILER_MARKER_TEXT( + mozilla::ProfilerString8View::WrapNullTerminatedString(name), GRAPHICS, + mozilla::MarkerTiming::Interval(start, now), + mozilla::ProfilerString8View(text_bytes, text_len)); + } +#endif +} + +bool gecko_profiler_thread_is_being_profiled() { +#ifdef MOZ_GECKO_PROFILER + return profiler_thread_is_being_profiled(); +#else + return false; +#endif +} + +bool is_glcontext_gles(void* const glcontext_ptr) { + MOZ_RELEASE_ASSERT(glcontext_ptr); + return reinterpret_cast(glcontext_ptr)->IsGLES(); +} + +bool is_glcontext_angle(void* glcontext_ptr) { + MOZ_ASSERT(glcontext_ptr); + + mozilla::gl::GLContext* glcontext = + reinterpret_cast(glcontext_ptr); + if (!glcontext) { + return false; + } + return glcontext->IsANGLE(); +} + +const char* gfx_wr_resource_path_override() { + return gfxPlatform::WebRenderResourcePathOverride(); +} + +bool gfx_wr_use_optimized_shaders() { + return mozilla::gfx::gfxVars::UseWebRenderOptimizedShaders(); +} + +void gfx_critical_note(const char* msg) { gfxCriticalNote << msg; } + +void gfx_critical_error(const char* msg) { gfxCriticalError() << msg; } + +void gecko_printf_stderr_output(const char* msg) { printf_stderr("%s\n", msg); } + +void* get_proc_address_from_glcontext(void* glcontext_ptr, + const char* procname) { + mozilla::gl::GLContext* glcontext = + reinterpret_cast(glcontext_ptr); + MOZ_ASSERT(glcontext); + if (!glcontext) { + return nullptr; + } + const auto& loader = glcontext->GetSymbolLoader(); + MOZ_ASSERT(loader); + + const auto ret = loader->GetProcAddress(procname); + return reinterpret_cast(ret); +} + +void gecko_profiler_register_thread(const char* name) { + PROFILER_REGISTER_THREAD(name); +} + +void gecko_profiler_unregister_thread() { PROFILER_UNREGISTER_THREAD(); } + +void record_telemetry_time(mozilla::wr::TelemetryProbe aProbe, + uint64_t aTimeNs) { + uint32_t time_ms = (uint32_t)(aTimeNs / 1000000); + switch (aProbe) { + case mozilla::wr::TelemetryProbe::SceneBuildTime: + mozilla::Telemetry::Accumulate(mozilla::Telemetry::WR_SCENEBUILD_TIME, + time_ms); + break; + case mozilla::wr::TelemetryProbe::SceneSwapTime: + mozilla::Telemetry::Accumulate(mozilla::Telemetry::WR_SCENESWAP_TIME, + time_ms); + break; + case mozilla::wr::TelemetryProbe::FrameBuildTime: + mozilla::Telemetry::Accumulate(mozilla::Telemetry::WR_FRAMEBUILD_TIME, + time_ms); + break; + default: + MOZ_ASSERT(false); + break; + } +} + +static CrashReporter::Annotation FromWrCrashAnnotation( + mozilla::wr::CrashAnnotation aAnnotation) { + switch (aAnnotation) { + case mozilla::wr::CrashAnnotation::CompileShader: + return CrashReporter::Annotation::GraphicsCompileShader; + default: + MOZ_ASSERT_UNREACHABLE("Unhandled annotation!"); + return CrashReporter::Annotation::Count; + } +} + +extern "C" { + +void gfx_wr_set_crash_annotation(mozilla::wr::CrashAnnotation aAnnotation, + const char* aValue) { + MOZ_ASSERT(aValue); + + auto annotation = FromWrCrashAnnotation(aAnnotation); + if (annotation == CrashReporter::Annotation::Count) { + return; + } + + CrashReporter::AnnotateCrashReport(annotation, nsDependentCString(aValue)); +} + +void gfx_wr_clear_crash_annotation(mozilla::wr::CrashAnnotation aAnnotation) { + auto annotation = FromWrCrashAnnotation(aAnnotation); + if (annotation == CrashReporter::Annotation::Count) { + return; + } + + CrashReporter::RemoveCrashReportAnnotation(annotation); +} +} + +namespace mozilla { + +namespace layers { + +using namespace mozilla::gfx; + +class ScheduleObserveLayersUpdate : public wr::NotificationHandler { + public: + ScheduleObserveLayersUpdate(RefPtr aBridge, + LayersId aLayersId, LayersObserverEpoch aEpoch, + bool aIsActive) + : mBridge(aBridge), + mLayersId(aLayersId), + mObserverEpoch(aEpoch), + mIsActive(aIsActive) {} + + void Notify(wr::Checkpoint) override { + CompositorThread()->Dispatch( + NewRunnableMethod( + "ObserveLayersUpdate", mBridge, + &CompositorBridgeParentBase::ObserveLayersUpdate, mLayersId, + mObserverEpoch, mIsActive)); + } + + protected: + RefPtr mBridge; + LayersId mLayersId; + LayersObserverEpoch mObserverEpoch; + bool mIsActive; +}; + +class SceneBuiltNotification : public wr::NotificationHandler { + public: + SceneBuiltNotification(WebRenderBridgeParent* aParent, wr::Epoch aEpoch, + TimeStamp aTxnStartTime) + : mParent(aParent), mEpoch(aEpoch), mTxnStartTime(aTxnStartTime) {} + + void Notify(wr::Checkpoint) override { + auto startTime = this->mTxnStartTime; + RefPtr parent = mParent; + wr::Epoch epoch = mEpoch; + CompositorThread()->Dispatch(NS_NewRunnableFunction( + "SceneBuiltNotificationRunnable", [parent, epoch, startTime]() { + auto endTime = TimeStamp::Now(); +#ifdef MOZ_GECKO_PROFILER + if (profiler_can_accept_markers()) { + profiler_add_marker("CONTENT_FULL_PAINT_TIME", + geckoprofiler::category::GRAPHICS, + MarkerTiming::Interval(startTime, endTime), + baseprofiler::markers::ContentBuildMarker{}); + } +#endif + Telemetry::Accumulate( + Telemetry::CONTENT_FULL_PAINT_TIME, + static_cast((endTime - startTime).ToMilliseconds())); + parent->NotifySceneBuiltForEpoch(epoch, endTime); + })); + } + + protected: + RefPtr mParent; + wr::Epoch mEpoch; + TimeStamp mTxnStartTime; +}; + +class WebRenderBridgeParent::ScheduleSharedSurfaceRelease final + : public wr::NotificationHandler { + public: + explicit ScheduleSharedSurfaceRelease(WebRenderBridgeParent* aWrBridge) + : mWrBridge(aWrBridge), mSurfaces(20) {} + + void Add(const wr::ImageKey& aKey, const wr::ExternalImageId& aId) { + mSurfaces.AppendElement(wr::ExternalImageKeyPair{aKey, aId}); + } + + void Notify(wr::Checkpoint) override { + CompositorThread()->Dispatch( + NewRunnableMethod>( + "ObserveSharedSurfaceRelease", mWrBridge, + &WebRenderBridgeParent::ObserveSharedSurfaceRelease, + std::move(mSurfaces))); + } + + private: + RefPtr mWrBridge; + nsTArray mSurfaces; +}; + +class MOZ_STACK_CLASS AutoWebRenderBridgeParentAsyncMessageSender final { + public: + explicit AutoWebRenderBridgeParentAsyncMessageSender( + WebRenderBridgeParent* aWebRenderBridgeParent, + nsTArray* aDestroyActors = nullptr) + : mWebRenderBridgeParent(aWebRenderBridgeParent), + mActorsToDestroy(aDestroyActors) { + mWebRenderBridgeParent->SetAboutToSendAsyncMessages(); + } + + ~AutoWebRenderBridgeParentAsyncMessageSender() { + mWebRenderBridgeParent->SendPendingAsyncMessages(); + if (mActorsToDestroy) { + // Destroy the actors after sending the async messages because the latter + // may contain references to some actors. + for (const auto& op : *mActorsToDestroy) { + mWebRenderBridgeParent->DestroyActor(op); + } + } + } + + private: + WebRenderBridgeParent* mWebRenderBridgeParent; + nsTArray* mActorsToDestroy; +}; + +WebRenderBridgeParent::WebRenderBridgeParent( + CompositorBridgeParentBase* aCompositorBridge, + const wr::PipelineId& aPipelineId, widget::CompositorWidget* aWidget, + CompositorVsyncScheduler* aScheduler, RefPtr&& aApi, + RefPtr&& aImageMgr, TimeDuration aVsyncRate) + : mCompositorBridge(aCompositorBridge), + mPipelineId(aPipelineId), + mWidget(aWidget), + mApi(aApi), + mAsyncImageManager(aImageMgr), + mCompositorScheduler(aScheduler), + mVsyncRate(aVsyncRate), + mChildLayersObserverEpoch{0}, + mParentLayersObserverEpoch{0}, + mWrEpoch{0}, + mIdNamespace(aApi->GetNamespace()), +#if defined(MOZ_WIDGET_ANDROID) + mScreenPixelsTarget(nullptr), +#endif + mPaused(false), + mDestroyed(false), + mReceivedDisplayList(false), + mIsFirstPaint(true), + mSkippedComposite(false), + mDisablingNativeCompositor(false), + mPendingScrollPayloads("WebRenderBridgeParent::mPendingScrollPayloads") { + MOZ_ASSERT(mAsyncImageManager); + mAsyncImageManager->AddPipeline(mPipelineId, this); + if (IsRootWebRenderBridgeParent()) { + MOZ_ASSERT(!mCompositorScheduler); + mCompositorScheduler = new CompositorVsyncScheduler(this, mWidget); + } + UpdateDebugFlags(); + UpdateQualitySettings(); + UpdateProfilerUI(); +} + +WebRenderBridgeParent::WebRenderBridgeParent(const wr::PipelineId& aPipelineId, + nsCString&& aError) + : mCompositorBridge(nullptr), + mPipelineId(aPipelineId), + mChildLayersObserverEpoch{0}, + mParentLayersObserverEpoch{0}, + mWrEpoch{0}, + mIdNamespace{0}, + mInitError(aError), + mPaused(false), + mDestroyed(true), + mReceivedDisplayList(false), + mIsFirstPaint(false), + mSkippedComposite(false), + mDisablingNativeCompositor(false), + mPendingScrollPayloads("WebRenderBridgeParent::mPendingScrollPayloads") {} + +WebRenderBridgeParent::~WebRenderBridgeParent() {} + +/* static */ +WebRenderBridgeParent* WebRenderBridgeParent::CreateDestroyed( + const wr::PipelineId& aPipelineId, nsCString&& aError) { + return new WebRenderBridgeParent(aPipelineId, std::move(aError)); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvEnsureConnected( + TextureFactoryIdentifier* aTextureFactoryIdentifier, + MaybeIdNamespace* aMaybeIdNamespace, nsCString* aError) { + if (mDestroyed) { + *aTextureFactoryIdentifier = + TextureFactoryIdentifier(LayersBackend::LAYERS_NONE); + *aMaybeIdNamespace = Nothing(); + if (mInitError.IsEmpty()) { + // Got destroyed after we initialized but before the handshake finished? + aError->AssignLiteral("FEATURE_FAILURE_WEBRENDER_INITIALIZE_RACE"); + } else { + *aError = std::move(mInitError); + } + return IPC_OK(); + } + + MOZ_ASSERT(mIdNamespace.mHandle != 0); + *aTextureFactoryIdentifier = GetTextureFactoryIdentifier(); + *aMaybeIdNamespace = Some(mIdNamespace); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvShutdown() { + return HandleShutdown(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvShutdownSync() { + return HandleShutdown(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::HandleShutdown() { + Destroy(); + IProtocol* mgr = Manager(); + if (!Send__delete__(this)) { + return IPC_FAIL_NO_REASON(mgr); + } + return IPC_OK(); +} + +void WebRenderBridgeParent::Destroy() { + if (mDestroyed) { + return; + } + mDestroyed = true; + if (mWebRenderBridgeRef) { + // Break mutual reference + mWebRenderBridgeRef->Clear(); + mWebRenderBridgeRef = nullptr; + } + ClearResources(); +} + +struct WROTSAlloc { + wr::Vec mVec; + + void* Grow(void* aPtr, size_t aLength) { + if (aLength > mVec.Capacity()) { + mVec.Reserve(aLength - mVec.Capacity()); + } + return mVec.inner.data; + } + wr::Vec ShrinkToFit(void* aPtr, size_t aLength) { + wr::Vec result(std::move(mVec)); + result.inner.length = aLength; + return result; + } + void Free(void* aPtr) {} +}; + +static bool ReadRawFont(const OpAddRawFont& aOp, wr::ShmSegmentsReader& aReader, + wr::TransactionBuilder& aUpdates) { + wr::Vec sourceBytes; + Maybe> ptr = + aReader.GetReadPointerOrCopy(aOp.bytes(), sourceBytes); + if (ptr.isNothing()) { + return false; + } + Range& source = ptr.ref(); + // Attempt to sanitize the font before passing it along for updating. + // Ensure that we're not strict here about font types, since any font that + // failed generating a descriptor might end up here as raw font data. + size_t lengthHint = gfxOTSContext::GuessSanitizedFontSize( + source.begin().get(), source.length(), false); + if (!lengthHint) { + gfxCriticalNote << "Could not determine font type for sanitizing font " + << aOp.key().mHandle; + return false; + } + gfxOTSExpandingMemoryStream output(lengthHint); + gfxOTSContext otsContext; + if (!otsContext.Process(&output, source.begin().get(), source.length())) { + gfxCriticalNote << "Failed sanitizing font " << aOp.key().mHandle; + return false; + } + wr::Vec bytes = output.forget(); + + aUpdates.AddRawFont(aOp.key(), bytes, aOp.fontIndex()); + return true; +} + +bool WebRenderBridgeParent::UpdateResources( + const nsTArray& aResourceUpdates, + const nsTArray& aSmallShmems, + const nsTArray& aLargeShmems, + wr::TransactionBuilder& aUpdates) { + wr::ShmSegmentsReader reader(aSmallShmems, aLargeShmems); + UniquePtr scheduleRelease; + + for (const auto& cmd : aResourceUpdates) { + switch (cmd.type()) { + case OpUpdateResource::TOpAddImage: { + const auto& op = cmd.get_OpAddImage(); + if (!MatchesNamespace(op.key())) { + MOZ_ASSERT_UNREACHABLE("Stale image key (add)!"); + break; + } + + wr::Vec bytes; + if (!reader.Read(op.bytes(), bytes)) { + return false; + } + aUpdates.AddImage(op.key(), op.descriptor(), bytes); + break; + } + case OpUpdateResource::TOpUpdateImage: { + const auto& op = cmd.get_OpUpdateImage(); + if (!MatchesNamespace(op.key())) { + MOZ_ASSERT_UNREACHABLE("Stale image key (update)!"); + break; + } + + wr::Vec bytes; + if (!reader.Read(op.bytes(), bytes)) { + return false; + } + aUpdates.UpdateImageBuffer(op.key(), op.descriptor(), bytes); + break; + } + case OpUpdateResource::TOpAddBlobImage: { + const auto& op = cmd.get_OpAddBlobImage(); + if (!MatchesNamespace(op.key())) { + MOZ_ASSERT_UNREACHABLE("Stale blob image key (add)!"); + break; + } + + wr::Vec bytes; + if (!reader.Read(op.bytes(), bytes)) { + return false; + } + aUpdates.AddBlobImage(op.key(), op.descriptor(), bytes, + wr::ToDeviceIntRect(op.visibleRect())); + break; + } + case OpUpdateResource::TOpUpdateBlobImage: { + const auto& op = cmd.get_OpUpdateBlobImage(); + if (!MatchesNamespace(op.key())) { + MOZ_ASSERT_UNREACHABLE("Stale blob image key (update)!"); + break; + } + + wr::Vec bytes; + if (!reader.Read(op.bytes(), bytes)) { + return false; + } + aUpdates.UpdateBlobImage(op.key(), op.descriptor(), bytes, + wr::ToDeviceIntRect(op.visibleRect()), + wr::ToLayoutIntRect(op.dirtyRect())); + break; + } + case OpUpdateResource::TOpSetBlobImageVisibleArea: { + const auto& op = cmd.get_OpSetBlobImageVisibleArea(); + if (!MatchesNamespace(op.key())) { + MOZ_ASSERT_UNREACHABLE("Stale blob image key (visible)!"); + break; + } + aUpdates.SetBlobImageVisibleArea(op.key(), + wr::ToDeviceIntRect(op.area())); + break; + } + case OpUpdateResource::TOpAddPrivateExternalImage: { + const auto& op = cmd.get_OpAddPrivateExternalImage(); + if (!AddPrivateExternalImage(op.externalImageId(), op.key(), + op.descriptor(), aUpdates)) { + return false; + } + break; + } + case OpUpdateResource::TOpAddSharedExternalImage: { + const auto& op = cmd.get_OpAddSharedExternalImage(); + if (!AddSharedExternalImage(op.externalImageId(), op.key(), aUpdates)) { + return false; + } + break; + } + case OpUpdateResource::TOpPushExternalImageForTexture: { + const auto& op = cmd.get_OpPushExternalImageForTexture(); + CompositableTextureHostRef texture; + texture = TextureHost::AsTextureHost(op.textureParent()); + if (!PushExternalImageForTexture(op.externalImageId(), op.key(), + texture, op.isUpdate(), aUpdates)) { + return false; + } + break; + } + case OpUpdateResource::TOpUpdatePrivateExternalImage: { + const auto& op = cmd.get_OpUpdatePrivateExternalImage(); + if (!UpdatePrivateExternalImage(op.externalImageId(), op.key(), + op.descriptor(), op.dirtyRect(), + aUpdates)) { + return false; + } + break; + } + case OpUpdateResource::TOpUpdateSharedExternalImage: { + const auto& op = cmd.get_OpUpdateSharedExternalImage(); + if (!UpdateSharedExternalImage(op.externalImageId(), op.key(), + op.dirtyRect(), aUpdates, + scheduleRelease)) { + return false; + } + break; + } + case OpUpdateResource::TOpAddRawFont: { + if (!ReadRawFont(cmd.get_OpAddRawFont(), reader, aUpdates)) { + return false; + } + break; + } + case OpUpdateResource::TOpAddFontDescriptor: { + const auto& op = cmd.get_OpAddFontDescriptor(); + if (!MatchesNamespace(op.key())) { + MOZ_ASSERT_UNREACHABLE("Stale font key (add descriptor)!"); + break; + } + + wr::Vec bytes; + if (!reader.Read(op.bytes(), bytes)) { + return false; + } + aUpdates.AddFontDescriptor(op.key(), bytes, op.fontIndex()); + break; + } + case OpUpdateResource::TOpAddFontInstance: { + const auto& op = cmd.get_OpAddFontInstance(); + if (!MatchesNamespace(op.instanceKey()) || + !MatchesNamespace(op.fontKey())) { + MOZ_ASSERT_UNREACHABLE("Stale font key (add instance)!"); + break; + } + + wr::Vec variations; + if (!reader.Read(op.variations(), variations)) { + return false; + } + aUpdates.AddFontInstance(op.instanceKey(), op.fontKey(), op.glyphSize(), + op.options().ptrOr(nullptr), + op.platformOptions().ptrOr(nullptr), + variations); + break; + } + case OpUpdateResource::TOpDeleteImage: { + const auto& op = cmd.get_OpDeleteImage(); + if (!MatchesNamespace(op.key())) { + // TODO(aosmond): We should also assert here, but the callers are less + // careful about checking when cleaning up their old keys. We should + // perform an audit on image key usage. + break; + } + + DeleteImage(op.key(), aUpdates); + break; + } + case OpUpdateResource::TOpDeleteBlobImage: { + const auto& op = cmd.get_OpDeleteBlobImage(); + if (!MatchesNamespace(op.key())) { + MOZ_ASSERT_UNREACHABLE("Stale blob image key (delete)!"); + break; + } + + aUpdates.DeleteBlobImage(op.key()); + break; + } + case OpUpdateResource::TOpDeleteFont: { + const auto& op = cmd.get_OpDeleteFont(); + if (!MatchesNamespace(op.key())) { + MOZ_ASSERT_UNREACHABLE("Stale font key (delete)!"); + break; + } + + aUpdates.DeleteFont(op.key()); + break; + } + case OpUpdateResource::TOpDeleteFontInstance: { + const auto& op = cmd.get_OpDeleteFontInstance(); + if (!MatchesNamespace(op.key())) { + MOZ_ASSERT_UNREACHABLE("Stale font instance key (delete)!"); + break; + } + + aUpdates.DeleteFontInstance(op.key()); + break; + } + case OpUpdateResource::T__None: + break; + } + } + + if (scheduleRelease) { + // When software WR is enabled, shared surfaces are read during rendering + // rather than copied to the texture cache. + wr::Checkpoint when = mApi->GetBackendType() == WebRenderBackend::SOFTWARE + ? wr::Checkpoint::FrameRendered + : wr::Checkpoint::FrameTexturesUpdated; + aUpdates.Notify(when, std::move(scheduleRelease)); + } + return true; +} + +bool WebRenderBridgeParent::AddPrivateExternalImage( + wr::ExternalImageId aExtId, wr::ImageKey aKey, wr::ImageDescriptor aDesc, + wr::TransactionBuilder& aResources) { + if (!MatchesNamespace(aKey)) { + MOZ_ASSERT_UNREACHABLE("Stale private external image key (add)!"); + return true; + } + + aResources.AddExternalImage(aKey, aDesc, aExtId, + wr::ExternalImageType::Buffer(), 0); + + return true; +} + +bool WebRenderBridgeParent::UpdatePrivateExternalImage( + wr::ExternalImageId aExtId, wr::ImageKey aKey, + const wr::ImageDescriptor& aDesc, const ImageIntRect& aDirtyRect, + wr::TransactionBuilder& aResources) { + if (!MatchesNamespace(aKey)) { + MOZ_ASSERT_UNREACHABLE("Stale private external image key (update)!"); + return true; + } + + aResources.UpdateExternalImageWithDirtyRect( + aKey, aDesc, aExtId, wr::ExternalImageType::Buffer(), + wr::ToDeviceIntRect(aDirtyRect), 0); + + return true; +} + +bool WebRenderBridgeParent::AddSharedExternalImage( + wr::ExternalImageId aExtId, wr::ImageKey aKey, + wr::TransactionBuilder& aResources) { + if (!MatchesNamespace(aKey)) { + MOZ_ASSERT_UNREACHABLE("Stale shared external image key (add)!"); + return true; + } + + auto key = wr::AsUint64(aKey); + auto it = mSharedSurfaceIds.find(key); + if (it != mSharedSurfaceIds.end()) { + gfxCriticalNote << "Readding known shared surface: " << key; + return false; + } + + RefPtr dSurf = SharedSurfacesParent::Acquire(aExtId); + if (!dSurf) { + gfxCriticalNote + << "DataSourceSurface of SharedSurfaces does not exist for extId:" + << wr::AsUint64(aExtId); + return false; + } + + mSharedSurfaceIds.insert(std::make_pair(key, aExtId)); + + auto imageType = + mApi->GetBackendType() == WebRenderBackend::SOFTWARE + ? wr::ExternalImageType::TextureHandle(wr::ImageBufferKind::Texture2D) + : wr::ExternalImageType::Buffer(); + wr::ImageDescriptor descriptor(dSurf->GetSize(), dSurf->Stride(), + dSurf->GetFormat()); + aResources.AddExternalImage(aKey, descriptor, aExtId, imageType, 0); + return true; +} + +bool WebRenderBridgeParent::PushExternalImageForTexture( + wr::ExternalImageId aExtId, wr::ImageKey aKey, TextureHost* aTexture, + bool aIsUpdate, wr::TransactionBuilder& aResources) { + if (!MatchesNamespace(aKey)) { + MOZ_ASSERT_UNREACHABLE("Stale texture external image key!"); + return true; + } + + if (!aTexture) { + gfxCriticalNote << "TextureHost does not exist for extId:" + << wr::AsUint64(aExtId); + return false; + } + + auto op = aIsUpdate ? TextureHost::UPDATE_IMAGE : TextureHost::ADD_IMAGE; + WebRenderTextureHost* wrTexture = aTexture->AsWebRenderTextureHost(); + if (wrTexture) { + Range keys(&aKey, 1); + wrTexture->PushResourceUpdates(aResources, op, keys, + wrTexture->GetExternalImageKey()); + auto it = mTextureHosts.find(wr::AsUint64(aKey)); + MOZ_ASSERT((it == mTextureHosts.end() && !aIsUpdate) || + (it != mTextureHosts.end() && aIsUpdate)); + if (it != mTextureHosts.end()) { + // Release Texture if it exists. + ReleaseTextureOfImage(aKey); + } + mTextureHosts.emplace(wr::AsUint64(aKey), + CompositableTextureHostRef(aTexture)); + return true; + } + + RefPtr dSurf = aTexture->GetAsSurface(); + if (!dSurf) { + gfxCriticalNote + << "TextureHost does not return DataSourceSurface for extId:" + << wr::AsUint64(aExtId); + return false; + } + + DataSourceSurface::MappedSurface map; + if (!dSurf->Map(gfx::DataSourceSurface::MapType::READ, &map)) { + gfxCriticalNote << "DataSourceSurface failed to map for Image for extId:" + << wr::AsUint64(aExtId); + return false; + } + + IntSize size = dSurf->GetSize(); + wr::ImageDescriptor descriptor(size, map.mStride, dSurf->GetFormat()); + wr::Vec data; + data.PushBytes(Range(map.mData, size.height * map.mStride)); + + if (op == TextureHost::UPDATE_IMAGE) { + aResources.UpdateImageBuffer(aKey, descriptor, data); + } else { + aResources.AddImage(aKey, descriptor, data); + } + + dSurf->Unmap(); + + return true; +} + +bool WebRenderBridgeParent::UpdateSharedExternalImage( + wr::ExternalImageId aExtId, wr::ImageKey aKey, + const ImageIntRect& aDirtyRect, wr::TransactionBuilder& aResources, + UniquePtr& aScheduleRelease) { + if (!MatchesNamespace(aKey)) { + MOZ_ASSERT_UNREACHABLE("Stale shared external image key (update)!"); + return true; + } + + auto key = wr::AsUint64(aKey); + auto it = mSharedSurfaceIds.find(key); + if (it == mSharedSurfaceIds.end()) { + gfxCriticalNote << "Updating unknown shared surface: " << key; + return false; + } + + RefPtr dSurf; + if (it->second == aExtId) { + dSurf = SharedSurfacesParent::Get(aExtId); + } else { + dSurf = SharedSurfacesParent::Acquire(aExtId); + } + + if (!dSurf) { + gfxCriticalNote << "Shared surface does not exist for extId:" + << wr::AsUint64(aExtId); + return false; + } + + if (!(it->second == aExtId)) { + // We already have a mapping for this image key, so ensure we release the + // previous external image ID. This can happen when an image is animated, + // and it is changing the external image that the animation points to. + if (!aScheduleRelease) { + aScheduleRelease = MakeUnique(this); + } + aScheduleRelease->Add(aKey, it->second); + it->second = aExtId; + } + + auto imageType = + mApi->GetBackendType() == WebRenderBackend::SOFTWARE + ? wr::ExternalImageType::TextureHandle(wr::ImageBufferKind::Texture2D) + : wr::ExternalImageType::Buffer(); + wr::ImageDescriptor descriptor(dSurf->GetSize(), dSurf->Stride(), + dSurf->GetFormat()); + aResources.UpdateExternalImageWithDirtyRect( + aKey, descriptor, aExtId, imageType, wr::ToDeviceIntRect(aDirtyRect), 0); + + return true; +} + +void WebRenderBridgeParent::ObserveSharedSurfaceRelease( + const nsTArray& aPairs) { + if (!mDestroyed) { + Unused << SendWrReleasedImages(aPairs); + } + for (const auto& pair : aPairs) { + SharedSurfacesParent::Release(pair.id); + } +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvUpdateResources( + const wr::IdNamespace& aIdNamespace, + nsTArray&& aResourceUpdates, + nsTArray&& aSmallShmems, + nsTArray&& aLargeShmems) { + if (mDestroyed || aIdNamespace != mIdNamespace) { + wr::IpcResourceUpdateQueue::ReleaseShmems(this, aSmallShmems); + wr::IpcResourceUpdateQueue::ReleaseShmems(this, aLargeShmems); + return IPC_OK(); + } + + wr::TransactionBuilder txn; + txn.SetLowPriority(!IsRootWebRenderBridgeParent()); + + Unused << GetNextWrEpoch(); + + bool success = + UpdateResources(aResourceUpdates, aSmallShmems, aLargeShmems, txn); + wr::IpcResourceUpdateQueue::ReleaseShmems(this, aSmallShmems); + wr::IpcResourceUpdateQueue::ReleaseShmems(this, aLargeShmems); + + // Even when txn.IsResourceUpdatesEmpty() is true, there could be resource + // updates. It is handled by WebRenderTextureHostWrapper. In this case + // txn.IsRenderedFrameInvalidated() becomes true. + if (!txn.IsResourceUpdatesEmpty() || txn.IsRenderedFrameInvalidated()) { + // There are resource updates, then we update Epoch of transaction. + txn.UpdateEpoch(mPipelineId, mWrEpoch); + mAsyncImageManager->SetWillGenerateFrame(); + ScheduleGenerateFrame(); + } else { + // If TransactionBuilder does not have resource updates nor display list, + // ScheduleGenerateFrame is not triggered via SceneBuilder and there is no + // need to update WrEpoch. + // Then we want to rollback WrEpoch. See Bug 1490117. + RollbackWrEpoch(); + } + + if (!success) { + return IPC_FAIL(this, "Invalid WebRender resource data shmem or address."); + } + + mApi->SendTransaction(txn); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvDeleteCompositorAnimations( + nsTArray&& aIds) { + if (mDestroyed) { + return IPC_OK(); + } + + // Once mWrEpoch has been rendered, we can delete these compositor animations + mCompositorAnimationsToDelete.push( + CompositorAnimationIdsForEpoch(mWrEpoch, std::move(aIds))); + return IPC_OK(); +} + +void WebRenderBridgeParent::RemoveEpochDataPriorTo( + const wr::Epoch& aRenderedEpoch) { + if (RefPtr sampler = GetOMTASampler()) { + sampler->RemoveEpochDataPriorTo(mCompositorAnimationsToDelete, + mActiveAnimations, aRenderedEpoch); + } +} + +bool WebRenderBridgeParent::IsRootWebRenderBridgeParent() const { + return !!mWidget; +} + +void WebRenderBridgeParent::BeginRecording(const TimeStamp& aRecordingStart) { + mApi->BeginRecording(aRecordingStart, mPipelineId); +} + +RefPtr +WebRenderBridgeParent::WriteCollectedFrames() { + return mApi->WriteCollectedFrames(); +} + +RefPtr +WebRenderBridgeParent::GetCollectedFrames() { + return mApi->GetCollectedFrames(); +} + +void WebRenderBridgeParent::AddPendingScrollPayload( + CompositionPayload& aPayload, const VsyncId& aCompositeStartId) { + auto pendingScrollPayloads = mPendingScrollPayloads.Lock(); + nsTArray* payloads = + pendingScrollPayloads->LookupOrAdd(aCompositeStartId.mId); + + payloads->AppendElement(aPayload); +} + +nsTArray WebRenderBridgeParent::TakePendingScrollPayload( + const VsyncId& aCompositeStartId) { + auto pendingScrollPayloads = mPendingScrollPayloads.Lock(); + nsTArray payload; + if (nsTArray* storedPayload = + pendingScrollPayloads->Get(aCompositeStartId.mId)) { + payload.AppendElements(std::move(*storedPayload)); + pendingScrollPayloads->Remove(aCompositeStartId.mId); + } + return payload; +} + +CompositorBridgeParent* WebRenderBridgeParent::GetRootCompositorBridgeParent() + const { + if (!mCompositorBridge) { + return nullptr; + } + + if (IsRootWebRenderBridgeParent()) { + // This WebRenderBridgeParent is attached to the root + // CompositorBridgeParent. + return static_cast(mCompositorBridge); + } + + // Otherwise, this WebRenderBridgeParent is attached to a + // ContentCompositorBridgeParent so we have an extra level of + // indirection to unravel. + CompositorBridgeParent::LayerTreeState* lts = + CompositorBridgeParent::GetIndirectShadowTree(GetLayersId()); + if (!lts) { + return nullptr; + } + return lts->mParent; +} + +RefPtr +WebRenderBridgeParent::GetRootWebRenderBridgeParent() const { + CompositorBridgeParent* cbp = GetRootCompositorBridgeParent(); + if (!cbp) { + return nullptr; + } + + return cbp->GetWebRenderBridgeParent(); +} + +void WebRenderBridgeParent::UpdateAPZFocusState(const FocusTarget& aFocus) { + CompositorBridgeParent* cbp = GetRootCompositorBridgeParent(); + if (!cbp) { + return; + } + LayersId rootLayersId = cbp->RootLayerTreeId(); + if (RefPtr apz = cbp->GetAPZUpdater()) { + apz->UpdateFocusState(rootLayersId, GetLayersId(), aFocus); + } +} + +void WebRenderBridgeParent::UpdateAPZScrollData(const wr::Epoch& aEpoch, + WebRenderScrollData&& aData) { + CompositorBridgeParent* cbp = GetRootCompositorBridgeParent(); + if (!cbp) { + return; + } + LayersId rootLayersId = cbp->RootLayerTreeId(); + if (RefPtr apz = cbp->GetAPZUpdater()) { + apz->UpdateScrollDataAndTreeState(rootLayersId, GetLayersId(), aEpoch, + std::move(aData)); + } +} + +void WebRenderBridgeParent::UpdateAPZScrollOffsets( + ScrollUpdatesMap&& aUpdates, uint32_t aPaintSequenceNumber) { + CompositorBridgeParent* cbp = GetRootCompositorBridgeParent(); + if (!cbp) { + return; + } + LayersId rootLayersId = cbp->RootLayerTreeId(); + if (RefPtr apz = cbp->GetAPZUpdater()) { + apz->UpdateScrollOffsets(rootLayersId, GetLayersId(), std::move(aUpdates), + aPaintSequenceNumber); + } +} + +void WebRenderBridgeParent::SetAPZSampleTime() { + CompositorBridgeParent* cbp = GetRootCompositorBridgeParent(); + if (!cbp) { + return; + } + if (RefPtr apz = cbp->GetAPZSampler()) { + SampleTime animationTime; + if (Maybe testTime = cbp->GetTestingTimeStamp()) { + animationTime = SampleTime::FromTest(*testTime); + } else { + animationTime = mCompositorScheduler->GetLastComposeTime(); + } + TimeDuration frameInterval = cbp->GetVsyncInterval(); + // As with the non-webrender codepath in AsyncCompositionManager, we want to + // use the timestamp for the next vsync when advancing animations. + if (frameInterval != TimeDuration::Forever()) { + animationTime = animationTime + frameInterval; + } + apz->SetSampleTime(animationTime); + } +} + +bool WebRenderBridgeParent::SetDisplayList( + const LayoutDeviceRect& aRect, ipc::ByteBuf&& aDL, + const wr::BuiltDisplayListDescriptor& aDLDesc, + const nsTArray& aResourceUpdates, + const nsTArray& aSmallShmems, + const nsTArray& aLargeShmems, const TimeStamp& aTxnStartTime, + wr::TransactionBuilder& aTxn, wr::Epoch aWrEpoch, + bool aObserveLayersUpdate) { + if (NS_WARN_IF(!UpdateResources(aResourceUpdates, aSmallShmems, aLargeShmems, + aTxn))) { + return false; + } + + wr::Vec dlData(std::move(aDL)); + + if (IsRootWebRenderBridgeParent()) { + LayoutDeviceIntSize widgetSize = mWidget->GetClientSize(); + LayoutDeviceIntRect rect = + LayoutDeviceIntRect(LayoutDeviceIntPoint(), widgetSize); + aTxn.SetDocumentView(rect); + } + gfx::DeviceColor clearColor(0.f, 0.f, 0.f, 0.f); + aTxn.SetDisplayList(clearColor, aWrEpoch, + wr::ToLayoutSize(RoundedToInt(aRect).Size()), mPipelineId, + aDLDesc, dlData); + + if (aObserveLayersUpdate) { + aTxn.Notify( + wr::Checkpoint::SceneBuilt, + MakeUnique( + mCompositorBridge, GetLayersId(), mChildLayersObserverEpoch, true)); + } + + if (!IsRootWebRenderBridgeParent()) { + aTxn.Notify(wr::Checkpoint::SceneBuilt, MakeUnique( + this, aWrEpoch, aTxnStartTime)); + } + + mApi->SendTransaction(aTxn); + + // We will schedule generating a frame after the scene + // build is done, so we don't need to do it here. + return true; +} + +bool WebRenderBridgeParent::ProcessDisplayListData( + DisplayListData& aDisplayList, wr::Epoch aWrEpoch, + const TimeStamp& aTxnStartTime, bool aValidTransaction, + bool aObserveLayersUpdate) { + wr::TransactionBuilder txn; + Maybe sender; + + // Note that this needs to happen before the display list transaction is + // sent to WebRender, so that the UpdateHitTestingTree call is guaranteed to + // be in the updater queue at the time that the scene swap completes. + if (aDisplayList.mScrollData) { + UpdateAPZScrollData(aWrEpoch, std::move(aDisplayList.mScrollData.ref())); + } + + txn.SetLowPriority(!IsRootWebRenderBridgeParent()); + if (aValidTransaction) { + MOZ_ASSERT(aDisplayList.mIdNamespace == mIdNamespace); + sender.emplace(mApi, &txn); + } + + if (NS_WARN_IF( + !ProcessWebRenderParentCommands(aDisplayList.mCommands, txn))) { + return false; + } + + if (aDisplayList.mDL && aValidTransaction && + !SetDisplayList(aDisplayList.mRect, std::move(aDisplayList.mDL.ref()), + aDisplayList.mDLDesc, aDisplayList.mResourceUpdates, + aDisplayList.mSmallShmems, aDisplayList.mLargeShmems, + aTxnStartTime, txn, aWrEpoch, aObserveLayersUpdate)) { + return false; + } + return true; +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvSetDisplayList( + DisplayListData&& aDisplayList, nsTArray&& aToDestroy, + const uint64_t& aFwdTransactionId, const TransactionId& aTransactionId, + const bool& aContainsSVGGroup, const VsyncId& aVsyncId, + const TimeStamp& aVsyncStartTime, const TimeStamp& aRefreshStartTime, + const TimeStamp& aTxnStartTime, const nsCString& aTxnURL, + const TimeStamp& aFwdTime, nsTArray&& aPayloads) { + if (mDestroyed) { + for (const auto& op : aToDestroy) { + DestroyActor(op); + } + return IPC_OK(); + } + + if (!IsRootWebRenderBridgeParent()) { + CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::URL, aTxnURL); + } + + AUTO_PROFILER_TRACING_MARKER("Paint", "SetDisplayList", GRAPHICS); + UpdateFwdTransactionId(aFwdTransactionId); + + // This ensures that destroy operations are always processed. It is not safe + // to early-return from RecvDPEnd without doing so. + AutoWebRenderBridgeParentAsyncMessageSender autoAsyncMessageSender( + this, &aToDestroy); + + wr::Epoch wrEpoch = GetNextWrEpoch(); + + mReceivedDisplayList = true; + + if (aDisplayList.mScrollData && aDisplayList.mScrollData->IsFirstPaint()) { + mIsFirstPaint = true; + } + + bool validTransaction = aDisplayList.mIdNamespace == mIdNamespace; + bool observeLayersUpdate = ShouldParentObserveEpoch(); + + if (!ProcessDisplayListData(aDisplayList, wrEpoch, aTxnStartTime, + validTransaction, observeLayersUpdate)) { + wr::IpcResourceUpdateQueue::ReleaseShmems(this, aDisplayList.mSmallShmems); + wr::IpcResourceUpdateQueue::ReleaseShmems(this, aDisplayList.mLargeShmems); + return IPC_FAIL(this, "Failed to process DisplayListData."); + } + + if (!validTransaction && observeLayersUpdate) { + mCompositorBridge->ObserveLayersUpdate(GetLayersId(), + mChildLayersObserverEpoch, true); + } + + if (!IsRootWebRenderBridgeParent()) { + aPayloads.AppendElement( + CompositionPayload{CompositionPayloadType::eContentPaint, aFwdTime}); + } + + HoldPendingTransactionId(wrEpoch, aTransactionId, aContainsSVGGroup, aVsyncId, + aVsyncStartTime, aRefreshStartTime, aTxnStartTime, + aTxnURL, aFwdTime, mIsFirstPaint, + std::move(aPayloads)); + mIsFirstPaint = false; + + if (!validTransaction) { + // Pretend we composited since someone is wating for this event, + // though DisplayList was not pushed to webrender. + if (CompositorBridgeParent* cbp = GetRootCompositorBridgeParent()) { + TimeStamp now = TimeStamp::Now(); + cbp->NotifyPipelineRendered(mPipelineId, wrEpoch, VsyncId(), now, now, + now); + } + } + + wr::IpcResourceUpdateQueue::ReleaseShmems(this, aDisplayList.mSmallShmems); + wr::IpcResourceUpdateQueue::ReleaseShmems(this, aDisplayList.mLargeShmems); + + return IPC_OK(); +} + +bool WebRenderBridgeParent::ProcessEmptyTransactionUpdates( + TransactionData& aData, bool* aScheduleComposite) { + *aScheduleComposite = false; + wr::TransactionBuilder txn; + txn.SetLowPriority(!IsRootWebRenderBridgeParent()); + + if (!aData.mScrollUpdates.IsEmpty()) { + UpdateAPZScrollOffsets(std::move(aData.mScrollUpdates), + aData.mPaintSequenceNumber); + } + + // Update WrEpoch for UpdateResources() and ProcessWebRenderParentCommands(). + // WrEpoch is used to manage ExternalImages lifetimes in + // AsyncImagePipelineManager. + Unused << GetNextWrEpoch(); + + if (aData.mIdNamespace == mIdNamespace && + !UpdateResources(aData.mResourceUpdates, aData.mSmallShmems, + aData.mLargeShmems, txn)) { + return false; + } + + if (!aData.mCommands.IsEmpty()) { + if (!ProcessWebRenderParentCommands(aData.mCommands, txn)) { + return false; + } + } + + if (ShouldParentObserveEpoch()) { + txn.Notify( + wr::Checkpoint::SceneBuilt, + MakeUnique( + mCompositorBridge, GetLayersId(), mChildLayersObserverEpoch, true)); + } + + // Even when txn.IsResourceUpdatesEmpty() is true, there could be resource + // updates. It is handled by WebRenderTextureHostWrapper. In this case + // txn.IsRenderedFrameInvalidated() becomes true. + if (!txn.IsResourceUpdatesEmpty() || txn.IsRenderedFrameInvalidated()) { + // There are resource updates, then we update Epoch of transaction. + txn.UpdateEpoch(mPipelineId, mWrEpoch); + *aScheduleComposite = true; + } else { + // If TransactionBuilder does not have resource updates nor display list, + // ScheduleGenerateFrame is not triggered via SceneBuilder and there is no + // need to update WrEpoch. + // Then we want to rollback WrEpoch. See Bug 1490117. + RollbackWrEpoch(); + } + + if (!txn.IsEmpty()) { + mApi->SendTransaction(txn); + } + + if (*aScheduleComposite) { + mAsyncImageManager->SetWillGenerateFrame(); + } + + return true; +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvEmptyTransaction( + const FocusTarget& aFocusTarget, Maybe&& aTransactionData, + nsTArray&& aToDestroy, const uint64_t& aFwdTransactionId, + const TransactionId& aTransactionId, const VsyncId& aVsyncId, + const TimeStamp& aVsyncStartTime, const TimeStamp& aRefreshStartTime, + const TimeStamp& aTxnStartTime, const nsCString& aTxnURL, + const TimeStamp& aFwdTime, nsTArray&& aPayloads) { + if (mDestroyed) { + for (const auto& op : aToDestroy) { + DestroyActor(op); + } + return IPC_OK(); + } + + if (!IsRootWebRenderBridgeParent()) { + CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::URL, aTxnURL); + } + + AUTO_PROFILER_TRACING_MARKER("Paint", "EmptyTransaction", GRAPHICS); + UpdateFwdTransactionId(aFwdTransactionId); + + // This ensures that destroy operations are always processed. It is not safe + // to early-return without doing so. + AutoWebRenderBridgeParentAsyncMessageSender autoAsyncMessageSender( + this, &aToDestroy); + + UpdateAPZFocusState(aFocusTarget); + + bool scheduleAnyComposite = false; + + if (aTransactionData) { + bool scheduleComposite = false; + if (!ProcessEmptyTransactionUpdates(*aTransactionData, + &scheduleComposite)) { + wr::IpcResourceUpdateQueue::ReleaseShmems(this, + aTransactionData->mSmallShmems); + wr::IpcResourceUpdateQueue::ReleaseShmems(this, + aTransactionData->mLargeShmems); + return IPC_FAIL(this, "Failed to process empty transaction update."); + } + scheduleAnyComposite = scheduleAnyComposite || scheduleComposite; + } + + // If we are going to kick off a new composite as a result of this + // transaction, or if there are already composite-triggering pending + // transactions inflight, then set sendDidComposite to false because we will + // send the DidComposite message after the composite occurs. + // If there are no pending transactions and we're not going to do a + // composite, then we leave sendDidComposite as true so we just send + // the DidComposite notification now. + bool sendDidComposite = + !scheduleAnyComposite && mPendingTransactionIds.empty(); + + // Only register a value for CONTENT_FRAME_TIME telemetry if we actually drew + // something. It is for consistency with disabling WebRender. + HoldPendingTransactionId(mWrEpoch, aTransactionId, false, aVsyncId, + aVsyncStartTime, aRefreshStartTime, aTxnStartTime, + aTxnURL, aFwdTime, + /* aIsFirstPaint */ false, std::move(aPayloads), + /* aUseForTelemetry */ scheduleAnyComposite); + + if (scheduleAnyComposite) { + ScheduleGenerateFrame(); + } else if (sendDidComposite) { + // The only thing in the pending transaction id queue should be the entry + // we just added, and now we're going to pretend we rendered it + MOZ_ASSERT(mPendingTransactionIds.size() == 1); + if (CompositorBridgeParent* cbp = GetRootCompositorBridgeParent()) { + TimeStamp now = TimeStamp::Now(); + cbp->NotifyPipelineRendered(mPipelineId, mWrEpoch, VsyncId(), now, now, + now); + } + } + + if (aTransactionData) { + wr::IpcResourceUpdateQueue::ReleaseShmems(this, + aTransactionData->mSmallShmems); + wr::IpcResourceUpdateQueue::ReleaseShmems(this, + aTransactionData->mLargeShmems); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvSetFocusTarget( + const FocusTarget& aFocusTarget) { + UpdateAPZFocusState(aFocusTarget); + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvParentCommands( + nsTArray&& aCommands) { + if (mDestroyed) { + return IPC_OK(); + } + + wr::TransactionBuilder txn; + txn.SetLowPriority(!IsRootWebRenderBridgeParent()); + if (!ProcessWebRenderParentCommands(aCommands, txn)) { + return IPC_FAIL(this, "Invalid parent command found"); + } + + mApi->SendTransaction(txn); + return IPC_OK(); +} + +bool WebRenderBridgeParent::ProcessWebRenderParentCommands( + const nsTArray& aCommands, + wr::TransactionBuilder& aTxn) { + // Transaction for async image pipeline that uses ImageBridge always need to + // be non low priority. + wr::TransactionBuilder txnForImageBridge; + wr::AutoTransactionSender sender(mApi, &txnForImageBridge); + + for (nsTArray::index_type i = 0; + i < aCommands.Length(); ++i) { + const WebRenderParentCommand& cmd = aCommands[i]; + switch (cmd.type()) { + case WebRenderParentCommand::TOpAddPipelineIdForCompositable: { + const OpAddPipelineIdForCompositable& op = + cmd.get_OpAddPipelineIdForCompositable(); + AddPipelineIdForCompositable(op.pipelineId(), op.handle(), op.isAsync(), + aTxn, txnForImageBridge); + break; + } + case WebRenderParentCommand::TOpRemovePipelineIdForCompositable: { + const OpRemovePipelineIdForCompositable& op = + cmd.get_OpRemovePipelineIdForCompositable(); + RemovePipelineIdForCompositable(op.pipelineId(), aTxn); + break; + } + case WebRenderParentCommand::TOpReleaseTextureOfImage: { + const OpReleaseTextureOfImage& op = cmd.get_OpReleaseTextureOfImage(); + ReleaseTextureOfImage(op.key()); + break; + } + case WebRenderParentCommand::TOpUpdateAsyncImagePipeline: { + const OpUpdateAsyncImagePipeline& op = + cmd.get_OpUpdateAsyncImagePipeline(); + mAsyncImageManager->UpdateAsyncImagePipeline( + op.pipelineId(), op.scBounds(), op.rotation(), op.filter(), + op.mixBlendMode()); + mAsyncImageManager->ApplyAsyncImageForPipeline(op.pipelineId(), aTxn, + txnForImageBridge); + break; + } + case WebRenderParentCommand::TOpUpdatedAsyncImagePipeline: { + const OpUpdatedAsyncImagePipeline& op = + cmd.get_OpUpdatedAsyncImagePipeline(); + aTxn.InvalidateRenderedFrame(); + mAsyncImageManager->ApplyAsyncImageForPipeline(op.pipelineId(), aTxn, + txnForImageBridge); + break; + } + case WebRenderParentCommand::TCompositableOperation: { + if (!ReceiveCompositableUpdate(cmd.get_CompositableOperation())) { + NS_ERROR("ReceiveCompositableUpdate failed"); + } + break; + } + case WebRenderParentCommand::TOpAddCompositorAnimations: { + const OpAddCompositorAnimations& op = + cmd.get_OpAddCompositorAnimations(); + CompositorAnimations data(std::move(op.data())); + // AnimationHelper::GetNextCompositorAnimationsId() encodes the child + // process PID in the upper 32 bits of the id, verify that this is as + // expected. + if ((data.id() >> 32) != (uint64_t)OtherPid()) { + return false; + } + if (data.animations().Length()) { + if (RefPtr sampler = GetOMTASampler()) { + sampler->SetAnimations(data.id(), GetLayersId(), data.animations()); + const auto activeAnim = mActiveAnimations.find(data.id()); + if (activeAnim == mActiveAnimations.end()) { + mActiveAnimations.emplace(data.id(), mWrEpoch); + } else { + // Update wr::Epoch if the animation already exists. + activeAnim->second = mWrEpoch; + } + } + } + break; + } + default: { + // other commands are handle on the child + break; + } + } + } + return true; +} + +void WebRenderBridgeParent::FlushSceneBuilds() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + + // Since we are sending transactions through the scene builder thread, we need + // to block until all the inflight transactions have been processed. This + // flush message blocks until all previously sent scenes have been built + // and received by the render backend thread. + mApi->FlushSceneBuilder(); + // The post-swap hook for async-scene-building calls the + // ScheduleRenderOnCompositorThread function from the scene builder thread, + // which then triggers a call to ScheduleGenerateFrame() on the compositor + // thread. But since *this* function is running on the compositor thread, + // that scheduling will not happen until this call stack unwinds (or we + // could spin a nested event loop, but that's more messy). Instead, we + // simulate it ourselves by calling ScheduleGenerateFrame() directly. + // Note also that the post-swap hook will run and do another + // ScheduleGenerateFrame() after we unwind here, so we will end up with an + // extra render/composite that is probably avoidable, but in practice we + // shouldn't be calling this function all that much in production so this + // is probably fine. If it becomes an issue we can add more state tracking + // machinery to optimize it away. + ScheduleGenerateFrame(); +} + +void WebRenderBridgeParent::FlushFrameGeneration() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + MOZ_ASSERT(IsRootWebRenderBridgeParent()); // This function is only useful on + // the root WRBP + + // This forces a new GenerateFrame transaction to be sent to the render + // backend thread, if one is pending. This doesn't block on any other threads. + if (mCompositorScheduler->NeedsComposite()) { + mCompositorScheduler->CancelCurrentCompositeTask(); + // Update timestamp of scheduler for APZ and animation. + mCompositorScheduler->UpdateLastComposeTime(); + MaybeGenerateFrame(VsyncId(), /* aForceGenerateFrame */ true); + } +} + +void WebRenderBridgeParent::FlushFramePresentation() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + + // This sends a message to the render backend thread to send a message + // to the renderer thread, and waits for that message to be processed. So + // this effectively blocks on the render backend and renderer threads, + // following the same codepath that WebRender takes to render and composite + // a frame. + mApi->WaitFlushed(); +} + +void WebRenderBridgeParent::DisableNativeCompositor() { + // Make sure that SceneBuilder thread does not have a task. + mApi->FlushSceneBuilder(); + // Disable WebRender's native compositor usage + mApi->EnableNativeCompositor(false); + // Ensure we generate and render a frame immediately. + ScheduleForcedGenerateFrame(); + + mDisablingNativeCompositor = true; +} + +void WebRenderBridgeParent::UpdateQualitySettings() { + wr::TransactionBuilder txn; + txn.UpdateQualitySettings(gfxVars::ForceSubpixelAAWherePossible()); + mApi->SendTransaction(txn); +} + +void WebRenderBridgeParent::UpdateDebugFlags() { + mApi->UpdateDebugFlags(gfxVars::WebRenderDebugFlags()); +} + +void WebRenderBridgeParent::UpdateProfilerUI() { + nsCString uiString = gfxVars::GetWebRenderProfilerUIOrDefault(); + mApi->SetProfilerUI(uiString); +} + +void WebRenderBridgeParent::UpdateMultithreading() { + mApi->EnableMultithreading(gfxVars::UseWebRenderMultithreading()); +} + +void WebRenderBridgeParent::UpdateBatchingParameters() { + uint32_t count = gfxVars::WebRenderBatchingLookback(); + mApi->SetBatchingLookback(count); +} + +#if defined(MOZ_WIDGET_ANDROID) +void WebRenderBridgeParent::RequestScreenPixels( + UiCompositorControllerParent* aController) { + mScreenPixelsTarget = aController; +} + +void WebRenderBridgeParent::MaybeCaptureScreenPixels() { + if (!mScreenPixelsTarget) { + return; + } + + if (mDestroyed) { + return; + } + MOZ_ASSERT(!mPaused); + + // This function should only get called in the root WRBP. + MOZ_ASSERT(IsRootWebRenderBridgeParent()); + + SurfaceFormat format = SurfaceFormat::R8G8B8A8; // On android we use RGBA8 + auto client_size = mWidget->GetClientSize(); + size_t buffer_size = + client_size.width * client_size.height * BytesPerPixel(format); + + ipc::Shmem mem; + if (!mScreenPixelsTarget->AllocPixelBuffer(buffer_size, &mem)) { + // Failed to alloc shmem, Just bail out. + return; + } + + IntSize size(client_size.width, client_size.height); + + bool needsYFlip = false; + mApi->Readback(TimeStamp::Now(), size, format, + Range(mem.get(), buffer_size), &needsYFlip); + + Unused << mScreenPixelsTarget->SendScreenPixels( + std::move(mem), ScreenIntSize(client_size.width, client_size.height), + needsYFlip); + + mScreenPixelsTarget = nullptr; +} +#endif + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvGetSnapshot( + PTextureParent* aTexture, bool* aNeedsYFlip) { + *aNeedsYFlip = false; + if (mDestroyed || mPaused) { + return IPC_OK(); + } + + // This function should only get called in the root WRBP. If this function + // gets called in a non-root WRBP, we will set mForceRendering in this WRBP + // but it will have no effect because CompositeToTarget (which reads the + // flag) only gets invoked in the root WRBP. So we assert that this is the + // root WRBP (i.e. has a non-null mWidget) to catch violations of this rule. + MOZ_ASSERT(IsRootWebRenderBridgeParent()); + + RefPtr texture = TextureHost::AsTextureHost(aTexture); + if (!texture) { + // We kill the content process rather than have it continue with an invalid + // snapshot, that may be too harsh and we could decide to return some sort + // of error to the child process and let it deal with it... + return IPC_FAIL_NO_REASON(this); + } + + // XXX Add other TextureHost supports. + // Only BufferTextureHost is supported now. + BufferTextureHost* bufferTexture = texture->AsBufferTextureHost(); + if (!bufferTexture) { + // We kill the content process rather than have it continue with an invalid + // snapshot, that may be too harsh and we could decide to return some sort + // of error to the child process and let it deal with it... + return IPC_FAIL_NO_REASON(this); + } + + TimeStamp start = TimeStamp::Now(); + MOZ_ASSERT(bufferTexture->GetBufferDescriptor().type() == + BufferDescriptor::TRGBDescriptor); + DebugOnly stride = ImageDataSerializer::GetRGBStride( + bufferTexture->GetBufferDescriptor().get_RGBDescriptor()); + uint8_t* buffer = bufferTexture->GetBuffer(); + IntSize size = bufferTexture->GetSize(); + + MOZ_ASSERT(buffer); + // For now the only formats we get here are RGBA and BGRA, and code below is + // assuming a bpp of 4. If we allow other formats, the code needs adjusting + // accordingly. + MOZ_ASSERT(BytesPerPixel(bufferTexture->GetFormat()) == 4); + uint32_t buffer_size = size.width * size.height * 4; + + // Assert the stride of the buffer is what webrender expects + MOZ_ASSERT((uint32_t)(size.width * 4) == stride); + + FlushSceneBuilds(); + FlushFrameGeneration(); + mApi->Readback(start, size, bufferTexture->GetFormat(), + Range(buffer, buffer_size), aNeedsYFlip); + + return IPC_OK(); +} + +void WebRenderBridgeParent::AddPipelineIdForCompositable( + const wr::PipelineId& aPipelineId, const CompositableHandle& aHandle, + const bool& aAsync, wr::TransactionBuilder& aTxn, + wr::TransactionBuilder& aTxnForImageBridge) { + if (mDestroyed) { + return; + } + + MOZ_ASSERT(mAsyncCompositables.find(wr::AsUint64(aPipelineId)) == + mAsyncCompositables.end()); + + RefPtr host; + if (aAsync) { + RefPtr imageBridge = + ImageBridgeParent::GetInstance(OtherPid()); + if (!imageBridge) { + return; + } + host = imageBridge->FindCompositable(aHandle); + } else { + host = FindCompositable(aHandle); + } + if (!host) { + return; + } + + WebRenderImageHost* wrHost = host->AsWebRenderImageHost(); + MOZ_ASSERT(wrHost); + if (!wrHost) { + gfxCriticalNote + << "Incompatible CompositableHost at WebRenderBridgeParent."; + } + + if (!wrHost) { + return; + } + + wrHost->SetWrBridge(aPipelineId, this); + mAsyncCompositables.emplace(wr::AsUint64(aPipelineId), wrHost); + mAsyncImageManager->AddAsyncImagePipeline(aPipelineId, wrHost); + + // If this is being called from WebRenderBridgeParent::RecvSetDisplayList, + // then aTxn might contain a display list that references pipelines that + // we just added to the async image manager. + // If we send the display list alone then WR will not yet have the content for + // the pipelines and so it will emit errors; the SetEmptyDisplayList call + // below ensure that we provide its content to WR as part of the same + // transaction. + mAsyncImageManager->SetEmptyDisplayList(aPipelineId, aTxn, + aTxnForImageBridge); +} + +void WebRenderBridgeParent::RemovePipelineIdForCompositable( + const wr::PipelineId& aPipelineId, wr::TransactionBuilder& aTxn) { + if (mDestroyed) { + return; + } + + auto it = mAsyncCompositables.find(wr::AsUint64(aPipelineId)); + if (it == mAsyncCompositables.end()) { + return; + } + RefPtr& wrHost = it->second; + + wrHost->ClearWrBridge(aPipelineId, this); + mAsyncImageManager->RemoveAsyncImagePipeline(aPipelineId, aTxn); + aTxn.RemovePipeline(aPipelineId); + mAsyncCompositables.erase(wr::AsUint64(aPipelineId)); +} + +void WebRenderBridgeParent::DeleteImage(const ImageKey& aKey, + wr::TransactionBuilder& aUpdates) { + if (mDestroyed) { + return; + } + + auto it = mSharedSurfaceIds.find(wr::AsUint64(aKey)); + if (it != mSharedSurfaceIds.end()) { + mAsyncImageManager->HoldExternalImage(mPipelineId, mWrEpoch, it->second); + mSharedSurfaceIds.erase(it); + } + + aUpdates.DeleteImage(aKey); +} + +void WebRenderBridgeParent::ReleaseTextureOfImage(const wr::ImageKey& aKey) { + if (mDestroyed) { + return; + } + + uint64_t id = wr::AsUint64(aKey); + CompositableTextureHostRef texture; + WebRenderTextureHost* wrTexture = nullptr; + + auto it = mTextureHosts.find(id); + if (it != mTextureHosts.end()) { + wrTexture = (*it).second->AsWebRenderTextureHost(); + } + if (wrTexture) { + mAsyncImageManager->HoldExternalImage(mPipelineId, mWrEpoch, wrTexture); + } + mTextureHosts.erase(id); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvSetLayersObserverEpoch( + const LayersObserverEpoch& aChildEpoch) { + if (mDestroyed) { + return IPC_OK(); + } + mChildLayersObserverEpoch = aChildEpoch; + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvClearCachedResources() { + if (mDestroyed) { + return IPC_OK(); + } + + // Clear resources + wr::TransactionBuilder txn; + txn.SetLowPriority(true); + txn.ClearDisplayList(GetNextWrEpoch(), mPipelineId); + txn.Notify( + wr::Checkpoint::SceneBuilt, + MakeUnique( + mCompositorBridge, GetLayersId(), mChildLayersObserverEpoch, false)); + mApi->SendTransaction(txn); + + // Schedule generate frame to clean up Pipeline + ScheduleGenerateFrame(); + + ClearAnimationResources(); + + return IPC_OK(); +} + +wr::Epoch WebRenderBridgeParent::UpdateWebRender( + CompositorVsyncScheduler* aScheduler, RefPtr&& aApi, + AsyncImagePipelineManager* aImageMgr, + const TextureFactoryIdentifier& aTextureFactoryIdentifier) { + MOZ_ASSERT(!IsRootWebRenderBridgeParent()); + MOZ_ASSERT(aScheduler); + MOZ_ASSERT(aApi); + MOZ_ASSERT(aImageMgr); + + if (mDestroyed) { + return mWrEpoch; + } + + // Update id name space to identify obsoleted keys. + // Since usage of invalid keys could cause crash in webrender. + mIdNamespace = aApi->GetNamespace(); + // XXX Remove it when webrender supports sharing/moving Keys between different + // webrender instances. + // XXX It requests client to update/reallocate webrender related resources, + // but parent side does not wait end of the update. + // The code could become simpler if we could serialise old keys deallocation + // and new keys allocation. But we do not do it, it is because client side + // deallocate old layers/webrender keys after new layers/webrender keys + // allocation. Without client side's layout refactoring, we could not finish + // all old layers/webrender keys removals before new layer/webrender keys + // allocation. In future, we could address the problem. + Unused << SendWrUpdated(mIdNamespace, aTextureFactoryIdentifier); + CompositorBridgeParentBase* cBridge = mCompositorBridge; + // XXX Stop to clear resources if webreder supports resources sharing between + // different webrender instances. + ClearResources(); + mCompositorBridge = cBridge; + mCompositorScheduler = aScheduler; + mApi = aApi; + mAsyncImageManager = aImageMgr; + + // Register pipeline to updated AsyncImageManager. + mAsyncImageManager->AddPipeline(mPipelineId, this); + + return GetNextWrEpoch(); // Update webrender epoch +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvInvalidateRenderedFrame() { + // This function should only get called in the root WRBP + MOZ_ASSERT(IsRootWebRenderBridgeParent()); + + InvalidateRenderedFrame(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvScheduleComposite() { + // Caller of LayerManager::ScheduleComposite() expects that it trigger + // composite. Then we do not want to skip generate frame. + ScheduleForcedGenerateFrame(); + return IPC_OK(); +} + +void WebRenderBridgeParent::InvalidateRenderedFrame() { + if (mDestroyed) { + return; + } + + wr::TransactionBuilder fastTxn(/* aUseSceneBuilderThread */ false); + fastTxn.InvalidateRenderedFrame(); + mApi->SendTransaction(fastTxn); +} + +void WebRenderBridgeParent::ScheduleForcedGenerateFrame() { + if (mDestroyed) { + return; + } + + InvalidateRenderedFrame(); + ScheduleGenerateFrame(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvCapture() { + if (!mDestroyed) { + mApi->Capture(); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvToggleCaptureSequence() { + if (!mDestroyed) { + mApi->ToggleCaptureSequence(); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvSyncWithCompositor() { + FlushSceneBuilds(); + if (RefPtr root = GetRootWebRenderBridgeParent()) { + root->FlushFrameGeneration(); + } + FlushFramePresentation(); + // Finally, we force the AsyncImagePipelineManager to handle all the + // pipeline updates produced in the last step, so that it frees any + // unneeded textures. Then we can return from this sync IPC call knowing + // that we've done everything we can to flush stuff on the compositor. + mAsyncImageManager->ProcessPipelineUpdates(); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvSetConfirmedTargetAPZC( + const uint64_t& aBlockId, nsTArray&& aTargets) { + for (size_t i = 0; i < aTargets.Length(); i++) { + // Guard against bad data from hijacked child processes + if (aTargets[i].mLayersId != GetLayersId()) { + NS_ERROR( + "Unexpected layers id in RecvSetConfirmedTargetAPZC; dropping " + "message..."); + return IPC_FAIL(this, "Bad layers id"); + } + } + + if (mDestroyed) { + return IPC_OK(); + } + mCompositorBridge->SetConfirmedTargetAPZC(GetLayersId(), aBlockId, + std::move(aTargets)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvSetTestSampleTime( + const TimeStamp& aTime) { + if (!mCompositorBridge->SetTestSampleTime(GetLayersId(), aTime)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvLeaveTestMode() { + if (mDestroyed) { + return IPC_FAIL_NO_REASON(this); + } + + mCompositorBridge->LeaveTestMode(GetLayersId()); + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvGetAnimationValue( + const uint64_t& aCompositorAnimationsId, OMTAValue* aValue) { + if (mDestroyed) { + return IPC_FAIL_NO_REASON(this); + } + + if (RefPtr sampler = GetOMTASampler()) { + Maybe testingTimeStamp; + if (CompositorBridgeParent* cbp = GetRootCompositorBridgeParent()) { + testingTimeStamp = cbp->GetTestingTimeStamp(); + } + + sampler->SampleForTesting(testingTimeStamp); + *aValue = sampler->GetOMTAValue(aCompositorAnimationsId); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvSetAsyncScrollOffset( + const ScrollableLayerGuid::ViewID& aScrollId, const float& aX, + const float& aY) { + if (mDestroyed) { + return IPC_OK(); + } + mCompositorBridge->SetTestAsyncScrollOffset(GetLayersId(), aScrollId, + CSSPoint(aX, aY)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvSetAsyncZoom( + const ScrollableLayerGuid::ViewID& aScrollId, const float& aZoom) { + if (mDestroyed) { + return IPC_OK(); + } + mCompositorBridge->SetTestAsyncZoom(GetLayersId(), aScrollId, + LayerToParentLayerScale(aZoom)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvFlushApzRepaints() { + if (mDestroyed) { + return IPC_OK(); + } + mCompositorBridge->FlushApzRepaints(GetLayersId()); + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvGetAPZTestData( + APZTestData* aOutData) { + mCompositorBridge->GetAPZTestData(GetLayersId(), aOutData); + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvGetFrameUniformity( + FrameUniformityData* aOutData) { + mCompositorBridge->GetFrameUniformity(GetLayersId(), aOutData); + return IPC_OK(); +} + +void WebRenderBridgeParent::ActorDestroy(ActorDestroyReason aWhy) { Destroy(); } + +void WebRenderBridgeParent::ResetPreviousSampleTime() { + if (RefPtr sampler = GetOMTASampler()) { + sampler->ResetPreviousSampleTime(); + } +} + +RefPtr WebRenderBridgeParent::GetOMTASampler() const { + CompositorBridgeParent* cbp = GetRootCompositorBridgeParent(); + if (!cbp) { + return nullptr; + } + return cbp->GetOMTASampler(); +} + +void WebRenderBridgeParent::SetOMTASampleTime() { + MOZ_ASSERT(IsRootWebRenderBridgeParent()); + if (RefPtr sampler = GetOMTASampler()) { + sampler->SetSampleTime(mCompositorScheduler->GetLastComposeTime().Time()); + } +} + +void WebRenderBridgeParent::CompositeIfNeeded() { + if (mSkippedComposite) { + mSkippedComposite = false; + if (mCompositorScheduler) { + mCompositorScheduler->ScheduleComposition(); + } + } +} + +void WebRenderBridgeParent::CompositeToTarget(VsyncId aId, + gfx::DrawTarget* aTarget, + const gfx::IntRect* aRect) { + // This function should only get called in the root WRBP + MOZ_ASSERT(IsRootWebRenderBridgeParent()); + + // The two arguments are part of the CompositorVsyncSchedulerOwner API but in + // this implementation they should never be non-null. + MOZ_ASSERT(aTarget == nullptr); + MOZ_ASSERT(aRect == nullptr); + + AUTO_PROFILER_TRACING_MARKER("Paint", "CompositeToTarget", GRAPHICS); + if (mPaused || !mReceivedDisplayList) { + ResetPreviousSampleTime(); + mCompositionOpportunityId = mCompositionOpportunityId.Next(); + PROFILER_MARKER_TEXT("SkippedComposite", GRAPHICS, {}, + mPaused ? "Paused"_ns : "No display list"_ns); + return; + } + + if (mSkippedComposite || + wr::RenderThread::Get()->TooManyPendingFrames(mApi->GetId())) { + // Render thread is busy, try next time. + mSkippedComposite = true; + ResetPreviousSampleTime(); + + // Record that we skipped presenting a frame for + // all pending transactions that have finished scene building. + for (auto& id : mPendingTransactionIds) { + if (id.mSceneBuiltTime) { + id.mSkippedComposites++; + } + } + + PROFILER_MARKER_TEXT("SkippedComposite", GRAPHICS, {}, + "Too many pending frames"); + return; + } + + mCompositionOpportunityId = mCompositionOpportunityId.Next(); + MaybeGenerateFrame(aId, /* aForceGenerateFrame */ false); +} + +TimeDuration WebRenderBridgeParent::GetVsyncInterval() const { + // This function should only get called in the root WRBP + MOZ_ASSERT(IsRootWebRenderBridgeParent()); + if (CompositorBridgeParent* cbp = GetRootCompositorBridgeParent()) { + return cbp->GetVsyncInterval(); + } + return TimeDuration(); +} + +void WebRenderBridgeParent::MaybeGenerateFrame(VsyncId aId, + bool aForceGenerateFrame) { + // This function should only get called in the root WRBP + MOZ_ASSERT(IsRootWebRenderBridgeParent()); + + if (CompositorBridgeParent* cbp = GetRootCompositorBridgeParent()) { + // Skip WR render during paused state. + if (cbp->IsPaused()) { + TimeStamp now = TimeStamp::NowUnfuzzed(); + PROFILER_MARKER_TEXT("SkippedComposite", GRAPHICS, + MarkerTiming::InstantAt(now), + "CompositorBridgeParent is paused"); + cbp->NotifyPipelineRendered(mPipelineId, mWrEpoch, VsyncId(), now, now, + now); + return; + } + } + + TimeStamp start = TimeStamp::Now(); + + // Ensure GenerateFrame is handled on the render backend thread rather + // than going through the scene builder thread. That way we continue + // generating frames with the old scene even during slow scene builds. + wr::TransactionBuilder fastTxn(false /* useSceneBuilderThread */); + // Handle transaction that is related to DisplayList. + wr::TransactionBuilder sceneBuilderTxn; + wr::AutoTransactionSender sender(mApi, &sceneBuilderTxn); + + mAsyncImageManager->SetCompositionInfo(start, mCompositionOpportunityId); + mAsyncImageManager->ApplyAsyncImagesOfImageBridge(sceneBuilderTxn, fastTxn); + mAsyncImageManager->SetCompositionInfo(TimeStamp(), + CompositionOpportunityId{}); + + if (!mAsyncImageManager->GetCompositeUntilTime().IsNull()) { + // Trigger another CompositeToTarget() call because there might be another + // frame that we want to generate after this one. + // It will check if we actually want to generate the frame or not. + mCompositorScheduler->ScheduleComposition(); + } + + bool generateFrame = mAsyncImageManager->GetAndResetWillGenerateFrame() || + !fastTxn.IsEmpty() || aForceGenerateFrame; + + if (!generateFrame) { + // Could skip generating frame now. + PROFILER_MARKER_TEXT("SkippedComposite", GRAPHICS, + MarkerTiming::InstantAt(start), + "No reason to generate frame"); + ResetPreviousSampleTime(); + return; + } + + if (RefPtr sampler = GetOMTASampler()) { + if (sampler->HasAnimations()) { + ScheduleGenerateFrame(); + } + } + + SetOMTASampleTime(); + SetAPZSampleTime(); + + wr::RenderThread::Get()->IncPendingFrameCount(mApi->GetId(), aId, start); + +#if defined(ENABLE_FRAME_LATENCY_LOG) + auto startTime = TimeStamp::Now(); + mApi->SetFrameStartTime(startTime); +#endif + + MOZ_ASSERT(generateFrame); + fastTxn.GenerateFrame(aId); + mApi->SendTransaction(fastTxn); + +#if defined(MOZ_WIDGET_ANDROID) + MaybeCaptureScreenPixels(); +#endif + + mMostRecentComposite = TimeStamp::Now(); + + // During disabling native compositor, webrender needs to render twice. + // Otherwise, browser flashes black. + // XXX better fix? + if (mDisablingNativeCompositor) { + mDisablingNativeCompositor = false; + + // Ensure we generate and render a frame immediately. + ScheduleForcedGenerateFrame(); + } +} + +void WebRenderBridgeParent::HoldPendingTransactionId( + const wr::Epoch& aWrEpoch, TransactionId aTransactionId, + bool aContainsSVGGroup, const VsyncId& aVsyncId, + const TimeStamp& aVsyncStartTime, const TimeStamp& aRefreshStartTime, + const TimeStamp& aTxnStartTime, const nsCString& aTxnURL, + const TimeStamp& aFwdTime, const bool aIsFirstPaint, + nsTArray&& aPayloads, const bool aUseForTelemetry) { + MOZ_ASSERT(aTransactionId > LastPendingTransactionId()); + mPendingTransactionIds.push_back(PendingTransactionId( + aWrEpoch, aTransactionId, aContainsSVGGroup, aVsyncId, aVsyncStartTime, + aRefreshStartTime, aTxnStartTime, aTxnURL, aFwdTime, aIsFirstPaint, + aUseForTelemetry, std::move(aPayloads))); +} + +TransactionId WebRenderBridgeParent::LastPendingTransactionId() { + TransactionId id{0}; + if (!mPendingTransactionIds.empty()) { + id = mPendingTransactionIds.back().mId; + } + return id; +} + +void WebRenderBridgeParent::NotifySceneBuiltForEpoch( + const wr::Epoch& aEpoch, const TimeStamp& aEndTime) { + for (auto& id : mPendingTransactionIds) { + if (id.mEpoch.mHandle == aEpoch.mHandle) { + id.mSceneBuiltTime = aEndTime; + break; + } + } +} + +void WebRenderBridgeParent::NotifyDidSceneBuild( + RefPtr aInfo) { + MOZ_ASSERT(IsRootWebRenderBridgeParent()); + if (!mCompositorScheduler) { + return; + } + + mAsyncImageManager->SetWillGenerateFrame(); + + // If the scheduler has a composite more recent than our last composite (which + // we missed), and we're within the threshold ms of the last vsync, then + // kick of a late composite. + TimeStamp lastVsync = mCompositorScheduler->GetLastVsyncTime(); + VsyncId lastVsyncId = mCompositorScheduler->GetLastVsyncId(); + if (lastVsyncId == VsyncId() || !mMostRecentComposite || + mMostRecentComposite >= lastVsync || + ((TimeStamp::Now() - lastVsync).ToMilliseconds() > + StaticPrefs::gfx_webrender_late_scenebuild_threshold())) { + mCompositorScheduler->ScheduleComposition(); + return; + } + + // Look through all the pipelines contained within the built scene + // and check which vsync they initiated from. + const auto& info = aInfo->Raw(); + for (const auto& epoch : info.epochs) { + WebRenderBridgeParent* wrBridge = this; + if (!(epoch.pipeline_id == PipelineId())) { + wrBridge = mAsyncImageManager->GetWrBridge(epoch.pipeline_id); + } + + if (wrBridge) { + VsyncId startId = wrBridge->GetVsyncIdForEpoch(epoch.epoch); + // If any of the pipelines started building on the current vsync (i.e + // we did all of display list building and scene building within the + // threshold), then don't do an early composite. + if (startId == lastVsyncId) { + mCompositorScheduler->ScheduleComposition(); + return; + } + } + } + + CompositeToTarget(mCompositorScheduler->GetLastVsyncId(), nullptr, nullptr); +} + +TransactionId WebRenderBridgeParent::FlushTransactionIdsForEpoch( + const wr::Epoch& aEpoch, const VsyncId& aCompositeStartId, + const TimeStamp& aCompositeStartTime, const TimeStamp& aRenderStartTime, + const TimeStamp& aEndTime, UiCompositorControllerParent* aUiController, + wr::RendererStats* aStats, nsTArray* aOutputStats) { + TransactionId id{0}; + while (!mPendingTransactionIds.empty()) { + const auto& transactionId = mPendingTransactionIds.front(); + + if (aEpoch.mHandle < transactionId.mEpoch.mHandle) { + break; + } + + if (!IsRootWebRenderBridgeParent() && !mVsyncRate.IsZero() && + transactionId.mUseForTelemetry) { + auto fullPaintTime = + transactionId.mSceneBuiltTime + ? transactionId.mSceneBuiltTime - transactionId.mTxnStartTime + : TimeDuration::FromMilliseconds(0); + + int32_t contentFrameTime = RecordContentFrameTime( + transactionId.mVsyncId, transactionId.mVsyncStartTime, + transactionId.mTxnStartTime, aCompositeStartId, aEndTime, + fullPaintTime, mVsyncRate, transactionId.mContainsSVGGroup, true, + aStats); + if (contentFrameTime > 200) { + aOutputStats->AppendElement(FrameStats( + transactionId.mId, aCompositeStartTime, aRenderStartTime, aEndTime, + contentFrameTime, + aStats ? (double(aStats->resource_upload_time) / 1000000.0) : 0.0, + aStats ? (double(aStats->gpu_cache_upload_time) / 1000000.0) : 0.0, + transactionId.mTxnStartTime, transactionId.mRefreshStartTime, + transactionId.mFwdTime, transactionId.mSceneBuiltTime, + transactionId.mSkippedComposites, transactionId.mTxnURL)); + } + } + +#if defined(ENABLE_FRAME_LATENCY_LOG) + if (transactionId.mRefreshStartTime) { + int32_t latencyMs = + lround((aEndTime - transactionId.mRefreshStartTime).ToMilliseconds()); + printf_stderr( + "From transaction start to end of generate frame latencyMs %d this " + "%p\n", + latencyMs, this); + } + if (transactionId.mFwdTime) { + int32_t latencyMs = + lround((aEndTime - transactionId.mFwdTime).ToMilliseconds()); + printf_stderr( + "From forwarding transaction to end of generate frame latencyMs %d " + "this %p\n", + latencyMs, this); + } +#endif + + if (aUiController && transactionId.mIsFirstPaint) { + aUiController->NotifyFirstPaint(); + } + + RecordCompositionPayloadsPresented(aEndTime, transactionId.mPayloads); + + id = transactionId.mId; + mPendingTransactionIds.pop_front(); + } + return id; +} + +LayersId WebRenderBridgeParent::GetLayersId() const { + return wr::AsLayersId(mPipelineId); +} + +void WebRenderBridgeParent::ScheduleGenerateFrame() { + if (mCompositorScheduler) { + mAsyncImageManager->SetWillGenerateFrame(); + mCompositorScheduler->ScheduleComposition(); + } +} + +void WebRenderBridgeParent::FlushRendering(bool aWaitForPresent) { + if (mDestroyed) { + return; + } + + // This gets called during e.g. window resizes, so we need to flush the + // scene (which has the display list at the new window size). + FlushSceneBuilds(); + FlushFrameGeneration(); + if (aWaitForPresent) { + FlushFramePresentation(); + } +} + +void WebRenderBridgeParent::SetClearColor(const gfx::DeviceColor& aColor) { + MOZ_ASSERT(IsRootWebRenderBridgeParent()); + + if (!IsRootWebRenderBridgeParent() || mDestroyed) { + return; + } + + mApi->SetClearColor(aColor); +} + +void WebRenderBridgeParent::Pause() { + MOZ_ASSERT(IsRootWebRenderBridgeParent()); + + if (!IsRootWebRenderBridgeParent() || mDestroyed) { + return; + } + + mApi->Pause(); + mPaused = true; +} + +bool WebRenderBridgeParent::Resume() { + MOZ_ASSERT(IsRootWebRenderBridgeParent()); + + if (!IsRootWebRenderBridgeParent() || mDestroyed) { + return false; + } + + if (!mApi->Resume()) { + return false; + } + + // Ensure we generate and render a frame immediately. + ScheduleForcedGenerateFrame(); + + mPaused = false; + return true; +} + +void WebRenderBridgeParent::ClearResources() { + if (!mApi) { + return; + } + + wr::Epoch wrEpoch = GetNextWrEpoch(); + mReceivedDisplayList = false; + // Schedule generate frame to clean up Pipeline + ScheduleGenerateFrame(); + + // WrFontKeys and WrImageKeys are deleted during WebRenderAPI destruction. + for (const auto& entry : mTextureHosts) { + WebRenderTextureHost* wrTexture = entry.second->AsWebRenderTextureHost(); + MOZ_ASSERT(wrTexture); + if (wrTexture) { + mAsyncImageManager->HoldExternalImage(mPipelineId, wrEpoch, wrTexture); + } + } + mTextureHosts.clear(); + + for (const auto& entry : mSharedSurfaceIds) { + mAsyncImageManager->HoldExternalImage(mPipelineId, mWrEpoch, entry.second); + } + mSharedSurfaceIds.clear(); + + mAsyncImageManager->RemovePipeline(mPipelineId, wrEpoch); + + wr::TransactionBuilder txn; + txn.SetLowPriority(true); + txn.ClearDisplayList(wrEpoch, mPipelineId); + + for (const auto& entry : mAsyncCompositables) { + wr::PipelineId pipelineId = wr::AsPipelineId(entry.first); + RefPtr host = entry.second; + host->ClearWrBridge(pipelineId, this); + mAsyncImageManager->RemoveAsyncImagePipeline(pipelineId, txn); + txn.RemovePipeline(pipelineId); + } + mAsyncCompositables.clear(); + txn.RemovePipeline(mPipelineId); + mApi->SendTransaction(txn); + + ClearAnimationResources(); + + if (IsRootWebRenderBridgeParent()) { + mCompositorScheduler->Destroy(); + } + + mCompositorScheduler = nullptr; + mAsyncImageManager = nullptr; + mApi = nullptr; + mCompositorBridge = nullptr; +} + +void WebRenderBridgeParent::ClearAnimationResources() { + if (RefPtr sampler = GetOMTASampler()) { + sampler->ClearActiveAnimations(mActiveAnimations); + } + mActiveAnimations.clear(); + std::queue().swap( + mCompositorAnimationsToDelete); // clear queue +} + +bool WebRenderBridgeParent::ShouldParentObserveEpoch() { + if (mParentLayersObserverEpoch == mChildLayersObserverEpoch) { + return false; + } + + mParentLayersObserverEpoch = mChildLayersObserverEpoch; + return true; +} + +void WebRenderBridgeParent::SendAsyncMessage( + const nsTArray& aMessage) { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); +} + +void WebRenderBridgeParent::SendPendingAsyncMessages() { + MOZ_ASSERT(mCompositorBridge); + mCompositorBridge->SendPendingAsyncMessages(); +} + +void WebRenderBridgeParent::SetAboutToSendAsyncMessages() { + MOZ_ASSERT(mCompositorBridge); + mCompositorBridge->SetAboutToSendAsyncMessages(); +} + +void WebRenderBridgeParent::NotifyNotUsed(PTextureParent* aTexture, + uint64_t aTransactionId) { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); +} + +base::ProcessId WebRenderBridgeParent::GetChildProcessId() { + return OtherPid(); +} + +bool WebRenderBridgeParent::IsSameProcess() const { + return OtherPid() == base::GetCurrentProcId(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvNewCompositable( + const CompositableHandle& aHandle, const TextureInfo& aInfo) { + if (mDestroyed) { + return IPC_OK(); + } + if (!AddCompositable(aHandle, aInfo, /* aUseWebRender */ true)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult WebRenderBridgeParent::RecvReleaseCompositable( + const CompositableHandle& aHandle) { + if (mDestroyed) { + return IPC_OK(); + } + ReleaseCompositable(aHandle); + return IPC_OK(); +} + +TextureFactoryIdentifier WebRenderBridgeParent::GetTextureFactoryIdentifier() { + MOZ_ASSERT(mApi); + + TextureFactoryIdentifier ident( + mApi->GetBackendType(), mApi->GetCompositorType(), XRE_GetProcessType(), + mApi->GetMaxTextureSize(), false, mApi->GetUseANGLE(), + mApi->GetUseDComp(), mAsyncImageManager->UseCompositorWnd(), false, false, + false, mApi->GetSyncHandle()); + return ident; +} + +wr::Epoch WebRenderBridgeParent::GetNextWrEpoch() { + MOZ_RELEASE_ASSERT(mWrEpoch.mHandle != UINT32_MAX); + mWrEpoch.mHandle++; + return mWrEpoch; +} + +void WebRenderBridgeParent::RollbackWrEpoch() { + MOZ_RELEASE_ASSERT(mWrEpoch.mHandle != 0); + mWrEpoch.mHandle--; +} + +void WebRenderBridgeParent::ExtractImageCompositeNotifications( + nsTArray* aNotifications) { + MOZ_ASSERT(IsRootWebRenderBridgeParent()); + if (mDestroyed) { + return; + } + mAsyncImageManager->FlushImageNotifications(aNotifications); +} + +RefPtr +WebRenderBridgeParent::GetWebRenderBridgeParentRef() { + if (mDestroyed) { + return nullptr; + } + + if (!mWebRenderBridgeRef) { + mWebRenderBridgeRef = new WebRenderBridgeParentRef(this); + } + return mWebRenderBridgeRef; +} + +WebRenderBridgeParentRef::WebRenderBridgeParentRef( + WebRenderBridgeParent* aWebRenderBridge) + : mWebRenderBridge(aWebRenderBridge) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + MOZ_ASSERT(mWebRenderBridge); +} + +RefPtr WebRenderBridgeParentRef::WrBridge() { + return mWebRenderBridge; +} + +void WebRenderBridgeParentRef::Clear() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + mWebRenderBridge = nullptr; +} + +WebRenderBridgeParentRef::~WebRenderBridgeParentRef() { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + MOZ_ASSERT(!mWebRenderBridge); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/wr/WebRenderBridgeParent.h b/gfx/layers/wr/WebRenderBridgeParent.h new file mode 100644 index 0000000000..3c91b8c84e --- /dev/null +++ b/gfx/layers/wr/WebRenderBridgeParent.h @@ -0,0 +1,535 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_layers_WebRenderBridgeParent_h +#define mozilla_layers_WebRenderBridgeParent_h + +#include +#include + +#include "CompositableHost.h" // for CompositableHost, ImageCompositeNotificationInfo +#include "GLContextProvider.h" +#include "mozilla/DataMutex.h" +#include "mozilla/layers/CompositableTransactionParent.h" +#include "mozilla/layers/CompositorVsyncSchedulerOwner.h" +#include "mozilla/layers/LayerManager.h" +#include "mozilla/layers/PWebRenderBridgeParent.h" +#include "mozilla/HashTable.h" +#include "mozilla/Maybe.h" +#include "mozilla/Result.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/webrender/WebRenderTypes.h" +#include "mozilla/webrender/WebRenderAPI.h" +#include "nsTArrayForwardDeclare.h" + +namespace mozilla { + +namespace gl { +class GLContext; +} + +namespace widget { +class CompositorWidget; +} + +namespace wr { +class WebRenderAPI; +class WebRenderPipelineInfo; +} // namespace wr + +namespace layers { + +class AsyncImagePipelineManager; +class Compositor; +class CompositorBridgeParentBase; +class CompositorVsyncScheduler; +class OMTASampler; +class UiCompositorControllerParent; +class WebRenderBridgeParentRef; +class WebRenderImageHost; +struct WrAnimations; + +struct CompositorAnimationIdsForEpoch { + CompositorAnimationIdsForEpoch(const wr::Epoch& aEpoch, + nsTArray&& aIds) + : mEpoch(aEpoch), mIds(std::move(aIds)) {} + + wr::Epoch mEpoch; + nsTArray mIds; +}; + +class WebRenderBridgeParent final : public PWebRenderBridgeParent, + public CompositorVsyncSchedulerOwner, + public CompositableParentManager, + public layers::FrameRecorder { + public: + WebRenderBridgeParent(CompositorBridgeParentBase* aCompositorBridge, + const wr::PipelineId& aPipelineId, + widget::CompositorWidget* aWidget, + CompositorVsyncScheduler* aScheduler, + RefPtr&& aApi, + RefPtr&& aImageMgr, + TimeDuration aVsyncRate); + + static WebRenderBridgeParent* CreateDestroyed( + const wr::PipelineId& aPipelineId, nsCString&& aError); + + wr::PipelineId PipelineId() { return mPipelineId; } + already_AddRefed GetWebRenderAPI() { + return do_AddRef(mApi); + } + AsyncImagePipelineManager* AsyncImageManager() { return mAsyncImageManager; } + CompositorVsyncScheduler* CompositorScheduler() { + return mCompositorScheduler.get(); + } + CompositorBridgeParentBase* GetCompositorBridge() { + return mCompositorBridge; + } + + void UpdateQualitySettings(); + void UpdateDebugFlags(); + void UpdateMultithreading(); + void UpdateBatchingParameters(); + void UpdateProfilerUI(); + + mozilla::ipc::IPCResult RecvEnsureConnected( + TextureFactoryIdentifier* aTextureFactoryIdentifier, + MaybeIdNamespace* aMaybeIdNamespace, nsCString* aError) override; + + mozilla::ipc::IPCResult RecvNewCompositable( + const CompositableHandle& aHandle, const TextureInfo& aInfo) override; + mozilla::ipc::IPCResult RecvReleaseCompositable( + const CompositableHandle& aHandle) override; + + mozilla::ipc::IPCResult RecvShutdown() override; + mozilla::ipc::IPCResult RecvShutdownSync() override; + mozilla::ipc::IPCResult RecvDeleteCompositorAnimations( + nsTArray&& aIds) override; + mozilla::ipc::IPCResult RecvUpdateResources( + const wr::IdNamespace& aIdNamespace, + nsTArray&& aUpdates, + nsTArray&& aSmallShmems, + nsTArray&& aLargeShmems) override; + mozilla::ipc::IPCResult RecvSetDisplayList( + DisplayListData&& aDisplayList, nsTArray&& aToDestroy, + const uint64_t& aFwdTransactionId, const TransactionId& aTransactionId, + const bool& aContainsSVGGroup, const VsyncId& aVsyncId, + const TimeStamp& aVsyncStartTime, const TimeStamp& aRefreshStartTime, + const TimeStamp& aTxnStartTime, const nsCString& aTxnURL, + const TimeStamp& aFwdTime, + nsTArray&& aPayloads) override; + mozilla::ipc::IPCResult RecvEmptyTransaction( + const FocusTarget& aFocusTarget, + Maybe&& aTransactionData, + nsTArray&& aToDestroy, const uint64_t& aFwdTransactionId, + const TransactionId& aTransactionId, const VsyncId& aVsyncId, + const TimeStamp& aVsyncStartTime, const TimeStamp& aRefreshStartTime, + const TimeStamp& aTxnStartTime, const nsCString& aTxnURL, + const TimeStamp& aFwdTime, + nsTArray&& aPayloads) override; + mozilla::ipc::IPCResult RecvSetFocusTarget( + const FocusTarget& aFocusTarget) override; + mozilla::ipc::IPCResult RecvParentCommands( + nsTArray&& commands) override; + mozilla::ipc::IPCResult RecvGetSnapshot(PTextureParent* aTexture, + bool* aNeedsYFlip) override; + + mozilla::ipc::IPCResult RecvSetLayersObserverEpoch( + const LayersObserverEpoch& aChildEpoch) override; + + mozilla::ipc::IPCResult RecvClearCachedResources() override; + mozilla::ipc::IPCResult RecvInvalidateRenderedFrame() override; + mozilla::ipc::IPCResult RecvScheduleComposite() override; + mozilla::ipc::IPCResult RecvCapture() override; + mozilla::ipc::IPCResult RecvToggleCaptureSequence() override; + mozilla::ipc::IPCResult RecvSyncWithCompositor() override; + + mozilla::ipc::IPCResult RecvSetConfirmedTargetAPZC( + const uint64_t& aBlockId, + nsTArray&& aTargets) override; + + mozilla::ipc::IPCResult RecvSetTestSampleTime( + const TimeStamp& aTime) override; + mozilla::ipc::IPCResult RecvLeaveTestMode() override; + mozilla::ipc::IPCResult RecvGetAnimationValue( + const uint64_t& aCompositorAnimationsId, OMTAValue* aValue) override; + mozilla::ipc::IPCResult RecvSetAsyncScrollOffset( + const ScrollableLayerGuid::ViewID& aScrollId, const float& aX, + const float& aY) override; + mozilla::ipc::IPCResult RecvSetAsyncZoom( + const ScrollableLayerGuid::ViewID& aScrollId, + const float& aZoom) override; + mozilla::ipc::IPCResult RecvFlushApzRepaints() override; + mozilla::ipc::IPCResult RecvGetAPZTestData(APZTestData* data) override; + mozilla::ipc::IPCResult RecvGetFrameUniformity( + FrameUniformityData* aOutData) override; + + void ActorDestroy(ActorDestroyReason aWhy) override; + + void SetClearColor(const gfx::DeviceColor& aColor); + + void Pause(); + bool Resume(); + + void Destroy(); + + // CompositorVsyncSchedulerOwner + bool IsPendingComposite() override { return false; } + void FinishPendingComposite() override {} + void CompositeToTarget(VsyncId aId, gfx::DrawTarget* aTarget, + const gfx::IntRect* aRect = nullptr) override; + TimeDuration GetVsyncInterval() const override; + + // CompositableParentManager + bool IsSameProcess() const override; + base::ProcessId GetChildProcessId() override; + void NotifyNotUsed(PTextureParent* aTexture, + uint64_t aTransactionId) override; + void SendAsyncMessage( + const nsTArray& aMessage) override; + void SendPendingAsyncMessages() override; + void SetAboutToSendAsyncMessages() override; + + void HoldPendingTransactionId( + const wr::Epoch& aWrEpoch, TransactionId aTransactionId, + bool aContainsSVGGroup, const VsyncId& aVsyncId, + const TimeStamp& aVsyncStartTime, const TimeStamp& aRefreshStartTime, + const TimeStamp& aTxnStartTime, const nsCString& aTxnURL, + const TimeStamp& aFwdTime, const bool aIsFirstPaint, + nsTArray&& aPayloads, + const bool aUseForTelemetry = true); + TransactionId LastPendingTransactionId(); + TransactionId FlushTransactionIdsForEpoch( + const wr::Epoch& aEpoch, const VsyncId& aCompositeStartId, + const TimeStamp& aCompositeStartTime, const TimeStamp& aRenderStartTime, + const TimeStamp& aEndTime, UiCompositorControllerParent* aUiController, + wr::RendererStats* aStats = nullptr, + nsTArray* aOutputStats = nullptr); + void NotifySceneBuiltForEpoch(const wr::Epoch& aEpoch, + const TimeStamp& aEndTime); + + void CompositeIfNeeded(); + + TextureFactoryIdentifier GetTextureFactoryIdentifier(); + + void ExtractImageCompositeNotifications( + nsTArray* aNotifications); + + wr::Epoch GetCurrentEpoch() const { return mWrEpoch; } + wr::IdNamespace GetIdNamespace() { return mIdNamespace; } + + bool MatchesNamespace(const wr::ImageKey& aImageKey) const { + return aImageKey.mNamespace == mIdNamespace; + } + + bool MatchesNamespace(const wr::BlobImageKey& aBlobKey) const { + return MatchesNamespace(wr::AsImageKey(aBlobKey)); + } + + bool MatchesNamespace(const wr::FontKey& aFontKey) const { + return aFontKey.mNamespace == mIdNamespace; + } + + bool MatchesNamespace(const wr::FontInstanceKey& aFontKey) const { + return aFontKey.mNamespace == mIdNamespace; + } + + void FlushRendering(bool aWaitForPresent = true); + + /** + * Schedule generating WebRender frame definitely at next composite timing. + * + * WebRenderBridgeParent uses composite timing to check if there is an update + * to AsyncImagePipelines. If there is no update, WebRenderBridgeParent skips + * to generate frame. If we need to generate new frame at next composite + * timing, call this method. + * + * Call CompositorVsyncScheduler::ScheduleComposition() directly, if we just + * want to trigger AsyncImagePipelines update checks. + */ + void ScheduleGenerateFrame(); + + /** + * Invalidate rendered frame. + * + * WebRender could skip frame rendering if there is no update. + * This function is used to force invalidating even when there is no update. + */ + void InvalidateRenderedFrame(); + + /** + * Schedule forced frame rendering at next composite timing. + * + * WebRender could skip frame rendering if there is no update. + * This function is used to force rendering even when there is no update. + */ + void ScheduleForcedGenerateFrame(); + + void NotifyDidSceneBuild(RefPtr aInfo); + + wr::Epoch UpdateWebRender( + CompositorVsyncScheduler* aScheduler, RefPtr&& aApi, + AsyncImagePipelineManager* aImageMgr, + const TextureFactoryIdentifier& aTextureFactoryIdentifier); + + void RemoveEpochDataPriorTo(const wr::Epoch& aRenderedEpoch); + + bool IsRootWebRenderBridgeParent() const; + LayersId GetLayersId() const; + + void BeginRecording(const TimeStamp& aRecordingStart); + + /** + * Write the frames collected since the call to BeginRecording to disk. + * + * If there is not currently a recorder, this is a no-op. + */ + RefPtr WriteCollectedFrames(); + +#if defined(MOZ_WIDGET_ANDROID) + /** + * Request a screengrab for android + */ + void RequestScreenPixels(UiCompositorControllerParent* aController); + void MaybeCaptureScreenPixels(); +#endif + /** + * Return the frames collected since the call to BeginRecording encoded + * as data URIs. + * + * If there is not currently a recorder, this is a no-op and the promise will + * be rejected. + */ + RefPtr GetCollectedFrames(); + + void DisableNativeCompositor(); + void AddPendingScrollPayload(CompositionPayload& aPayload, + const VsyncId& aCompositeStartId); + + nsTArray TakePendingScrollPayload( + const VsyncId& aCompositeStartId); + + RefPtr GetWebRenderBridgeParentRef(); + + private: + class ScheduleSharedSurfaceRelease; + + WebRenderBridgeParent(const wr::PipelineId& aPipelineId, nsCString&& aError); + virtual ~WebRenderBridgeParent(); + + bool ProcessEmptyTransactionUpdates(TransactionData& aData, + bool* aScheduleComposite); + + bool ProcessDisplayListData(DisplayListData& aDisplayList, wr::Epoch aWrEpoch, + const TimeStamp& aTxnStartTime, + bool aValidTransaction, + bool aObserveLayersUpdate); + + bool SetDisplayList(const LayoutDeviceRect& aRect, ipc::ByteBuf&& aDL, + const wr::BuiltDisplayListDescriptor& aDLDesc, + const nsTArray& aResourceUpdates, + const nsTArray& aSmallShmems, + const nsTArray& aLargeShmems, + const TimeStamp& aTxnStartTime, + wr::TransactionBuilder& aTxn, wr::Epoch aWrEpoch, + bool aObserveLayersUpdate); + + void UpdateAPZFocusState(const FocusTarget& aFocus); + void UpdateAPZScrollData(const wr::Epoch& aEpoch, + WebRenderScrollData&& aData); + void UpdateAPZScrollOffsets(ScrollUpdatesMap&& aUpdates, + uint32_t aPaintSequenceNumber); + + bool UpdateResources(const nsTArray& aResourceUpdates, + const nsTArray& aSmallShmems, + const nsTArray& aLargeShmems, + wr::TransactionBuilder& aUpdates); + bool AddPrivateExternalImage(wr::ExternalImageId aExtId, wr::ImageKey aKey, + wr::ImageDescriptor aDesc, + wr::TransactionBuilder& aResources); + bool UpdatePrivateExternalImage(wr::ExternalImageId aExtId, wr::ImageKey aKey, + const wr::ImageDescriptor& aDesc, + const ImageIntRect& aDirtyRect, + wr::TransactionBuilder& aResources); + bool AddSharedExternalImage(wr::ExternalImageId aExtId, wr::ImageKey aKey, + wr::TransactionBuilder& aResources); + bool UpdateSharedExternalImage( + wr::ExternalImageId aExtId, wr::ImageKey aKey, + const ImageIntRect& aDirtyRect, wr::TransactionBuilder& aResources, + UniquePtr& aScheduleRelease); + void ObserveSharedSurfaceRelease( + const nsTArray& aPairs); + + bool PushExternalImageForTexture(wr::ExternalImageId aExtId, + wr::ImageKey aKey, TextureHost* aTexture, + bool aIsUpdate, + wr::TransactionBuilder& aResources); + + void AddPipelineIdForCompositable(const wr::PipelineId& aPipelineIds, + const CompositableHandle& aHandle, + const bool& aAsync, + wr::TransactionBuilder& aTxn, + wr::TransactionBuilder& aTxnForImageBridge); + void RemovePipelineIdForCompositable(const wr::PipelineId& aPipelineId, + wr::TransactionBuilder& aTxn); + + void DeleteImage(const wr::ImageKey& aKey, wr::TransactionBuilder& aUpdates); + void ReleaseTextureOfImage(const wr::ImageKey& aKey); + + bool ProcessWebRenderParentCommands( + const nsTArray& aCommands, + wr::TransactionBuilder& aTxn); + + void ClearResources(); + void ClearAnimationResources(); + bool ShouldParentObserveEpoch(); + mozilla::ipc::IPCResult HandleShutdown(); + + void ResetPreviousSampleTime(); + + void SetOMTASampleTime(); + RefPtr GetOMTASampler() const; + + CompositorBridgeParent* GetRootCompositorBridgeParent() const; + + RefPtr GetRootWebRenderBridgeParent() const; + + // Tell APZ what the subsequent sampling's timestamp should be. + void SetAPZSampleTime(); + + wr::Epoch GetNextWrEpoch(); + // This function is expected to be used when GetNextWrEpoch() is called, + // but TransactionBuilder does not have resource updates nor display list. + // In this case, ScheduleGenerateFrame is not triggered via SceneBuilder. + // Then we want to rollback WrEpoch. See Bug 1490117. + void RollbackWrEpoch(); + + void FlushSceneBuilds(); + void FlushFrameGeneration(); + void FlushFramePresentation(); + + void MaybeGenerateFrame(VsyncId aId, bool aForceGenerateFrame); + + VsyncId GetVsyncIdForEpoch(const wr::Epoch& aEpoch) { + for (auto& id : mPendingTransactionIds) { + if (id.mEpoch.mHandle == aEpoch.mHandle) { + return id.mVsyncId; + } + } + return VsyncId(); + } + + private: + struct PendingTransactionId { + PendingTransactionId(const wr::Epoch& aEpoch, TransactionId aId, + bool aContainsSVGGroup, const VsyncId& aVsyncId, + const TimeStamp& aVsyncStartTime, + const TimeStamp& aRefreshStartTime, + const TimeStamp& aTxnStartTime, + const nsCString& aTxnURL, const TimeStamp& aFwdTime, + const bool aIsFirstPaint, const bool aUseForTelemetry, + nsTArray&& aPayloads) + : mEpoch(aEpoch), + mId(aId), + mVsyncId(aVsyncId), + mVsyncStartTime(aVsyncStartTime), + mRefreshStartTime(aRefreshStartTime), + mTxnStartTime(aTxnStartTime), + mTxnURL(aTxnURL), + mFwdTime(aFwdTime), + mSkippedComposites(0), + mContainsSVGGroup(aContainsSVGGroup), + mIsFirstPaint(aIsFirstPaint), + mUseForTelemetry(aUseForTelemetry), + mPayloads(std::move(aPayloads)) {} + wr::Epoch mEpoch; + TransactionId mId; + VsyncId mVsyncId; + TimeStamp mVsyncStartTime; + TimeStamp mRefreshStartTime; + TimeStamp mTxnStartTime; + nsCString mTxnURL; + TimeStamp mFwdTime; + TimeStamp mSceneBuiltTime; + uint32_t mSkippedComposites; + bool mContainsSVGGroup; + bool mIsFirstPaint; + bool mUseForTelemetry; + nsTArray mPayloads; + }; + + CompositorBridgeParentBase* MOZ_NON_OWNING_REF mCompositorBridge; + wr::PipelineId mPipelineId; + RefPtr mWidget; + RefPtr mApi; + RefPtr mAsyncImageManager; + RefPtr mCompositorScheduler; + // mActiveAnimations is used to avoid leaking animations when + // WebRenderBridgeParent is destroyed abnormally and Tab move between + // different windows. + std::unordered_map mActiveAnimations; + std::unordered_map> mAsyncCompositables; + std::unordered_map mTextureHosts; + std::unordered_map mSharedSurfaceIds; + + TimeDuration mVsyncRate; + TimeStamp mPreviousFrameTimeStamp; + // These fields keep track of the latest layer observer epoch values in the + // child and the parent. mChildLayersObserverEpoch is the latest epoch value + // received from the child. mParentLayersObserverEpoch is the latest epoch + // value that we have told BrowserParent about (via ObserveLayerUpdate). + LayersObserverEpoch mChildLayersObserverEpoch; + LayersObserverEpoch mParentLayersObserverEpoch; + + std::deque mPendingTransactionIds; + std::queue mCompositorAnimationsToDelete; + wr::Epoch mWrEpoch; + wr::IdNamespace mIdNamespace; + CompositionOpportunityId mCompositionOpportunityId; + nsCString mInitError; + + TimeStamp mMostRecentComposite; + + RefPtr mWebRenderBridgeRef; + +#if defined(MOZ_WIDGET_ANDROID) + UiCompositorControllerParent* mScreenPixelsTarget; +#endif + bool mPaused; + bool mDestroyed; + bool mReceivedDisplayList; + bool mIsFirstPaint; + bool mSkippedComposite; + bool mDisablingNativeCompositor; + // These payloads are being used for SCROLL_PRESENT_LATENCY telemetry + DataMutex>> + mPendingScrollPayloads; +}; + +// Use this class, since WebRenderBridgeParent could not supports +// ThreadSafeWeakPtr. +// This class provides a ref of WebRenderBridgeParent when +// the WebRenderBridgeParent is not destroyed. Then it works similar to +// weak pointer. +class WebRenderBridgeParentRef final { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebRenderBridgeParentRef) + + explicit WebRenderBridgeParentRef(WebRenderBridgeParent* aWebRenderBridge); + + RefPtr WrBridge(); + void Clear(); + + protected: + ~WebRenderBridgeParentRef(); + + RefPtr mWebRenderBridge; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_WebRenderBridgeParent_h diff --git a/gfx/layers/wr/WebRenderCanvasRenderer.cpp b/gfx/layers/wr/WebRenderCanvasRenderer.cpp new file mode 100644 index 0000000000..2576cbd8ec --- /dev/null +++ b/gfx/layers/wr/WebRenderCanvasRenderer.cpp @@ -0,0 +1,81 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WebRenderCanvasRenderer.h" + +#include "GLContext.h" +#include "GLScreenBuffer.h" +#include "mozilla/layers/CompositorBridgeChild.h" +#include "SharedSurfaceGL.h" +#include "WebRenderBridgeChild.h" +#include "RenderRootStateManager.h" + +namespace mozilla { +namespace layers { + +CompositableForwarder* WebRenderCanvasRenderer::GetForwarder() { + return mManager->WrBridge(); +} + +WebRenderCanvasRendererAsync::~WebRenderCanvasRendererAsync() { + if (mPipelineId.isSome()) { + mManager->RemovePipelineIdForCompositable(mPipelineId.ref()); + mPipelineId.reset(); + } +} + +void WebRenderCanvasRendererAsync::Initialize(const CanvasRendererData& aData) { + WebRenderCanvasRenderer::Initialize(aData); + + ClearCachedResources(); +} + +bool WebRenderCanvasRendererAsync::CreateCompositable() { + if (!mCanvasClient) { + auto compositableFlags = TextureFlags::NO_FLAGS; + if (!mData.mIsAlphaPremult) { + // WR needs this flag marked on the compositable, not just the texture. + compositableFlags |= TextureFlags::NON_PREMULTIPLIED; + } + mCanvasClient = new CanvasClient(GetForwarder(), compositableFlags); + mCanvasClient->Connect(); + } + + if (!mPipelineId) { + // Alloc async image pipeline id. + mPipelineId = Some( + mManager->WrBridge()->GetCompositorBridgeChild()->GetNextPipelineId()); + mManager->AddPipelineIdForCompositable(mPipelineId.ref(), + mCanvasClient->GetIPCHandle()); + } + + return true; +} + +void WebRenderCanvasRendererAsync::ClearCachedResources() { + if (mPipelineId.isSome()) { + mManager->RemovePipelineIdForCompositable(mPipelineId.ref()); + mPipelineId.reset(); + } +} + +void WebRenderCanvasRendererAsync:: + UpdateCompositableClientForEmptyTransaction() { + bool wasDirty = IsDirty(); + UpdateCompositableClient(); + if (wasDirty && mPipelineId.isSome()) { + // Notify an update of async image pipeline during empty transaction. + // During non empty transaction, WebRenderBridgeParent receives + // OpUpdateAsyncImagePipeline message, but during empty transaction, the + // message is not sent to WebRenderBridgeParent. Then + // OpUpdatedAsyncImagePipeline is used to notify the update. + mManager->AddWebRenderParentCommand( + OpUpdatedAsyncImagePipeline(mPipelineId.ref())); + } +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/wr/WebRenderCanvasRenderer.h b/gfx/layers/wr/WebRenderCanvasRenderer.h new file mode 100644 index 0000000000..0aa6d5d580 --- /dev/null +++ b/gfx/layers/wr/WebRenderCanvasRenderer.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_WEBRENDERCANVASRENDERER_H +#define GFX_WEBRENDERCANVASRENDERER_H + +#include "ShareableCanvasRenderer.h" + +namespace mozilla { +namespace layers { + +class RenderRootStateManager; + +class WebRenderCanvasRenderer : public ShareableCanvasRenderer { + public: + explicit WebRenderCanvasRenderer(RenderRootStateManager* aManager) + : mManager(aManager) {} + + CompositableForwarder* GetForwarder() override; + + protected: + RenderRootStateManager* mManager; +}; + +class WebRenderCanvasRendererAsync final : public WebRenderCanvasRenderer { + public: + explicit WebRenderCanvasRendererAsync(RenderRootStateManager* aManager) + : WebRenderCanvasRenderer(aManager) {} + virtual ~WebRenderCanvasRendererAsync(); + + WebRenderCanvasRendererAsync* AsWebRenderCanvasRendererAsync() override { + return this; + } + + void Initialize(const CanvasRendererData& aData) override; + bool CreateCompositable() override; + + void ClearCachedResources() override; + + void UpdateCompositableClientForEmptyTransaction(); + + Maybe GetPipelineId() { return mPipelineId; } + + protected: + Maybe mPipelineId; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/wr/WebRenderCommandBuilder.cpp b/gfx/layers/wr/WebRenderCommandBuilder.cpp new file mode 100644 index 0000000000..c36349e7b6 --- /dev/null +++ b/gfx/layers/wr/WebRenderCommandBuilder.cpp @@ -0,0 +1,2689 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WebRenderCommandBuilder.h" + +#include "BasicLayers.h" +#include "Layers.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/EffectCompositor.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/SVGGeometryFrame.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/gfx/Types.h" +#include "mozilla/layers/AnimationHelper.h" +#include "mozilla/layers/ClipManager.h" +#include "mozilla/layers/ImageClient.h" +#include "mozilla/layers/RenderRootStateManager.h" +#include "mozilla/layers/WebRenderBridgeChild.h" +#include "mozilla/layers/WebRenderLayerManager.h" +#include "mozilla/layers/IpcResourceUpdateQueue.h" +#include "mozilla/layers/SharedSurfacesChild.h" +#include "mozilla/layers/SourceSurfaceSharedData.h" +#include "mozilla/layers/StackingContextHelper.h" +#include "mozilla/layers/UpdateImageHelper.h" +#include "mozilla/layers/WebRenderDrawEventRecorder.h" +#include "UnitTransforms.h" +#include "gfxEnv.h" +#include "nsDisplayListInvalidation.h" +#include "nsLayoutUtils.h" +#include "WebRenderCanvasRenderer.h" +#include "LayerTreeInvalidation.h" + +namespace mozilla { +namespace layers { + +using namespace gfx; +static bool PaintByLayer(nsDisplayItem* aItem, + nsDisplayListBuilder* aDisplayListBuilder, + const RefPtr& aManager, + gfxContext* aContext, const gfx::Size& aScale, + const std::function& aPaintFunc); +static int sIndent; +#include +#include + +static void GP(const char* fmt, ...) { + va_list args; + va_start(args, fmt); +#if 0 + for (int i = 0; i < sIndent; i++) { printf(" "); } + vprintf(fmt, args); +#endif + va_end(args); +} + +// XXX: problems: +// - How do we deal with scrolling while having only a single invalidation rect? +// We can have a valid rect and an invalid rect. As we scroll the valid rect +// will move and the invalid rect will be the new area + +struct BlobItemData; +static void DestroyBlobGroupDataProperty(nsTArray* aArray); +NS_DECLARE_FRAME_PROPERTY_WITH_DTOR(BlobGroupDataProperty, + nsTArray, + DestroyBlobGroupDataProperty); + +// These are currently manually allocated and ownership is help by the +// mDisplayItems hash table in DIGroup +struct BlobItemData { + // a weak pointer to the frame for this item. + // DisplayItemData has a mFrameList to deal with merged frames. Hopefully we + // don't need to worry about that. + nsIFrame* mFrame; + + uint32_t mDisplayItemKey; + nsTArray* + mArray; // a weak pointer to the array that's owned by the frame property + + IntRect mRect; + // It would be nice to not need this. We need to be able to call + // ComputeInvalidationRegion. ComputeInvalidationRegion will sometimes reach + // into parent style structs to get information that can change the + // invalidation region + UniquePtr mGeometry; + DisplayItemClip mClip; + bool mUsed; // initialized near construction + // XXX: only used for debugging + bool mInvalid; + + // a weak pointer to the group that owns this item + // we use this to track whether group for a particular item has changed + struct DIGroup* mGroup; + + // properties that are used to emulate layer tree invalidation + Matrix mMatrix; // updated to track the current transform to device space + RefPtr mLayerManager; + + // We need to keep a list of all the external surfaces used by the blob image. + // We do this on a per-display item basis so that the lists remains correct + // during invalidations. + std::vector> mExternalSurfaces; + + IntRect mImageRect; + + BlobItemData(DIGroup* aGroup, nsDisplayItem* aItem) + : mUsed(false), mGroup(aGroup) { + mInvalid = false; + mDisplayItemKey = aItem->GetPerFrameKey(); + AddFrame(aItem->Frame()); + } + + private: + void AddFrame(nsIFrame* aFrame) { + mFrame = aFrame; + + nsTArray* array = + aFrame->GetProperty(BlobGroupDataProperty()); + if (!array) { + array = new nsTArray(); + aFrame->SetProperty(BlobGroupDataProperty(), array); + } + array->AppendElement(this); + mArray = array; + } + + public: + void ClearFrame() { + // Delete the weak pointer to this BlobItemData on the frame + MOZ_RELEASE_ASSERT(mFrame); + // the property may already be removed if WebRenderUserData got deleted + // first so we use our own mArray pointer. + mArray->RemoveElement(this); + + // drop the entire property if nothing's left in the array + if (mArray->IsEmpty()) { + // If the frame is in the process of being destroyed this will fail + // but that's ok, because the the property will be removed then anyways + mFrame->RemoveProperty(BlobGroupDataProperty()); + } + mFrame = nullptr; + } + + ~BlobItemData() { + if (mFrame) { + ClearFrame(); + } + } +}; + +static BlobItemData* GetBlobItemData(nsDisplayItem* aItem) { + nsIFrame* frame = aItem->Frame(); + uint32_t key = aItem->GetPerFrameKey(); + const nsTArray* array = + frame->GetProperty(BlobGroupDataProperty()); + if (array) { + for (BlobItemData* item : *array) { + if (item->mDisplayItemKey == key) { + return item; + } + } + } + return nullptr; +} + +// We keep around the BlobItemData so that when we invalidate it get properly +// included in the rect +static void DestroyBlobGroupDataProperty(nsTArray* aArray) { + for (BlobItemData* item : *aArray) { + GP("DestroyBlobGroupDataProperty: %p-%d\n", item->mFrame, + item->mDisplayItemKey); + item->mFrame = nullptr; + } + delete aArray; +} + +static void TakeExternalSurfaces( + WebRenderDrawEventRecorder* aRecorder, + std::vector>& aExternalSurfaces, + RenderRootStateManager* aManager, wr::IpcResourceUpdateQueue& aResources) { + aRecorder->TakeExternalSurfaces(aExternalSurfaces); + + for (auto& surface : aExternalSurfaces) { + // While we don't use the image key with the surface, because the blob image + // renderer doesn't have easy access to the resource set, we still want to + // ensure one is generated. That will ensure the surface remains alive until + // at least the last epoch which the blob image could be used in. + wr::ImageKey key; + DebugOnly rv = + SharedSurfacesChild::Share(surface, aManager, aResources, key); + MOZ_ASSERT(rv.value != NS_ERROR_NOT_IMPLEMENTED); + } +} + +struct DIGroup; +struct Grouper { + explicit Grouper(ClipManager& aClipManager) + : mAppUnitsPerDevPixel(0), + mDisplayListBuilder(nullptr), + mClipManager(aClipManager) {} + + int32_t mAppUnitsPerDevPixel; + nsDisplayListBuilder* mDisplayListBuilder; + ClipManager& mClipManager; + Matrix mTransform; + + // Paint the list of aChildren display items. + void PaintContainerItem(DIGroup* aGroup, nsDisplayItem* aItem, + BlobItemData* aData, const IntRect& aItemBounds, + nsDisplayList* aChildren, gfxContext* aContext, + WebRenderDrawEventRecorder* aRecorder, + RenderRootStateManager* aRootManager, + wr::IpcResourceUpdateQueue& aResources); + + // Builds groups of display items split based on 'layer activity' + void ConstructGroups(nsDisplayListBuilder* aDisplayListBuilder, + WebRenderCommandBuilder* aCommandBuilder, + wr::DisplayListBuilder& aBuilder, + wr::IpcResourceUpdateQueue& aResources, DIGroup* aGroup, + nsDisplayList* aList, const StackingContextHelper& aSc); + // Builds a group of display items without promoting anything to active. + void ConstructGroupInsideInactive(WebRenderCommandBuilder* aCommandBuilder, + wr::DisplayListBuilder& aBuilder, + wr::IpcResourceUpdateQueue& aResources, + DIGroup* aGroup, nsDisplayList* aList, + const StackingContextHelper& aSc); + // Helper method for processing a single inactive item + void ConstructItemInsideInactive(WebRenderCommandBuilder* aCommandBuilder, + wr::DisplayListBuilder& aBuilder, + wr::IpcResourceUpdateQueue& aResources, + DIGroup* aGroup, nsDisplayItem* aItem, + const StackingContextHelper& aSc); + ~Grouper() = default; +}; + +// Returns whether this is an item for which complete invalidation was +// reliant on LayerTreeInvalidation in the pre-webrender world. +static bool IsContainerLayerItem(nsDisplayItem* aItem) { + switch (aItem->GetType()) { + case DisplayItemType::TYPE_WRAP_LIST: + case DisplayItemType::TYPE_CONTAINER: + case DisplayItemType::TYPE_TRANSFORM: + case DisplayItemType::TYPE_OPACITY: + case DisplayItemType::TYPE_FILTER: + case DisplayItemType::TYPE_BLEND_CONTAINER: + case DisplayItemType::TYPE_BLEND_MODE: + case DisplayItemType::TYPE_MASK: + case DisplayItemType::TYPE_PERSPECTIVE: { + return true; + } + default: { + return false; + } + } +} + +#include + +static bool DetectContainerLayerPropertiesBoundsChange( + nsDisplayItem* aItem, BlobItemData* aData, + nsDisplayItemGeometry& aGeometry) { + if (aItem->GetType() == DisplayItemType::TYPE_FILTER) { + // Filters go through BasicLayerManager composition which clips to + // the BuildingRect + aGeometry.mBounds = aGeometry.mBounds.Intersect(aItem->GetBuildingRect()); + } + + return !aGeometry.mBounds.IsEqualEdges(aData->mGeometry->mBounds); +} + +struct DIGroup { + // XXX: Storing owning pointers to the BlobItemData in a hash table is not + // a good choice. There are two better options: + // + // 1. We should just be using a linked list for this stuff. + // That we can iterate over only the used items. + // We remove from the unused list and add to the used list + // when we see an item. + // + // we allocate using a free list. + // + // 2. We can use a Vec and use SwapRemove(). + // We'll just need to be careful when iterating. + // The advantage of a Vec is that everything stays compact + // and we don't need to heap allocate the BlobItemData's + nsTHashtable> mDisplayItems; + + IntRect mInvalidRect; + nsRect mGroupBounds; + LayerIntRect mVisibleRect; + // This is the last visible rect sent to WebRender. It's used + // to compute the invalid rect and ensure that we send + // the appropriate data to WebRender for merging. + LayerIntRect mLastVisibleRect; + + // This is the intersection of mVisibleRect and mLastVisibleRect + // we ensure that mInvalidRect is contained in mPreservedRect + IntRect mPreservedRect; + IntRect mActualBounds; + int32_t mAppUnitsPerDevPixel; + gfx::Size mScale; + ScrollableLayerGuid::ViewID mScrollId; + CompositorHitTestInfo mHitInfo; + LayerPoint mResidualOffset; + LayerIntRect mLayerBounds; // mGroupBounds converted to Layer space + // mLayerBounds clipped to the container/parent of the + // current item being processed. + IntRect mClippedImageBounds; // mLayerBounds with the clipping of any + // containers applied + Maybe mKey; + std::vector> mFonts; + + DIGroup() + : mAppUnitsPerDevPixel(0), + mScrollId(ScrollableLayerGuid::NULL_SCROLL_ID), + mHitInfo(CompositorHitTestInvisibleToHit) {} + + void InvalidateRect(const IntRect& aRect) { + auto r = aRect.Intersect(mPreservedRect); + // Empty rects get dropped + if (!r.IsEmpty()) { + mInvalidRect = mInvalidRect.Union(r); + } + } + + IntRect ItemBounds(nsDisplayItem* aItem) { + BlobItemData* data = GetBlobItemData(aItem); + return data->mRect; + } + + void ClearItems() { + GP("items: %d\n", mDisplayItems.Count()); + for (auto iter = mDisplayItems.Iter(); !iter.Done(); iter.Next()) { + BlobItemData* data = iter.Get()->GetKey(); + GP("Deleting %p-%d\n", data->mFrame, data->mDisplayItemKey); + iter.Remove(); + delete data; + } + } + + void ClearImageKey(RenderRootStateManager* aManager, bool aForce = false) { + if (mKey) { + MOZ_RELEASE_ASSERT(aForce || mInvalidRect.IsEmpty()); + aManager->AddBlobImageKeyForDiscard(*mKey); + mKey = Nothing(); + } + mFonts.clear(); + } + + static IntRect ToDeviceSpace(nsRect aBounds, Matrix& aMatrix, + int32_t aAppUnitsPerDevPixel) { + // RoundedOut can convert empty rectangles to non-empty ones + // so special case them here + if (aBounds.IsEmpty()) { + return IntRect(); + } + return RoundedOut(aMatrix.TransformBounds( + ToRect(nsLayoutUtils::RectToGfxRect(aBounds, aAppUnitsPerDevPixel)))); + } + + void ComputeGeometryChange(nsDisplayItem* aItem, BlobItemData* aData, + Matrix& aMatrix, nsDisplayListBuilder* aBuilder) { + // If the frame is marked as invalidated, and didn't specify a rect to + // invalidate then we want to invalidate both the old and new bounds, + // otherwise we only want to invalidate the changed areas. If we do get an + // invalid rect, then we want to add this on top of the change areas. + nsRect invalid; + const DisplayItemClip& clip = aItem->GetClip(); + + int32_t appUnitsPerDevPixel = + aItem->Frame()->PresContext()->AppUnitsPerDevPixel(); + MOZ_RELEASE_ASSERT(mAppUnitsPerDevPixel == appUnitsPerDevPixel); + GP("\n"); + GP("clippedImageRect %d %d %d %d\n", mClippedImageBounds.x, + mClippedImageBounds.y, mClippedImageBounds.width, + mClippedImageBounds.height); + LayerIntSize size = mVisibleRect.Size(); + GP("imageSize: %d %d\n", size.width, size.height); + /*if (aItem->IsReused() && aData->mGeometry) { + return; + }*/ + + GP("pre mInvalidRect: %s %p-%d - inv: %d %d %d %d\n", aItem->Name(), + aItem->Frame(), aItem->GetPerFrameKey(), mInvalidRect.x, mInvalidRect.y, + mInvalidRect.width, mInvalidRect.height); + if (!aData->mGeometry) { + // This item is being added for the first time, invalidate its entire + // area. + UniquePtr geometry( + aItem->AllocateGeometry(aBuilder)); + nsRect clippedBounds = clip.ApplyNonRoundedIntersection( + geometry->ComputeInvalidationRegion()); + aData->mGeometry = std::move(geometry); + + IntRect transformedRect = + ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel); + aData->mRect = transformedRect.Intersect(mClippedImageBounds); + GP("CGC %s %d %d %d %d\n", aItem->Name(), clippedBounds.x, + clippedBounds.y, clippedBounds.width, clippedBounds.height); + GP("%d %d, %f %f\n", mVisibleRect.TopLeft().x, mVisibleRect.TopLeft().y, + aMatrix._11, aMatrix._22); + GP("mRect %d %d %d %d\n", aData->mRect.x, aData->mRect.y, + aData->mRect.width, aData->mRect.height); + InvalidateRect(aData->mRect); + aData->mInvalid = true; + } else if (aData->mInvalid || + /* XXX: handle image load invalidation */ ( + aItem->IsInvalid(invalid) && invalid.IsEmpty())) { + UniquePtr geometry( + aItem->AllocateGeometry(aBuilder)); + nsRect clippedBounds = clip.ApplyNonRoundedIntersection( + geometry->ComputeInvalidationRegion()); + aData->mGeometry = std::move(geometry); + + GP("matrix: %f %f\n", aMatrix._31, aMatrix._32); + GP("frame invalid invalidate: %s\n", aItem->Name()); + GP("old rect: %d %d %d %d\n", aData->mRect.x, aData->mRect.y, + aData->mRect.width, aData->mRect.height); + InvalidateRect(aData->mRect); + // We want to snap to outside pixels. When should we multiply by the + // matrix? + // XXX: TransformBounds is expensive. We should avoid doing it if we have + // no transform + IntRect transformedRect = + ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel); + aData->mRect = transformedRect.Intersect(mClippedImageBounds); + InvalidateRect(aData->mRect); + GP("new rect: %d %d %d %d\n", aData->mRect.x, aData->mRect.y, + aData->mRect.width, aData->mRect.height); + aData->mInvalid = true; + } else { + GP("else invalidate: %s\n", aItem->Name()); + nsRegion combined; + // this includes situations like reflow changing the position + aItem->ComputeInvalidationRegion(aBuilder, aData->mGeometry.get(), + &combined); + if (!combined.IsEmpty()) { + // There might be no point in doing this elaborate tracking here to get + // smaller areas + InvalidateRect(aData->mRect); // invalidate the old area -- in theory + // combined should take care of this + UniquePtr geometry( + aItem->AllocateGeometry(aBuilder)); + // invalidate the invalidated area. + + aData->mGeometry = std::move(geometry); + + nsRect clippedBounds = clip.ApplyNonRoundedIntersection( + aData->mGeometry->ComputeInvalidationRegion()); + IntRect transformedRect = + ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel); + aData->mRect = transformedRect.Intersect(mClippedImageBounds); + InvalidateRect(aData->mRect); + + aData->mInvalid = true; + } else { + if (aData->mClip != clip) { + UniquePtr geometry( + aItem->AllocateGeometry(aBuilder)); + if (!IsContainerLayerItem(aItem)) { + // the bounds of layer items can change on us without + // ComputeInvalidationRegion returning any change. Other items + // shouldn't have any hidden geometry change. + MOZ_RELEASE_ASSERT( + geometry->mBounds.IsEqualEdges(aData->mGeometry->mBounds)); + } else { + aData->mGeometry = std::move(geometry); + } + nsRect clippedBounds = clip.ApplyNonRoundedIntersection( + aData->mGeometry->ComputeInvalidationRegion()); + IntRect transformedRect = + ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel); + InvalidateRect(aData->mRect); + aData->mRect = transformedRect.Intersect(mClippedImageBounds); + InvalidateRect(aData->mRect); + + GP("ClipChange: %s %d %d %d %d\n", aItem->Name(), aData->mRect.x, + aData->mRect.y, aData->mRect.XMost(), aData->mRect.YMost()); + + } else if (!aMatrix.ExactlyEquals(aData->mMatrix)) { + // We haven't detected any changes so far. Unfortunately we don't + // currently have a good way of checking if the transform has changed + // so we just store it and see if it see if it has changed. + // If we want this to go faster, we can probably put a flag on the + // frame using the style sytem UpdateTransformLayer hint and check for + // that. + + UniquePtr geometry( + aItem->AllocateGeometry(aBuilder)); + if (!IsContainerLayerItem(aItem)) { + // the bounds of layer items can change on us + // other items shouldn't + MOZ_RELEASE_ASSERT( + geometry->mBounds.IsEqualEdges(aData->mGeometry->mBounds)); + } else { + aData->mGeometry = std::move(geometry); + } + nsRect clippedBounds = clip.ApplyNonRoundedIntersection( + aData->mGeometry->ComputeInvalidationRegion()); + IntRect transformedRect = + ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel); + InvalidateRect(aData->mRect); + aData->mRect = transformedRect.Intersect(mClippedImageBounds); + InvalidateRect(aData->mRect); + + GP("TransformChange: %s %d %d %d %d\n", aItem->Name(), aData->mRect.x, + aData->mRect.y, aData->mRect.XMost(), aData->mRect.YMost()); + } else if (IsContainerLayerItem(aItem)) { + UniquePtr geometry( + aItem->AllocateGeometry(aBuilder)); + // we need to catch bounds changes of containers so that we continue + // to have the correct bounds rects in the recording + if (DetectContainerLayerPropertiesBoundsChange(aItem, aData, + *geometry)) { + nsRect clippedBounds = clip.ApplyNonRoundedIntersection( + geometry->ComputeInvalidationRegion()); + aData->mGeometry = std::move(geometry); + IntRect transformedRect = + ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel); + InvalidateRect(aData->mRect); + aData->mRect = transformedRect.Intersect(mClippedImageBounds); + InvalidateRect(aData->mRect); + GP("DetectContainerLayerPropertiesBoundsChange change\n"); + } else { + // Handle changes in mClippedImageBounds + nsRect clippedBounds = clip.ApplyNonRoundedIntersection( + geometry->ComputeInvalidationRegion()); + IntRect transformedRect = + ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel); + auto rect = transformedRect.Intersect(mClippedImageBounds); + if (!rect.IsEqualEdges(aData->mRect)) { + GP("ContainerLayer image rect bounds change\n"); + InvalidateRect(aData->mRect); + aData->mRect = rect; + InvalidateRect(aData->mRect); + } else { + GP("Layer NoChange: %s %d %d %d %d\n", aItem->Name(), + aData->mRect.x, aData->mRect.y, aData->mRect.XMost(), + aData->mRect.YMost()); + } + } + } else { + UniquePtr geometry( + aItem->AllocateGeometry(aBuilder)); + nsRect clippedBounds = clip.ApplyNonRoundedIntersection( + geometry->ComputeInvalidationRegion()); + IntRect transformedRect = + ToDeviceSpace(clippedBounds, aMatrix, appUnitsPerDevPixel); + auto rect = transformedRect.Intersect(mClippedImageBounds); + // Make sure we update mRect for mClippedImageBounds changes + if (!rect.IsEqualEdges(aData->mRect)) { + GP("ContainerLayer image rect bounds change\n"); + InvalidateRect(aData->mRect); + aData->mRect = rect; + InvalidateRect(aData->mRect); + } else { + GP("NoChange: %s %d %d %d %d\n", aItem->Name(), aData->mRect.x, + aData->mRect.y, aData->mRect.XMost(), aData->mRect.YMost()); + } + } + } + } + mActualBounds.OrWith(aData->mRect); + aData->mClip = clip; + aData->mMatrix = aMatrix; + aData->mImageRect = mClippedImageBounds; + GP("post mInvalidRect: %d %d %d %d\n", mInvalidRect.x, mInvalidRect.y, + mInvalidRect.width, mInvalidRect.height); + } + + void EndGroup(WebRenderLayerManager* aWrManager, + nsDisplayListBuilder* aDisplayListBuilder, + wr::DisplayListBuilder& aBuilder, + wr::IpcResourceUpdateQueue& aResources, Grouper* aGrouper, + nsDisplayItem* aStartItem, nsDisplayItem* aEndItem) { + GP("\n\n"); + GP("Begin EndGroup\n"); + + mVisibleRect = mVisibleRect.Intersect(ViewAs( + mActualBounds, PixelCastJustification::LayerIsImage)); + + if (mVisibleRect.IsEmpty()) { + return; + } + + // Invalidate any unused items + GP("mDisplayItems\n"); + for (auto iter = mDisplayItems.Iter(); !iter.Done(); iter.Next()) { + BlobItemData* data = iter.Get()->GetKey(); + GP(" : %p-%d\n", data->mFrame, data->mDisplayItemKey); + if (!data->mUsed) { + GP("Invalidate unused: %p-%d\n", data->mFrame, data->mDisplayItemKey); + InvalidateRect(data->mRect); + iter.Remove(); + delete data; + } else { + data->mUsed = false; + } + } + + IntSize dtSize = mVisibleRect.Size().ToUnknownSize(); + // The actual display item's size shouldn't have the scale factored in + // Round the bounds out to leave space for unsnapped content + LayoutDeviceToLayerScale2D scale(mScale.width, mScale.height); + LayoutDeviceRect itemBounds = + (LayerRect(mVisibleRect) - mResidualOffset) / scale; + + if (mInvalidRect.IsEmpty() && mVisibleRect.IsEqualEdges(mLastVisibleRect)) { + GP("Not repainting group because it's empty\n"); + GP("End EndGroup\n"); + if (mKey) { + // Although the contents haven't changed, the visible area *may* have, + // so request it be updated unconditionally (wr should be able to easily + // detect if this is a no-op on its side, if that matters) + aResources.SetBlobImageVisibleArea( + *mKey, ViewAs(mVisibleRect, + PixelCastJustification::LayerIsImage)); + mLastVisibleRect = mVisibleRect; + PushImage(aBuilder, itemBounds); + } + return; + } + + gfx::SurfaceFormat format = gfx::SurfaceFormat::B8G8R8A8; + std::vector> fonts; + bool validFonts = true; + RefPtr recorder = + MakeAndAddRef( + [&](MemStream& aStream, + std::vector>& aScaledFonts) { + size_t count = aScaledFonts.size(); + aStream.write((const char*)&count, sizeof(count)); + for (auto& scaled : aScaledFonts) { + Maybe key = + aWrManager->WrBridge()->GetFontKeyForScaledFont( + scaled, &aResources); + if (key.isNothing()) { + validFonts = false; + break; + } + BlobFont font = {key.value(), scaled}; + aStream.write((const char*)&font, sizeof(font)); + } + fonts = std::move(aScaledFonts); + }); + + RefPtr dummyDt = gfx::Factory::CreateDrawTarget( + gfx::BackendType::SKIA, gfx::IntSize(1, 1), format); + + RefPtr dt = gfx::Factory::CreateRecordingDrawTarget( + recorder, dummyDt, mLayerBounds.ToUnknownRect()); + // Setup the gfxContext + RefPtr context = gfxContext::CreateOrNull(dt); + context->SetMatrix( + Matrix::Scaling(mScale.width, mScale.height) + .PostTranslate(mResidualOffset.x, mResidualOffset.y)); + + GP("mInvalidRect: %d %d %d %d\n", mInvalidRect.x, mInvalidRect.y, + mInvalidRect.width, mInvalidRect.height); + + RenderRootStateManager* rootManager = + aWrManager->GetRenderRootStateManager(); + bool empty = aStartItem == aEndItem; + if (empty) { + ClearImageKey(rootManager, true); + return; + } + + // Reset mHitInfo, it will get updated inside PaintItemRange + mHitInfo = CompositorHitTestInvisibleToHit; + + PaintItemRange(aGrouper, aStartItem, aEndItem, context, recorder, + rootManager, aResources); + + // XXX: set this correctly perhaps using + // aItem->GetOpaqueRegion(aDisplayListBuilder, &snapped). + // Contains(paintBounds);? + wr::OpacityType opacity = wr::OpacityType::HasAlphaChannel; + + bool hasItems = recorder->Finish(); + GP("%d Finish\n", hasItems); + if (!validFonts) { + gfxCriticalNote << "Failed serializing fonts for blob image"; + return; + } + Range bytes((uint8_t*)recorder->mOutputStream.mData, + recorder->mOutputStream.mLength); + if (!mKey) { + // we don't want to send a new image that doesn't have any + // items in it + if (!hasItems || mVisibleRect.IsEmpty()) { + return; + } + + wr::BlobImageKey key = + wr::BlobImageKey{aWrManager->WrBridge()->GetNextImageKey()}; + GP("No previous key making new one %d\n", key._0.mHandle); + wr::ImageDescriptor descriptor(dtSize, 0, dt->GetFormat(), opacity); + MOZ_RELEASE_ASSERT(bytes.length() > sizeof(size_t)); + if (!aResources.AddBlobImage( + key, descriptor, bytes, + ViewAs(mVisibleRect, + PixelCastJustification::LayerIsImage))) { + return; + } + mKey = Some(key); + } else { + wr::ImageDescriptor descriptor(dtSize, 0, dt->GetFormat(), opacity); + + // Convert mInvalidRect to image space by subtracting the corner of the + // image bounds + auto dirtyRect = ViewAs(mInvalidRect); + + auto bottomRight = dirtyRect.BottomRight(); + GP("check invalid %d %d - %d %d\n", bottomRight.x, bottomRight.y, + dtSize.width, dtSize.height); + GP("Update Blob %d %d %d %d\n", mInvalidRect.x, mInvalidRect.y, + mInvalidRect.width, mInvalidRect.height); + if (!aResources.UpdateBlobImage( + *mKey, descriptor, bytes, + ViewAs(mVisibleRect, + PixelCastJustification::LayerIsImage), + dirtyRect)) { + return; + } + } + mFonts = std::move(fonts); + aResources.SetBlobImageVisibleArea( + *mKey, + ViewAs(mVisibleRect, PixelCastJustification::LayerIsImage)); + mLastVisibleRect = mVisibleRect; + PushImage(aBuilder, itemBounds); + GP("End EndGroup\n\n"); + } + + void PushImage(wr::DisplayListBuilder& aBuilder, + const LayoutDeviceRect& bounds) { + wr::LayoutRect dest = wr::ToLayoutRect(bounds); + GP("PushImage: %f %f %f %f\n", dest.origin.x, dest.origin.y, + dest.size.width, dest.size.height); + gfx::SamplingFilter sampleFilter = gfx::SamplingFilter:: + LINEAR; // nsLayoutUtils::GetSamplingFilterForFrame(aItem->Frame()); + bool backfaceHidden = false; + + // We don't really know the exact shape of this blob because it may contain + // SVG shapes. Also mHitInfo may be a combination of hit info flags from + // different shapes so generate an irregular-area hit-test region for it. + CompositorHitTestInfo hitInfo = mHitInfo; + if (hitInfo.contains(CompositorHitTestFlags::eVisibleToHitTest)) { + hitInfo += CompositorHitTestFlags::eIrregularArea; + } + + // XXX - clipping the item against the paint rect breaks some content. + // cf. Bug 1455422. + // wr::LayoutRect clip = wr::ToLayoutRect(bounds.Intersect(mVisibleRect)); + + aBuilder.PushHitTest(dest, dest, !backfaceHidden, mScrollId, hitInfo, + SideBits::eNone); + + aBuilder.PushImage(dest, dest, !backfaceHidden, + wr::ToImageRendering(sampleFilter), + wr::AsImageKey(*mKey)); + } + + void PaintItemRange(Grouper* aGrouper, nsDisplayItem* aStartItem, + nsDisplayItem* aEndItem, gfxContext* aContext, + WebRenderDrawEventRecorder* aRecorder, + RenderRootStateManager* aRootManager, + wr::IpcResourceUpdateQueue& aResources) { + LayerIntSize size = mVisibleRect.Size(); + for (nsDisplayItem* item = aStartItem; item != aEndItem; + item = item->GetAbove()) { + BlobItemData* data = GetBlobItemData(item); + IntRect bounds = data->mRect; + auto bottomRight = bounds.BottomRight(); + + GP("Trying %s %p-%d %d %d %d %d\n", item->Name(), item->Frame(), + item->GetPerFrameKey(), bounds.x, bounds.y, bounds.XMost(), + bounds.YMost()); + + if (item->GetType() == DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO) { + // Accumulate the hit-test info flags. In cases where there are multiple + // hittest-info display items with different flags, mHitInfo will have + // the union of all those flags. If that is the case, we will + // additionally set eIrregularArea (at the site that we use mHitInfo) + // so that downstream consumers of this (primarily APZ) will know that + // the exact shape of what gets hit with what is unknown. + mHitInfo += + static_cast(item)->HitTestFlags(); + continue; + } + + GP("paint check invalid %d %d - %d %d\n", bottomRight.x, bottomRight.y, + size.width, size.height); + // skip empty items + if (bounds.IsEmpty()) { + continue; + } + + bool dirty = true; + auto preservedBounds = bounds.Intersect(mPreservedRect); + if (!mInvalidRect.Contains(preservedBounds)) { + GP("Passing\n"); + dirty = false; + BlobItemData* data = GetBlobItemData(item); + if (data->mInvalid) { + gfxCriticalError() + << "DisplayItem" << item->Name() << "-should be invalid"; + } + // if the item is invalid it needs to be fully contained + MOZ_RELEASE_ASSERT(!data->mInvalid); + } + + nsDisplayList* children = item->GetChildren(); + if (children) { + GP("doing children in EndGroup\n"); + aGrouper->PaintContainerItem(this, item, data, bounds, children, + aContext, aRecorder, aRootManager, + aResources); + continue; + } + nsPaintedDisplayItem* paintedItem = item->AsPaintedDisplayItem(); + if (!paintedItem) { + continue; + } + if (dirty) { + // What should the clip settting strategy be? We can set the full + // clip everytime. this is probably easiest for now. An alternative + // would be to put the push and the pop into separate items and let + // invalidation handle it that way. + DisplayItemClip currentClip = paintedItem->GetClip(); + + if (currentClip.HasClip()) { + aContext->Save(); + currentClip.ApplyTo(aContext, aGrouper->mAppUnitsPerDevPixel); + } + aContext->NewPath(); + GP("painting %s %p-%d\n", paintedItem->Name(), paintedItem->Frame(), + paintedItem->GetPerFrameKey()); + if (aGrouper->mDisplayListBuilder->IsPaintingToWindow()) { + paintedItem->Frame()->AddStateBits(NS_FRAME_PAINTED_THEBES); + } + + paintedItem->Paint(aGrouper->mDisplayListBuilder, aContext); + TakeExternalSurfaces(aRecorder, data->mExternalSurfaces, aRootManager, + aResources); + + if (currentClip.HasClip()) { + aContext->Restore(); + } + } + aContext->GetDrawTarget()->FlushItem(bounds); + } + } + + ~DIGroup() { + GP("Group destruct\n"); + for (auto iter = mDisplayItems.Iter(); !iter.Done(); iter.Next()) { + BlobItemData* data = iter.Get()->GetKey(); + GP("Deleting %p-%d\n", data->mFrame, data->mDisplayItemKey); + iter.Remove(); + delete data; + } + } +}; + +// If we have an item we need to make sure it matches the current group +// otherwise it means the item switched groups and we need to invalidate +// it and recreate the data. +static BlobItemData* GetBlobItemDataForGroup(nsDisplayItem* aItem, + DIGroup* aGroup) { + BlobItemData* data = GetBlobItemData(aItem); + if (data) { + MOZ_RELEASE_ASSERT(data->mGroup->mDisplayItems.Contains(data)); + if (data->mGroup != aGroup) { + GP("group don't match %p %p\n", data->mGroup, aGroup); + data->ClearFrame(); + // the item is for another group + // it should be cleared out as being unused at the end of this paint + data = nullptr; + } + } + if (!data) { + GP("Allocating blob data\n"); + data = new BlobItemData(aGroup, aItem); + aGroup->mDisplayItems.PutEntry(data); + } + data->mUsed = true; + return data; +} + +void Grouper::PaintContainerItem(DIGroup* aGroup, nsDisplayItem* aItem, + BlobItemData* aData, + const IntRect& aItemBounds, + nsDisplayList* aChildren, gfxContext* aContext, + WebRenderDrawEventRecorder* aRecorder, + RenderRootStateManager* aRootManager, + wr::IpcResourceUpdateQueue& aResources) { + switch (aItem->GetType()) { + case DisplayItemType::TYPE_TRANSFORM: { + DisplayItemClip currentClip = aItem->GetClip(); + + gfxContextMatrixAutoSaveRestore saveMatrix; + if (currentClip.HasClip()) { + aContext->Save(); + currentClip.ApplyTo(aContext, this->mAppUnitsPerDevPixel); + aContext->GetDrawTarget()->FlushItem(aItemBounds); + } else { + saveMatrix.SetContext(aContext); + } + + auto transformItem = static_cast(aItem); + Matrix4x4Flagged trans = transformItem->GetTransform(); + Matrix trans2d; + if (!trans.Is2D(&trans2d)) { + // We don't currently support doing invalidation inside 3d transforms. + // For now just paint it as a single item. + BlobItemData* data = GetBlobItemDataForGroup(aItem, aGroup); + if (data->mLayerManager->GetRoot()) { + data->mLayerManager->BeginTransaction(); + data->mLayerManager->EndTransaction( + FrameLayerBuilder::DrawPaintedLayer, mDisplayListBuilder); + TakeExternalSurfaces(aRecorder, data->mExternalSurfaces, aRootManager, + aResources); + aContext->GetDrawTarget()->FlushItem(aItemBounds); + } + } else { + aContext->Multiply(ThebesMatrix(trans2d)); + aGroup->PaintItemRange(this, aChildren->GetBottom(), nullptr, aContext, + aRecorder, aRootManager, aResources); + } + + if (currentClip.HasClip()) { + aContext->Restore(); + aContext->GetDrawTarget()->FlushItem(aItemBounds); + } + break; + } + case DisplayItemType::TYPE_OPACITY: { + auto opacityItem = static_cast(aItem); + float opacity = opacityItem->GetOpacity(); + if (opacity == 0.0f) { + return; + } + + aContext->GetDrawTarget()->PushLayer(false, opacityItem->GetOpacity(), + nullptr, mozilla::gfx::Matrix(), + aItemBounds); + GP("beginGroup %s %p-%d\n", aItem->Name(), aItem->Frame(), + aItem->GetPerFrameKey()); + aContext->GetDrawTarget()->FlushItem(aItemBounds); + aGroup->PaintItemRange(this, aChildren->GetBottom(), nullptr, aContext, + aRecorder, aRootManager, aResources); + aContext->GetDrawTarget()->PopLayer(); + GP("endGroup %s %p-%d\n", aItem->Name(), aItem->Frame(), + aItem->GetPerFrameKey()); + aContext->GetDrawTarget()->FlushItem(aItemBounds); + break; + } + case DisplayItemType::TYPE_BLEND_MODE: { + auto blendItem = static_cast(aItem); + auto blendMode = blendItem->BlendMode(); + aContext->GetDrawTarget()->PushLayerWithBlend( + false, 1.0, nullptr, mozilla::gfx::Matrix(), aItemBounds, false, + blendMode); + GP("beginGroup %s %p-%d\n", aItem->Name(), aItem->Frame(), + aItem->GetPerFrameKey()); + aContext->GetDrawTarget()->FlushItem(aItemBounds); + aGroup->PaintItemRange(this, aChildren->GetBottom(), nullptr, aContext, + aRecorder, aRootManager, aResources); + aContext->GetDrawTarget()->PopLayer(); + GP("endGroup %s %p-%d\n", aItem->Name(), aItem->Frame(), + aItem->GetPerFrameKey()); + aContext->GetDrawTarget()->FlushItem(aItemBounds); + break; + } + case DisplayItemType::TYPE_BLEND_CONTAINER: { + aContext->GetDrawTarget()->PushLayer(false, 1.0, nullptr, + mozilla::gfx::Matrix(), aItemBounds); + GP("beginGroup %s %p-%d\n", aItem->Name(), aItem->Frame(), + aItem->GetPerFrameKey()); + aContext->GetDrawTarget()->FlushItem(aItemBounds); + aGroup->PaintItemRange(this, aChildren->GetBottom(), nullptr, aContext, + aRecorder, aRootManager, aResources); + aContext->GetDrawTarget()->PopLayer(); + GP("endGroup %s %p-%d\n", aItem->Name(), aItem->Frame(), + aItem->GetPerFrameKey()); + aContext->GetDrawTarget()->FlushItem(aItemBounds); + break; + } + case DisplayItemType::TYPE_MASK: { + GP("Paint Mask\n"); + auto maskItem = static_cast(aItem); + maskItem->SetPaintRect(maskItem->GetClippedBounds(mDisplayListBuilder)); + if (maskItem->IsValidMask()) { + maskItem->PaintWithContentsPaintCallback( + mDisplayListBuilder, aContext, [&] { + GP("beginGroup %s %p-%d\n", aItem->Name(), aItem->Frame(), + aItem->GetPerFrameKey()); + aContext->GetDrawTarget()->FlushItem(aItemBounds); + aGroup->PaintItemRange(this, aChildren->GetBottom(), nullptr, + aContext, aRecorder, aRootManager, + aResources); + GP("endGroup %s %p-%d\n", aItem->Name(), aItem->Frame(), + aItem->GetPerFrameKey()); + }); + TakeExternalSurfaces(aRecorder, aData->mExternalSurfaces, aRootManager, + aResources); + aContext->GetDrawTarget()->FlushItem(aItemBounds); + } + break; + } + case DisplayItemType::TYPE_FILTER: { + GP("Paint Filter\n"); + // We don't currently support doing invalidation inside nsDisplayFilters + // for now just paint it as a single item + BlobItemData* data = GetBlobItemDataForGroup(aItem, aGroup); + if (data->mLayerManager->GetRoot()) { + data->mLayerManager->BeginTransaction(); + static_cast(aItem)->PaintAsLayer( + mDisplayListBuilder, aContext, data->mLayerManager); + if (data->mLayerManager->InTransaction()) { + data->mLayerManager->AbortTransaction(); + } + TakeExternalSurfaces(aRecorder, data->mExternalSurfaces, aRootManager, + aResources); + aContext->GetDrawTarget()->FlushItem(aItemBounds); + } + break; + } + + default: + aGroup->PaintItemRange(this, aChildren->GetBottom(), nullptr, aContext, + aRecorder, aRootManager, aResources); + break; + } +} + +class WebRenderGroupData : public WebRenderUserData { + public: + WebRenderGroupData(RenderRootStateManager* aWRManager, nsDisplayItem* aItem); + virtual ~WebRenderGroupData(); + + WebRenderGroupData* AsGroupData() override { return this; } + UserDataType GetType() override { return UserDataType::eGroup; } + static UserDataType Type() { return UserDataType::eGroup; } + + DIGroup mSubGroup; + DIGroup mFollowingGroup; +}; + +static bool IsItemProbablyActive( + nsDisplayItem* aItem, mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder, bool aSiblingActive); + +static bool HasActiveChildren(const nsDisplayList& aList, + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + for (nsDisplayItem* i = aList.GetBottom(); i; i = i->GetAbove()) { + if (IsItemProbablyActive(i, aBuilder, aResources, aSc, aManager, + aDisplayListBuilder, false)) { + return true; + } + } + return false; +} + +// This function decides whether we want to treat this item as "active", which +// means that it's a container item which we will turn into a WebRender +// StackingContext, or whether we treat it as "inactive" and include it inside +// the parent blob image. +// +// We can't easily use GetLayerState because it wants a bunch of layers related +// information. +static bool IsItemProbablyActive( + nsDisplayItem* aItem, mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const mozilla::layers::StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder, + bool aHasActivePrecedingSibling) { + switch (aItem->GetType()) { + case DisplayItemType::TYPE_TRANSFORM: { + nsDisplayTransform* transformItem = + static_cast(aItem); + const Matrix4x4Flagged& t = transformItem->GetTransform(); + Matrix t2d; + bool is2D = t.Is2D(&t2d); + GP("active: %d\n", transformItem->MayBeAnimated(aDisplayListBuilder)); + return transformItem->MayBeAnimated(aDisplayListBuilder, false) || + !is2D || + HasActiveChildren(*transformItem->GetChildren(), aBuilder, + aResources, aSc, aManager, aDisplayListBuilder); + } + case DisplayItemType::TYPE_OPACITY: { + nsDisplayOpacity* opacityItem = static_cast(aItem); + bool active = opacityItem->NeedsActiveLayer(aDisplayListBuilder, + opacityItem->Frame(), false); + GP("active: %d\n", active); + return active || + HasActiveChildren(*opacityItem->GetChildren(), aBuilder, + aResources, aSc, aManager, aDisplayListBuilder); + } + case DisplayItemType::TYPE_FOREIGN_OBJECT: { + return true; + } + case DisplayItemType::TYPE_SVG_GEOMETRY: { + if (StaticPrefs::gfx_webrender_svg_images()) { + auto* svgItem = static_cast(aItem); + return svgItem->ShouldBeActive(aBuilder, aResources, aSc, aManager, + aDisplayListBuilder); + } + return false; + } + case DisplayItemType::TYPE_BLEND_MODE: { + /* BLEND_MODE needs to be active if it might have a previous sibling + * that is active so that it's able to blend with that content. */ + return aHasActivePrecedingSibling || + HasActiveChildren(*aItem->GetChildren(), aBuilder, aResources, aSc, + aManager, aDisplayListBuilder); + } + case DisplayItemType::TYPE_WRAP_LIST: + case DisplayItemType::TYPE_CONTAINER: + case DisplayItemType::TYPE_MASK: + case DisplayItemType::TYPE_PERSPECTIVE: { + if (aItem->GetChildren()) { + return HasActiveChildren(*aItem->GetChildren(), aBuilder, aResources, + aSc, aManager, aDisplayListBuilder); + } + return false; + } + case DisplayItemType::TYPE_FILTER: { + nsDisplayFilters* filters = static_cast(aItem); + return filters->CanCreateWebRenderCommands(); + } + default: + // TODO: handle other items? + return false; + } +} + +// This does a pass over the display lists and will join the display items +// into groups as well as paint them +void Grouper::ConstructGroups(nsDisplayListBuilder* aDisplayListBuilder, + WebRenderCommandBuilder* aCommandBuilder, + wr::DisplayListBuilder& aBuilder, + wr::IpcResourceUpdateQueue& aResources, + DIGroup* aGroup, nsDisplayList* aList, + const StackingContextHelper& aSc) { + DIGroup* currentGroup = aGroup; + + nsDisplayItem* item = aList->GetBottom(); + nsDisplayItem* startOfCurrentGroup = item; + RenderRootStateManager* manager = + aCommandBuilder->mManager->GetRenderRootStateManager(); + // We need to track whether we have active siblings for mixed blend mode. + bool encounteredActiveItem = false; + while (item) { + if (IsItemProbablyActive(item, aBuilder, aResources, aSc, manager, + mDisplayListBuilder, encounteredActiveItem)) { + encounteredActiveItem = true; + // We're going to be starting a new group. + RefPtr groupData = + aCommandBuilder->CreateOrRecycleWebRenderUserData( + item); + + groupData->mFollowingGroup.mInvalidRect.SetEmpty(); + + // Initialize groupData->mFollowingGroup with data from currentGroup. + // We want to copy out this information before calling EndGroup because + // EndGroup will set mLastVisibleRect depending on whether + // we send something to WebRender. + + // TODO: compute the group bounds post-grouping, so that they can be + // tighter for just the sublist that made it into this group. + // We want to ensure the tight bounds are still clipped by area + // that we're building the display list for. + if (groupData->mFollowingGroup.mScale != currentGroup->mScale || + groupData->mFollowingGroup.mAppUnitsPerDevPixel != + currentGroup->mAppUnitsPerDevPixel || + groupData->mFollowingGroup.mResidualOffset != + currentGroup->mResidualOffset) { + if (groupData->mFollowingGroup.mAppUnitsPerDevPixel != + currentGroup->mAppUnitsPerDevPixel) { + GP("app unit change following: %d %d\n", + groupData->mFollowingGroup.mAppUnitsPerDevPixel, + currentGroup->mAppUnitsPerDevPixel); + } + // The group changed size + GP("Inner group size change\n"); + groupData->mFollowingGroup.ClearItems(); + groupData->mFollowingGroup.ClearImageKey( + aCommandBuilder->mManager->GetRenderRootStateManager()); + } + groupData->mFollowingGroup.mGroupBounds = currentGroup->mGroupBounds; + groupData->mFollowingGroup.mAppUnitsPerDevPixel = + currentGroup->mAppUnitsPerDevPixel; + groupData->mFollowingGroup.mLayerBounds = currentGroup->mLayerBounds; + groupData->mFollowingGroup.mClippedImageBounds = + currentGroup->mClippedImageBounds; + groupData->mFollowingGroup.mScale = currentGroup->mScale; + groupData->mFollowingGroup.mResidualOffset = + currentGroup->mResidualOffset; + groupData->mFollowingGroup.mVisibleRect = currentGroup->mVisibleRect; + groupData->mFollowingGroup.mPreservedRect = + groupData->mFollowingGroup.mVisibleRect + .Intersect(groupData->mFollowingGroup.mLastVisibleRect) + .ToUnknownRect(); + groupData->mFollowingGroup.mActualBounds = IntRect(); + + currentGroup->EndGroup(aCommandBuilder->mManager, aDisplayListBuilder, + aBuilder, aResources, this, startOfCurrentGroup, + item); + + { + auto spaceAndClipChain = mClipManager.SwitchItem(item); + wr::SpaceAndClipChainHelper saccHelper(aBuilder, spaceAndClipChain); + + sIndent++; + // Note: this call to CreateWebRenderCommands can recurse back into + // this function. + bool createdWRCommands = item->CreateWebRenderCommands( + aBuilder, aResources, aSc, manager, mDisplayListBuilder); + sIndent--; + MOZ_RELEASE_ASSERT( + createdWRCommands, + "active transforms should always succeed at creating " + "WebRender commands"); + } + + currentGroup = &groupData->mFollowingGroup; + + startOfCurrentGroup = item->GetAbove(); + } else { // inactive item + ConstructItemInsideInactive(aCommandBuilder, aBuilder, aResources, + currentGroup, item, aSc); + } + + item = item->GetAbove(); + } + + currentGroup->EndGroup(aCommandBuilder->mManager, aDisplayListBuilder, + aBuilder, aResources, this, startOfCurrentGroup, + nullptr); +} + +// This does a pass over the display lists and will join the display items +// into a single group. +void Grouper::ConstructGroupInsideInactive( + WebRenderCommandBuilder* aCommandBuilder, wr::DisplayListBuilder& aBuilder, + wr::IpcResourceUpdateQueue& aResources, DIGroup* aGroup, + nsDisplayList* aList, const StackingContextHelper& aSc) { + nsDisplayItem* item = aList->GetBottom(); + while (item) { + ConstructItemInsideInactive(aCommandBuilder, aBuilder, aResources, aGroup, + item, aSc); + item = item->GetAbove(); + } +} + +bool BuildLayer(nsDisplayItem* aItem, BlobItemData* aData, + nsDisplayListBuilder* aDisplayListBuilder, + const gfx::Size& aScale); + +void Grouper::ConstructItemInsideInactive( + WebRenderCommandBuilder* aCommandBuilder, wr::DisplayListBuilder& aBuilder, + wr::IpcResourceUpdateQueue& aResources, DIGroup* aGroup, + nsDisplayItem* aItem, const StackingContextHelper& aSc) { + nsDisplayList* children = aItem->GetChildren(); + BlobItemData* data = GetBlobItemDataForGroup(aItem, aGroup); + + /* mInvalid unfortunately persists across paints. Clear it so that if we don't + * set it to 'true' we ensure that we're not using the value from the last + * time that we painted */ + data->mInvalid = false; + + // we compute the geometry change here because we have the transform around + // still + aGroup->ComputeGeometryChange(aItem, data, mTransform, mDisplayListBuilder); + + // Temporarily restrict the image bounds to the bounds of the container so + // that clipped children within the container know about the clip. This + // ensures that the bounds passed to FlushItem are contained in the bounds of + // the clip so that we don't include items in the recording without including + // their corresponding clipping items. + IntRect oldClippedImageBounds = aGroup->mClippedImageBounds; + aGroup->mClippedImageBounds = + aGroup->mClippedImageBounds.Intersect(data->mRect); + + if (aItem->GetType() == DisplayItemType::TYPE_FILTER) { + gfx::Size scale(1, 1); + // If ComputeDifferences finds any change, we invalidate the entire + // container item. This is needed because blob merging requires the entire + // item to be within the invalid region. + if (BuildLayer(aItem, data, mDisplayListBuilder, scale)) { + data->mInvalid = true; + aGroup->InvalidateRect(data->mRect); + } + } else if (aItem->GetType() == DisplayItemType::TYPE_TRANSFORM) { + nsDisplayTransform* transformItem = static_cast(aItem); + const Matrix4x4Flagged& t = transformItem->GetTransform(); + Matrix t2d; + bool is2D = t.Is2D(&t2d); + if (!is2D) { + // We'll use BasicLayerManager to handle 3d transforms. + gfx::Size scale(1, 1); + // If ComputeDifferences finds any change, we invalidate the entire + // container item. This is needed because blob merging requires the entire + // item to be within the invalid region. + if (BuildLayer(aItem, data, mDisplayListBuilder, scale)) { + data->mInvalid = true; + aGroup->InvalidateRect(data->mRect); + } + } else { + Matrix m = mTransform; + + GP("t2d: %f %f\n", t2d._31, t2d._32); + mTransform.PreMultiply(t2d); + GP("mTransform: %f %f\n", mTransform._31, mTransform._32); + ConstructGroupInsideInactive(aCommandBuilder, aBuilder, aResources, + aGroup, children, aSc); + + mTransform = m; + } + } else if (children) { + sIndent++; + ConstructGroupInsideInactive(aCommandBuilder, aBuilder, aResources, aGroup, + children, aSc); + sIndent--; + } + + GP("Including %s of %d\n", aItem->Name(), aGroup->mDisplayItems.Count()); + aGroup->mClippedImageBounds = oldClippedImageBounds; +} + +/* This is just a copy of nsRect::ScaleToOutsidePixels with an offset added in. + * The offset is applied just before the rounding. It's in the scaled space. */ +static mozilla::LayerIntRect ScaleToOutsidePixelsOffset( + nsRect aRect, float aXScale, float aYScale, nscoord aAppUnitsPerPixel, + LayerPoint aOffset) { + mozilla::LayerIntRect rect; + rect.SetNonEmptyBox( + NSToIntFloor(NSAppUnitsToFloatPixels(aRect.x, float(aAppUnitsPerPixel)) * + aXScale + + aOffset.x), + NSToIntFloor(NSAppUnitsToFloatPixels(aRect.y, float(aAppUnitsPerPixel)) * + aYScale + + aOffset.y), + NSToIntCeil( + NSAppUnitsToFloatPixels(aRect.XMost(), float(aAppUnitsPerPixel)) * + aXScale + + aOffset.x), + NSToIntCeil( + NSAppUnitsToFloatPixels(aRect.YMost(), float(aAppUnitsPerPixel)) * + aYScale + + aOffset.y)); + return rect; +} + +/* This function is the same as the above except that it rounds to the + * nearest instead of rounding out. We use it for attempting to compute the + * actual pixel bounds of opaque items */ +static mozilla::gfx::IntRect ScaleToNearestPixelsOffset( + nsRect aRect, float aXScale, float aYScale, nscoord aAppUnitsPerPixel, + LayerPoint aOffset) { + mozilla::gfx::IntRect rect; + rect.SetNonEmptyBox( + NSToIntFloor(NSAppUnitsToFloatPixels(aRect.x, float(aAppUnitsPerPixel)) * + aXScale + + aOffset.x + 0.5), + NSToIntFloor(NSAppUnitsToFloatPixels(aRect.y, float(aAppUnitsPerPixel)) * + aYScale + + aOffset.y + 0.5), + NSToIntFloor( + NSAppUnitsToFloatPixels(aRect.XMost(), float(aAppUnitsPerPixel)) * + aXScale + + aOffset.x + 0.5), + NSToIntFloor( + NSAppUnitsToFloatPixels(aRect.YMost(), float(aAppUnitsPerPixel)) * + aYScale + + aOffset.y + 0.5)); + return rect; +} + +RenderRootStateManager* WebRenderCommandBuilder::GetRenderRootStateManager() { + return mManager->GetRenderRootStateManager(); +} + +void WebRenderCommandBuilder::DoGroupingForDisplayList( + nsDisplayList* aList, nsDisplayItem* aWrappingItem, + nsDisplayListBuilder* aDisplayListBuilder, const StackingContextHelper& aSc, + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources) { + if (!aList->GetBottom()) { + return; + } + + GP("DoGroupingForDisplayList\n"); + + mClipManager.BeginList(aSc); + Grouper g(mClipManager); + + int32_t appUnitsPerDevPixel = + aWrappingItem->Frame()->PresContext()->AppUnitsPerDevPixel(); + + g.mDisplayListBuilder = aDisplayListBuilder; + RefPtr groupData = + CreateOrRecycleWebRenderUserData(aWrappingItem); + + bool snapped; + nsRect groupBounds = + aWrappingItem->GetUntransformedBounds(aDisplayListBuilder, &snapped); + DIGroup& group = groupData->mSubGroup; + + gfx::Size scale = aSc.GetInheritedScale(); + GP("Inherrited scale %f %f\n", scale.width, scale.height); + + auto trans = + ViewAs(aSc.GetSnappingSurfaceTransform().GetTranslation()); + auto snappedTrans = LayerIntPoint::Floor(trans); + LayerPoint residualOffset = trans - snappedTrans; + + auto p = group.mGroupBounds; + auto q = groupBounds; + // XXX: we currently compute the paintRect for the entire svg, but if the svg + // gets split into multiple groups (blobs), then they will all inherit this + // overall size even though they may each be much smaller. This can lead to + // allocating much larger textures than necessary in webrender. + // + // Don't bother fixing this unless we run into this in the real world, though. + auto layerBounds = + ScaleToOutsidePixelsOffset(groupBounds, scale.width, scale.height, + appUnitsPerDevPixel, residualOffset); + + const nsRect& untransformedPaintRect = + aWrappingItem->GetUntransformedPaintRect(); + + auto visibleRect = ScaleToOutsidePixelsOffset( + untransformedPaintRect, scale.width, scale.height, + appUnitsPerDevPixel, residualOffset) + .Intersect(layerBounds); + + GP("LayerBounds: %d %d %d %d\n", layerBounds.x, layerBounds.y, + layerBounds.width, layerBounds.height); + GP("VisibleRect: %d %d %d %d\n", visibleRect.x, visibleRect.y, + visibleRect.width, visibleRect.height); + + GP("Inherrited scale %f %f\n", scale.width, scale.height); + GP("Bounds: %d %d %d %d vs %d %d %d %d\n", p.x, p.y, p.width, p.height, q.x, + q.y, q.width, q.height); + + group.mInvalidRect.SetEmpty(); + if (group.mAppUnitsPerDevPixel != appUnitsPerDevPixel || + group.mScale != scale || group.mResidualOffset != residualOffset) { + GP("Property change. Deleting blob\n"); + + if (group.mAppUnitsPerDevPixel != appUnitsPerDevPixel) { + GP(" App unit change %d -> %d\n", group.mAppUnitsPerDevPixel, + appUnitsPerDevPixel); + } + // The bounds have changed so we need to discard the old image and add all + // the commands again. + auto p = group.mGroupBounds; + auto q = groupBounds; + if (!group.mGroupBounds.IsEqualEdges(groupBounds)) { + GP(" Bounds change: %d %d %d %d -> %d %d %d %d\n", p.x, p.y, p.width, + p.height, q.x, q.y, q.width, q.height); + } + + if (group.mScale != scale) { + GP(" Scale %f %f -> %f %f\n", group.mScale.width, group.mScale.height, + scale.width, scale.height); + } + + if (group.mResidualOffset != residualOffset) { + GP(" Residual Offset %f %f -> %f %f\n", group.mResidualOffset.x, + group.mResidualOffset.y, residualOffset.x, residualOffset.y); + } + + group.ClearItems(); + group.ClearImageKey(mManager->GetRenderRootStateManager()); + } + + ScrollableLayerGuid::ViewID scrollId = ScrollableLayerGuid::NULL_SCROLL_ID; + if (const ActiveScrolledRoot* asr = aWrappingItem->GetActiveScrolledRoot()) { + scrollId = asr->GetViewId(); + } + + g.mAppUnitsPerDevPixel = appUnitsPerDevPixel; + group.mResidualOffset = residualOffset; + group.mGroupBounds = groupBounds; + group.mLayerBounds = layerBounds; + group.mVisibleRect = visibleRect; + group.mActualBounds = IntRect(); + group.mPreservedRect = + group.mVisibleRect.Intersect(group.mLastVisibleRect).ToUnknownRect(); + group.mAppUnitsPerDevPixel = appUnitsPerDevPixel; + group.mClippedImageBounds = layerBounds.ToUnknownRect(); + + g.mTransform = Matrix::Scaling(scale.width, scale.height) + .PostTranslate(residualOffset.x, residualOffset.y); + group.mScale = scale; + group.mScrollId = scrollId; + g.ConstructGroups(aDisplayListBuilder, this, aBuilder, aResources, &group, + aList, aSc); + mClipManager.EndList(aSc); +} + +WebRenderCommandBuilder::WebRenderCommandBuilder( + WebRenderLayerManager* aManager) + : mManager(aManager), + mLastAsr(nullptr), + mBuilderDumpIndex(0), + mDumpIndent(0), + mDoGrouping(false), + mContainsSVGGroup(false) {} + +void WebRenderCommandBuilder::Destroy() { + mLastCanvasDatas.Clear(); + mLastLocalCanvasDatas.Clear(); + ClearCachedResources(); +} + +void WebRenderCommandBuilder::EmptyTransaction() { + // We need to update canvases that might have changed. + for (auto iter = mLastCanvasDatas.Iter(); !iter.Done(); iter.Next()) { + RefPtr canvasData = iter.Get()->GetKey(); + WebRenderCanvasRendererAsync* canvas = canvasData->GetCanvasRenderer(); + if (canvas) { + canvas->UpdateCompositableClientForEmptyTransaction(); + } + } + for (auto iter = mLastLocalCanvasDatas.Iter(); !iter.Done(); iter.Next()) { + RefPtr canvasData = iter.Get()->GetKey(); + canvasData->RefreshExternalImage(); + canvasData->RequestFrameReadback(); + } +} + +bool WebRenderCommandBuilder::NeedsEmptyTransaction() { + return !mLastCanvasDatas.IsEmpty() || !mLastLocalCanvasDatas.IsEmpty(); +} + +void WebRenderCommandBuilder::BuildWebRenderCommands( + wr::DisplayListBuilder& aBuilder, + wr::IpcResourceUpdateQueue& aResourceUpdates, nsDisplayList* aDisplayList, + nsDisplayListBuilder* aDisplayListBuilder, WebRenderScrollData& aScrollData, + WrFiltersHolder&& aFilters) { + AUTO_PROFILER_LABEL_CATEGORY_PAIR(GRAPHICS_WRDisplayList); + + StackingContextHelper sc; + aScrollData = WebRenderScrollData(mManager, aDisplayListBuilder); + MOZ_ASSERT(mLayerScrollData.empty()); + mClipManager.BeginBuild(mManager, aBuilder); + mBuilderDumpIndex = 0; + mLastCanvasDatas.Clear(); + mLastLocalCanvasDatas.Clear(); + mLastAsr = nullptr; + mContainsSVGGroup = false; + MOZ_ASSERT(mDumpIndent == 0); + + { + wr::StackingContextParams params; + params.mFilters = std::move(aFilters.filters); + params.mFilterDatas = std::move(aFilters.filter_datas); + params.clip = + wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId()); + + StackingContextHelper pageRootSc(sc, nullptr, nullptr, nullptr, aBuilder, + params); + if (ShouldDumpDisplayList(aDisplayListBuilder)) { + mBuilderDumpIndex = + aBuilder.Dump(mDumpIndent + 1, Some(mBuilderDumpIndex), Nothing()); + } + CreateWebRenderCommandsFromDisplayList(aDisplayList, nullptr, + aDisplayListBuilder, pageRootSc, + aBuilder, aResourceUpdates); + } + + // Make a "root" layer data that has everything else as descendants + mLayerScrollData.emplace_back(); + mLayerScrollData.back().InitializeRoot(mLayerScrollData.size() - 1); + auto callback = + [&aScrollData](ScrollableLayerGuid::ViewID aScrollId) -> bool { + return aScrollData.HasMetadataFor(aScrollId).isSome(); + }; + Maybe rootMetadata = nsLayoutUtils::GetRootMetadata( + aDisplayListBuilder, mManager, ContainerLayerParameters(), callback); + if (rootMetadata) { + // Put the fallback root metadata on the rootmost layer that is + // a matching async zoom container, or the root layer that we just + // created above. + size_t rootMetadataTarget = mLayerScrollData.size() - 1; + for (size_t i = rootMetadataTarget; i > 0; i--) { + if (auto zoomContainerId = + mLayerScrollData[i - 1].GetAsyncZoomContainerId()) { + if (*zoomContainerId == rootMetadata->GetMetrics().GetScrollId()) { + rootMetadataTarget = i - 1; + break; + } + } + } + mLayerScrollData[rootMetadataTarget].AppendScrollMetadata( + aScrollData, rootMetadata.ref()); + } + + // Append the WebRenderLayerScrollData items into WebRenderScrollData + // in reverse order, from topmost to bottommost. This is in keeping with + // the semantics of WebRenderScrollData. + for (auto it = mLayerScrollData.crbegin(); it != mLayerScrollData.crend(); + it++) { + aScrollData.AddLayerData(*it); + } + mLayerScrollData.clear(); + mClipManager.EndBuild(); + + // Remove the user data those are not displayed on the screen and + // also reset the data to unused for next transaction. + RemoveUnusedAndResetWebRenderUserData(); +} + +bool WebRenderCommandBuilder::ShouldDumpDisplayList( + nsDisplayListBuilder* aBuilder) { + return aBuilder && aBuilder->IsInActiveDocShell() && + ((XRE_IsParentProcess() && + StaticPrefs::gfx_webrender_dl_dump_parent()) || + (XRE_IsContentProcess() && + StaticPrefs::gfx_webrender_dl_dump_content())); +} + +void WebRenderCommandBuilder::CreateWebRenderCommands( + nsDisplayItem* aItem, mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + nsDisplayListBuilder* aDisplayListBuilder) { + auto* item = aItem->AsPaintedDisplayItem(); + MOZ_RELEASE_ASSERT(item, "Tried to paint item that cannot be painted"); + + if (aBuilder.ReuseItem(item)) { + // No further processing should be needed, since the item was reused. + return; + } + + aItem->SetPaintRect(aItem->GetBuildingRect()); + RenderRootStateManager* manager = mManager->GetRenderRootStateManager(); + + // Note: this call to CreateWebRenderCommands can recurse back into + // this function if the |item| is a wrapper for a sublist. + const bool createdWRCommands = aItem->CreateWebRenderCommands( + aBuilder, aResources, aSc, manager, aDisplayListBuilder); + + if (!createdWRCommands) { + PushItemAsImage(aItem, aBuilder, aResources, aSc, aDisplayListBuilder); + } +} + +void WebRenderCommandBuilder::CreateWebRenderCommandsFromDisplayList( + nsDisplayList* aDisplayList, nsDisplayItem* aWrappingItem, + nsDisplayListBuilder* aDisplayListBuilder, const StackingContextHelper& aSc, + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources) { + if (mDoGrouping) { + MOZ_RELEASE_ASSERT( + aWrappingItem, + "Only the root list should have a null wrapping item, and mDoGrouping " + "should never be true for the root list."); + GP("actually entering the grouping code\n"); + DoGroupingForDisplayList(aDisplayList, aWrappingItem, aDisplayListBuilder, + aSc, aBuilder, aResources); + return; + } + + bool dumpEnabled = ShouldDumpDisplayList(aDisplayListBuilder); + if (dumpEnabled) { + // If we're inside a nested display list, print the WR DL items from the + // wrapper item before we start processing the nested items. + mBuilderDumpIndex = + aBuilder.Dump(mDumpIndent + 1, Some(mBuilderDumpIndex), Nothing()); + } + + mDumpIndent++; + mClipManager.BeginList(aSc); + + bool apzEnabled = mManager->AsyncPanZoomEnabled(); + + FlattenedDisplayListIterator iter(aDisplayListBuilder, aDisplayList); + while (iter.HasNext()) { + nsDisplayItem* item = iter.GetNextItem(); + + DisplayItemType itemType = item->GetType(); + + // If this is an unscrolled background color item, in the root display list + // for the parent process, consider doing opaque checks. + if (XRE_IsParentProcess() && !aWrappingItem && + itemType == DisplayItemType::TYPE_BACKGROUND_COLOR && + !item->GetActiveScrolledRoot() && + item->GetClip().GetRoundedRectCount() == 0) { + bool snap; + nsRegion opaque = item->GetOpaqueRegion(aDisplayListBuilder, &snap); + if (opaque.GetNumRects() == 1) { + nsRect clippedOpaque = + item->GetClip().ApplyNonRoundedIntersection(opaque.GetBounds()); + if (!clippedOpaque.IsEmpty()) { + aDisplayListBuilder->AddWindowOpaqueRegion(item->Frame(), + clippedOpaque); + } + } + } + + bool forceNewLayerData = false; + size_t layerCountBeforeRecursing = mLayerScrollData.size(); + if (apzEnabled) { + // For some types of display items we want to force a new + // WebRenderLayerScrollData object, to ensure we preserve the APZ-relevant + // data that is in the display item. + forceNewLayerData = item->UpdateScrollData(nullptr, nullptr); + + // Anytime the ASR changes we also want to force a new layer data because + // the stack of scroll metadata is going to be different for this + // display item than previously, so we can't squash the display items + // into the same "layer". + const ActiveScrolledRoot* asr = item->GetActiveScrolledRoot(); + if (asr != mLastAsr) { + mLastAsr = asr; + forceNewLayerData = true; + } + + // Refer to the comment on StackingContextHelper::mDeferredTransformItem + // for an overview of what this is about. This bit of code applies to the + // case where we are deferring a transform item, and we then need to defer + // another transform with a different ASR. In such a case we cannot just + // merge the deferred transforms, but need to force a new + // WebRenderLayerScrollData item to flush the old deferred transform, so + // that we can then start deferring the new one. + if (!forceNewLayerData && + item->GetType() == DisplayItemType::TYPE_TRANSFORM && + aSc.GetDeferredTransformItem() && + (*aSc.GetDeferredTransformItem())->GetActiveScrolledRoot() != asr) { + forceNewLayerData = true; + } + + // If we're going to create a new layer data for this item, stash the + // ASR so that if we recurse into a sublist they will know where to stop + // walking up their ASR chain when building scroll metadata. + if (forceNewLayerData) { + mAsrStack.push_back(asr); + } + } + + // This is where we emulate the clip/scroll stack that was previously + // implemented on the WR display list side. + auto spaceAndClipChain = mClipManager.SwitchItem(item); + wr::SpaceAndClipChainHelper saccHelper(aBuilder, spaceAndClipChain); + + { // scope restoreDoGrouping + AutoRestore restoreDoGrouping(mDoGrouping); + if (itemType == DisplayItemType::TYPE_SVG_WRAPPER) { + // Inside an , all display items that are not LAYER_ACTIVE wrapper + // display items (like animated transforms / opacity) share the same + // animated geometry root, so we can combine subsequent items of that + // type into the same image. + mContainsSVGGroup = mDoGrouping = true; + GP("attempting to enter the grouping code\n"); + } + + if (dumpEnabled) { + std::stringstream ss; + nsIFrame::PrintDisplayItem(aDisplayListBuilder, item, ss, + static_cast(mDumpIndent)); + printf_stderr("%s", ss.str().c_str()); + } + + CreateWebRenderCommands(item, aBuilder, aResources, aSc, + aDisplayListBuilder); + + if (dumpEnabled) { + mBuilderDumpIndex = + aBuilder.Dump(mDumpIndent + 1, Some(mBuilderDumpIndex), Nothing()); + } + } + + if (apzEnabled) { + if (forceNewLayerData) { + // Pop the thing we pushed before the recursion, so the topmost item on + // the stack is enclosing display item's ASR (or the stack is empty) + mAsrStack.pop_back(); + const ActiveScrolledRoot* stopAtAsr = + mAsrStack.empty() ? nullptr : mAsrStack.back(); + + int32_t descendants = + mLayerScrollData.size() - layerCountBeforeRecursing; + + // See the comments on StackingContextHelper::mDeferredTransformItem + // for an overview of what deferred transforms are. + // In the case where we deferred a transform, but have a child display + // item with a different ASR than the deferred transform item, we cannot + // put the transform on the WebRenderLayerScrollData item for the child. + // We cannot do this because it will not conform to APZ's expectations + // with respect to how the APZ tree ends up structured. In particular, + // the GetTransformToThis() for the child APZ (which is created for the + // child item's ASR) will not include the transform when we actually do + // want it to. + // When we run into this scenario, we solve it by creating two + // WebRenderLayerScrollData items; one that just holds the transform, + // that we deferred, and a child WebRenderLayerScrollData item that + // holds the scroll metadata for the child's ASR. + Maybe deferred = aSc.GetDeferredTransformItem(); + if (deferred && (*deferred)->GetActiveScrolledRoot() != + item->GetActiveScrolledRoot()) { + // This creates the child WebRenderLayerScrollData for |item|, but + // omits the transform (hence the Nothing() as the last argument to + // Initialize(...)). We also need to make sure that the ASR from + // the deferred transform item is not on this node, so we use that + // ASR as the "stop at" ASR for this WebRenderLayerScrollData. + mLayerScrollData.emplace_back(); + mLayerScrollData.back().Initialize( + mManager->GetScrollData(), item, descendants, + (*deferred)->GetActiveScrolledRoot(), Nothing()); + + // The above WebRenderLayerScrollData will also be a descendant of + // the transform-holding WebRenderLayerScrollData we create below. + descendants++; + + // This creates the WebRenderLayerScrollData for the deferred + // transform item. This holds the transform matrix and the remaining + // ASRs needed to complete the ASR chain (i.e. the ones from the + // stopAtAsr down to the deferred transform item's ASR, which must be + // "between" stopAtAsr and |item|'s ASR in the ASR tree). + mLayerScrollData.emplace_back(); + mLayerScrollData.back().Initialize(mManager->GetScrollData(), + *deferred, descendants, stopAtAsr, + aSc.GetDeferredTransformMatrix()); + } else { + // This is the "simple" case where we don't need to create two + // WebRenderLayerScrollData items; we can just create one that also + // holds the deferred transform matrix, if any. + mLayerScrollData.emplace_back(); + mLayerScrollData.back().Initialize(mManager->GetScrollData(), item, + descendants, stopAtAsr, + aSc.GetDeferredTransformMatrix()); + } + } + } + } + + mDumpIndent--; + mClipManager.EndList(aSc); +} + +void WebRenderCommandBuilder::PushOverrideForASR( + const ActiveScrolledRoot* aASR, const wr::WrSpatialId& aSpatialId) { + mClipManager.PushOverrideForASR(aASR, aSpatialId); +} + +void WebRenderCommandBuilder::PopOverrideForASR( + const ActiveScrolledRoot* aASR) { + mClipManager.PopOverrideForASR(aASR); +} + +Maybe WebRenderCommandBuilder::CreateImageKey( + nsDisplayItem* aItem, ImageContainer* aContainer, + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + mozilla::wr::ImageRendering aRendering, const StackingContextHelper& aSc, + gfx::IntSize& aSize, const Maybe& aAsyncImageBounds) { + RefPtr imageData = + CreateOrRecycleWebRenderUserData(aItem); + MOZ_ASSERT(imageData); + + if (aContainer->IsAsync()) { + MOZ_ASSERT(aAsyncImageBounds); + + LayoutDeviceRect rect = aAsyncImageBounds.value(); + LayoutDeviceRect scBounds(LayoutDevicePoint(0, 0), rect.Size()); + // TODO! + // We appear to be using the image bridge for a lot (most/all?) of + // layers-free image handling and that breaks frame consistency. + imageData->CreateAsyncImageWebRenderCommands( + aBuilder, aContainer, aSc, rect, scBounds, aContainer->GetRotation(), + aRendering, wr::MixBlendMode::Normal, !aItem->BackfaceIsHidden()); + return Nothing(); + } + + AutoLockImage autoLock(aContainer); + if (!autoLock.HasImage()) { + return Nothing(); + } + mozilla::layers::Image* image = autoLock.GetImage(); + aSize = image->GetSize(); + + return imageData->UpdateImageKey(aContainer, aResources); +} + +bool WebRenderCommandBuilder::PushImage( + nsDisplayItem* aItem, ImageContainer* aContainer, + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, const LayoutDeviceRect& aRect, + const LayoutDeviceRect& aClip) { + mozilla::wr::ImageRendering rendering = wr::ToImageRendering( + nsLayoutUtils::GetSamplingFilterForFrame(aItem->Frame())); + gfx::IntSize size; + Maybe key = + CreateImageKey(aItem, aContainer, aBuilder, aResources, rendering, aSc, + size, Some(aRect)); + if (aContainer->IsAsync()) { + // Async ImageContainer does not create ImageKey, instead it uses Pipeline. + MOZ_ASSERT(key.isNothing()); + return true; + } + if (!key) { + return false; + } + + auto r = wr::ToLayoutRect(aRect); + auto c = wr::ToLayoutRect(aClip); + aBuilder.PushImage(r, c, !aItem->BackfaceIsHidden(), rendering, key.value()); + + return true; +} + +bool BuildLayer(nsDisplayItem* aItem, BlobItemData* aData, + nsDisplayListBuilder* aDisplayListBuilder, + const gfx::Size& aScale) { + if (!aData->mLayerManager) { + aData->mLayerManager = + new BasicLayerManager(BasicLayerManager::BLM_INACTIVE); + } + RefPtr blm = aData->mLayerManager; + UniquePtr props; + if (blm->GetRoot()) { + props = LayerProperties::CloneFrom(blm->GetRoot()); + } + FrameLayerBuilder* layerBuilder = new FrameLayerBuilder(); + layerBuilder->Init(aDisplayListBuilder, blm, nullptr, true); + layerBuilder->DidBeginRetainedLayerTransaction(blm); + + blm->BeginTransaction(); + bool isInvalidated = false; + + ContainerLayerParameters param(aScale.width, aScale.height); + RefPtr root = aItem->AsPaintedDisplayItem()->BuildLayer( + aDisplayListBuilder, blm, param); + + if (root) { + blm->SetRoot(root); + layerBuilder->WillEndTransaction(); + + // Check if there is any invalidation region. + nsIntRegion invalid; + if (props) { + props->ComputeDifferences(root, invalid, nullptr); + if (!invalid.IsEmpty()) { + isInvalidated = true; + } + } else { + isInvalidated = true; + } + } + blm->AbortTransaction(); + + return isInvalidated; +} + +static bool PaintByLayer(nsDisplayItem* aItem, + nsDisplayListBuilder* aDisplayListBuilder, + const RefPtr& aManager, + gfxContext* aContext, const gfx::Size& aScale, + const std::function& aPaintFunc) { + UniquePtr props; + if (aManager->GetRoot()) { + props = LayerProperties::CloneFrom(aManager->GetRoot()); + } + FrameLayerBuilder* layerBuilder = new FrameLayerBuilder(); + layerBuilder->Init(aDisplayListBuilder, aManager, nullptr, true); + layerBuilder->DidBeginRetainedLayerTransaction(aManager); + + aManager->SetDefaultTarget(aContext); + nsCString none; + aManager->BeginTransactionWithTarget(aContext, none); + bool isInvalidated = false; + + ContainerLayerParameters param(aScale.width, aScale.height); + RefPtr root = aItem->AsPaintedDisplayItem()->BuildLayer( + aDisplayListBuilder, aManager, param); + + if (root) { + aManager->SetRoot(root); + layerBuilder->WillEndTransaction(); + + aPaintFunc(); + + // Check if there is any invalidation region. + nsIntRegion invalid; + if (props) { + props->ComputeDifferences(root, invalid, nullptr); + if (!invalid.IsEmpty()) { + isInvalidated = true; + } + } else { + isInvalidated = true; + } + } + +#ifdef MOZ_DUMP_PAINTING + if (gfxUtils::DumpDisplayList() || gfxEnv::DumpPaint()) { + fprintf_stderr( + gfxUtils::sDumpPaintFile, + "Basic layer tree for painting contents of display item %s(%p):\n", + aItem->Name(), aItem->Frame()); + std::stringstream stream; + aManager->Dump(stream, "", gfxEnv::DumpPaintToFile()); + fprint_stderr(gfxUtils::sDumpPaintFile, + stream); // not a typo, fprint_stderr declared in nsDebug.h + } +#endif + + if (aManager->InTransaction()) { + aManager->AbortTransaction(); + } + + aManager->SetTarget(nullptr); + aManager->SetDefaultTarget(nullptr); + + return isInvalidated; +} + +static bool PaintItemByDrawTarget(nsDisplayItem* aItem, gfx::DrawTarget* aDT, + const LayoutDevicePoint& aOffset, + const IntRect& visibleRect, + nsDisplayListBuilder* aDisplayListBuilder, + const RefPtr& aManager, + const gfx::Size& aScale, + Maybe& aHighlight) { + MOZ_ASSERT(aDT); + + bool isInvalidated = false; + // XXX Why is this ClearRect() needed? + aDT->ClearRect(Rect(visibleRect)); + RefPtr context = gfxContext::CreateOrNull(aDT); + MOZ_ASSERT(context); + + switch (aItem->GetType()) { + case DisplayItemType::TYPE_SVG_WRAPPER: + case DisplayItemType::TYPE_MASK: { + // These items should be handled by other code paths + MOZ_RELEASE_ASSERT(0); + break; + } + case DisplayItemType::TYPE_FILTER: { + context->SetMatrix(context->CurrentMatrix() + .PreScale(aScale.width, aScale.height) + .PreTranslate(-aOffset.x, -aOffset.y)); + isInvalidated = PaintByLayer( + aItem, aDisplayListBuilder, aManager, context, {1, 1}, [&]() { + static_cast(aItem)->PaintAsLayer( + aDisplayListBuilder, context, aManager); + }); + break; + } + + default: + if (!aItem->AsPaintedDisplayItem()) { + break; + } + + context->SetMatrix(context->CurrentMatrix() + .PreScale(aScale.width, aScale.height) + .PreTranslate(-aOffset.x, -aOffset.y)); + if (aDisplayListBuilder->IsPaintingToWindow()) { + aItem->Frame()->AddStateBits(NS_FRAME_PAINTED_THEBES); + } + aItem->AsPaintedDisplayItem()->Paint(aDisplayListBuilder, context); + isInvalidated = true; + break; + } + + if (aItem->GetType() != DisplayItemType::TYPE_MASK) { + // Apply highlight fills, if the appropriate prefs are set. + // We don't do this for masks because we'd be filling the A8 mask surface, + // which isn't very useful. + if (aHighlight) { + aDT->SetTransform(gfx::Matrix()); + aDT->FillRect(Rect(visibleRect), gfx::ColorPattern(aHighlight.value())); + } + if (aItem->Frame()->PresContext()->GetPaintFlashing() && isInvalidated) { + aDT->SetTransform(gfx::Matrix()); + float r = float(rand()) / float(RAND_MAX); + float g = float(rand()) / float(RAND_MAX); + float b = float(rand()) / float(RAND_MAX); + aDT->FillRect(Rect(visibleRect), + gfx::ColorPattern(gfx::DeviceColor(r, g, b, 0.5))); + } + } + + return isInvalidated; +} + +// When drawing fallback images we create either +// a real image or a blob image that will contain the display item. +// In the case of a blob image we paint the item at 0,0 instead +// of trying to keep at aItem->GetBounds().TopLeft() like we do +// with SVG. We do this because there's not necessarily a reference frame +// between us and the rest of the world so the the coordinates +// that we get for the bounds are not necessarily stable across scrolling +// or other movement. +already_AddRefed +WebRenderCommandBuilder::GenerateFallbackData( + nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder, + wr::IpcResourceUpdateQueue& aResources, const StackingContextHelper& aSc, + nsDisplayListBuilder* aDisplayListBuilder, LayoutDeviceRect& aImageRect) { + const bool paintOnContentSide = aItem->MustPaintOnContentSide(); + bool useBlobImage = + StaticPrefs::gfx_webrender_blob_images() && !paintOnContentSide; + Maybe highlight = Nothing(); + if (StaticPrefs::gfx_webrender_highlight_painted_layers()) { + highlight = Some(useBlobImage ? gfx::DeviceColor(1.0, 0.0, 0.0, 0.5) + : gfx::DeviceColor(1.0, 1.0, 0.0, 0.5)); + } + + RefPtr fallbackData = + CreateOrRecycleWebRenderUserData(aItem); + + bool snap; + nsRect itemBounds = aItem->GetBounds(aDisplayListBuilder, &snap); + + // Blob images will only draw the visible area of the blob so we don't need to + // clip them here and can just rely on the webrender clipping. + // TODO We also don't clip native themed widget to avoid over-invalidation + // during scrolling. It would be better to support a sort of streaming/tiling + // scheme for large ones but the hope is that we should not have large native + // themed items. + nsRect paintBounds = (useBlobImage || paintOnContentSide) + ? itemBounds + : aItem->GetClippedBounds(aDisplayListBuilder); + + // nsDisplayItem::Paint() may refer the variables that come from + // ComputeVisibility(). So we should call ComputeVisibility() before painting. + // e.g.: nsDisplayBoxShadowInner uses mPaintRect in Paint() and mPaintRect is + // computed in nsDisplayBoxShadowInner::ComputeVisibility(). + nsRegion visibleRegion(paintBounds); + aItem->SetPaintRect(paintBounds); + aItem->ComputeVisibility(aDisplayListBuilder, &visibleRegion); + + const int32_t appUnitsPerDevPixel = + aItem->Frame()->PresContext()->AppUnitsPerDevPixel(); + auto bounds = + LayoutDeviceRect::FromAppUnits(paintBounds, appUnitsPerDevPixel); + if (bounds.IsEmpty()) { + return nullptr; + } + + gfx::Size scale = aSc.GetInheritedScale(); + gfx::Size oldScale = fallbackData->mScale; + // We tolerate slight changes in scale so that we don't, for example, + // rerasterize on MotionMark + bool differentScale = gfx::FuzzyEqual(scale.width, oldScale.width, 1e-6f) && + gfx::FuzzyEqual(scale.height, oldScale.height, 1e-6f); + + LayoutDeviceToLayerScale2D layerScale(scale.width, scale.height); + + auto trans = + ViewAs(aSc.GetSnappingSurfaceTransform().GetTranslation()); + auto snappedTrans = LayerIntPoint::Floor(trans); + LayerPoint residualOffset = trans - snappedTrans; + + nsRegion opaqueRegion = aItem->GetOpaqueRegion(aDisplayListBuilder, &snap); + wr::OpacityType opacity = opaqueRegion.Contains(paintBounds) + ? wr::OpacityType::Opaque + : wr::OpacityType::HasAlphaChannel; + + LayerIntRect dtRect, visibleRect; + // If we think the item is opaque we round the bounds + // to the nearest pixel instead of rounding them out. If we rounded + // out we'd potentially introduce transparent pixels. + // + // Ideally we'd be able to ask an item its bounds in pixels and whether + // they're all opaque. Unfortunately no such API exists so we currently + // just hope that we get it right. + if (opacity == wr::OpacityType::Opaque && snap) { + dtRect = LayerIntRect::FromUnknownRect( + ScaleToNearestPixelsOffset(paintBounds, scale.width, scale.height, + appUnitsPerDevPixel, residualOffset)); + + visibleRect = LayerIntRect::FromUnknownRect( + ScaleToNearestPixelsOffset( + aItem->GetBuildingRect(), scale.width, scale.height, + appUnitsPerDevPixel, residualOffset)) + .Intersect(dtRect); + } else { + dtRect = ScaleToOutsidePixelsOffset(paintBounds, scale.width, scale.height, + appUnitsPerDevPixel, residualOffset); + + visibleRect = ScaleToOutsidePixelsOffset( + aItem->GetBuildingRect(), scale.width, scale.height, + appUnitsPerDevPixel, residualOffset) + .Intersect(dtRect); + } + + auto visibleSize = visibleRect.Size(); + // these rectangles can overflow from scaling so try to + // catch that with IsEmpty() checks. See bug 1622126. + if (visibleSize.IsEmpty() || dtRect.IsEmpty()) { + return nullptr; + } + + if (useBlobImage) { + // Display item bounds should be unscaled + aImageRect = visibleRect / layerScale; + } else { + // Display item bounds should be unscaled + aImageRect = dtRect / layerScale; + } + + // We always paint items at 0,0 so the visibleRect that we use inside the blob + // is needs to be adjusted by the display item bounds top left. + visibleRect -= dtRect.TopLeft(); + + nsDisplayItemGeometry* geometry = fallbackData->mGeometry.get(); + + bool needPaint = true; + + // nsDisplayFilters is rendered via BasicLayerManager which means the + // invalidate region is unknown until we traverse the displaylist contained by + // it. + if (geometry && !fallbackData->IsInvalid() && + aItem->GetType() != DisplayItemType::TYPE_FILTER && + aItem->GetType() != DisplayItemType::TYPE_SVG_WRAPPER && differentScale) { + nsRect invalid; + nsRegion invalidRegion; + + if (aItem->IsInvalid(invalid)) { + invalidRegion.OrWith(paintBounds); + } else { + nsPoint shift = itemBounds.TopLeft() - geometry->mBounds.TopLeft(); + geometry->MoveBy(shift); + aItem->ComputeInvalidationRegion(aDisplayListBuilder, geometry, + &invalidRegion); + + nsRect lastBounds = fallbackData->mBounds; + lastBounds.MoveBy(shift); + + if (!lastBounds.IsEqualInterior(paintBounds)) { + invalidRegion.OrWith(lastBounds); + invalidRegion.OrWith(paintBounds); + } + } + needPaint = !invalidRegion.IsEmpty(); + } + + if (needPaint || !fallbackData->GetImageKey()) { + fallbackData->mGeometry = + WrapUnique(aItem->AllocateGeometry(aDisplayListBuilder)); + + gfx::SurfaceFormat format = aItem->GetType() == DisplayItemType::TYPE_MASK + ? gfx::SurfaceFormat::A8 + : (opacity == wr::OpacityType::Opaque + ? gfx::SurfaceFormat::B8G8R8X8 + : gfx::SurfaceFormat::B8G8R8A8); + if (useBlobImage) { + MOZ_ASSERT(!opaqueRegion.IsComplex()); + + std::vector> fonts; + bool validFonts = true; + RefPtr recorder = + MakeAndAddRef( + [&](MemStream& aStream, + std::vector>& aScaledFonts) { + size_t count = aScaledFonts.size(); + aStream.write((const char*)&count, sizeof(count)); + for (auto& scaled : aScaledFonts) { + Maybe key = + mManager->WrBridge()->GetFontKeyForScaledFont( + scaled, &aResources); + if (key.isNothing()) { + validFonts = false; + break; + } + BlobFont font = {key.value(), scaled}; + aStream.write((const char*)&font, sizeof(font)); + } + fonts = std::move(aScaledFonts); + }); + RefPtr dummyDt = gfx::Factory::CreateDrawTarget( + gfx::BackendType::SKIA, gfx::IntSize(1, 1), format); + RefPtr dt = gfx::Factory::CreateRecordingDrawTarget( + recorder, dummyDt, (dtRect - dtRect.TopLeft()).ToUnknownRect()); + if (!fallbackData->mBasicLayerManager) { + fallbackData->mBasicLayerManager = + new BasicLayerManager(BasicLayerManager::BLM_INACTIVE); + } + bool isInvalidated = PaintItemByDrawTarget( + aItem, dt, (dtRect / layerScale).TopLeft(), + /*aVisibleRect: */ dt->GetRect(), aDisplayListBuilder, + fallbackData->mBasicLayerManager, scale, highlight); + if (!isInvalidated) { + if (!aItem->GetBuildingRect().IsEqualInterior( + fallbackData->mBuildingRect)) { + // The building rect has changed but we didn't see any invalidations. + // We should still consider this an invalidation. + isInvalidated = true; + } + } + + // the item bounds are relative to the blob origin which is + // dtRect.TopLeft() + recorder->FlushItem((dtRect - dtRect.TopLeft()).ToUnknownRect()); + recorder->Finish(); + + if (!validFonts) { + gfxCriticalNote << "Failed serializing fonts for blob image"; + return nullptr; + } + + if (isInvalidated) { + Range bytes((uint8_t*)recorder->mOutputStream.mData, + recorder->mOutputStream.mLength); + wr::BlobImageKey key = + wr::BlobImageKey{mManager->WrBridge()->GetNextImageKey()}; + wr::ImageDescriptor descriptor(visibleSize.ToUnknownSize(), 0, + dt->GetFormat(), opacity); + if (!aResources.AddBlobImage( + key, descriptor, bytes, + ViewAs(visibleRect, + PixelCastJustification::LayerIsImage))) { + return nullptr; + } + TakeExternalSurfaces(recorder, fallbackData->mExternalSurfaces, + mManager->GetRenderRootStateManager(), aResources); + fallbackData->SetBlobImageKey(key); + fallbackData->SetFonts(fonts); + } else { + // If there is no invalidation region and we don't have a image key, + // it means we don't need to push image for the item. + if (!fallbackData->GetBlobImageKey().isSome()) { + return nullptr; + } + } + } else { + WebRenderImageData* imageData = fallbackData->PaintIntoImage(); + + imageData->CreateImageClientIfNeeded(); + RefPtr imageClient = imageData->GetImageClient(); + RefPtr imageContainer = + LayerManager::CreateImageContainer(); + bool isInvalidated = false; + + { + UpdateImageHelper helper(imageContainer, imageClient, + dtRect.Size().ToUnknownSize(), format); + { + RefPtr dt = helper.GetDrawTarget(); + if (!dt) { + return nullptr; + } + if (!fallbackData->mBasicLayerManager) { + fallbackData->mBasicLayerManager = + new BasicLayerManager(mManager->GetWidget()); + } + isInvalidated = PaintItemByDrawTarget( + aItem, dt, + /*aOffset: */ aImageRect.TopLeft(), + /*aVisibleRect: */ dt->GetRect(), aDisplayListBuilder, + fallbackData->mBasicLayerManager, scale, highlight); + } + + if (isInvalidated) { + // Update image if there it's invalidated. + if (!helper.UpdateImage()) { + return nullptr; + } + } else { + // If there is no invalidation region and we don't have a image key, + // it means we don't need to push image for the item. + if (!imageData->GetImageKey().isSome()) { + return nullptr; + } + } + } + + // Force update the key in fallback data since we repaint the image in + // this path. If not force update, fallbackData may reuse the original key + // because it doesn't know UpdateImageHelper already updated the image + // container. + if (isInvalidated && + !imageData->UpdateImageKey(imageContainer, aResources, true)) { + return nullptr; + } + } + + fallbackData->mScale = scale; + fallbackData->SetInvalid(false); + } + + if (useBlobImage) { + aResources.SetBlobImageVisibleArea( + fallbackData->GetBlobImageKey().value(), + ViewAs(visibleRect, PixelCastJustification::LayerIsImage)); + } + + // Update current bounds to fallback data + fallbackData->mBounds = paintBounds; + fallbackData->mBuildingRect = aItem->GetBuildingRect(); + + MOZ_ASSERT(fallbackData->GetImageKey()); + + return fallbackData.forget(); +} + +class WebRenderMaskData : public WebRenderUserData { + public: + explicit WebRenderMaskData(RenderRootStateManager* aManager, + nsDisplayItem* aItem) + : WebRenderUserData(aManager, aItem), + mMaskStyle(nsStyleImageLayers::LayerType::Mask), + mShouldHandleOpacity(false) { + MOZ_COUNT_CTOR(WebRenderMaskData); + } + virtual ~WebRenderMaskData() { + MOZ_COUNT_DTOR(WebRenderMaskData); + ClearImageKey(); + } + + void ClearImageKey() { + if (mBlobKey) { + mManager->AddBlobImageKeyForDiscard(mBlobKey.value()); + } + mBlobKey.reset(); + } + + UserDataType GetType() override { return UserDataType::eMask; } + static UserDataType Type() { return UserDataType::eMask; } + + Maybe mBlobKey; + std::vector> mFonts; + std::vector> mExternalSurfaces; + LayerIntRect mItemRect; + nsPoint mMaskOffset; + nsStyleImageLayers mMaskStyle; + gfx::Size mScale; + bool mShouldHandleOpacity; +}; + +Maybe WebRenderCommandBuilder::BuildWrMaskImage( + nsDisplayMasksAndClipPaths* aMaskItem, wr::DisplayListBuilder& aBuilder, + wr::IpcResourceUpdateQueue& aResources, const StackingContextHelper& aSc, + nsDisplayListBuilder* aDisplayListBuilder, + const LayoutDeviceRect& aBounds) { + RefPtr maskData = + CreateOrRecycleWebRenderUserData(aMaskItem); + + if (!maskData) { + return Nothing(); + } + + bool snap; + nsRect bounds = aMaskItem->GetBounds(aDisplayListBuilder, &snap); + + const int32_t appUnitsPerDevPixel = + aMaskItem->Frame()->PresContext()->AppUnitsPerDevPixel(); + + Size scale = aSc.GetInheritedScale(); + Size oldScale = maskData->mScale; + // This scale determination should probably be done using + // ChooseScaleAndSetTransform but for now we just fake it. + // We tolerate slight changes in scale so that we don't, for example, + // rerasterize on MotionMark + bool sameScale = FuzzyEqual(scale.width, oldScale.width, 1e-6f) && + FuzzyEqual(scale.height, oldScale.height, 1e-6f); + + LayerIntRect itemRect = + LayerIntRect::FromUnknownRect(bounds.ScaleToOutsidePixels( + scale.width, scale.height, appUnitsPerDevPixel)); + + if (itemRect.IsEmpty()) { + return Nothing(); + } + + LayoutDeviceToLayerScale2D layerScale(scale.width, scale.height); + LayoutDeviceRect imageRect = LayerRect(itemRect) / layerScale; + + nsPoint maskOffset = aMaskItem->ToReferenceFrame() - bounds.TopLeft(); + + nsRect dirtyRect; + // If this mask item is being painted for the first time, some members of + // WebRenderMaskData are still default initialized. This is intentional. + if (aMaskItem->IsInvalid(dirtyRect) || + !itemRect.IsEqualInterior(maskData->mItemRect) || + !(aMaskItem->Frame()->StyleSVGReset()->mMask == maskData->mMaskStyle) || + maskOffset != maskData->mMaskOffset || !sameScale || + aMaskItem->ShouldHandleOpacity() != maskData->mShouldHandleOpacity) { + IntSize size = itemRect.Size().ToUnknownSize(); + + std::vector> fonts; + bool validFonts = true; + RefPtr recorder = + MakeAndAddRef( + [&](MemStream& aStream, + std::vector>& aScaledFonts) { + size_t count = aScaledFonts.size(); + aStream.write((const char*)&count, sizeof(count)); + + for (auto& scaled : aScaledFonts) { + Maybe key = + mManager->WrBridge()->GetFontKeyForScaledFont(scaled, + &aResources); + if (key.isNothing()) { + validFonts = false; + break; + } + BlobFont font = {key.value(), scaled}; + aStream.write((const char*)&font, sizeof(font)); + } + + fonts = std::move(aScaledFonts); + }); + + RefPtr dummyDt = Factory::CreateDrawTarget( + BackendType::SKIA, IntSize(1, 1), SurfaceFormat::A8); + RefPtr dt = Factory::CreateRecordingDrawTarget( + recorder, dummyDt, IntRect(IntPoint(0, 0), size)); + + RefPtr context = gfxContext::CreateOrNull(dt); + MOZ_ASSERT(context); + + context->SetMatrix(context->CurrentMatrix() + .PreTranslate(-itemRect.x, -itemRect.y) + .PreScale(scale.width, scale.height)); + + bool maskPainted = false; + bool maskIsComplete = + aMaskItem->PaintMask(aDisplayListBuilder, context, &maskPainted); + if (!maskPainted) { + return Nothing(); + } + + // If a mask is incomplete or missing (e.g. it's display: none) the proper + // behaviour depends on the masked frame being html or svg. + // + // For an HTML frame: + // According to css-masking spec, always create a mask surface when + // we have any item in maskFrame even if all of those items are + // non-resolvable or so continue with the + // painting code. Note that in a common case of no layer of the mask being + // complete or even partially complete then the mask surface will be + // transparent black so this results in hiding the frame. + // For an SVG frame: + // SVG 1.1 say that if we fail to resolve a mask, we should draw the + // object unmasked so return Nothing(). + if (!maskIsComplete && + (aMaskItem->Frame()->GetStateBits() & NS_FRAME_SVG_LAYOUT)) { + return Nothing(); + } + + recorder->FlushItem(IntRect(0, 0, size.width, size.height)); + recorder->Finish(); + + if (!validFonts) { + gfxCriticalNote << "Failed serializing fonts for blob mask image"; + return Nothing(); + } + + Range bytes((uint8_t*)recorder->mOutputStream.mData, + recorder->mOutputStream.mLength); + wr::BlobImageKey key = + wr::BlobImageKey{mManager->WrBridge()->GetNextImageKey()}; + wr::ImageDescriptor descriptor(size, 0, dt->GetFormat(), + wr::OpacityType::HasAlphaChannel); + if (!aResources.AddBlobImage(key, descriptor, bytes, + ImageIntRect(0, 0, size.width, size.height))) { + return Nothing(); + } + maskData->ClearImageKey(); + maskData->mBlobKey = Some(key); + maskData->mFonts = fonts; + TakeExternalSurfaces(recorder, maskData->mExternalSurfaces, + mManager->GetRenderRootStateManager(), aResources); + if (maskIsComplete) { + maskData->mItemRect = itemRect; + maskData->mMaskOffset = maskOffset; + maskData->mScale = scale; + maskData->mMaskStyle = aMaskItem->Frame()->StyleSVGReset()->mMask; + maskData->mShouldHandleOpacity = aMaskItem->ShouldHandleOpacity(); + } + } + + wr::ImageMask imageMask; + imageMask.image = wr::AsImageKey(maskData->mBlobKey.value()); + imageMask.rect = wr::ToLayoutRect(imageRect); + imageMask.repeat = false; + return Some(imageMask); +} + +bool WebRenderCommandBuilder::PushItemAsImage( + nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder, + wr::IpcResourceUpdateQueue& aResources, const StackingContextHelper& aSc, + nsDisplayListBuilder* aDisplayListBuilder) { + LayoutDeviceRect imageRect; + RefPtr fallbackData = GenerateFallbackData( + aItem, aBuilder, aResources, aSc, aDisplayListBuilder, imageRect); + if (!fallbackData) { + return false; + } + + wr::LayoutRect dest = wr::ToLayoutRect(imageRect); + gfx::SamplingFilter sampleFilter = + nsLayoutUtils::GetSamplingFilterForFrame(aItem->Frame()); + aBuilder.PushImage(dest, dest, !aItem->BackfaceIsHidden(), + wr::ToImageRendering(sampleFilter), + fallbackData->GetImageKey().value()); + return true; +} + +void WebRenderCommandBuilder::RemoveUnusedAndResetWebRenderUserData() { + for (auto iter = mWebRenderUserDatas.Iter(); !iter.Done(); iter.Next()) { + WebRenderUserData* data = iter.Get()->GetKey(); + if (!data->IsUsed()) { + nsIFrame* frame = data->GetFrame(); + + MOZ_ASSERT(frame->HasProperty(WebRenderUserDataProperty::Key())); + + WebRenderUserDataTable* userDataTable = + frame->GetProperty(WebRenderUserDataProperty::Key()); + + MOZ_ASSERT(userDataTable->Count()); + + userDataTable->Remove( + WebRenderUserDataKey(data->GetDisplayItemKey(), data->GetType())); + + if (!userDataTable->Count()) { + frame->RemoveProperty(WebRenderUserDataProperty::Key()); + userDataTable = nullptr; + } + + switch (data->GetType()) { + case WebRenderUserData::UserDataType::eCanvas: + mLastCanvasDatas.RemoveEntry(data->AsCanvasData()); + break; + case WebRenderUserData::UserDataType::eLocalCanvas: + mLastLocalCanvasDatas.RemoveEntry(data->AsLocalCanvasData()); + break; + case WebRenderUserData::UserDataType::eAnimation: + EffectCompositor::ClearIsRunningOnCompositor( + frame, GetDisplayItemTypeFromKey(data->GetDisplayItemKey())); + break; + default: + break; + } + + iter.Remove(); + continue; + } + + data->SetUsed(false); + } +} + +void WebRenderCommandBuilder::ClearCachedResources() { + RemoveUnusedAndResetWebRenderUserData(); + // UserDatas should only be in the used state during a call to + // WebRenderCommandBuilder::BuildWebRenderCommands The should always be false + // upon return from BuildWebRenderCommands(). + MOZ_RELEASE_ASSERT(mWebRenderUserDatas.Count() == 0); +} + +WebRenderGroupData::WebRenderGroupData( + RenderRootStateManager* aRenderRootStateManager, nsDisplayItem* aItem) + : WebRenderUserData(aRenderRootStateManager, aItem) { + MOZ_COUNT_CTOR(WebRenderGroupData); +} + +WebRenderGroupData::~WebRenderGroupData() { + MOZ_COUNT_DTOR(WebRenderGroupData); + GP("Group data destruct\n"); + mSubGroup.ClearImageKey(mManager, true); + mFollowingGroup.ClearImageKey(mManager, true); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/wr/WebRenderCommandBuilder.h b/gfx/layers/wr/WebRenderCommandBuilder.h new file mode 100644 index 0000000000..22ebac8d4f --- /dev/null +++ b/gfx/layers/wr/WebRenderCommandBuilder.h @@ -0,0 +1,215 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_WEBRENDERCOMMANDBUILDER_H +#define GFX_WEBRENDERCOMMANDBUILDER_H + +#include "mozilla/webrender/WebRenderAPI.h" +#include "mozilla/layers/ClipManager.h" +#include "mozilla/layers/WebRenderMessages.h" +#include "mozilla/layers/WebRenderScrollData.h" +#include "mozilla/layers/WebRenderUserData.h" +#include "mozilla/SVGIntegrationUtils.h" // for WrFiltersHolder +#include "nsDisplayList.h" +#include "nsIFrame.h" +#include "DisplayItemCache.h" + +namespace mozilla { + +namespace layers { + +class CanvasLayer; +class ImageClient; +class ImageContainer; +class WebRenderBridgeChild; +class WebRenderCanvasData; +class WebRenderCanvasRendererAsync; +class WebRenderImageData; +class WebRenderFallbackData; +class WebRenderParentCommand; +class WebRenderUserData; + +class WebRenderCommandBuilder final { + typedef nsTHashtable> + WebRenderUserDataRefTable; + typedef nsTHashtable> CanvasDataSet; + typedef nsTHashtable> + LocalCanvasDataSet; + + public: + explicit WebRenderCommandBuilder(WebRenderLayerManager* aManager); + + void Destroy(); + + void EmptyTransaction(); + + bool NeedsEmptyTransaction(); + + void BuildWebRenderCommands(wr::DisplayListBuilder& aBuilder, + wr::IpcResourceUpdateQueue& aResourceUpdates, + nsDisplayList* aDisplayList, + nsDisplayListBuilder* aDisplayListBuilder, + WebRenderScrollData& aScrollData, + WrFiltersHolder&& aFilters); + + void PushOverrideForASR(const ActiveScrolledRoot* aASR, + const wr::WrSpatialId& aSpatialId); + void PopOverrideForASR(const ActiveScrolledRoot* aASR); + + Maybe CreateImageKey( + nsDisplayItem* aItem, ImageContainer* aContainer, + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + mozilla::wr::ImageRendering aRendering, const StackingContextHelper& aSc, + gfx::IntSize& aSize, const Maybe& aAsyncImageBounds); + + WebRenderUserDataRefTable* GetWebRenderUserDataTable() { + return &mWebRenderUserDatas; + } + + bool PushImage(nsDisplayItem* aItem, ImageContainer* aContainer, + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + const LayoutDeviceRect& aRect, const LayoutDeviceRect& aClip); + + Maybe BuildWrMaskImage( + nsDisplayMasksAndClipPaths* aMaskItem, wr::DisplayListBuilder& aBuilder, + wr::IpcResourceUpdateQueue& aResources, const StackingContextHelper& aSc, + nsDisplayListBuilder* aDisplayListBuilder, + const LayoutDeviceRect& aBounds); + + bool PushItemAsImage(nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder, + wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + nsDisplayListBuilder* aDisplayListBuilder); + + void CreateWebRenderCommandsFromDisplayList( + nsDisplayList* aDisplayList, nsDisplayItem* aWrappingItem, + nsDisplayListBuilder* aDisplayListBuilder, + const StackingContextHelper& aSc, wr::DisplayListBuilder& aBuilder, + wr::IpcResourceUpdateQueue& aResources); + + // aWrappingItem has to be non-null. + void DoGroupingForDisplayList(nsDisplayList* aDisplayList, + nsDisplayItem* aWrappingItem, + nsDisplayListBuilder* aDisplayListBuilder, + const StackingContextHelper& aSc, + wr::DisplayListBuilder& aBuilder, + wr::IpcResourceUpdateQueue& aResources); + + already_AddRefed GenerateFallbackData( + nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder, + wr::IpcResourceUpdateQueue& aResources, const StackingContextHelper& aSc, + nsDisplayListBuilder* aDisplayListBuilder, LayoutDeviceRect& aImageRect); + + void RemoveUnusedAndResetWebRenderUserData(); + void ClearCachedResources(); + + bool ShouldDumpDisplayList(nsDisplayListBuilder* aBuilder); + wr::usize GetBuilderDumpIndex() const { return mBuilderDumpIndex; } + + bool GetContainsSVGGroup() { return mContainsSVGGroup; } + + // Those are data that we kept between transactions. We used to cache some + // data in the layer. But in layers free mode, we don't have layer which + // means we need some other place to cached the data between transaction. + // We store the data in frame's property. + template + already_AddRefed CreateOrRecycleWebRenderUserData( + nsDisplayItem* aItem, bool* aOutIsRecycled = nullptr) { + MOZ_ASSERT(aItem); + nsIFrame* frame = aItem->Frame(); + if (aOutIsRecycled) { + *aOutIsRecycled = true; + } + + WebRenderUserDataTable* userDataTable = + frame->GetProperty(WebRenderUserDataProperty::Key()); + + if (!userDataTable) { + userDataTable = new WebRenderUserDataTable(); + frame->AddProperty(WebRenderUserDataProperty::Key(), userDataTable); + } + + RefPtr& data = userDataTable->GetOrInsert( + WebRenderUserDataKey(aItem->GetPerFrameKey(), T::Type())); + if (!data) { + data = new T(GetRenderRootStateManager(), aItem); + mWebRenderUserDatas.PutEntry(data); + if (aOutIsRecycled) { + *aOutIsRecycled = false; + } + } + + MOZ_ASSERT(data); + MOZ_ASSERT(data->GetType() == T::Type()); + + // Mark the data as being used. We will remove unused user data in the end + // of EndTransaction. + data->SetUsed(true); + + switch (T::Type()) { + case WebRenderUserData::UserDataType::eCanvas: + mLastCanvasDatas.PutEntry(data->AsCanvasData()); + break; + case WebRenderUserData::UserDataType::eLocalCanvas: + mLastLocalCanvasDatas.PutEntry(data->AsLocalCanvasData()); + break; + default: + break; + } + + RefPtr res = static_cast(data.get()); + return res.forget(); + } + + WebRenderLayerManager* mManager; + + private: + RenderRootStateManager* GetRenderRootStateManager(); + void CreateWebRenderCommands(nsDisplayItem* aItem, + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + nsDisplayListBuilder* aDisplayListBuilder); + + ClipManager mClipManager; + + // We use this as a temporary data structure while building the mScrollData + // inside a layers-free transaction. + std::vector mLayerScrollData; + // We use this as a temporary data structure to track the current display + // item's ASR as we recurse in CreateWebRenderCommandsFromDisplayList. We + // need this so that WebRenderLayerScrollData items that deeper in the + // tree don't duplicate scroll metadata that their ancestors already have. + std::vector mAsrStack; + const ActiveScrolledRoot* mLastAsr; + + WebRenderUserDataRefTable mWebRenderUserDatas; + + // Store of WebRenderCanvasData objects for use in empty transactions + CanvasDataSet mLastCanvasDatas; + // Store of WebRenderLocalCanvasData objects for use in empty transactions + LocalCanvasDataSet mLastLocalCanvasDatas; + + wr::usize mBuilderDumpIndex; + wr::usize mDumpIndent; + + public: + // Whether consecutive inactive display items should be grouped into one + // blob image. + bool mDoGrouping; + + // True if the most recently build display list contained an svg that + // we did grouping for. + bool mContainsSVGGroup; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_WEBRENDERCOMMANDBUILDER_H */ diff --git a/gfx/layers/wr/WebRenderDrawEventRecorder.cpp b/gfx/layers/wr/WebRenderDrawEventRecorder.cpp new file mode 100644 index 0000000000..db4345d592 --- /dev/null +++ b/gfx/layers/wr/WebRenderDrawEventRecorder.cpp @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WebRenderDrawEventRecorder.h" +#include "mozilla/layers/SharedSurfacesChild.h" +#include "mozilla/layers/SharedSurfacesParent.h" + +namespace mozilla { +using namespace gfx; + +namespace layers { + +void WebRenderDrawEventRecorder::StoreSourceSurfaceRecording( + SourceSurface* aSurface, const char* aReason) { + wr::ExternalImageId extId; + nsresult rv = layers::SharedSurfacesChild::Share(aSurface, extId); + if (NS_FAILED(rv)) { + DrawEventRecorderMemory::StoreSourceSurfaceRecording(aSurface, aReason); + return; + } + + StoreExternalSurfaceRecording(aSurface, wr::AsUint64(extId)); +} + +already_AddRefed WebRenderTranslator::LookupExternalSurface( + uint64_t aKey) { + return SharedSurfacesParent::Get(wr::ToExternalImageId(aKey)); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/wr/WebRenderDrawEventRecorder.h b/gfx/layers/wr/WebRenderDrawEventRecorder.h new file mode 100644 index 0000000000..b08ff5b959 --- /dev/null +++ b/gfx/layers/wr/WebRenderDrawEventRecorder.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_LAYERS_WEBRENDERDRAWTARGETRECORDER_H +#define MOZILLA_LAYERS_WEBRENDERDRAWTARGETRECORDER_H + +#include "mozilla/gfx/DrawEventRecorder.h" +#include "mozilla/gfx/InlineTranslator.h" +#include "mozilla/webrender/webrender_ffi.h" + +namespace mozilla { +namespace layers { + +struct BlobFont { + wr::FontInstanceKey mFontInstanceKey; + gfx::ReferencePtr mScaledFontPtr; +}; + +class WebRenderDrawEventRecorder final : public gfx::DrawEventRecorderMemory { + public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(WebRenderDrawEventRecorder, final) + + explicit WebRenderDrawEventRecorder( + const gfx::SerializeResourcesFn& aSerialize) + : DrawEventRecorderMemory(aSerialize) {} + + void StoreSourceSurfaceRecording(gfx::SourceSurface* aSurface, + const char* aReason) final; + + private: + virtual ~WebRenderDrawEventRecorder() = default; +}; + +class WebRenderTranslator final : public gfx::InlineTranslator { + public: + explicit WebRenderTranslator(gfx::DrawTarget* aDT, + void* aFontContext = nullptr) + : InlineTranslator(aDT, aFontContext) {} + + already_AddRefed LookupExternalSurface( + uint64_t aKey) final; +}; + +} // namespace layers +} // namespace mozilla + +#endif diff --git a/gfx/layers/wr/WebRenderImageHost.cpp b/gfx/layers/wr/WebRenderImageHost.cpp new file mode 100644 index 0000000000..b3e0bf627d --- /dev/null +++ b/gfx/layers/wr/WebRenderImageHost.cpp @@ -0,0 +1,281 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WebRenderImageHost.h" + +#include + +#include "mozilla/ScopeExit.h" +#include "mozilla/layers/AsyncImagePipelineManager.h" +#include "mozilla/layers/Compositor.h" // for Compositor +#include "mozilla/layers/CompositorVsyncScheduler.h" // for CompositorVsyncScheduler +#include "mozilla/layers/Effects.h" // for TexturedEffect, Effect, etc +#include "mozilla/layers/LayerManagerComposite.h" // for TexturedEffect, Effect, etc +#include "mozilla/layers/WebRenderBridgeParent.h" +#include "mozilla/layers/WebRenderTextureHost.h" +#include "nsAString.h" +#include "nsDebug.h" // for NS_WARNING, NS_ASSERTION +#include "nsPrintfCString.h" // for nsPrintfCString +#include "nsString.h" // for nsAutoCString + +namespace mozilla { + +using namespace gfx; + +namespace layers { + +class ISurfaceAllocator; + +WebRenderImageHost::WebRenderImageHost(const TextureInfo& aTextureInfo) + : CompositableHost(aTextureInfo), + ImageComposite(), + mCurrentAsyncImageManager(nullptr) {} + +WebRenderImageHost::~WebRenderImageHost() { MOZ_ASSERT(mWrBridges.empty()); } + +void WebRenderImageHost::UseTextureHost( + const nsTArray& aTextures) { + CompositableHost::UseTextureHost(aTextures); + MOZ_ASSERT(aTextures.Length() >= 1); + + nsTArray newImages; + + for (uint32_t i = 0; i < aTextures.Length(); ++i) { + const TimedTexture& t = aTextures[i]; + MOZ_ASSERT(t.mTexture); + if (i + 1 < aTextures.Length() && t.mProducerID == mLastProducerID && + t.mFrameID < mLastFrameID) { + // Ignore frames before a frame that we already composited. We don't + // ever want to display these frames. This could be important if + // the frame producer adjusts timestamps (e.g. to track the audio clock) + // and the new frame times are earlier. + continue; + } + TimedImage& img = *newImages.AppendElement(); + img.mTextureHost = t.mTexture; + img.mTimeStamp = t.mTimeStamp; + img.mPictureRect = t.mPictureRect; + img.mFrameID = t.mFrameID; + img.mProducerID = t.mProducerID; + img.mTextureHost->SetCropRect(img.mPictureRect); + img.mTextureHost->Updated(); + } + + SetImages(std::move(newImages)); + + if (GetAsyncRef()) { + for (const auto& it : mWrBridges) { + RefPtr wrBridge = it.second->WrBridge(); + if (wrBridge && wrBridge->CompositorScheduler()) { + wrBridge->CompositorScheduler()->ScheduleComposition(); + } + } + } + + // Video producers generally send replacement images with the same frameID but + // slightly different timestamps in order to sync with the audio clock. This + // means that any CompositeUntil() call we made in Composite() may no longer + // guarantee that we'll composite until the next frame is ready. Fix that + // here. + if (mLastFrameID >= 0 && !mWrBridges.empty()) { + for (const auto& img : Images()) { + bool frameComesAfter = + img.mFrameID > mLastFrameID || img.mProducerID != mLastProducerID; + if (frameComesAfter && !img.mTimeStamp.IsNull()) { + for (const auto& it : mWrBridges) { + RefPtr wrBridge = it.second->WrBridge(); + if (wrBridge) { + wrBridge->AsyncImageManager()->CompositeUntil( + img.mTimeStamp + TimeDuration::FromMilliseconds(BIAS_TIME_MS)); + } + } + break; + } + } + } +} + +void WebRenderImageHost::UseComponentAlphaTextures( + TextureHost* aTextureOnBlack, TextureHost* aTextureOnWhite) { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); +} + +void WebRenderImageHost::CleanupResources() { + ClearImages(); + SetCurrentTextureHost(nullptr); +} + +void WebRenderImageHost::RemoveTextureHost(TextureHost* aTexture) { + CompositableHost::RemoveTextureHost(aTexture); + RemoveImagesWithTextureHost(aTexture); +} + +TimeStamp WebRenderImageHost::GetCompositionTime() const { + TimeStamp time; + + MOZ_ASSERT(mCurrentAsyncImageManager); + if (mCurrentAsyncImageManager) { + time = mCurrentAsyncImageManager->GetCompositionTime(); + } + return time; +} + +CompositionOpportunityId WebRenderImageHost::GetCompositionOpportunityId() + const { + CompositionOpportunityId id; + + MOZ_ASSERT(mCurrentAsyncImageManager); + if (mCurrentAsyncImageManager) { + id = mCurrentAsyncImageManager->GetCompositionOpportunityId(); + } + return id; +} + +void WebRenderImageHost::AppendImageCompositeNotification( + const ImageCompositeNotificationInfo& aInfo) const { + if (mCurrentAsyncImageManager) { + mCurrentAsyncImageManager->AppendImageCompositeNotification(aInfo); + } +} + +TextureHost* WebRenderImageHost::GetAsTextureHost(IntRect* aPictureRect) { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + return nullptr; +} + +TextureHost* WebRenderImageHost::GetAsTextureHostForComposite( + AsyncImagePipelineManager* aAsyncImageManager) { + mCurrentAsyncImageManager = aAsyncImageManager; + const auto onExit = + mozilla::MakeScopeExit([&]() { mCurrentAsyncImageManager = nullptr; }); + + int imageIndex = ChooseImageIndex(); + if (imageIndex < 0) { + SetCurrentTextureHost(nullptr); + return nullptr; + } + + if (uint32_t(imageIndex) + 1 < ImagesCount()) { + mCurrentAsyncImageManager->CompositeUntil( + GetImage(imageIndex + 1)->mTimeStamp + + TimeDuration::FromMilliseconds(BIAS_TIME_MS)); + } + + const TimedImage* img = GetImage(imageIndex); + SetCurrentTextureHost(img->mTextureHost); + + if (mCurrentAsyncImageManager->GetCompositionTime()) { + // We are in a composition. Send ImageCompositeNotifications. + OnFinishRendering(imageIndex, img, mAsyncRef.mProcessId, mAsyncRef.mHandle); + } + + return mCurrentTextureHost; +} + +void WebRenderImageHost::SetCurrentTextureHost(TextureHost* aTexture) { + if (aTexture == mCurrentTextureHost.get()) { + return; + } + mCurrentTextureHost = aTexture; +} + +void WebRenderImageHost::Attach(Layer* aLayer, TextureSourceProvider* aProvider, + AttachFlags aFlags) {} + +void WebRenderImageHost::Composite( + Compositor* aCompositor, LayerComposite* aLayer, EffectChain& aEffectChain, + float aOpacity, const gfx::Matrix4x4& aTransform, + const gfx::SamplingFilter aSamplingFilter, const gfx::IntRect& aClipRect, + const nsIntRegion* aVisibleRegion, const Maybe& aGeometry) { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); +} + +void WebRenderImageHost::SetTextureSourceProvider( + TextureSourceProvider* aProvider) { + if (mTextureSourceProvider != aProvider) { + for (const auto& img : Images()) { + img.mTextureHost->SetTextureSourceProvider(aProvider); + } + } + CompositableHost::SetTextureSourceProvider(aProvider); +} + +void WebRenderImageHost::PrintInfo(std::stringstream& aStream, + const char* aPrefix) { + aStream << aPrefix; + aStream << nsPrintfCString("WebRenderImageHost (0x%p)", this).get(); + + nsAutoCString pfx(aPrefix); + pfx += " "; + for (const auto& img : Images()) { + aStream << "\n"; + img.mTextureHost->PrintInfo(aStream, pfx.get()); + aStream << " [picture-rect=" << img.mPictureRect << "]"; + } +} + +void WebRenderImageHost::Dump(std::stringstream& aStream, const char* aPrefix, + bool aDumpHtml) { + for (const auto& img : Images()) { + aStream << aPrefix; + aStream << (aDumpHtml ? "
    • TextureHost: " : "TextureHost: "); + DumpTextureHost(aStream, img.mTextureHost); + aStream << (aDumpHtml ? "
    " : " "); + } +} + +already_AddRefed WebRenderImageHost::GetAsSurface() { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + return nullptr; +} + +bool WebRenderImageHost::Lock() { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); + return false; +} + +void WebRenderImageHost::Unlock() { + MOZ_ASSERT_UNREACHABLE("unexpected to be called"); +} + +IntSize WebRenderImageHost::GetImageSize() { + const TimedImage* img = ChooseImage(); + if (img) { + return IntSize(img->mPictureRect.Width(), img->mPictureRect.Height()); + } + return IntSize(); +} + +void WebRenderImageHost::SetWrBridge(const wr::PipelineId& aPipelineId, + WebRenderBridgeParent* aWrBridge) { + MOZ_ASSERT(aWrBridge); + MOZ_ASSERT(!mCurrentAsyncImageManager); +#ifdef DEBUG + const auto it = mWrBridges.find(wr::AsUint64(aPipelineId)); + MOZ_ASSERT(it == mWrBridges.end()); +#endif + RefPtr ref = + aWrBridge->GetWebRenderBridgeParentRef(); + mWrBridges.emplace(wr::AsUint64(aPipelineId), ref); +} + +void WebRenderImageHost::ClearWrBridge(const wr::PipelineId& aPipelineId, + WebRenderBridgeParent* aWrBridge) { + MOZ_ASSERT(aWrBridge); + MOZ_ASSERT(!mCurrentAsyncImageManager); + + const auto it = mWrBridges.find(wr::AsUint64(aPipelineId)); + MOZ_ASSERT(it != mWrBridges.end()); + if (it == mWrBridges.end()) { + gfxCriticalNote << "WrBridge mismatch happened"; + return; + } + mWrBridges.erase(it); + SetCurrentTextureHost(nullptr); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/wr/WebRenderImageHost.h b/gfx/layers/wr/WebRenderImageHost.h new file mode 100644 index 0000000000..f2fb463ff9 --- /dev/null +++ b/gfx/layers/wr/WebRenderImageHost.h @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_WEBRENDERIMAGEHOST_H +#define MOZILLA_GFX_WEBRENDERIMAGEHOST_H + +#include + +#include "CompositableHost.h" // for CompositableHost +#include "mozilla/layers/ImageComposite.h" // for ImageComposite +#include "mozilla/WeakPtr.h" + +namespace mozilla { +namespace layers { + +class AsyncImagePipelineManager; +class WebRenderBridgeParent; +class WebRenderBridgeParentRef; + +/** + * ImageHost. Works with ImageClientSingle and ImageClientBuffered + */ +class WebRenderImageHost : public CompositableHost, public ImageComposite { + public: + explicit WebRenderImageHost(const TextureInfo& aTextureInfo); + virtual ~WebRenderImageHost(); + + CompositableType GetType() override { return mTextureInfo.mCompositableType; } + + void Composite(Compositor* aCompositor, LayerComposite* aLayer, + EffectChain& aEffectChain, float aOpacity, + const gfx::Matrix4x4& aTransform, + const gfx::SamplingFilter aSamplingFilter, + const gfx::IntRect& aClipRect, + const nsIntRegion* aVisibleRegion = nullptr, + const Maybe& aGeometry = Nothing()) override; + + void UseTextureHost(const nsTArray& aTextures) override; + void UseComponentAlphaTextures(TextureHost* aTextureOnBlack, + TextureHost* aTextureOnWhite) override; + void RemoveTextureHost(TextureHost* aTexture) override; + + TextureHost* GetAsTextureHost(gfx::IntRect* aPictureRect = nullptr) override; + + void Attach(Layer* aLayer, TextureSourceProvider* aProvider, + AttachFlags aFlags = NO_FLAGS) override; + + void SetTextureSourceProvider(TextureSourceProvider* aProvider) override; + + gfx::IntSize GetImageSize() override; + + void PrintInfo(std::stringstream& aStream, const char* aPrefix) override; + + void Dump(std::stringstream& aStream, const char* aPrefix = "", + bool aDumpHtml = false) override; + + already_AddRefed GetAsSurface() override; + + bool Lock() override; + + void Unlock() override; + + void CleanupResources() override; + + uint32_t GetDroppedFrames() override { return GetDroppedFramesAndReset(); } + + WebRenderImageHost* AsWebRenderImageHost() override { return this; } + + TextureHost* GetAsTextureHostForComposite( + AsyncImagePipelineManager* aAsyncImageManager); + + void SetWrBridge(const wr::PipelineId& aPipelineId, + WebRenderBridgeParent* aWrBridge); + + void ClearWrBridge(const wr::PipelineId& aPipelineId, + WebRenderBridgeParent* aWrBridge); + + TextureHost* GetCurrentTextureHost() { return mCurrentTextureHost; } + + protected: + // ImageComposite + TimeStamp GetCompositionTime() const override; + CompositionOpportunityId GetCompositionOpportunityId() const override; + void AppendImageCompositeNotification( + const ImageCompositeNotificationInfo& aInfo) const override; + + void SetCurrentTextureHost(TextureHost* aTexture); + + std::unordered_map> mWrBridges; + + AsyncImagePipelineManager* mCurrentAsyncImageManager; + + CompositableTextureHostRef mCurrentTextureHost; +}; + +} // namespace layers +} // namespace mozilla + +#endif // MOZILLA_GFX_WEBRENDERIMAGEHOST_H diff --git a/gfx/layers/wr/WebRenderLayerManager.cpp b/gfx/layers/wr/WebRenderLayerManager.cpp new file mode 100644 index 0000000000..b0011e24de --- /dev/null +++ b/gfx/layers/wr/WebRenderLayerManager.cpp @@ -0,0 +1,772 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WebRenderLayerManager.h" + +#include "BasicLayers.h" +#include "Layers.h" + +#include "GeckoProfiler.h" +#include "mozilla/StaticPrefs_apz.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/gfx/DrawEventRecorder.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/layers/CompositorBridgeChild.h" +#include "mozilla/layers/StackingContextHelper.h" +#include "mozilla/layers/TextureClient.h" +#include "mozilla/layers/TransactionIdAllocator.h" +#include "mozilla/layers/WebRenderBridgeChild.h" +#include "mozilla/layers/UpdateImageHelper.h" +#include "nsDisplayList.h" +#include "nsLayoutUtils.h" +#include "WebRenderCanvasRenderer.h" + +#ifdef XP_WIN +# include "gfxDWriteFonts.h" +#endif + +namespace mozilla { + +using namespace gfx; + +namespace layers { + +WebRenderLayerManager::WebRenderLayerManager(nsIWidget* aWidget) + : mWidget(aWidget), + mLatestTransactionId{0}, + mNeedsComposite(false), + mIsFirstPaint(false), + mTarget(nullptr), + mPaintSequenceNumber(0), + mWebRenderCommandBuilder(this), + mLastDisplayListSize(0) { + MOZ_COUNT_CTOR(WebRenderLayerManager); + mStateManager.mLayerManager = this; + + if (XRE_IsContentProcess() && + StaticPrefs::gfx_webrender_enable_item_cache_AtStartup()) { + static const size_t kInitialCacheSize = 1024; + static const size_t kMaximumCacheSize = 10240; + + mDisplayItemCache.SetCapacity(kInitialCacheSize, kMaximumCacheSize); + } +} + +KnowsCompositor* WebRenderLayerManager::AsKnowsCompositor() { return mWrChild; } + +bool WebRenderLayerManager::Initialize( + PCompositorBridgeChild* aCBChild, wr::PipelineId aLayersId, + TextureFactoryIdentifier* aTextureFactoryIdentifier, nsCString& aError) { + MOZ_ASSERT(mWrChild == nullptr); + MOZ_ASSERT(aTextureFactoryIdentifier); + + // When we fail to initialize WebRender, it is useful to know if it has ever + // succeeded, or if this is the first attempt. + static bool hasInitialized = false; + + WindowKind windowKind; + if (mWidget->WindowType() != eWindowType_popup) { + windowKind = WindowKind::MAIN; + } else { + windowKind = WindowKind::SECONDARY; + } + + LayoutDeviceIntSize size = mWidget->GetClientSize(); + PWebRenderBridgeChild* bridge = + aCBChild->SendPWebRenderBridgeConstructor(aLayersId, size, windowKind); + if (!bridge) { + // This should only fail if we attempt to access a layer we don't have + // permission for, or more likely, the GPU process crashed again during + // reinitialization. We can expect to be notified again to reinitialize + // (which may or may not be using WebRender). + gfxCriticalNote << "Failed to create WebRenderBridgeChild."; + aError.Assign(hasInitialized + ? "FEATURE_FAILURE_WEBRENDER_INITIALIZE_IPDL_POST"_ns + : "FEATURE_FAILURE_WEBRENDER_INITIALIZE_IPDL_FIRST"_ns); + return false; + } + + TextureFactoryIdentifier textureFactoryIdentifier; + wr::MaybeIdNamespace idNamespace; + // Sync ipc + if (!bridge->SendEnsureConnected(&textureFactoryIdentifier, &idNamespace, + &aError)) { + gfxCriticalNote << "Failed as lost WebRenderBridgeChild."; + aError.Assign(hasInitialized + ? "FEATURE_FAILURE_WEBRENDER_INITIALIZE_SYNC_POST"_ns + : "FEATURE_FAILURE_WEBRENDER_INITIALIZE_SYNC_FIRST"_ns); + return false; + } + + if (textureFactoryIdentifier.mParentBackend == LayersBackend::LAYERS_NONE || + idNamespace.isNothing()) { + gfxCriticalNote << "Failed to connect WebRenderBridgeChild."; + aError.Append(hasInitialized ? "_POST"_ns : "_FIRST"_ns); + return false; + } + + mWrChild = static_cast(bridge); + WrBridge()->SetWebRenderLayerManager(this); + WrBridge()->IdentifyTextureHost(textureFactoryIdentifier); + WrBridge()->SetNamespace(idNamespace.ref()); + *aTextureFactoryIdentifier = textureFactoryIdentifier; + hasInitialized = true; + return true; +} + +void WebRenderLayerManager::Destroy() { DoDestroy(/* aIsSync */ false); } + +void WebRenderLayerManager::DoDestroy(bool aIsSync) { + MOZ_ASSERT(NS_IsMainThread()); + + if (IsDestroyed()) { + return; + } + + LayerManager::Destroy(); + + mStateManager.Destroy(); + + if (WrBridge()) { + WrBridge()->Destroy(aIsSync); + } + + mWebRenderCommandBuilder.Destroy(); + + if (mTransactionIdAllocator) { + // Make sure to notify the refresh driver just in case it's waiting on a + // pending transaction. Do this at the top of the event loop so we don't + // cause a paint to occur during compositor shutdown. + RefPtr allocator = mTransactionIdAllocator; + TransactionId id = mLatestTransactionId; + + RefPtr task = NS_NewRunnableFunction( + "TransactionIdAllocator::NotifyTransactionCompleted", + [allocator, id]() -> void { + allocator->NotifyTransactionCompleted(id); + }); + NS_DispatchToMainThread(task.forget()); + } + + // Forget the widget pointer in case we outlive our owning widget. + mWidget = nullptr; +} + +WebRenderLayerManager::~WebRenderLayerManager() { + Destroy(); + MOZ_COUNT_DTOR(WebRenderLayerManager); +} + +CompositorBridgeChild* WebRenderLayerManager::GetCompositorBridgeChild() { + return WrBridge()->GetCompositorBridgeChild(); +} + +void WebRenderLayerManager::GetBackendName(nsAString& name) { + if (WrBridge()->UsingSoftwareWebRenderD3D11()) { + name.AssignLiteral("WebRender (Software D3D11)"); + } else if (WrBridge()->UsingSoftwareWebRender()) { + name.AssignLiteral("WebRender (Software)"); + } else { + name.AssignLiteral("WebRender"); + } +} + +uint32_t WebRenderLayerManager::StartFrameTimeRecording(int32_t aBufferSize) { + CompositorBridgeChild* renderer = GetCompositorBridgeChild(); + if (renderer) { + uint32_t startIndex; + renderer->SendStartFrameTimeRecording(aBufferSize, &startIndex); + return startIndex; + } + return -1; +} + +void WebRenderLayerManager::StopFrameTimeRecording( + uint32_t aStartIndex, nsTArray& aFrameIntervals) { + CompositorBridgeChild* renderer = GetCompositorBridgeChild(); + if (renderer) { + renderer->SendStopFrameTimeRecording(aStartIndex, &aFrameIntervals); + } +} + +void WebRenderLayerManager::PayloadPresented(const TimeStamp& aTimeStamp) { + MOZ_CRASH("WebRenderLayerManager::PayloadPresented should not be called"); +} + +void WebRenderLayerManager::TakeCompositionPayloads( + nsTArray& aPayloads) { + aPayloads.Clear(); + + std::swap(mPayload, aPayloads); +} + +bool WebRenderLayerManager::BeginTransactionWithTarget(gfxContext* aTarget, + const nsCString& aURL) { + mTarget = aTarget; + return BeginTransaction(aURL); +} + +bool WebRenderLayerManager::BeginTransaction(const nsCString& aURL) { + if (!WrBridge()->IPCOpen()) { + gfxCriticalNote << "IPC Channel is already torn down unexpectedly\n"; + return false; + } + + mTransactionStart = TimeStamp::Now(); + mURL = aURL; + + // Increment the paint sequence number even if test logging isn't + // enabled in this process; it may be enabled in the parent process, + // and the parent process expects unique sequence numbers. + ++mPaintSequenceNumber; + if (StaticPrefs::apz_test_logging_enabled()) { + mApzTestData.StartNewPaint(mPaintSequenceNumber); + } + return true; +} + +bool WebRenderLayerManager::EndEmptyTransaction(EndTransactionFlags aFlags) { + // If we haven't sent a display list (since creation or since the last time we + // sent ClearDisplayList to the parent) then we can't do an empty transaction + // because the parent doesn't have a display list for us and we need to send a + // display list first. + if (!WrBridge()->GetSentDisplayList()) { + return false; + } + + mDisplayItemCache.SkipWaitingForPartialDisplayList(); + + // Since we don't do repeat transactions right now, just set the time + mAnimationReadyTime = TimeStamp::Now(); + + mLatestTransactionId = + mTransactionIdAllocator->GetTransactionId(/*aThrottle*/ true); + + if (aFlags & EndTransactionFlags::END_NO_COMPOSITE && + !mWebRenderCommandBuilder.NeedsEmptyTransaction()) { + if (mPendingScrollUpdates.IsEmpty()) { + MOZ_ASSERT(!mTarget); + WrBridge()->SendSetFocusTarget(mFocusTarget); + // Revoke TransactionId to trigger next paint. + mTransactionIdAllocator->RevokeTransactionId(mLatestTransactionId); + mLatestTransactionId = mLatestTransactionId.Prev(); + return true; + } + } + + LayoutDeviceIntSize size = mWidget->GetClientSize(); + WrBridge()->BeginTransaction(); + + mWebRenderCommandBuilder.EmptyTransaction(); + + // Get the time of when the refresh driver start its tick (if available), + // otherwise use the time of when LayerManager::BeginTransaction was called. + TimeStamp refreshStart = mTransactionIdAllocator->GetTransactionStart(); + if (!refreshStart) { + refreshStart = mTransactionStart; + } + + // Skip the synchronization for buffer since we also skip the painting during + // device-reset status. + if (!gfxPlatform::GetPlatform()->DidRenderingDeviceReset()) { + if (WrBridge()->GetSyncObject() && + WrBridge()->GetSyncObject()->IsSyncObjectValid()) { + WrBridge()->GetSyncObject()->Synchronize(); + } + } + + GetCompositorBridgeChild()->EndCanvasTransaction(); + + Maybe transactionData; + if (mStateManager.mAsyncResourceUpdates || !mPendingScrollUpdates.IsEmpty() || + WrBridge()->HasWebRenderParentCommands()) { + transactionData.emplace(); + transactionData->mIdNamespace = WrBridge()->GetNamespace(); + transactionData->mPaintSequenceNumber = mPaintSequenceNumber; + if (mStateManager.mAsyncResourceUpdates) { + mStateManager.mAsyncResourceUpdates->Flush( + transactionData->mResourceUpdates, transactionData->mSmallShmems, + transactionData->mLargeShmems); + } + transactionData->mScrollUpdates = std::move(mPendingScrollUpdates); + for (auto it = transactionData->mScrollUpdates.Iter(); !it.Done(); + it.Next()) { + nsLayoutUtils::NotifyPaintSkipTransaction(/*scroll id=*/it.Key()); + } + } + + Maybe nothing; + WrBridge()->EndEmptyTransaction(mFocusTarget, std::move(transactionData), + mLatestTransactionId, + mTransactionIdAllocator->GetVsyncId(), + mTransactionIdAllocator->GetVsyncStart(), + refreshStart, mTransactionStart, mURL); + mTransactionStart = TimeStamp(); + + MakeSnapshotIfRequired(size); + return true; +} + +void WebRenderLayerManager::EndTransaction(DrawPaintedLayerCallback aCallback, + void* aCallbackData, + EndTransactionFlags aFlags) { + // This should never get called, all callers should use + // EndTransactionWithoutLayer instead. + MOZ_ASSERT(false); +} + +void WebRenderLayerManager::EndTransactionWithoutLayer( + nsDisplayList* aDisplayList, nsDisplayListBuilder* aDisplayListBuilder, + WrFiltersHolder&& aFilters, WebRenderBackgroundData* aBackground) { + AUTO_PROFILER_TRACING_MARKER("Paint", "RenderLayers", GRAPHICS); + + // Since we don't do repeat transactions right now, just set the time + mAnimationReadyTime = TimeStamp::Now(); + + WrBridge()->BeginTransaction(); + + LayoutDeviceIntSize size = mWidget->GetClientSize(); + + // While the first display list after tab-switch can be large, the + // following ones are always smaller thanks to interning (rarely above 0.3MB). + // So don't let the spike of the first allocation make us allocate a large + // contiguous buffer (with some likelihood of OOM, see bug 1531819). + static const size_t kMaxPrealloc = 300000; + size_t preallocate = + mLastDisplayListSize < kMaxPrealloc ? mLastDisplayListSize : kMaxPrealloc; + + wr::DisplayListBuilder builder(WrBridge()->GetPipeline(), preallocate, + &mDisplayItemCache); + + wr::IpcResourceUpdateQueue resourceUpdates(WrBridge()); + wr::usize builderDumpIndex = 0; + bool containsSVGGroup = false; + bool dumpEnabled = + mWebRenderCommandBuilder.ShouldDumpDisplayList(aDisplayListBuilder); + Maybe cacheSuppressor; + if (dumpEnabled) { + cacheSuppressor.emplace(&mDisplayItemCache); + printf_stderr("-- WebRender display list build --\n"); + } + + if (XRE_IsContentProcess() && + StaticPrefs::gfx_webrender_dl_dump_content_serialized()) { + builder.DumpSerializedDisplayList(); + } + + if (aDisplayList) { + MOZ_ASSERT(aDisplayListBuilder && !aBackground); + // Record the time spent "layerizing". WR doesn't actually layerize but + // generating the WR display list is the closest equivalent + PaintTelemetry::AutoRecord record(PaintTelemetry::Metric::Layerization); + + mDisplayItemCache.SetDisplayList(aDisplayListBuilder, aDisplayList); + + mWebRenderCommandBuilder.BuildWebRenderCommands( + builder, resourceUpdates, aDisplayList, aDisplayListBuilder, + mScrollData, std::move(aFilters)); + + aDisplayListBuilder->NotifyAndClearScrollFrames(); + + builderDumpIndex = mWebRenderCommandBuilder.GetBuilderDumpIndex(); + containsSVGGroup = mWebRenderCommandBuilder.GetContainsSVGGroup(); + } else { + // ViewToPaint does not have frame yet, then render only background clolor. + MOZ_ASSERT(!aDisplayListBuilder && aBackground); + aBackground->AddWebRenderCommands(builder); + if (dumpEnabled) { + printf_stderr("(no display list; background only)\n"); + builderDumpIndex = + builder.Dump(/*indent*/ 1, Some(builderDumpIndex), Nothing()); + } + } + + mWidget->AddWindowOverlayWebRenderCommands(WrBridge(), builder, + resourceUpdates); + if (dumpEnabled) { + printf_stderr("(window overlay)\n"); + Unused << builder.Dump(/*indent*/ 1, Some(builderDumpIndex), Nothing()); + } + + if (AsyncPanZoomEnabled()) { + if (mIsFirstPaint) { + mScrollData.SetIsFirstPaint(); + mIsFirstPaint = false; + } + mScrollData.SetPaintSequenceNumber(mPaintSequenceNumber); + if (dumpEnabled) { + std::stringstream str; + str << mScrollData; + print_stderr(str); + } + } + + // Since we're sending a full mScrollData that will include the new scroll + // offsets, and we can throw away the pending scroll updates we had kept for + // an empty transaction. + auto scrollIdsUpdated = ClearPendingScrollInfoUpdate(); + for (ScrollableLayerGuid::ViewID update : scrollIdsUpdated) { + nsLayoutUtils::NotifyPaintSkipTransaction(update); + } + + mLatestTransactionId = + mTransactionIdAllocator->GetTransactionId(/*aThrottle*/ true); + + // Get the time of when the refresh driver start its tick (if available), + // otherwise use the time of when LayerManager::BeginTransaction was called. + TimeStamp refreshStart = mTransactionIdAllocator->GetTransactionStart(); + if (!refreshStart) { + refreshStart = mTransactionStart; + } + + if (mStateManager.mAsyncResourceUpdates) { + if (resourceUpdates.IsEmpty()) { + resourceUpdates.ReplaceResources( + std::move(mStateManager.mAsyncResourceUpdates.ref())); + } else { + WrBridge()->UpdateResources(mStateManager.mAsyncResourceUpdates.ref()); + } + mStateManager.mAsyncResourceUpdates.reset(); + } + mStateManager.DiscardImagesInTransaction(resourceUpdates); + + WrBridge()->RemoveExpiredFontKeys(resourceUpdates); + + // Skip the synchronization for buffer since we also skip the painting during + // device-reset status. + if (!gfxPlatform::GetPlatform()->DidRenderingDeviceReset()) { + if (WrBridge()->GetSyncObject() && + WrBridge()->GetSyncObject()->IsSyncObjectValid()) { + WrBridge()->GetSyncObject()->Synchronize(); + } + } + + GetCompositorBridgeChild()->EndCanvasTransaction(); + + { + AUTO_PROFILER_TRACING_MARKER("Paint", "ForwardDPTransaction", GRAPHICS); + DisplayListData dlData; + builder.Finalize(dlData); + mLastDisplayListSize = dlData.mDL->mCapacity; + resourceUpdates.Flush(dlData.mResourceUpdates, dlData.mSmallShmems, + dlData.mLargeShmems); + dlData.mRect = + LayoutDeviceRect(LayoutDevicePoint(), LayoutDeviceSize(size)); + dlData.mScrollData.emplace(std::move(mScrollData)); + + bool ret = WrBridge()->EndTransaction( + std::move(dlData), mLatestTransactionId, containsSVGGroup, + mTransactionIdAllocator->GetVsyncId(), + mTransactionIdAllocator->GetVsyncStart(), refreshStart, + mTransactionStart, mURL); + if (!ret) { + // Failed to send display list, reset display item cache state. + mDisplayItemCache.Clear(); + } + + WrBridge()->SendSetFocusTarget(mFocusTarget); + mFocusTarget = FocusTarget(); + } + + // Discard animations after calling WrBridge()->EndTransaction(). + // It updates mWrEpoch in WebRenderBridgeParent. The updated mWrEpoch is + // necessary for deleting animations at the correct time. + mStateManager.DiscardCompositorAnimations(); + + mTransactionStart = TimeStamp(); + + MakeSnapshotIfRequired(size); + mNeedsComposite = false; +} + +void WebRenderLayerManager::SetFocusTarget(const FocusTarget& aFocusTarget) { + mFocusTarget = aFocusTarget; +} + +bool WebRenderLayerManager::AsyncPanZoomEnabled() const { + return mWidget->AsyncPanZoomEnabled(); +} + +void WebRenderLayerManager::MakeSnapshotIfRequired(LayoutDeviceIntSize aSize) { + if (!mTarget || aSize.IsEmpty()) { + return; + } + + // XXX Add other TextureData supports. + // Only BufferTexture is supported now. + + // TODO: fixup for proper surface format. + // The GLES spec only guarantees that RGBA can be used with glReadPixels, + // so on Android we use RGBA. + SurfaceFormat format = +#ifdef MOZ_WIDGET_ANDROID + SurfaceFormat::R8G8B8A8; +#else + SurfaceFormat::B8G8R8A8; +#endif + RefPtr texture = TextureClient::CreateForRawBufferAccess( + WrBridge(), format, aSize.ToUnknownSize(), BackendType::SKIA, + TextureFlags::SNAPSHOT); + if (!texture) { + return; + } + + texture->InitIPDLActor(WrBridge()); + if (!texture->GetIPDLActor()) { + return; + } + + IntRect bounds = ToOutsideIntRect(mTarget->GetClipExtents()); + bool needsYFlip = false; + if (!WrBridge()->SendGetSnapshot(texture->GetIPDLActor(), &needsYFlip)) { + return; + } + + TextureClientAutoLock autoLock(texture, OpenMode::OPEN_READ_ONLY); + if (!autoLock.Succeeded()) { + return; + } + RefPtr drawTarget = texture->BorrowDrawTarget(); + if (!drawTarget || !drawTarget->IsValid()) { + return; + } + RefPtr snapshot = drawTarget->Snapshot(); + /* + static int count = 0; + char filename[100]; + snprintf(filename, 100, "output%d.png", count++); + printf_stderr("Writing to :%s\n", filename); + gfxUtils::WriteAsPNG(snapshot, filename); + */ + + Rect dst(bounds.X(), bounds.Y(), bounds.Width(), bounds.Height()); + Rect src(0, 0, bounds.Width(), bounds.Height()); + + Matrix m; + if (needsYFlip) { + m = Matrix::Scaling(1.0, -1.0).PostTranslate(0.0, aSize.height); + } + SurfacePattern pattern(snapshot, ExtendMode::CLAMP, m); + DrawTarget* dt = mTarget->GetDrawTarget(); + MOZ_RELEASE_ASSERT(dt); + dt->FillRect(dst, pattern); + + mTarget = nullptr; +} + +void WebRenderLayerManager::DiscardImages() { + wr::IpcResourceUpdateQueue resources(WrBridge()); + mStateManager.DiscardImagesInTransaction(resources); + WrBridge()->UpdateResources(resources); +} + +void WebRenderLayerManager::DiscardLocalImages() { + mStateManager.DiscardLocalImages(); +} + +void WebRenderLayerManager::SetLayersObserverEpoch(LayersObserverEpoch aEpoch) { + if (WrBridge()->IPCOpen()) { + WrBridge()->SendSetLayersObserverEpoch(aEpoch); + } +} + +void WebRenderLayerManager::DidComposite( + TransactionId aTransactionId, const mozilla::TimeStamp& aCompositeStart, + const mozilla::TimeStamp& aCompositeEnd) { + if (IsDestroyed()) { + return; + } + + MOZ_ASSERT(mWidget); + + // Notifying the observers may tick the refresh driver which can cause + // a lot of different things to happen that may affect the lifetime of + // this layer manager. So let's make sure this object stays alive until + // the end of the method invocation. + RefPtr selfRef = this; + + // |aTransactionId| will be > 0 if the compositor is acknowledging a shadow + // layers transaction. + if (aTransactionId.IsValid()) { + nsIWidgetListener* listener = mWidget->GetWidgetListener(); + if (listener) { + listener->DidCompositeWindow(aTransactionId, aCompositeStart, + aCompositeEnd); + } + listener = mWidget->GetAttachedWidgetListener(); + if (listener) { + listener->DidCompositeWindow(aTransactionId, aCompositeStart, + aCompositeEnd); + } + if (mTransactionIdAllocator) { + mTransactionIdAllocator->NotifyTransactionCompleted(aTransactionId); + } + } + + // These observers fire whether or not we were in a transaction. + for (size_t i = 0; i < mDidCompositeObservers.Length(); i++) { + mDidCompositeObservers[i]->DidComposite(); + } +} + +void WebRenderLayerManager::ClearCachedResources(Layer* aSubtree) { + if (!WrBridge()->IPCOpen()) { + gfxCriticalNote << "IPC Channel is already torn down unexpectedly\n"; + return; + } + WrBridge()->BeginClearCachedResources(); + mWebRenderCommandBuilder.ClearCachedResources(); + DiscardImages(); + mStateManager.ClearCachedResources(); + WrBridge()->EndClearCachedResources(); +} + +void WebRenderLayerManager::WrUpdated() { + ClearAsyncAnimations(); + mWebRenderCommandBuilder.ClearCachedResources(); + DiscardLocalImages(); + mDisplayItemCache.Clear(); + + if (mWidget) { + if (dom::BrowserChild* browserChild = mWidget->GetOwningBrowserChild()) { + browserChild->SchedulePaint(); + } + } +} + +void WebRenderLayerManager::UpdateTextureFactoryIdentifier( + const TextureFactoryIdentifier& aNewIdentifier) { + WrBridge()->IdentifyTextureHost(aNewIdentifier); +} + +TextureFactoryIdentifier WebRenderLayerManager::GetTextureFactoryIdentifier() { + return WrBridge()->GetTextureFactoryIdentifier(); +} + +void WebRenderLayerManager::SetTransactionIdAllocator( + TransactionIdAllocator* aAllocator) { + // When changing the refresh driver, the previous refresh driver may never + // receive updates of pending transactions it's waiting for. So clear the + // waiting state before assigning another refresh driver. + if (mTransactionIdAllocator && (aAllocator != mTransactionIdAllocator)) { + mTransactionIdAllocator->ClearPendingTransactions(); + + // We should also reset the transaction id of the new allocator to previous + // allocator's last transaction id, so that completed transactions for + // previous allocator will be ignored and won't confuse the new allocator. + if (aAllocator) { + aAllocator->ResetInitialTransactionId( + mTransactionIdAllocator->LastTransactionId()); + } + } + + mTransactionIdAllocator = aAllocator; +} + +TransactionId WebRenderLayerManager::GetLastTransactionId() { + return mLatestTransactionId; +} + +void WebRenderLayerManager::AddDidCompositeObserver( + DidCompositeObserver* aObserver) { + if (!mDidCompositeObservers.Contains(aObserver)) { + mDidCompositeObservers.AppendElement(aObserver); + } +} + +void WebRenderLayerManager::RemoveDidCompositeObserver( + DidCompositeObserver* aObserver) { + mDidCompositeObservers.RemoveElement(aObserver); +} + +void WebRenderLayerManager::FlushRendering() { + CompositorBridgeChild* cBridge = GetCompositorBridgeChild(); + if (!cBridge) { + return; + } + MOZ_ASSERT(mWidget); + + // If value of IsResizingNativeWidget() is nothing, we assume that resizing + // might happen. + bool resizing = mWidget && mWidget->IsResizingNativeWidget().valueOr(true); + + // Limit async FlushRendering to !resizing and Win DComp. + // XXX relax the limitation + if (WrBridge()->GetCompositorUseDComp() && !resizing) { + cBridge->SendFlushRenderingAsync(); + } else if (mWidget->SynchronouslyRepaintOnResize() || + StaticPrefs::layers_force_synchronous_resize()) { + cBridge->SendFlushRendering(); + } else { + cBridge->SendFlushRenderingAsync(); + } +} + +void WebRenderLayerManager::WaitOnTransactionProcessed() { + CompositorBridgeChild* bridge = GetCompositorBridgeChild(); + if (bridge) { + bridge->SendWaitOnTransactionProcessed(); + } +} + +void WebRenderLayerManager::SendInvalidRegion(const nsIntRegion& aRegion) { + // XXX Webrender does not support invalid region yet. + +#ifdef XP_WIN + // When DWM is disabled, each window does not have own back buffer. They would + // paint directly to a buffer that was to be displayed by the video card. + // WM_PAINT via SendInvalidRegion() requests necessary re-paint. + const bool needsInvalidate = !gfx::gfxVars::DwmCompositionEnabled(); +#else + const bool needsInvalidate = true; +#endif + if (needsInvalidate && WrBridge()) { + WrBridge()->SendInvalidateRenderedFrame(); + } +} + +void WebRenderLayerManager::ScheduleComposite() { + WrBridge()->SendScheduleComposite(); +} + +void WebRenderLayerManager::SetRoot(Layer* aLayer) { + // This should never get called + MOZ_ASSERT(false); +} + +already_AddRefed +WebRenderLayerManager::CreatePersistentBufferProvider( + const gfx::IntSize& aSize, gfx::SurfaceFormat aFormat) { + // Ensure devices initialization for canvas 2d. The devices are lazily + // initialized with WebRender to reduce memory usage. + gfxPlatform::GetPlatform()->EnsureDevicesInitialized(); + + RefPtr provider = + PersistentBufferProviderShared::Create(aSize, aFormat, + AsKnowsCompositor()); + if (provider) { + return provider.forget(); + } + + return LayerManager::CreatePersistentBufferProvider(aSize, aFormat); +} + +void WebRenderLayerManager::ClearAsyncAnimations() { + mStateManager.ClearAsyncAnimations(); +} + +void WebRenderLayerManager::WrReleasedImages( + const nsTArray& aPairs) { + mStateManager.WrReleasedImages(aPairs); +} + +void WebRenderLayerManager::GetFrameUniformity(FrameUniformityData* aOutData) { + WrBridge()->SendGetFrameUniformity(aOutData); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/wr/WebRenderLayerManager.h b/gfx/layers/wr/WebRenderLayerManager.h new file mode 100644 index 0000000000..ef7bcc1931 --- /dev/null +++ b/gfx/layers/wr/WebRenderLayerManager.h @@ -0,0 +1,247 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_WEBRENDERLAYERMANAGER_H +#define GFX_WEBRENDERLAYERMANAGER_H + +#include // for size_t +#include // for uint32_t, int32_t, INT32_MAX +#include // for string +#include "Units.h" // for LayoutDeviceIntSize +#include "mozilla/AlreadyAddRefed.h" // for already_AddRefed +#include "mozilla/Assertions.h" // for AssertionConditionType, MOZ_ASSERT, MOZ_ASSERT_HELPER2 +#include "mozilla/Attributes.h" // for MOZ_NON_OWNING_REF +#include "mozilla/RefPtr.h" // for RefPtr +#include "mozilla/StaticPrefs_apz.h" // for apz_test_logging_enabled +#include "mozilla/TimeStamp.h" // for TimeStamp +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/gfx/Types.h" // for SurfaceFormat +#include "mozilla/layers/APZTestData.h" // for APZTestData +#include "mozilla/layers/CompositorTypes.h" // for TextureFactoryIdentifier +#include "mozilla/layers/DisplayItemCache.h" // for DisplayItemCache +#include "mozilla/layers/FocusTarget.h" // for FocusTarget +#include "mozilla/layers/LayerManager.h" // for DidCompositeObserver (ptr only), LayerManager::END_DEFAULT, LayerManager::En... +#include "mozilla/layers/LayersTypes.h" // for TransactionId, LayersBackend, CompositionPayload (ptr only), LayersBackend::... +#include "mozilla/layers/RenderRootStateManager.h" // for RenderRootStateManager +#include "mozilla/layers/ScrollableLayerGuid.h" // for ScrollableLayerGuid, ScrollableLayerGuid::ViewID +#include "mozilla/layers/WebRenderCommandBuilder.h" // for WebRenderCommandBuilder +#include "mozilla/layers/WebRenderScrollData.h" // for WebRenderScrollData +#include "nsHashKeys.h" // for nsRefPtrHashKey +#include "nsRegion.h" // for nsIntRegion +#include "nsStringFwd.h" // for nsCString, nsAString +#include "nsTArray.h" // for nsTArray +#include "nsTHashtable.h" // for nsTHashtable<>::Iterator, nsTHashtable + +class gfxContext; +class nsDisplayList; +class nsDisplayListBuilder; +class nsIWidget; + +namespace mozilla { + +struct ActiveScrolledRoot; + +namespace layers { + +class CompositorBridgeChild; +class KnowsCompositor; +class Layer; +class PCompositorBridgeChild; +class WebRenderBridgeChild; +class WebRenderParentCommand; + +class WebRenderLayerManager final : public LayerManager { + typedef nsTArray> LayerRefArray; + typedef nsTHashtable> + WebRenderUserDataRefTable; + + public: + explicit WebRenderLayerManager(nsIWidget* aWidget); + bool Initialize(PCompositorBridgeChild* aCBChild, wr::PipelineId aLayersId, + TextureFactoryIdentifier* aTextureFactoryIdentifier, + nsCString& aError); + + void Destroy() override; + + void DoDestroy(bool aIsSync); + + protected: + virtual ~WebRenderLayerManager(); + + public: + KnowsCompositor* AsKnowsCompositor() override; + WebRenderLayerManager* AsWebRenderLayerManager() override { return this; } + CompositorBridgeChild* GetCompositorBridgeChild() override; + + // WebRender can handle images larger than the max texture size via tiling. + int32_t GetMaxTextureSize() const override { return INT32_MAX; } + + bool BeginTransactionWithTarget(gfxContext* aTarget, + const nsCString& aURL) override; + bool BeginTransaction(const nsCString& aURL) override; + bool EndEmptyTransaction(EndTransactionFlags aFlags = END_DEFAULT) override; + void EndTransactionWithoutLayer( + nsDisplayList* aDisplayList, nsDisplayListBuilder* aDisplayListBuilder, + WrFiltersHolder&& aFilters = WrFiltersHolder(), + WebRenderBackgroundData* aBackground = nullptr); + void EndTransaction(DrawPaintedLayerCallback aCallback, void* aCallbackData, + EndTransactionFlags aFlags = END_DEFAULT) override; + + LayersBackend GetBackendType() override { return LayersBackend::LAYERS_WR; } + void GetBackendName(nsAString& name) override; + const char* Name() const override { return "WebRender"; } + + void SetRoot(Layer* aLayer) override; + + already_AddRefed CreatePaintedLayer() override { + return nullptr; + } + already_AddRefed CreateContainerLayer() override { + return nullptr; + } + already_AddRefed CreateImageLayer() override { return nullptr; } + already_AddRefed CreateColorLayer() override { return nullptr; } + already_AddRefed CreateCanvasLayer() override { return nullptr; } + + bool NeedsWidgetInvalidation() override { return false; } + + void SetLayersObserverEpoch(LayersObserverEpoch aEpoch) override; + + void DidComposite(TransactionId aTransactionId, + const mozilla::TimeStamp& aCompositeStart, + const mozilla::TimeStamp& aCompositeEnd) override; + + void ClearCachedResources(Layer* aSubtree = nullptr) override; + void UpdateTextureFactoryIdentifier( + const TextureFactoryIdentifier& aNewIdentifier) override; + TextureFactoryIdentifier GetTextureFactoryIdentifier() override; + + void SetTransactionIdAllocator(TransactionIdAllocator* aAllocator) override; + TransactionId GetLastTransactionId() override; + + void AddDidCompositeObserver(DidCompositeObserver* aObserver) override; + void RemoveDidCompositeObserver(DidCompositeObserver* aObserver) override; + + void FlushRendering() override; + void WaitOnTransactionProcessed() override; + + void SendInvalidRegion(const nsIntRegion& aRegion) override; + + void ScheduleComposite() override; + + void SetNeedsComposite(bool aNeedsComposite) override { + mNeedsComposite = aNeedsComposite; + } + bool NeedsComposite() const override { return mNeedsComposite; } + void SetIsFirstPaint() override { mIsFirstPaint = true; } + bool GetIsFirstPaint() const override { return mIsFirstPaint; } + void SetFocusTarget(const FocusTarget& aFocusTarget) override; + + already_AddRefed CreatePersistentBufferProvider( + const gfx::IntSize& aSize, gfx::SurfaceFormat aFormat) override; + + bool AsyncPanZoomEnabled() const override; + + // adds an imagekey to a list of keys that will be discarded on the next + // transaction or destruction + void DiscardImages(); + void DiscardLocalImages(); + + void ClearAsyncAnimations(); + void WrReleasedImages(const nsTArray& aPairs); + + WebRenderBridgeChild* WrBridge() const { return mWrChild; } + + // See equivalent function in ClientLayerManager + void LogTestDataForCurrentPaint(ScrollableLayerGuid::ViewID aScrollId, + const std::string& aKey, + const std::string& aValue) { + MOZ_ASSERT(StaticPrefs::apz_test_logging_enabled(), "don't call me"); + mApzTestData.LogTestDataForPaint(mPaintSequenceNumber, aScrollId, aKey, + aValue); + } + void LogAdditionalTestData(const std::string& aKey, + const std::string& aValue) { + MOZ_ASSERT(StaticPrefs::apz_test_logging_enabled(), "don't call me"); + mApzTestData.RecordAdditionalData(aKey, aValue); + } + + // See equivalent function in ClientLayerManager + const APZTestData& GetAPZTestData() const { return mApzTestData; } + + WebRenderCommandBuilder& CommandBuilder() { return mWebRenderCommandBuilder; } + WebRenderUserDataRefTable* GetWebRenderUserDataTable() { + return mWebRenderCommandBuilder.GetWebRenderUserDataTable(); + } + WebRenderScrollData& GetScrollData() { return mScrollData; } + + void WrUpdated(); + nsIWidget* GetWidget() { return mWidget; } + + uint32_t StartFrameTimeRecording(int32_t aBufferSize) override; + void StopFrameTimeRecording(uint32_t aStartIndex, + nsTArray& aFrameIntervals) override; + + RenderRootStateManager* GetRenderRootStateManager() { return &mStateManager; } + + virtual void PayloadPresented(const TimeStamp& aTimeStamp) override; + + void TakeCompositionPayloads(nsTArray& aPayloads); + + void GetFrameUniformity(FrameUniformityData* aOutData) override; + + private: + /** + * Take a snapshot of the parent context, and copy + * it into mTarget. + */ + void MakeSnapshotIfRequired(LayoutDeviceIntSize aSize); + + private: + nsIWidget* MOZ_NON_OWNING_REF mWidget; + + RefPtr mWrChild; + + RefPtr mTransactionIdAllocator; + TransactionId mLatestTransactionId; + + nsTArray mDidCompositeObservers; + + // This holds the scroll data that we need to send to the compositor for + // APZ to do it's job + WebRenderScrollData mScrollData; + + bool mNeedsComposite; + bool mIsFirstPaint; + FocusTarget mFocusTarget; + + // When we're doing a transaction in order to draw to a non-default + // target, the layers transaction is only performed in order to send + // a PLayers:Update. We save the original non-default target to + // mTarget, and then perform the transaction. After the transaction ends, + // we send a message to our remote side to capture the actual pixels + // being drawn to the default target, and then copy those pixels + // back to mTarget. + RefPtr mTarget; + + // See equivalent field in ClientLayerManager + uint32_t mPaintSequenceNumber; + // See equivalent field in ClientLayerManager + APZTestData mApzTestData; + + TimeStamp mTransactionStart; + nsCString mURL; + WebRenderCommandBuilder mWebRenderCommandBuilder; + + size_t mLastDisplayListSize; + RenderRootStateManager mStateManager; + DisplayItemCache mDisplayItemCache; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_WEBRENDERLAYERMANAGER_H */ diff --git a/gfx/layers/wr/WebRenderMessageUtils.h b/gfx/layers/wr/WebRenderMessageUtils.h new file mode 100644 index 0000000000..ac30e5e4d3 --- /dev/null +++ b/gfx/layers/wr/WebRenderMessageUtils.h @@ -0,0 +1,150 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_WEBRENDERMESSAGEUTILS_H +#define GFX_WEBRENDERMESSAGEUTILS_H + +#include "chrome/common/ipc_message_utils.h" + +#include "ipc/EnumSerializer.h" +#include "ipc/IPCMessageUtils.h" +#include "mozilla/webrender/webrender_ffi.h" +#include "mozilla/webrender/WebRenderTypes.h" +#include "mozilla/dom/MediaIPCUtils.h" + +namespace IPC { + +template <> +struct ParamTraits { + typedef mozilla::wr::ByteBuffer paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mLength); + aMsg->WriteBytes(aParam.mData, aParam.mLength); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + size_t length; + return ReadParam(aMsg, aIter, &length) && aResult->Allocate(length) && + aMsg->ReadBytesInto(aIter, aResult->mData, length); + } +}; + +template <> +struct ParamTraits { + typedef mozilla::wr::ImageDescriptor paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.format); + WriteParam(aMsg, aParam.width); + WriteParam(aMsg, aParam.height); + WriteParam(aMsg, aParam.stride); + WriteParam(aMsg, aParam.opacity); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return ReadParam(aMsg, aIter, &aResult->format) && + ReadParam(aMsg, aIter, &aResult->width) && + ReadParam(aMsg, aIter, &aResult->height) && + ReadParam(aMsg, aIter, &aResult->stride) && + ReadParam(aMsg, aIter, &aResult->opacity); + } +}; + +template <> +struct ParamTraits + : public PlainOldDataSerializer {}; + +template <> +struct ParamTraits + : public PlainOldDataSerializer {}; + +template <> +struct ParamTraits + : public PlainOldDataSerializer {}; + +template <> +struct ParamTraits + : public PlainOldDataSerializer {}; + +template <> +struct ParamTraits + : public PlainOldDataSerializer {}; + +template <> +struct ParamTraits + : public PlainOldDataSerializer {}; + +template <> +struct ParamTraits + : public PlainOldDataSerializer { +}; + +template <> +struct ParamTraits + : public PlainOldDataSerializer {}; + +template <> +struct ParamTraits + : public PlainOldDataSerializer {}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializer {}; + +template <> +struct ParamTraits + : public PlainOldDataSerializer {}; + +template <> +struct ParamTraits + : public PlainOldDataSerializer {}; + +template <> +struct ParamTraits + : public PlainOldDataSerializer {}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializer {}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializer {}; + +template <> +struct ParamTraits + : public PlainOldDataSerializer {}; + +template <> +struct ParamTraits + : public ContiguousEnumSerializer {}; + +template <> +struct ParamTraits + : public PlainOldDataSerializer {}; + +template <> +struct ParamTraits + : public PlainOldDataSerializer {}; + +template <> +struct ParamTraits + : public PlainOldDataSerializer {}; + +} // namespace IPC + +#endif // GFX_WEBRENDERMESSAGEUTILS_H diff --git a/gfx/layers/wr/WebRenderScrollData.cpp b/gfx/layers/wr/WebRenderScrollData.cpp new file mode 100644 index 0000000000..424e6b03d5 --- /dev/null +++ b/gfx/layers/wr/WebRenderScrollData.cpp @@ -0,0 +1,371 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/layers/WebRenderScrollData.h" + +#include + +#include "Layers.h" +#include "mozilla/layers/LayersMessageUtils.h" +#include "mozilla/layers/WebRenderLayerManager.h" +#include "mozilla/ToString.h" +#include "mozilla/Unused.h" +#include "nsDisplayList.h" +#include "nsTArray.h" +#include "UnitTransforms.h" + +namespace mozilla { +namespace layers { + +WebRenderLayerScrollData::WebRenderLayerScrollData() + : mDescendantCount(-1), + mTransformIsPerspective(false), + mEventRegionsOverride(EventRegionsOverride::NoOverride), + mFixedPositionSides(mozilla::SideBits::eNone), + mFixedPosScrollContainerId(ScrollableLayerGuid::NULL_SCROLL_ID), + mStickyPosScrollContainerId(ScrollableLayerGuid::NULL_SCROLL_ID) {} + +WebRenderLayerScrollData::~WebRenderLayerScrollData() = default; + +void WebRenderLayerScrollData::InitializeRoot(int32_t aDescendantCount) { + mDescendantCount = aDescendantCount; +} + +void WebRenderLayerScrollData::Initialize( + WebRenderScrollData& aOwner, nsDisplayItem* aItem, int32_t aDescendantCount, + const ActiveScrolledRoot* aStopAtAsr, + const Maybe& aAncestorTransform) { + MOZ_ASSERT(aDescendantCount >= 0); // Ensure value is valid + MOZ_ASSERT(mDescendantCount == + -1); // Don't allow re-setting an already set value + mDescendantCount = aDescendantCount; + + MOZ_ASSERT(aItem); + aItem->UpdateScrollData(&aOwner, this); + + const ActiveScrolledRoot* asr = aItem->GetActiveScrolledRoot(); + if (ActiveScrolledRoot::IsAncestor(asr, aStopAtAsr)) { + // If the item's ASR is an ancestor of the stop-at ASR, then we don't need + // any more metrics information because we'll end up duplicating what the + // ancestor WebRenderLayerScrollData already has. + asr = nullptr; + } + + while (asr && asr != aStopAtAsr) { + MOZ_ASSERT(aOwner.GetManager()); + ScrollableLayerGuid::ViewID scrollId = asr->GetViewId(); + if (Maybe index = aOwner.HasMetadataFor(scrollId)) { + mScrollIds.AppendElement(index.ref()); + } else { + Maybe metadata = + asr->mScrollableFrame->ComputeScrollMetadata( + aOwner.GetManager(), aItem->ReferenceFrame(), Nothing(), nullptr); + aOwner.GetBuilder()->AddScrollFrameToNotify(asr->mScrollableFrame); + if (metadata) { + MOZ_ASSERT(metadata->GetMetrics().GetScrollId() == scrollId); + mScrollIds.AppendElement(aOwner.AddMetadata(metadata.ref())); + } else { + MOZ_ASSERT_UNREACHABLE("Expected scroll metadata to be available!"); + } + } + asr = asr->mParent; + } + + // See the comments on StackingContextHelper::mDeferredTransformItem for an + // overview of what deferred transforms are. + // aAncestorTransform, if present, is the transform from a deferred transform + // item that is an ancestor of |aItem|. We store this transform value + // separately from mTransform because in the case where we have multiple + // scroll metadata on this layer item, the mAncestorTransform is associated + // with the "topmost" scroll metadata, and the mTransform is associated with + // the "bottommost" scroll metadata. The code in + // WebRenderScrollDataWrapper::GetTransform() is responsible for combining + // these transforms and exposing them appropriately. Also, we don't save the + // ancestor transform for thumb layers, because those are a special case in + // APZ; we need to keep the ancestor transform for the scrollable content that + // the thumb scrolls, but not for the thumb itself, as it will result in + // incorrect visual positioning of the thumb. + if (aAncestorTransform && + mScrollbarData.mScrollbarLayerType != ScrollbarLayerType::Thumb) { + mAncestorTransform = *aAncestorTransform; + } +} + +int32_t WebRenderLayerScrollData::GetDescendantCount() const { + MOZ_ASSERT(mDescendantCount >= 0); // check that it was set + return mDescendantCount; +} + +size_t WebRenderLayerScrollData::GetScrollMetadataCount() const { + return mScrollIds.Length(); +} + +void WebRenderLayerScrollData::AppendScrollMetadata( + WebRenderScrollData& aOwner, const ScrollMetadata& aData) { + mScrollIds.AppendElement(aOwner.AddMetadata(aData)); +} + +const ScrollMetadata& WebRenderLayerScrollData::GetScrollMetadata( + const WebRenderScrollData& aOwner, size_t aIndex) const { + MOZ_ASSERT(aIndex < mScrollIds.Length()); + return aOwner.GetScrollMetadata(mScrollIds[aIndex]); +} + +CSSTransformMatrix WebRenderLayerScrollData::GetTransformTyped() const { + return ViewAs(GetTransform()); +} + +void WebRenderLayerScrollData::Dump(std::ostream& aOut, + const WebRenderScrollData& aOwner) const { + aOut << "WebRenderLayerScrollData(" << this + << "), descendantCount=" << mDescendantCount; + for (size_t i = 0; i < mScrollIds.Length(); i++) { + aOut << ", metadata" << i << "=" << aOwner.GetScrollMetadata(mScrollIds[i]); + } + if (!mAncestorTransform.IsIdentity()) { + aOut << ", ancestorTransform=" << mAncestorTransform; + } + if (!mTransform.IsIdentity()) { + aOut << ", transform=" << mTransform; + if (mTransformIsPerspective) { + aOut << ", transformIsPerspective"; + } + } + aOut << ", visible=" << mVisibleRegion; + if (mReferentId) { + aOut << ", refLayersId=" << *mReferentId; + } + if (mEventRegionsOverride) { + aOut << std::hex << ", eventRegionsOverride=0x" + << (int)mEventRegionsOverride << std::dec; + } + if (mScrollbarData.mScrollbarLayerType != ScrollbarLayerType::None) { + aOut << ", scrollbarType=" << (int)mScrollbarData.mScrollbarLayerType + << std::hex << ", scrollbarAnimationId=0x" + << mScrollbarAnimationId.valueOr(0) << std::dec; + } + if (mFixedPosScrollContainerId != ScrollableLayerGuid::NULL_SCROLL_ID) { + aOut << ", fixedContainer=" << mFixedPosScrollContainerId << std::hex + << ", fixedAnimation=0x" << mFixedPositionAnimationId.valueOr(0) + << ", sideBits=0x" << (int)mFixedPositionSides << std::dec; + } + if (mStickyPosScrollContainerId != ScrollableLayerGuid::NULL_SCROLL_ID) { + aOut << ", stickyContainer=" << mStickyPosScrollContainerId << std::hex + << ", stickyAnimation=" << mStickyPositionAnimationId.valueOr(0) + << std::dec << ", stickyInner=" << mStickyScrollRangeInner + << ", stickyOuter=" << mStickyScrollRangeOuter; + } +} + +WebRenderScrollData::WebRenderScrollData() + : mManager(nullptr), mIsFirstPaint(false), mPaintSequenceNumber(0) {} + +WebRenderScrollData::WebRenderScrollData(WebRenderLayerManager* aManager, + nsDisplayListBuilder* aBuilder) + : mManager(aManager), + mBuilder(aBuilder), + mIsFirstPaint(false), + mPaintSequenceNumber(0) {} + +WebRenderLayerManager* WebRenderScrollData::GetManager() const { + return mManager; +} + +nsDisplayListBuilder* WebRenderScrollData::GetBuilder() const { + return mBuilder; +} + +size_t WebRenderScrollData::AddMetadata(const ScrollMetadata& aMetadata) { + ScrollableLayerGuid::ViewID scrollId = aMetadata.GetMetrics().GetScrollId(); + auto p = mScrollIdMap.lookupForAdd(scrollId); + if (!p) { + // It's a scrollId we hadn't seen before + bool ok = mScrollIdMap.add(p, scrollId, mScrollMetadatas.Length()); + MOZ_RELEASE_ASSERT(ok); + mScrollMetadatas.AppendElement(aMetadata); + } // else we didn't insert, because it already existed + return p->value(); +} + +size_t WebRenderScrollData::AddLayerData( + const WebRenderLayerScrollData& aData) { + mLayerScrollData.AppendElement(aData); + return mLayerScrollData.Length() - 1; +} + +size_t WebRenderScrollData::GetLayerCount() const { + return mLayerScrollData.Length(); +} + +const WebRenderLayerScrollData* WebRenderScrollData::GetLayerData( + size_t aIndex) const { + if (aIndex >= mLayerScrollData.Length()) { + return nullptr; + } + return &(mLayerScrollData.ElementAt(aIndex)); +} + +const ScrollMetadata& WebRenderScrollData::GetScrollMetadata( + size_t aIndex) const { + MOZ_ASSERT(aIndex < mScrollMetadatas.Length()); + return mScrollMetadatas[aIndex]; +} + +Maybe WebRenderScrollData::HasMetadataFor( + const ScrollableLayerGuid::ViewID& aScrollId) const { + auto ptr = mScrollIdMap.lookup(aScrollId); + return (ptr ? Some(ptr->value()) : Nothing()); +} + +void WebRenderScrollData::SetIsFirstPaint() { mIsFirstPaint = true; } + +bool WebRenderScrollData::IsFirstPaint() const { return mIsFirstPaint; } + +void WebRenderScrollData::SetPaintSequenceNumber( + uint32_t aPaintSequenceNumber) { + mPaintSequenceNumber = aPaintSequenceNumber; +} + +uint32_t WebRenderScrollData::GetPaintSequenceNumber() const { + return mPaintSequenceNumber; +} + +void WebRenderScrollData::ApplyUpdates(ScrollUpdatesMap&& aUpdates, + uint32_t aPaintSequenceNumber) { + for (auto it = aUpdates.Iter(); !it.Done(); it.Next()) { + if (Maybe index = HasMetadataFor(it.Key())) { + mScrollMetadatas[*index].UpdatePendingScrollInfo(std::move(it.Data())); + } + } + mPaintSequenceNumber = aPaintSequenceNumber; +} + +void WebRenderScrollData::DumpSubtree(std::ostream& aOut, size_t aIndex, + const std::string& aIndent) const { + aOut << aIndent; + mLayerScrollData.ElementAt(aIndex).Dump(aOut, *this); + aOut << std::endl; + + int32_t descendants = mLayerScrollData.ElementAt(aIndex).GetDescendantCount(); + if (descendants == 0) { + return; + } + + // Build a stack of indices at which this aIndex's children live. We do + // this because we want to dump them first-to-last but they are stored + // last-to-first. + std::stack childIndices; + size_t childIndex = aIndex + 1; + while (descendants > 0) { + childIndices.push(childIndex); + // "1" for the child itelf, plus whatever descendants it has + int32_t subtreeSize = + 1 + mLayerScrollData.ElementAt(childIndex).GetDescendantCount(); + childIndex += subtreeSize; + descendants -= subtreeSize; + MOZ_ASSERT(descendants >= 0); + } + + std::string indent = aIndent + " "; + while (!childIndices.empty()) { + size_t child = childIndices.top(); + childIndices.pop(); + DumpSubtree(aOut, child, indent); + } +} + +std::ostream& operator<<(std::ostream& aOut, const WebRenderScrollData& aData) { + aOut << "--- WebRenderScrollData (firstPaint=" << aData.mIsFirstPaint + << ") ---" << std::endl; + + if (aData.mLayerScrollData.Length() > 0) { + aData.DumpSubtree(aOut, 0, std::string()); + } + return aOut; +} + +bool WebRenderScrollData::RepopulateMap() { + MOZ_ASSERT(mScrollIdMap.empty()); + for (size_t i = 0; i < mScrollMetadatas.Length(); i++) { + ScrollableLayerGuid::ViewID scrollId = + mScrollMetadatas[i].GetMetrics().GetScrollId(); + bool ok = mScrollIdMap.putNew(scrollId, i); + MOZ_RELEASE_ASSERT(ok); + } + return true; +} + +} // namespace layers +} // namespace mozilla + +namespace IPC { + +void ParamTraits::Write( + Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mDescendantCount); + WriteParam(aMsg, aParam.mScrollIds); + WriteParam(aMsg, aParam.mAncestorTransform); + WriteParam(aMsg, aParam.mTransform); + WriteParam(aMsg, aParam.mTransformIsPerspective); + WriteParam(aMsg, aParam.mVisibleRegion); + WriteParam(aMsg, aParam.mRemoteDocumentSize); + WriteParam(aMsg, aParam.mReferentId); + WriteParam(aMsg, aParam.mEventRegionsOverride); + WriteParam(aMsg, aParam.mScrollbarData); + WriteParam(aMsg, aParam.mScrollbarAnimationId); + WriteParam(aMsg, aParam.mFixedPositionAnimationId); + WriteParam(aMsg, aParam.mFixedPositionSides); + WriteParam(aMsg, aParam.mFixedPosScrollContainerId); + WriteParam(aMsg, aParam.mStickyPosScrollContainerId); + WriteParam(aMsg, aParam.mStickyScrollRangeOuter); + WriteParam(aMsg, aParam.mStickyScrollRangeInner); + WriteParam(aMsg, aParam.mStickyPositionAnimationId); + WriteParam(aMsg, aParam.mZoomAnimationId); + WriteParam(aMsg, aParam.mAsyncZoomContainerId); +} + +bool ParamTraits::Read( + const Message* aMsg, PickleIterator* aIter, paramType* aResult) { + return ReadParam(aMsg, aIter, &aResult->mDescendantCount) && + ReadParam(aMsg, aIter, &aResult->mScrollIds) && + ReadParam(aMsg, aIter, &aResult->mAncestorTransform) && + ReadParam(aMsg, aIter, &aResult->mTransform) && + ReadParam(aMsg, aIter, &aResult->mTransformIsPerspective) && + ReadParam(aMsg, aIter, &aResult->mVisibleRegion) && + ReadParam(aMsg, aIter, &aResult->mRemoteDocumentSize) && + ReadParam(aMsg, aIter, &aResult->mReferentId) && + ReadParam(aMsg, aIter, &aResult->mEventRegionsOverride) && + ReadParam(aMsg, aIter, &aResult->mScrollbarData) && + ReadParam(aMsg, aIter, &aResult->mScrollbarAnimationId) && + ReadParam(aMsg, aIter, &aResult->mFixedPositionAnimationId) && + ReadParam(aMsg, aIter, &aResult->mFixedPositionSides) && + ReadParam(aMsg, aIter, &aResult->mFixedPosScrollContainerId) && + ReadParam(aMsg, aIter, &aResult->mStickyPosScrollContainerId) && + ReadParam(aMsg, aIter, &aResult->mStickyScrollRangeOuter) && + ReadParam(aMsg, aIter, &aResult->mStickyScrollRangeInner) && + ReadParam(aMsg, aIter, &aResult->mStickyPositionAnimationId) && + ReadParam(aMsg, aIter, &aResult->mZoomAnimationId) && + ReadParam(aMsg, aIter, &aResult->mAsyncZoomContainerId); +} + +void ParamTraits::Write( + Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mScrollMetadatas); + WriteParam(aMsg, aParam.mLayerScrollData); + WriteParam(aMsg, aParam.mIsFirstPaint); + WriteParam(aMsg, aParam.mPaintSequenceNumber); +} + +bool ParamTraits::Read( + const Message* aMsg, PickleIterator* aIter, paramType* aResult) { + return ReadParam(aMsg, aIter, &aResult->mScrollMetadatas) && + ReadParam(aMsg, aIter, &aResult->mLayerScrollData) && + ReadParam(aMsg, aIter, &aResult->mIsFirstPaint) && + ReadParam(aMsg, aIter, &aResult->mPaintSequenceNumber) && + aResult->RepopulateMap(); +} + +} // namespace IPC diff --git a/gfx/layers/wr/WebRenderScrollData.h b/gfx/layers/wr/WebRenderScrollData.h new file mode 100644 index 0000000000..b0e4b50e26 --- /dev/null +++ b/gfx/layers/wr/WebRenderScrollData.h @@ -0,0 +1,322 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_WEBRENDERSCROLLDATA_H +#define GFX_WEBRENDERSCROLLDATA_H + +#include +#include + +#include "chrome/common/ipc_message_utils.h" +#include "FrameMetrics.h" +#include "ipc/IPCMessageUtils.h" +#include "LayersTypes.h" +#include "mozilla/Attributes.h" +#include "mozilla/GfxMessageUtils.h" +#include "mozilla/layers/LayerAttributes.h" +#include "mozilla/layers/FocusTarget.h" +#include "mozilla/layers/WebRenderMessageUtils.h" +#include "mozilla/webrender/WebRenderTypes.h" +#include "mozilla/HashTable.h" +#include "mozilla/Maybe.h" +#include "nsTArrayForwardDeclare.h" + +class nsDisplayListBuilder; +class nsDisplayItem; + +namespace mozilla { + +struct ActiveScrolledRoot; + +namespace layers { + +class Layer; +class WebRenderLayerManager; +class WebRenderScrollData; + +// Data needed by APZ, per layer. One instance of this class is created for +// each layer in the layer tree and sent over PWebRenderBridge to the APZ code. +// Each WebRenderLayerScrollData is conceptually associated with an "owning" +// WebRenderScrollData. +class WebRenderLayerScrollData final { + public: + WebRenderLayerScrollData(); // needed for IPC purposes + ~WebRenderLayerScrollData(); + + void InitializeRoot(int32_t aDescendantCount); + void Initialize(WebRenderScrollData& aOwner, nsDisplayItem* aItem, + int32_t aDescendantCount, + const ActiveScrolledRoot* aStopAtAsr, + const Maybe& aAncestorTransform); + + int32_t GetDescendantCount() const; + size_t GetScrollMetadataCount() const; + + void AppendScrollMetadata(WebRenderScrollData& aOwner, + const ScrollMetadata& aData); + // Return the ScrollMetadata object that used to be on the original Layer + // at the given index. Since we deduplicate the ScrollMetadata objects into + // the array in the owning WebRenderScrollData object, we need to be passed + // in a reference to that owner as well. + const ScrollMetadata& GetScrollMetadata(const WebRenderScrollData& aOwner, + size_t aIndex) const; + + gfx::Matrix4x4 GetAncestorTransform() const { return mAncestorTransform; } + void SetTransform(const gfx::Matrix4x4& aTransform) { + mTransform = aTransform; + } + gfx::Matrix4x4 GetTransform() const { return mTransform; } + CSSTransformMatrix GetTransformTyped() const; + void SetTransformIsPerspective(bool aTransformIsPerspective) { + mTransformIsPerspective = aTransformIsPerspective; + } + bool GetTransformIsPerspective() const { return mTransformIsPerspective; } + + EventRegions GetEventRegions() const { return EventRegions(); } + void SetEventRegionsOverride(const EventRegionsOverride& aOverride) { + mEventRegionsOverride = aOverride; + } + EventRegionsOverride GetEventRegionsOverride() const { + return mEventRegionsOverride; + } + + void SetVisibleRegion(const LayerIntRegion& aRegion) { + mVisibleRegion = aRegion; + } + const LayerIntRegion& GetVisibleRegion() const { return mVisibleRegion; } + void SetRemoteDocumentSize(const LayerIntSize& aRemoteDocumentSize) { + mRemoteDocumentSize = aRemoteDocumentSize; + } + const LayerIntSize& GetRemoteDocumentSize() const { + return mRemoteDocumentSize; + } + void SetReferentId(LayersId aReferentId) { mReferentId = Some(aReferentId); } + Maybe GetReferentId() const { return mReferentId; } + + void SetScrollbarData(const ScrollbarData& aData) { mScrollbarData = aData; } + const ScrollbarData& GetScrollbarData() const { return mScrollbarData; } + void SetScrollbarAnimationId(const uint64_t& aId) { + mScrollbarAnimationId = Some(aId); + } + Maybe GetScrollbarAnimationId() const { + return mScrollbarAnimationId; + } + + void SetFixedPositionAnimationId(const uint64_t& aId) { + mFixedPositionAnimationId = Some(aId); + } + Maybe GetFixedPositionAnimationId() const { + return mFixedPositionAnimationId; + } + + void SetFixedPositionSides(const SideBits& aSideBits) { + mFixedPositionSides = aSideBits; + } + SideBits GetFixedPositionSides() const { return mFixedPositionSides; } + + void SetFixedPositionScrollContainerId(ScrollableLayerGuid::ViewID aId) { + mFixedPosScrollContainerId = aId; + } + ScrollableLayerGuid::ViewID GetFixedPositionScrollContainerId() const { + return mFixedPosScrollContainerId; + } + + void SetStickyPositionScrollContainerId(ScrollableLayerGuid::ViewID aId) { + mStickyPosScrollContainerId = aId; + } + ScrollableLayerGuid::ViewID GetStickyPositionScrollContainerId() const { + return mStickyPosScrollContainerId; + } + + void SetStickyScrollRangeOuter(const LayerRectAbsolute& scrollRange) { + mStickyScrollRangeOuter = scrollRange; + } + const LayerRectAbsolute& GetStickyScrollRangeOuter() const { + return mStickyScrollRangeOuter; + } + + void SetStickyScrollRangeInner(const LayerRectAbsolute& scrollRange) { + mStickyScrollRangeInner = scrollRange; + } + const LayerRectAbsolute& GetStickyScrollRangeInner() const { + return mStickyScrollRangeInner; + } + + void SetStickyPositionAnimationId(const uint64_t& aId) { + mStickyPositionAnimationId = Some(aId); + } + Maybe GetStickyPositionAnimationId() const { + return mStickyPositionAnimationId; + } + + void SetZoomAnimationId(const uint64_t& aId) { mZoomAnimationId = Some(aId); } + Maybe GetZoomAnimationId() const { return mZoomAnimationId; } + + void SetAsyncZoomContainerId(const ScrollableLayerGuid::ViewID aId) { + mAsyncZoomContainerId = Some(aId); + } + Maybe GetAsyncZoomContainerId() const { + return mAsyncZoomContainerId; + } + bool IsAsyncZoomContainer() const { return mAsyncZoomContainerId.isSome(); } + + void Dump(std::ostream& aOut, const WebRenderScrollData& aOwner) const; + + friend struct IPC::ParamTraits; + + private: + // The number of descendants this layer has (not including the layer itself). + // This is needed to reconstruct the depth-first layer tree traversal + // efficiently. Leaf layers should always have 0 descendants. + int32_t mDescendantCount; + + // Handles to the ScrollMetadata objects that were on this layer. The values + // stored in this array are indices into the owning WebRenderScrollData's + // mScrollMetadatas array. This indirection is used to deduplicate the + // ScrollMetadata objects, since there is usually heavy duplication of them + // within a layer tree. + CopyableTArray mScrollIds; + + // Various data that we collect from the Layer in Initialize(), serialize + // over IPC, and use on the parent side in APZ. + + gfx::Matrix4x4 mAncestorTransform; + gfx::Matrix4x4 mTransform; + bool mTransformIsPerspective; + LayerIntRegion mVisibleRegion; + // The remote documents only need their size because their origin is always + // (0, 0). + LayerIntSize mRemoteDocumentSize; + Maybe mReferentId; + EventRegionsOverride mEventRegionsOverride; + ScrollbarData mScrollbarData; + Maybe mScrollbarAnimationId; + Maybe mFixedPositionAnimationId; + SideBits mFixedPositionSides; + ScrollableLayerGuid::ViewID mFixedPosScrollContainerId; + ScrollableLayerGuid::ViewID mStickyPosScrollContainerId; + LayerRectAbsolute mStickyScrollRangeOuter; + LayerRectAbsolute mStickyScrollRangeInner; + Maybe mStickyPositionAnimationId; + Maybe mZoomAnimationId; + Maybe mAsyncZoomContainerId; +}; + +// Data needed by APZ, for the whole layer tree. One instance of this class +// is created for each transaction sent over PWebRenderBridge. It is populated +// with information from the WebRender layer tree on the client side and the +// information is used by APZ on the parent side. +class WebRenderScrollData final { + public: + WebRenderScrollData(); + explicit WebRenderScrollData(WebRenderLayerManager* aManager, + nsDisplayListBuilder* aBuilder); + + WebRenderLayerManager* GetManager() const; + + nsDisplayListBuilder* GetBuilder() const; + + // Add the given ScrollMetadata if it doesn't already exist. Return an index + // that can be used to look up the metadata later. + size_t AddMetadata(const ScrollMetadata& aMetadata); + // Add the provided WebRenderLayerScrollData and return the index that can + // be used to look it up via GetLayerData. + size_t AddLayerData(const WebRenderLayerScrollData& aData); + + size_t GetLayerCount() const; + + // Return a pointer to the scroll data at the given index. Use with caution, + // as the pointer may be invalidated if this WebRenderScrollData is mutated. + const WebRenderLayerScrollData* GetLayerData(size_t aIndex) const; + + const ScrollMetadata& GetScrollMetadata(size_t aIndex) const; + Maybe HasMetadataFor( + const ScrollableLayerGuid::ViewID& aScrollId) const; + + void SetIsFirstPaint(); + bool IsFirstPaint() const; + void SetPaintSequenceNumber(uint32_t aPaintSequenceNumber); + uint32_t GetPaintSequenceNumber() const; + + void ApplyUpdates(ScrollUpdatesMap&& aUpdates, uint32_t aPaintSequenceNumber); + + friend struct IPC::ParamTraits; + + friend std::ostream& operator<<(std::ostream& aOut, + const WebRenderScrollData& aData); + + private: + // This is called by the ParamTraits implementation to rebuild mScrollIdMap + // based on mScrollMetadatas + bool RepopulateMap(); + + // This is a helper for the dumping code + void DumpSubtree(std::ostream& aOut, size_t aIndex, + const std::string& aIndent) const; + + private: + // Pointer back to the layer manager; if this is non-null, it will always be + // valid, because the WebRenderLayerManager that created |this| will + // outlive |this|. + WebRenderLayerManager* MOZ_NON_OWNING_REF mManager; + + // Pointer to the display list builder; if this is non-null, it will always be + // valid, because the nsDisplayListBuilder that created the layer manager will + // outlive |this|. + nsDisplayListBuilder* MOZ_NON_OWNING_REF mBuilder; + + // Internal data structure used to maintain uniqueness of mScrollMetadatas. + // This is not serialized/deserialized over IPC, but it is rebuilt on the + // parent side when mScrollMetadatas is deserialized. So it should always be + // valid on both the child and parent. + // The key into this map is the scrollId of a ScrollMetadata, and the value is + // an index into the mScrollMetadatas array. + HashMap mScrollIdMap; + + // A list of all the unique ScrollMetadata objects from the layer tree. Each + // ScrollMetadata in this list must have a unique scroll id. + nsTArray mScrollMetadatas; + + // A list of per-layer scroll data objects, generated via a depth-first, + // pre-order, last-to-first traversal of the layer tree (i.e. a recursive + // traversal where a node N first pushes itself, followed by its children in + // last-to-first order). Each layer's scroll data object knows how many + // descendants that layer had, which allows reconstructing the traversal on + // the other side. + nsTArray mLayerScrollData; + + bool mIsFirstPaint; + uint32_t mPaintSequenceNumber; +}; + +} // namespace layers +} // namespace mozilla + +namespace IPC { + +template <> +struct ParamTraits { + typedef mozilla::layers::WebRenderLayerScrollData paramType; + + static void Write(Message* aMsg, const paramType& aParam); + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult); +}; + +template <> +struct ParamTraits { + typedef mozilla::layers::WebRenderScrollData paramType; + + static void Write(Message* aMsg, const paramType& aParam); + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult); +}; + +} // namespace IPC + +#endif /* GFX_WEBRENDERSCROLLDATA_H */ diff --git a/gfx/layers/wr/WebRenderScrollDataWrapper.h b/gfx/layers/wr/WebRenderScrollDataWrapper.h new file mode 100644 index 0000000000..1f681a7cff --- /dev/null +++ b/gfx/layers/wr/WebRenderScrollDataWrapper.h @@ -0,0 +1,424 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_WEBRENDERSCROLLDATAWRAPPER_H +#define GFX_WEBRENDERSCROLLDATAWRAPPER_H + +#include "FrameMetrics.h" +#include "mozilla/layers/CompositorBridgeParent.h" +#include "mozilla/layers/WebRenderBridgeParent.h" +#include "mozilla/layers/WebRenderScrollData.h" + +namespace mozilla { +namespace layers { + +/* + * This class is a wrapper to walk through a WebRenderScrollData object, with + * an exposed API that is template-compatible to LayerMetricsWrapper. This + * allows APZ to walk through both layer trees and WebRender scroll metadata + * structures without a lot of code duplication. (Note that not all functions + * from LayerMetricsWrapper are implemented here, only the ones we've needed in + * APZ code so far.) + * + * A WebRenderScrollData object is basically a flattened layer tree, with a + * number of WebRenderLayerScrollData objects that have a 1:1 correspondence + * to layers in a layer tree. Therefore the mLayer pointer in this class can + * be considered equivalent to the mLayer pointer in the LayerMetricsWrapper. + * There are some extra fields (mData, mLayerIndex, mContainingSubtreeLastIndex) + * to move around between these "layers" given the flattened representation. + * The mMetadataIndex field in this class corresponds to the mIndex field in + * LayerMetricsWrapper, as both classes also need to manage walking through + * "virtual" container layers implied by the list of ScrollMetadata objects. + * + * One important note here is that this class holds a pointer to the "owning" + * WebRenderScrollData. The caller must ensure that this class does not outlive + * the owning WebRenderScrollData, or this may result in use-after-free errors. + * This class being declared a MOZ_STACK_CLASS should help with that. + * + * Refer to LayerMetricsWrapper.h for actual documentation on the exposed API. + */ +class MOZ_STACK_CLASS WebRenderScrollDataWrapper final { + public: + // Basic constructor for external callers. Starts the walker at the root of + // the tree. + explicit WebRenderScrollDataWrapper( + const APZUpdater& aUpdater, const WebRenderScrollData* aData = nullptr) + : mUpdater(&aUpdater), + mData(aData), + mLayerIndex(0), + mContainingSubtreeLastIndex(0), + mLayer(nullptr), + mMetadataIndex(0) { + if (!mData) { + return; + } + mLayer = mData->GetLayerData(mLayerIndex); + if (!mLayer) { + return; + } + + // sanity check on the data + MOZ_ASSERT(mData->GetLayerCount() == + (size_t)(1 + mLayer->GetDescendantCount())); + mContainingSubtreeLastIndex = mData->GetLayerCount(); + + // See documentation in LayerMetricsWrapper.h about this. mMetadataIndex + // in this class is equivalent to mIndex in that class. + mMetadataIndex = mLayer->GetScrollMetadataCount(); + if (mMetadataIndex > 0) { + mMetadataIndex--; + } + } + + private: + // Internal constructor for walking from one WebRenderLayerScrollData to + // another. In this case we need to recompute the mMetadataIndex to be the + // "topmost" scroll metadata on the new layer. + WebRenderScrollDataWrapper(const APZUpdater* aUpdater, + const WebRenderScrollData* aData, + size_t aLayerIndex, + size_t aContainingSubtreeLastIndex) + : mUpdater(aUpdater), + mData(aData), + mLayerIndex(aLayerIndex), + mContainingSubtreeLastIndex(aContainingSubtreeLastIndex), + mLayer(nullptr), + mMetadataIndex(0) { + MOZ_ASSERT(mData); + mLayer = mData->GetLayerData(mLayerIndex); + MOZ_ASSERT(mLayer); + + // See documentation in LayerMetricsWrapper.h about this. mMetadataIndex + // in this class is equivalent to mIndex in that class. + mMetadataIndex = mLayer->GetScrollMetadataCount(); + if (mMetadataIndex > 0) { + mMetadataIndex--; + } + } + + // Internal constructor for walking from one metadata to another metadata on + // the same WebRenderLayerScrollData. + WebRenderScrollDataWrapper(const APZUpdater* aUpdater, + const WebRenderScrollData* aData, + size_t aLayerIndex, + size_t aContainingSubtreeLastIndex, + const WebRenderLayerScrollData* aLayer, + uint32_t aMetadataIndex) + : mUpdater(aUpdater), + mData(aData), + mLayerIndex(aLayerIndex), + mContainingSubtreeLastIndex(aContainingSubtreeLastIndex), + mLayer(aLayer), + mMetadataIndex(aMetadataIndex) { + MOZ_ASSERT(mData); + MOZ_ASSERT(mLayer); + MOZ_ASSERT(mLayer == mData->GetLayerData(mLayerIndex)); + MOZ_ASSERT(mMetadataIndex == 0 || + mMetadataIndex < mLayer->GetScrollMetadataCount()); + } + + public: + bool IsValid() const { return mLayer != nullptr; } + + explicit operator bool() const { return IsValid(); } + + WebRenderScrollDataWrapper GetLastChild() const { + MOZ_ASSERT(IsValid()); + + if (!AtBottomLayer()) { + // If we're still walking around in the virtual container layers created + // by the ScrollMetadata array, we just need to update the metadata index + // and that's it. + return WebRenderScrollDataWrapper(mUpdater, mData, mLayerIndex, + mContainingSubtreeLastIndex, mLayer, + mMetadataIndex - 1); + } + + // Otherwise, we need to walk to a different WebRenderLayerScrollData in + // mData. + + // Since mData contains the layer in depth-first, last-to-first order, + // the index after mLayerIndex must be mLayerIndex's last child, if it + // has any children (indicated by GetDescendantCount() > 0). Furthermore + // we compute the first index outside the subtree rooted at this node + // (in |subtreeLastIndex|) and pass that in to the child wrapper to use as + // its mContainingSubtreeLastIndex. + if (mLayer->GetDescendantCount() > 0) { + size_t prevSiblingIndex = mLayerIndex + 1 + mLayer->GetDescendantCount(); + size_t subtreeLastIndex = + std::min(mContainingSubtreeLastIndex, prevSiblingIndex); + return WebRenderScrollDataWrapper(mUpdater, mData, mLayerIndex + 1, + subtreeLastIndex); + } + + // We've run out of descendants. But! If the original layer was a RefLayer, + // then it connects to another layer tree and we need to traverse that too. + // So return a WebRenderScrollDataWrapper for the root of the child layer + // tree. + if (mLayer->GetReferentId()) { + return WebRenderScrollDataWrapper( + *mUpdater, mUpdater->GetScrollData(*mLayer->GetReferentId())); + } + + return WebRenderScrollDataWrapper(*mUpdater); + } + + WebRenderScrollDataWrapper GetPrevSibling() const { + MOZ_ASSERT(IsValid()); + + if (!AtTopLayer()) { + // The virtual container layers don't have siblings + return WebRenderScrollDataWrapper(*mUpdater); + } + + // Skip past the descendants to get to the previous sibling. However, we + // might be at the last sibling already. + size_t prevSiblingIndex = mLayerIndex + 1 + mLayer->GetDescendantCount(); + if (prevSiblingIndex < mContainingSubtreeLastIndex) { + return WebRenderScrollDataWrapper(mUpdater, mData, prevSiblingIndex, + mContainingSubtreeLastIndex); + } + return WebRenderScrollDataWrapper(*mUpdater); + } + + const ScrollMetadata& Metadata() const { + MOZ_ASSERT(IsValid()); + + if (mMetadataIndex >= mLayer->GetScrollMetadataCount()) { + return *ScrollMetadata::sNullMetadata; + } + return mLayer->GetScrollMetadata(*mData, mMetadataIndex); + } + + const FrameMetrics& Metrics() const { return Metadata().GetMetrics(); } + + AsyncPanZoomController* GetApzc() const { return nullptr; } + + void SetApzc(AsyncPanZoomController* aApzc) const {} + + const char* Name() const { return "WebRenderScrollDataWrapper"; } + + gfx::Matrix4x4 GetTransform() const { + MOZ_ASSERT(IsValid()); + + // See WebRenderLayerScrollData::Initialize for more context. The ancestor + // transform is associated with the "topmost" layer, and the transform is + // associated with the "bottommost" layer. If there is only one + // scrollmetadata on the layer, then it is both "topmost" and "bottommost" + // and we combine the two transforms. + + gfx::Matrix4x4 transform; + if (AtTopLayer()) { + transform = mLayer->GetAncestorTransform(); + } + if (AtBottomLayer()) { + transform = transform * mLayer->GetTransform(); + } + return transform; + } + + CSSTransformMatrix GetTransformTyped() const { + return ViewAs(GetTransform()); + } + + bool TransformIsPerspective() const { + MOZ_ASSERT(IsValid()); + + if (AtBottomLayer()) { + return mLayer->GetTransformIsPerspective(); + } + return false; + } + + EventRegions GetEventRegions() const { + MOZ_ASSERT(IsValid()); + + if (AtBottomLayer()) { + return mLayer->GetEventRegions(); + } + return EventRegions(); + } + + LayerIntRegion GetVisibleRegion() const { + MOZ_ASSERT(IsValid()); + + if (AtBottomLayer()) { + return mLayer->GetVisibleRegion(); + } + + return ViewAs( + TransformBy(mLayer->GetTransformTyped(), mLayer->GetVisibleRegion()), + PixelCastJustification::MovingDownToChildren); + } + + LayerIntSize GetRemoteDocumentSize() const { + MOZ_ASSERT(IsValid()); + + if (mLayer->GetReferentId().isNothing()) { + return LayerIntSize(); + } + + if (AtBottomLayer()) { + return mLayer->GetRemoteDocumentSize(); + } + + return ViewAs(mLayer->GetRemoteDocumentSize(), + PixelCastJustification::MovingDownToChildren); + } + + Maybe GetReferentId() const { + MOZ_ASSERT(IsValid()); + + if (AtBottomLayer()) { + return mLayer->GetReferentId(); + } + return Nothing(); + } + + Maybe GetClipRect() const { + // TODO + return Nothing(); + } + + EventRegionsOverride GetEventRegionsOverride() const { + MOZ_ASSERT(IsValid()); + // Only ref layers can have an event regions override. + if (GetReferentId()) { + return mLayer->GetEventRegionsOverride(); + } + return EventRegionsOverride::NoOverride; + } + + const ScrollbarData& GetScrollbarData() const { + MOZ_ASSERT(IsValid()); + return mLayer->GetScrollbarData(); + } + + Maybe GetScrollbarAnimationId() const { + MOZ_ASSERT(IsValid()); + return mLayer->GetScrollbarAnimationId(); + } + + Maybe GetFixedPositionAnimationId() const { + MOZ_ASSERT(IsValid()); + + if (AtBottomLayer()) { + return mLayer->GetFixedPositionAnimationId(); + } + return Nothing(); + } + + ScrollableLayerGuid::ViewID GetFixedPositionScrollContainerId() const { + MOZ_ASSERT(IsValid()); + + if (AtBottomLayer()) { + return mLayer->GetFixedPositionScrollContainerId(); + } + return ScrollableLayerGuid::NULL_SCROLL_ID; + } + + SideBits GetFixedPositionSides() const { + MOZ_ASSERT(IsValid()); + + if (AtBottomLayer()) { + return mLayer->GetFixedPositionSides(); + } + return SideBits::eNone; + } + + ScrollableLayerGuid::ViewID GetStickyScrollContainerId() const { + MOZ_ASSERT(IsValid()); + + if (AtBottomLayer()) { + return mLayer->GetStickyPositionScrollContainerId(); + } + return ScrollableLayerGuid::NULL_SCROLL_ID; + } + + const LayerRectAbsolute& GetStickyScrollRangeOuter() const { + MOZ_ASSERT(IsValid()); + + if (AtBottomLayer()) { + return mLayer->GetStickyScrollRangeOuter(); + } + + static const LayerRectAbsolute empty; + return empty; + } + + const LayerRectAbsolute& GetStickyScrollRangeInner() const { + MOZ_ASSERT(IsValid()); + + if (AtBottomLayer()) { + return mLayer->GetStickyScrollRangeInner(); + } + + static const LayerRectAbsolute empty; + return empty; + } + + Maybe GetStickyPositionAnimationId() const { + MOZ_ASSERT(IsValid()); + + if (AtBottomLayer()) { + return mLayer->GetStickyPositionAnimationId(); + } + return Nothing(); + } + + Maybe GetZoomAnimationId() const { + MOZ_ASSERT(IsValid()); + return mLayer->GetZoomAnimationId(); + } + + bool IsBackfaceHidden() const { + // This is only used by APZCTM hit testing, and WR does its own + // hit testing, so no need to implement this. + return false; + } + + bool IsAsyncZoomContainer() const { + MOZ_ASSERT(IsValid()); + return mLayer->IsAsyncZoomContainer(); + } + + const void* GetLayer() const { + MOZ_ASSERT(IsValid()); + return mLayer; + } + + private: + bool AtBottomLayer() const { return mMetadataIndex == 0; } + + bool AtTopLayer() const { + return mLayer->GetScrollMetadataCount() == 0 || + mMetadataIndex == mLayer->GetScrollMetadataCount() - 1; + } + + private: + const APZUpdater* mUpdater; + const WebRenderScrollData* mData; + // The index (in mData->mLayerScrollData) of the WebRenderLayerScrollData this + // wrapper is pointing to. + size_t mLayerIndex; + // The upper bound on the set of valid indices inside the subtree rooted at + // the parent of this "layer". That is, any layer index |i| in the range + // mLayerIndex <= i < mContainingSubtreeLastIndex is guaranteed to point to + // a layer that is a descendant of "parent", where "parent" is the parent + // layer of the layer at mLayerIndex. This is needed in order to implement + // GetPrevSibling() correctly. + size_t mContainingSubtreeLastIndex; + // The WebRenderLayerScrollData this wrapper is pointing to. + const WebRenderLayerScrollData* mLayer; + // The index of the scroll metadata within mLayer that this wrapper is + // pointing to. + uint32_t mMetadataIndex; +}; + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_WEBRENDERSCROLLDATAWRAPPER_H */ diff --git a/gfx/layers/wr/WebRenderTextureHost.cpp b/gfx/layers/wr/WebRenderTextureHost.cpp new file mode 100644 index 0000000000..6e9a6d88ab --- /dev/null +++ b/gfx/layers/wr/WebRenderTextureHost.cpp @@ -0,0 +1,253 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WebRenderTextureHost.h" + +#include "mozilla/layers/ImageDataSerializer.h" +#include "mozilla/layers/LayersSurfaces.h" +#include "mozilla/layers/TextureSourceProvider.h" +#include "mozilla/webrender/RenderThread.h" +#include "mozilla/webrender/WebRenderAPI.h" + +#ifdef MOZ_WIDGET_ANDROID +# include "mozilla/layers/TextureHostOGL.h" +#endif + +namespace mozilla::layers { + +class ScheduleHandleRenderTextureOps : public wr::NotificationHandler { + public: + explicit ScheduleHandleRenderTextureOps() {} + + virtual void Notify(wr::Checkpoint aCheckpoint) override { + if (aCheckpoint == wr::Checkpoint::FrameTexturesUpdated) { + MOZ_ASSERT(wr::RenderThread::IsInRenderThread()); + wr::RenderThread::Get()->HandleRenderTextureOps(); + } else { + MOZ_ASSERT(aCheckpoint == wr::Checkpoint::TransactionDropped); + } + } + + protected: +}; + +WebRenderTextureHost::WebRenderTextureHost( + const SurfaceDescriptor& aDesc, TextureFlags aFlags, TextureHost* aTexture, + wr::ExternalImageId& aExternalImageId) + : TextureHost(aFlags), mWrappedTextureHost(aTexture) { + // The wrapped textureHost will be used in WebRender, and the WebRender could + // run at another thread. It's hard to control the life-time when gecko + // receives PTextureParent destroy message. It's possible that textureHost is + // still used by WebRender. So, we only accept the textureHost without + // DEALLOCATE_CLIENT flag here. If the buffer deallocation is controlled by + // parent, we could do something to make sure the wrapped textureHost is not + // used by WebRender and then release it. + MOZ_ASSERT(!(aFlags & TextureFlags::DEALLOCATE_CLIENT)); + MOZ_ASSERT(mWrappedTextureHost); + + MOZ_COUNT_CTOR(WebRenderTextureHost); + + mExternalImageId = Some(aExternalImageId); +} + +WebRenderTextureHost::~WebRenderTextureHost() { + MOZ_COUNT_DTOR(WebRenderTextureHost); +} + +wr::ExternalImageId WebRenderTextureHost::GetExternalImageKey() { + if (IsValid()) { + mWrappedTextureHost->EnsureRenderTexture(mExternalImageId); + } + MOZ_ASSERT(mWrappedTextureHost->mExternalImageId.isSome()); + return mWrappedTextureHost->mExternalImageId.ref(); +} + +bool WebRenderTextureHost::IsValid() { return mWrappedTextureHost->IsValid(); } + +bool WebRenderTextureHost::Lock() { + MOZ_ASSERT(mWrappedTextureHost->AsBufferTextureHost()); + + if (mWrappedTextureHost->AsBufferTextureHost()) { + return mWrappedTextureHost->Lock(); + } + return false; +} + +void WebRenderTextureHost::Unlock() { + MOZ_ASSERT(mWrappedTextureHost->AsBufferTextureHost()); + + if (mWrappedTextureHost->AsBufferTextureHost()) { + mWrappedTextureHost->Unlock(); + } +} + +void WebRenderTextureHost::PrepareTextureSource( + CompositableTextureSourceRef& aTexture) { + MOZ_ASSERT(mWrappedTextureHost->AsBufferTextureHost()); + + if (mWrappedTextureHost->AsBufferTextureHost()) { + mWrappedTextureHost->PrepareTextureSource(aTexture); + } +} + +bool WebRenderTextureHost::BindTextureSource( + CompositableTextureSourceRef& aTexture) { + MOZ_ASSERT(mWrappedTextureHost->AsBufferTextureHost()); + + if (mWrappedTextureHost->AsBufferTextureHost()) { + return mWrappedTextureHost->BindTextureSource(aTexture); + } + return false; +} + +void WebRenderTextureHost::UnbindTextureSource() { + if (mWrappedTextureHost->AsBufferTextureHost()) { + mWrappedTextureHost->UnbindTextureSource(); + } + // Handle read unlock + TextureHost::UnbindTextureSource(); +} + +void WebRenderTextureHost::SetTextureSourceProvider( + TextureSourceProvider* aProvider) { + // During using WebRender, only BasicCompositor could exist + MOZ_ASSERT(!aProvider || aProvider->AsBasicCompositor()); + MOZ_ASSERT(mWrappedTextureHost->AsBufferTextureHost()); + + if (mWrappedTextureHost->AsBufferTextureHost()) { + mWrappedTextureHost->SetTextureSourceProvider(aProvider); + } +} + +already_AddRefed WebRenderTextureHost::GetAsSurface() { + return mWrappedTextureHost->GetAsSurface(); +} + +gfx::YUVColorSpace WebRenderTextureHost::GetYUVColorSpace() const { + return mWrappedTextureHost->GetYUVColorSpace(); +} + +gfx::ColorRange WebRenderTextureHost::GetColorRange() const { + return mWrappedTextureHost->GetColorRange(); +} + +gfx::IntSize WebRenderTextureHost::GetSize() const { + return mWrappedTextureHost->GetSize(); +} + +gfx::SurfaceFormat WebRenderTextureHost::GetFormat() const { + return mWrappedTextureHost->GetFormat(); +} + +void WebRenderTextureHost::NotifyNotUsed() { +#ifdef MOZ_WIDGET_ANDROID + if (mWrappedTextureHost->AsSurfaceTextureHost()) { + wr::RenderThread::Get()->NotifyNotUsed(wr::AsUint64(GetExternalImageKey())); + } +#endif + TextureHost::NotifyNotUsed(); +} + +void WebRenderTextureHost::MaybeNotifyForUse(wr::TransactionBuilder& aTxn) { +#if defined(MOZ_WIDGET_ANDROID) + if (mWrappedTextureHost->AsSurfaceTextureHost()) { + wr::RenderThread::Get()->NotifyForUse(wr::AsUint64(GetExternalImageKey())); + aTxn.Notify(wr::Checkpoint::FrameTexturesUpdated, + MakeUnique()); + } +#endif +} + +void WebRenderTextureHost::PrepareForUse() { + if (mWrappedTextureHost->AsSurfaceTextureHost() || + mWrappedTextureHost->AsBufferTextureHost()) { + // Call PrepareForUse on render thread. + // See RenderAndroidSurfaceTextureHostOGL::PrepareForUse. + wr::RenderThread::Get()->PrepareForUse(wr::AsUint64(GetExternalImageKey())); + } +} + +gfx::SurfaceFormat WebRenderTextureHost::GetReadFormat() const { + return mWrappedTextureHost->GetReadFormat(); +} + +int32_t WebRenderTextureHost::GetRGBStride() { + gfx::SurfaceFormat format = GetFormat(); + if (GetFormat() == gfx::SurfaceFormat::YUV) { + // XXX this stride is used until yuv image rendering by webrender is used. + // Software converted RGB buffers strides are aliened to 16 + return gfx::GetAlignedStride<16>( + GetSize().width, BytesPerPixel(gfx::SurfaceFormat::B8G8R8A8)); + } + return ImageDataSerializer::ComputeRGBStride(format, GetSize().width); +} + +bool WebRenderTextureHost::HasIntermediateBuffer() const { + return mWrappedTextureHost->HasIntermediateBuffer(); +} + +bool WebRenderTextureHost::NeedsDeferredDeletion() const { + return mWrappedTextureHost->NeedsDeferredDeletion(); +} + +uint32_t WebRenderTextureHost::NumSubTextures() { + return mWrappedTextureHost->NumSubTextures(); +} + +void WebRenderTextureHost::PushResourceUpdates( + wr::TransactionBuilder& aResources, ResourceUpdateOp aOp, + const Range& aImageKeys, const wr::ExternalImageId& aExtID) { + MOZ_ASSERT(GetExternalImageKey() == aExtID); + + mWrappedTextureHost->PushResourceUpdates(aResources, aOp, aImageKeys, aExtID); +} + +void WebRenderTextureHost::PushDisplayItems( + wr::DisplayListBuilder& aBuilder, const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, wr::ImageRendering aFilter, + const Range& aImageKeys, PushDisplayItemFlagSet aFlags) { + MOZ_ASSERT(aImageKeys.length() > 0); + + mWrappedTextureHost->PushDisplayItems(aBuilder, aBounds, aClip, aFilter, + aImageKeys, aFlags); +} + +bool WebRenderTextureHost::SupportsExternalCompositing() { + return mWrappedTextureHost->SupportsExternalCompositing(); +} + +bool WebRenderTextureHost::NeedsYFlip() const { + bool yFlip = TextureHost::NeedsYFlip(); + if (mWrappedTextureHost->AsSurfaceTextureHost()) { + MOZ_ASSERT(yFlip); + // With WebRender, SurfaceTextureHost always requests y-flip. + // But y-flip should not be handled, since + // SurfaceTexture.getTransformMatrix() is not handled yet. + // See Bug 1507076. + yFlip = false; + } + return yFlip; +} + +void WebRenderTextureHost::SetAcquireFence( + mozilla::ipc::FileDescriptor&& aFenceFd) { + mWrappedTextureHost->SetAcquireFence(std::move(aFenceFd)); +} + +void WebRenderTextureHost::SetReleaseFence( + mozilla::ipc::FileDescriptor&& aFenceFd) { + mWrappedTextureHost->SetReleaseFence(std::move(aFenceFd)); +} + +mozilla::ipc::FileDescriptor WebRenderTextureHost::GetAndResetReleaseFence() { + return mWrappedTextureHost->GetAndResetReleaseFence(); +} + +AndroidHardwareBuffer* WebRenderTextureHost::GetAndroidHardwareBuffer() const { + return mWrappedTextureHost->GetAndroidHardwareBuffer(); +} + +} // namespace mozilla::layers diff --git a/gfx/layers/wr/WebRenderTextureHost.h b/gfx/layers/wr/WebRenderTextureHost.h new file mode 100644 index 0000000000..e4e930e9bc --- /dev/null +++ b/gfx/layers/wr/WebRenderTextureHost.h @@ -0,0 +1,111 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_GFX_WEBRENDERTEXTUREHOST_H +#define MOZILLA_GFX_WEBRENDERTEXTUREHOST_H + +#include "mozilla/layers/TextureHost.h" +#include "mozilla/webrender/WebRenderTypes.h" + +namespace mozilla { +namespace layers { + +class SurfaceDescriptor; + +// This textureHost is specialized for WebRender usage. With WebRender, there is +// no Compositor during composition. Instead, we use RendererOGL for +// composition. So, there are some UNREACHABLE asserts for the original +// Compositor related code path in this class. Furthermore, the RendererOGL runs +// at RenderThead instead of Compositor thread. This class is also creating the +// corresponding RenderXXXTextureHost used by RendererOGL at RenderThread. +class WebRenderTextureHost : public TextureHost { + public: + WebRenderTextureHost(const SurfaceDescriptor& aDesc, TextureFlags aFlags, + TextureHost* aTexture, + wr::ExternalImageId& aExternalImageId); + virtual ~WebRenderTextureHost(); + + void DeallocateDeviceData() override {} + + bool Lock() override; + + void Unlock() override; + + void PrepareTextureSource(CompositableTextureSourceRef& aTexture) override; + bool BindTextureSource(CompositableTextureSourceRef& aTexture) override; + void UnbindTextureSource() override; + void SetTextureSourceProvider(TextureSourceProvider* aProvider) override; + + gfx::SurfaceFormat GetFormat() const override; + + virtual void NotifyNotUsed() override; + + virtual bool IsValid() override; + + // Return the format used for reading the texture. Some hardware specific + // textureHosts use their special data representation internally, but we could + // treat these textureHost as the read-format when we read them. + // Please check TextureHost::GetReadFormat(). + gfx::SurfaceFormat GetReadFormat() const override; + + already_AddRefed GetAsSurface() override; + + gfx::YUVColorSpace GetYUVColorSpace() const override; + gfx::ColorRange GetColorRange() const override; + + gfx::IntSize GetSize() const override; + +#ifdef MOZ_LAYERS_HAVE_LOG + const char* Name() override { return "WebRenderTextureHost"; } +#endif + + WebRenderTextureHost* AsWebRenderTextureHost() override { return this; } + + virtual void PrepareForUse() override; + + wr::ExternalImageId GetExternalImageKey(); + + int32_t GetRGBStride(); + + bool HasIntermediateBuffer() const override; + + bool NeedsDeferredDeletion() const override; + + uint32_t NumSubTextures() override; + + void PushResourceUpdates(wr::TransactionBuilder& aResources, + ResourceUpdateOp aOp, + const Range& aImageKeys, + const wr::ExternalImageId& aExtID) override; + + void PushDisplayItems(wr::DisplayListBuilder& aBuilder, + const wr::LayoutRect& aBounds, + const wr::LayoutRect& aClip, wr::ImageRendering aFilter, + const Range& aImageKeys, + PushDisplayItemFlagSet aFlags) override; + + bool SupportsExternalCompositing() override; + + bool NeedsYFlip() const override; + + void SetAcquireFence(mozilla::ipc::FileDescriptor&& aFenceFd) override; + + void SetReleaseFence(mozilla::ipc::FileDescriptor&& aFenceFd) override; + + mozilla::ipc::FileDescriptor GetAndResetReleaseFence() override; + + AndroidHardwareBuffer* GetAndroidHardwareBuffer() const override; + + void MaybeNotifyForUse(wr::TransactionBuilder& aTxn); + + protected: + RefPtr mWrappedTextureHost; +}; + +} // namespace layers +} // namespace mozilla + +#endif // MOZILLA_GFX_WEBRENDERTEXTUREHOST_H diff --git a/gfx/layers/wr/WebRenderUserData.cpp b/gfx/layers/wr/WebRenderUserData.cpp new file mode 100644 index 0000000000..b9f885d6b6 --- /dev/null +++ b/gfx/layers/wr/WebRenderUserData.cpp @@ -0,0 +1,432 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WebRenderUserData.h" + +#include "BasicLayers.h" +#include "mozilla/layers/AnimationHelper.h" +#include "mozilla/layers/CompositorBridgeChild.h" +#include "mozilla/layers/ImageClient.h" +#include "mozilla/layers/WebRenderBridgeChild.h" +#include "mozilla/layers/RenderRootStateManager.h" +#include "mozilla/layers/WebRenderMessages.h" +#include "mozilla/layers/IpcResourceUpdateQueue.h" +#include "mozilla/layers/SharedSurfacesChild.h" +#include "mozilla/webgpu/WebGPUChild.h" +#include "nsDisplayListInvalidation.h" +#include "nsIFrame.h" +#include "WebRenderCanvasRenderer.h" + +namespace mozilla { +namespace layers { + +void WebRenderBackgroundData::AddWebRenderCommands( + wr::DisplayListBuilder& aBuilder) { + aBuilder.PushRect(mBounds, mBounds, true, mColor); +} + +/* static */ +bool WebRenderUserData::SupportsAsyncUpdate(nsIFrame* aFrame) { + if (!aFrame) { + return false; + } + RefPtr data = GetWebRenderUserData( + aFrame, static_cast(DisplayItemType::TYPE_VIDEO)); + if (data) { + return data->IsAsync(); + } + + return false; +} + +/* static */ +bool WebRenderUserData::ProcessInvalidateForImage( + nsIFrame* aFrame, DisplayItemType aType, ContainerProducerID aProducerId) { + MOZ_ASSERT(aFrame); + + if (!aFrame->HasProperty(WebRenderUserDataProperty::Key())) { + return false; + } + + auto type = static_cast(aType); + RefPtr fallback = + GetWebRenderUserData(aFrame, type); + if (fallback) { + fallback->SetInvalid(true); + aFrame->SchedulePaint(); + return true; + } + + RefPtr image = + GetWebRenderUserData(aFrame, type); + if (image && image->UsingSharedSurface(aProducerId)) { + return true; + } + + aFrame->SchedulePaint(); + return false; +} + +WebRenderUserData::WebRenderUserData(RenderRootStateManager* aManager, + uint32_t aDisplayItemKey, nsIFrame* aFrame) + : mManager(aManager), + mFrame(aFrame), + mDisplayItemKey(aDisplayItemKey), + mTable(aManager->GetWebRenderUserDataTable()), + mUsed(false) {} + +WebRenderUserData::WebRenderUserData(RenderRootStateManager* aManager, + nsDisplayItem* aItem) + : mManager(aManager), + mFrame(aItem->Frame()), + mDisplayItemKey(aItem->GetPerFrameKey()), + mTable(aManager->GetWebRenderUserDataTable()), + mUsed(false) {} + +WebRenderUserData::~WebRenderUserData() = default; + +void WebRenderUserData::RemoveFromTable() { mTable->RemoveEntry(this); } + +WebRenderBridgeChild* WebRenderUserData::WrBridge() const { + return mManager->WrBridge(); +} + +WebRenderImageData::WebRenderImageData(RenderRootStateManager* aManager, + nsDisplayItem* aItem) + : WebRenderUserData(aManager, aItem), mOwnsKey(false) {} + +WebRenderImageData::WebRenderImageData(RenderRootStateManager* aManager, + uint32_t aDisplayItemKey, + nsIFrame* aFrame) + : WebRenderUserData(aManager, aDisplayItemKey, aFrame), mOwnsKey(false) {} + +WebRenderImageData::~WebRenderImageData() { + ClearImageKey(); + + if (mPipelineId) { + mManager->RemovePipelineIdForCompositable(mPipelineId.ref()); + } +} + +bool WebRenderImageData::UsingSharedSurface( + ContainerProducerID aProducerId) const { + if (!mContainer || !mKey || mOwnsKey) { + return false; + } + + // If this is just an update with the same image key, then we know that the + // share request initiated an asynchronous update so that we don't need to + // rebuild the scene. + wr::ImageKey key; + nsresult rv = SharedSurfacesChild::Share( + mContainer, mManager, mManager->AsyncResourceUpdates(), key, aProducerId); + return NS_SUCCEEDED(rv) && mKey.ref() == key; +} + +void WebRenderImageData::ClearImageKey() { + if (mKey) { + // If we don't own the key, then the owner is responsible for discarding the + // key when appropriate. + if (mOwnsKey) { + mManager->AddImageKeyForDiscard(mKey.value()); + if (mTextureOfImage) { + WrBridge()->ReleaseTextureOfImage(mKey.value()); + mTextureOfImage = nullptr; + } + } + mKey.reset(); + } + mOwnsKey = false; + MOZ_ASSERT(!mTextureOfImage); +} + +Maybe WebRenderImageData::UpdateImageKey( + ImageContainer* aContainer, wr::IpcResourceUpdateQueue& aResources, + bool aFallback) { + MOZ_ASSERT(aContainer); + + if (mContainer != aContainer) { + mContainer = aContainer; + } + + wr::WrImageKey key; + if (!aFallback) { + nsresult rv = SharedSurfacesChild::Share(aContainer, mManager, aResources, + key, kContainerProducerID_Invalid); + if (NS_SUCCEEDED(rv)) { + // Ensure that any previously owned keys are released before replacing. We + // don't own this key, the surface itself owns it, so that it can be + // shared across multiple elements. + ClearImageKey(); + mKey = Some(key); + return mKey; + } + + if (rv != NS_ERROR_NOT_IMPLEMENTED) { + // We should be using the shared surface but somehow sharing it failed. + ClearImageKey(); + return Nothing(); + } + } + + CreateImageClientIfNeeded(); + if (!mImageClient) { + return Nothing(); + } + + MOZ_ASSERT(mImageClient->AsImageClientSingle()); + + ImageClientSingle* imageClient = mImageClient->AsImageClientSingle(); + uint32_t oldCounter = imageClient->GetLastUpdateGenerationCounter(); + + bool ret = imageClient->UpdateImage(aContainer, /* unused */ 0); + RefPtr currentTexture = imageClient->GetForwardedTexture(); + if (!ret || !currentTexture) { + // Delete old key + ClearImageKey(); + return Nothing(); + } + + // Reuse old key if generation is not updated. + if (!aFallback && + oldCounter == imageClient->GetLastUpdateGenerationCounter() && mKey) { + return mKey; + } + + // If we already had a texture and the format hasn't changed, better to reuse + // the image keys than create new ones. + bool useUpdate = mKey.isSome() && !!mTextureOfImage && !!currentTexture && + mTextureOfImage->GetSize() == currentTexture->GetSize() && + mTextureOfImage->GetFormat() == currentTexture->GetFormat(); + + wr::MaybeExternalImageId extId = currentTexture->GetExternalImageKey(); + MOZ_RELEASE_ASSERT(extId.isSome()); + + if (useUpdate) { + MOZ_ASSERT(mKey.isSome()); + MOZ_ASSERT(mTextureOfImage); + aResources.PushExternalImageForTexture( + extId.ref(), mKey.ref(), currentTexture, /* aIsUpdate */ true); + } else { + ClearImageKey(); + key = WrBridge()->GetNextImageKey(); + aResources.PushExternalImageForTexture(extId.ref(), key, currentTexture, + /* aIsUpdate */ false); + mKey = Some(key); + } + + mTextureOfImage = currentTexture; + mOwnsKey = true; + + return mKey; +} + +already_AddRefed WebRenderImageData::GetImageClient() { + RefPtr imageClient = mImageClient; + return imageClient.forget(); +} + +void WebRenderImageData::CreateAsyncImageWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, ImageContainer* aContainer, + const StackingContextHelper& aSc, const LayoutDeviceRect& aBounds, + const LayoutDeviceRect& aSCBounds, VideoInfo::Rotation aRotation, + const wr::ImageRendering& aFilter, const wr::MixBlendMode& aMixBlendMode, + bool aIsBackfaceVisible) { + MOZ_ASSERT(aContainer->IsAsync()); + + if (mPipelineId.isSome() && mContainer != aContainer) { + // In this case, we need to remove the existed pipeline and create new one + // because the ImageContainer is changed. + WrBridge()->RemovePipelineIdForCompositable(mPipelineId.ref()); + mPipelineId.reset(); + } + + if (!mPipelineId) { + // Alloc async image pipeline id. + mPipelineId = + Some(WrBridge()->GetCompositorBridgeChild()->GetNextPipelineId()); + WrBridge()->AddPipelineIdForAsyncCompositable( + mPipelineId.ref(), aContainer->GetAsyncContainerHandle()); + mContainer = aContainer; + } + MOZ_ASSERT(!mImageClient); + + // Push IFrame for async image pipeline. + // + // We don't push a stacking context for this async image pipeline here. + // Instead, we do it inside the iframe that hosts the image. As a result, + // a bunch of the calculations normally done as part of that stacking + // context need to be done manually and pushed over to the parent side, + // where it will be done when we build the display list for the iframe. + // That happens in AsyncImagePipelineManager. + wr::LayoutRect r = wr::ToLayoutRect(aBounds); + aBuilder.PushIFrame(r, aIsBackfaceVisible, mPipelineId.ref(), + /*ignoreMissingPipelines*/ false); + + WrBridge()->AddWebRenderParentCommand(OpUpdateAsyncImagePipeline( + mPipelineId.value(), aSCBounds, aRotation, aFilter, aMixBlendMode)); +} + +void WebRenderImageData::CreateImageClientIfNeeded() { + if (!mImageClient) { + mImageClient = ImageClient::CreateImageClient( + CompositableType::IMAGE, WrBridge(), TextureFlags::DEFAULT); + if (!mImageClient) { + return; + } + + mImageClient->Connect(); + } +} + +WebRenderFallbackData::WebRenderFallbackData(RenderRootStateManager* aManager, + nsDisplayItem* aItem) + : WebRenderUserData(aManager, aItem), mInvalid(false) {} + +WebRenderFallbackData::~WebRenderFallbackData() { ClearImageKey(); } + +void WebRenderFallbackData::SetBlobImageKey(const wr::BlobImageKey& aKey) { + ClearImageKey(); + mBlobKey = Some(aKey); +} + +Maybe WebRenderFallbackData::GetImageKey() { + if (mBlobKey) { + return Some(wr::AsImageKey(mBlobKey.value())); + } + + if (mImageData) { + return mImageData->GetImageKey(); + } + + return Nothing(); +} + +void WebRenderFallbackData::ClearImageKey() { + if (mImageData) { + mImageData->ClearImageKey(); + mImageData = nullptr; + } + + if (mBlobKey) { + mManager->AddBlobImageKeyForDiscard(mBlobKey.value()); + mBlobKey.reset(); + } +} + +WebRenderImageData* WebRenderFallbackData::PaintIntoImage() { + if (mBlobKey) { + mManager->AddBlobImageKeyForDiscard(mBlobKey.value()); + mBlobKey.reset(); + } + + if (mImageData) { + return mImageData.get(); + } + + mImageData = MakeAndAddRef(mManager.get(), + mDisplayItemKey, mFrame); + + return mImageData.get(); +} + +WebRenderAPZAnimationData::WebRenderAPZAnimationData( + RenderRootStateManager* aManager, nsDisplayItem* aItem) + : WebRenderUserData(aManager, aItem), + mAnimationId(AnimationHelper::GetNextCompositorAnimationsId()) {} + +WebRenderAnimationData::WebRenderAnimationData(RenderRootStateManager* aManager, + nsDisplayItem* aItem) + : WebRenderUserData(aManager, aItem) {} + +WebRenderAnimationData::~WebRenderAnimationData() { + // It may be the case that nsDisplayItem that created this WebRenderUserData + // gets destroyed without getting a chance to discard the compositor animation + // id, so we should do it as part of cleanup here. + uint64_t animationId = mAnimationInfo.GetCompositorAnimationsId(); + // animationId might be 0 if mAnimationInfo never held any active animations. + if (animationId) { + mManager->AddCompositorAnimationsIdForDiscard(animationId); + } +} + +WebRenderCanvasData::WebRenderCanvasData(RenderRootStateManager* aManager, + nsDisplayItem* aItem) + : WebRenderUserData(aManager, aItem) {} + +WebRenderCanvasData::~WebRenderCanvasData() { + if (mCanvasRenderer) { + mCanvasRenderer->ClearCachedResources(); + } +} + +void WebRenderCanvasData::ClearCanvasRenderer() { mCanvasRenderer = nullptr; } + +WebRenderCanvasRendererAsync* WebRenderCanvasData::GetCanvasRenderer() { + return mCanvasRenderer.get(); +} + +WebRenderCanvasRendererAsync* WebRenderCanvasData::CreateCanvasRenderer() { + mCanvasRenderer = new WebRenderCanvasRendererAsync(mManager); + return mCanvasRenderer.get(); +} + +void WebRenderCanvasData::SetImageContainer(ImageContainer* aImageContainer) { + mContainer = aImageContainer; +} + +ImageContainer* WebRenderCanvasData::GetImageContainer() { + if (!mContainer) { + mContainer = LayerManager::CreateImageContainer(); + } + return mContainer; +} + +void WebRenderCanvasData::ClearImageContainer() { mContainer = nullptr; } + +WebRenderLocalCanvasData::WebRenderLocalCanvasData( + RenderRootStateManager* aManager, nsDisplayItem* aItem) + : WebRenderUserData(aManager, aItem) {} + +WebRenderLocalCanvasData::~WebRenderLocalCanvasData() = default; + +void WebRenderLocalCanvasData::RequestFrameReadback() { + if (mGpuBridge) { + mGpuBridge->SwapChainPresent(mExternalImageId, mGpuTextureId); + } +} + +void WebRenderLocalCanvasData::RefreshExternalImage() { + if (!mDirty) { + return; + } + + const ImageIntRect dirtyRect(0, 0, mDescriptor.width, mDescriptor.height); + // Update the WR external image, forcing the composition of a new frame. + mManager->AsyncResourceUpdates().UpdatePrivateExternalImage( + mExternalImageId, mImageKey, mDescriptor, dirtyRect); + mDirty = false; +} + +WebRenderRemoteData::WebRenderRemoteData(RenderRootStateManager* aManager, + nsDisplayItem* aItem) + : WebRenderUserData(aManager, aItem) {} + +WebRenderRemoteData::~WebRenderRemoteData() { + if (mRemoteBrowser) { + mRemoteBrowser->UpdateEffects(mozilla::dom::EffectsInfo::FullyHidden()); + } +} + +void DestroyWebRenderUserDataTable(WebRenderUserDataTable* aTable) { + for (auto iter = aTable->Iter(); !iter.Done(); iter.Next()) { + iter.UserData()->RemoveFromTable(); + } + delete aTable; +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/wr/WebRenderUserData.h b/gfx/layers/wr/WebRenderUserData.h new file mode 100644 index 0000000000..66a74b2a09 --- /dev/null +++ b/gfx/layers/wr/WebRenderUserData.h @@ -0,0 +1,352 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_WEBRENDERUSERDATA_H +#define GFX_WEBRENDERUSERDATA_H + +#include +#include "mozilla/webrender/WebRenderAPI.h" +#include "mozilla/layers/AnimationInfo.h" +#include "mozilla/dom/RemoteBrowser.h" +#include "mozilla/UniquePtr.h" +#include "nsIFrame.h" +#include "nsRefPtrHashtable.h" +#include "ImageTypes.h" + +class nsDisplayItemGeometry; + +namespace mozilla { +namespace webgpu { +class WebGPUChild; +} + +namespace wr { +class IpcResourceUpdateQueue; +} + +namespace gfx { +class SourceSurface; +} + +namespace layers { +class BasicLayerManager; +class CanvasLayer; +class ImageClient; +class ImageContainer; +class WebRenderBridgeChild; +class WebRenderCanvasData; +class WebRenderCanvasRenderer; +class WebRenderCanvasRendererAsync; +class WebRenderImageData; +class WebRenderFallbackData; +class WebRenderLocalCanvasData; +class RenderRootStateManager; +class WebRenderGroupData; + +class WebRenderBackgroundData { + public: + WebRenderBackgroundData(wr::LayoutRect aBounds, wr::ColorF aColor) + : mBounds(aBounds), mColor(aColor) {} + void AddWebRenderCommands(wr::DisplayListBuilder& aBuilder); + + protected: + wr::LayoutRect mBounds; + wr::ColorF mColor; +}; + +/// Parent class for arbitrary WebRender-specific data that can be associated +/// to an nsFrame. +class WebRenderUserData { + public: + typedef nsTHashtable> + WebRenderUserDataRefTable; + + static bool SupportsAsyncUpdate(nsIFrame* aFrame); + + static bool ProcessInvalidateForImage(nsIFrame* aFrame, DisplayItemType aType, + ContainerProducerID aProducerId); + + NS_INLINE_DECL_REFCOUNTING(WebRenderUserData) + + WebRenderUserData(RenderRootStateManager* aManager, nsDisplayItem* aItem); + WebRenderUserData(RenderRootStateManager* aManager, uint32_t mDisplayItemKey, + nsIFrame* aFrame); + + virtual WebRenderImageData* AsImageData() { return nullptr; } + virtual WebRenderFallbackData* AsFallbackData() { return nullptr; } + virtual WebRenderCanvasData* AsCanvasData() { return nullptr; } + virtual WebRenderLocalCanvasData* AsLocalCanvasData() { return nullptr; } + virtual WebRenderGroupData* AsGroupData() { return nullptr; } + + enum class UserDataType { + eImage, + eFallback, + eAPZAnimation, + eAnimation, + eCanvas, + eLocalCanvas, + eRemote, + eGroup, + eMask, + }; + + virtual UserDataType GetType() = 0; + bool IsUsed() { return mUsed; } + void SetUsed(bool aUsed) { mUsed = aUsed; } + nsIFrame* GetFrame() { return mFrame; } + uint32_t GetDisplayItemKey() { return mDisplayItemKey; } + void RemoveFromTable(); + virtual nsDisplayItemGeometry* GetGeometry() { return nullptr; } + + protected: + virtual ~WebRenderUserData(); + + WebRenderBridgeChild* WrBridge() const; + + RefPtr mManager; + nsIFrame* mFrame; + uint32_t mDisplayItemKey; + WebRenderUserDataRefTable* mTable; + bool mUsed; +}; + +struct WebRenderUserDataKey { + WebRenderUserDataKey(uint32_t aFrameKey, + WebRenderUserData::UserDataType aType) + : mFrameKey(aFrameKey), mType(aType) {} + + bool operator==(const WebRenderUserDataKey& other) const { + return mFrameKey == other.mFrameKey && mType == other.mType; + } + PLDHashNumber Hash() const { + return HashGeneric( + mFrameKey, + static_cast::type>(mType)); + } + + uint32_t mFrameKey; + WebRenderUserData::UserDataType mType; +}; + +typedef nsRefPtrHashtable< + nsGenericHashKey, WebRenderUserData> + WebRenderUserDataTable; + +/// Holds some data used to share TextureClient/ImageClient with the parent +/// process. +class WebRenderImageData : public WebRenderUserData { + public: + WebRenderImageData(RenderRootStateManager* aManager, nsDisplayItem* aItem); + WebRenderImageData(RenderRootStateManager* aManager, uint32_t aDisplayItemKey, + nsIFrame* aFrame); + virtual ~WebRenderImageData(); + + WebRenderImageData* AsImageData() override { return this; } + UserDataType GetType() override { return UserDataType::eImage; } + static UserDataType Type() { return UserDataType::eImage; } + Maybe GetImageKey() { return mKey; } + void SetImageKey(const wr::ImageKey& aKey); + already_AddRefed GetImageClient(); + + Maybe UpdateImageKey(ImageContainer* aContainer, + wr::IpcResourceUpdateQueue& aResources, + bool aFallback = false); + + void CreateAsyncImageWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, ImageContainer* aContainer, + const StackingContextHelper& aSc, const LayoutDeviceRect& aBounds, + const LayoutDeviceRect& aSCBounds, VideoInfo::Rotation aRotation, + const wr::ImageRendering& aFilter, const wr::MixBlendMode& aMixBlendMode, + bool aIsBackfaceVisible); + + void CreateImageClientIfNeeded(); + + bool IsAsync() { return mPipelineId.isSome(); } + + bool UsingSharedSurface(ContainerProducerID aProducerId) const; + + void ClearImageKey(); + + protected: + Maybe mKey; + RefPtr mTextureOfImage; + RefPtr mImageClient; + Maybe mPipelineId; + RefPtr mContainer; + // The key can be owned by a shared surface that is used by several elements. + // when this is the case the shared surface is responsible for managing the + // destruction of the key. + // TODO: we surely can come up with a simpler/safer way to model this. + bool mOwnsKey; +}; + +/// Used for fallback rendering. +/// +/// In most cases this uses blob images but it can also render on the content +/// side directly into a texture. +class WebRenderFallbackData : public WebRenderUserData { + public: + WebRenderFallbackData(RenderRootStateManager* aManager, nsDisplayItem* aItem); + virtual ~WebRenderFallbackData(); + + WebRenderFallbackData* AsFallbackData() override { return this; } + UserDataType GetType() override { return UserDataType::eFallback; } + static UserDataType Type() { return UserDataType::eFallback; } + + void SetInvalid(bool aInvalid) { mInvalid = aInvalid; } + bool IsInvalid() { return mInvalid; } + void SetFonts(const std::vector>& aFonts) { + mFonts = aFonts; + } + Maybe GetBlobImageKey() { return mBlobKey; } + void SetBlobImageKey(const wr::BlobImageKey& aKey); + Maybe GetImageKey(); + + /// Create a WebRenderImageData to manage the image we are about to render + /// into. + WebRenderImageData* PaintIntoImage(); + + std::vector> mExternalSurfaces; + RefPtr mBasicLayerManager; + UniquePtr mGeometry; + nsRect mBounds; + nsRect mBuildingRect; + gfx::Size mScale; + + protected: + void ClearImageKey(); + + std::vector> mFonts; + Maybe mBlobKey; + // When rendering into a blob image, mImageData is null. It is non-null only + // when we render directly into a texture on the content side. + RefPtr mImageData; + bool mInvalid; +}; + +class WebRenderAPZAnimationData : public WebRenderUserData { + public: + WebRenderAPZAnimationData(RenderRootStateManager* aManager, + nsDisplayItem* aItem); + virtual ~WebRenderAPZAnimationData() = default; + + UserDataType GetType() override { return UserDataType::eAPZAnimation; } + static UserDataType Type() { return UserDataType::eAPZAnimation; } + uint64_t GetAnimationId() { return mAnimationId; } + + private: + uint64_t mAnimationId; +}; + +class WebRenderAnimationData : public WebRenderUserData { + public: + WebRenderAnimationData(RenderRootStateManager* aManager, + nsDisplayItem* aItem); + virtual ~WebRenderAnimationData(); + + UserDataType GetType() override { return UserDataType::eAnimation; } + static UserDataType Type() { return UserDataType::eAnimation; } + AnimationInfo& GetAnimationInfo() { return mAnimationInfo; } + + protected: + AnimationInfo mAnimationInfo; +}; + +class WebRenderCanvasData : public WebRenderUserData { + public: + WebRenderCanvasData(RenderRootStateManager* aManager, nsDisplayItem* aItem); + virtual ~WebRenderCanvasData(); + + WebRenderCanvasData* AsCanvasData() override { return this; } + UserDataType GetType() override { return UserDataType::eCanvas; } + static UserDataType Type() { return UserDataType::eCanvas; } + + void ClearCanvasRenderer(); + WebRenderCanvasRendererAsync* GetCanvasRenderer(); + WebRenderCanvasRendererAsync* CreateCanvasRenderer(); + + void SetImageContainer(ImageContainer* aImageContainer); + ImageContainer* GetImageContainer(); + void ClearImageContainer(); + + protected: + RefPtr mCanvasRenderer; + RefPtr mContainer; +}; + +// WebRender data assocatiated with canvases that don't need to +// synchronize across content-GPU process barrier. +class WebRenderLocalCanvasData : public WebRenderUserData { + public: + WebRenderLocalCanvasData(RenderRootStateManager* aManager, + nsDisplayItem* aItem); + virtual ~WebRenderLocalCanvasData(); + + WebRenderLocalCanvasData* AsLocalCanvasData() override { return this; } + UserDataType GetType() override { return UserDataType::eLocalCanvas; } + static UserDataType Type() { return UserDataType::eLocalCanvas; } + + void RequestFrameReadback(); + void RefreshExternalImage(); + + // TODO: introduce a CanvasRenderer derivative to store here? + + WeakPtr mGpuBridge; + uint64_t mGpuTextureId = 0; + wr::ExternalImageId mExternalImageId = {0}; + wr::ImageKey mImageKey = {}; + wr::ImageDescriptor mDescriptor; + gfx::SurfaceFormat mFormat = gfx::SurfaceFormat::UNKNOWN; + bool mDirty = false; +}; + +class WebRenderRemoteData : public WebRenderUserData { + public: + WebRenderRemoteData(RenderRootStateManager* aManager, nsDisplayItem* aItem); + virtual ~WebRenderRemoteData(); + + UserDataType GetType() override { return UserDataType::eRemote; } + static UserDataType Type() { return UserDataType::eRemote; } + + void SetRemoteBrowser(dom::RemoteBrowser* aBrowser) { + mRemoteBrowser = aBrowser; + } + + protected: + RefPtr mRemoteBrowser; +}; + +extern void DestroyWebRenderUserDataTable(WebRenderUserDataTable* aTable); + +struct WebRenderUserDataProperty { + NS_DECLARE_FRAME_PROPERTY_WITH_DTOR(Key, WebRenderUserDataTable, + DestroyWebRenderUserDataTable) +}; + +template +already_AddRefed GetWebRenderUserData(const nsIFrame* aFrame, + uint32_t aPerFrameKey) { + MOZ_ASSERT(aFrame); + WebRenderUserDataTable* userDataTable = + aFrame->GetProperty(WebRenderUserDataProperty::Key()); + if (!userDataTable) { + return nullptr; + } + + WebRenderUserData* data = + userDataTable->GetWeak(WebRenderUserDataKey(aPerFrameKey, T::Type())); + if (data) { + RefPtr result = static_cast(data); + return result.forget(); + } + + return nullptr; +} + +} // namespace layers +} // namespace mozilla + +#endif /* GFX_WEBRENDERUSERDATA_H */ -- cgit v1.2.3