You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
234 lines
9.8 KiB
Python
234 lines
9.8 KiB
Python
#
|
|
# 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', 'android')
|
|
shaders.append(gles3)
|
|
|
|
metal = _ShaderType('METAL', 'METAL_LLVM_DXC')
|
|
metal.add_configuration('Mac', 'mac')
|
|
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')
|