summaryrefslogtreecommitdiff
path: root/framework
diff options
context:
space:
mode:
authorAndres Gomez <agomez@igalia.com>2020-09-24 21:47:13 +0300
committerAndres Gomez <agomez@igalia.com>2020-11-02 22:22:33 +0200
commit79f6e056269cba9c28103b2bf187129ee48b719c (patch)
tree12869c0749bce2d8f8499e76224958b1ab5ef21c /framework
parent4bbbb004f960ff54aecd5ee590355f5bd8586748 (diff)
framework/replay: split dump_trace_images in backends
This will allow writing new backends more easily and, additionally, move the testtrace backend out from the framework so it is only used for actual testing. v2: - Correct several typos in the apitrace backend. - Correct comments in the package file. - Added init check to the backends. - Deal with exceptions from the backends in compare_replay. - Deal with possible exceptions in query_traces_yaml. v3: - Do not default to "[]" for calls in the abstract class but do the default initialization in the instance code (Dylan). - Do not use "len()" to check for containers emptiness (Dylan). - Do not use yoda style comparisons (Dylan). - Use "format()" to improve text messages readability (Dylan). - Combine exceptions detection in compare_replay (Dylan). - Let AttributeError raise all the way up (Dylan). - Stop using filter and lambda (Dylan). - Use always "-1" in the gfxreconstruct backend when checking for the last frame call returns a negative value. v4: - Make DumpBackend.dump() an abstractmethod by moving its code to the dump_handler decorator, with the rest of the logging (Alexandros). Signed-off-by: Andres Gomez <agomez@igalia.com> Reviewed-by: Dylan Baker <dylan@pnwbakers.com> [v3] Part-of: <https://gitlab.freedesktop.org/mesa/piglit/-/merge_requests/353>
Diffstat (limited to 'framework')
-rw-r--r--framework/replay/README.md2
-rw-r--r--framework/replay/__init__.py2
-rw-r--r--framework/replay/backends/__init__.py117
-rw-r--r--framework/replay/backends/abstract.py140
-rw-r--r--framework/replay/backends/apitrace.py123
-rw-r--r--framework/replay/backends/gfxreconstruct.py120
-rw-r--r--framework/replay/backends/register.py33
-rw-r--r--framework/replay/backends/renderdoc.py73
-rwxr-xr-xframework/replay/backends/renderdoc/renderdoc_dump_images.py (renamed from framework/replay/renderdoc_dump_images.py)0
-rw-r--r--framework/replay/backends/testtrace.py78
-rw-r--r--framework/replay/compare_replay.py9
-rw-r--r--framework/replay/dump_trace_images.py191
-rw-r--r--framework/replay/programs/dump.py5
-rw-r--r--framework/replay/programs/query.py11
-rw-r--r--framework/replay/query_traces_yaml.py26
-rw-r--r--framework/replay/trace_utils.py74
16 files changed, 719 insertions, 285 deletions
diff --git a/framework/replay/README.md b/framework/replay/README.md
index 9028fb80a..6b2647b6b 100644
--- a/framework/replay/README.md
+++ b/framework/replay/README.md
@@ -131,7 +131,7 @@ Examples:
--yaml-file ./traces.yml \
traces \
--device-name gl-vmware-llvmpipe \
- --trace-types "apitrace,renderdoc"
+ --trace-extensions ".trace,.rdc"
--checksum
```
diff --git a/framework/replay/__init__.py b/framework/replay/__init__.py
index eea2d157a..9dea3fb2a 100644
--- a/framework/replay/__init__.py
+++ b/framework/replay/__init__.py
@@ -31,8 +31,6 @@
from .compare_replay import *
from .download_utils import *
-from .dump_trace_images import *
from .image_checksum import *
from .options import *
from .query_traces_yaml import *
-from .trace_utils import *
diff --git a/framework/replay/backends/__init__.py b/framework/replay/backends/__init__.py
new file mode 100644
index 000000000..8c2e5031f
--- /dev/null
+++ b/framework/replay/backends/__init__.py
@@ -0,0 +1,117 @@
+# coding=utf-8
+#
+# Copyright (c) 2014-2016, 2019 Intel Corporation
+# Copyright © 2019-2020 Valve Corporation.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+#
+# SPDX-License-Identifier: MIT
+
+"""Provides a module like interface for dump backends.
+
+This package provides an abstract interface for working with dump backends,
+which implement various functions as provided in the Registry class, and then
+provide a Registry instance as REGISTRY, which maps individual objects to
+objects that piglit expects to use. This module then provides a thin
+abstraction layer so that piglit is never aware of what backend it's using, it
+just asks for an object and receives one.
+
+Most consumers will want to import framework.replay.backends and work directly
+with the helper functions here. For some more detailed uses (test cases
+especially) the modules themselves may be used.
+
+When this module is loaded it will search through framework/replay/backends for
+python modules (those ending in .py), and attempt to import them. Then it will
+look for an attribute REGISTRY in those modules, and if it as a
+framework.replay.register.Registry instance, it will add the name of that
+module (sans .py) as a key, and the instance as a value to the DUMPBACKENDS
+dictionary. Each of the helper functions in this module uses that dictionary to
+find the function that a user actually wants.
+
+"""
+
+import os
+import importlib
+
+from os import path
+
+from .register import Registry
+
+__all__ = [
+ 'DUMPBACKENDS',
+ 'DumpBackendError',
+ 'DumpBackendNotImplementedError',
+ 'dump',
+]
+
+
+class DumpBackendError(Exception):
+ pass
+
+
+class DumpBackendNotImplementedError(NotImplementedError):
+ pass
+
+
+def _register():
+ """Register backends.
+
+ Walk through the list of backends and register them to a name in a
+ dictionary, so that they can be referenced from helper functions.
+
+ """
+ registry = {}
+
+ for module in os.listdir(path.dirname(path.abspath(__file__))):
+ module, extension = path.splitext(module)
+ if extension == '.py':
+ mod = importlib.import_module(
+ 'framework.replay.backends.{}'.format(module))
+ if hasattr(mod, 'REGISTRY') and isinstance(mod.REGISTRY, Registry):
+ registry[module] = mod.REGISTRY
+
+ return registry
+
+
+DUMPBACKENDS = _register()
+
+
+def dump(trace_path, output_dir=None, calls=None):
+ """Wrapper for dumping traces.
+
+ This function will attempt to determine how to dump the trace file (based
+ on file extension), and then pass the trace file path, output_dir and calls
+ into the appropriate instance, and then call dump in such instance.
+
+ """
+ name, extension = path.splitext(trace_path)
+
+ for dump_backend in DUMPBACKENDS.values():
+ if extension in dump_backend.extensions:
+ backend = dump_backend.backend
+
+ if backend is None:
+ raise DumpBackendNotImplementedError(
+ 'DumpBackend for "{}" is not implemented'.format(extension))
+
+ instance = backend(trace_path, output_dir, calls)
+ return instance.dump()
+
+ raise DumpBackendError(
+ 'No module supports file extensions "{}"'.format(extension))
diff --git a/framework/replay/backends/abstract.py b/framework/replay/backends/abstract.py
new file mode 100644
index 000000000..905549b8c
--- /dev/null
+++ b/framework/replay/backends/abstract.py
@@ -0,0 +1,140 @@
+# coding=utf-8
+#
+# Copyright (c) 2014, 2016, 2019 Intel Corporation
+# Copyright © 2020 Valve Corporation.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+#
+# SPDX-License-Identifier: MIT
+
+
+""" Base class for dump backends
+
+This module provides a base class for replayer dump backend modules.
+
+"""
+
+import abc
+import functools
+import subprocess
+
+from os import path
+
+from framework import core
+from framework.replay.options import OPTIONS
+
+
+class DumpBackend(metaclass=abc.ABCMeta):
+ """ Base class for dump backends
+
+ This class provides an basic ancestor for classes implementing dump
+ backends, providing a light public API. The goal of this API is to be "just
+ enough", not a generic writing solution. To that end it provides the
+ method, 'dump'. This method is designed to be just enough to write a
+ backend without needing format specific options.
+
+ """
+ def __init__(self, trace_path, output_dir=None, calls=None, **kwargs):
+ """ Generic constructor
+
+ This method takes keyword arguments that define options for the
+ backends. Options should be prefixed to identify which backends they
+ apply to. For example, an apitrace specific value should be passed as
+ apitrace_*, while a file gfxrecon value should be passed as gfxrecon_*)
+
+ Arguments:
+
+ trace_path -- the path to the trace from which we want to dump calls as
+ images.
+ output_dir -- the place to write the images to.
+ calls -- an array of the calls in the trace for which we want to
+ dump images.
+
+ """
+ self._trace_path = trace_path
+ self._output_dir = output_dir
+ self._calls = calls or []
+
+ if self._output_dir is None:
+ self._output_dir = path.join('trace', OPTIONS.device_name,
+ path.dirname(self._trace_path))
+
+
+ @staticmethod
+ def log(severity, msg, end='\n'):
+ print('[dump_trace_images] {}: {}'.format(severity, msg), flush=True,
+ end=end)
+
+
+ @staticmethod
+ def log_result(msg):
+ print(msg, flush=True)
+
+
+ @staticmethod
+ def _run_logged_command(cmd, env):
+ ret = subprocess.run(cmd, stdout=subprocess.PIPE, env=env)
+ logoutput = '[dump_trace_images] Running: {}\n'.format(
+ ' '.join(cmd)).encode() + ret.stdout
+ print(logoutput.decode(errors='replace'))
+ if ret.returncode:
+ raise RuntimeError(
+ '[dump_traces_images] Process failed with error code: {}'.format(
+ ret.returncode))
+
+
+ @abc.abstractmethod
+ def _get_last_frame_call(self):
+ """Get the number of the last frame call from the trace"""
+
+
+ @abc.abstractmethod
+ def dump(self):
+ """ Dump the calls to images from the trace
+
+ This method actually dumps the calls from a trace.
+
+ """
+
+
+def dump_handler(func):
+ """ Decorator function for handling trace dumps.
+
+ This will handle exceptions and log the result.
+
+ """
+
+ @functools.wraps(func)
+ def _inner(self, *args, **kwargs):
+ try:
+ DumpBackend.log('Info',
+ 'Dumping trace {}'.format(self._trace_path),
+ end='...\n')
+ core.check_dir(self._output_dir)
+ func(self, *args, **kwargs)
+ DumpBackend.log_result('OK')
+ return True
+ except Exception as e:
+ DumpBackend.log_result('ERROR')
+ DumpBackend.log('Debug', '=== Failure log start ===')
+ print(e)
+ DumpBackend.log('Debug', '=== Failure log end ===')
+ return False
+
+ return _inner
diff --git a/framework/replay/backends/apitrace.py b/framework/replay/backends/apitrace.py
new file mode 100644
index 000000000..692badeb8
--- /dev/null
+++ b/framework/replay/backends/apitrace.py
@@ -0,0 +1,123 @@
+# coding=utf-8
+#
+# Copyright (c) 2014, 2016-2017, 2019-2020 Intel Corporation
+# Copyright (c) 2019 Collabora Ltd
+# Copyright © 2019-2020 Valve Corporation.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+#
+# SPDX-License-Identifier: MIT
+
+
+""" Module providing an apitrace dump backend for replayer """
+
+import os
+import subprocess
+
+from os import path
+
+from framework import core, exceptions
+from .abstract import DumpBackend, dump_handler
+from .register import Registry
+
+
+__all__ = [
+ 'REGISTRY',
+ 'APITraceBackend',
+]
+
+
+class APITraceBackend(DumpBackend):
+ """ replayer's apitrace dump backend
+
+ This backend uses apitrace for replaying its traces.
+
+ It supports OpenGL/ES traces, to be replayed with eglretrace, and DXGI
+ traces, to be replayed with d3dretrace on Wine.
+
+ However, the binary paths to the apitrace, eglretrace, d3dretrace and wine
+ binaries are configurable. Hence, using wine could be omitted, for example.
+
+ """
+
+ def __init__(self, trace_path, output_dir=None, calls=None, **kwargs):
+ super(APITraceBackend, self).__init__(trace_path, output_dir, calls,
+ **kwargs)
+ extension = path.splitext(self._trace_path)[1]
+
+ if extension == '.trace':
+ eglretrace_bin = core.get_option('PIGLIT_REPLAY_EGLRETRACE_BINARY',
+ ('replay', 'eglretrace_bin'),
+ default='eglretrace')
+ self._retrace_cmd = [eglretrace_bin]
+ elif extension == '.trace-dxgi':
+ wine_bin = core.get_option('PIGLIT_REPLAY_WINE_BINARY',
+ ('replay', 'wine_bin'),
+ default='wine')
+ wine_d3dretrace_bin = core.get_option(
+ 'PIGLIT_REPLAY_WINE_D3DRETRACE_BINARY',
+ ('replay', 'wine_d3dretrace_bin'),
+ default='d3dretrace')
+ self._retrace_cmd = [wine_bin, wine_d3dretrace_bin]
+ else:
+ raise exceptions.PiglitFatalError(
+ 'Invalid trace_path: "{}" tried to be dumped '
+ 'by the APITraceBackend.\n'.format(self._trace_path))
+
+
+ def _get_last_frame_call(self):
+ cmd_wrapper = self._retrace_cmd[:-1]
+ if cmd_wrapper:
+ apitrace_bin = core.get_option(
+ 'PIGLIT_REPLAY_WINE_APITRACE_BINARY',
+ ('replay', 'wine_apitrace_bin'),
+ default='apitrace')
+ else:
+ apitrace_bin = core.get_option('PIGLIT_REPLAY_APITRACE_BINARY',
+ ('replay', 'apitrace_bin'),
+ default='apitrace')
+ cmd = cmd_wrapper + [apitrace_bin,
+ 'dump', '--calls=frame', self._trace_path]
+ ret = subprocess.run(cmd, stdout=subprocess.PIPE)
+ logoutput = '[dump_trace_images] Running: {}\n'.format(
+ ' '.join(cmd)) + ret.stdout.decode(errors='replace')
+ print(logoutput)
+ for l in reversed(ret.stdout.decode(errors='replace').splitlines()):
+ s = l.split(None, 1)
+ if s and s[0].isnumeric():
+ return int(s[0])
+ return -1
+
+ @dump_handler
+ def dump(self):
+ outputprefix = '{}-'.format(path.join(self._output_dir,
+ path.basename(self._trace_path)))
+ if not self._calls:
+ self._calls = [str(self._get_last_frame_call())]
+ cmd = self._retrace_cmd + ['--headless',
+ '--snapshot=' + ','.join(self._calls),
+ '--snapshot-prefix=' + outputprefix,
+ self._trace_path]
+ self._run_logged_command(cmd, None)
+
+
+REGISTRY = Registry(
+ extensions=['.trace', '.trace-dxgi'],
+ backend=APITraceBackend,
+)
diff --git a/framework/replay/backends/gfxreconstruct.py b/framework/replay/backends/gfxreconstruct.py
new file mode 100644
index 000000000..07cb3b846
--- /dev/null
+++ b/framework/replay/backends/gfxreconstruct.py
@@ -0,0 +1,120 @@
+# coding=utf-8
+#
+# Copyright (c) 2014, 2016-2017, 2019-2020 Intel Corporation
+# Copyright (c) 2019 Collabora Ltd
+# Copyright © 2019-2020 Valve Corporation.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+#
+# SPDX-License-Identifier: MIT
+
+
+""" Module providing a GFXReconstruct dump backend for replayer """
+
+import os
+import subprocess
+
+from os import path
+
+from framework import core, exceptions
+from .abstract import DumpBackend, dump_handler
+from .register import Registry
+
+
+__all__ = [
+ 'REGISTRY',
+ 'GFXReconstructBackend',
+]
+
+
+class GFXReconstructBackend(DumpBackend):
+ """ replayer's GFXReconstruct dump backend
+
+ This backend uses GFXReconstruct for replaying its traces.
+
+ The path to the GFXReconstruct binary is configurable.
+
+ It also admits configuration for passing extra parameters to
+ GFXReconstruct.
+
+ """
+
+ def __init__(self, trace_path, output_dir=None, calls=None, **kwargs):
+ super(GFXReconstructBackend, self).__init__(trace_path, output_dir,
+ calls, **kwargs)
+ extension = path.splitext(self._trace_path)[1]
+
+ if extension != '.gfxr':
+ raise exceptions.PiglitFatalError(
+ 'Invalid trace_path: "{}" tried to be dumped '
+ 'by the GFXReconstructBackend.\n'.format(self._trace_path))
+
+ def _get_last_frame_call(self):
+ gfxrecon_info_bin = core.get_option(
+ 'PIGLIT_REPLAY_GFXRECON_INFO_BINARY',
+ ('replay', 'gfxrecon-info_bin'),
+ default='gfxrecon-info')
+ cmd = [gfxrecon_info_bin, self._trace_path]
+ ret = subprocess.run(cmd, stdout=subprocess.PIPE)
+ lines = ret.stdout.decode(errors='replace').splitlines()
+ print(ret.stdout.decode(errors='replace'))
+ if lines:
+ c = lines[0].split(': ', 1)
+ if len(c) > 1 and c[1].isnumeric():
+ return int(c[1])
+ return -1
+
+ @dump_handler
+ def dump(self):
+ from PIL import Image
+ outputprefix = path.join(self._output_dir,
+ path.basename(self._trace_path))
+ if not self._calls:
+ # FIXME: The VK_LAYER_LUNARG_screenshot numbers the calls from 0 to
+ # (total-num-calls - 1) while gfxreconstruct does it from 1 to
+ # total-num-calls:
+ # https://github.com/LunarG/gfxreconstruct/issues/284
+ self._calls = [str(max(-1, self._get_last_frame_call() - 1))]
+ gfxrecon_replay_bin = core.get_option(
+ 'PIGLIT_REPLAY_GFXRECON_REPLAY_BINARY',
+ ('replay', 'gfxrecon-replay_bin'),
+ default='gfxrecon-replay')
+ gfxrecon_replay_extra_args = core.get_option(
+ 'PIGLIT_REPLAY_GFXRECON_REPLAY_EXTRA_ARGS',
+ ('replay', 'gfxrecon-replay_extra_args'),
+ default='').split()
+ cmd = ([gfxrecon_replay_bin]
+ + gfxrecon_replay_extra_args + [self._trace_path])
+ env = os.environ.copy()
+ env['VK_INSTANCE_LAYERS'] = 'VK_LAYER_LUNARG_screenshot'
+ env['VK_SCREENSHOT_FRAMES'] = ','.join(self._calls)
+ env['VK_SCREENSHOT_DIR'] = self._output_dir
+ self._run_logged_command(cmd, env)
+ for c in self._calls:
+ ppm = '{}.ppm'.format(path.join(self._output_dir, c))
+ outputfile = '{}-{}.png'.format(outputprefix, c)
+ print('Writing: {} to {}'.format(ppm, outputfile))
+ Image.open(ppm).save(outputfile)
+ os.remove(ppm)
+
+
+REGISTRY = Registry(
+ extensions=['.gfxr'],
+ backend=GFXReconstructBackend,
+)
diff --git a/framework/replay/backends/register.py b/framework/replay/backends/register.py
new file mode 100644
index 000000000..e380cbd47
--- /dev/null
+++ b/framework/replay/backends/register.py
@@ -0,0 +1,33 @@
+# coding=utf-8
+#
+# Copyright (c) 2015-2016, 2019 Intel Corporation
+# Copyright © 2020 Valve Corporation.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+#
+# SPDX-License-Identifier: MIT
+
+"""An object for registering dump backends."""
+
+import collections
+
+Registry = collections.namedtuple(
+ 'Registry',
+ ['extensions', 'backend']
+)
diff --git a/framework/replay/backends/renderdoc.py b/framework/replay/backends/renderdoc.py
new file mode 100644
index 000000000..c26e4f8ea
--- /dev/null
+++ b/framework/replay/backends/renderdoc.py
@@ -0,0 +1,73 @@
+# coding=utf-8
+#
+# Copyright (c) 2014, 2016-2017, 2019-2020 Intel Corporation
+# Copyright (c) 2019 Collabora Ltd
+# Copyright © 2020 Valve Corporation.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+#
+# SPDX-License-Identifier: MIT
+
+
+""" Module providing a RenderDoc dump backend for replayer """
+
+from os import path
+
+from framework import exceptions
+from .abstract import DumpBackend, dump_handler
+from .register import Registry
+
+
+__all__ = [
+ 'REGISTRY',
+ 'RenderDocBackend',
+]
+
+
+class RenderDocBackend(DumpBackend):
+ """ replayer's RenderDoc dump backend
+
+ This backend uses RenderDoc for replaying its traces.
+
+ """
+ _get_last_frame_call = None # this silences the abstract-not-subclassed warning
+
+ def __init__(self, trace_path, output_dir=None, calls=None, **kwargs):
+ super(RenderDocBackend, self).__init__(trace_path, output_dir, calls,
+ **kwargs)
+ extension = path.splitext(self._trace_path)[1]
+
+ if extension != '.rdc':
+ raise exceptions.PiglitFatalError(
+ 'Invalid trace_path: "{}" tried to be dumped '
+ 'by the RenderDocBackend.\n'.format(self._trace_path))
+
+ @dump_handler
+ def dump(self):
+ script_path = path.dirname(path.abspath(__file__))
+ cmd = [path.join(script_path, 'renderdoc/renderdoc_dump_images.py'),
+ self._trace_path, self._output_dir]
+ cmd.extend(self._calls)
+ self._run_logged_command(cmd, None)
+
+
+REGISTRY = Registry(
+ extensions=['.rdc'],
+ backend=RenderDocBackend,
+)
diff --git a/framework/replay/renderdoc_dump_images.py b/framework/replay/backends/renderdoc/renderdoc_dump_images.py
index db0ada466..db0ada466 100755
--- a/framework/replay/renderdoc_dump_images.py
+++ b/framework/replay/backends/renderdoc/renderdoc_dump_images.py
diff --git a/framework/replay/backends/testtrace.py b/framework/replay/backends/testtrace.py
new file mode 100644
index 000000000..08f8b717e
--- /dev/null
+++ b/framework/replay/backends/testtrace.py
@@ -0,0 +1,78 @@
+# coding=utf-8
+#
+# Copyright (c) 2014, 2016-2017, 2019-2020 Intel Corporation
+# Copyright (c) 2019 Collabora Ltd
+# Copyright © 2019-2020 Valve Corporation.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+#
+# SPDX-License-Identifier: MIT
+
+
+""" Module providing a trace testing dump backend for replayer """
+
+import os
+import subprocess
+
+from os import path
+
+from framework import core
+from .abstract import DumpBackend, dump_handler
+from .register import Registry
+
+
+__all__ = [
+ 'REGISTRY',
+ 'TestTraceBackend',
+]
+
+
+class TestTraceBackend(DumpBackend):
+ """ replayer's dump backend for trace testing
+
+ This is a simple dump backend which reads a RGBA color from a text file and
+ dumps a square image with that color.
+
+ """
+ _get_last_frame_call = None # this silences the abstract-not-subclassed warning
+
+ def __init__(self, trace_path, output_dir=None, calls=[], **kwargs):
+ super(TestTraceBackend, self).__init__(trace_path, output_dir, calls,
+ **kwargs)
+ if len(self._calls) == 0: self._calls = ['0']
+
+ @dump_handler
+ def dump(self):
+ from PIL import Image
+ outputprefix = path.join(self._output_dir, path.basename(self._trace_path))
+ with open(self._trace_path) as f:
+ rgba = f.read()
+ color = [int(rgba[0:2], 16), int(rgba[2:4], 16),
+ int(rgba[4:6], 16), int(rgba[6:8], 16)]
+ for c in self._calls:
+ outputfile = outputprefix + '-' + c + '.png'
+ print('Writing RGBA: {} to {}'.format(rgba, outputfile))
+ Image.frombytes('RGBA', (32, 32),
+ bytes(color * 32 * 32)).save(outputfile)
+
+
+REGISTRY = Registry(
+ extensions=['.testtrace'],
+ backend=TestTraceBackend,
+)
diff --git a/framework/replay/compare_replay.py b/framework/replay/compare_replay.py
index e4f18cabf..84183ecfc 100644
--- a/framework/replay/compare_replay.py
+++ b/framework/replay/compare_replay.py
@@ -36,9 +36,9 @@ from os import path
from framework import core
from framework import status
+from framework.replay import backends
from framework.replay import query_traces_yaml as qty
from framework.replay.download_utils import ensure_file
-from framework.replay.dump_trace_images import dump_from_trace
from framework.replay.image_checksum import hexdigest_from_image
from framework.replay.options import OPTIONS
@@ -56,7 +56,12 @@ class Result(Enum):
def _replay(trace_path, results_path):
- success = dump_from_trace(trace_path, results_path, [])
+ try:
+ success = backends.dump(trace_path, results_path, [])
+ except (backends.DumpBackendNotImplementedError,
+ backends.DumpBackendError) as e:
+ print(e)
+ success = False
if not success:
print("[check_image] Trace {} couldn't be replayed. "
diff --git a/framework/replay/dump_trace_images.py b/framework/replay/dump_trace_images.py
deleted file mode 100644
index f07cfd8e2..000000000
--- a/framework/replay/dump_trace_images.py
+++ /dev/null
@@ -1,191 +0,0 @@
-# coding=utf-8
-#
-# Copyright (c) 2019 Collabora Ltd
-# Copyright © 2019-2020 Valve Corporation.
-#
-# Permission is hereby granted, free of charge, to any person obtaining a
-# copy of this software and associated documentation files (the "Software"),
-# to deal in the Software without restriction, including without limitation
-# the rights to use, copy, modify, merge, publish, distribute, sublicense,
-# and/or sell copies of the Software, and to permit persons to whom the
-# Software is furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included
-# in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
-# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
-# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
-# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-# OTHER DEALINGS IN THE SOFTWARE.
-#
-# SPDX-License-Identifier: MIT
-
-import os
-import subprocess
-
-from os import path
-
-from framework import core
-from framework.replay.options import OPTIONS
-from framework.replay.trace_utils import trace_type_from_filename, TraceType
-
-
-__all__ = ['dump_from_trace']
-
-
-def _log(severity, msg, end='\n'):
- print('[dump_trace_images] {}: {}'.format(severity, msg), flush=True,
- end=end)
-
-def _log_result(msg):
- print(msg, flush=True)
-
-def _run_logged_command(cmd, env):
- ret = subprocess.run(cmd, stdout=subprocess.PIPE, env=env)
- logoutput = '[dump_trace_images] Running: {}\n'.format(
- ' '.join(cmd)).encode() + ret.stdout
- print(logoutput.decode(errors='replace'))
- if ret.returncode:
- raise RuntimeError(
- '[dump_traces_images] Process failed with error code: {}'.format(
- ret.returncode))
-
-def _get_last_apitrace_frame_call(cmd_wrapper, trace_path):
- if len(cmd_wrapper) > 0:
- apitrace_bin = core.get_option('PIGLIT_REPLAY_WINE_APITRACE_BINARY',
- ('replay', 'wine_apitrace_bin'),
- default='apitrace')
- else:
- apitrace_bin = core.get_option('PIGLIT_REPLAY_APITRACE_BINARY',
- ('replay', 'apitrace_bin'),
- default='apitrace')
- cmd = cmd_wrapper + [apitrace_bin, 'dump', '--calls=frame', trace_path]
- ret = subprocess.run(cmd, stdout=subprocess.PIPE)
- logoutput = '[dump_trace_images] Running: {}\n'.format(
- ' '.join(cmd)) + ret.stdout.decode(errors='replace')
- print(logoutput)
- for l in reversed(ret.stdout.decode(errors='replace').splitlines()):
- s = l.split(None, 1)
- if len(s) >= 1 and s[0].isnumeric():
- return int(s[0])
- return -1
-
-def _get_last_gfxreconstruct_frame_call(trace_path):
- gfxrecon_info_bin = core.get_option('PIGLIT_REPLAY_GFXRECON_INFO_BINARY',
- ('replay', 'gfxrecon-info_bin'),
- default='gfxrecon-info')
- cmd = [gfxrecon_info_bin, trace_path]
- ret = subprocess.run(cmd, stdout=subprocess.PIPE)
- lines = ret.stdout.decode(errors='replace').splitlines()
- print(ret.stdout.decode(errors='replace'))
- if len(lines) >= 1:
- c = lines[0].split(': ', 1)
- if len(c) >= 2 and c[1].isnumeric():
- return int(c[1])
- return -1
-
-def _dump_with_apitrace(retrace_cmd, trace_path, output_dir, calls):
- outputprefix = path.join(output_dir, path.basename(trace_path)) + '-'
- if len(calls) == 0:
- calls = [str(_get_last_apitrace_frame_call(retrace_cmd[:-1],
- trace_path))]
- cmd = retrace_cmd + ['--headless',
- '--snapshot=' + ','.join(calls),
- '--snapshot-prefix=' + outputprefix,
- trace_path]
- _run_logged_command(cmd, None)
-
-def _dump_with_renderdoc(trace_path, output_dir, calls):
- script_path = path.dirname(path.abspath(__file__))
- cmd = [path.join(script_path, 'renderdoc_dump_images.py'),
- trace_path, output_dir]
- cmd.extend(calls)
- _run_logged_command(cmd, None)
-
-def _dump_with_gfxreconstruct(trace_path, output_dir, calls):
- from PIL import Image
- outputprefix = path.join(output_dir, path.basename(trace_path))
- if len(calls) == 0:
- # FIXME: The VK_LAYER_LUNARG_screenshot numbers the calls from
- # 0 to (total-num-calls - 1) while gfxreconstruct does it from
- # 1 to total-num-calls:
- # https://github.com/LunarG/gfxreconstruct/issues/284
- calls = [str(_get_last_gfxreconstruct_frame_call(trace_path) - 1)]
- gfxrecon_replay_bin = core.get_option(
- 'PIGLIT_REPLAY_GFXRECON_REPLAY_BINARY',
- ('replay', 'gfxrecon-replay_bin'),
- default='gfxrecon-replay')
- gfxrecon_replay_extra_args = core.get_option(
- 'PIGLIT_REPLAY_GFXRECON_REPLAY_EXTRA_ARGS',
- ('replay', 'gfxrecon-replay_extra_args'),
- default='').split()
- cmd = [gfxrecon_replay_bin] + gfxrecon_replay_extra_args + [trace_path]
- env = os.environ.copy()
- env['VK_INSTANCE_LAYERS'] = 'VK_LAYER_LUNARG_screenshot'
- env['VK_SCREENSHOT_FRAMES'] = ','.join(calls)
- env['VK_SCREENSHOT_DIR'] = output_dir
- _run_logged_command(cmd, env)
- for c in calls:
- ppm = path.join(output_dir, c) + '.ppm'
- outputfile = outputprefix + '-' + c + '.png'
- print('Writing: {} to {}'.format(ppm, outputfile))
- Image.open(ppm).save(outputfile)
- os.remove(ppm)
-
-def _dump_with_testtrace(trace_path, output_dir, calls):
- from PIL import Image
- outputprefix = path.join(output_dir, path.basename(trace_path))
- with open(trace_path) as f:
- rgba = f.read()
- color = [int(rgba[0:2], 16), int(rgba[2:4], 16),
- int(rgba[4:6], 16), int(rgba[6:8], 16)]
- if len(calls) == 0: calls = ['0']
- for c in calls:
- outputfile = outputprefix + '-' + c + '.png'
- print('Writing RGBA: {} to {}'.format(rgba, outputfile))
- Image.frombytes('RGBA', (32, 32),
- bytes(color * 32 * 32)).save(outputfile)
-
-def dump_from_trace(trace_path, output_dir=None, calls=[]):
- _log('Info', 'Dumping trace {}'.format(trace_path), end='...\n')
- if output_dir is None:
- output_dir = path.join('trace', OPTIONS.device_name,
- path.dirname(trace_path))
- core.check_dir(output_dir)
- trace_type = trace_type_from_filename(path.basename(trace_path))
- try:
- if trace_type == TraceType.APITRACE:
- eglretrace_bin = core.get_option('PIGLIT_REPLAY_EGLRETRACE_BINARY',
- ('replay', 'eglretrace_bin'),
- default='eglretrace')
- _dump_with_apitrace([eglretrace_bin],
- trace_path, output_dir, calls)
- elif trace_type == TraceType.APITRACE_DXGI:
- wine_bin = core.get_option('PIGLIT_REPLAY_WINE_BINARY',
- ('replay', 'wine_bin'),
- default='wine')
- wine_d3dretrace_bin = core.get_option(
- 'PIGLIT_REPLAY_WINE_D3DRETRACE_BINARY',
- ('replay', 'wine_d3dretrace_bin'),
- default='d3dretrace')
- _dump_with_apitrace([wine_bin, wine_d3dretrace_bin],
- trace_path, output_dir, calls)
- elif trace_type == TraceType.RENDERDOC:
- _dump_with_renderdoc(trace_path, output_dir, calls)
- elif trace_type == TraceType.GFXRECONSTRUCT:
- _dump_with_gfxreconstruct(trace_path, output_dir, calls)
- elif trace_type == TraceType.TESTTRACE:
- _dump_with_testtrace(trace_path, output_dir, calls)
- else:
- raise RuntimeError('Unknown tracefile extension')
- _log_result('OK')
- return True
- except Exception as e:
- _log_result('ERROR')
- _log('Debug', '=== Failure log start ===')
- print(e)
- _log('Debug', '=== Failure log end ===')
- return False
diff --git a/framework/replay/programs/dump.py b/framework/replay/programs/dump.py
index 7a52ea0ac..5fc8cecd8 100644
--- a/framework/replay/programs/dump.py
+++ b/framework/replay/programs/dump.py
@@ -25,7 +25,7 @@
import argparse
from framework import exceptions
-from framework.replay import dump_trace_images
+from framework.replay import backends
from framework.replay import options
from framework.programs import parsers as piglit_parsers
from . import parsers
@@ -43,8 +43,7 @@ def _dump_from_trace(args):
else:
calls = []
- return dump_trace_images.dump_from_trace(args.file_path, args.output,
- calls)
+ return backends.dump(args.file_path, args.output, calls)
@exceptions.handler
diff --git a/framework/replay/programs/query.py b/framework/replay/programs/query.py
index ba9b40585..977b8f5e8 100644
--- a/framework/replay/programs/query.py
+++ b/framework/replay/programs/query.py
@@ -45,7 +45,7 @@ def _traces(args):
y = qty.load_yaml(args.yaml_file)
t_list = qty.traces(y,
- trace_types=args.trace_types,
+ trace_extensions=args.trace_extensions,
device_name=args.device_name,
checksum=args.checksum)
@@ -97,12 +97,13 @@ def query(input_):
parents=[parsers.DEVICE],
help=('Outputs the trace files filtered by the optional arguments.'))
parser_traces.add_argument(
- '-t', '--trace-types',
+ '-t', '--trace-extensions',
required=False,
default=None,
- help=('a comma separated list of trace types to look for '
- 'in recursive dir walks. '
- 'If none are provide, all types are used by default'))
+ help=('a comma separated list of trace extensions to look for '
+ 'in recursive dir walks (e.g. ".trace,.rdc"). '
+ 'If none are provide, all supported extensions '
+ 'are used by default'))
parser_traces.add_argument(
'-c', '--checksum',
action='store_true',
diff --git a/framework/replay/query_traces_yaml.py b/framework/replay/query_traces_yaml.py
index 1fb6c94aa..e3a27bdad 100644
--- a/framework/replay/query_traces_yaml.py
+++ b/framework/replay/query_traces_yaml.py
@@ -25,8 +25,9 @@
import yaml
+from os import path
+
from framework import exceptions
-from framework.replay.trace_utils import trace_type_from_filename, trace_type_from_name
__all__ = ['download_url',
@@ -70,14 +71,25 @@ def download_url(y):
return None
-def traces(y, trace_types=None, device_name=None, checksum=False):
+def traces(y, trace_extensions=None, device_name=None, checksum=False):
+
+ def _trace_extension(trace_path):
+ name, extension = path.splitext(trace_path)
+
+ return extension
+
traces = y.get('traces', []) or []
- if trace_types is not None:
- split_trace_types = [trace_type_from_name(t) for t
- in trace_types.split(',')]
- traces = filter(lambda t: trace_type_from_filename(t['path'])
- in split_trace_types, traces)
+ if trace_extensions is not None:
+ extensions = trace_extensions.split(',')
+
+ def _filter_trace_extension(trace):
+ try:
+ return _trace_extension(trace['path']) in extensions
+ except KeyError:
+ return False
+
+ traces = [t for t in traces if _filter_trace_extension(t)]
if device_name is not None:
traces = [t for t in traces if device_name in trace_devices(t)]
diff --git a/framework/replay/trace_utils.py b/framework/replay/trace_utils.py
deleted file mode 100644
index b8836eaed..000000000
--- a/framework/replay/trace_utils.py
+++ /dev/null
@@ -1,74 +0,0 @@
-# coding=utf-8
-#
-# Copyright (c) 2019 Collabora Ltd
-# Copyright © 2019-2020 Valve Corporation.
-#
-# Permission is hereby granted, free of charge, to any person obtaining a
-# copy of this software and associated documentation files (the "Software"),
-# to deal in the Software without restriction, including without limitation
-# the rights to use, copy, modify, merge, publish, distribute, sublicense,
-# and/or sell copies of the Software, and to permit persons to whom the
-# Software is furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included
-# in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
-# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
-# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
-# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-# OTHER DEALINGS IN THE SOFTWARE.
-#
-# SPDX-License-Identifier: MIT
-
-
-from enum import Enum, auto
-
-
-__all__ = ['TraceType',
- 'all_trace_type_names',
- 'trace_type_from_name',
- 'trace_type_from_filename']
-
-
-class TraceType(Enum):
- UNKNOWN = auto()
- APITRACE = auto()
- APITRACE_DXGI = auto()
- RENDERDOC = auto()
- GFXRECONSTRUCT = auto()
- TESTTRACE = auto()
-
-_trace_type_info_map = {
- TraceType.APITRACE : ('apitrace', '.trace'),
- TraceType.APITRACE_DXGI : ('apitrace-dxgi', '.trace-dxgi'),
- TraceType.RENDERDOC : ('renderdoc', '.rdc'),
- TraceType.GFXRECONSTRUCT : ('gfxreconstruct', '.gfxr'),
- TraceType.TESTTRACE : ('testtrace', '.testtrace')
-}
-
-
-def all_trace_type_names():
- s = []
- for t,(name, ext) in _trace_type_info_map.items():
- if t != TraceType.UNKNOWN:
- s.append(name)
- return s
-
-
-def trace_type_from_name(tt_name):
- for t,(name, ext) in _trace_type_info_map.items():
- if tt_name == name:
- return t
-
- return TraceType.UNKNOWN
-
-
-def trace_type_from_filename(trace_file):
- for t,(name, ext) in _trace_type_info_map.items():
- if trace_file.endswith(ext):
- return t
-
- return TraceType.UNKNOWN