Skip to content

Commit

Permalink
Vulkan generator 3 (#1098)
Browse files Browse the repository at this point in the history
Implements function pointer parsing from Vulkan XML.
  • Loading branch information
yalcinmelihyasin committed Apr 29, 2022
1 parent 2658bb3 commit c57ee8f
Show file tree
Hide file tree
Showing 7 changed files with 248 additions and 8 deletions.
97 changes: 97 additions & 0 deletions vulkan_generator/tests/test_funcptr_parsing.py
@@ -0,0 +1,97 @@
# Copyright (C) 2022 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.

"""
This package is responsible for testing Vulkan Parser
Examples in this files stems from vk.xml that relesed by Khronos.
Anytime the particular xml updated, test should be checked
if they reflect the new XML
"""

import xml.etree.ElementTree as ET

from vulkan_parser import funcptr_parser
from vulkan_parser import types


def test_vulkan_func_pointer() -> None:
"""""Test the parsing of a function pointer"""
xml = """<?xml version="1.0" encoding="UTF-8"?>
<type category="funcpointer">typedef void (VKAPI_PTR *
<name>PFN_vkInternalAllocationNotification</name>)(
<type>void</type>* pUserData,
<type>size_t</type> size,
<type>VkInternalAllocationType</type> allocationType,
<type>VkSystemAllocationScope</type> allocationScope);</type>
"""
funcptr = funcptr_parser.parse(ET.fromstring(xml))

assert isinstance(funcptr, types.VulkanFunctionPtr)

assert funcptr.typename == "PFN_vkInternalAllocationNotification"
assert funcptr.return_type == "void"
assert len(funcptr.arguments) == 4

assert funcptr.arguments[0].typename == "void*"
assert funcptr.arguments[0].argument_name == "pUserData"

assert funcptr.arguments[1].typename == "size_t"
assert funcptr.arguments[1].argument_name == "size"

assert funcptr.arguments[2].typename == "VkInternalAllocationType"
assert funcptr.arguments[2].argument_name == "allocationType"

assert funcptr.arguments[3].typename == "VkSystemAllocationScope"
assert funcptr.arguments[3].argument_name == "allocationScope"


def test_vulkan_func_pointer_with_pointer_return_value() -> None:
"""""Test the parsing of a function pointer with a pointer return type"""
xml = """<?xml version="1.0" encoding="UTF-8"?>
<type category="funcpointer">typedef void* (VKAPI_PTR *
<name>PFN_vkReallocationFunction</name>)(
<type>void</type>* pUserData,
<type>void</type>* pOriginal,
<type>size_t</type> size,
<type>size_t</type> alignment,
<type>VkSystemAllocationScope</type> allocationScope);</type>
"""

funcptr = funcptr_parser.parse(ET.fromstring(xml))

assert isinstance(funcptr, types.VulkanFunctionPtr)
assert funcptr.return_type == "void*"


def test_vulkan_func_pointer_with_const_member() -> None:
"""""Test the parsing of a function pointer with a const pointer argument"""

xml = """<?xml version="1.0" encoding="UTF-8"?>
<type category="funcpointer">typedef VkBool32 (VKAPI_PTR *
<name>PFN_vkDebugReportCallbackEXT</name>)(
<type>VkDebugReportFlagsEXT</type> flags,
<type>VkDebugReportObjectTypeEXT</type> objectType,
<type>uint64_t</type> object,
<type>size_t</type> location,
<type>int32_t</type> messageCode,
const <type>char</type>* pLayerPrefix,
const <type>char</type>* pMessage,
<type>void</type>* pUserData);</type>
"""

funcptr = funcptr_parser.parse(ET.fromstring(xml))

assert funcptr.arguments[4].argument_name == "messageCode"
assert funcptr.arguments[5].typename == "const char*"
3 changes: 3 additions & 0 deletions vulkan_generator/vulkan_generator.py
Expand Up @@ -38,6 +38,9 @@ def print_vulkan_metaadata(vulkan_metadata: type_parser.AllVulkanTypes) -> None:
print("=== Vulkan Struct Aliases ===")
pretty_printer.pprint(vulkan_metadata.struct_aliases)

print("=== Vulkan Function Pointers ===")
pretty_printer.pprint(vulkan_metadata.funcpointers)


def generate(vulkan_xml_path: Path) -> bool:
""" Generator function """
Expand Down
100 changes: 100 additions & 0 deletions vulkan_generator/vulkan_parser/funcptr_parser.py
@@ -0,0 +1,100 @@
# Copyright (C) 2022 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.

""" This module is responsible for parsing Vulkan function pointers"""

from typing import List
import xml.etree.ElementTree as ET

from vulkan_parser import types
from vulkan_utils import parsing_utils


def parse_arguments(function_ptr_elem: ET.Element) -> List[types.VulkanFunctionArgument]:
"""Parses the arguments of a Vulkan Function Pointer"""
arguments: List[types.VulkanFunctionArgument] = []

# In the XML const modifier of the type is part of the
# previous argument of the function
# <type>int32_t</type> messageCode,
# const <type> char </type> * pLayerPrefix,
is_next_type_const = False
for elem in function_ptr_elem:
# Skip the name tag
if elem.tag != "type":
continue

typename = parsing_utils.clean_type_string(
parsing_utils.get_text_from_tag(elem, "type"))
argument_name = parsing_utils.clean_type_string(
parsing_utils.get_tail_from_tag(elem, "type"))

# Multiple const are not supported
if argument_name.count("const") > 1:
raise SyntaxError(f"Double const are not supported: {typename} {argument_name}")

# Multiple pointers are not supported
if argument_name.count("*") > 1:
raise SyntaxError(f"Double pointers are not supported: {typename} {argument_name}")

# This means previous argument has the const modifier for this type
if is_next_type_const:
typename = f"const {typename}"
is_next_type_const = False

if "const" in argument_name:
if not argument_name.endswith("const"):
raise SyntaxError(f"""This is probably a const pointer which is not supported:
{typename} {argument_name}""")

is_next_type_const = True
argument_name = argument_name.replace("const", "")

# Pointers of the type is actually in the argument name
if "*" in argument_name:
typename = typename + "*"
argument_name = argument_name[1:]

arguments.append(types.VulkanFunctionArgument(
typename=typename,
argument_name=argument_name,
))

return arguments


def parse(func_ptr_elem: ET.Element) -> types.VulkanFunctionPtr:
"""Returns a Vulkan function pointer from the XML element that defines it.
A sample Vulkan function_pointer:
< type category="funcpointer" > typedef void(VKAPI_PTR *
<name > PFN_vkInternalAllocationNotification < /name > )(
< type > void < /type > * pUserData,
< type > size_t < /type > size,
< type > VkInternalAllocationType < /type > allocationType,
< type > VkSystemAllocationScope < /type > allocationScope); < /type >
"""

function_name = parsing_utils.get_text_from_tag(func_ptr_elem, "name")

# Return type is in the type tag's text field with some extra information
# e.g typedef void (VKAPI_PTR *
return_type = parsing_utils.get_text_from_tag(func_ptr_elem, "type")
# remove the function pointer boilers around type
return_type = return_type.split("(")[0]
return_type = return_type.replace("typedef", "")
return_type = parsing_utils.clean_type_string(return_type)

arguments = parse_arguments(func_ptr_elem)
return types.VulkanFunctionPtr(function_name, return_type, arguments)
2 changes: 1 addition & 1 deletion vulkan_generator/vulkan_parser/struct_parser.py
Expand Up @@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

""" This module is responsible for parsing Vulkan Handles and aliases of them"""
""" This module is responsible for parsing Vulkan structs and aliases of them"""

from typing import Dict
import xml.etree.ElementTree as ET
Expand Down
18 changes: 16 additions & 2 deletions vulkan_generator/vulkan_parser/type_parser.py
Expand Up @@ -23,6 +23,7 @@

from vulkan_parser import handle_parser
from vulkan_parser import struct_parser
from vulkan_parser import funcptr_parser
from vulkan_parser import types


Expand All @@ -49,9 +50,12 @@ class AllVulkanTypes:
struct_aliases: Dict[str, types.VulkanStructAlias] = field(
default_factory=dict)

funcpointers: Dict[str, types.VulkanFunctionPtr] = field(
default_factory=dict)


def process_handle(vulkan_types: AllVulkanTypes, handle_element: ET.Element) -> None:
""" Parse the Vulkan type "Handle". This can be an handle or an alias to another handle """
""" Parse the Vulkan type "Handle". This can be an handle or an alias to another handle"""
handle = handle_parser.parse(handle_element)

if isinstance(handle, types.VulkanHandle):
Expand All @@ -61,7 +65,7 @@ def process_handle(vulkan_types: AllVulkanTypes, handle_element: ET.Element) ->


def process_struct(vulkan_types: AllVulkanTypes, struct_element: ET.Element) -> None:
""" Parse the Vulkan type "Struct". This can be a struct or an alias to another struct """
""" Parse the Vulkan type "Struct". This can be a struct or an alias to another struct"""
vulkan_struct = struct_parser.parse(struct_element)

if isinstance(vulkan_struct, types.VulkanStruct):
Expand All @@ -70,6 +74,14 @@ def process_struct(vulkan_types: AllVulkanTypes, struct_element: ET.Element) ->
vulkan_types.struct_aliases[vulkan_struct.typename] = vulkan_struct


def process_funcpointer(vulkan_types: AllVulkanTypes, func_ptr_element: ET.Element) -> None:
""" Parse the Vulkan type "Funcpointer"""
vulkan_func_ptr = funcptr_parser.parse(func_ptr_element)

if isinstance(vulkan_func_ptr, types.VulkanFunctionPtr):
vulkan_types.funcpointers[vulkan_func_ptr.typename] = vulkan_func_ptr


def parse(types_root: ET.Element) -> AllVulkanTypes:
""" Parses all the Vulkan types and returns them in an object with dictionaries to each type """
vulkan_types = AllVulkanTypes()
Expand All @@ -81,5 +93,7 @@ def parse(types_root: ET.Element) -> AllVulkanTypes:
process_handle(vulkan_types, type_element)
elif type_category == "struct":
process_struct(vulkan_types, type_element)
elif type_category == "funcpointer":
process_funcpointer(vulkan_types, type_element)

return vulkan_types
18 changes: 17 additions & 1 deletion vulkan_generator/vulkan_parser/types.py
Expand Up @@ -16,7 +16,9 @@

from dataclasses import dataclass
from dataclasses import field
from typing import Dict, Optional
from typing import Dict
from typing import List
from typing import Optional


@dataclass
Expand Down Expand Up @@ -80,3 +82,17 @@ class VulkanStruct(VulkanType):
class VulkanStructAlias(VulkanType):
"""The meta data defines a Vulkan Handle alias"""
aliased_typename: str


@dataclass
class VulkanFunctionArgument(VulkanType):
"""The meta data defines a Function argument"""
argument_name: str


@dataclass
class VulkanFunctionPtr(VulkanType):
"""The meta data defines a Function Pointer"""
return_type: str
arguments: List[VulkanFunctionArgument] = field(
default_factory=list)
18 changes: 14 additions & 4 deletions vulkan_generator/vulkan_utils/parsing_utils.py
Expand Up @@ -14,9 +14,9 @@

"""This module contains the utility functions that needed elsewhere while parsing Vulkan XML"""

import os
from typing import Optional
import xml.etree.ElementTree as ET
from typing import Optional
import os


def get_text_from_tag(elem: ET.Element, tag: str) -> str:
Expand All @@ -25,8 +25,7 @@ def get_text_from_tag(elem: ET.Element, tag: str) -> str:

if value is None:
# This should not happen
raise SyntaxError(
f"No {tag} tag found in {ET.tostring(elem, 'utf-8')}")
raise SyntaxError(f"No {tag} tag found in {ET.tostring(elem, 'utf-8')}")

return value

Expand All @@ -41,6 +40,17 @@ def try_get_text_from_tag(elem: ET.Element, tag: str) -> Optional[str]:
return None


def get_tail_from_tag(elem: ET.Element, tag: str) -> str:
"""Gets the tail of the element with the given tag"""
value = try_get_tail_from_tag(elem, tag)

if value is None:
# This should not happen
raise SyntaxError(f"No tail found in {ET.tostring(elem, 'utf-8')}")

return value


def try_get_tail_from_tag(elem: ET.Element, tag: str) -> Optional[str]:
"""Tries to Gets the text of the element with the given tag
and returns None if the tag does not exits"""
Expand Down

0 comments on commit c57ee8f

Please sign in to comment.