nir/scheduler: Add an option to specify what stages share memory for I/O
[mesa.git] / .gitlab-ci / tracie / dump_trace_images.py
1 #!/usr/bin/python3
2
3 # Copyright (c) 2019 Collabora Ltd
4 # Copyright © 2019-2020 Valve Corporation.
5 #
6 # Permission is hereby granted, free of charge, to any person obtaining a
7 # copy of this software and associated documentation files (the "Software"),
8 # to deal in the Software without restriction, including without limitation
9 # the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 # and/or sell copies of the Software, and to permit persons to whom the
11 # Software is furnished to do so, subject to the following conditions:
12 #
13 # The above copyright notice and this permission notice shall be included
14 # in all copies or substantial portions of the Software.
15 #
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19 # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 # OTHER DEALINGS IN THE SOFTWARE.
23 #
24 # SPDX-License-Identifier: MIT
25
26 import argparse
27 import os
28 import sys
29 import subprocess
30 from pathlib import Path
31 from traceutil import trace_type_from_filename, TraceType
32
33 def log(severity, msg, end='\n'):
34 print("[dump_trace_images] %s: %s" % (severity, msg), flush=True, end=end)
35
36 def log_result(msg):
37 print(msg, flush=True)
38
39 def run_logged_command(cmd, env, log_path):
40 ret = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env)
41 logoutput = ("[dump_trace_images] Running: %s\n" % " ".join(cmd)).encode() + \
42 ret.stdout
43 log_path.parent.mkdir(parents=True, exist_ok=True)
44 with log_path.open(mode='wb') as log:
45 log.write(logoutput)
46 if ret.returncode:
47 raise RuntimeError(
48 logoutput.decode(errors='replace') +
49 "[dump_traces_images] Process failed with error code: %d" % ret.returncode)
50
51 def get_last_apitrace_frame_call(cmd_wrapper, trace_path):
52 cmd = cmd_wrapper + ["apitrace", "dump", "--calls=frame", str(trace_path)]
53 ret = subprocess.run(cmd, stdout=subprocess.PIPE)
54 for l in reversed(ret.stdout.decode(errors='replace').splitlines()):
55 s = l.split(None, 1)
56 if len(s) >= 1 and s[0].isnumeric():
57 return int(s[0])
58 return -1
59
60 def get_last_gfxreconstruct_frame_call(trace_path):
61 # FIXME: It would be great to have another way to get the amount of
62 # traces which wouldn't imply replaying the whole trace:
63 # https://github.com/LunarG/gfxreconstruct/issues/329
64 cmd = ["gfxrecon-replay", str(trace_path)]
65 ret = subprocess.run(cmd, stdout=subprocess.PIPE)
66 for l in reversed(ret.stdout.decode(errors='replace').splitlines()):
67 s = l.split(", ", 2)
68 if len(s) >= 3:
69 c = s[2].split(None, 1)
70 if len(c) >= 1 and c[0].isnumeric():
71 return int(c[0])
72 return -1
73
74 def dump_with_apitrace(retrace_cmd, trace_path, calls, device_name):
75 outputdir = str(trace_path.parent / "test" / device_name)
76 os.makedirs(outputdir, exist_ok=True)
77 outputprefix = str(Path(outputdir) / trace_path.name) + "-"
78 if len(calls) == 0:
79 calls = [str(get_last_apitrace_frame_call(retrace_cmd[:-1], trace_path))]
80 cmd = retrace_cmd + ["--headless",
81 "--snapshot=" + ','.join(calls),
82 "--snapshot-prefix=" + outputprefix, str(trace_path)]
83 log_path = Path(outputdir) / (trace_path.name + ".log")
84 run_logged_command(cmd, None, log_path)
85
86 def dump_with_renderdoc(trace_path, calls, device_name):
87 outputdir = str(trace_path.parent / "test" / device_name)
88 script_path = Path(os.path.dirname(os.path.abspath(__file__)))
89 cmd = [str(script_path / "renderdoc_dump_images.py"), str(trace_path), outputdir]
90 cmd.extend(calls)
91 log_path = Path(outputdir) / (trace_path.name + ".log")
92 run_logged_command(cmd, None, log_path)
93
94 def dump_with_gfxreconstruct(trace_path, calls, device_name):
95 from PIL import Image
96 outputdir_path = trace_path.parent / "test" / device_name
97 outputdir_path.mkdir(parents=True, exist_ok=True)
98 outputprefix = str(outputdir_path / trace_path.name) + "-"
99 if len(calls) == 0:
100 # FIXME: The VK_LAYER_LUNARG_screenshot numbers the calls from
101 # 0 to (total-num-calls - 1) while gfxreconstruct does it from
102 # 1 to total-num-calls:
103 # https://github.com/LunarG/gfxreconstruct/issues/284
104 calls = [str(get_last_gfxreconstruct_frame_call(trace_path) - 1)]
105 cmd = ["gfxrecon-replay", str(trace_path)]
106 log_path = outputdir_path / (trace_path.name + ".log")
107 env = os.environ.copy()
108 env["VK_INSTANCE_LAYERS"] = "VK_LAYER_LUNARG_screenshot"
109 env["VK_SCREENSHOT_FRAMES"] = ",".join(calls)
110 env["VK_SCREENSHOT_DIR"] = str(outputdir_path)
111 run_logged_command(cmd, env, log_path)
112 for c in calls:
113 ppm = str(outputdir_path / c) + ".ppm"
114 outputfile = outputprefix + c + ".png"
115 with log_path.open(mode='w') as log:
116 log.write("Writing: %s to %s" % (ppm, outputfile))
117 Image.open(ppm).save(outputfile)
118 os.remove(ppm)
119
120 def dump_with_testtrace(trace_path, calls, device_name):
121 from PIL import Image
122 outputdir_path = trace_path.parent / "test" / device_name
123 outputdir_path.mkdir(parents=True, exist_ok=True)
124 with trace_path.open() as f:
125 rgba = f.read()
126 color = [int(rgba[0:2], 16), int(rgba[2:4], 16),
127 int(rgba[4:6], 16), int(rgba[6:8], 16)]
128 if len(calls) == 0: calls = ["0"]
129 for c in calls:
130 outputfile = str(outputdir_path / trace_path.name) + "-" + c + ".png"
131 log_path = outputdir_path / (trace_path.name + ".log")
132 with log_path.open(mode='w') as log:
133 log.write("Writing RGBA: %s to %s" % (rgba, outputfile))
134 Image.frombytes('RGBA', (32, 32), bytes(color * 32 * 32)).save(outputfile)
135
136 def dump_from_trace(trace_path, calls, device_name):
137 log("Info", "Dumping trace %s" % trace_path, end='... ')
138 trace_type = trace_type_from_filename(trace_path.name)
139 try:
140 if trace_type == TraceType.APITRACE:
141 dump_with_apitrace(["eglretrace"], trace_path, calls, device_name)
142 elif trace_type == TraceType.APITRACE_DXGI:
143 dump_with_apitrace(["wine", "d3dretrace"], trace_path, calls, device_name)
144 elif trace_type == TraceType.RENDERDOC:
145 dump_with_renderdoc(trace_path, calls, device_name)
146 elif trace_type == TraceType.GFXRECONSTRUCT:
147 dump_with_gfxreconstruct(trace_path, calls, device_name)
148 elif trace_type == TraceType.TESTTRACE:
149 dump_with_testtrace(trace_path, calls, device_name)
150 else:
151 raise RuntimeError("Unknown tracefile extension")
152 log_result("OK")
153 return True
154 except Exception as e:
155 log_result("ERROR")
156 log("Debug", "=== Failure log start ===")
157 print(e)
158 log("Debug", "=== Failure log end ===")
159 return False
160
161 def main():
162 parser = argparse.ArgumentParser()
163 parser.add_argument('tracepath', help="trace to dump")
164 parser.add_argument('--device-name', required=True,
165 help="the name of the graphics device used to produce images")
166 parser.add_argument('--calls', required=False,
167 help="the call numbers from the trace to dump (default: last frame)")
168
169 args = parser.parse_args()
170 if args.calls is not None:
171 args.calls = args.calls.split(",")
172 else:
173 args.calls = []
174
175 success = dump_from_trace(Path(args.tracepath), args.calls, args.device_name)
176
177 sys.exit(0 if success else 1)
178
179 if __name__ == "__main__":
180 main()