blob: 64f7b38c00635b827f8f67922847a3a4cf176a50 [file] [log] [blame]
# Copyright (c) 2018 The Android Open Source Project
# Copyright (c) 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from generator import noneStr
from copy import copy
# Holds information about core Vulkan objects
# and the API calls that are used to create/destroy each one.
class HandleInfo(object):
def __init__(self, name, createApis, destroyApis):
self.name = name
self.createApis = createApis
self.destroyApis = destroyApis
def isCreateApi(self, apiName):
return apiName == self.createApis or (apiName in self.createApis)
def isDestroyApi(self, apiName):
if self.destroyApis is None:
return False
return apiName == self.destroyApis or (apiName in self.destroyApis)
DISPATCHABLE_HANDLE_TYPES = [
"VkInstance",
"VkPhysicalDevice",
"VkDevice",
"VkQueue",
"VkCommandBuffer",
]
NON_DISPATCHABLE_HANDLE_TYPES = [
"VkDeviceMemory",
"VkBuffer",
"VkBufferView",
"VkImage",
"VkImageView",
"VkShaderModule",
"VkDescriptorPool",
"VkDescriptorSetLayout",
"VkDescriptorSet",
"VkSampler",
"VkPipeline",
"VkPipelineLayout",
"VkRenderPass",
"VkFramebuffer",
"VkPipelineCache",
"VkCommandPool",
"VkFence",
"VkSemaphore",
"VkEvent",
"VkQueryPool",
"VkSamplerYcbcrConversion",
"VkSamplerYcbcrConversionKHR",
"VkDescriptorUpdateTemplate",
"VkSurfaceKHR",
"VkSwapchainKHR",
"VkDisplayKHR",
"VkDisplayModeKHR",
"VkObjectTableNVX",
"VkIndirectCommandsLayoutNVX",
"VkValidationCacheEXT",
"VkDebugReportCallbackEXT",
"VkDebugUtilsMessengerEXT",
]
CUSTOM_HANDLE_CREATE_TYPES = [
"VkPhysicalDevice",
"VkQueue",
"VkPipeline",
"VkDeviceMemory",
"VkDescriptorSet",
"VkCommandBuffer",
]
HANDLE_TYPES = list(sorted(list(set(DISPATCHABLE_HANDLE_TYPES +
NON_DISPATCHABLE_HANDLE_TYPES + CUSTOM_HANDLE_CREATE_TYPES))))
HANDLE_INFO = {}
for h in HANDLE_TYPES:
if h in CUSTOM_HANDLE_CREATE_TYPES:
if h == "VkPhysicalDevice":
HANDLE_INFO[h] = \
HandleInfo(
"VkPhysicalDevice",
"vkEnumeratePhysicalDevices", None)
if h == "VkQueue":
HANDLE_INFO[h] = \
HandleInfo(
"VkQueue",
"vkGetDeviceQueue", None)
if h == "VkPipeline":
HANDLE_INFO[h] = \
HandleInfo(
"VkPipeline",
["vkCreateGraphicsPipelines", "vkCreateComputePipelines"],
"vkDestroyPipeline")
if h == "VkDeviceMemory":
HANDLE_INFO[h] = \
HandleInfo("VkDeviceMemory",
"vkAllocateMemory", "vkFreeMemory")
if h == "VkDescriptorSet":
HANDLE_INFO[h] = \
HandleInfo("VkDescriptorSet", "vkAllocateDescriptorSets",
"vkFreeDescriptorSets")
if h == "VkCommandBuffer":
HANDLE_INFO[h] = \
HandleInfo("VkCommandBuffer", "vkAllocateCommandBuffers",
"vkFreeCommandBuffers")
else:
HANDLE_INFO[h] = \
HandleInfo(h, "vkCreate" + h[2:], "vkDestroy" + h[2:])
EXCLUDED_APIS = [
"vkEnumeratePhysicalDeviceGroups",
]
EXPLICITLY_ABI_PORTABLE_TYPES = [
"VkResult",
"VkBool32",
"VkSampleMask",
"VkFlags",
"VkDeviceSize",
]
EXPLICITLY_ABI_NON_PORTABLE_TYPES = [
"size_t"
]
NON_ABI_PORTABLE_TYPE_CATEGORIES = [
"handle",
"funcpointer",
]
DEVICE_MEMORY_INFO_KEYS = [
"devicememoryhandle",
"devicememoryoffset",
"devicememorysize",
"devicememorytypeindex",
"devicememorytypebits",
]
TRANSFORMED_TYPES = [
"VkExternalMemoryProperties",
"VkPhysicalDeviceExternalImageFormatInfo",
"VkPhysicalDeviceExternalBufferInfo",
"VkExternalMemoryImageCreateInfo",
"VkExternalMemoryBufferCreateInfo",
"VkExportMemoryAllocateInfo",
"VkExternalImageFormatProperties",
"VkExternalBufferProperties",
]
# Holds information about a Vulkan type instance (i.e., not a type definition).
# Type instances are used as struct field definitions or function parameters,
# to be later fed to code generation.
# VulkanType instances can be constructed in two ways:
# 1. From an XML tag with <type> / <param> tags in vk.xml,
# using makeVulkanTypeFromXMLTag
# 2. User-defined instances with makeVulkanTypeSimple.
class VulkanType(object):
def __init__(self):
self.parent = None
self.typeName = ""
self.isTransformed = False
self.paramName = None
self.lenExpr = None
self.isOptional = False
self.isConst = False
self.staticArrExpr = "" # "" means not static array
# 0 means the array size is not a numeric literal
self.staticArrCount = 0
self.pointerIndirectionLevels = 0 # 0 means not pointer
self.isPointerToConstPointer = False
self.primitiveEncodingSize = None
# self.deviceMemoryAttrib/Val stores
# device memory info attributes from the XML.
# devicememoryhandle
# devicememoryoffset
# devicememorysize
# devicememorytypeindex
# devicememorytypebits
self.deviceMemoryAttrib = None
self.deviceMemoryVal = None
def __str__(self,):
return ("(vulkantype %s %s paramName %s len %s optional? %s "
"staticArrExpr %s %s)") % (
self.typeName + ("*" * self.pointerIndirectionLevels) +
("ptr2constptr" if self.isPointerToConstPointer else ""), "const"
if self.isConst else "nonconst", self.paramName, self.lenExpr,
self.isOptional, self.staticArrExpr, self.staticArrCount)
def isString(self,):
return self.pointerIndirectionLevels == 1 and (self.typeName == "char")
def isArrayOfStrings(self,):
return self.isPointerToConstPointer and (self.typeName == "char")
def primEncodingSize(self,):
return self.primitiveEncodingSize
# Utility functions to make codegen life easier.
# This method derives the correct "count" expression if possible.
# Otherwise, returns None or "null-terminated" if a string.
def getLengthExpression(self,):
if self.staticArrExpr != "":
return self.staticArrExpr
if self.lenExpr:
return self.lenExpr
return None
# Can we just pass this to functions expecting T*
def accessibleAsPointer(self,):
if self.staticArrExpr != "":
return True
if self.pointerIndirectionLevels > 0:
return True
return False
# Rough attempt to infer where a type could be an output.
# Good for inferring which things need to be marshaled in
# versus marshaled out for Vulkan API calls
def possiblyOutput(self,):
return self.pointerIndirectionLevels > 0 and (not self.isConst)
def isVoidWithNoSize(self,):
return self.typeName == "void" and self.pointerIndirectionLevels == 0
def getCopy(self,):
return copy(self)
def getTransformed(self, isConstChoice=None, ptrIndirectionChoice=None):
res = self.getCopy()
if isConstChoice is not None:
res.isConst = isConstChoice
if ptrIndirectionChoice is not None:
res.pointerIndirectionLevels = ptrIndirectionChoice
return res
def getWithCustomName(self):
return self.getTransformed(
ptrIndirectionChoice=self.pointerIndirectionLevels + 1)
def getForAddressAccess(self):
return self.getTransformed(
ptrIndirectionChoice=self.pointerIndirectionLevels + 1)
def getForValueAccess(self):
if self.typeName == "void" and self.pointerIndirectionLevels == 1:
asUint8Type = self.getCopy()
asUint8Type.typeName = "uint8_t"
return asUint8Type.getForValueAccess()
return self.getTransformed(
ptrIndirectionChoice=self.pointerIndirectionLevels - 1)
def getForNonConstAccess(self):
return self.getTransformed(isConstChoice=False)
def withModifiedName(self, newName):
res = self.getCopy()
res.paramName = newName
return res
def isNextPointer(self):
if self.paramName == "pNext":
return True
return False
# Only deals with 'core' handle types here.
def isDispatchableHandleType(self):
return self.typeName in DISPATCHABLE_HANDLE_TYPES
def isNonDispatchableHandleType(self):
return self.typeName in NON_DISPATCHABLE_HANDLE_TYPES
def isHandleType(self):
return self.isDispatchableHandleType() or \
self.isNonDispatchableHandleType()
def isCreatedBy(self, api):
if self.typeName in HANDLE_INFO.keys():
nonKhrRes = HANDLE_INFO[self.typeName].isCreateApi(api.name)
if nonKhrRes:
return True
if len(api.name) > 3 and "KHR" == api.name[-3:]:
return HANDLE_INFO[self.typeName].isCreateApi(api.name[:-3])
return False
def isDestroyedBy(self, api):
if self.typeName in HANDLE_INFO.keys():
nonKhrRes = HANDLE_INFO[self.typeName].isDestroyApi(api.name)
if nonKhrRes:
return True
if len(api.name) > 3 and "KHR" == api.name[-3:]:
return HANDLE_INFO[self.typeName].isDestroyApi(api.name[:-3])
return False
def isSimpleValueType(self, typeInfo):
if typeInfo.isCompoundType(self.typeName):
return False
if self.isString() or self.isArrayOfStrings():
return False
if self.staticArrExpr or self.pointerIndirectionLevels > 0:
return False
return True
def getStructEnumExpr(self,):
return None
def makeVulkanTypeFromXMLTag(typeInfo, tag):
res = VulkanType()
# Process the length expression
if tag.attrib.get("len") is not None:
lengths = tag.attrib.get("len").split(",")
res.lenExpr = lengths[0]
# Calculate static array expression
nametag = tag.find("name")
enumtag = tag.find("enum")
if enumtag is not None:
res.staticArrExpr = enumtag.text
elif nametag is not None:
res.staticArrExpr = noneStr(nametag.tail)[1:-1]
if res.staticArrExpr != "":
res.staticArrCount = int(res.staticArrExpr)
# Determine const
beforeTypePart = noneStr(tag.text)
if "const" in beforeTypePart:
res.isConst = True
# Calculate type and pointer info
for elem in tag:
if elem.tag == "name":
res.paramName = elem.text
if elem.tag == "type":
duringTypePart = noneStr(elem.text)
afterTypePart = noneStr(elem.tail)
# Now we know enough to fill some stuff in
res.typeName = duringTypePart
if res.typeName in TRANSFORMED_TYPES:
res.isTransformed = True
# This only handles pointerIndirectionLevels == 2
# along with optional constant pointer for the inner part.
for c in afterTypePart:
if c == "*":
res.pointerIndirectionLevels += 1
if "const" in afterTypePart and res.pointerIndirectionLevels == 2:
res.isPointerToConstPointer = True
# If void*, treat like it's not a pointer
# if duringTypePart == "void":
# res.pointerIndirectionLevels -= 1
# Calculate optionality (based on validitygenerator.py)
if tag.attrib.get("optional") is not None:
res.isOptional = True
# If no validity is being generated, it usually means that
# validity is complex and not absolute, so let's say yes.
if tag.attrib.get("noautovalidity") is not None:
res.isOptional = True
# If this is a structure extension, it is optional.
if tag.attrib.get("structextends") is not None:
res.isOptional = True
# If this is a pNext pointer, it is optional.
if res.paramName == "pNext":
res.isOptional = True
res.primitiveEncodingSize = \
typeInfo.getPrimitiveEncodingSize(res.typeName)
# it is assumed only one such key
# applies to any field.
for k in DEVICE_MEMORY_INFO_KEYS:
if tag.attrib.get(k) is not None:
res.deviceMemoryAttrib = k
res.deviceMemoryVal = \
tag.attrib.get(k)
break;
return res
def makeVulkanTypeSimple(isConst,
typeName,
ptrIndirectionLevels,
paramName=None):
res = VulkanType()
res.typeName = typeName
res.isConst = isConst
res.pointerIndirectionLevels = ptrIndirectionLevels
res.isPointerToConstPointer = False
res.paramName = paramName
res.primitiveEncodingSize = None
return res
# A class for holding the parameter indices corresponding to various
# attributes about a VkDeviceMemory, such as the handle, size, offset, etc.
class DeviceMemoryInfoParameterIndices(object):
def __init__(self, handle, offset, size, typeIndex, typeBits):
self.handle = handle
self.offset = offset
self.size = size
self.typeIndex = typeIndex
self.typeBits = typeBits
# initializes DeviceMemoryInfoParameterIndices for each
# abstract VkDeviceMemory encountered over |parameters|
def initDeviceMemoryInfoParameterIndices(parameters):
use = False
deviceMemoryInfoById = {}
for (i, p) in enumerate(parameters):
a = p.deviceMemoryAttrib
if not a:
continue
if a in DEVICE_MEMORY_INFO_KEYS:
use = True
deviceMemoryInfoById[
p.deviceMemoryVal] = \
DeviceMemoryInfoParameterIndices(
None, None, None, None, None)
for (i, p) in enumerate(parameters):
a = p.deviceMemoryAttrib
if not a:
continue
info = \
deviceMemoryInfoById[p.deviceMemoryVal]
if a == "devicememoryhandle":
info.handle = i
if a == "devicememoryoffset":
info.offset = i
if a == "devicememorysize":
info.size = i
if a == "devicememorytypeindex":
info.typeIndex = i
if a == "devicememorytypebits":
info.typeBits = i
if not use:
return None
return deviceMemoryInfoById
# Classes for describing aggregate types (unions, structs) and API calls.
class VulkanCompoundType(object):
def __init__(self, name, members, isUnion=False, structEnumExpr=None, structExtendsExpr=None, feature=None):
self.name = name
self.typeName = name
self.members = members
self.isUnion = isUnion
self.structEnumExpr = structEnumExpr
self.structExtendsExpr = structExtendsExpr
self.feature = feature
self.deviceMemoryInfoParameterIndices = \
initDeviceMemoryInfoParameterIndices(self.members)
self.isTransformed = name in TRANSFORMED_TYPES
self.copy = None
def initCopies(self):
self.copy = self
for m in self.members:
m.parent = self.copy
def getMember(self, memberName):
for m in self.members:
if m.paramName == memberName:
return m
return None
def getStructEnumExpr(self,):
return self.structEnumExpr
class VulkanAPI(object):
def __init__(self, name, retType, parameters, origName=None):
self.name = name
self.origName = name
self.retType = retType
self.parameters = parameters
self.deviceMemoryInfoParameterIndices = \
initDeviceMemoryInfoParameterIndices(self.parameters)
self.copy = None
self.isTransformed = name in TRANSFORMED_TYPES
if origName:
self.origName = origName
def initCopies(self):
self.copy = self
for m in self.parameters:
m.parent = self.copy
def getCopy(self,):
return copy(self)
def getParameter(self, parameterName):
for p in self.parameters:
if p.paramName == parameterName:
return p
return None
def withModifiedName(self, newName):
res = VulkanAPI(newName, self.retType, self.parameters)
return res
def getRetVarExpr(self):
if self.retType.typeName == "void":
return None
return "%s_%s_return" % (self.name, self.retType.typeName)
def getRetTypeExpr(self):
return self.retType.typeName
def withCustomParameters(self, customParams):
res = self.getCopy()
res.parameters = customParams
return res
# Whether or not special handling of virtual elements
# such as VkDeviceMemory is needed.
def vulkanTypeNeedsTransform(structOrApi):
return structOrApi.deviceMemoryInfoParameterIndices != None
def vulkanTypeGetNeededTransformTypes(structOrApi):
res = []
if structOrApi.deviceMemoryInfoParameterIndices != None:
res.append("devicememory")
return res
def vulkanTypeforEachSubType(structOrApi, f):
toLoop = None
if type(structOrApi) == VulkanCompoundType:
toLoop = structOrApi.members
if type(structOrApi) == VulkanAPI:
toLoop = structOrApi.parameters
for (i, x) in enumerate(toLoop):
f(i, x)
# Parses everything about Vulkan types into a Python readable format.
class VulkanTypeInfo(object):
def __init__(self,):
self.categories = set([])
# Tracks what Vulkan type is part of what category.
self.typeCategories = {}
# Tracks the primitve encoding size for each type,
# if applicable.
self.encodingSizes = {}
self.structs = {}
self.apis = {}
self.feature = None
def initType(self, name, category):
self.categories.add(category)
self.typeCategories[name] = category
self.encodingSizes[name] = self.setPrimitiveEncodingSize(name)
def categoryOf(self, name):
return self.typeCategories[name]
def getPrimitiveEncodingSize(self, name):
return self.encodingSizes[name]
# Queries relating to categories of Vulkan types.
def isHandleType(self, name):
if name in self.typeCategories:
return self.typeCategories[name] == "handle"
return False
def isCompoundType(self, name):
if name in self.typeCategories:
return self.typeCategories[name] in ["struct", "union"]
else:
return False
# Gets the best size in bytes
# for encoding/decoding a particular Vulkan type.
# If not applicable, returns None.
def setPrimitiveEncodingSize(self, name):
baseEncodingSizes = {
"void" : 8,
"char" : 1,
"float" : 4,
"uint8_t" : 1,
"uint16_t" : 2,
"uint32_t" : 4,
"uint64_t" : 8,
"size_t" : 8,
"ssize_t" : 8,
}
if name in baseEncodingSizes:
return baseEncodingSizes[name]
category = self.typeCategories[name]
if category in [None, "api", "bitmask", "include", "define", "struct", "union"]:
return None
# Must be 8---handles are pointers and basetype includes VkDeviceSize
# which is 8 bytes
if category in ["handle", "basetype", "funcpointer"]:
return 8
# Most of the time, enums are only 4 bytes, but this is
# vague enough to be the source of a future headache, and
# it's easy to just stream 8 bytes there anyway.
if category in ["enum"]:
return 8
def isNonAbiPortableType(self, typeName):
if typeName in EXPLICITLY_ABI_PORTABLE_TYPES:
return False
if typeName in EXPLICITLY_ABI_NON_PORTABLE_TYPES:
return True
category = self.typeCategories[typeName]
return category in NON_ABI_PORTABLE_TYPE_CATEGORIES
def onBeginFeature(self, featureName):
self.feature = featureName
def onEndFeature(self):
self.feature = None
def onGenType(self, typeinfo, name, alias):
category = typeinfo.elem.get("category")
self.initType(name, category)
if category in ["struct", "union"]:
self.onGenStruct(typeinfo, name, alias)
def onGenStruct(self, typeinfo, typeName, alias):
if not alias:
members = []
structExtendsExpr = typeinfo.elem.get("structextends")
structEnumExpr = None
for member in typeinfo.elem.findall(".//member"):
vulkanType = makeVulkanTypeFromXMLTag(self, member)
members.append(vulkanType)
if vulkanType.typeName == "VkStructureType" and \
member.get("values"):
structEnumExpr = member.get("values")
self.structs[typeName] = \
VulkanCompoundType( \
typeName,
members,
isUnion = self.categoryOf(typeName) == "union",
structEnumExpr = structEnumExpr,
structExtendsExpr = structExtendsExpr,
feature = self.feature)
self.structs[typeName].initCopies()
def onGenGroup(self, _groupinfo, groupName, _alias=None):
self.initType(groupName, "enum")
def onGenEnum(self, _enuminfo, name, _alias):
self.initType(name, "enum")
def onGenCmd(self, cmdinfo, name, _alias):
self.initType(name, "api")
proto = cmdinfo.elem.find("proto")
params = cmdinfo.elem.findall("param")
self.apis[name] = \
VulkanAPI(
name,
makeVulkanTypeFromXMLTag(self, proto),
list(map(lambda p: makeVulkanTypeFromXMLTag(self, p),
params)))
self.apis[name].initCopies()
def onEnd(self,):
pass
# General function to iterate over a vulkan type and call code that processes
# each of its sub-components, if any.
def iterateVulkanType(typeInfo, vulkanType, forEachType):
if not vulkanType.isArrayOfStrings():
if vulkanType.isPointerToConstPointer:
return False
forEachType.registerTypeInfo(typeInfo)
needCheck = \
vulkanType.isOptional and \
vulkanType.pointerIndirectionLevels > 0 and \
(not vulkanType.isNextPointer())
if typeInfo.isCompoundType(vulkanType.typeName) and not vulkanType.isNextPointer():
if needCheck:
forEachType.onCheck(vulkanType)
forEachType.onCompoundType(vulkanType)
if needCheck:
forEachType.endCheck(vulkanType)
else:
if vulkanType.isString():
forEachType.onString(vulkanType)
elif vulkanType.isArrayOfStrings():
forEachType.onStringArray(vulkanType)
elif vulkanType.staticArrExpr:
forEachType.onStaticArr(vulkanType)
elif vulkanType.isNextPointer():
if needCheck:
forEachType.onCheck(vulkanType)
forEachType.onStructExtension(vulkanType)
if needCheck:
forEachType.endCheck(vulkanType)
elif vulkanType.pointerIndirectionLevels > 0:
if needCheck:
forEachType.onCheck(vulkanType)
forEachType.onPointer(vulkanType)
if needCheck:
forEachType.endCheck(vulkanType)
else:
forEachType.onValue(vulkanType)
return True
class VulkanTypeIterator(object):
def __init__(self,):
self.typeInfo = None
def registerTypeInfo(self, typeInfo):
self.typeInfo = typeInfo