blob: 8fb03fd965c58599544c641854021849488b61d4 [file] [log] [blame]
#!/usr/bin/python3 -i
#
# Copyright (c) 2021 The Khronos Group 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.
#
# Author: Spencer Fricke <s.fricke@samsung.com>
import os,re,sys,string,json
import xml.etree.ElementTree as etree
from generator import *
from collections import namedtuple
from common_codegen import *
# This is a workaround to use a Python 2.7 and 3.x compatible syntax
from io import open
class CommandValidationOutputGeneratorOptions(GeneratorOptions):
def __init__(self,
conventions = None,
filename = None,
directory = '.',
genpath = None,
apiname = 'vulkan',
profile = None,
versions = '.*',
emitversions = '.*',
defaultExtensions = 'vulkan',
addExtensions = None,
removeExtensions = None,
emitExtensions = None,
emitSpirv = None,
sortProcedure = regSortFeatures,
genFuncPointers = True,
protectFile = True,
protectFeature = False,
apicall = 'VKAPI_ATTR ',
apientry = 'VKAPI_CALL ',
apientryp = 'VKAPI_PTR *',
indentFuncProto = True,
indentFuncPointer = False,
alignFuncParam = 48,
expandEnumerants = False,
valid_usage_path = ''):
GeneratorOptions.__init__(self,
conventions = conventions,
filename = filename,
directory = directory,
genpath = genpath,
apiname = apiname,
profile = profile,
versions = versions,
emitversions = emitversions,
defaultExtensions = defaultExtensions,
addExtensions = addExtensions,
removeExtensions = removeExtensions,
emitExtensions = emitExtensions,
emitSpirv = emitSpirv,
sortProcedure = sortProcedure)
self.genFuncPointers = genFuncPointers
self.protectFile = protectFile
self.protectFeature = protectFeature
self.apicall = apicall
self.apientry = apientry
self.apientryp = apientryp
self.indentFuncProto = indentFuncProto
self.indentFuncPointer = indentFuncPointer
self.alignFuncParam = alignFuncParam
self.expandEnumerants = expandEnumerants
self.valid_usage_path = valid_usage_path
#
# CommandValidationOutputGenerator - Generate implicit vkCmd validation for CoreChecks
class CommandValidationOutputGenerator(OutputGenerator):
def __init__(self,
errFile = sys.stderr,
warnFile = sys.stderr,
diagFile = sys.stdout):
OutputGenerator.__init__(self, errFile, warnFile, diagFile)
self.valid_vuids = set() # Set of all valid VUIDs
self.vuid_dict = dict() # VUID dictionary (from JSON)
self.commands = dict() # dictionary of all vkCmd* calls to cmdInfo
self.alias_dict = dict() # Dict of cmd aliases
self.header_file = False # Header file generation flag
self.source_file = False # Source file generation flag
#
# Walk the JSON-derived dict and find all "vuid" key values
def ExtractVUIDs(self, d):
if hasattr(d, 'items'):
for k, v in d.items():
if k == "vuid":
yield v
elif isinstance(v, dict):
for s in self.ExtractVUIDs(v):
yield s
elif isinstance (v, list):
for l in v:
for s in self.ExtractVUIDs(l):
yield s
#
# Called at beginning of processing as file is opened
def beginFile(self, genOpts):
OutputGenerator.beginFile(self, genOpts)
self.header_file = (genOpts.filename == 'command_validation.h')
self.source_file = (genOpts.filename == 'command_validation.cpp')
if not self.header_file and not self.source_file:
print("Error: Output Filenames have changed, update generator source.\n")
sys.exit(1)
# Build a set of all vuid text strings found in validusage.json
self.valid_usage_path = genOpts.valid_usage_path
vu_json_filename = os.path.join(self.valid_usage_path + os.sep, 'validusage.json')
if os.path.isfile(vu_json_filename):
json_file = open(vu_json_filename, 'r', encoding='utf-8')
self.vuid_dict = json.load(json_file)
json_file.close()
if len(self.vuid_dict) == 0:
print("Error: Could not find, or error loading %s/validusage.json\n", vu_json_filename)
sys.exit(1)
for json_vuid_string in self.ExtractVUIDs(self.vuid_dict):
self.valid_vuids.add(json_vuid_string)
# File Comment
file_comment = '// *** THIS FILE IS GENERATED - DO NOT EDIT ***\n'
file_comment += '// See command_validation_generator.py for modifications\n'
write(file_comment, file=self.outFile)
# Copyright Statement
copyright = ''
copyright += '\n'
copyright += '/***************************************************************************\n'
copyright += ' *\n'
copyright += ' * Copyright (c) 2021 The Khronos Group Inc.\n'
copyright += ' *\n'
copyright += ' * Licensed under the Apache License, Version 2.0 (the "License");\n'
copyright += ' * you may not use this file except in compliance with the License.\n'
copyright += ' * You may obtain a copy of the License at\n'
copyright += ' *\n'
copyright += ' * http://www.apache.org/licenses/LICENSE-2.0\n'
copyright += ' *\n'
copyright += ' * Unless required by applicable law or agreed to in writing, software\n'
copyright += ' * distributed under the License is distributed on an "AS IS" BASIS,\n'
copyright += ' * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n'
copyright += ' * See the License for the specific language governing permissions and\n'
copyright += ' * limitations under the License.\n'
copyright += ' *\n'
copyright += ' * Author: Spencer Fricke <s.fricke@samsung.com>\n'
copyright += ' *\n'
copyright += ' ****************************************************************************/\n'
write(copyright, file=self.outFile)
if self.source_file:
write('#include "vk_layer_logging.h"', file=self.outFile)
write('#include "core_validation.h"', file=self.outFile)
elif self.header_file:
write('#pragma once', file=self.outFile)
write('#include <array>', file=self.outFile)
#
# Write generated file content to output file
def endFile(self):
if self.header_file:
write(self.commandTypeEnum(), file=self.outFile)
write(self.commandNameList(), file=self.outFile)
elif self.source_file:
write(self.commandRecordingList(), file=self.outFile)
write(self.commandQueueTypeList(), file=self.outFile)
write(self.commandRenderPassList(), file=self.outFile)
write(self.commandBufferLevelList(), file=self.outFile)
write(self.validateFunction(), file=self.outFile)
# Finish processing in superclass
OutputGenerator.endFile(self)
#
# Retrieve the type and name for a parameter
def getTypeNameTuple(self, param):
type = ''
name = ''
for elem in param:
if elem.tag == 'type':
type = noneStr(elem.text)
elif elem.tag == 'name':
name = noneStr(elem.text)
return (type, name)
#
# Capture command parameter info to be used for param check code generation.
def genCmd(self, cmdinfo, name, alias):
OutputGenerator.genCmd(self, cmdinfo, name, alias)
# Get first param type
params = cmdinfo.elem.findall('param')
info = self.getTypeNameTuple(params[0])
if name.startswith('vkCmd') and info[0] == 'VkCommandBuffer':
self.commands[name] = cmdinfo
if alias is not None:
self.alias_dict[name] = alias
#
# List the enum for the commands
def commandTypeEnum(self):
output = '''
// Used as key for maps of all vkCmd* calls
// Does not include vkBeginCommandBuffer/vkEndCommandBuffer
typedef enum CMD_TYPE {
CMD_NONE = 0,\n'''
counter = 1
for name, cmdinfo in sorted(self.commands.items()):
output += ' CMD_' + name[5:].upper() + ' = ' + str(counter) + ',\n'
counter += 1
output += ' CMD_RANGE_SIZE = ' + str(counter)
output += '''
} CMD_TYPE;'''
return output
#
# For each CMD_TYPE give a string name
def commandNameList(self):
output = '''
static const std::array<const char *, CMD_RANGE_SIZE> kGeneratedCommandNameList = {{
"Command_Undefined",\n'''
for name, cmdinfo in sorted(self.commands.items()):
output += ' \"' + name + '\",\n'
output += '}};'
return output
#
# For each CMD_TYPE give a string name add a *-recording VUID
# Each vkCmd* will have one
def commandRecordingList(self):
output = '''
static const std::array<const char *, CMD_RANGE_SIZE> kGeneratedMustBeRecordingList = {{
kVUIDUndefined,\n'''
for name, cmdinfo in sorted(self.commands.items()):
if name in self.alias_dict:
name = self.alias_dict[name]
vuid = 'VUID-' + name + '-commandBuffer-recording'
if vuid not in self.valid_vuids:
print("Warning: Could not find {} in validusage.json".format(vuid))
vuid = vuid.replace('VUID-', 'UNASSIGNED-')
output += ' \"' + vuid + '\",\n'
output += '}};'
return output
#
# For each CMD_TYPE give a queue type and string name add a *-commandBuffer-cmdpool VUID
# Each vkCmd* will have one
def commandQueueTypeList(self):
output = '''
struct CommandSupportedQueueType {
VkQueueFlags flags;
const char* vuid;
};
static const std::array<CommandSupportedQueueType, CMD_RANGE_SIZE> kGeneratedQueueTypeList = {{
{VK_QUEUE_FLAG_BITS_MAX_ENUM, kVUIDUndefined},\n'''
for name, cmdinfo in sorted(self.commands.items()):
if name in self.alias_dict:
name = self.alias_dict[name]
flags = []
queues = cmdinfo.elem.attrib.get('queues').split(',')
for queue in queues:
if queue == 'graphics':
flags.append("VK_QUEUE_GRAPHICS_BIT")
elif queue == 'compute':
flags.append("VK_QUEUE_COMPUTE_BIT")
elif queue == 'transfer':
flags.append("VK_QUEUE_TRANSFER_BIT")
elif queue == 'sparse_binding':
flags.append("VK_QUEUE_SPARSE_BINDING_BIT")
elif queue == 'protected':
flags.append("VK_QUEUE_PROTECTED_BIT")
elif queue == 'decode':
flags.append("VK_QUEUE_VIDEO_DECODE_BIT_KHR")
elif queue == 'encode':
flags.append("VK_QUEUE_VIDEO_ENCODE_BIT_KHR")
else:
print("A new queue type %s was added to VkQueueFlagBits and need to update generation code", queue)
sys.exit(1)
vuid = 'VUID-' + name + '-commandBuffer-cmdpool'
if vuid not in self.valid_vuids:
print("Warning: Could not find {} in validusage.json".format(vuid))
vuid = vuid.replace('VUID-', 'UNASSIGNED-')
output += ' {' + ' | '.join(flags) + ', \"' + vuid + '\"},\n'
output += '}};'
return output
#
# For each CMD_TYPE give a the renderpass restriction and a *-renderpass VUID
def commandRenderPassList(self):
output = '''
enum CMD_RENDER_PASS_TYPE {
CMD_RENDER_PASS_INSIDE,
CMD_RENDER_PASS_OUTSIDE,
CMD_RENDER_PASS_BOTH
};
struct CommandSupportedRenderPass {
CMD_RENDER_PASS_TYPE renderPass;
const char* vuid;
};
static const std::array<CommandSupportedRenderPass, CMD_RANGE_SIZE> kGeneratedRenderPassList = {{
{CMD_RENDER_PASS_BOTH, kVUIDUndefined}, // CMD_NONE\n'''
for name, cmdinfo in sorted(self.commands.items()):
if name in self.alias_dict:
name = self.alias_dict[name]
render_pass_type = ''
render_pass = cmdinfo.elem.attrib.get('renderpass')
if render_pass == 'inside':
render_pass_type = 'CMD_RENDER_PASS_INSIDE'
elif render_pass == 'outside':
render_pass_type = 'CMD_RENDER_PASS_OUTSIDE'
elif render_pass != 'both':
print("renderpass attribute was %s and not known, need to update generation code", renderpass)
sys.exit(1)
# Only will be a VUID if not BOTH
if render_pass == 'both':
output += ' {CMD_RENDER_PASS_BOTH, kVUIDUndefined},\n'
else:
vuid = 'VUID-' + name + '-renderpass'
if vuid not in self.valid_vuids:
print("Warning: Could not find {} in validusage.json".format(vuid))
vuid = vuid.replace('VUID-', 'UNASSIGNED-')
output += ' {' + render_pass_type + ', \"' + vuid + '\"},\n'
output += '}};'
return output
#
# For each CMD_TYPE give a buffer level restriction and add a *-bufferlevel VUID
def commandBufferLevelList(self):
output = '''
static const std::array<const char *, CMD_RANGE_SIZE> kGeneratedBufferLevelList = {{
kVUIDUndefined, // CMD_NONE\n'''
for name, cmdinfo in sorted(self.commands.items()):
if name in self.alias_dict:
name = self.alias_dict[name]
buffer_level = cmdinfo.elem.attrib.get('cmdbufferlevel')
# Currently there is only "primary" or "primary,secondary" in XML
# Hard to predict what might change, so will error out instead if assumption breaks
if buffer_level == "primary,secondary":
output += ' nullptr,\n'
elif buffer_level == "primary":
vuid = 'VUID-' + name + '-bufferlevel'
if vuid not in self.valid_vuids:
print("Warning: Could not find {} in validusage.json".format(vuid))
vuid = vuid.replace('VUID-', 'UNASSIGNED-')
output += ' \"' + vuid + '\",\n'
else:
print("cmdbufferlevel attribute was %s and not known, need to update generation code", buffer_level)
sys.exit(1)
output += '}};'
return output
#
# The main function to validate all the commands
def validateFunction(self):
output = '''
// Used to handle all the implicit VUs that are autogenerated from the registry
bool CoreChecks::ValidateCmd(const CMD_BUFFER_STATE *cb_state, const CMD_TYPE cmd) const {
bool skip = false;
const char *caller_name = CommandTypeString(cmd);
// Validate the given command being added to the specified cmd buffer,
// flagging errors if CB is not in the recording state or if there's an issue with the Cmd ordering
switch (cb_state->state) {
case CB_RECORDING:
skip |= ValidateCmdSubpassState(cb_state, cmd);
break;
case CB_INVALID_COMPLETE:
case CB_INVALID_INCOMPLETE:
skip |= ReportInvalidCommandBuffer(cb_state, caller_name);
break;
default:
assert(cmd != CMD_NONE);
const auto error = kGeneratedMustBeRecordingList[cmd];
skip |= LogError(cb_state->commandBuffer(), error, "You must call vkBeginCommandBuffer() before this call to %s.",
caller_name);
}
// Validate the command pool from which the command buffer is from that the command is allowed for queue type
const auto supportedQueueType = kGeneratedQueueTypeList[cmd];
skip |= ValidateCmdQueueFlags(cb_state, caller_name, supportedQueueType.flags, supportedQueueType.vuid);
// Validate if command is inside or outside a render pass if applicable
const auto supportedRenderPass = kGeneratedRenderPassList[cmd];
if (supportedRenderPass.renderPass == CMD_RENDER_PASS_INSIDE) {
skip |= OutsideRenderPass(cb_state, caller_name, supportedRenderPass.vuid);
} else if (supportedRenderPass.renderPass == CMD_RENDER_PASS_OUTSIDE) {
skip |= InsideRenderPass(cb_state, caller_name, supportedRenderPass.vuid);
}
// Validate if command has to be recorded in a primary command buffer
const auto supportedBufferLevel = kGeneratedBufferLevelList[cmd];
if (supportedBufferLevel != nullptr) {
skip |= ValidatePrimaryCommandBuffer(cb_state, caller_name, supportedBufferLevel);
}
return skip;
}'''
return output