const path = require('path');
const fs = require('fs');
const XML = require('pixl-xml')
const { EXTENSIONS } = require('./data');
const DOWNLOAD_DIR_PATH = path.join(__dirname, '..', 'download');
const VULKAN_CORE_H_PATH = path.join(DOWNLOAD_DIR_PATH, `vulkan_core.h`);
const VK_XML_PATH = path.join(DOWNLOAD_DIR_PATH, `vk.xml`);
let VULKAN_H = null;
let VK_XML_STR = null;
let VK_XML = null;
let ENUMS = null;
let BIT_FLAGS = null;
let STRUCTS = null;
let HANDLES = null;
let FUNCTIONS = null;
let EXTENSION_NAMES = null;
let TYPEDEFS = null;
let BOOTSTRAP_DONE = false;
function bootstrap() {
if (!BOOTSTRAP_DONE) {
VULKAN_H = fs.readFileSync(VULKAN_CORE_H_PATH, 'utf8');
VK_XML_STR = fs.readFileSync(VK_XML_PATH, 'utf8').replace(/->/g, '::');
VK_XML = XML.parse(VK_XML_STR);
ENUMS = parseEnums();
BIT_FLAGS = parseBitFlags();
STRUCTS = parseStructs();
HANDLES = parseHandles();
FUNCTIONS = parseFunctions();
EXTENSION_NAMES = parseExtensionNames();
TYPEDEFS = parseTypedefs();
BOOTSTRAP_DONE = true;
}
}
function getByName(obj, typeName) {
const { name, extension } = parseName(typeName);
return obj[extension][name];
}
function getAll(obj) {
bootstrap();
return Object.values(obj).reduce((acc, subObj) => acc.concat(Object.values(subObj)), []);
}
function get(obj, type) {
return (obj[type.extension] || {})[type.typeName];
}
function isAllowedFunction(func) {
return true;
const allowedExtensions = ['EXT', 'KHR'];
const hasExtension = /[A-Z]{2}$/.test(func.name);
return !hasExtension || allowedExtensions.some(ext => func.name.endsWith(ext));
}
function getAllEnums() { return getAll(ENUMS); }
function getAllBitFlags() { return getAll(BIT_FLAGS); }
function getAllStructs() { return getAll(STRUCTS); }
function getAllHandles() { return getAll(HANDLES); }
function getAllFunctions() { bootstrap(); return FUNCTIONS.slice().filter(isAllowedFunction); }
function getAllExtensionNames() { bootstrap(); return EXTENSION_NAMES.slice(); }
function getAllTypedefs() { bootstrap(); return TYPEDEFS.slice(); }
function getEnumByName(name) { return getByName(ENUMS, name); }
function getBitFlagsByName(name) { return getByName(BIT_FLAGS, name); }
function getStructByName(name) { return getByName(STRUCTS, name); }
function getHandleByName(name) { return getByName(HANDLES, name); }
function getEnum(type) { return get(ENUMS, type); }
function getBitFlags(type) { return get(BIT_FLAGS, type); }
function getStruct(type) { return get(STRUCTS, type); }
function getHandle(type) { return get(HANDLES, type); }
function isHandle(name) { return !!getByName(HANDLES, name); }
function parseName(str) {
let extension = '';
for (let ext of EXTENSIONS) {
if (str.endsWith(ext)) {
extension = ext.toLowerCase();
}
}
return {
extension: extension,
name: str.substring(0, str.length - extension.length)
};
}
function listToObj(array) {
const types = {};
array.forEach(type => {
const byExtension = types[type.extension] || (types[type.extension] = {});
byExtension[type.name] = type;
});
return types;
}
function parseField(str) {
const match = str.match(/\s*([\w* ]+)\s+(\w+)(?:\[(\w+)\])?(?:\[(\w+)\])?(:\d+)?\s*;?\s*$/);
if (!match) {
return null;
}
const fullType = match[1].replace('FlagBits', 'Flags').replace(' struct ', ' ').trim();
const name = match[2].trim();
const fieldName = fullType.replace(/\*\*$/, '').replace(/ const\*$/, '').replace(/(?:const )?(\w+)\*?/, '$1');
const fieldTypeNameInfo = parseName(fieldName);
const typeName = fieldTypeNameInfo.name;
const extension = fieldTypeNameInfo.extension;
const isPointer = fullType.endsWith('*');
const isDoublePointer = fullType.endsWith(' const*') || fullType.endsWith('**');
const isConst = fullType.startsWith('const ');
const arraySizeIdentifier1 = match[3];
const arraySizeIdentifier2 = match[4];
const arraySize1 = parseConstant(arraySizeIdentifier1);
const arraySize2 = parseConstant(arraySizeIdentifier2);
const countFor = [];
let arraySize = null;
if (arraySize1) {
if (!arraySize2) {
arraySize = arraySize1;
} else {
arraySize = arraySize1 * arraySize2;
}
}
return { name, extension, fullType, typeName, isPointer, isDoublePointer, isConst, arraySize, countFor };
}
function parseExtensionNames() {
const regexp = /#define \w+\s+"\w+"/g;
const match = VULKAN_H.match(regexp);
const extensionNames = match.map(str => {
const [_, name, value] = str.split(/\s+/)
return { name, value };
});
return extensionNames;
}
function parseTypedefs() {
const regexp = /typedef\s+\w+\s+\w+;/g;
const match = VULKAN_H.match(regexp);
return match.map(str => {
const [_, baseType, newType] = str.substring(0, str.indexOf(';')).split(/\s+/);
if (baseType.startsWith('uint') || baseType === 'VkFlags' || baseType.endsWith('FlagBits')) {
return null;
}
return {
baseType: parseName(baseType),
newType: parseName(newType)
};
}).filter(x => x);
}
function parseEnums() {
const regexp = /typedef enum \w+ {\n([^}]+)\n}/gmi;
const match = VULKAN_H.match(regexp);
const enums = match.map(str => {
const structName = str.split(' ', 3)[2];
const structNameInfo = parseName(structName);
const name = structNameInfo.name;
const extension = structNameInfo.extension;
const fieldsStr = str.substring(str.indexOf('{') + 2, str.indexOf('}') - 1);
if (name.endsWith('FlagBits')) {
return null;
}
const fields = fieldsStr.split('\n').map(line => {
const match = line.match(/^\s*([0-9A-Z_]+)\s*=\s*(-?(?:0x)?\d+),?$/);
if (!match) {
return null;
}
return {
name: match[1].trim(),
value: match[2].trim()
};
}).filter(x => x);
return { name, extension, fields };
}).filter(x => x);
return listToObj(enums);
}
function parseBitFlags() {
const defined = {};
const flagBitsRegexp = /typedef enum \w+FlagBits[A-Z]* {\n([^}]+)\n}/gmi;
const match = VULKAN_H.match(flagBitsRegexp);
match.forEach(str => {
const name = str.split(' ', 3)[2];
const fieldsStr = str.substring(str.indexOf('{') + 2, str.indexOf('}') - 1);
const fields = fieldsStr.split('\n').map(line => {
const match = line.match(/^\s*([0-9A-Z_]+)\s*=\s*(0x[\dA-F]{8})|([A-Z_]+)|(0),?\s*$/);
if (!match) {
throw new Error(`for enum ${name}: unexpected field "${line}"`);
}
return {
name: match[1],
value: match[2] || match[3] || match[4]
};
}).filter(({value}) => value !== '0x7FFFFFFF' && value.startsWith('0x'));
defined[name] = fields;
});
const flagsRegexp = /typedef VkFlags \w+;/g
const match2 = VULKAN_H.match(flagsRegexp);
const bitFlags = match2.map(str => {
const fullName = str.substring(str.lastIndexOf(' ') + 1, str.indexOf(';'));
const flagBitsName = fullName.replace('Flags', 'FlagBits');
const nameInfo = parseName(fullName);
const fields = defined[flagBitsName] || [];
return {
name: nameInfo.name,
extension: nameInfo.extension,
fields: fields
};
});
return listToObj(bitFlags);
}
function parseHandles() {
const regexp = /(VK_DEFINE_HANDLE|VK_DEFINE_NON_DISPATCHABLE_HANDLE)\(\w+\)\n/gm;
const match = VULKAN_H.match(regexp);
const handles = match.map(line => {
const handleName = line.substring(line.indexOf('(') + 1, line.indexOf(')'));
const nameInfo = parseName(handleName);
return {
name: nameInfo.name,
extension: nameInfo.extension
};
});
return listToObj(handles);
}
function parseConstant(name) {
if (!name) {
return null;
}
if (!isNaN(+name)) {
return name;
}
const match = VULKAN_H.match(new RegExp(`#define\\s+${name}\\s+([0-9.]+)`));
if (!match) {
throw new Error(`cannot find constant ${name}`);
}
return match[1];
}
function parseStructs() {
const regexp = /typedef struct \w+ {\n([^}]+)\n}/gmi;
const match = VULKAN_H.match(regexp);
const structs = match.map(str => {
const structName = str.split(' ', 3)[2];
const structNameInfo = parseName(structName);
const name = structNameInfo.name;
const extension = structNameInfo.extension;
const fieldsStr = str.substring(str.indexOf('{') + 2, str.indexOf('}') - 1);
const xmlDef = VK_XML.types.type.find(def => def.name === structName);
if (!Array.isArray(xmlDef.member)) {
xmlDef.member = [xmlDef.member];
}
const fields = fieldsStr.split('\n').filter(x => x).map(line => {
const fieldInfo = parseField(line);
if (!fieldInfo) {
throw new Error(`unexpected line for struct ${structName}: "${line}"`);
}
return fieldInfo;
});
let lastField = null;
for (let field of fields) {
const xmlMember = xmlDef.member.find(member => member.name === field.name);
field.values = xmlMember.values;
if (xmlMember.name === 'pCode') {
xmlMember.len = "codeSize";
}
field.isOptional = !!xmlMember.optional;
field.countField = (xmlMember.len || '').split(',').find(str => fields.some(field => field.name === str));
if (areCountAndArray(lastField, field)) {
field.countField = lastField.name;
}
lastField = field;
}
for (let field of fields) {
if (structName !== 'VkDescriptorSetLayoutBinding' || field.name !== 'descriptorCount') {
field.countFor = fields.filter(otherField => otherField.countField === field.name).map(f => f.name);
}
}
return { name, extension, fields };
});
return listToObj(structs);
}
function areCountAndArray(field1, field2) {
return field1 && field2 &&
(
(
(field1.name === 'dataSize' || field1.name === 'pDataSize') &&
field2.name === 'pData'
)
||
(
field1.name.startsWith(field2.name.substring(0, field2.name.length - 1)) &&
field1.name.endsWith('Count') &&
field1.fullType === 'uint32_t'
)
);
}
function parseFunctions() {
const regexp = /(?:VKAPI_ATTR\s+)?(VkResult|void)\s+(?:VKAPI_CALL\s+)?(\w+)\s*\(([^;]+)\)/gm;
const match = VULKAN_H.match(regexp);
const functions = match.map(str => {
const words = str.replace(/VKAPI_ATTR|VKAPI_CALL/g, '').trim().split(/\W+/, 2);
const type = words[0];
const name = words[1];
const args = str.substring(str.indexOf('(') + 1, str.indexOf(')')).split(',').map(x => x.trim()).map(argStr => {
const argInfo = parseField(argStr);
if (!argInfo) {
throw new Error(`unexpected arg "${argStr}" for function "${name}"`);
}
return argInfo;
});
let xml = VK_XML.commands.command.find(c => (c.proto && c.proto.name === name) || c.name === name);
let successCodes = null;
let errorCodes = null;
if (xml) {
if (xml.alias) {
return null;
xml = VK_XML.commands.command.find(c => c.proto && c.proto.name === xml.alias)
}
successCodes = (xml.successcodes ? xml.successcodes.split(',') : []);
errorCodes = (xml.errorcodes ? xml.errorcodes.split(',') : []);
const xmlParams = Array.isArray(xml.param) ? xml.param : [xml.param];
if (!xmlParams) {
throw new Error(`function ${name} does not have a xml`)
}
let lastArg = null;
for (let arg of args) {
const xmlParam = xmlParams.find(p => p.name === arg.name);
if (!xmlParam) {
throw new Error(`function "${name}": missing xml parameter ${arg.name}`);
}
arg.values = xmlParam.values;
arg.isOptional = !!xmlParam.optional;
arg.countField = (xmlParam.len || '').split(',').find(str => args.some(arg => arg.name === str || str.startsWith(`${arg.name}::`)));
if (areCountAndArray(lastArg, arg)) {
arg.countField = lastArg.name;
}
lastArg = arg;
}
for (let arg of args) {
arg.countFor = args.filter(otherArg => otherArg.countField === arg.name).map(a => a.name);
}
}
return { name, type, args, successCodes, errorCodes };
}).filter(f => f);
return functions;
}
function getExtensions() {
return EXTENSIONS.slice();
}
module.exports = {
getAllEnums,
getAllBitFlags,
getAllStructs,
getAllHandles,
getAllFunctions,
getAllExtensionNames,
getAllTypedefs,
getEnumByName,
getBitFlagsByName,
getStructByName,
getHandleByName,
getEnum,
getBitFlags,
getStruct,
getHandle,
isHandle,
getExtensions
};