2023-11-24 17:54:11 +01:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
2023-11-27 18:01:11 +01:00
|
|
|
import argparse
|
2023-11-27 18:31:41 +01:00
|
|
|
import concurrent.futures
|
2023-11-28 18:32:40 +01:00
|
|
|
import json
|
2023-11-27 18:01:11 +01:00
|
|
|
import os
|
|
|
|
import pathlib
|
2023-11-30 17:40:53 +01:00
|
|
|
import re
|
2023-11-27 18:01:11 +01:00
|
|
|
import shutil
|
|
|
|
import subprocess
|
|
|
|
|
|
|
|
ROOT = os.path.dirname(os.path.abspath(__file__))
|
2023-11-28 18:32:40 +01:00
|
|
|
imagecompare = f'{ROOT}/imagecompare'
|
2024-02-12 20:36:25 +01:00
|
|
|
convert = f'convert' # set path for imagemagick convert if need
|
2023-11-28 18:32:40 +01:00
|
|
|
WORKDIR = f'{ROOT}/work'
|
|
|
|
REPORT_ROOT = f'{ROOT}' # FIXME should be workdir?
|
2023-11-27 18:01:11 +01:00
|
|
|
|
2023-11-27 19:15:54 +01:00
|
|
|
# TODO rename to <map>_<description>_<issue(opt)>
|
2023-11-30 17:40:53 +01:00
|
|
|
saves = []
|
|
|
|
for (_, _, files) in os.walk(os.path.join(ROOT, 'save')):
|
|
|
|
for file in files:
|
|
|
|
saves.append(file.removesuffix('.sav').removeprefix('rendertest_'))
|
2023-11-24 17:54:11 +01:00
|
|
|
|
2023-11-28 18:32:40 +01:00
|
|
|
channels = {
|
2024-02-01 18:16:36 +01:00
|
|
|
# channel name: rt_debug_display_only value
|
2023-11-24 17:54:11 +01:00
|
|
|
'full': '',
|
|
|
|
'basecolor': 'basecolor',
|
|
|
|
'emissive': 'emissive',
|
|
|
|
'nshade': 'nshade',
|
|
|
|
'ngeom': 'ngeom',
|
|
|
|
'lighting': 'lighting',
|
|
|
|
'direct': 'direct',
|
2024-02-01 18:16:36 +01:00
|
|
|
'direct_diffuse': 'direct_diff',
|
|
|
|
'direct_specular': 'direct_spec',
|
|
|
|
'diffuse': 'diffuse',
|
|
|
|
'specular': 'specular',
|
|
|
|
'material': 'material',
|
2023-11-24 17:54:11 +01:00
|
|
|
'indirect': 'indirect',
|
|
|
|
'indirect_specular': 'indirect_spec',
|
|
|
|
'indirect_diffuse': 'indirect_diff',
|
|
|
|
}
|
|
|
|
|
2023-11-27 16:30:17 +01:00
|
|
|
def test_list(arg: str) -> [str]:
|
|
|
|
items = arg.split(',')
|
|
|
|
tests = []
|
|
|
|
for item in items:
|
2023-11-30 17:40:53 +01:00
|
|
|
r = re.compile(item)
|
|
|
|
for save in saves:
|
2023-12-04 19:50:54 +01:00
|
|
|
if r.search(save):
|
2023-11-30 17:40:53 +01:00
|
|
|
tests.append(save)
|
|
|
|
if not tests:
|
|
|
|
raise argparse.ArgumentTypeError(f'No tests match {item}. Available tests are: {saves}')
|
2023-11-27 16:30:17 +01:00
|
|
|
return tests
|
|
|
|
|
2023-11-24 17:54:11 +01:00
|
|
|
parser = argparse.ArgumentParser(description='Generate scripts and makefiles for rendertest')
|
2023-11-27 18:01:11 +01:00
|
|
|
#parser.add_argument('--script', '-s', type=argparse.FileType('w'), help='Console script for generating images')
|
2023-11-27 16:30:17 +01:00
|
|
|
parser.add_argument('--tests', '-t', type=test_list, default=saves, help='Run only these tests')
|
2023-11-27 18:01:11 +01:00
|
|
|
|
|
|
|
# TODO how to check that the dir is valid? presence of xash3d executable and valve dir?
|
|
|
|
parser.add_argument('--xash-dir', '-x', type=str, default=os.getcwd(), help='Path to xash3d-fwgs installation directory')
|
|
|
|
|
2023-12-04 19:50:54 +01:00
|
|
|
# TODO parse commands in type=.. function
|
2023-11-27 18:01:11 +01:00
|
|
|
parser.add_argument('command', type=str, default=None, help='Action to perform')
|
2023-11-24 17:54:11 +01:00
|
|
|
args = parser.parse_args()
|
|
|
|
|
2023-11-27 16:30:17 +01:00
|
|
|
def make_script(file, tests: [str]):
|
2023-11-24 17:54:11 +01:00
|
|
|
header = '''sv_cheats 1
|
|
|
|
developer 0
|
|
|
|
m_ignore 1
|
|
|
|
cl_showfps 0
|
2023-12-05 16:57:02 +01:00
|
|
|
scr_conspeed 1000000
|
2023-11-24 17:54:11 +01:00
|
|
|
con_notifytime 0
|
|
|
|
hud_draw 0
|
|
|
|
r_speeds 0
|
|
|
|
rt_debug_fixed_random_seed 31337
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
2023-11-27 18:01:11 +01:00
|
|
|
print(f'Generating script {file.name}')
|
2023-11-24 17:54:11 +01:00
|
|
|
file.write(header)
|
|
|
|
|
2023-11-27 16:30:17 +01:00
|
|
|
for test in tests:
|
2024-02-01 18:45:42 +01:00
|
|
|
white_furnace = "whitefurnace" in test
|
2023-11-24 17:54:11 +01:00
|
|
|
screenshot_base = 'rendertest/'
|
2023-11-27 16:30:17 +01:00
|
|
|
file.write(f'load rendertest_{test}\n')
|
2024-02-01 18:45:42 +01:00
|
|
|
if white_furnace:
|
|
|
|
file.write(f'rt_debug_flags white_furnace\n')
|
2023-11-24 17:54:11 +01:00
|
|
|
file.write(f'wait 4; echo DONE WAIT4; playersonly; wait 11\n')
|
|
|
|
# for i in range(13):
|
|
|
|
# file.write(f'echo FRAME {i+4}; wait 1;\n')
|
2023-11-28 18:32:40 +01:00
|
|
|
for channel, display in channels.items():
|
|
|
|
file.write(f'rt_debug_display_only "{display}"; screenshot {screenshot_base}{test}_{channel}.tga; wait 1\n')
|
2023-11-24 17:54:11 +01:00
|
|
|
file.write('\n')
|
2024-02-01 18:45:42 +01:00
|
|
|
if white_furnace:
|
|
|
|
file.write(f'rt_debug_flags ""\n')
|
2023-11-24 17:54:11 +01:00
|
|
|
|
|
|
|
file.write('quit\n')
|
|
|
|
|
2023-11-27 18:01:11 +01:00
|
|
|
# if args.script:
|
|
|
|
# print(f'Generating script {args.script.name}')
|
|
|
|
# make_script(args.script, args.tests)
|
|
|
|
|
2023-11-27 18:31:41 +01:00
|
|
|
def mkdir_p(path: str):
|
|
|
|
pathlib.Path(path).mkdir(parents=True, exist_ok=True)
|
|
|
|
|
2023-11-27 18:01:11 +01:00
|
|
|
def compile():
|
|
|
|
subprocess.run(['make', 'imagecompare'], cwd=ROOT, check=True)
|
|
|
|
|
|
|
|
def copy_assets():
|
|
|
|
print('Copying assets')
|
2023-12-04 19:50:54 +01:00
|
|
|
shutil.copy2(src=f'{ROOT}/vulkan_debug.wad', dst=f'{args.xash_dir}/valve/')
|
2023-11-27 18:01:11 +01:00
|
|
|
shutil.copytree(src=f'{ROOT}/maps', dst=f'{args.xash_dir}/valve/maps/', dirs_exist_ok=True)
|
|
|
|
shutil.copytree(src=f'{ROOT}/save', dst=f'{args.xash_dir}/valve/save/', dirs_exist_ok=True)
|
2023-11-28 18:32:40 +01:00
|
|
|
with open(f'{args.xash_dir}/rendertest.script', 'w') as script:
|
|
|
|
make_script(script, args.tests)
|
2023-11-27 18:01:11 +01:00
|
|
|
|
|
|
|
def render():
|
|
|
|
print('Running xash3d')
|
2023-11-27 18:31:41 +01:00
|
|
|
mkdir_p(f'{args.xash_dir}/valve/rendertest')
|
2023-11-27 18:01:11 +01:00
|
|
|
env = os.environ.copy()
|
|
|
|
env['RADV_PERFTEST'] = 'rt'
|
|
|
|
env['LD_LIBRARY_PATH'] = '.'
|
|
|
|
subprocess.run([f'{args.xash_dir}/xash3d', '-ref', 'vk',
|
|
|
|
'-nowriteconfig', '-nosound', '-log',
|
2024-02-01 18:16:36 +01:00
|
|
|
#'-dev', '2', '-vkverboselogs',
|
2023-11-27 18:01:11 +01:00
|
|
|
'-width', '1280', '-height', '800',
|
|
|
|
'+exec', 'rendertest.script'],
|
|
|
|
env=env, check=True)
|
|
|
|
|
2023-11-28 18:32:40 +01:00
|
|
|
def compare_one(test: str, channel: str, image_base: str, image_gold: str, image_test: str, image_diff: str, image_flip: str):
|
|
|
|
result = subprocess.run([imagecompare, image_gold, image_test, image_diff], text=True, capture_output=True)
|
|
|
|
match result.returncode:
|
2023-11-27 18:31:41 +01:00
|
|
|
case 0:
|
|
|
|
pass
|
|
|
|
case 1:
|
|
|
|
raise Exception(f'FATAL imagecompare error: TBD')
|
|
|
|
case 2:
|
|
|
|
print(f'ERROR: {image_base} differ by more than threshold')
|
|
|
|
case _:
|
2023-11-28 18:32:40 +01:00
|
|
|
raise Exception(f'Unexpected imagecompare return code {result.returncode}')
|
|
|
|
ret = json.loads(result.stdout)
|
|
|
|
ret['test'] = test
|
|
|
|
ret['channel'] = channel
|
|
|
|
ret['image_gold'] = os.path.relpath(image_gold, REPORT_ROOT)
|
|
|
|
ret['image_test'] = os.path.relpath(image_test, REPORT_ROOT)
|
|
|
|
ret['image_diff'] = os.path.relpath(image_diff, REPORT_ROOT)
|
|
|
|
ret['image_flip'] = os.path.relpath(image_flip, REPORT_ROOT)
|
|
|
|
return ret
|
2023-11-27 18:31:41 +01:00
|
|
|
|
2023-11-27 18:01:11 +01:00
|
|
|
def compare():
|
2023-12-04 19:50:54 +01:00
|
|
|
mkdir_p(WORKDIR)
|
2023-11-27 18:01:11 +01:00
|
|
|
screenshot_base = f'{args.xash_dir}/valve/rendertest'
|
2023-11-27 18:31:41 +01:00
|
|
|
diffs = []
|
2024-02-12 15:00:30 +01:00
|
|
|
command_png()
|
|
|
|
print(f'Compare...')
|
2023-11-27 18:31:41 +01:00
|
|
|
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
|
|
for test in args.tests:
|
2023-11-28 18:32:40 +01:00
|
|
|
for channel, _ in channels.items():
|
|
|
|
image_base = f'{test}_{channel}'
|
2024-02-12 15:00:30 +01:00
|
|
|
#image_test = f'{screenshot_base}/{image_base}.tga'
|
|
|
|
image_test = f'{ROOT}/work/gold/{image_base}.png'
|
2023-11-27 18:31:41 +01:00
|
|
|
image_gold = f'{ROOT}/gold/{image_base}.png'
|
2023-11-28 18:32:40 +01:00
|
|
|
image_diff = f'{WORKDIR}/{image_base}_diff.png'
|
|
|
|
image_flip = f'{WORKDIR}/{image_base}_flip.gif'
|
2023-11-27 18:31:41 +01:00
|
|
|
|
2023-11-28 18:32:40 +01:00
|
|
|
diffs.append(executor.submit(compare_one, test, channel, image_base, image_gold, image_test, image_diff, image_flip))
|
2023-11-27 18:31:41 +01:00
|
|
|
|
2024-02-12 15:00:30 +01:00
|
|
|
# legacy
|
|
|
|
#executor.submit(subprocess.run, [convert,
|
|
|
|
# '(', image_gold, '-bordercolor', 'gold', '-border', '2x2', '-gravity', 'SouthWest', '-font', 'Impact', '-pointsize', '24', '-fill', 'gold', '-stroke', 'black', '-annotate', '0', 'GOLD', ')',
|
|
|
|
# '(', image_test, '-bordercolor', 'white', '-border', '2x2', '-fill', 'white', '-annotate', '0', 'TEST', ')',
|
|
|
|
# '-loop', '0', '-set', 'delay', '100', image_flip], check=True)
|
2023-11-28 18:32:40 +01:00
|
|
|
|
|
|
|
results = [diff.result() for diff in diffs]
|
|
|
|
# json.dump(results, open(f'{WORKDIR}/data.json', 'w'))
|
|
|
|
jsons = json.dumps(results)
|
|
|
|
with open(f'{WORKDIR}/data.js', 'w') as js:
|
|
|
|
js.write(f'"use strict";\nconst data = {jsons};\n')
|
2023-11-27 18:01:11 +01:00
|
|
|
|
|
|
|
def command_compare():
|
2023-11-28 18:32:40 +01:00
|
|
|
compile()
|
2023-11-27 18:01:11 +01:00
|
|
|
compare()
|
|
|
|
|
2023-11-27 18:31:41 +01:00
|
|
|
def command_png():
|
|
|
|
screenshot_base = f'{args.xash_dir}/valve/rendertest'
|
2023-11-28 18:32:40 +01:00
|
|
|
new_gold_base = f'{WORKDIR}/gold'
|
2023-11-27 18:31:41 +01:00
|
|
|
mkdir_p(new_gold_base)
|
|
|
|
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
|
|
for test in args.tests:
|
2023-11-28 18:32:40 +01:00
|
|
|
for channel, _ in channels.items():
|
|
|
|
image_base = f'{test}_{channel}'
|
2023-11-27 18:31:41 +01:00
|
|
|
image_test = f'{screenshot_base}/{image_base}.tga'
|
|
|
|
image_new_gold = f'{new_gold_base}/{image_base}.png'
|
|
|
|
|
2024-02-12 15:00:30 +01:00
|
|
|
print(f'convert to {image_new_gold}')
|
|
|
|
executor.submit(subprocess.run, [convert, "-auto-orient", image_test, image_new_gold], check=True)
|
2023-11-27 18:31:41 +01:00
|
|
|
|
2023-11-27 18:01:11 +01:00
|
|
|
def command_run():
|
|
|
|
compile()
|
|
|
|
copy_assets()
|
|
|
|
render()
|
|
|
|
compare()
|
|
|
|
|
2023-11-27 18:46:54 +01:00
|
|
|
def command_render():
|
|
|
|
compile()
|
|
|
|
copy_assets()
|
|
|
|
render()
|
|
|
|
|
2023-11-28 18:32:40 +01:00
|
|
|
# TODO dict
|
2023-11-27 18:01:11 +01:00
|
|
|
match args.command:
|
|
|
|
case 'run':
|
|
|
|
command_run()
|
|
|
|
case 'compare':
|
|
|
|
command_compare()
|
2023-11-27 18:46:54 +01:00
|
|
|
case 'render':
|
|
|
|
command_render()
|
2023-11-27 18:31:41 +01:00
|
|
|
case 'png':
|
|
|
|
command_png()
|
2023-11-27 18:01:11 +01:00
|
|
|
case _:
|
|
|
|
raise Exception(f'Unknown command {args.command}')
|
2023-11-27 16:30:17 +01:00
|
|
|
|
|
|
|
# TODO:
|
2023-11-27 18:46:54 +01:00
|
|
|
# - settings object to pass as an argument
|