GenerateShaderVariantListUtil.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. """
  2. Copyright (c) Contributors to the Open 3D Engine Project.
  3. For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. SPDX-License-Identifier: Apache-2.0 OR MIT
  5. """
  6. import os
  7. import azlmbr.asset as asset
  8. import azlmbr.atom
  9. import azlmbr.bus
  10. import azlmbr.math as math
  11. import azlmbr.name
  12. import azlmbr.paths
  13. import azlmbr.shadermanagementconsole
  14. import azlmbr.shader
  15. from PySide2 import QtWidgets
  16. import json
  17. PROJECT_SHADER_VARIANTS_FOLDER = "ShaderVariants"
  18. # Input is Material Asset id list
  19. # For all material in the asset database, save a shader variant list in the project's ShaderVariants folder
  20. def save_shadervariantlists_for_materials(materialAssetIds):
  21. shaderOptionGroupsDict = {} # Key: ShaderAssetId, Value: ShaderOptionGroups list
  22. progressDialog = QtWidgets.QProgressDialog("Gather shader variant information...", "Cancel", 0, len(materialAssetIds))
  23. progressDialog.setMaximumWidth(400)
  24. progressDialog.setMaximumHeight(100)
  25. progressDialog.setModal(True)
  26. progressDialog.setWindowTitle("Gather shader variants from material assets")
  27. # This loop collects all uniquely-identified shader items used by the materials based on its shader variant id.
  28. for i, materialAssetId in enumerate(materialAssetIds):
  29. materialInstanceShaderItems = azlmbr.shadermanagementconsole.ShaderManagementConsoleRequestBus(azlmbr.bus.Broadcast, 'GetMaterialInstanceShaderItems', materialAssetId)
  30. for shaderItem in materialInstanceShaderItems:
  31. shaderAssetId = shaderItem.GetShaderAsset().get_id()
  32. shaderVariantId = shaderItem.GetShaderVariantId()
  33. if not shaderVariantId.IsEmpty():
  34. if shaderOptionGroupsDict.get(shaderAssetId) == None:
  35. shaderOptionGroupsDict[shaderAssetId] = []
  36. shaderOptionGroupsDict[shaderAssetId].append(shaderItem.GetShaderOptionGroup())
  37. else:
  38. # check for repetition
  39. has_repeat = False
  40. for shaderOptionGroup in shaderOptionGroupsDict[shaderAssetId]:
  41. variantId = shaderOptionGroup.GetShaderVariantId()
  42. if shaderVariantId == variantId:
  43. has_repeat = True
  44. break
  45. if has_repeat:
  46. continue
  47. shaderOptionGroupsDict[shaderAssetId].append(shaderItem.GetShaderOptionGroup())
  48. progressDialog.setValue(i)
  49. if progressDialog.wasCanceled():
  50. return
  51. progressDialog.close()
  52. progressDialog = QtWidgets.QProgressDialog("Generating .shadervariantlist files...", "Cancel", 0, len(shaderOptionGroupsDict))
  53. progressDialog.setMaximumWidth(400)
  54. progressDialog.setMaximumHeight(100)
  55. progressDialog.setModal(True)
  56. progressDialog.setWindowTitle("Generating Shader Variant Lists")
  57. # Generate the shader variant list for each shader asset
  58. for i, shaderAssetId in enumerate(shaderOptionGroupsDict):
  59. shaderAssetInfo = asset.AssetCatalogRequestBus(
  60. azlmbr.bus.Broadcast,
  61. 'GetAssetInfoById',
  62. shaderAssetId
  63. )
  64. fullShaderPath = azlmbr.shadermanagementconsole.ShaderManagementConsoleRequestBus(
  65. azlmbr.bus.Broadcast,
  66. 'GetFullSourcePathFromRelativeProductPath',
  67. shaderAssetInfo.relativePath
  68. )
  69. relativeShaderPath = azlmbr.shadermanagementconsole.ShaderManagementConsoleRequestBus(
  70. azlmbr.bus.Broadcast,
  71. 'GenerateRelativeSourcePath',
  72. fullShaderPath
  73. )
  74. shaderVariantList = azlmbr.shader.ShaderVariantListSourceData()
  75. shaderVariantList.shaderFilePath = relativeShaderPath
  76. shaderVariants = []
  77. stableId = 1
  78. for shaderOptionGroup in shaderOptionGroupsDict[shaderAssetId]:
  79. variantInfo = azlmbr.shader.ShaderVariantInfo()
  80. variantInfo.stableId = stableId
  81. options = {}
  82. shaderOptionDescriptors = shaderOptionGroup.GetShaderOptionDescriptors()
  83. for shaderOptionDescriptor in shaderOptionDescriptors:
  84. optionName = shaderOptionDescriptor.GetName()
  85. optionValue = shaderOptionGroup.GetValueByOptionName(optionName)
  86. if not optionValue.IsValid():
  87. continue
  88. valueName = shaderOptionDescriptor.GetValueName(optionValue)
  89. options[optionName] = valueName
  90. if len(options) != 0:
  91. variantInfo.options = options
  92. shaderVariants.append(variantInfo)
  93. stableId += 1
  94. if shaderVariants:
  95. shaderVariantList.shaderVariants = shaderVariants
  96. pre, ext = os.path.splitext(relativeShaderPath)
  97. projectShaderVariantListFilePath = os.path.join(azlmbr.paths.projectroot, PROJECT_SHADER_VARIANTS_FOLDER, f'{pre}.shadervariantlist')
  98. # clean previously generated shader variant list file so they don't clash.
  99. if os.path.exists(projectShaderVariantListFilePath):
  100. os.remove(projectShaderVariantListFilePath)
  101. azlmbr.shader.SaveShaderVariantListSourceData(projectShaderVariantListFilePath, shaderVariantList)
  102. progressDialog.setValue(i)
  103. if progressDialog.wasCanceled():
  104. return
  105. progressDialog.close()
  106. # Make a copy of shaderVariants, update target option value and return copy, accumulate stableId
  107. def updateOptionValue(shaderVariants, targetOptionName, targetValue, stableId):
  108. tempShaderVariants = []
  109. for variantInfo in shaderVariants:
  110. tempVariantInfo = azlmbr.shader.ShaderVariantInfo()
  111. options = {}
  112. for optionName in variantInfo.options:
  113. if targetOptionName == optionName:
  114. options[optionName] = targetValue
  115. else:
  116. options[optionName] = variantInfo.options[optionName]
  117. tempVariantInfo.options = options
  118. tempVariantInfo.stableId = stableId
  119. tempShaderVariants.append(tempVariantInfo)
  120. stableId += 1
  121. return tempShaderVariants, stableId
  122. # Input is one .shader
  123. def create_shadervariantlist_for_shader(filename):
  124. # Get info such as relative path of the file and asset id
  125. shaderAssetInfo = azlmbr.shadermanagementconsole.ShaderManagementConsoleRequestBus(
  126. azlmbr.bus.Broadcast,
  127. 'GetSourceAssetInfo',
  128. filename
  129. )
  130. # retrieves a list of all material source files that use the shader. Note that materials inherit from materialtype files, which are actual files that refer to shader files.
  131. materialAssetIds = azlmbr.shadermanagementconsole.ShaderManagementConsoleRequestBus(
  132. azlmbr.bus.Broadcast,
  133. 'FindMaterialAssetsUsingShader',
  134. shaderAssetInfo.relativePath
  135. )
  136. shaderVariantList = azlmbr.shader.ShaderVariantListSourceData()
  137. shaderVariantList.shaderFilePath = shaderAssetInfo.relativePath
  138. if len(materialAssetIds) == 0:
  139. # No material is using this shader, so we can't get ShaderOptionDescriptor in the script
  140. # Return early and handle user assigned system option after shaderAsset is loaded
  141. return shaderVariantList
  142. # This loop collects all uniquely-identified shader items used by the materials based on its shader variant id.
  143. shader_file = os.path.basename(filename)
  144. shaderVariantIds = []
  145. shaderOptionGroups = []
  146. progressDialog = QtWidgets.QProgressDialog(f"Generating .shadervariantlist file for:\n{shader_file}", "Cancel", 0, len(materialAssetIds))
  147. progressDialog.setMaximumWidth(400)
  148. progressDialog.setMaximumHeight(100)
  149. progressDialog.setModal(True)
  150. progressDialog.setWindowTitle("Generating Shader Variant List")
  151. for i, materialAssetId in enumerate(materialAssetIds):
  152. materialInstanceShaderItems = azlmbr.shadermanagementconsole.ShaderManagementConsoleRequestBus(azlmbr.bus.Broadcast, 'GetMaterialInstanceShaderItems', materialAssetId)
  153. for shaderItem in materialInstanceShaderItems:
  154. shaderAssetId = shaderItem.GetShaderAsset().get_id()
  155. if shaderAssetInfo.assetId == shaderAssetId:
  156. shaderVariantId = shaderItem.GetShaderVariantId()
  157. if not shaderVariantId.IsEmpty():
  158. # Check for repeat shader variant ids. We are using a list here
  159. # instead of a set to check for duplicates on shaderVariantIds because
  160. # shaderVariantId is not hashed by the ID like it is in the C++ side.
  161. has_repeat = False
  162. for variantId in shaderVariantIds:
  163. if shaderVariantId == variantId:
  164. has_repeat = True
  165. break
  166. if has_repeat:
  167. continue
  168. shaderVariantIds.append(shaderVariantId)
  169. shaderOptionGroups.append(shaderItem.GetShaderOptionGroup())
  170. progressDialog.setValue(i)
  171. if progressDialog.wasCanceled():
  172. return
  173. progressDialog.close()
  174. # Read from shaderPath.systemoptions to get user assigned system option value
  175. pre, ext = os.path.splitext(filename)
  176. systemOptionFilePath = f'{pre}.systemoptions'
  177. systemOptionFilePath = systemOptionFilePath.replace("\\", "/")
  178. systemOptionDict = {}
  179. if os.path.isfile(systemOptionFilePath):
  180. with open(systemOptionFilePath, "r") as systemOptionFile:
  181. systemOptionDict = json.load(systemOptionFile)
  182. # Generate the shader variant list data by collecting shader option name-value pairs.s
  183. shaderVariants = []
  184. systemOptionDescriptor = {}
  185. stableId = 1
  186. for shaderOptionGroup in shaderOptionGroups:
  187. variantInfo = azlmbr.shader.ShaderVariantInfo()
  188. variantInfo.stableId = stableId
  189. options = {}
  190. shaderOptionDescriptors = shaderOptionGroup.GetShaderOptionDescriptors()
  191. for shaderOptionDescriptor in shaderOptionDescriptors:
  192. optionName = shaderOptionDescriptor.GetName()
  193. optionValue = shaderOptionGroup.GetValueByOptionName(optionName)
  194. if not optionValue.IsValid():
  195. continue
  196. valueName = shaderOptionDescriptor.GetValueName(optionValue)
  197. options[optionName] = valueName
  198. # Check user assigned value
  199. optionNameString = optionName.ToString()
  200. if optionNameString in systemOptionDict:
  201. if systemOptionDict[optionNameString] != "":
  202. options[optionName] = azlmbr.name.Name(systemOptionDict[optionNameString])
  203. else:
  204. # Value is unset, expansion handling later
  205. systemOptionDescriptor[optionName] = shaderOptionDescriptor
  206. if len(options) != 0:
  207. variantInfo.options = options
  208. shaderVariants.append(variantInfo)
  209. stableId += 1
  210. # Expand the unset system option
  211. for systemOptionName in systemOptionDescriptor:
  212. optionDescriptor = systemOptionDescriptor[systemOptionName]
  213. defaultValue = optionDescriptor.GetDefaultValue()
  214. valueMin = optionDescriptor.GetMinValue()
  215. valueMax = optionDescriptor.GetMaxValue()
  216. totalShaderVariants = []
  217. for index in range(valueMin.GetIndex(), valueMax.GetIndex() + 1):
  218. optionValue = optionDescriptor.GetValueNameByIndex(index)
  219. if optionValue != defaultValue:
  220. tempShaderVariants, stableId = updateOptionValue(shaderVariants, systemOptionName, optionValue, stableId)
  221. totalShaderVariants.extend(tempShaderVariants)
  222. shaderVariants.extend(totalShaderVariants)
  223. shaderVariantList.shaderVariants = shaderVariants
  224. return shaderVariantList