blob: 4b7fef4cb0383a0aab19918e40a61bc973fb2e99 [file] [log] [blame]
# Copyright 2016 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import os
import sys
from gpu_tests import common_browser_args as cba
from gpu_tests import gpu_integration_test
from gpu_tests import path_util
from gpu_tests import pixel_test_pages
from telemetry.timeline import model as model_module
from telemetry.timeline import tracing_config
gpu_relative_path = "content/test/data/gpu/"
data_paths = [
os.path.join(path_util.GetChromiumSrcDir(), gpu_relative_path),
os.path.join(path_util.GetChromiumSrcDir(), 'media', 'test', 'data')
]
webgl_test_harness_script = r"""
var domAutomationController = {};
domAutomationController._finished = false;
domAutomationController.send = function(msg) {
// Issue a read pixel to synchronize the gpu process to ensure
// the asynchronous category enabling is finished.
var temp_canvas = document.createElement("canvas")
temp_canvas.width = 1;
temp_canvas.height = 1;
var temp_gl = temp_canvas.getContext("experimental-webgl") ||
temp_canvas.getContext("webgl");
if (temp_gl) {
temp_gl.clear(temp_gl.COLOR_BUFFER_BIT);
var id = new Uint8Array(4);
temp_gl.readPixels(0, 0, 1, 1, temp_gl.RGBA, temp_gl.UNSIGNED_BYTE, id);
} else {
console.log('Failed to get WebGL context.');
}
domAutomationController._finished = true;
}
window.domAutomationController = domAutomationController;
"""
basic_test_harness_script = r"""
var domAutomationController = {};
domAutomationController._proceed = false;
domAutomationController._readyForActions = false;
domAutomationController._succeeded = false;
domAutomationController._finished = false;
domAutomationController.send = function(msg) {
domAutomationController._proceed = true;
let lmsg = msg.toLowerCase();
if (lmsg == "ready") {
domAutomationController._readyForActions = true;
} else {
domAutomationController._finished = true;
if (lmsg == "success") {
domAutomationController._succeeded = true;
} else {
domAutomationController._succeeded = false;
}
}
}
window.domAutomationController = domAutomationController;
"""
# Presentation mode enums match DXGI_FRAME_PRESENTATION_MODE
_SWAP_CHAIN_PRESENTATION_MODE_COMPOSED = 0
_SWAP_CHAIN_PRESENTATION_MODE_OVERLAY = 1
_SWAP_CHAIN_PRESENTATION_MODE_NONE = 2
_SWAP_CHAIN_PRESENTATION_MODE_COMPOSITION_FAILURE = 3
# The following is defined for Chromium testing internal use.
_SWAP_CHAIN_GET_FRAME_STATISTICS_MEDIA_FAILED = -1
_GET_STATISTICS_EVENT_NAME = 'GetFrameStatisticsMedia'
_SWAP_CHAIN_PRESENT_EVENT_NAME = 'SwapChain::Present'
_PRESENT_TO_SWAP_CHAIN_EVENT_NAME = 'SwapChainPresenter::PresentToSwapChain'
class _TraceTestArguments(object):
"""Struct-like object for passing trace test arguments instead of dicts."""
def __init__(self,
browser_args,
category,
test_harness_script,
finish_js_condition,
success_eval_func,
other_args=None):
self.browser_args = browser_args
self.category = category
self.test_harness_script = test_harness_script
self.finish_js_condition = finish_js_condition
self.success_eval_func = success_eval_func
self.other_args = other_args
class TraceIntegrationTest(gpu_integration_test.GpuIntegrationTest):
"""Tests GPU traces are plumbed through properly.
Also tests that GPU Device traces show up on devices that support them."""
@classmethod
def Name(cls):
return 'trace_test'
@classmethod
def GenerateGpuTests(cls, options):
# Include the device level trace tests, even though they're
# currently skipped on all platforms, to give a hint that they
# should perhaps be enabled in the future.
namespace = pixel_test_pages.PixelTestPages
for p in namespace.DefaultPages('TraceTest'):
yield (p.name, gpu_relative_path + p.url,
_TraceTestArguments(
browser_args=[],
category=cls._DisabledByDefaultTraceCategory('gpu.service'),
test_harness_script=webgl_test_harness_script,
finish_js_condition='domAutomationController._finished',
success_eval_func='CheckGLCategory'))
for p in namespace.DefaultPages('DeviceTraceTest'):
yield (p.name, gpu_relative_path + p.url,
_TraceTestArguments(
browser_args=[],
category=cls._DisabledByDefaultTraceCategory('gpu.device'),
test_harness_script=webgl_test_harness_script,
finish_js_condition='domAutomationController._finished',
success_eval_func='CheckGLCategory'))
for p in namespace.DirectCompositionPages('VideoPathTraceTest'):
yield (p.name, gpu_relative_path + p.url,
_TraceTestArguments(
browser_args=p.browser_args,
category=cls._DisabledByDefaultTraceCategory('gpu.service'),
test_harness_script=basic_test_harness_script,
finish_js_condition='domAutomationController._finished',
success_eval_func='CheckVideoPath',
other_args=p.other_args))
for p in namespace.LowLatencyPages('SwapChainTraceTest'):
yield (p.name, gpu_relative_path + p.url,
_TraceTestArguments(
browser_args=p.browser_args,
category='gpu',
test_harness_script=basic_test_harness_script,
finish_js_condition='domAutomationController._finished',
success_eval_func='CheckSwapChainPath',
other_args=p.other_args))
for p in namespace.DirectCompositionPages('OverlayModeTraceTest'):
if p.other_args and p.other_args.get('video_is_rotated', False):
# For all drivers we tested, when a video is rotated, frames won't
# be promoted to hardware overlays.
continue
yield (p.name, gpu_relative_path + p.url,
_TraceTestArguments(
browser_args=p.browser_args,
category=cls._DisabledByDefaultTraceCategory('gpu.service'),
test_harness_script=basic_test_harness_script,
finish_js_condition='domAutomationController._finished',
success_eval_func='CheckOverlayMode',
other_args=p.other_args))
def RunActualGpuTest(self, test_path, *args):
test_params = args[0]
# The version of this test in the old GPU test harness restarted
# the browser after each test, so continue to do that to match its
# behavior.
self.RestartBrowserWithArgs(test_params.browser_args)
# Set up tracing.
config = tracing_config.TracingConfig()
config.chrome_trace_config.category_filter.AddExcludedCategory('*')
config.chrome_trace_config.category_filter.AddFilter(test_params.category)
config.enable_chrome_trace = True
tab = self.tab
tab.browser.platform.tracing_controller.StartTracing(config, 60)
# Perform page navigation.
url = self.UrlOfStaticFilePath(test_path)
tab.Navigate(url,
script_to_evaluate_on_commit=test_params.test_harness_script)
tab.action_runner.WaitForJavaScriptCondition(
test_params.finish_js_condition, timeout=30)
# Stop tracing.
timeline_data = tab.browser.platform.tracing_controller.StopTracing()
# Evaluate success.
if test_params.success_eval_func:
timeline_model = model_module.TimelineModel(timeline_data)
event_iter = timeline_model.IterAllEvents(
event_type_predicate=timeline_model.IsSliceOrAsyncSlice)
prefixed_func_name = '_EvaluateSuccess_' + test_params.success_eval_func
getattr(self, prefixed_func_name)(test_params.category, event_iter,
test_params.other_args)
@classmethod
def SetUpProcess(cls):
super(TraceIntegrationTest, cls).SetUpProcess()
path_util.SetupTelemetryPaths()
cls.CustomizeBrowserArgs([])
cls.StartBrowser()
cls.SetStaticServerDirs(data_paths)
@classmethod
def GenerateBrowserArgs(cls, additional_args):
"""Adds default arguments to |additional_args|.
See the parent class' method documentation for additional information.
"""
default_args = super(TraceIntegrationTest,
cls).GenerateBrowserArgs(additional_args)
default_args.extend([
'--enable-logging',
cba.ENABLE_EXPERIMENTAL_WEB_PLATFORM_FEATURES,
# All bots are connected with a power source, however, we want to to
# test with the code path that's enabled with battery power.
cba.DISABLE_VP_SCALING,
])
return default_args
def _GetOverlayBotConfigHelper(self):
system_info = self.browser.GetSystemInfo()
if not system_info:
raise Exception("Browser doesn't support GetSystemInfo")
gpu = system_info.gpu.devices[0]
if not gpu:
raise Exception("System Info doesn't have a gpu")
os_version_name = self.browser.platform.GetOSVersionName()
return self.GetOverlayBotConfig(os_version_name, gpu.vendor_id,
gpu.device_id)
@staticmethod
def _SwapChainPresentationModeToStr(presentation_mode):
if presentation_mode == _SWAP_CHAIN_PRESENTATION_MODE_COMPOSED:
return 'COMPOSED'
if presentation_mode == _SWAP_CHAIN_PRESENTATION_MODE_OVERLAY:
return 'OVERLAY'
if presentation_mode == _SWAP_CHAIN_PRESENTATION_MODE_NONE:
return 'NONE'
if presentation_mode == _SWAP_CHAIN_PRESENTATION_MODE_COMPOSITION_FAILURE:
return 'COMPOSITION_FAILURE'
if presentation_mode == _SWAP_CHAIN_GET_FRAME_STATISTICS_MEDIA_FAILED:
return 'GET_STATISTICS_FAILED'
return str(presentation_mode)
@staticmethod
def _SwapChainPresentationModeListToStr(presentation_mode_list):
list_str = None
for mode in presentation_mode_list:
mode_str = TraceIntegrationTest._SwapChainPresentationModeToStr(mode)
if list_str is None:
list_str = mode_str
else:
list_str = '%s,%s' % (list_str, mode_str)
return '[%s]' % list_str
@staticmethod
def _DisabledByDefaultTraceCategory(category):
return 'disabled-by-default-%s' % category
#########################################
# The test success evaluation functions
def _EvaluateSuccess_CheckGLCategory(self, category, event_iterator,
other_args):
del other_args # Unused in this particular success evaluation.
for event in event_iterator:
if (event.category == category
and event.args.get('gl_category', None) == 'gpu_toplevel'):
break
else:
self.fail('Trace markers for GPU category %s were not found' % category)
def _EvaluateSuccess_CheckVideoPath(self, category, event_iterator,
other_args):
"""Verifies Chrome goes down the code path as expected.
Depending on whether hardware overlays are supported or not, which formats
are supported in overlays, whether video is downscaled or not, whether
video is rotated or not, Chrome's video presentation code path can be
different.
"""
os_name = self.browser.platform.GetOSName()
assert os_name and os_name.lower() == 'win'
# Calculate expectations.
other_args = other_args if other_args is not None else {}
expect_yuy2 = other_args.get('expect_yuy2', False)
zero_copy = other_args.get('zero_copy', False)
overlay_bot_config = self.GetOverlayBotConfig()
if overlay_bot_config is None:
self.fail('Overlay bot config can not be determined')
assert overlay_bot_config.get('direct_composition', False)
expected_pixel_format = "NV12"
supports_nv12_overlays = False
if overlay_bot_config.get('supports_overlays', False):
supports_yuy2_overlays = False
if overlay_bot_config['yuy2_overlay_support'] != 'NONE':
supports_yuy2_overlays = True
if overlay_bot_config['nv12_overlay_support'] != 'NONE':
supports_nv12_overlays = True
assert supports_yuy2_overlays or supports_nv12_overlays
if expect_yuy2 or not supports_nv12_overlays:
if overlay_bot_config['yuy2_overlay_support'] != 'SOFTWARE':
expected_pixel_format = "YUY2"
if not supports_nv12_overlays or overlay_bot_config[
'nv12_overlay_support'] == 'SOFTWARE':
zero_copy = False
expect_no_overlay = other_args.get('no_overlay', False)
# Verify expectations through captured trace events.
for event in event_iterator:
if event.category != category:
continue
if event.name != _SWAP_CHAIN_PRESENT_EVENT_NAME:
continue
if expect_no_overlay:
self.fail('Expected no overlay got %s' % _SWAP_CHAIN_PRESENT_EVENT_NAME)
detected_pixel_format = event.args.get('PixelFormat', None)
if detected_pixel_format is None:
self.fail('PixelFormat is missing from event %s' %
_SWAP_CHAIN_PRESENT_EVENT_NAME)
if expected_pixel_format != detected_pixel_format:
self.fail('SwapChain pixel format mismatch, expected %s got %s' %
(expected_pixel_format, detected_pixel_format))
detected_zero_copy = event.args.get('ZeroCopy', None)
if detected_zero_copy is None:
self.fail('ZeroCopy is missing from event %s' %
_SWAP_CHAIN_PRESENT_EVENT_NAME)
if zero_copy != detected_zero_copy:
self.fail('ZeroCopy mismatch, expected %s got %s' %
(zero_copy, detected_zero_copy))
break
else:
if expect_no_overlay:
return
self.fail(
'Events with name %s were not found' % _SWAP_CHAIN_PRESENT_EVENT_NAME)
def _EvaluateSuccess_CheckOverlayMode(self, category, event_iterator,
other_args):
"""Verifies video frames are promoted to overlays when supported."""
os_name = self.browser.platform.GetOSName()
assert os_name and os_name.lower() == 'win'
overlay_bot_config = self.GetOverlayBotConfig()
if overlay_bot_config is None:
self.fail('Overlay bot config can not be determined')
assert overlay_bot_config.get('direct_composition', False)
expected_presentation_mode = _SWAP_CHAIN_PRESENTATION_MODE_COMPOSED
if overlay_bot_config.get('supports_overlays', False):
if overlay_bot_config['nv12_overlay_support'] != 'SOFTWARE':
expected_presentation_mode = _SWAP_CHAIN_PRESENTATION_MODE_OVERLAY
other_args = other_args if other_args is not None else {}
expect_no_overlay = other_args.get('no_overlay', False)
presentation_mode_history = []
for event in event_iterator:
if event.category != category:
continue
if event.name != _GET_STATISTICS_EVENT_NAME:
continue
if expect_no_overlay:
self.fail('Expected no overlay got %s' % _GET_STATISTICS_EVENT_NAME)
detected_presentation_mode = event.args.get('CompositionMode', None)
if detected_presentation_mode is None:
self.fail('PresentationMode is missing from event %s' %
_GET_STATISTICS_EVENT_NAME)
presentation_mode_history.append(detected_presentation_mode)
if expect_no_overlay:
return
valid_entry_found = False
for index in range(len(presentation_mode_history)):
mode = presentation_mode_history[index]
if (mode == _SWAP_CHAIN_PRESENTATION_MODE_NONE
or mode == _SWAP_CHAIN_GET_FRAME_STATISTICS_MEDIA_FAILED):
# Be more tolerant to avoid test flakiness
continue
if mode != expected_presentation_mode:
if index >= len(presentation_mode_history) // 2:
# Be more tolerant for the first half frames in non-overlay mode.
self.fail('SwapChain presentation mode mismatch, expected %s got %s' %
(TraceIntegrationTest._SwapChainPresentationModeToStr(
expected_presentation_mode),
TraceIntegrationTest._SwapChainPresentationModeListToStr(
presentation_mode_history)))
valid_entry_found = True
if not valid_entry_found:
self.fail(
'No valid frame statistics being collected: %s' % TraceIntegrationTest
._SwapChainPresentationModeListToStr(presentation_mode_history))
def _EvaluateSuccess_CheckSwapChainPath(self, category, event_iterator,
other_args):
"""Verified that swap chains were used for low latency canvas."""
del other_args # Unused in this particular success evaluation.
os_name = self.browser.platform.GetOSName()
assert os_name and os_name.lower() == 'win'
overlay_bot_config = self.GetOverlayBotConfig()
if overlay_bot_config is None:
self.fail('Overlay bot config can not be determined')
assert overlay_bot_config.get('direct_composition', False)
# Verify expectations through captured trace events.
for event in event_iterator:
if event.category != category:
continue
if event.name != _PRESENT_TO_SWAP_CHAIN_EVENT_NAME:
continue
presentation_mode = event.args.get('image_type', None)
if presentation_mode == 'swap chain':
break
else:
self.fail(
'Events with name %s were not found' % _SWAP_CHAIN_PRESENT_EVENT_NAME)
@classmethod
def ExpectationsFiles(cls):
return [
os.path.join(
os.path.dirname(os.path.abspath(__file__)), 'test_expectations',
'trace_test_expectations.txt')
]
def load_tests(loader, tests, pattern):
del loader, tests, pattern # Unused.
return gpu_integration_test.LoadAllTestsInModule(sys.modules[__name__])