177 lines
7.4 KiB
Python
177 lines
7.4 KiB
Python
#!/usr/bin/python3
|
|
|
|
# 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 argparse
|
|
import os
|
|
import sys
|
|
import subprocess
|
|
from pathlib import Path
|
|
from traceutil import trace_type_from_filename, TraceType
|
|
|
|
def log(severity, msg, end='\n'):
|
|
print("[dump_trace_images] %s: %s" % (severity, msg), flush=True, end=end)
|
|
|
|
def log_result(msg):
|
|
print(msg, flush=True)
|
|
|
|
def run_logged_command(cmd, env, log_path):
|
|
ret = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env)
|
|
logoutput = ("[dump_trace_images] Running: %s\n" % " ".join(cmd)).encode() + \
|
|
ret.stdout
|
|
log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
with log_path.open(mode='wb') as log:
|
|
log.write(logoutput)
|
|
if ret.returncode:
|
|
raise RuntimeError(
|
|
logoutput.decode(errors='replace') +
|
|
"[dump_traces_images] Process failed with error code: %d" % ret.returncode)
|
|
|
|
def get_last_apitrace_frame_call(cmd_wrapper, trace_path):
|
|
cmd = cmd_wrapper + ["apitrace", "dump", "--calls=frame", str(trace_path)]
|
|
ret = subprocess.run(cmd, stdout=subprocess.PIPE)
|
|
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):
|
|
cmd = ["gfxrecon-info", str(trace_path)]
|
|
ret = subprocess.run(cmd, stdout=subprocess.PIPE)
|
|
lines = ret.stdout.decode(errors='replace').splitlines()
|
|
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, calls, device_name):
|
|
outputdir = str(trace_path.parent / "test" / device_name)
|
|
os.makedirs(outputdir, exist_ok=True)
|
|
outputprefix = str(Path(outputdir) / trace_path.name) + "-"
|
|
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, str(trace_path)]
|
|
log_path = Path(outputdir) / (trace_path.name + ".log")
|
|
run_logged_command(cmd, None, log_path)
|
|
|
|
def dump_with_renderdoc(trace_path, calls, device_name):
|
|
outputdir = str(trace_path.parent / "test" / device_name)
|
|
script_path = Path(os.path.dirname(os.path.abspath(__file__)))
|
|
cmd = [str(script_path / "renderdoc_dump_images.py"), str(trace_path), outputdir]
|
|
cmd.extend(calls)
|
|
log_path = Path(outputdir) / (trace_path.name + ".log")
|
|
run_logged_command(cmd, None, log_path)
|
|
|
|
def dump_with_gfxreconstruct(trace_path, calls, device_name):
|
|
from PIL import Image
|
|
outputdir_path = trace_path.parent / "test" / device_name
|
|
outputdir_path.mkdir(parents=True, exist_ok=True)
|
|
outputprefix = str(outputdir_path / trace_path.name) + "-"
|
|
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)]
|
|
cmd = ["gfxrecon-replay", str(trace_path)]
|
|
log_path = outputdir_path / (trace_path.name + ".log")
|
|
env = os.environ.copy()
|
|
env["VK_INSTANCE_LAYERS"] = "VK_LAYER_LUNARG_screenshot"
|
|
env["VK_SCREENSHOT_FRAMES"] = ",".join(calls)
|
|
env["VK_SCREENSHOT_DIR"] = str(outputdir_path)
|
|
run_logged_command(cmd, env, log_path)
|
|
for c in calls:
|
|
ppm = str(outputdir_path / c) + ".ppm"
|
|
outputfile = outputprefix + c + ".png"
|
|
with log_path.open(mode='w') as log:
|
|
log.write("Writing: %s to %s" % (ppm, outputfile))
|
|
Image.open(ppm).save(outputfile)
|
|
os.remove(ppm)
|
|
|
|
def dump_with_testtrace(trace_path, calls, device_name):
|
|
from PIL import Image
|
|
outputdir_path = trace_path.parent / "test" / device_name
|
|
outputdir_path.mkdir(parents=True, exist_ok=True)
|
|
with trace_path.open() 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 = str(outputdir_path / trace_path.name) + "-" + c + ".png"
|
|
log_path = outputdir_path / (trace_path.name + ".log")
|
|
with log_path.open(mode='w') as log:
|
|
log.write("Writing RGBA: %s to %s" % (rgba, outputfile))
|
|
Image.frombytes('RGBA', (32, 32), bytes(color * 32 * 32)).save(outputfile)
|
|
|
|
def dump_from_trace(trace_path, calls, device_name):
|
|
log("Info", "Dumping trace %s" % trace_path, end='... ')
|
|
trace_type = trace_type_from_filename(trace_path.name)
|
|
try:
|
|
if trace_type == TraceType.APITRACE:
|
|
dump_with_apitrace(["eglretrace"], trace_path, calls, device_name)
|
|
elif trace_type == TraceType.APITRACE_DXGI:
|
|
dump_with_apitrace(["wine", "d3dretrace"], trace_path, calls, device_name)
|
|
elif trace_type == TraceType.RENDERDOC:
|
|
dump_with_renderdoc(trace_path, calls, device_name)
|
|
elif trace_type == TraceType.GFXRECONSTRUCT:
|
|
dump_with_gfxreconstruct(trace_path, calls, device_name)
|
|
elif trace_type == TraceType.TESTTRACE:
|
|
dump_with_testtrace(trace_path, calls, device_name)
|
|
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
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('tracepath', help="trace to dump")
|
|
parser.add_argument('--device-name', required=True,
|
|
help="the name of the graphics device used to produce images")
|
|
parser.add_argument('--calls', required=False,
|
|
help="the call numbers from the trace to dump (default: last frame)")
|
|
|
|
args = parser.parse_args()
|
|
if args.calls is not None:
|
|
args.calls = args.calls.split(",")
|
|
else:
|
|
args.calls = []
|
|
|
|
success = dump_from_trace(Path(args.tracepath), args.calls, args.device_name)
|
|
|
|
sys.exit(0 if success else 1)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|