Merge branch 'main' into build-strike/SPEC-6281

This commit is contained in:
Shirang Jia
2021-04-13 14:54:23 -07:00
committed by GitHub
1727 changed files with 35800 additions and 520979 deletions
View File
View File
View File
View File
View File
+1 -1
View File
@@ -77,7 +77,7 @@ class PackageEnv(Params):
def validate_engine_root(engine_root):
if not os.path.isdir(engine_root):
return False
return os.path.exists(os.path.join(engine_root, 'engineroot.txt'))
return os.path.exists(os.path.join(engine_root, 'engine.json'))
workspace = os.getenv('WORKSPACE')
if workspace is not None:
@@ -86,7 +86,6 @@
"ExpressionEvaluation": "#include",
"FastNoise": "#include",
"GameEffectSystem": "#include",
"GameLift": "#include",
"GameState": "#include",
"GameStateSamples": "#include",
"Gestures": "#include",
@@ -0,0 +1,99 @@
"""
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
its licensors.
For complete copyright and license terms please see the LICENSE at the root of this
distribution (the "License"). All use of this software is governed by the License,
or, if provided, by the license below or the license accompanying this file. Do not
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Config file for pytest & PythonTestTools. Warns user if the packaged Python is not
being used.
"""
import argparse
import os
import pathlib
import subprocess
# Move upwards twice from the <engine-root>/scripts/bundler directory to get the parent directory
SCRIPT_PATH = pathlib.Path(__file__).resolve()
DEFAULT_ENGINE_ROOT = SCRIPT_PATH.parents[2]
def printMessage(message):
print(message)
def getCommandLineArgs():
parser = argparse.ArgumentParser(
description='O3DE project release build automation tool, generates auxiliary content for the project specified on the command line.')
parser.add_argument('--platforms', required=True,
help='Required: The comma separated platform list to generate auxiliary content for.')
parser.add_argument('--buildFolder', required=True,
help='Required: The build folder for your engine, containing AssetProcessorBatch.exe')
parser.add_argument('--recompress', action='store_true',
help='If present, the ResourceCompiler (RC.exe) will decompress and compress back each PAK file found as they are transferred from the cache folder to the game_pc_pak folder.')
parser.add_argument('--use_fastest', action='store_true',
help='As each file is being added to its PAK file, they will be compressed across all available codecs (ZLIB, ZSTD and LZ4) and the one with the fastest decompression time will be chosen. The default is to always use ZLIB.')
parser.add_argument('--skipAssetProcessing', action='store_true',
help='If present, assetprocessorbatch will not be launched before creating the auxiliary content for the current game project specified in the bootstrap.cfg file. The default behavior of processing assets first helps you make sure your auxiliary content will always be generated from the most up to date data for your game.')
parser.add_argument('--skiplevelPaks', action='store_true',
help='If present, the ResourceCompiler (RC.exe) will skip putting any level related pak files for e.g level.pak and terraintexture.pak in the auxiliary contents. Consider using this option if your levels are being packaged through some other system, such as the AssetBundler.')
parser.add_argument('--project-path', required=True, type=pathlib.Path,
help='Required: specifies the path to the o3de project to use')
parser.add_argument('--engine-path', type=pathlib.Path, default=DEFAULT_ENGINE_ROOT,
help='specifies the path to the engine root to use')
return parser.parse_args()
def subprocessWithPrint(command):
printMessage(command)
subprocess.check_call(command)
def main():
args = getCommandLineArgs()
if not args.project_path:
printMessage(
"Project-path has not been supplied, it is required to in order to locate a project auxiliary content.")
return
printMessage(f"Generating auxiliary content for project at path {str(args.project_path)} from build folder {args.buildFolder} for platforms {args.platforms}")
# Build paths to tools
apBatchExe = os.path.join(args.buildFolder, "AssetProcessorBatch.exe")
rcFolder = args.buildFolder
rcExe = os.path.join(rcFolder, "rc.exe")
rc_script_name = 'RCJob_Generic_MakeAuxiliaryContent.xml'
rc_script_path = os.path.join(rcFolder, rc_script_name)
if not os.path.exists(rc_script_path):
rc_script_path = args.engine_path / 'Code' / 'Tools' / 'rc' / 'Config' / 'rc' / rc_script_name
if not os.path.exists(rc_script_path):
printMessage(f"Could not find {rc_script_name} at {rcFolder} or {str(args.engine_path)}")
return
# Make sure all assets are up to date.
if not args.skipAssetProcessing:
printMessage("Updating game assets")
# Asset processor will read the current project from bootstrap.
subprocessWithPrint(f"{apBatchExe} --project-path={str(args.project_path)} --platforms={args.platforms}")
else:
printMessage("Skipping asset processing")
platformsSplit = args.platforms.split(',')
for platform in platformsSplit:
printMessage("Generating auxiliary content for platform {}".format(platform))
# Call RC to generate the auxiliary content.
recompressArg = 1 if args.recompress else 0
useFastestArg = 1 if args.use_fastest else 0
skiplevelPaksArg = 1 if args.skiplevelPaks else 0
rc_cmd = f"{rcExe} --job={rc_script_path} --platform={platform}" \
f" --regset=/Amazon/AzCore/Bootstrap/project_path={str(args.project_path)} " \
f"--recompress={recompressArg}" \
f" --use_fastest={useFastestArg} --skiplevelPaks={skiplevelPaksArg}"
subprocessWithPrint(rc_cmd)
if __name__ == "__main__":
main()
+233
View File
@@ -0,0 +1,233 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
import argparse
import importlib
import json
import logging
import os
import pathlib
import shutil
import subprocess
import sys
ROOT_ENGINE_PATH = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', '..'))
if ROOT_ENGINE_PATH not in sys.path:
sys.path.append(ROOT_ENGINE_PATH)
class _Configuration:
def __init__(self, platform, asset_platform, core_compiler):
self.platform = platform
self.asset_platform = asset_platform
self.compiler = '{}-{}'.format(platform, core_compiler)
def __str__(self):
return '{} ({})'.format(self.platform, self.asset_platform)
class _ShaderType:
def __init__(self, name, base_compiler):
self.name = name
self.core_compiler = '{}-{}'.format(base_compiler, name)
self.configurations = []
def add_configuration(self, platform, asset_platform):
self.configurations.append(_Configuration(platform, asset_platform, self.core_compiler))
def find_shader_type(shader_type_name, shader_types):
return next((shader for shader in shader_types if shader.name == shader_type_name), None)
def find_shader_configuration(platform, assets, shader_configurations):
if platform:
check_func = lambda config: config.platform == platform and config.asset_platform == assets
else:
check_func = lambda config: config.asset_platform == assets
return next((config for config in shader_configurations if check_func(config)), None)
def error(msg):
print(msg)
exit(1)
def is_windows():
if os.name == 'nt':
return True
else:
return False
def read_project_name_from_project_json(project_path):
project_name = None
try:
with (pathlib.Path(project_path) / 'project.json').open('r') as project_file:
project_json = json.load(project_file)
project_name = project_json['project_name']
except OSError as os_error:
logging.warning(f'Unable to open "project.json" file: {os_error}')
except json.JSONDecodeError as json_error:
logging.warning(f'Unable to decode json in {project_file}: {json_error}')
except KeyError as key_error:
logging.warning(f'{project_file} is missing project_name key: {key_error}')
return project_name
def gen_shaders(shader_type, shader_config, shader_list, bin_folder, project_path, engine_path, verbose):
"""
Generates the shaders for a specific platform and shader type using a list of shaders using ShaderCacheGen.
The generated shaders will be output at Cache/<game_name>/<asset_platform>/user/cache/Shaders/Cache/<shader_type>
"""
platform = shader_config.platform
asset_platform = shader_config.asset_platform
compiler = shader_config.compiler
project_name = read_project_name_from_project_json(project_path)
asset_cache_root = os.path.join(project_path, 'Cache', asset_platform)
# Make sure that the <project-path>/user folder exists
user_cache_folder = os.path.join(project_path, 'user', 'Cache')
if not os.path.isdir(user_cache_folder):
try:
os.makedirs(user_cache_folder)
except os.error as err:
error("Unable to create the required cache folder '{}': {}".format(user_cache_folder, err))
cache_shader_list = os.path.join(user_cache_folder, 'shaders', 'shaderlist.txt')
if shader_list is None:
if is_windows():
shader_compiler_platform = 'x64'
else:
shader_compiler_platform = 'osx'
shader_list_path = os.path.join(user_cache_folder, shader_compiler_platform, compiler,
'ShaderList_{}.txt'.format(shader_type))
if not os.path.isfile(shader_list_path):
shader_list_path = cache_shader_list
print("Source Shader List not specified, using {} by default".format(shader_list_path))
else:
shader_list_path = shader_list
normalized_shaderlist_path = os.path.normpath(os.path.normcase(os.path.realpath(shader_list_path)))
normalized_cache_shader_list = os.path.normpath(os.path.normcase(os.path.realpath(cache_shader_list)))
if normalized_shaderlist_path != normalized_cache_shader_list:
cache_shader_list_basename = os.path.split(cache_shader_list)[0]
if not os.path.exists(cache_shader_list_basename):
os.makedirs(cache_shader_list_basename)
print("Copying shader_list from {} to {}".format(shader_list_path, cache_shader_list))
shutil.copy2(shader_list_path, cache_shader_list)
platform_shader_cache_path = os.path.join(user_cache_folder, 'shaders', 'cache', shader_type.lower())
shutil.rmtree(platform_shader_cache_path, ignore_errors=True)
shadergen_path = os.path.join(engine_path, bin_folder, 'ShaderCacheGen')
if is_windows():
shadergen_path += '.exe'
if not os.path.isfile(shadergen_path):
error("ShaderCacheGen could not be found at {}".format(shadergen_path))
else:
command_arguments = [
shadergen_path,
f'--project-path={project_path}',
'--BuildGlobalCache',
'--ShadersPlatform={}'.format(shader_type),
'--TargetPlatform={}'.format(asset_platform)
]
if verbose:
print('Running: {}'.format(' '.join(command_arguments)))
subprocess.call(command_arguments)
def add_shaders_types():
"""
Add the shader types for the non restricted platforms.
The compiler argument is used for locating the shader_list file.
"""
shaders = []
d3d11 = _ShaderType('D3D11', 'D3D11_FXC')
d3d11.add_configuration('PC', 'pc')
shaders.append(d3d11)
gl4 = _ShaderType('GL4', 'GLSL_HLSLcc')
gl4.add_configuration('PC', 'pc')
shaders.append(gl4)
gles3 = _ShaderType('GLES3', 'GLSL_HLSLcc')
gles3.add_configuration('Android', 'es3')
shaders.append(gles3)
metal = _ShaderType('METAL', 'METAL_LLVM_DXC')
metal.add_configuration('Mac', 'osx_gl')
metal.add_configuration('iOS', 'ios')
shaders.append(metal)
restricted_path = os.path.join(ROOT_ENGINE_PATH, 'restricted')
if os.path.exists(restricted_path):
restricted_platforms = os.listdir(restricted_path)
for platform in restricted_platforms:
try:
imported_module = importlib.import_module(f'restricted.{platform}.Tools.PakShaders.gen_shaders')
except ImportError:
continue
restricted_func = getattr(imported_module, 'get_restricted_platform_shader', lambda: iter(()))
for shader_type, shader_compiler, platform_name, asset_platform in restricted_func():
shader = find_shader_type(shader_type, shaders)
if shader is None:
shader = _ShaderType(shader_type, shader_compiler)
shaders.append(shader)
shader.add_configuration(platform_name, asset_platform)
return shaders
def check_arguments(args, parser, shader_types):
"""
Check that the platform and shader type arguments are correct.
"""
shader_names = [shader.name for shader in shader_types]
shader_found = find_shader_type(args.shader_type, shader_types)
if shader_found is None:
parser.error('Invalid shader type {}. Must be one of [{}]'.format(args.shader_type, ' '.join(shader_names)))
else:
config_found = find_shader_configuration(args.shader_platform, args.asset_platform, shader_found.configurations)
if config_found is None:
parser.error('Invalid configuration for shader type "{}". It must be one of the following: {}'.format(shader_found.name, ', '.join(str(config) for config in shader_found.configurations)))
parser = argparse.ArgumentParser(description='Generates the shaders for a specific platform and shader type.')
parser.add_argument('asset_platform', type=str, help="The asset cache sub folder to use for shader generation")
parser.add_argument('shader_type', type=str, help="The shader type to use")
parser.add_argument('-p', '--shader-platform', type=str, required=False, default='', help="The target platform to generate shaders for.")
parser.add_argument('-b', '--bin-folder', type=str, help="Folder where the ShaderCacheGen executable lives. This is used along the project (ShaderCacheGen)")
parser.add_argument('-e', '--engine-path', type=str, help="Path to the engine root folder. This the same as game_path for non external projects")
parser.add_argument('-g', '--project-path', type=str, required=True, help="Path to the game root folder. This the same as engine_path for non external projects")
parser.add_argument('-s', '--shader-list', type=str, required=False, help="Optional path to the list of shaders. If not provided will use the list generated by the local shader compiler.")
parser.add_argument('-v', '--verbose', action="store_true", required=False, help="Increase the logging output")
args = parser.parse_args()
shader_types = add_shaders_types()
check_arguments(args, parser, shader_types)
print('Generating shaders for {} (shaders={}, platform={}, assets={})'.format(args.project_path, args.shader_type, args.shader_platform, args.asset_platform))
shader = find_shader_type(args.shader_type, shader_types)
shader_config = find_shader_configuration(args.shader_platform, args.asset_platform, shader.configurations)
gen_shaders(args.shader_type, shader_config, args.shader_list, args.bin_folder, args.project_path, args.engine_path, args.verbose)
print('Finish generating shaders')
+68
View File
@@ -0,0 +1,68 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
import argparse
import os
import pathlib
import subprocess
def error(msg):
print(msg)
exit(1)
def is_windows():
if os.name == 'nt':
return True
else:
return False
def get_shader_list(project_path, asset_platform, shader_type, shader_platform, shadergen_path):
"""
Gets the shader list for a specific platform using ShaderCacheGen.
Right now the shader list will always output at <project-path>/user/Cache/Shaders
That will change when this is updated to take a destination path
"""
shadergen_path = os.path.join(shadergen_path, 'ShaderCacheGen')
if is_windows():
shadergen_path += '.exe'
command_args = [
shadergen_path,
f'--project-path={str(project_path)}'
'--GetShaderList',
'--ShadersPlatform={}'.format(shader_type),
'--TargetPlatform={}'.format(asset_platform)
]
if not os.path.isfile(shadergen_path):
error("[ERROR] ShaderCacheGen could not be found at {}".format(shadergen_path))
else:
command = ' '.join(command_args)
print('[INFO] get_shader_list: Running command - {}'.format(command))
try:
subprocess.check_call(command, shell=True)
except subprocess.CalledProcessError:
error('[ERROR] Failed to get the shader list for {}'.format(shader_type))
parser = argparse.ArgumentParser(description='Gets the shader list for a specific platform from the current shader compiler server')
parser.add_argument('-g', 'project-path', type=pathlib.Path, required=True, help="Path to the project")
parser.add_argument('asset-platform', type=str, help="The asset cache sub folder to use for shader generation")
parser.add_argument('shader-type', type=str, help="The shader type to use")
parser.add_argument('-p', '--shader_platform', type=str, required=False, default='', help="The target platform to generate shaders for.")
parser.add_argument('-s', '--shadergen_path', type=str, help="Path to where the the ShaderCacheGen executable lives")
args = parser.parse_args()
print('Getting shader list for {}'.format(args.asset_platform))
get_shader_list(args.project_path, args.asset_platform, args.shader_type, args.shader_platform, args.shadergen_path)
print('Finish getting shader list')
+142
View File
@@ -0,0 +1,142 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
import argparse
import fnmatch
import glob
import os
import re
import sys
import zipfile
def _create_zip(source_path, zip_file_path, append, filter_list, ignore_list, compression_level, zip_internal_path = ''):
"""
Internal function for creating a zip file containing the files that match the list of filters provided.
File matching is case insensitive.
"""
ignore_list = [filter.lower() for filter in ignore_list]
filter_list = [filter.lower() for filter in filter_list]
ignore_list = r'|'.join([fnmatch.translate(x) for x in ignore_list])
try:
mode = 'a' if append else 'w'
with zipfile.ZipFile(zip_file_path, mode, compression_level) as myzip:
# Go through all the files in the source path.
for root, dirnames, filenames in os.walk(source_path):
# Remove files that match the ignore list.
files = [os.path.relpath(os.path.join(root, file), source_path).lower() for file in filenames if not re.match(ignore_list, file.lower())]
# Match the files we have found against the filters.
for filter in filter_list:
for filename in fnmatch.filter(files, filter):
# Add the file to the zip using the specified internal destination.
myzip.write(os.path.join(source_path, filename), os.path.join(zip_internal_path, filename))
except IOError as error:
print("I/O error({0}) while creating zip file {1}: {2}".format(error.errno, zip_file_path, error.strerror))
return False
except:
print("Unexpected error while creating zip file {0}: {1}".format(zip_file_path, sys.exc_info()[0]))
return False
return True
# Create or append a pak file with all the shaders found in source_path.
def pak_shaders_in_folder(source_path, output_folder, shader_type, append):
"""
Creates the shadercache.pak and the shadercachestartup.pak using the shader files located at source_path.
"""
ignore_list = ['shaderlist.txt', 'shadercachemisses.txt']
shaders_cache_startup_filters = ['Common.cfib', 'FXConstantDefs.cfib', 'FXSamplerDefs.cfib', 'FXSetupEnvVars.cfib',
'FXStreamDefs.cfib', 'fallback.cfxb', 'fallback.fxb', 'FixedPipelineEmu.cfxb',
'FixedPipelineEmu.fxb', 'Stereo.cfxb', 'Stereo.fxb', 'lookupdata.bin',
'Video.cfxb', 'Video.fxb',
os.path.join('CGPShaders', 'FixedPipelineEmu@*'),
os.path.join('CGVShaders', 'FixedPipelineEmu@*'),
os.path.join('CGPShaders', 'FixedPipelineEmu', '*'),
os.path.join('CGVShaders', 'FixedPipelineEmu', '*'),
os.path.join('CGPShaders', 'Stereo@*'),
os.path.join('CGVShaders', 'Stereo@*'),
os.path.join('CGPShaders', 'Stereo', '*'),
os.path.join('CGVShaders', 'Stereo', '*'),
os.path.join('CGPShaders', 'Video@*'),
os.path.join('CGVShaders', 'Video@*'),
os.path.join('CGPShaders', 'Video', '*'),
os.path.join('CGVShaders', 'Video', '*')
]
print('Packing shader source folder {}'.format(source_path))
result = True
if os.path.exists(source_path):
if not os.path.exists(output_folder):
os.makedirs(output_folder)
# We want the files to be added to the "shaders/cache/$shader_type" path inside the pak file.
zip_interal_path = os.path.join('shaders', 'cache', shader_type)
result &= _create_zip(source_path, os.path.join(output_folder, 'shadercache.pak'), append, ['*.*'], ignore_list, zipfile.ZIP_STORED, zip_interal_path)
result &= _create_zip(source_path, os.path.join(output_folder, 'shadercachestartup.pak'), append, shaders_cache_startup_filters, ignore_list, zipfile.ZIP_STORED, zip_interal_path)
else:
print('[Error] Shader source folder is not available at {}. Shader type: {}.'.format(source_path, shader_type))
result = False
return result
# Generate a shaders pak file with all the shader types indicated.
# NOTE: A shader type can specify an specific source path or not. Examples:
# - 'metal,specific/path/to/shaders': Use the folder specified as the source path to all metal shaders.
# - 'metal': Use source_path/metal as source path to all metal shaders. Wildcard usage is allowed, for example 'gles3*'.
def pak_shaders(source_path, output_folder, shader_types):
shader_flavors_packed = 0
for shader_info in shader_types:
# First element is the type, the second (if present) is the specific source
shader_type = shader_info[0]
if len(shader_info) > 1:
shader_type_source = shader_info[1]
if pak_shaders_in_folder(shader_type_source, output_folder, shader_type, shader_flavors_packed > 0):
shader_flavors_packed = shader_flavors_packed + 1
else:
return False
else:
# No specific source path for this shader type so use the global source path. Wildcard allowed.
listing = glob.glob(os.path.join(source_path, shader_type))
for shader_type_source in listing:
if os.path.isdir(shader_type_source):
# Since the shader_type can use wildcard we have to obtain the actual shader type found by removing source_path
# Example: If shader_type is 'gl4*' then now will be 'gl4_4'
shader_type = shader_type_source[len(source_path)+1:]
if pak_shaders_in_folder(shader_type_source, output_folder, shader_type, shader_flavors_packed > 0):
shader_flavors_packed = shader_flavors_packed + 1
else:
return False
if shader_flavors_packed == 0:
print('Failed to pack any shader type')
return shader_flavors_packed > 0
def pair_arg(arg):
return [str(x) for x in arg.split(',')]
parser = argparse.ArgumentParser(description='Pack the provided shader files into paks.')
parser.add_argument("output", type=str, help="specify the output folder")
parser.add_argument('-r', '--source', type=str, required=False, help="specify global input folder")
parser.add_argument('-s', '--shaders_types', required=True, nargs='+', type=pair_arg,
help='list of shader types with optional source path')
args = parser.parse_args()
print('Packing shaders...')
if not pak_shaders(args.source, args.output, args.shaders_types):
print('Failed to pack shaders')
exit(1)
print('Packs have been placed at "{}"'.format(args.output))
print('To use them, deploy them in your assets folder.')
print('Finish packing shaders')
@@ -172,7 +172,7 @@ BUILD_FILE_PATTERNS: Tuple[re.Pattern, ...] = (
SOURCE_AND_SCRIPT_FILE_PATTERNS: Tuple[re.Pattern, ...] = BUILD_FILE_PATTERNS
EXCLUDED_VALIDATION_PATTERNS = [
'.git/*',
'*/.git/*',
'*/3rdParty/*',
'*/__pycache__/*',
'*/External/*',
+2 -4
View File
@@ -216,9 +216,7 @@ class ProjectDialog(QObject):
return os.path.basename(self.path_for_selection())
def get_launch_project(self) -> str:
# We can't currently pass an absolute path, so we need to assume the folder lives under engine and just pass
# the base name of the selected project
return os.path.basename(os.path.normpath(self.path_for_selection()))
return os.path.normpath(self.path_for_selection())
def get_executable_launch_params(self) -> list:
"""
@@ -551,7 +549,7 @@ class ProjectDialog(QObject):
self.add_new_project(project_folder[0], validate=False)
msg_box = QMessageBox(parent=self.dialog)
msg_box.setWindowTitle("O3DE")
msg_box.setText(f"Project {os.path.basename(os.path.normpath(project_folder[0]))} created."
msg_box.setText(f"Project {os.path.basename(os.path.normpath(project_folder[0]))} created."
" Build your\nnew project before hitting OK to launch.")
msg_box.exec()
return