Files
d4r/tests/test_optiscaler_packaging.py
T
ayo d5c8be25c8 ASI add-on for existing OptiScaler installs
Add an optional D4R_INSTALL_MODE=asi release (d4r-<version>-asi.zip)
holding only d4r.asi and a companion d4r/ folder, to drop into an
existing OptiScaler plugin directory.

- Shim: export InitializeASI. It identifies OptiScaler by its PE export
  name to find the active INI, then launches d4r-setup.exe out of
  process (no GPU or network work under the loader lock). The .asi
  build loads its runtime and config from the companion d4r/ folder.

- tools/d4r_asi_setup.cpp: native helper. It waits for the game to
  exit, installs the D3D12 wrapper and the private d4r12core.dll beside
  the EXE, and merges the NGX route into the loader's INI (including
  GE-Proton's prefix-local INI) with backups. Unchanged launches do
  nothing. It skips Proton helpers and background utilities and runs
  setup for one plugin install at a time.

- scripts/private_d3d12_core.py: points vkd3d's d3d12.dll forwarder at
  d4r12core.dll without moving code addresses.

- install.sh runs from inside the ASI's d4r/ as well as the proxy
  release root. d4r-check.sh checks ASI installs (loader, plugin path,
  LoadAsiPlugins, HookOriginalNvngxOnly, Wine drive paths).

- Packaging, build script, docs, ASI_README.txt and tests.
2026-10-12 17:36:01 -04:00

265 lines
16 KiB
Python

