Files
HLRTest/render/rendertest.py
Ivan Avdeev 8c3aa04318 update gold images for latest denoiser
NOTE that this gold is not fully correct itself. It has following
issues:
- indirect channel is incorrect
- missing indirect diffuse
- missing specular fresnel

See https://github.com/w23/xash3d-fwgs/issues/759

Committing it as a new gold for the sole reason of detecting changes
compared to current vulkan branch state. More correct gold will be
submitted when the issues above are fixed.

Also, changes rendertest script to use the new denoiser, and remove some
extra text messages things from the frame.
2025-02-06 14:05:01 -05:00

221 lines
6.7 KiB
Python
Executable File

#!/usr/bin/env python3
import argparse
import concurrent.futures
import json
import os
import pathlib
import re
import shutil
import subprocess
ROOT = os.path.dirname(os.path.abspath(__file__))
imagecompare = f'{ROOT}/imagecompare'
magick = f'magick' # set path for imagemagick if need
WORKDIR = f'{ROOT}/work'
REPORT_ROOT = f'{ROOT}' # FIXME should be workdir?
# TODO rename to <map>_<description>_<issue(opt)>
saves = []
for (_, _, files) in os.walk(os.path.join(ROOT, 'save')):
for file in files:
saves.append(file.removesuffix('.sav').removeprefix('rendertest_'))
channels = {
# channel name: rt_debug_display_only value
'full': '',
'basecolor': 'basecolor',
'emissive': 'emissive',
'nshade': 'nshade',
'ngeom': 'ngeom',
'lighting': 'lighting',
'direct': 'direct',
'direct_diffuse': 'direct_diff',
'direct_specular': 'direct_spec',
'diffuse': 'diffuse',
'specular': 'specular',
'material': 'material',
'indirect': 'indirect',
'indirect_specular': 'indirect_spec',
'indirect_diffuse': 'indirect_diff',
}
def test_list(arg: str) -> [str]:
items = arg.split(',')
tests = []
for item in items:
r = re.compile(item)
for save in saves:
if r.search(save):
tests.append(save)
if not tests:
raise argparse.ArgumentTypeError(f'No tests match {item}. Available tests are: {saves}')
return tests
parser = argparse.ArgumentParser(description='Generate scripts and makefiles for rendertest')
#parser.add_argument('--script', '-s', type=argparse.FileType('w'), help='Console script for generating images')
parser.add_argument('--tests', '-t', type=test_list, default=saves, help='Run only these tests')
# 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')
# TODO parse commands in type=.. function
parser.add_argument('command', type=str, default=None, help='Action to perform')
args = parser.parse_args()
def make_script(file, tests: [str]):
header = '''sv_cheats 1
developer 0
m_ignore 1
cl_showfps 0
scr_conspeed 1000000
con_notifytime -10
hud_draw 0
r_speeds 0
rt_debug_fixed_random_seed 31337
rt_denoise_gi_by_sh 1
rt_separated_reflection 0
rt_spatial_reconstruction 0
scr_drawversion 0
'''
print(f'Generating script {file.name}')
file.write(header)
for test in tests:
white_furnace = "whitefurnace" in test
screenshot_base = 'rendertest/'
file.write(f'load rendertest_{test}\n')
if white_furnace:
file.write(f'rt_debug_flags white_furnace\n')
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')
for channel, display in channels.items():
file.write(f'rt_debug_display_only "{display}"; screenshot {screenshot_base}{test}_{channel}.tga; wait 1\n')
file.write('\n')
if white_furnace:
file.write(f'rt_debug_flags ""\n')
file.write('quit\n')
# if args.script:
# print(f'Generating script {args.script.name}')
# make_script(args.script, args.tests)
def mkdir_p(path: str):
pathlib.Path(path).mkdir(parents=True, exist_ok=True)
def compile():
subprocess.run(['make', 'imagecompare'], cwd=ROOT, check=True)
def copy_assets():
print('Copying assets...')
shutil.copy2(src=f'{ROOT}/vulkan_debug.wad', dst=f'{args.xash_dir}/valve/')
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)
with open(f'{args.xash_dir}/rendertest.script', 'w') as script:
make_script(script, args.tests)
def render():
print('Running xash3d...')
mkdir_p(f'{args.xash_dir}/valve/rendertest')
env = os.environ.copy()
env['LD_LIBRARY_PATH'] = '.'
with open(f'{WORKDIR}/xash-stdout.log', 'wb') as stdout, open(f'{WORKDIR}/xash-stderr.log', 'wb') as stderr:
result = subprocess.run([f'{args.xash_dir}/xash3d', '-ref', 'vk',
'-nowriteconfig', '-nosound', '-log',
'-dev', '2',# '-vkverboselogs',
'-width', '1280', '-height', '800',
'+exec', 'rendertest.script'],
env=env, check=True, stdout=stdout, stderr=stderr)
def compare_one(test: str, channel: str, image_base: str, image_gold: str, image_test: str, image_diff: str):
result = subprocess.run([imagecompare, image_gold, image_test, image_diff], text=True, capture_output=True)
match result.returncode:
case 0:
pass
case 1:
raise Exception(f'FATAL imagecompare failed: stdout={result.stdout} stderr={result.stderr}')
case 2:
print(f'ERROR: {image_base} differ by more than threshold')
case _:
raise Exception(f'Unexpected imagecompare return code {result.returncode}: {result}')
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)
return ret
def compare():
mkdir_p(WORKDIR)
screenshot_base = f'{args.xash_dir}/valve/rendertest'
diffs = []
command_png()
print(f'Comparing...')
with concurrent.futures.ThreadPoolExecutor() as executor:
for test in args.tests:
for channel, _ in channels.items():
image_base = f'{test}_{channel}'
image_test = f'{ROOT}/work/gold/{image_base}.png'
image_gold = f'{ROOT}/gold/{image_base}.png'
image_diff = f'{WORKDIR}/{image_base}_diff.png'
diffs.append(executor.submit(compare_one, test, channel, image_base, image_gold, image_test, image_diff))
results = [diff.result() for diff in diffs]
json.dump(results, open(f'{WORKDIR}/test_results.json', 'w'))
jsons = json.dumps(results)
with open(f'{WORKDIR}/data.js', 'w') as js:
js.write(f'"use strict";\nconst data = {jsons};\n')
def command_compare():
compile()
compare()
def command_png():
print(f'Converting to png...')
screenshot_base = f'{args.xash_dir}/valve/rendertest'
new_gold_base = f'{WORKDIR}/gold'
mkdir_p(new_gold_base)
with concurrent.futures.ThreadPoolExecutor() as executor:
for test in args.tests:
for channel, _ in channels.items():
image_base = f'{test}_{channel}'
image_test = f'{screenshot_base}/{image_base}.tga'
image_new_gold = f'{new_gold_base}/{image_base}.png'
# TODO if verbose: print(f'convert to {image_new_gold}')
executor.submit(subprocess.run, [magick, image_test, '-auto-orient', image_new_gold], check=True)
def command_run():
compile()
copy_assets()
render()
compare()
def command_render():
compile()
copy_assets()
render()
# TODO dict
match args.command:
case 'run':
command_run()
case 'compare':
command_compare()
case 'render':
command_render()
case 'png':
command_png()
case _:
raise Exception(f'Unknown command {args.command}')
# TODO:
# - settings object to pass as an argument