| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433 |
- # Copyright (C) Microsoft Corporation. All rights reserved.
- # This file is distributed under the University of Illinois Open Source License. See LICENSE.TXT for details.
- import argparse
- import glob
- import shutil
- import os
- import subprocess
- import re
- try:
- import xml.etree.cElementTree as ET
- except ImportError:
- import xml.etree.ElementTree as ET
- # Comment namespace to make working with ElementTree easier:
- def ReadXmlString(text):
- global xmlheader
- text = text.replace('xmlns=', 'xmlns_commented=')
- xmlheader = text[:text.find('\n')+1]
- # TODO: read xml and return xml root
- return ET.fromstring(text)
- def WriteXmlString(root):
- global xmlheader
- text = ET.tostring(root, encoding="utf-8")
- return xmlheader + text.replace('xmlns_commented=', 'xmlns=')
- def DeepCopyElement(e):
- cpy = ET.Element(e.tag, e.attrib)
- cpy.text = e.text
- cpy.tail = e.tail
- for child in e:
- cpy.append(DeepCopyElement(child))
- return cpy
- sample_names = [
- "D3D1211On12",
- "D3D12Bundles",
- "D3D12DynamicIndexing",
- "D3D12ExecuteIndirect",
- "D3D12Fullscreen",
- # "D3D12HelloWorld",
- "D3D12HelloWorld\\src\\HelloBundles",
- "D3D12HelloWorld\\src\\HelloConstBuffers",
- "D3D12HelloWorld\\src\\HelloFrameBuffering",
- "D3D12HelloWorld\\src\\HelloTexture",
- "D3D12HelloWorld\\src\\HelloTriangle",
- "D3D12HelloWorld\\src\\HelloWindow",
- "D3D12HeterogeneousMultiadapter",
- # WarpAssert in ReadPointerOperand: pInfoSrc->getName().startswith("dx.var.x").
- # Fix tested - incorrect rendered result produced.
- # Worked around in sample shaders for now by moving to non-static locals.
- "D3D12Multithreading", # works and is visually impressive!
- "D3D12nBodyGravity", # expected empty cbuffer culling due to static members causes this to fail! FIXED!
- "D3D12PipelineStateCache", # Requires workaround for static globals
- "D3D12PredicationQueries",
- "D3D12ReservedResources",
- "D3D12Residency", # has problems on x86 due to mem limitations. The following seems to help:
- # change NumTextures to 1024 * 1, and add min with 1GB here:
- # UINT64 memoryToUse = UINT64 (float(min(memoryInfo.Budget, UINT64(1 << 30))) * 0.95f);
- # Still produces a bunch of these errors:
- # D3D12 ERROR: ID3D12CommandAllocator::Reset: A command allocator is being reset before previous executions associated with the allocator have completed. [ EXECUTION ERROR #552: COMMAND_ALLOCATOR_SYNC]
- # but at least it no longer crashes
- "D3D12SmallResources"]
- class Sample(object):
- def __init__(self, name):
- self.name = name
- self.preBuild = []
- self.postBuild = []
- samples = dict([(name, Sample(name)) for name in sample_names])
- def SetSampleActions():
- # Actions called with (name, args, dxil)
- # Actions for all projects:
- for name, sample in samples.items():
- sample.postBuild.append(ActionCopyD3DBins)
- sample.postBuild.append(ActionCopySDKLayers)
- sample.postBuild.append(ActionCopyWarp12)
- sample.postBuild.append(ActionCopyCompilerBins) # Do this for all projects for now
- # TODO: limit ActionCopyCompilerBins action to ones that do run-time compilation.
- # # Runtime HLSL compilation:
- # for name in [ "D3D1211On12",
- # "D3D12HelloWorld\\src\\HelloTriangle",
- # "D3D12ExecuteIndirect",
- # ]:
- # samples[name].postBuild.append(ActionCopyCompilerBins)
- def CopyFiles(source_path, dest_path, filenames, symbols=False):
- for filename in filenames:
- renamed = filename
- try:
- filename, renamed = filename.split(';')
- print('Copying %s from %s to %s' % (filename, source_path, dest_path))
- print('.. with new name: %s' % (renamed))
- except:
- print('Copying %s from %s to %s' % (filename, source_path, dest_path))
- try:
- shutil.copy2(os.path.join(source_path, filename), os.path.join(dest_path, renamed))
- except:
- print('Error copying "%s" from "%s" to "%s"' % (filename, source_path, dest_path))
- # sys.excepthook(*sys.exc_info())
- continue
- if symbols and (filename.endswith('.exe') or filename.endswith('.dll')):
- symbol_filename = filename[:-4] + '.pdb'
- try:
- shutil.copy2(os.path.join(source_path, symbol_filename), os.path.join(dest_path, symbol_filename))
- except:
- print('Error copying symbols "%s" from "%s" to "%s"' % (symbol_filename, source_path, dest_path))
- def CopyBins(args, name, dxil, filenames, symbols=False):
- samples_path = args.samples
- config = dxil and 'DxilDebug' or 'Debug'
- CopyFiles(args.bins, os.path.join(PathToSampleSrc(samples_path, name), 'bin', args.arch, config), filenames, symbols)
- def ActionCopyCompilerBins(args, name, dxil):
- if dxil:
- CopyBins(args, name, dxil, [
- 'dxcompiler.dll',
- 'dxil.dll',
- 'd3dcompiler_dxc_bridge.dll;d3dcompiler_47.dll', # Wrapper version that calls into dxcompiler.dll
- ], args.symbols)
- def ActionCopyD3DBins(args, name, dxil):
- if dxil:
- CopyBins(args, name, dxil, [
- 'd3d12.dll',
- ], args.symbols)
- def ActionCopySDKLayers(args, name, dxil):
- CopyBins(args, name, dxil, [
- 'D3D11_3SDKLayers.dll',
- 'D3D12SDKLayers.dll',
- 'DXGIDebug.dll',
- ], args.symbols)
- def ActionCopyWarp12(args, name, dxil):
- CopyBins(args, name, dxil, [
- 'd3d12warp.dll',
- ], args.symbols)
- def MakeD3D12WarpCopy(bin_path):
- # Copy d3d10warp.dll to d3d12warp.dll
- shutil.copy2(os.path.join(bin_path, 'd3d10warp.dll'), os.path.join(bin_path, 'd3d12warp.dll'))
- def PathSplitAll(p):
- s = filter(None, os.path.split(p))
- if len(s) > 1:
- return PathSplitAll(s[0]) + (s[1],)
- else:
- return (s[0],)
- def GetBinPath(args, name):
- return os.path.join(args.bins, name)
- def GetProjectBinFilePath(args, samples_path, sample_name, file_name):
- src = PathToSampleSrc(samples_path, sample_name)
- return os.path.join(src, "bin", args.arch, file_name)
- def ListRuntimeCompilePaths(args):
- return [os.path.join(args.bins, name) for name in [
- 'fxc.exe',
- 'dxc.exe',
- 'dxcompiler.dll',
- 'dxil.dll',
- 'd3dcompiler_47.dll',
- 'd3d12.dll',
- 'D3D11_3SDKLayers.dll',
- 'D3D12SDKLayers.dll',
- 'DXGIDebug.dll',
- 'd3d12warp.dll',
- ]]
- def CheckEnvironment(args):
- if not args.bins:
- print("The -bins argument is needed to populate tool binaries.")
- exit(1)
- if not os.path.exists(args.bins):
- print("The -bins argument '" + args.bins + "' does not exist.")
- exit(1)
- for fn in ListRuntimeCompilePaths(args):
- if not os.path.exists(fn):
- print("Expected file '" + fn + "' not found.")
- exit(1)
- if os.path.getmtime(GetBinPath(args, 'fxc.exe')) != os.path.getmtime(GetBinPath(args, 'dxc.exe')):
- print("fxc.exe should be a copy of dxc.exe.")
- print("Please copy " + GetBinPath(args, 'dxc.exe') + " " + GetBinPath(args, 'fxc.exe'))
- exit(1)
- try:
- msbuild_version = subprocess.check_output(["msbuild", "-nologo", "-ver"])
- print("msbuild version: " + msbuild_version)
- except Exception as E:
- print("Unable to get the version from msbuild: " + str(E))
- print("This command should be run from a Developer Command Prompt")
- exit(1)
- def SampleIsNested(name):
- return "\\src\\" in name
- def PathToSampleSrc(basePath, sampleName):
- if SampleIsNested(sampleName):
- return os.path.join(basePath, "Samples", "Desktop", sampleName)
- return os.path.join(basePath, "Samples", "Desktop", sampleName, "src")
- reConfig = r'(Debug|Release|DxilDebug|DxilRelease)\|(Win32|x64)'
- def AddProjectConfigs(root, args):
- rxConfig = re.compile(reConfig)
- changed = False
- for e in root.findall('.//WindowsTargetPlatformVersion'):
- if e.text == '10.0.10240.0':
- e.text = '10.0.10586.0'
- changed = True
- # Override fxc path for Dxil configs:
- for config in ['DxilDebug', 'DxilRelease']:
- for arch in ['Win32', 'x64']:
- if not root.find('''./PropertyGroup[@Condition="'$(Configuration)|$(Platform)'=='%s|%s'"]/FXCToolPath''' % (config, arch)):
- e = ET.Element('PropertyGroup', {'Condition': "'$(Configuration)|$(Platform)'=='%s|%s'" % (config, arch)})
- e.text = '\n '
- e.tail = '\n '
- root.insert(0, e)
- e = ET.SubElement(e, 'FXCToolPath')
- e.text = '$(DXC_BIN_PATH)' # args.bins
- e.tail = '\n '
- # Extend ProjectConfiguration for Win32 and Dxil configs
- ig = root.find('./ItemGroup[@Label="ProjectConfigurations"]') or []
- debug_config = release_config = None # ProjectConfiguration
- configs = {}
- for e in ig:
- try:
- m = rxConfig.match(e.attrib['Include'])
- if m:
- key = m.groups()
- configs[key] = e
- if m.group(1) == 'Debug':
- debug_config = key, e
- elif m.group(1) == 'Release':
- release_config = key, e
- except:
- continue
- parents = root.findall('''.//*[@Condition="'$(Configuration)|$(Platform)'=='%s|%s'"]/..''' % ('Debug', 'x64'))
- if not configs or not debug_config or not release_config:
- print('No ProjectConfigurations found')
- return False
- for arch in ['Win32', 'x64']:
- for config in ['Debug', 'DxilDebug', 'Release', 'DxilRelease']:
- if config in ('Debug', 'DxilDebug'):
- t_config = 'Debug', 'x64'
- else:
- t_config = 'Release', 'x64'
- config_condition = "'$(Configuration)|$(Platform)'=='%s|%s'" % t_config
- if not configs.get((config, arch), None):
- changed = True
- if config in ('Debug', 'DxilDebug'):
- e = DeepCopyElement(debug_config[1])
- else:
- e = DeepCopyElement(release_config[1])
- e.set('Include', '%s|%s' % (config, arch))
- e.find('./Configuration').text = config
- e.find('./Platform').text = arch
- ig.append(e)
- for parent in reversed(parents):
- for n, e in reversed(list(enumerate(parent))):
- try:
- cond = e.attrib['Condition']
- except KeyError:
- continue
- if cond == config_condition:
- e = DeepCopyElement(e)
- # Override DisableOptimizations for DxilDebug since this flag is problematic right now
- if e.tag == 'ItemDefinitionGroup' and config == 'DxilDebug':
- FxCompile = e.find('./FxCompile') or ET.SubElement(e, 'FxCompile')
- DisableOptimizations = FxCompile.find('./DisableOptimizations') or ET.SubElement(FxCompile, 'DisableOptimizations')
- DisableOptimizations.text = 'false'
- e.attrib['Condition'] = "'$(Configuration)|$(Platform)'=='%s|%s'" % (config, arch)
- parent.insert(n+1, e)
- return changed
- def AddSlnConfigs(sln_text):
- # sln: GlobalSection(SolutionConfigurationPlatforms)
- rxSlnConfig = re.compile(r'^\s+%s = \1\|\2$' % reConfig)
- # sln: GlobalSection(ProjectConfigurationPlatforms)
- rxActiveCfg = re.compile(r'^\s+\{[0-9A-Z-]+\}\.%s\.ActiveCfg = \1\|\2$' % reConfig)
- rxBuild = re.compile(r'^\s+\{[0-9A-Z-]+\}\.%s\.Build\.0 = \1\|\2$' % reConfig)
- sln_changed = []
- line_set = set(sln_text.splitlines())
- def add_line(lst, line):
- "Prevent duplicates from being added"
- if line not in line_set:
- lst.append(line)
- for line in sln_text.splitlines():
- if line == 'VisualStudioVersion = 14.0.23107.0':
- sln_changed.append('VisualStudioVersion = 14.0.25123.0')
- continue
- m = rxSlnConfig.match(line)
- if not m:
- m = rxActiveCfg.match(line)
- if not m:
- m = rxBuild.match(line)
- if m:
- sln_changed.append(line)
- config = m.group(1)
- if config in ('Debug', 'Release') and m.group(2) == 'x64':
- add_line(sln_changed, line.replace('x64', 'Win32'))
- add_line(sln_changed, line.replace(config, 'Dxil' + config))
- add_line(sln_changed, line.replace(config, 'Dxil' + config).replace('x64', 'Win32'))
- continue
- sln_changed.append(line)
- return '\n'.join(sln_changed)
- def PatchProjects(args):
- for name in sample_names + ['D3D12HelloWorld']:
- sample_path = PathToSampleSrc(args.samples, name)
- print("Patching " + name + " in " + sample_path)
- for proj_path in glob.glob(os.path.join(sample_path, "*.vcxproj")):
- # Consider looking for msbuild and other tool paths in registry, etg:
- # reg query HKLM\Software\Microsoft\MSBuild\ToolsVersions\14.0
- with open (proj_path, "r") as proj_file:
- proj_text = proj_file.read()
- root = ReadXmlString(proj_text)
- if AddProjectConfigs(root, args):
- changed_text = WriteXmlString(root)
- print("Patching the Windows SDK version in " + proj_path)
- with open (proj_path, "w") as proj_file:
- proj_file.write(changed_text)
- # Extend project configs in solution file
- for sln_path in glob.glob(os.path.join(sample_path, "*.sln")):
- with open (sln_path, "r") as sln_file:
- sln_text = sln_file.read()
- changed_text = AddSlnConfigs(sln_text)
- if changed_text != sln_text:
- print("Adding additional configurations to " + sln_path)
- with open (sln_path, "w") as sln_file:
- sln_file.write(changed_text)
- def BuildSample(samples_path, name, x86, dxil):
- sample_path = PathToSampleSrc(samples_path, name)
- if not SampleIsNested(name):
- print("Building " + name + " in " + sample_path)
- Platform = x86 and 'Win32' or 'x64'
- Configuration = dxil and 'DxilDebug' or 'Debug'
- subprocess.check_call(["msbuild", "-nologo",
- '/p:Configuration=%s;Platform=%s' % (Configuration, Platform),
- '/t:Rebuild'],
- cwd=sample_path)
- def BuildSamples(args, dxil):
- samples_path = args.samples
- rxSample = args.sample and re.compile(args.sample, re.I)
- buildHelloWorld = False
- os.environ['DXC_BIN_PATH'] = args.bins
- for sample_name in sample_names:
- if rxSample and not rxSample.match(sample_name):
- continue
- if SampleIsNested(sample_name):
- buildHelloWorld = True
- continue
- BuildSample(samples_path, sample_name, args.x86, dxil)
- if buildHelloWorld:
- # HelloWorld containts sub-samples that must be built from the solution file.
- BuildSample(samples_path, "D3D12HelloWorld", args.x86, dxil)
- def PatchSample(args, name, dxil, afterBuild):
- if args.sample and not re.match(args.sample, name, re.I):
- return
- try:
- sample = samples[name]
- except:
- print("Error: selected sample missing from sample map '" + name + "'.")
- return
- if afterBuild: actions = sample.postBuild
- else: actions = sample.preBuild
- for action in actions:
- action(args, name, dxil)
- def PatchSamplesAfterBuild(args, dxil):
- for sample_name in sample_names:
- PatchSample(args, sample_name, dxil, True)
- def PatchSamplesBeforeBuild(args, dxil):
- for sample_name in sample_names:
- PatchSample(args, sample_name, dxil, False)
- def RunSampleTests(args):
- CheckEnvironment(args)
- print("Building Debug config ...")
- BuildSamples(args, False)
-
- print("Building DxilDebug config ...")
- BuildSamples(args, True)
- print("Applying patch to post-build binaries to enable dxc ...")
- PatchSamplesAfterBuild(args, False)
- PatchSamplesAfterBuild(args, True)
- print("TODO - run Debug config vs. DxilDebug config and verify results are the same")
- if __name__ == "__main__":
- parser = argparse.ArgumentParser(description="Run D3D sample tests...")
- parser.add_argument("-x86", action="store_true", help="add x86 targets")
- parser.add_argument("-samples", help="path to root of D3D12 samples")
- parser.add_argument("-bins", help="path to dxcompiler.dll and related binaries")
- parser.add_argument("-sample", help="choose a single sample to build/test (* wildcard supported)")
- parser.add_argument("-postbuild", action="store_true", help="only perform post-build operations")
- parser.add_argument("-patch", action="store_true", help="patch projects")
- parser.add_argument("-symbols", action="store_true", help="try to copy symbols for various dependencies")
- args = parser.parse_args()
- SetSampleActions()
- if args.x86:
- args.arch = "Win32"
- else:
- args.arch = "x64"
- if not args.samples:
- print("The -samples option must be used to indicate the root of D3D12 Samples.")
- print("Samples are available at this URL.")
- print("https://github.com/Microsoft/DirectX-Graphics-Samples")
- exit(1)
- if args.sample:
- print('Applying sample filter: %s' % args.sample)
- args.sample = re.escape(args.sample).replace('\\*', '.*')
- rxSample = re.compile(args.sample, re.I)
- for name in samples:
- if rxSample.match(name):
- print(' %s' % name)
- if args.postbuild:
- print("Applying patch to post-build binaries to enable dxc ...")
- PatchSamplesAfterBuild(args, False)
- PatchSamplesAfterBuild(args, True)
- elif args.patch:
- print("Patching projects ...")
- PatchProjects(args)
- else:
- RunSampleTests(args)
|