| # 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) |