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 cmd
= ["gfxrecon-info", str(trace_path
)]
62 ret
= subprocess
.run(cmd
, stdout
=subprocess
.PIPE
)
63 lines
= ret
.stdout
.decode(errors
='replace').splitlines()
65 c
= lines
[0].split(": ", 1)
66 if len(c
) >= 2 and c
[1].isnumeric():
70 def dump_with_apitrace(retrace_cmd
, trace_path
, calls
, device_name
):
71 outputdir
= str(trace_path
.parent
/ "test" / device_name
)
72 os
.makedirs(outputdir
, exist_ok
=True)
73 outputprefix
= str(Path(outputdir
) / trace_path
.name
) + "-"
75 calls
= [str(get_last_apitrace_frame_call(retrace_cmd
[:-1], trace_path
))]
76 cmd
= retrace_cmd
+ ["--headless",
77 "--snapshot=" + ','.join(calls
),
78 "--snapshot-prefix=" + outputprefix
, str(trace_path
)]
79 log_path
= Path(outputdir
) / (trace_path
.name
+ ".log")
80 run_logged_command(cmd
, None, log_path
)
82 def dump_with_renderdoc(trace_path
, calls
, device_name
):
83 outputdir
= str(trace_path
.parent
/ "test" / device_name
)
84 script_path
= Path(os
.path
.dirname(os
.path
.abspath(__file__
)))
85 cmd
= [str(script_path
/ "renderdoc_dump_images.py"), str(trace_path
), outputdir
]
87 log_path
= Path(outputdir
) / (trace_path
.name
+ ".log")
88 run_logged_command(cmd
, None, log_path
)
90 def dump_with_gfxreconstruct(trace_path
, calls
, device_name
):
92 outputdir_path
= trace_path
.parent
/ "test" / device_name
93 outputdir_path
.mkdir(parents
=True, exist_ok
=True)
94 outputprefix
= str(outputdir_path
/ trace_path
.name
) + "-"
96 # FIXME: The VK_LAYER_LUNARG_screenshot numbers the calls from
97 # 0 to (total-num-calls - 1) while gfxreconstruct does it from
98 # 1 to total-num-calls:
99 # https://github.com/LunarG/gfxreconstruct/issues/284
100 calls
= [str(get_last_gfxreconstruct_frame_call(trace_path
) - 1)]
101 cmd
= ["gfxrecon-replay", str(trace_path
)]
102 log_path
= outputdir_path
/ (trace_path
.name
+ ".log")
103 env
= os
.environ
.copy()
104 env
["VK_INSTANCE_LAYERS"] = "VK_LAYER_LUNARG_screenshot"
105 env
["VK_SCREENSHOT_FRAMES"] = ",".join(calls
)
106 env
["VK_SCREENSHOT_DIR"] = str(outputdir_path
)
107 run_logged_command(cmd
, env
, log_path
)
109 ppm
= str(outputdir_path
/ c
) + ".ppm"
110 outputfile
= outputprefix
+ c
+ ".png"
111 with log_path
.open(mode
='w') as log
:
112 log
.write("Writing: %s to %s" % (ppm
, outputfile
))
113 Image
.open(ppm
).save(outputfile
)
116 def dump_with_testtrace(trace_path
, calls
, device_name
):
117 from PIL
import Image
118 outputdir_path
= trace_path
.parent
/ "test" / device_name
119 outputdir_path
.mkdir(parents
=True, exist_ok
=True)
120 with trace_path
.open() as f
:
122 color
= [int(rgba
[0:2], 16), int(rgba
[2:4], 16),
123 int(rgba
[4:6], 16), int(rgba
[6:8], 16)]
124 if len(calls
) == 0: calls
= ["0"]
126 outputfile
= str(outputdir_path
/ trace_path
.name
) + "-" + c
+ ".png"
127 log_path
= outputdir_path
/ (trace_path
.name
+ ".log")
128 with log_path
.open(mode
='w') as log
:
129 log
.write("Writing RGBA: %s to %s" % (rgba
, outputfile
))
130 Image
.frombytes('RGBA', (32, 32), bytes(color
* 32 * 32)).save(outputfile
)
132 def dump_from_trace(trace_path
, calls
, device_name
):
133 log("Info", "Dumping trace %s" % trace_path
, end
='... ')
134 trace_type
= trace_type_from_filename(trace_path
.name
)
136 if trace_type
== TraceType
.APITRACE
:
137 dump_with_apitrace(["eglretrace"], trace_path
, calls
, device_name
)
138 elif trace_type
== TraceType
.APITRACE_DXGI
:
139 dump_with_apitrace(["wine", "d3dretrace"], trace_path
, calls
, device_name
)
140 elif trace_type
== TraceType
.RENDERDOC
:
141 dump_with_renderdoc(trace_path
, calls
, device_name
)
142 elif trace_type
== TraceType
.GFXRECONSTRUCT
:
143 dump_with_gfxreconstruct(trace_path
, calls
, device_name
)
144 elif trace_type
== TraceType
.TESTTRACE
:
145 dump_with_testtrace(trace_path
, calls
, device_name
)
147 raise RuntimeError("Unknown tracefile extension")
150 except Exception as e
:
152 log("Debug", "=== Failure log start ===")
154 log("Debug", "=== Failure log end ===")
158 parser
= argparse
.ArgumentParser()
159 parser
.add_argument('tracepath', help="trace to dump")
160 parser
.add_argument('--device-name', required
=True,
161 help="the name of the graphics device used to produce images")
162 parser
.add_argument('--calls', required
=False,
163 help="the call numbers from the trace to dump (default: last frame)")
165 args
= parser
.parse_args()
166 if args
.calls
is not None:
167 args
.calls
= args
.calls
.split(",")
171 success
= dump_from_trace(Path(args
.tracepath
), args
.calls
, args
.device_name
)
173 sys
.exit(0 if success
else 1)
175 if __name__
== "__main__":