blob: dc3a907285351295e1d6126da348336b8c75ece3 [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 typing import Dict, Optional, List, Set, Union
from xml.etree.ElementTree import Element
from generator import noneStr
from copy import copy
from string import whitespace
# 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",
"VkAccelerationStructureNV",
"VkIndirectCommandsLayoutNV",
"VkAccelerationStructureKHR",
]
CUSTOM_HANDLE_CREATE_TYPES = [
"VkPhysicalDevice",
"VkQueue",
"VkPipeline",
"VkDeviceMemory",
"VkDescriptorSet",
"VkCommandBuffer",
"VkRenderPass",
]
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", "vkGetDeviceQueue2"],
None)
if h == "VkPipeline":
HANDLE_INFO[h] = \
HandleInfo(
"VkPipeline",
["vkCreateGraphicsPipelines", "vkCreateComputePipelines"],
"vkDestroyPipeline")
if h == "VkDeviceMemory":
HANDLE_INFO[h] = \
HandleInfo("VkDeviceMemory",
"vkAllocateMemory", ["vkFreeMemory", "vkFreeMemorySyncGOOGLE"])
if h == "VkDescriptorSet":
HANDLE_INFO[h] = \
HandleInfo("VkDescriptorSet", "vkAllocateDescriptorSets",
"vkFreeDescriptorSets")
if h == "VkCommandBuffer":
HANDLE_INFO[h] = \
HandleInfo("VkCommandBuffer", "vkAllocateCommandBuffers",
"vkFreeCommandBuffers")
if h == "VkRenderPass":
HANDLE_INFO[h] = \
HandleInfo(
"VkRenderPass",
["vkCreateRenderPass", "vkCreateRenderPass2", "vkCreateRenderPass2KHR"],
"vkDestroyRenderPass")
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",
]
TRIVIAL_TRANSFORMED_TYPES = [
"VkPhysicalDeviceExternalImageFormatInfo",
"VkPhysicalDeviceExternalBufferInfo",
"VkExternalMemoryImageCreateInfo",
"VkExternalMemoryBufferCreateInfo",
"VkExportMemoryAllocateInfo",
"VkExternalImageFormatProperties",
"VkExternalBufferProperties",
]
NON_TRIVIAL_TRANSFORMED_TYPES = [
"VkExternalMemoryProperties",
"VkImageCreateInfo",
]
TRANSFORMED_TYPES = TRIVIAL_TRANSFORMED_TYPES + NON_TRIVIAL_TRANSFORMED_TYPES
# 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: Optional[VulkanType] = None
self.typeName: str = ""
self.isTransformed = False
self.paramName: Optional[str] = None
self.lenExpr: Optional[str] = None # Value of the `len` attribute in the spec
self.isOptional: bool = False
self.optionalStr: Optional[str] = None # Value of the `optional` attribute in the spec
self.isConst = False
# "" means it's not a static array, otherwise this is the total size of
# all elements. e.g. staticArrExpr of "x[3][2][8]" will be "((3)*(2)*(8))".
self.staticArrExpr = ""
# "" means it's not a static array, otherwise it's the raw expression
# of static array size, which can be one-dimensional or multi-dimensional.
self.rawStaticArrExpr = ""
self.pointerIndirectionLevels = 0 # 0 means not pointer
self.isPointerToConstPointer = False
self.primitiveEncodingSize = None
self.deviceMemoryInfoParameterIndices = None
# Annotations
# Environment annotation for binding current
# variables to sub-structures
self.binds = {}
# Device memory annotations
# self.deviceMemoryAttrib/Val stores
# device memory info attributes from the XML.
# devicememoryhandle
# devicememoryoffset
# devicememorysize
# devicememorytypeindex
# devicememorytypebits
self.deviceMemoryAttrib = None
self.deviceMemoryVal = None
# Filter annotations
self.filterVar = None
self.filterVals = None
self.filterFunc = None
self.filterOtherwise = None
# Stream feature
self.streamFeature = None
# All other annotations
self.attribs = {}
self.nonDispatchableHandleCreate = False
self.nonDispatchableHandleDestroy = False
self.dispatchHandle = False
self.dispatchableHandleCreate = False
self.dispatchableHandleDestroy = False
def __str__(self,):
return ("(vulkantype %s %s paramName %s len %s optional? %s "
"staticArrExpr %s)") % (
self.typeName + ("*" * self.pointerIndirectionLevels) +
("ptr2constptr" if self.isPointerToConstPointer else ""), "const"
if self.isConst else "nonconst", self.paramName, self.lenExpr,
self.isOptional, self.staticArrExpr)
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):
return self.paramName == "pNext"
def isSigned(self):
return self.typeName in ["int", "int8_t", "int16_t", "int32_t", "int64_t"]
def isEnum(self, typeInfo):
return typeInfo.categoryOf(self.typeName) == "enum"
def isBitmask(self, typeInfo):
return typeInfo.categoryOf(self.typeName) == "enum"
# 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])
if self.typeName == "VkImage" and api.name == "vkCreateImageWithRequirementsGOOGLE":
return True
if self.typeName == "VkBuffer" and api.name == "vkCreateBufferWithRequirementsGOOGLE":
return True
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 getPrintFormatSpecifier(self):
kKnownTypePrintFormatSpecifiers = {
'float': '%f',
'int': '%d',
'int32_t': '%d',
'size_t': '%ld',
'uint16_t': '%d',
'uint32_t': '%d',
'uint64_t': '%ld',
'VkBool32': '%d',
'VkDeviceSize': '%ld',
'VkFormat': '%d',
'VkImageLayout': '%d',
}
if self.pointerIndirectionLevels > 0 or self.isHandleType():
return '%p'
if self.typeName in kKnownTypePrintFormatSpecifiers:
return kKnownTypePrintFormatSpecifiers[self.typeName]
if self.typeName.endswith('Flags'):
# Based on `typedef uint32_t VkFlags;`
return '%d'
return None
def isOptionalPointer(self) -> bool:
return self.isOptional and \
self.pointerIndirectionLevels > 0 and \
(not self.isNextPointer())
# Is an S-expression w/ the following spec:
# From https://gist.github.com/pib/240957
class Atom(object):
def __init__(self, name):
self.name = name
def __repr__(self,):
return self.name
def parse_sexp(sexp):
atom_end = set('()"\'') | set(whitespace)
stack, i, length = [[]], 0, len(sexp)
while i < length:
c = sexp[i]
reading = type(stack[-1])
if reading == list:
if c == '(': stack.append([])
elif c == ')':
stack[-2].append(stack.pop())
if stack[-1][0] == ('quote',): stack[-2].append(stack.pop())
elif c == '"': stack.append('')
elif c == "'": stack.append([('quote',)])
elif c in whitespace: pass
else: stack.append(Atom(c))
elif reading == str:
if c == '"':
stack[-2].append(stack.pop())
if stack[-1][0] == ('quote',): stack[-2].append(stack.pop())
elif c == '\\':
i += 1
stack[-1] += sexp[i]
else: stack[-1] += c
elif reading == Atom:
if c in atom_end:
atom = stack.pop()
if atom.name[0].isdigit(): stack[-1].append(eval(atom.name))
else: stack[-1].append(atom)
if stack[-1][0] == ('quote',): stack[-2].append(stack.pop())
continue
else: stack[-1] = Atom(stack[-1].name + c)
i += 1
return stack.pop()
class FuncExprVal(object):
def __init__(self, val):
self.val = val
def __repr__(self,):
return self.val.__repr__()
class FuncExpr(object):
def __init__(self, name, args):
self.name = name
self.args = args
def __repr__(self,):
if len(self.args) == 0:
return "(%s)" % (self.name.__repr__())
else:
return "(%s %s)" % (self.name.__repr__(), " ".join(map(lambda x: x.__repr__(), self.args)))
class FuncLambda(object):
def __init__(self, vs, body):
self.vs = vs
self.body = body
def __repr__(self,):
return "(L (%s) %s)" % (" ".join(map(lambda x: x.__repr__(), self.vs)), self.body.__repr__())
class FuncLambdaParam(object):
def __init__(self, name, typ):
self.name = name
self.typ = typ
def __repr__(self,):
return "%s : %s" % (self.name, self.typ)
def parse_func_expr(parsed_sexp):
if len(parsed_sexp) != 1:
print("Error: parsed # expressions != 1: %d" % (len(parsed_sexp)))
raise
e = parsed_sexp[0]
def parse_lambda_param(e):
return FuncLambdaParam(e[0].name, e[1].name)
def parse_one(exp):
if list == type(exp):
if "lambda" == exp[0].__repr__():
return FuncLambda(list(map(parse_lambda_param, exp[1])), parse_one(exp[2]))
else:
return FuncExpr(exp[0], list(map(parse_one, exp[1:])))
else:
return FuncExprVal(exp)
return parse_one(e)
def parseFilterFuncExpr(expr):
res = parse_func_expr(parse_sexp(expr))
print("parseFilterFuncExpr: parsed %s" % res)
return res
def parseLetBodyExpr(expr):
res = parse_func_expr(parse_sexp(expr))
print("parseLetBodyExpr: parsed %s" % res)
return res
def makeVulkanTypeFromXMLTag(typeInfo, tag: Element) -> VulkanType:
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.rawStaticArrExpr = noneStr(nametag.tail)
dimensions = res.rawStaticArrExpr.count('[')
if dimensions == 1:
res.staticArrExpr = res.rawStaticArrExpr[1:-1]
elif dimensions > 1:
arraySizes = res.rawStaticArrExpr[1:-1].split('][')
res.staticArrExpr = '(' + \
'*'.join(f'({size})' for size in arraySizes) + ')'
# 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
res.optionalStr = tag.attrib.get("optional")
# 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)
# Annotations: Environment binds
if tag.attrib.get("binds") is not None:
bindPairs = map(lambda x: x.strip(), tag.attrib.get("binds").split(","))
bindPairsSplit = map(lambda p: p.split(":"), bindPairs)
res.binds = dict(map(lambda sp: (sp[0].strip(), sp[1].strip()), bindPairsSplit))
# Annotations: Device memory
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
# Annotations: Filters
if tag.attrib.get("filterVar") is not None:
res.filterVar = tag.attrib.get("filterVar").strip()
if tag.attrib.get("filterVals") is not None:
res.filterVals = \
list(map(lambda v: v.strip(),
tag.attrib.get("filterVals").strip().split(",")))
print("Filtervals: %s" % res.filterVals)
if tag.attrib.get("filterFunc") is not None:
res.filterFunc = parseFilterFuncExpr(tag.attrib.get("filterFunc"))
if tag.attrib.get("filterOtherwise") is not None:
res.Otherwise = tag.attrib.get("filterOtherwise")
# store all other attribs here
res.attribs = dict(tag.attrib)
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: str, members: List[VulkanType], isUnion=False, structEnumExpr=None, structExtendsExpr=None, feature=None, initialEnv={}, optional=None):
self.name: str = name
self.typeName: str = name
self.members: List[VulkanType] = members
self.environment = initialEnv
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
self.optionalStr = optional
def initCopies(self):
self.copy = self
for m in self.members:
m.parent = self.copy
def getMember(self, memberName) -> Optional[VulkanType]:
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: str, retType: VulkanType, parameters, origName=None):
self.name: str = name
self.origName = name
self.retType: VulkanType = retType
self.parameters: List[VulkanType] = 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
def withCustomReturnType(self, retType):
res = self.getCopy()
res.retType = retType
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, generator):
self.generator = generator
self.categories: Set[str] = set([])
# Tracks what Vulkan type is part of what category.
self.typeCategories: Dict[str, str] = {}
# Tracks the primitive encoding size for each type, if applicable.
self.encodingSizes: Dict[str, Optional[int]] = {}
self.structs: Dict[str, VulkanCompoundType] = {}
self.apis: Dict[str, VulkanAPI] = {}
# Maps bitmask types to the enum type used for the flags
# E.g. "VkImageAspectFlags" -> "VkImageAspectFlagBits"
self.bitmasks: Dict[str, str] = {}
# Maps all enum names to their values.
# For aliases, the value is the name of the canonical enum
self.enumValues: Dict[str, Union[int, str]] = {}
self.feature = None
def initType(self, name: str, category: str):
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):
return self.typeCategories.get(name) == "handle"
def isCompoundType(self, name: str):
return self.typeCategories.get(name) in ["struct", "union"]
# Gets the best size in bytes
# for encoding/decoding a particular Vulkan type.
# If not applicable, returns None.
def setPrimitiveEncodingSize(self, name: str) -> Optional[int]:
baseEncodingSizes = {
"void": 8,
"char": 1,
"float": 4,
"uint8_t": 1,
"uint16_t": 2,
"uint32_t": 4,
"uint64_t": 8,
"int": 4,
"int8_t": 1,
"int16_t": 2,
"int32_t": 4,
"int64_t": 8,
"size_t": 8,
"ssize_t": 8,
"VkBool32": 4,
}
if name in baseEncodingSizes:
return baseEncodingSizes[name]
category = self.typeCategories[name]
if category in [None, "api", "include", "define", "struct", "union"]:
return None
# Handles are pointers so they must be 8 bytes. Basetype includes VkDeviceSize which is 8 bytes.
if category in ["handle", "basetype", "funcpointer"]:
return 8
if category in ["enum", "bitmask"]:
return 4
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, featureType):
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)
if category == "bitmask":
self.bitmasks[name] = typeinfo.elem.get("requires")
def onGenStruct(self, typeinfo, typeName, alias):
if not alias:
members: List[VulkanType] = []
structExtendsExpr = typeinfo.elem.get("structextends")
structEnumExpr = None
initialEnv = {}
envStr = typeinfo.elem.get("exists")
if envStr != None:
comma_separated = envStr.split(",")
name_type_pairs = map(lambda cs: tuple(map(lambda t: t.strip(), cs.split(":"))), comma_separated)
for (name, typ) in name_type_pairs:
initialEnv[name] = {
"type" : typ,
"binding" : None,
"structmember" : False,
"body" : None,
}
letenvStr = typeinfo.elem.get("let")
if letenvStr != None:
comma_separated = letenvStr.split(",")
name_body_pairs = map(lambda cs: tuple(map(lambda t: t.strip(), cs.split(":"))), comma_separated)
for (name, body) in name_body_pairs:
initialEnv[name] = {
"type" : "uint32_t",
"binding" : name,
"structmember" : False,
"body" : parseLetBodyExpr(body)
}
for member in typeinfo.elem.findall(".//member"):
vulkanType = makeVulkanTypeFromXMLTag(self, member)
initialEnv[vulkanType.paramName] = {
"type": vulkanType.typeName,
"binding": vulkanType.paramName,
"structmember": True,
"body": None,
}
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,
initialEnv = initialEnv,
optional = typeinfo.elem.get("optional", None))
self.structs[typeName].initCopies()
def onGenGroup(self, groupinfo, groupName, _alias=None):
self.initType(groupName, "enum")
enums = groupinfo.elem.findall("enum")
for enum in enums:
intVal, strVal = self.generator.enumToValue(enum, True)
self.enumValues[enum.get('name')] = intVal if intVal is not None else strVal
def onGenEnum(self, enuminfo, name: str, alias):
self.initType(name, "enum")
value: str = enuminfo.elem.get("value")
if value and value.isdigit():
self.enumValues[name] = int(value)
elif value and value[0] == '"' and value[-1] == '"':
self.enumValues[name] = value[1:-1]
elif alias is not None:
self.enumValues[name] = alias
else:
# There's about a dozen cases of using the bitwise NOT operator (e.g.: `(~0U)`, `(~0ULL)`)
# to concisely represent large values. Just ignore them for now.
# In the future, we can add a lookup table to convert these to int
return
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
def hasNullOptionalStringFeature(forEachType):
return (hasattr(forEachType, "onCheckWithNullOptionalStringFeature")) and \
(hasattr(forEachType, "endCheckWithNullOptionalStringFeature")) and \
(hasattr(forEachType, "finalCheckWithNullOptionalStringFeature"))
# General function to iterate over a vulkan type and call code that processes
# each of its sub-components, if any.
def iterateVulkanType(typeInfo: VulkanTypeInfo, vulkanType: VulkanType, forEachType):
if not vulkanType.isArrayOfStrings():
if vulkanType.isPointerToConstPointer:
return False
forEachType.registerTypeInfo(typeInfo)
needCheck = vulkanType.isOptionalPointer()
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():
if needCheck and hasNullOptionalStringFeature(forEachType):
forEachType.onCheckWithNullOptionalStringFeature(vulkanType)
forEachType.onString(vulkanType)
forEachType.endCheckWithNullOptionalStringFeature(vulkanType)
forEachType.onString(vulkanType)
forEachType.finalCheckWithNullOptionalStringFeature(vulkanType)
elif needCheck:
forEachType.onCheck(vulkanType)
forEachType.onString(vulkanType)
forEachType.endCheck(vulkanType)
else:
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
def vulkanTypeGetStructFieldLengthInfo(structInfo, vulkanType):
def getSpecialCaseVulkanStructFieldLength(structInfo, vulkanType):
cases = [
{
"structName": "VkShaderModuleCreateInfo",
"field": "pCode",
"lenExpr": "codeSize",
"postprocess": lambda expr: "(%s / 4)" % expr
},
{
"structName": "VkPipelineMultisampleStateCreateInfo",
"field": "pSampleMask",
"lenExpr": "rasterizationSamples",
"postprocess": lambda expr: "(((%s) + 31) / 32)" % expr
},
]
for c in cases:
if (structInfo.name, vulkanType.paramName) == (c["structName"], c["field"]):
return c
return None
specialCaseAccess = getSpecialCaseVulkanStructFieldLength(structInfo, vulkanType)
if specialCaseAccess is not None:
return specialCaseAccess
lenExpr = vulkanType.getLengthExpression()
if lenExpr is None:
return None
return {
"structName": structInfo.name,
"field": vulkanType.typeName,
"lenExpr": lenExpr,
"postprocess": lambda expr: expr}
class VulkanTypeProtobufInfo(object):
def __init__(self, typeInfo, structInfo, vulkanType):
self.needsMessage = typeInfo.isCompoundType(vulkanType.typeName)
self.isRepeatedString = vulkanType.isArrayOfStrings()
self.isString = vulkanType.isString() or (
vulkanType.typeName == "char" and (vulkanType.staticArrExpr != ""))
if structInfo is not None:
self.lengthInfo = vulkanTypeGetStructFieldLengthInfo(
structInfo, vulkanType)
else:
self.lengthInfo = vulkanType.getLengthExpression()
self.protobufType = None
self.origTypeCategory = typeInfo.categoryOf(vulkanType.typeName)
self.isExtensionStruct = \
vulkanType.typeName == "void" and \
vulkanType.pointerIndirectionLevels > 0 and \
vulkanType.paramName == "pNext"
if self.needsMessage:
return
if typeInfo.categoryOf(vulkanType.typeName) in ["enum", "bitmask"]:
self.protobufType = "uint32"
if typeInfo.categoryOf(vulkanType.typeName) in ["funcpointer", "handle", "define"]:
self.protobufType = "uint64"
if typeInfo.categoryOf(vulkanType.typeName) in ["basetype"]:
baseTypeMapping = {
"VkFlags" : "uint32",
"VkBool32" : "uint32",
"VkDeviceSize" : "uint64",
"VkSampleMask" : "uint32",
}
self.protobufType = baseTypeMapping[vulkanType.typeName]
if typeInfo.categoryOf(vulkanType.typeName) == None:
otherTypeMapping = {
"void" : "uint64",
"char" : "uint8",
"size_t" : "uint64",
"float" : "float",
"uint8_t" : "uint32",
"uint16_t" : "uint32",
"int32_t" : "int32",
"uint32_t" : "uint32",
"uint64_t" : "uint64",
"VkDeviceSize" : "uint64",
"VkSampleMask" : "uint32",
}
if vulkanType.typeName in otherTypeMapping:
self.protobufType = otherTypeMapping[vulkanType.typeName]
else:
self.protobufType = "uint64"
protobufCTypeMapping = {
"uint8" : "uint8_t",
"uint32" : "uint32_t",
"int32" : "int32_t",
"uint64" : "uint64_t",
"float" : "float",
"string" : "const char*",
}
self.protobufCType = protobufCTypeMapping[self.protobufType]