"""Release NGX routing: real INI generation, full/clean ZIP staging, install diagnostics.
Package tests use disposable runtime files and stand-ins for GPU kernel compilation.
Actual OptiScaler/Proton loading additionally needs the D3D12 harness.
"""
import configparser
import hashlib
import itertools
import os
from pathlib import Path
import shutil
import subprocess
import tempfile
import unittest
import zipfile
ROOT = Path(__file__).resolve().parents[1]
SETTINGS = ROOT / 'packaging/optiscaler.settings'
GENERATOR = ROOT / 'scripts/configure_optiscaler.py'
def upstream_ini():
sections = {}
for line in SETTINGS.read_text().splitlines():
if not line or line.startswith('#'):
continue
path, _ = line.split('=', 1)
section, key = path.split('.', 1)
sections.setdefault(section, []).append(key)
# Keep NvngxPath independent of the settings file so removing the release
# override reproduces the original bug rather than removing the fixture key.
if 'NvngxPath' not in sections['Libraries']:
sections['Libraries'].append('NvngxPath')
return '; upstream comment\n' + ''.join(
f'[{section}]\n' + ''.join(f'{key}=auto\n' for key in keys)
for section, keys in sections.items()) + '[Menu]\nShowFps=true\n'
class OptiScalerConfigTests(unittest.TestCase):
def generate(self, directory, source):
source_path = directory / 'upstream.ini'
output = directory / 'OptiScaler.ini'
source_path.write_bytes(source.encode())
result = subprocess.run(['python3', str(GENERATOR), str(source_path), str(SETTINGS), str(output)],
capture_output=True, text=True)
return result, output
def assert_routing(self, data):
ini = configparser.ConfigParser()
ini.read_string(data.decode())
self.assertEqual(ini['Libraries']['NvngxPath'], r'd4r\nvngx.dll')
self.assertEqual(ini['Libraries']['OptiDllPath'], 'd4r')
self.assertEqual(ini['Upscalers']['Dx12Upscaler'], 'dlss')
self.assertEqual(ini['DLSS']['Enabled'], 'true')
self.assertEqual(ini['Hooks']['HookOriginalNvngxOnly'], 'true')
self.assertEqual(ini['Menu']['ShowFps'], 'true')
def test_generated_config_and_line_endings(self):
for newline in ('\n', '\r\n'):
with self.subTest(newline=repr(newline)), tempfile.TemporaryDirectory() as temporary:
source = upstream_ini().replace('\n', newline)
result, output = self.generate(Path(temporary), source)
self.assertEqual(result.returncode, 0, result.stderr)
data = output.read_bytes()
self.assert_routing(data)
self.assertTrue(data.startswith(b'; upstream comment' + newline.encode()))
self.assertEqual(data.count(b'\r\n'), data.count(b'\n') if newline == '\r\n' else 0)
self.assertEqual(source.count(newline), data.count(newline.encode()))
def test_missing_or_duplicate_override_fails_without_output(self):
for replacement in ('', 'NvngxPath=auto\nnvngxpath=d4r\\ngx\\_nvngx.dll\n'):
with self.subTest(replacement=replacement), tempfile.TemporaryDirectory() as temporary:
result, output = self.generate(Path(temporary), upstream_ini().replace('NvngxPath=auto\n', replacement))
self.assertNotEqual(result.returncode, 0)
self.assertIn('exactly one NvngxPath', result.stderr)
self.assertFalse(output.exists())
def test_missing_or_duplicate_libraries_section_fails(self):
for source in (upstream_ini().replace('[Libraries]', '[Other]'),
upstream_ini() + '[libraries]\nNvngxPath=auto\n'):
with tempfile.TemporaryDirectory() as temporary:
result, output = self.generate(Path(temporary), source)
self.assertNotEqual(result.returncode, 0)
self.assertIn('exactly one [Libraries]', result.stderr)
self.assertFalse(output.exists())
def test_full_and_clean_release_archives(self):
with tempfile.TemporaryDirectory() as temporary:
repo = Path(temporary) / 'repo'
inputs = Path(temporary) / 'inputs'
inputs.mkdir()
(repo / 'scripts').mkdir(parents=True)
for name in ('package_release.sh', 'configure_optiscaler.py', 'check_glibc_compat.sh',
'check_redistributable.py', 'private_d3d12_core.py'):
shutil.copy2(ROOT / 'scripts' / name, repo / 'scripts' / name)
shutil.copytree(ROOT / 'packaging', repo / 'packaging')
for name in ('LICENSE', 'NOTICE'):
shutil.copy2(ROOT / name, repo / name)
for component in ('zluda', 'vkd3d-proton'):
(repo / 'patches' / component).mkdir(parents=True)
(repo / 'patches' / component / 'fixture.patch').write_text('fixture\n')
# All shell staging, INI generation, ABI checks and archiving are real;
# replace only expensive GPU builds/manifests with fixture artifacts.
(repo / 'kernels/tools').mkdir(parents=True)
kernel_build = repo / 'kernels/build.sh'
kernel_build.write_text('#!/bin/sh\nmkdir -p "$2"\nprintf fixture > "$2/layer.hsaco"\n')
kernel_build.chmod(0o755)
(repo / 'kernels/tools/kernel_manifest.py').write_text(
'import pathlib, sys\npathlib.Path(sys.argv[1], "d4r-kernels.txt").write_text("layer 1234\\n")\n')
def fixture(path, data=b'fixture'):
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(data)
return path
# A real minimal ELF lets the package's existing GLIBC checker run.
elf = inputs / 'library.so'
subprocess.run(['cc', '-shared', '-x', 'c', '-o', str(elf), '-'],
input='int fixture(void) { return 0; }\n', text=True, check=True)
fixture(repo / 'build/d4r_nvngx.dll')
fixture(repo / 'build/d4r-setup.exe')
fixture(repo / 'build/wine-nvcuda/x86_64-unix/nvcuda.dll.so', elf.read_bytes())
fixture(inputs / 'zluda/libnvcuda.so', elf.read_bytes())
fixture(inputs / 'rocm/lib/libamdhip64.so.7', elf.read_bytes())
fixture(inputs / 'rocm/licenses/runtime.txt')
for name in ('d3d12.dll', 'd3d12core.dll'):
fixture(inputs / 'vkd3d' / name, b'MZ fixture d3d12core.dll\0 then d3d12core.dll\0' if name == 'd3d12.dll' else b'fixture')
fixture(inputs / 'opti/OptiScaler.dll')
fixture(inputs / 'opti/OptiScaler.ini', upstream_ini().replace('\n', '\r\n').encode())
dlss = fixture(inputs / 'nvngx_dlss.dll')
core = fixture(inputs / '_nvngx.dll')
installer = (ROOT / 'install.sh').read_text()
installer = '\n'.join('DLSS_SHA256=' + hashlib.sha256(dlss.read_bytes()).hexdigest()
if line.startswith('DLSS_SHA256=') else line
for line in installer.splitlines()) + '\n'
(repo / 'install.sh').write_text(installer)
license_path = fixture(inputs / 'license.txt')
for name in ('LICENSE-APACHE', 'LICENSE-MIT', 'ext/llvm-project/llvm/LICENSE.TXT', 'LICENSE', 'COPYING'):
fixture(inputs / 'source' / name)
# Prebuilt L texture names keep the full flow from calling an emitter.
names = ['rrlite_dec0_4x4']
for mv in ('mvhi', 'mvlo'):
for dynamic_range in ('hdr', 'ldr'):
names.append(f'rrlite_enc0_4x4_{mv}_{dynamic_range}')
names.extend(f'rrlite_post_{v}_{mv}_{dynamic_range}' for v in ('3_1', '3_2'))
for prefix in ('gfx1101', 'accuracy/gfx1101'):
for name in names:
fixture(inputs / 'tex' / prefix / (name + '.hsaco'))
fixture(inputs / 'tex' / prefix / 'd4r-accuracy.txt', b'1\n')
env = {k: v for k, v in os.environ.items() if not k.startswith('D4R_')}
env.update(D4R_SKIP_BUILD='1', D4R_GPU_ARCHS='gfx1101', SOURCE_DATE_EPOCH='1700000000',
D4R_OPTISCALER=str(inputs / 'opti'), D4R_DLSS_DLLS=str(dlss),
D4R_ZLUDA_DIR=str(inputs / 'zluda'), D4R_VKD3D_DIR=str(inputs / 'vkd3d'),
D4R_ROCM_RUNTIME=str(inputs / 'rocm'), D4R_OPTISCALER_LICENSE=str(license_path),
D4R_ZLUDA_SRC=str(inputs / 'source'), D4R_VKD3D_SRC=str(inputs / 'source'),
D4R_BUNDLE_DLSS=str(dlss), D4R_BUNDLE_NGX=str(core), D4R_BUNDLE_TEX=str(inputs / 'tex'))
version = (repo / 'packaging/VERSION').read_text().strip()
configs = []
# A compiled engine model: only d4r's shaders and marker may reach a zip.
for name in ('input_k.spv', 'output_k.spv', 'exposure_k0.spv', 'exposure_k1.spv', 'enc0.spv',
'direct_origins.bin', 'weights.bin', 'offsets.bin', 'lut.bin', 'hipnet.bin', 'model.json'):
fixture(inputs / 'engine' / name)
env['D4R_BUNDLE_ENGINE'] = str(inputs / 'engine')
for mode, bundled in itertools.product(('proxy', 'asi'), ('1', '0', 'installer')):
with self.subTest(mode=mode, bundled=bundled):
env['D4R_BUNDLE_NVIDIA'] = bundled
env['D4R_INSTALL_MODE'] = mode
if mode == 'asi':
env.pop('D4R_OPTISCALER', None)
result = subprocess.run(['bash', str(repo / 'scripts/package_release.sh'), str(repo / 'dist')],
env=env, text=True, capture_output=True)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
name = f'd4r-{version}' + ('-nonvidia' if bundled == '0' else '')
if mode == 'asi':
name += '-asi'
with zipfile.ZipFile(repo / 'dist' / (name + '.zip')) as archive:
self.assertIsNone(archive.testzip())
if mode == 'proxy':
data = archive.read('OptiScaler.ini')
self.assert_routing(data)
configs.append(data)
self.assertEqual(data, (repo / 'dist' / name / 'OptiScaler.ini').read_bytes())
else:
self.assertNotIn('OptiScaler.ini', archive.namelist())
self.assertNotIn('dxgi.dll', archive.namelist())
self.assertEqual(archive.read('d4r.asi'), archive.read('d4r/nvngx.dll'))
self.assertIn('d4r/d4r-setup.exe', archive.namelist())
self.assertEqual('d4r/install.sh' in archive.namelist(), bundled == 'installer')
self.assertFalse(any(n.endswith('.py') for n in archive.namelist()))
self.assertIn(b'ASI add-on', archive.read('d4r/README.txt'))
self.assertEqual({n.split('/')[0] for n in archive.namelist()}, {'d4r.asi', 'd4r'})
self.assertIn('d4r/d3d12.dll', archive.namelist())
self.assertIn('d4r/d4r12core.dll', archive.namelist())
self.assertNotIn('d4r/d3d12core.dll', archive.namelist())
self.assertIn('d4r/nvngx.dll', archive.namelist())
self.assertNotIn('nvngx.dll', archive.namelist())
self.assertNotIn('d4r/_nvngx.dll', archive.namelist())
self.assertEqual('d4r/ngx/_nvngx.dll' in archive.namelist(), bundled == '1')
self.assertEqual('d4r/nvngx_dlss.dll' in archive.namelist(), bundled == '1')
engine = sorted(n for n in archive.namelist() if n.startswith('d4r/engine/'))
self.assertEqual(engine, ['d4r/engine/k/' + n for n in (
'README.txt', 'direct_origins.bin', 'enc0.spv', 'exposure_k0.spv', 'exposure_k1.spv',
'input_k.spv', 'output_k.spv')])
self.assertEqual(archive.read('d4r/d4r-check.sh'), (ROOT / 'packaging/d4r-check.sh').read_bytes())
self.assertEqual(configs[0], configs[1])
class InstallCheckTests(unittest.TestCase):
def test_routing_separate_from_file_presence(self):
with tempfile.TemporaryDirectory() as temporary:
game = Path(temporary) / 'game with spaces'
for name in ('Game.exe', 'dxgi.dll', 'd3d12.dll', 'd3d12core.dll', 'd4r/nvngx.dll',
'd4r/nvcuda.dll', 'd4r/zluda/libcuda.so', 'd4r/d4r.ini', 'd4r/nvngx_dlss.dll',
'd4r/ngx/_nvngx.dll', 'd4r/rocm/lib/libamdhip64.so.7'):
path = game / name
path.parent.mkdir(parents=True, exist_ok=True)
path.touch()
for value, good in ((r'd4r\nvngx.dll', True), ('./d4r/nvngx.dll', True),
('Z:' + str(game / 'd4r/nvngx.dll').replace('/', '\\'), True),
('auto', False), ('d4r', False), (r'd4r\ngx\_nvngx.dll', False),
(r'C:\windows\system32\_nvngx.dll', False), ('', False)):
with self.subTest(value=value):
setting = f'NvngxPath={value}\n' if value else ''
(game / 'OptiScaler.ini').write_bytes(
('[Libraries]\nOptiDllPath=d4r\n' + setting).replace('\n', '\r\n').encode())
result = subprocess.run(['sh', str(ROOT / 'packaging/d4r-check.sh'), str(game)],
text=True, capture_output=True)
self.assertIn('ok d4r/nvngx.dll', result.stdout)
if good:
self.assertIn('OptiScaler NGX routing: NvngxPath=', result.stdout)
self.assertNotIn('CONFIG', result.stdout)
else:
self.assertIn('CONFIG', result.stdout)
self.assertIn('OptiDllPath alone can load system _nvngx.dll', result.stdout)
self.assertNotIn('checks passed', result.stdout)
self.assertNotEqual(result.returncode, 0)
def test_section_and_duplicate_keys(self):
for ini in ('[Other]\nNvngxPath=d4r\\nvngx.dll\n[Libraries]\nOptiDllPath=d4r\n',
'[Libraries]\nNvngxPath=d4r\\nvngx.dll\nnvngxpath=auto\n'):
with tempfile.TemporaryDirectory() as temporary:
game = Path(temporary)
(game / 'd4r').mkdir()
(game / 'OptiScaler.ini').write_text(ini)
result = subprocess.run(['sh', str(ROOT / 'packaging/d4r-check.sh'), temporary],
capture_output=True, text=True)
self.assertIn('CONFIG', result.stdout)
self.assertNotEqual(result.returncode, 0)
def test_correct_routing_does_not_hide_missing_shim(self):
with tempfile.TemporaryDirectory() as temporary:
game = Path(temporary)
(game / 'd4r').mkdir()
(game / 'OptiScaler.ini').write_text('[Libraries]\nNvngxPath=d4r\\nvngx.dll\n')
result = subprocess.run(['sh', str(ROOT / 'packaging/d4r-check.sh'), temporary],
capture_output=True, text=True)
self.assertIn('MISSING d4r/nvngx.dll', result.stdout)
self.assertIn('OptiScaler NGX routing: NvngxPath=', result.stdout)
self.assertNotIn('checks passed', result.stdout)
self.assertNotEqual(result.returncode, 0)
if __name__ == '__main__':
unittest.main()