3 # Copyright (c) 2019 Collabora Ltd
4 # Copyright © 2019-2020 Valve Corporation.
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:
13 # The above copyright notice and this permission notice shall be included
14 # in all copies or substantial portions of the Software.
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.
24 # SPDX-License-Identifier: MIT
30 from pathlib
import Path
31 from traceutil
import trace_type_from_filename
, TraceType
33 def log(severity
, msg
, end
='\n'):
34 print("[dump_trace_images] %s: %s" % (severity
, msg
), flush
=True, end
=end
)
37 print(msg
, flush
=True)
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() + \
43 log_path
.parent
.mkdir(parents
=True, exist_ok
=True)
44 with log_path
.open(mode
='wb') as log
:
48 logoutput
.decode(errors
='replace') +
49 "[dump_traces_images] Process failed with error code: %d" % ret
.returncode
)
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()):
56 if len(s
) >= 1 and s
[0].isnumeric():
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()):
69 c
= s
[2].split(None, 1)
70 if len(c
) >= 1 and c
[0].isnumeric():
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
) + "-"
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
)
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
]
91 log_path
= Path(outputdir
) / (trace_path
.name
+ ".log")
92 run_logged_command(cmd
, None, log_path
)
94 def dump_with_gfxreconstruct(trace_path
, calls
, device_name
):
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
) + "-"
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
)
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
)
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
:
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"]
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
)
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
)
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
)
151 raise RuntimeError("Unknown tracefile extension")
154 except Exception as e
:
156 log("Debug", "=== Failure log start ===")
158 log("Debug", "=== Failure log end ===")
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)")
169 args
= parser
.parse_args()
170 if args
.calls
is not None:
171 args
.calls
= args
.calls
.split(",")
175 success
= dump_from_trace(Path(args
.tracepath
), args
.calls
, args
.device_name
)
177 sys
.exit(0 if success
else 1)
179 if __name__
== "__main__":