| # Copyright 2017 The Chromium Authors | |
| # Use of this source code is governed by a BSD-style license that can be | |
| # found in the LICENSE file. | |
| import logging | |
| import os | |
| import sys | |
| import time | |
| from typing import Any | |
| import unittest | |
| from devil.android.sdk import version_codes | |
| from gpu_tests import common_browser_args as cba | |
| from gpu_tests import common_typing as ct | |
| from gpu_tests import gpu_integration_test | |
| from gpu_tests.util import host_information | |
| import gpu_path_util | |
| _ASAN_NAVIGATE_MULTIPLIER = 4 | |
| _FUCHSIA_TEST_COMPLETION_MULTIPLIER = 2 | |
| _GPU_PAGE_TIMEOUT = 30 | |
| _GPU_VISIBILITY_POLL_DURATION_SEC = 5 | |
| _GPU_VISIBILITY_POLL_DELAY_SEC = 0.1 | |
| data_path = os.path.join( | |
| gpu_path_util.CHROMIUM_SRC_DIR, 'content', 'test', 'data' | |
| ) | |
| test_harness_script = r""" | |
| var domAutomationController = {}; | |
| domAutomationController._finished = false; | |
| domAutomationController._succeeded = false; | |
| domAutomationController.send = function(msg) { | |
| domAutomationController._finished = true; | |
| if (msg.toLowerCase() == "success") { | |
| domAutomationController._succeeded = true; | |
| } else { | |
| domAutomationController._succeeded = false; | |
| } | |
| } | |
| window.domAutomationController = domAutomationController; | |
| function GetDriverBugWorkarounds() { | |
| return getGPUInfo('workarounds'); | |
| }; | |
| """ | |
| def _GetBrowserBridgeProperty(tab: ct.Tab, path: str) -> dict: | |
| """The GPU WebUI uses JS modules and may not have initialized the global | |
| browserBridge object by the time we can start injecting JavaScript. This | |
| ensures we don't have that problem.""" | |
| tab.WaitForJavaScriptCondition( | |
| 'window.gpuPagePopulated', timeout=_GPU_PAGE_TIMEOUT | |
| ) | |
| return tab.EvaluateJavaScript('browserBridge.' + path) | |
| class GpuProcessIntegrationTest(gpu_integration_test.GpuIntegrationTest): | |
| @classmethod | |
| def Name(cls) -> str: | |
| """The name by which this test is invoked on the command line.""" | |
| return 'gpu_process' | |
| @classmethod | |
| def SetUpProcess(cls) -> None: | |
| super(GpuProcessIntegrationTest, cls).SetUpProcess() | |
| cls.CustomizeBrowserArgs([]) | |
| cls.StartBrowser() | |
| cls.SetStaticServerDirs([data_path]) | |
| @classmethod | |
| def GenerateBrowserArgs(cls, additional_args: list[str]) -> list[str]: | |
| """Adds default arguments to |additional_args|. | |
| See the parent class' method documentation for additional information. | |
| """ | |
| default_args = super(GpuProcessIntegrationTest, cls).GenerateBrowserArgs( | |
| additional_args | |
| ) | |
| default_args.extend( | |
| [ | |
| cba.ENABLE_GPU_BENCHMARKING, | |
| # TODO(kbr): figure out why the following option seems to be | |
| # needed on Android for robustness. | |
| # https://github.com/catapult-project/catapult/issues/3122 | |
| '--no-first-run', | |
| # Disable: | |
| # Do you want the application "Chromium Helper.app" to accept incoming | |
| # network connections? | |
| # dialogs on macOS. crbug.com/969559 | |
| cba.DISABLE_DEVICE_DISCOVERY_NOTIFICATIONS, | |
| ] | |
| ) | |
| return default_args | |
| @classmethod | |
| def GenerateGpuTests(cls, options: ct.ParsedCmdArgs) -> ct.TestGenerator: | |
| # The browser test runner synthesizes methods with the exact name | |
| # given in GenerateGpuTests, so in order to hand-write our tests but | |
| # also go through the _RunGpuTest trampoline, the test needs to be | |
| # slightly differently named. | |
| # Also note that since functional_video.html refers to files in | |
| # ../media/ , the serving dir must be the common parent directory. | |
| tests = ( | |
| ('GpuProcess_canvas2d', 'gpu/functional_canvas_demo.html'), | |
| ('GpuProcess_css3d', 'gpu/functional_3d_css.html'), | |
| ('GpuProcess_webgl', 'gpu/functional_webgl.html'), | |
| ('GpuProcess_video', 'gpu/functional_video.html'), | |
| ('GpuProcess_gpu_info_complete', 'gpu/functional_3d_css.html'), | |
| ('GpuProcess_driver_bug_workarounds_in_gpu_process', 'chrome:gpu'), | |
| ('GpuProcess_readback_webgl_gpu_process', 'chrome:gpu'), | |
| ('GpuProcess_feature_status_under_swiftshader', 'chrome:gpu'), | |
| ('GpuProcess_one_extra_workaround', 'chrome:gpu'), | |
| ('GpuProcess_disable_gpu', 'gpu/functional_webgl.html'), | |
| ('GpuProcess_disable_gpu_and_swiftshader', 'gpu/functional_webgl.html'), | |
| ('GpuProcess_disable_swiftshader', 'gpu/functional_webgl.html'), | |
| ('GpuProcess_disabling_workarounds_works', 'chrome:gpu'), | |
| ( | |
| 'GpuProcess_mac_webgl_backgrounded_high_performance', | |
| 'gpu/functional_blank.html', | |
| ), | |
| ( | |
| 'GpuProcess_mac_webgl_high_performance', | |
| 'gpu/functional_webgl_high_performance.html', | |
| ), | |
| ('GpuProcess_mac_webgl_low_power', 'gpu/functional_webgl_low_power.html'), | |
| ( | |
| 'GpuProcess_mac_webgl_terminated_high_performance', | |
| 'gpu/functional_blank.html', | |
| ), | |
| ('GpuProcess_swiftshader_for_webgl', 'gpu/functional_webgl.html'), | |
| ( | |
| 'GpuProcess_no_swiftshader_for_webgl_without_flags', | |
| 'gpu/functional_webgl.html', | |
| ), | |
| ( | |
| 'GpuProcess_webgl_disabled_extension', | |
| 'gpu/functional_webgl_disabled_extension.html', | |
| ), | |
| ('GpuProcess_webgpu_iframe_removed', 'gpu/webgpu-iframe-removed.html'), | |
| ('GpuProcess_vulkan_vma_race', 'gpu/vulkan_vma_race.html'), | |
| ('GpuProcess_visibility', 'about:blank'), | |
| ('GpuProcess_webgl_y16_uploads', 'gpu/webgl_y16_uploads.html'), | |
| ('GpuProcess_webgl_background_clear', 'gpu/webgl-background-clear.html'), | |
| ) | |
| for t in tests: | |
| yield (t[0], t[1], ['_' + t[0]]) | |
| def RunActualGpuTest(self, test_path: str, args: ct.TestArgs) -> None: | |
| test_name = args[0] | |
| getattr(self, test_name)(test_path) | |
| ###################################### | |
| # Helper functions for the tests below | |
| def _GetNavigateTimeout(self) -> float: | |
| # 60 matches Telemetry's default timeout for Navigate(). | |
| timeout = 60 | |
| if self._is_asan: | |
| timeout *= _ASAN_NAVIGATE_MULTIPLIER | |
| return timeout | |
| def _GetTestCompletionTimeout(self) -> float: | |
| timeout = 10 | |
| if self.browser.platform.GetOSName() == 'fuchsia': | |
| timeout *= _FUCHSIA_TEST_COMPLETION_MULTIPLIER | |
| return timeout | |
| def _Navigate(self, test_path: str) -> None: | |
| url = self.UrlOfStaticFilePath(test_path) | |
| # It's crucial to use the action_runner, rather than the tab's | |
| # Navigate method directly. It waits for the document ready state | |
| # to become interactive or better, avoiding critical race | |
| # conditions. | |
| self.tab.action_runner.Navigate( | |
| url, | |
| script_to_evaluate_on_commit=test_harness_script, | |
| timeout_in_seconds=self._GetNavigateTimeout(), | |
| ) | |
| def _WaitForTestCompletion(self, tab: ct.Tab) -> None: | |
| tab.action_runner.WaitForJavaScriptCondition( | |
| 'window.domAutomationController._finished', | |
| timeout=self._GetTestCompletionTimeout(), | |
| ) | |
| if not tab.EvaluateJavaScript('window.domAutomationController._succeeded'): | |
| self.fail('Test reported that it failed') | |
| def _NavigateAndWait(self, test_path: str) -> None: | |
| self._Navigate(test_path) | |
| self._WaitForTestCompletion(self.tab) | |
| def _VerifyGpuProcessPresent(self) -> None: | |
| tab = self.tab | |
| if not tab.EvaluateJavaScript('chrome.gpuBenchmarking.hasGpuChannel()'): | |
| self.fail('No GPU channel detected') | |
| def _ValidateDriverBugWorkaroundsImpl( | |
| self, is_expected: bool, workaround_name: str | |
| ) -> None: | |
| tab = self.tab | |
| gpu_driver_bug_workarounds = tab.EvaluateJavaScript( | |
| 'chrome.gpuBenchmarking.getGpuDriverBugWorkarounds()' | |
| ) | |
| is_present = workaround_name in gpu_driver_bug_workarounds | |
| failure = False | |
| error_message = None | |
| if is_expected and not is_present: | |
| failure = True | |
| error_message = 'is missing' | |
| elif not is_expected and is_present: | |
| failure = True | |
| error_message = 'is not expected' | |
| if failure: | |
| print('Test failed. Printing page contents:') | |
| print(tab.EvaluateJavaScript('document.body.innerHTML')) | |
| self.fail( | |
| f'{workaround_name} {error_message} workarounds: ' | |
| f'{gpu_driver_bug_workarounds}' | |
| ) | |
| def _ValidateDriverBugWorkarounds( | |
| self, expected_workaround: str | None, unexpected_workaround: str | None | |
| ) -> None: | |
| if not expected_workaround and not unexpected_workaround: | |
| return | |
| if expected_workaround: | |
| self._ValidateDriverBugWorkaroundsImpl(True, expected_workaround) | |
| if unexpected_workaround: | |
| self._ValidateDriverBugWorkaroundsImpl(False, unexpected_workaround) | |
| @staticmethod | |
| def _Filterer(workaround: str) -> bool: | |
| # Filter all entries starting with "disabled_extension_" and | |
| # "disabled_webgl_extension_", as these are synthetic entries | |
| # added to make it easier to read the logs. | |
| banned_prefixes = ['disabled_extension_', 'disabled_webgl_extension_'] | |
| for p in banned_prefixes: | |
| if workaround.startswith(p): | |
| return False | |
| return True | |
| def _CompareAndCaptureDriverBugWorkarounds( | |
| self, | |
| ) -> tuple[list[str], str | None]: | |
| tab = self.tab | |
| if not tab.EvaluateJavaScript('chrome.gpuBenchmarking.hasGpuProcess()'): | |
| self.fail('No GPU process detected') | |
| if not tab.EvaluateJavaScript('chrome.gpuBenchmarking.hasGpuChannel()'): | |
| self.fail('No GPU channel detected') | |
| browser_list = [ | |
| x | |
| for x in tab.EvaluateJavaScript('GetDriverBugWorkarounds()') | |
| if self._Filterer(x) | |
| ] | |
| gpu_list = [ | |
| x | |
| for x in tab.EvaluateJavaScript( | |
| 'chrome.gpuBenchmarking.getGpuDriverBugWorkarounds()' | |
| ) | |
| if self._Filterer(x) | |
| ] | |
| diff = set(browser_list).symmetric_difference(set(gpu_list)) | |
| if len(diff) > 0: | |
| self.fail( | |
| f'Browser and GPU process list of driver bug workarounds are ' | |
| f'not equal: {browser_list} != {gpu_list}, diff: {list(diff)}' | |
| ) | |
| basic_infos = _GetBrowserBridgeProperty(tab, 'gpuInfo.basicInfo') | |
| disabled_gl_extensions = None | |
| for info in basic_infos: | |
| if info['description'].startswith('Disabled Extensions'): | |
| disabled_gl_extensions = info['value'] | |
| break | |
| return gpu_list, disabled_gl_extensions | |
| ###################################### | |
| # The actual tests | |
| def _GpuProcess_canvas2d(self, test_path: str) -> None: | |
| self.RestartBrowserIfNecessaryWithArgs([]) | |
| self._NavigateAndWait(test_path) | |
| self._VerifyGpuProcessPresent() | |
| def _GpuProcess_css3d(self, test_path: str) -> None: | |
| self.RestartBrowserIfNecessaryWithArgs([]) | |
| self._NavigateAndWait(test_path) | |
| self._VerifyGpuProcessPresent() | |
| def _GpuProcess_webgl(self, test_path: str) -> None: | |
| self.RestartBrowserIfNecessaryWithArgs([]) | |
| self._NavigateAndWait(test_path) | |
| self._VerifyGpuProcessPresent() | |
| def _GpuProcess_video(self, test_path: str) -> None: | |
| self.RestartBrowserIfNecessaryWithArgs([]) | |
| self._NavigateAndWait(test_path) | |
| self._VerifyGpuProcessPresent() | |
| def _GpuProcess_gpu_info_complete(self, test_path: str) -> None: | |
| # Regression test for crbug.com/454906 | |
| self.RestartBrowserIfNecessaryWithArgs([]) | |
| self._NavigateAndWait(test_path) | |
| tab = self.tab | |
| system_info = tab.browser.GetSystemInfo() | |
| if not system_info: | |
| self.fail('Browser must support system info') | |
| if not system_info.gpu: | |
| self.fail('Target machine must have a GPU') | |
| if not system_info.gpu.aux_attributes: | |
| self.fail('Browser must support GPU aux attributes') | |
| if not 'gl_renderer' in system_info.gpu.aux_attributes: | |
| self.fail('Browser must have gl_renderer in aux attribs') | |
| if ( | |
| not host_information.IsMac() | |
| and len(system_info.gpu.aux_attributes['gl_renderer']) <= 0 | |
| ): | |
| # On MacOSX we don't create a context to collect GL strings.1 | |
| self.fail('Must have a non-empty gl_renderer string') | |
| def _GpuProcess_driver_bug_workarounds_in_gpu_process( | |
| self, test_path: str | |
| ) -> None: | |
| self.RestartBrowserIfNecessaryWithArgs( | |
| ['--use_gpu_driver_workaround_for_testing'] | |
| ) | |
| self._Navigate(test_path) | |
| self.tab.WaitForJavaScriptCondition( | |
| 'window.gpuPagePopulated', timeout=_GPU_PAGE_TIMEOUT | |
| ) | |
| self._ValidateDriverBugWorkarounds( | |
| 'use_gpu_driver_workaround_for_testing', None | |
| ) | |
| def _GpuProcess_readback_webgl_gpu_process(self, test_path: str) -> None: | |
| # Hit test group 1 with entry 152 from kSoftwareRenderingListEntries. | |
| self.RestartBrowserIfNecessaryWithArgs( | |
| [ | |
| '--gpu-blocklist-test-group=1', | |
| cba.DISABLE_GPU_COMPOSITING, | |
| ] | |
| ) | |
| self._Navigate(test_path) | |
| feature_status_list = _GetBrowserBridgeProperty( | |
| self.tab, 'gpuInfo.featureStatus.featureStatus' | |
| ) | |
| result = True | |
| for name, status in feature_status_list.items(): | |
| if name == 'webgl': | |
| result = result and status == 'enabled_readback' | |
| elif name == 'webgl2': | |
| result = result and status == 'unavailable_off' | |
| else: | |
| pass | |
| if not result: | |
| self.fail(f'WebGL readback setup failed: {feature_status_list}') | |
| def _GpuProcess_feature_status_under_swiftshader( | |
| self, test_path: str | |
| ) -> None: | |
| # Hit test group 2 with entry 153 from kSoftwareRenderingListEntries. | |
| self.RestartBrowserIfNecessaryWithArgs( | |
| [ | |
| '--gpu-blocklist-test-group=2', | |
| '--enable-unsafe-swiftshader', | |
| '--disable-features=AllowD3D11WarpFallback', | |
| ] | |
| ) | |
| self._Navigate(test_path) | |
| feature_status_list = _GetBrowserBridgeProperty( | |
| self.tab, 'gpuInfo.featureStatus.featureStatus' | |
| ) | |
| for name, status in feature_status_list.items(): | |
| if name == 'webgl' and status != 'unavailable_software': | |
| self.fail(f'WebGL status for SwiftShader failed: {status}') | |
| elif name == '2d_canvas' and status != 'unavailable_software': | |
| self.fail(f'2D Canvas status for SwiftShader failed: {status}') | |
| # On Linux we relaunch GPU process to fallback to SwiftShader, therefore | |
| # featureStatusForHardwareGpu isn't available. So finish early if we're on | |
| # Linux. | |
| if host_information.IsLinux(): | |
| return | |
| # On Mac, featureStatusForHardwareGpu is not populated when running on | |
| # SwiftShader. The previous checks are enough to confirm that WebGL is | |
| # blocklisted. | |
| if host_information.IsMac(): | |
| return | |
| feature_status_for_hardware_gpu_list = _GetBrowserBridgeProperty( | |
| self.tab, 'gpuInfo.featureStatusForHardwareGpu.featureStatus' | |
| ) | |
| for name, status in feature_status_for_hardware_gpu_list.items(): | |
| if name == 'webgl' and status != 'unavailable_off': | |
| self.fail(f'WebGL status for hardware GPU failed: {status}') | |
| elif name == '2d_canvas' and status != 'enabled': | |
| self.fail(f'2D Canvas status for hardware GPU failed: {status}') | |
| def _GpuProcess_feature_status_under_warp(self, test_path: str) -> None: | |
| if not host_information.IsWindows(): | |
| return | |
| # Hit test group 2 with entry 153 from kSoftwareRenderingListEntries. | |
| self.RestartBrowserIfNecessaryWithArgs( | |
| [ | |
| '--gpu-blocklist-test-group=2', | |
| '--enable-features=AllowD3D11WarpFallback', | |
| ] | |
| ) | |
| self._Navigate(test_path) | |
| feature_status_list = _GetBrowserBridgeProperty( | |
| self.tab, 'gpuInfo.featureStatus.featureStatus' | |
| ) | |
| for name, status in feature_status_list.items(): | |
| if name == 'webgl' and status != 'unavailable_software': | |
| self.fail(f'WebGL status for WARP failed: {status}') | |
| elif name == '2d_canvas' and status != 'unavailable_software': | |
| self.fail(f'2D Canvas status for WARP failed: {status}') | |
| feature_status_for_hardware_gpu_list = _GetBrowserBridgeProperty( | |
| self.tab, 'gpuInfo.featureStatusForHardwareGpu.featureStatus' | |
| ) | |
| for name, status in feature_status_for_hardware_gpu_list.items(): | |
| if name == 'webgl' and status != 'unavailable_off': | |
| self.fail(f'WebGL status for hardware GPU failed: {status}') | |
| elif name == '2d_canvas' and status != 'enabled': | |
| self.fail(f'2D Canvas status for hardware GPU failed: {status}') | |
| def _GpuProcess_one_extra_workaround(self, test_path: str) -> None: | |
| # Start this test by launching the browser with no command line | |
| # arguments. | |
| self.RestartBrowserIfNecessaryWithArgs([]) | |
| self._Navigate(test_path) | |
| self._VerifyGpuProcessPresent() | |
| self.tab.WaitForJavaScriptCondition( | |
| 'window.gpuPagePopulated', timeout=_GPU_PAGE_TIMEOUT | |
| ) | |
| recorded_workarounds, recorded_disabled_gl_extensions = ( | |
| self._CompareAndCaptureDriverBugWorkarounds() | |
| ) | |
| # Relaunch the browser enabling test group 1 with entry 215, where | |
| # use_gpu_driver_workaround_for_testing is enabled. | |
| additional_args = ['--gpu-driver-bug-list-test-group=1'] | |
| # Add the testing workaround to the recorded workarounds. | |
| recorded_workarounds.append('use_gpu_driver_workaround_for_testing') | |
| additional_args.append( | |
| '--disable-gl-extensions=' + recorded_disabled_gl_extensions | |
| ) | |
| self.RestartBrowserIfNecessaryWithArgs(additional_args) | |
| self._Navigate(test_path) | |
| self._VerifyGpuProcessPresent() | |
| self.tab.WaitForJavaScriptCondition( | |
| 'window.gpuPagePopulated', timeout=_GPU_PAGE_TIMEOUT | |
| ) | |
| new_workarounds, new_disabled_gl_extensions = ( | |
| self._CompareAndCaptureDriverBugWorkarounds() | |
| ) | |
| diff = set(recorded_workarounds).symmetric_difference(new_workarounds) | |
| tab = self.tab | |
| if len(diff) > 0: | |
| print('Test failed. Printing page contents:') | |
| print(tab.EvaluateJavaScript('document.body.innerHTML')) | |
| self.fail( | |
| f'GPU process and expected list of driver bug workarounds are ' | |
| f'not equal: {recorded_workarounds} != {new_workarounds}, ' | |
| f'diff: {list(diff)}' | |
| ) | |
| if recorded_disabled_gl_extensions != new_disabled_gl_extensions: | |
| print('Test failed. Printing page contents:') | |
| print(tab.EvaluateJavaScript('document.body.innerHTML')) | |
| self.fail( | |
| f'The expected disabled gl extensions are incorrect: ' | |
| f'{recorded_disabled_gl_extensions} != ' | |
| f'{new_disabled_gl_extensions}:' | |
| ) | |
| def _GpuProcess_disable_gpu(self, test_path: str) -> None: | |
| # This test loads functional_webgl.html so that there is a | |
| # deliberate attempt to use an API which would start the GPU | |
| # process. | |
| self.RestartBrowserIfNecessaryWithArgs([cba.DISABLE_GPU]) | |
| self._NavigateAndWait(test_path) | |
| has_gpu_process = self.tab.EvaluateJavaScript( | |
| 'chrome.gpuBenchmarking.hasGpuProcess()' | |
| ) | |
| if not has_gpu_process: | |
| self.fail('GPU process not detected') | |
| def _GpuProcess_visibility(self, test_path: str) -> None: | |
| def _PollUntilVisibilityCallbackCount(target_count: int) -> None: | |
| start_time = time.monotonic() | |
| while time.monotonic() - start_time < _GPU_VISIBILITY_POLL_DURATION_SEC: | |
| system_info = self.browser.GetSystemInfo() | |
| callback_count = system_info.gpu.aux_attributes[ | |
| 'visibility_callback_call_count' | |
| ] | |
| if callback_count == target_count: | |
| return | |
| time.sleep(_GPU_VISIBILITY_POLL_DELAY_SEC) | |
| self.fail( | |
| f'Visibility callback call count expected {target_count}, got ' | |
| f'{callback_count}' | |
| ) | |
| os_name = self.browser.platform.GetOSName() | |
| if os_name != 'android': | |
| logging.info('Skipping test because not running on Android') | |
| return | |
| # pylint: disable=protected-access | |
| sdk_version = ( | |
| self.browser.platform._platform_backend.device.build_version_sdk | |
| ) | |
| # pylint: enable=protected-access | |
| if sdk_version < version_codes.PIE: | |
| logging.info('Skipping test because not running on Android P+') | |
| return | |
| has_gpu_process = self.tab.EvaluateJavaScript( | |
| 'chrome.gpuBenchmarking.hasGpuProcess()' | |
| ) | |
| if not has_gpu_process: | |
| logging.info('Skipping test because no out-of-process GPU service') | |
| return | |
| self.RestartBrowserIfNecessaryWithArgs([]) | |
| self._Navigate(test_path) | |
| self.tab.WaitForJavaScriptCondition( | |
| 'document.visibilityState == "visible"', timeout=_GPU_PAGE_TIMEOUT | |
| ) | |
| _PollUntilVisibilityCallbackCount(1) | |
| self.browser.platform.android_action_runner.TurnScreenOff() | |
| self.tab.WaitForJavaScriptCondition( | |
| 'document.visibilityState == "hidden"', timeout=_GPU_PAGE_TIMEOUT | |
| ) | |
| _PollUntilVisibilityCallbackCount(2) | |
| self.browser.platform.android_action_runner.TurnScreenOn() | |
| self.tab.WaitForJavaScriptCondition( | |
| 'document.visibilityState == "visible"', timeout=_GPU_PAGE_TIMEOUT | |
| ) | |
| _PollUntilVisibilityCallbackCount(3) | |
| def _GpuProcess_webgl_y16_uploads(self, test_path: str) -> None: | |
| self.RestartBrowserIfNecessaryWithArgs( | |
| [ | |
| '--use-fake-device-for-media-stream=device-count=2', | |
| '--use-fake-ui-for-media-stream', | |
| '--autoplay-policy=no-user-gesture-required', | |
| '--use-angle=swiftshader', | |
| ] | |
| ) | |
| self._NavigateAndWait(test_path) | |
| def _GpuProcess_disable_gpu_and_swiftshader(self, test_path: str) -> None: | |
| # Disable SwiftShader, GPU process should launch for display compositing. | |
| self.RestartBrowserIfNecessaryWithArgs( | |
| [cba.DISABLE_GPU, cba.DISABLE_SOFTWARE_RASTERIZER] | |
| ) | |
| self._NavigateAndWait(test_path) | |
| has_gpu_process = self.tab.EvaluateJavaScript( | |
| 'chrome.gpuBenchmarking.hasGpuProcess()' | |
| ) | |
| if not has_gpu_process: | |
| self.fail('GPU process not detected') | |
| def _GpuProcess_disable_swiftshader(self, test_path: str) -> None: | |
| # Disable SwiftShader, GPU process should be able to launch. | |
| self.RestartBrowserIfNecessaryWithArgs([cba.DISABLE_SOFTWARE_RASTERIZER]) | |
| self._NavigateAndWait(test_path) | |
| has_gpu_process = self.tab.EvaluateJavaScript( | |
| 'chrome.gpuBenchmarking.hasGpuProcess()' | |
| ) | |
| if not has_gpu_process: | |
| self.fail('GPU process not detected') | |
| def _GpuProcess_disabling_workarounds_works(self, test_path): | |
| # Hit exception from id 215 from kGpuDriverBugListEntries. | |
| self.RestartBrowserIfNecessaryWithArgs( | |
| [ | |
| '--gpu-driver-bug-list-test-group=1', | |
| '--use_gpu_driver_workaround_for_testing=0', | |
| ] | |
| ) | |
| self._Navigate(test_path) | |
| self.tab.WaitForJavaScriptCondition( | |
| 'window.gpuPagePopulated', timeout=_GPU_PAGE_TIMEOUT | |
| ) | |
| workarounds, _ = self._CompareAndCaptureDriverBugWorkarounds() | |
| if 'use_gpu_driver_workaround_for_testing' in workarounds: | |
| self.fail('use_gpu_driver_workaround_for_testing erroneously present') | |
| def _GpuProcess_swiftshader_for_webgl(self, test_path: str) -> None: | |
| # This test loads functional_webgl.html so that there is a deliberate | |
| # attempt to use an API which would start the GPU process. | |
| args_list = ( | |
| # Triggering test_group 2 where WebGL is blocklisted. | |
| [ | |
| '--gpu-blocklist-test-group=2', | |
| '--enable-unsafe-swiftshader', | |
| '--disable-features=AllowD3D11WarpFallback', | |
| ], | |
| # Explicitly disable GPU access. | |
| [ | |
| cba.DISABLE_GPU, | |
| '--enable-unsafe-swiftshader', | |
| '--disable-features=AllowD3D11WarpFallback', | |
| ], | |
| ) | |
| for args in args_list: | |
| self.RestartBrowserIfNecessaryWithArgs(args) | |
| self._NavigateAndWait(test_path) | |
| # Validate the WebGL unmasked renderer string. | |
| renderer = self.tab.EvaluateJavaScript('gl_renderer') | |
| if not renderer: | |
| self.fail('getParameter(UNMASKED_RENDERER_WEBGL) was null') | |
| if 'SwiftShader' not in renderer: | |
| self.fail('Expected SwiftShader renderer; instead got ' + renderer) | |
| # Validate GPU info. | |
| system_info = self.browser.GetSystemInfo() | |
| if not system_info: | |
| self.fail("Browser doesn't support GetSystemInfo") | |
| gpu = system_info.gpu | |
| if not gpu: | |
| self.fail('Target machine must have a GPU') | |
| if not gpu.aux_attributes: | |
| self.fail('Browser must support GPU aux attributes') | |
| if 'SwiftShader' not in gpu.aux_attributes['gl_renderer']: | |
| self.fail('Expected "SwiftShader" in GPU info GL renderer string') | |
| if 'Google' not in gpu.aux_attributes['gl_vendor']: | |
| self.fail('Expected "Google" in GPU info GL vendor string') | |
| device = gpu.devices[0] | |
| if not device: | |
| self.fail("System Info doesn't have a device") | |
| # Validate extensions. | |
| ext_list = [ | |
| 'ANGLE_instanced_arrays', | |
| 'EXT_blend_minmax', | |
| 'EXT_texture_filter_anisotropic', | |
| 'OES_element_index_uint', | |
| 'OES_standard_derivatives', | |
| 'OES_texture_float', | |
| 'OES_texture_float_linear', | |
| 'OES_texture_half_float', | |
| 'OES_texture_half_float_linear', | |
| 'OES_vertex_array_object', | |
| 'WEBGL_compressed_texture_etc1', | |
| 'WEBGL_debug_renderer_info', | |
| 'WEBGL_depth_texture', | |
| 'WEBGL_draw_buffers', | |
| 'WEBGL_lose_context', | |
| ] | |
| tab = self.tab | |
| for ext in ext_list: | |
| if tab.EvaluateJavaScript('!gl_context.getExtension("' + ext + '")'): | |
| self.fail(f'Expected {ext} support') | |
| def _GpuProcess_warp_for_webgl(self, test_path: str) -> None: | |
| if not host_information.IsWindows(): | |
| return | |
| # This test loads functional_webgl.html so that there is a deliberate | |
| # attempt to use an API which would start the GPU process. | |
| # WARP fallback is preferred over SwiftShader if both are enabled. | |
| args_list = ( | |
| # Triggering test_group 2 where WebGL is blocklisted. | |
| [ | |
| '--gpu-blocklist-test-group=2', | |
| '--enable-features=AllowD3D11WarpFallback', | |
| ], | |
| # Explicitly disable GPU access. | |
| [cba.DISABLE_GPU, '--enable-features=AllowD3D11WarpFallback'], | |
| ) | |
| for args in args_list: | |
| self.RestartBrowserIfNecessaryWithArgs(args) | |
| self._NavigateAndWait(test_path) | |
| # Validate the WebGL unmasked renderer string. | |
| renderer = self.tab.EvaluateJavaScript('gl_renderer') | |
| if not renderer: | |
| self.fail('getParameter(UNMASKED_RENDERER_WEBGL) was null') | |
| if 'WARP' not in renderer: | |
| self.fail('Expected WARP renderer; instead got ' + renderer) | |
| # Validate GPU info. | |
| system_info = self.browser.GetSystemInfo() | |
| if not system_info: | |
| self.fail("Browser doesn't support GetSystemInfo") | |
| gpu = system_info.gpu | |
| if not gpu: | |
| self.fail('Target machine must have a GPU') | |
| if not gpu.aux_attributes: | |
| self.fail('Browser must support GPU aux attributes') | |
| if 'WARP' not in gpu.aux_attributes['gl_renderer']: | |
| self.fail('Expected "WARP" in GPU info GL renderer string') | |
| if 'Google' not in gpu.aux_attributes['gl_vendor']: | |
| self.fail('Expected "Google" in GPU info GL vendor string') | |
| device = gpu.devices[0] | |
| if not device: | |
| self.fail("System Info doesn't have a device") | |
| # Validate extensions. | |
| ext_list = [ | |
| 'ANGLE_instanced_arrays', | |
| 'EXT_blend_minmax', | |
| 'EXT_texture_filter_anisotropic', | |
| 'OES_element_index_uint', | |
| 'OES_standard_derivatives', | |
| 'OES_texture_float', | |
| 'OES_texture_float_linear', | |
| 'OES_texture_half_float', | |
| 'OES_texture_half_float_linear', | |
| 'OES_vertex_array_object', | |
| 'WEBGL_compressed_texture_etc1', | |
| 'WEBGL_debug_renderer_info', | |
| 'WEBGL_depth_texture', | |
| 'WEBGL_draw_buffers', | |
| 'WEBGL_lose_context', | |
| ] | |
| tab = self.tab | |
| for ext in ext_list: | |
| if tab.EvaluateJavaScript('!gl_context.getExtension("' + ext + '")'): | |
| self.fail(f'Expected {ext} support') | |
| def _GpuProcess_no_swiftshader_for_webgl_without_flags( | |
| self, test_path: str | |
| ) -> None: | |
| # This test loads functional_webgl.html with GPU disabled and verifies that | |
| # SwiftShader is not available without the --enable-unsafe-swiftshader flag | |
| # or AllowSwiftShaderFallback killswitch. | |
| # Because AllowSwiftShaderFallback is currently enabled by default, disable | |
| # it to test the upcoming default behavior. | |
| # Make sure to force disable WARP fallback which takes precedence over | |
| # SwiftShader fallback when both are enabled. | |
| disable_hardware_webgl_args_list = [ | |
| # Triggering test_group 2 where WebGL is blocklisted. | |
| ['--gpu-blocklist-test-group=2'], | |
| # Explicitly disable GPU access. | |
| [cba.DISABLE_GPU], | |
| ] | |
| disable_swiftshader_fallback_feature = [ | |
| '--disable-features=AllowSwiftShaderFallback', | |
| '--disable-features=AllowD3D11WarpFallback', | |
| ] | |
| allow_swiftshader_args_list = [ | |
| [ | |
| '--enable-unsafe-swiftshader', | |
| '--disable-features=AllowD3D11WarpFallback', | |
| ], | |
| [ | |
| '--use-gl=angle', | |
| '--use-angle=swiftshader', | |
| '--disable-features=AllowD3D11WarpFallback', | |
| ], | |
| [ | |
| '--enable-features=AllowSwiftShaderFallback', | |
| '--disable-features=AllowD3D11WarpFallback', | |
| ], | |
| ] | |
| for disable_hardware_webgl_args in disable_hardware_webgl_args_list: | |
| self.RestartBrowserIfNecessaryWithArgs( | |
| disable_hardware_webgl_args + disable_swiftshader_fallback_feature | |
| ) | |
| self._NavigateAndWait(test_path) | |
| renderer = self.tab.EvaluateJavaScript('gl_renderer') | |
| if renderer: | |
| self.fail('Expected no WebGL renderer; instead got ' + renderer) | |
| for allow_swiftshader_args in allow_swiftshader_args_list: | |
| self.RestartBrowserIfNecessaryWithArgs( | |
| disable_hardware_webgl_args + allow_swiftshader_args | |
| ) | |
| self._NavigateAndWait(test_path) | |
| # Validate the WebGL unmasked renderer string. | |
| renderer = self.tab.EvaluateJavaScript('gl_renderer') | |
| if not renderer: | |
| self.fail('getParameter(UNMASKED_RENDERER_WEBGL) was null') | |
| if 'SwiftShader' not in renderer: | |
| self.fail('Expected SwiftShader renderer; instead got ' + renderer) | |
| def _GpuProcess_webgl_disabled_extension(self, test_path: str) -> None: | |
| # Hit exception from id 257 from kGpuDriverBugListEntries. | |
| self.RestartBrowserIfNecessaryWithArgs( | |
| [ | |
| '--gpu-driver-bug-list-test-group=2', | |
| ] | |
| ) | |
| self._NavigateAndWait(test_path) | |
| def _GpuProcess_mac_webgl_low_power(self, test_path: str) -> None: | |
| # Ensures that low-power WebGL content stays on the low-power GPU. | |
| if not self.IsDualGPUMacLaptop(): | |
| logging.info('Skipping test because not running on dual-GPU Mac laptop') | |
| return | |
| # Start with a clean browser instance to ensure the GPU process is in a | |
| # clean state. | |
| self.RestartBrowserWithArgs([]) | |
| # Wait a few seconds for the system to dispatch any GPU switched | |
| # notifications. | |
| time.sleep(3) | |
| self._NavigateAndWait(test_path) | |
| # Sleep for several seconds to ensure that any GPU switch is detected. | |
| time.sleep(6) | |
| if not self._IsIntelGPUActive(): | |
| self.fail( | |
| 'Low-power WebGL context incorrectly activated the high-performance GPU' | |
| ) | |
| def _GpuProcess_mac_webgl_high_performance(self, test_path: str) -> None: | |
| # Ensures that high-performance WebGL content activates the high-performance | |
| # GPU. | |
| if not self.IsDualGPUMacLaptop(): | |
| logging.info('Skipping test because not running on dual-GPU Mac laptop') | |
| return | |
| # Start with a clean browser instance to ensure the GPU process is in a | |
| # clean state. | |
| self.RestartBrowserWithArgs([]) | |
| # Wait a few seconds for the system to dispatch any GPU switched | |
| # notifications. | |
| time.sleep(3) | |
| self._NavigateAndWait(test_path) | |
| # Sleep for several seconds to ensure that any GPU switch is detected. | |
| time.sleep(6) | |
| if self._IsIntelGPUActive(): | |
| self.fail( | |
| 'High-performance WebGL context did not activate the ' | |
| 'high-performance GPU' | |
| ) | |
| def _GpuProcess_mac_webgl_backgrounded_high_performance( | |
| self, test_path: str | |
| ) -> None: | |
| # Ensures that high-performance WebGL content in a background tab releases | |
| # the hold on the discrete GPU after 10 seconds. | |
| if not self.IsDualGPUMacLaptop(): | |
| logging.info('Skipping test because not running on dual-GPU Mac laptop') | |
| return | |
| # Start with a clean browser instance to ensure the GPU process is in a | |
| # clean state. | |
| self.RestartBrowserWithArgs([]) | |
| # Wait a few seconds for the system to dispatch any GPU switched | |
| # notifications. | |
| time.sleep(3) | |
| self._NavigateAndWait(test_path) | |
| blank_tab = self.tab | |
| # Create a new tab and navigate it to the high-power WebGL test. | |
| webgl_tab = self.browser.tabs.New() | |
| webgl_tab.Activate() | |
| webgl_url = self.UrlOfStaticFilePath( | |
| 'gpu/functional_webgl_high_performance.html' | |
| ) | |
| webgl_tab.action_runner.Navigate( | |
| webgl_url, script_to_evaluate_on_commit=test_harness_script | |
| ) | |
| self._WaitForTestCompletion(webgl_tab) | |
| # Verify that the high-performance GPU is active. | |
| if self._IsIntelGPUActive(): | |
| self.fail( | |
| 'High-performance WebGL context did not activate the ' | |
| 'high-performance GPU' | |
| ) | |
| # Now activate the original tab. | |
| blank_tab.Activate() | |
| # Sleep for >10 seconds in order to wait for the hold on the | |
| # high-performance GPU to be released. | |
| time.sleep(15) | |
| if not self._IsIntelGPUActive(): | |
| self.fail( | |
| 'Backgrounded high-performance WebGL context did not release the ' | |
| 'hold on the high-performance GPU' | |
| ) | |
| def _GpuProcess_mac_webgl_terminated_high_performance( | |
| self, test_path: str | |
| ) -> None: | |
| # Ensures that high-performance WebGL content in a background tab releases | |
| # the hold on the discrete GPU after 10 seconds. | |
| if not self.IsDualGPUMacLaptop(): | |
| logging.info('Skipping test because not running on dual-GPU Mac laptop') | |
| return | |
| # Start with a clean browser instance to ensure the GPU process is in a | |
| # clean state. | |
| self.RestartBrowserWithArgs([]) | |
| # Wait a few seconds for the system to dispatch any GPU switched | |
| # notifications. | |
| time.sleep(3) | |
| self._NavigateAndWait(test_path) | |
| # Create a new tab and navigate it to the high-power WebGL test. | |
| webgl_tab = self.browser.tabs.New() | |
| webgl_tab.Activate() | |
| webgl_url = self.UrlOfStaticFilePath( | |
| 'gpu/functional_webgl_high_performance.html' | |
| ) | |
| webgl_tab.action_runner.Navigate( | |
| webgl_url, script_to_evaluate_on_commit=test_harness_script | |
| ) | |
| self._WaitForTestCompletion(webgl_tab) | |
| # Verify that the high-performance GPU is active. | |
| if self._IsIntelGPUActive(): | |
| self.fail( | |
| 'High-performance WebGL context did not activate the ' | |
| 'high-performance GPU' | |
| ) | |
| # Close the high-performance WebGL tab. | |
| webgl_tab.Close() | |
| # Sleep for >10 seconds in order to wait for the hold on the | |
| # high-performance GPU to be released. | |
| time.sleep(15) | |
| if not self._IsIntelGPUActive(): | |
| self.fail( | |
| 'Backgrounded high-performance WebGL context did not release the ' | |
| 'hold on the high-performance GPU' | |
| ) | |
| def _GpuProcess_webgpu_iframe_removed(self, test_path: str) -> None: | |
| self.RestartBrowserIfNecessaryWithArgs([]) | |
| self._NavigateAndWait(test_path) | |
| def _GpuProcess_vulkan_vma_race(self, test_path: str) -> None: | |
| self.RestartBrowserIfNecessaryWithArgs([]) | |
| self._NavigateAndWait(test_path) | |
| self._VerifyGpuProcessPresent() | |
| def _GpuProcess_webgl_background_clear(self, test_path: str) -> None: | |
| self.RestartBrowserWithArgs([]) | |
| self._Navigate(test_path) | |
| webgl_tab = self.tab | |
| if not webgl_tab.browser.supports_tab_control: | |
| self.fail('Browser must support tab control') | |
| webgl_tab.WaitForJavaScriptCondition('window.setupFinished', timeout=10) | |
| blank_tab = self.browser.tabs.New() | |
| blank_tab.Activate() | |
| self._WaitForTestCompletion(webgl_tab) | |
| @classmethod | |
| def ExpectationsFiles(cls) -> list[str]: | |
| return [ | |
| os.path.join( | |
| os.path.dirname(os.path.abspath(__file__)), | |
| 'test_expectations', | |
| 'gpu_process_expectations.txt', | |
| ) | |
| ] | |
| def load_tests( | |
| loader: unittest.TestLoader, tests: Any, pattern: Any | |
| ) -> unittest.TestSuite: | |
| del loader, tests, pattern # Unused. | |
| return gpu_integration_test.LoadAllTestsInModule(sys.modules[__name__]) |