3 # Copyright (c) 2019 Collabora Ltd
4 # Copyright © 2019 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(trace_path
):
52 cmd
= ["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(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(trace_path
))]
80 cmd
= ["eglretrace", "--snapshot=" + ','.join(calls
),
81 "--snapshot-prefix=" + outputprefix
, str(trace_path
)]
82 log_path
= Path(outputdir
) / (trace_path
.name
+ ".log")
83 run_logged_command(cmd
, None, log_path
)
85 def dump_with_renderdoc(trace_path
, calls
, device_name
):
86 outputdir
= str(trace_path
.parent
/ "test" / device_name
)
87 script_path
= Path(os
.path
.dirname(os
.path
.abspath(__file__
)))
88 cmd
= [str(script_path
/ "renderdoc_dump_images.py"), str(trace_path
), outputdir
]
90 log_path
= Path(outputdir
) / (trace_path
.name
+ ".log")
91 run_logged_command(cmd
, None, log_path
)
93 def dump_with_gfxreconstruct(trace_path
, calls
, device_name
):
95 outputdir_path
= trace_path
.parent
/ "test" / device_name
96 outputdir_path
.mkdir(parents
=True, exist_ok
=True)
97 outputprefix
= str(outputdir_path
/ trace_path
.name
) + "-"
99 # FIXME: The VK_LAYER_LUNARG_screenshot numbers the calls from
100 # 0 to (total-num-calls - 1) while gfxreconstruct does it from
101 # 1 to total-num-calls:
102 # https://github.com/LunarG/gfxreconstruct/issues/284
103 calls
= [str(get_last_gfxreconstruct_frame_call(trace_path
) - 1)]
104 cmd
= ["gfxrecon-replay", str(trace_path
)]
105 log_path
= outputdir_path
/ (trace_path
.name
+ ".log")
106 env
= os
.environ
.copy()
107 env
["VK_INSTANCE_LAYERS"] = "VK_LAYER_LUNARG_screenshot"
108 env
["VK_SCREENSHOT_FRAMES"] = ",".join(calls
)
109 env
["VK_SCREENSHOT_DIR"] = str(outputdir_path
)
110 run_logged_command(cmd
, env
, log_path
)
112 ppm
= str(outputdir_path
/ c
) + ".ppm"
113 outputfile
= outputprefix
+ c
+ ".png"
114 with log_path
.open(mode
='w') as log
:
115 log
.write("Writing: %s to %s" % (ppm
, outputfile
))
116 Image
.open(ppm
).save(outputfile
)
119 def dump_with_testtrace(trace_path
, calls
, device_name
):
120 from PIL
import Image
121 outputdir_path
= trace_path
.parent
/ "test" / device_name
122 outputdir_path
.mkdir(parents
=True, exist_ok
=True)
123 with trace_path
.open() as f
:
125 color
= [int(rgba
[0:2], 16), int(rgba
[2:4], 16),
126 int(rgba
[4:6], 16), int(rgba
[6:8], 16)]
127 if len(calls
) == 0: calls
= ["0"]
129 outputfile
= str(outputdir_path
/ trace_path
.name
) + "-" + c
+ ".png"
130 log_path
= outputdir_path
/ (trace_path
.name
+ ".log")
131 with log_path
.open(mode
='w') as log
:
132 log
.write("Writing RGBA: %s to %s" % (rgba
, outputfile
))
133 Image
.frombytes('RGBA', (32, 32), bytes(color
* 32 * 32)).save(outputfile
)
135 def dump_from_trace(trace_path
, calls
, device_name
):
136 log("Info", "Dumping trace %s" % trace_path
, end
='... ')
137 trace_type
= trace_type_from_filename(trace_path
.name
)
139 if trace_type
== TraceType
.APITRACE
:
140 dump_with_apitrace(trace_path
, calls
, device_name
)
141 elif trace_type
== TraceType
.RENDERDOC
:
142 dump_with_renderdoc(trace_path
, calls
, device_name
)
143 elif trace_type
== TraceType
.GFXRECONSTRUCT
:
144 dump_with_gfxreconstruct(trace_path
, calls
, device_name
)
145 elif trace_type
== TraceType
.TESTTRACE
:
146 dump_with_testtrace(trace_path
, calls
, device_name
)
148 raise RuntimeError("Unknown tracefile extension")
151 except Exception as e
:
153 log("Debug", "=== Failure log start ===")
155 log("Debug", "=== Failure log end ===")
159 parser
= argparse
.ArgumentParser()
160 parser
.add_argument('tracepath', help="trace to dump")
161 parser
.add_argument('--device-name', required
=True,
162 help="the name of the graphics device used to produce images")
163 parser
.add_argument('--calls', required
=False,
164 help="the call numbers from the trace to dump (default: last frame)")
166 args
= parser
.parse_args()
167 if args
.calls
is not None:
168 args
.calls
= args
.calls
.split(",")
172 success
= dump_from_trace(Path(args
.tracepath
), args
.calls
, args
.device_name
)
174 sys
.exit(0 if success
else 1)
176 if __name__
== "__main__":