layers: Consolidate thread-safety header files
Combine thread-safety.h and thread_safety_validation.h to allow for
more code-gen.
Change-Id: I5b6535d550efbc5c45c49c0058fd07b6dc895208
diff --git a/scripts/layer_chassis_generator.py b/scripts/layer_chassis_generator.py
index f36c132..4a9171f 100644
--- a/scripts/layer_chassis_generator.py
+++ b/scripts/layer_chassis_generator.py
@@ -373,7 +373,7 @@
#include "object_lifetime_validation.h"
#define OBJECT_LAYER_NAME "VK_LAYER_LUNARG_object_tracker"
#elif BUILD_THREAD_SAFETY
-#include "thread_safety_validation.h"
+#include "thread_safety.h"
#define OBJECT_LAYER_NAME "VK_LAYER_GOOGLE_threading"
#elif BUILD_PARAMETER_VALIDATION
#define OBJECT_LAYER_NAME "VK_LAYER_LUNARG_parameter_validation"
diff --git a/scripts/thread_safety_generator.py b/scripts/thread_safety_generator.py
index ae80b91..f3a434c 100644
--- a/scripts/thread_safety_generator.py
+++ b/scripts/thread_safety_generator.py
@@ -140,6 +140,413 @@
* Author: Mark Lobodzinski <mark@lunarg.com>
*/"""
+ inline_custom_header_preamble = """
+#pragma once
+
+#include <condition_variable>
+#include <mutex>
+#include <vector>
+#include <unordered_set>
+#include <string>
+
+VK_DEFINE_NON_DISPATCHABLE_HANDLE(DISTINCT_NONDISPATCHABLE_PHONY_HANDLE)
+// The following line must match the vulkan_core.h condition guarding VK_DEFINE_NON_DISPATCHABLE_HANDLE
+#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || \
+ defined(_M_IA64) || defined(__aarch64__) || defined(__powerpc64__)
+// If pointers are 64-bit, then there can be separate counters for each
+// NONDISPATCHABLE_HANDLE type. Otherwise they are all typedef uint64_t.
+#define DISTINCT_NONDISPATCHABLE_HANDLES
+// Make sure we catch any disagreement between us and the vulkan definition
+static_assert(std::is_pointer<DISTINCT_NONDISPATCHABLE_PHONY_HANDLE>::value,
+ "Mismatched non-dispatchable handle handle, expected pointer type.");
+#else
+// Make sure we catch any disagreement between us and the vulkan definition
+static_assert(std::is_same<uint64_t, DISTINCT_NONDISPATCHABLE_PHONY_HANDLE>::value,
+ "Mismatched non-dispatchable handle handle, expected uint64_t.");
+#endif
+
+// Suppress unused warning on Linux
+#if defined(__GNUC__)
+#define DECORATE_UNUSED __attribute__((unused))
+#else
+#define DECORATE_UNUSED
+#endif
+
+// clang-format off
+static const char DECORATE_UNUSED *kVUID_Threading_Info = "UNASSIGNED-Threading-Info";
+static const char DECORATE_UNUSED *kVUID_Threading_MultipleThreads = "UNASSIGNED-Threading-MultipleThreads";
+static const char DECORATE_UNUSED *kVUID_Threading_SingleThreadReuse = "UNASSIGNED-Threading-SingleThreadReuse";
+// clang-format on
+
+#undef DECORATE_UNUSED
+
+struct object_use_data {
+ loader_platform_thread_id thread;
+ int reader_count;
+ int writer_count;
+};
+
+template <typename T>
+class counter {
+public:
+ const char *typeName;
+ VkDebugReportObjectTypeEXT objectType;
+ debug_report_data **report_data;
+ std::unordered_map<T, object_use_data> uses;
+ std::mutex counter_lock;
+ std::condition_variable counter_condition;
+
+
+ void StartWrite(T object) {
+ if (object == VK_NULL_HANDLE) {
+ return;
+ }
+ bool skip = false;
+ loader_platform_thread_id tid = loader_platform_get_thread_id();
+ std::unique_lock<std::mutex> lock(counter_lock);
+ if (uses.find(object) == uses.end()) {
+ // There is no current use of the object. Record writer thread.
+ struct object_use_data *use_data = &uses[object];
+ use_data->reader_count = 0;
+ use_data->writer_count = 1;
+ use_data->thread = tid;
+ } else {
+ struct object_use_data *use_data = &uses[object];
+ if (use_data->reader_count == 0) {
+ // There are no readers. Two writers just collided.
+ if (use_data->thread != tid) {
+ skip |= log_msg(*report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, objectType, (uint64_t)(object),
+ kVUID_Threading_MultipleThreads,
+ "THREADING ERROR : object of type %s is simultaneously used in "
+ "thread 0x%" PRIx64 " and thread 0x%" PRIx64,
+ typeName, (uint64_t)use_data->thread, (uint64_t)tid);
+ if (skip) {
+ // Wait for thread-safe access to object instead of skipping call.
+ while (uses.find(object) != uses.end()) {
+ counter_condition.wait(lock);
+ }
+ // There is now no current use of the object. Record writer thread.
+ struct object_use_data *new_use_data = &uses[object];
+ new_use_data->thread = tid;
+ new_use_data->reader_count = 0;
+ new_use_data->writer_count = 1;
+ } else {
+ // Continue with an unsafe use of the object.
+ use_data->thread = tid;
+ use_data->writer_count += 1;
+ }
+ } else {
+ // This is either safe multiple use in one call, or recursive use.
+ // There is no way to make recursion safe. Just forge ahead.
+ use_data->writer_count += 1;
+ }
+ } else {
+ // There are readers. This writer collided with them.
+ if (use_data->thread != tid) {
+ skip |= log_msg(*report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, objectType, (uint64_t)(object),
+ kVUID_Threading_MultipleThreads,
+ "THREADING ERROR : object of type %s is simultaneously used in "
+ "thread 0x%" PRIx64 " and thread 0x%" PRIx64,
+ typeName, (uint64_t)use_data->thread, (uint64_t)tid);
+ if (skip) {
+ // Wait for thread-safe access to object instead of skipping call.
+ while (uses.find(object) != uses.end()) {
+ counter_condition.wait(lock);
+ }
+ // There is now no current use of the object. Record writer thread.
+ struct object_use_data *new_use_data = &uses[object];
+ new_use_data->thread = tid;
+ new_use_data->reader_count = 0;
+ new_use_data->writer_count = 1;
+ } else {
+ // Continue with an unsafe use of the object.
+ use_data->thread = tid;
+ use_data->writer_count += 1;
+ }
+ } else {
+ // This is either safe multiple use in one call, or recursive use.
+ // There is no way to make recursion safe. Just forge ahead.
+ use_data->writer_count += 1;
+ }
+ }
+ }
+ }
+
+ void FinishWrite(T object) {
+ if (object == VK_NULL_HANDLE) {
+ return;
+ }
+ // Object is no longer in use
+ std::unique_lock<std::mutex> lock(counter_lock);
+ uses[object].writer_count -= 1;
+ if ((uses[object].reader_count == 0) && (uses[object].writer_count == 0)) {
+ uses.erase(object);
+ }
+ // Notify any waiting threads that this object may be safe to use
+ lock.unlock();
+ counter_condition.notify_all();
+ }
+
+ void StartRead(T object) {
+ if (object == VK_NULL_HANDLE) {
+ return;
+ }
+ bool skip = false;
+ loader_platform_thread_id tid = loader_platform_get_thread_id();
+ std::unique_lock<std::mutex> lock(counter_lock);
+ if (uses.find(object) == uses.end()) {
+ // There is no current use of the object. Record reader count
+ struct object_use_data *use_data = &uses[object];
+ use_data->reader_count = 1;
+ use_data->writer_count = 0;
+ use_data->thread = tid;
+ } else if (uses[object].writer_count > 0 && uses[object].thread != tid) {
+ // There is a writer of the object.
+ skip |= false;
+ log_msg(*report_data, VK_DEBUG_REPORT_ERROR_BIT_EXT, objectType, (uint64_t)(object), kVUID_Threading_MultipleThreads,
+ "THREADING ERROR : object of type %s is simultaneously used in "
+ "thread 0x%" PRIx64 " and thread 0x%" PRIx64,
+ typeName, (uint64_t)uses[object].thread, (uint64_t)tid);
+ if (skip) {
+ // Wait for thread-safe access to object instead of skipping call.
+ while (uses.find(object) != uses.end()) {
+ counter_condition.wait(lock);
+ }
+ // There is no current use of the object. Record reader count
+ struct object_use_data *use_data = &uses[object];
+ use_data->reader_count = 1;
+ use_data->writer_count = 0;
+ use_data->thread = tid;
+ } else {
+ uses[object].reader_count += 1;
+ }
+ } else {
+ // There are other readers of the object. Increase reader count
+ uses[object].reader_count += 1;
+ }
+ }
+ void FinishRead(T object) {
+ if (object == VK_NULL_HANDLE) {
+ return;
+ }
+ std::unique_lock<std::mutex> lock(counter_lock);
+ uses[object].reader_count -= 1;
+ if ((uses[object].reader_count == 0) && (uses[object].writer_count == 0)) {
+ uses.erase(object);
+ }
+ // Notify any waiting threads that this object may be safe to use
+ lock.unlock();
+ counter_condition.notify_all();
+ }
+ counter(const char *name = "", VkDebugReportObjectTypeEXT type = VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT, debug_report_data **rep_data = nullptr) {
+ typeName = name;
+ objectType = type;
+ report_data = rep_data;
+ }
+};
+
+
+
+class ThreadSafety : public ValidationObject {
+public:
+
+ // Override chassis read/write locks for this validation object
+ void write_lock() {}
+ void write_unlock() {}
+
+ std::mutex command_pool_lock;
+ std::unordered_map<VkCommandBuffer, VkCommandPool> command_pool_map;
+
+ counter<VkCommandBuffer> c_VkCommandBuffer;
+ counter<VkDevice> c_VkDevice;
+ counter<VkInstance> c_VkInstance;
+ counter<VkQueue> c_VkQueue;
+#ifdef DISTINCT_NONDISPATCHABLE_HANDLES
+
+ // Special entry to allow tracking of command pool Reset and Destroy
+ counter<VkCommandPool> c_VkCommandPoolContents;
+ counter<VkBuffer> c_VkBuffer;
+ counter<VkBufferView> c_VkBufferView;
+ counter<VkCommandPool> c_VkCommandPool;
+ counter<VkDescriptorPool> c_VkDescriptorPool;
+ counter<VkDescriptorSet> c_VkDescriptorSet;
+ counter<VkDescriptorSetLayout> c_VkDescriptorSetLayout;
+ counter<VkDeviceMemory> c_VkDeviceMemory;
+ counter<VkEvent> c_VkEvent;
+ counter<VkFence> c_VkFence;
+ counter<VkFramebuffer> c_VkFramebuffer;
+ counter<VkImage> c_VkImage;
+ counter<VkImageView> c_VkImageView;
+ counter<VkPipeline> c_VkPipeline;
+ counter<VkPipelineCache> c_VkPipelineCache;
+ counter<VkPipelineLayout> c_VkPipelineLayout;
+ counter<VkQueryPool> c_VkQueryPool;
+ counter<VkRenderPass> c_VkRenderPass;
+ counter<VkSampler> c_VkSampler;
+ counter<VkSemaphore> c_VkSemaphore;
+ counter<VkShaderModule> c_VkShaderModule;
+ counter<VkDebugReportCallbackEXT> c_VkDebugReportCallbackEXT;
+ counter<VkObjectTableNVX> c_VkObjectTableNVX;
+ counter<VkIndirectCommandsLayoutNVX> c_VkIndirectCommandsLayoutNVX;
+ counter<VkDisplayKHR> c_VkDisplayKHR;
+ counter<VkDisplayModeKHR> c_VkDisplayModeKHR;
+ counter<VkSurfaceKHR> c_VkSurfaceKHR;
+ counter<VkSwapchainKHR> c_VkSwapchainKHR;
+ counter<VkDescriptorUpdateTemplateKHR> c_VkDescriptorUpdateTemplateKHR;
+ counter<VkValidationCacheEXT> c_VkValidationCacheEXT;
+ counter<VkSamplerYcbcrConversionKHR> c_VkSamplerYcbcrConversionKHR;
+ counter<VkDebugUtilsMessengerEXT> c_VkDebugUtilsMessengerEXT;
+ counter<VkAccelerationStructureNV> c_VkAccelerationStructureNV;
+
+#else // DISTINCT_NONDISPATCHABLE_HANDLES
+ // Special entry to allow tracking of command pool Reset and Destroy
+ counter<uint64_t> c_VkCommandPoolContents;
+
+ counter<uint64_t> c_uint64_t;
+#endif // DISTINCT_NONDISPATCHABLE_HANDLES
+
+ ThreadSafety()
+ : c_VkCommandBuffer("VkCommandBuffer", VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT, &report_data),
+ c_VkDevice("VkDevice", VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_EXT, &report_data),
+ c_VkInstance("VkInstance", VK_DEBUG_REPORT_OBJECT_TYPE_INSTANCE_EXT, &report_data),
+ c_VkQueue("VkQueue", VK_DEBUG_REPORT_OBJECT_TYPE_QUEUE_EXT, &report_data),
+ c_VkCommandPoolContents("VkCommandPool", VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_POOL_EXT, &report_data),
+
+#ifdef DISTINCT_NONDISPATCHABLE_HANDLES
+ c_VkBuffer("VkBuffer", VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT, &report_data),
+ c_VkBufferView("VkBufferView", VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_VIEW_EXT, &report_data),
+ c_VkCommandPool("VkCommandPool", VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_POOL_EXT, &report_data),
+ c_VkDescriptorPool("VkDescriptorPool", VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_POOL_EXT, &report_data),
+ c_VkDescriptorSet("VkDescriptorSet", VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_EXT, &report_data),
+ c_VkDescriptorSetLayout("VkDescriptorSetLayout", VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT_EXT, &report_data),
+ c_VkDeviceMemory("VkDeviceMemory", VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_MEMORY_EXT, &report_data),
+ c_VkEvent("VkEvent", VK_DEBUG_REPORT_OBJECT_TYPE_EVENT_EXT, &report_data),
+ c_VkFence("VkFence", VK_DEBUG_REPORT_OBJECT_TYPE_FENCE_EXT, &report_data),
+ c_VkFramebuffer("VkFramebuffer", VK_DEBUG_REPORT_OBJECT_TYPE_FRAMEBUFFER_EXT, &report_data),
+ c_VkImage("VkImage", VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT, &report_data),
+ c_VkImageView("VkImageView", VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_VIEW_EXT, &report_data),
+ c_VkPipeline("VkPipeline", VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT, &report_data),
+ c_VkPipelineCache("VkPipelineCache", VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_CACHE_EXT, &report_data),
+ c_VkPipelineLayout("VkPipelineLayout", VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_LAYOUT_EXT, &report_data),
+ c_VkQueryPool("VkQueryPool", VK_DEBUG_REPORT_OBJECT_TYPE_QUERY_POOL_EXT, &report_data),
+ c_VkRenderPass("VkRenderPass", VK_DEBUG_REPORT_OBJECT_TYPE_RENDER_PASS_EXT, &report_data),
+ c_VkSampler("VkSampler", VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_EXT, &report_data),
+ c_VkSemaphore("VkSemaphore", VK_DEBUG_REPORT_OBJECT_TYPE_SEMAPHORE_EXT, &report_data),
+ c_VkShaderModule("VkShaderModule", VK_DEBUG_REPORT_OBJECT_TYPE_SHADER_MODULE_EXT, &report_data),
+ c_VkDebugReportCallbackEXT("VkDebugReportCallbackEXT", VK_DEBUG_REPORT_OBJECT_TYPE_DEBUG_REPORT_EXT, &report_data),
+ c_VkObjectTableNVX("VkObjectTableNVX", VK_DEBUG_REPORT_OBJECT_TYPE_OBJECT_TABLE_NVX_EXT, &report_data),
+ c_VkIndirectCommandsLayoutNVX("VkIndirectCommandsLayoutNVX",
+ VK_DEBUG_REPORT_OBJECT_TYPE_INDIRECT_COMMANDS_LAYOUT_NVX_EXT, &report_data),
+ c_VkDisplayKHR("VkDisplayKHR", VK_DEBUG_REPORT_OBJECT_TYPE_DISPLAY_KHR_EXT, &report_data),
+ c_VkDisplayModeKHR("VkDisplayModeKHR", VK_DEBUG_REPORT_OBJECT_TYPE_DISPLAY_MODE_KHR_EXT, &report_data),
+ c_VkSurfaceKHR("VkSurfaceKHR", VK_DEBUG_REPORT_OBJECT_TYPE_SURFACE_KHR_EXT, &report_data),
+ c_VkSwapchainKHR("VkSwapchainKHR", VK_DEBUG_REPORT_OBJECT_TYPE_SWAPCHAIN_KHR_EXT, &report_data),
+ c_VkDescriptorUpdateTemplateKHR("VkDescriptorUpdateTemplateKHR",
+ VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_KHR_EXT, &report_data),
+ c_VkSamplerYcbcrConversionKHR("VkSamplerYcbcrConversionKHR",
+ VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION_KHR_EXT, &report_data),
+ c_VkDebugUtilsMessengerEXT("VkDebugUtilsMessengerEXT", VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT, &report_data),
+ c_VkAccelerationStructureNV("VkAccelerationStructureNV", VK_DEBUG_REPORT_OBJECT_TYPE_ACCELERATION_STRUCTURE_NV_EXT, &report_data)
+
+#else // DISTINCT_NONDISPATCHABLE_HANDLES
+ c_uint64_t("NON_DISPATCHABLE_HANDLE", VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT, &report_data)
+#endif // DISTINCT_NONDISPATCHABLE_HANDLES
+ {};
+
+#define WRAPPER(type) \
+ void StartWriteObject(type object) { \
+ c_##type.StartWrite(object); \
+ } \
+ void FinishWriteObject(type object) { \
+ c_##type.FinishWrite(object); \
+ } \
+ void StartReadObject(type object) { \
+ c_##type.StartRead(object); \
+ } \
+ void FinishReadObject(type object) { \
+ c_##type.FinishRead(object); \
+ }
+
+WRAPPER(VkDevice)
+WRAPPER(VkInstance)
+WRAPPER(VkQueue)
+#ifdef DISTINCT_NONDISPATCHABLE_HANDLES
+
+WRAPPER(VkBuffer)
+WRAPPER(VkBufferView)
+WRAPPER(VkCommandPool)
+WRAPPER(VkDescriptorPool)
+WRAPPER(VkDescriptorSet)
+WRAPPER(VkDescriptorSetLayout)
+WRAPPER(VkDeviceMemory)
+WRAPPER(VkEvent)
+WRAPPER(VkFence)
+WRAPPER(VkFramebuffer)
+WRAPPER(VkImage)
+WRAPPER(VkImageView)
+WRAPPER(VkPipeline)
+WRAPPER(VkPipelineCache)
+WRAPPER(VkPipelineLayout)
+WRAPPER(VkQueryPool)
+WRAPPER(VkRenderPass)
+WRAPPER(VkSampler)
+WRAPPER(VkSemaphore)
+WRAPPER(VkShaderModule)
+WRAPPER(VkDebugReportCallbackEXT)
+WRAPPER(VkObjectTableNVX)
+WRAPPER(VkIndirectCommandsLayoutNVX)
+WRAPPER(VkDisplayKHR)
+WRAPPER(VkDisplayModeKHR)
+WRAPPER(VkSurfaceKHR)
+WRAPPER(VkSwapchainKHR)
+WRAPPER(VkDescriptorUpdateTemplateKHR)
+WRAPPER(VkValidationCacheEXT)
+WRAPPER(VkSamplerYcbcrConversionKHR)
+WRAPPER(VkDebugUtilsMessengerEXT)
+WRAPPER(VkAccelerationStructureNV)
+
+#else // DISTINCT_NONDISPATCHABLE_HANDLES
+WRAPPER(uint64_t)
+#endif // DISTINCT_NONDISPATCHABLE_HANDLES
+
+ // VkCommandBuffer needs check for implicit use of command pool
+ void StartWriteObject(VkCommandBuffer object, bool lockPool = true) {
+ if (lockPool) {
+ std::unique_lock<std::mutex> lock(command_pool_lock);
+ VkCommandPool pool = command_pool_map[object];
+ lock.unlock();
+ StartWriteObject(pool);
+ }
+ c_VkCommandBuffer.StartWrite(object);
+ }
+ void FinishWriteObject(VkCommandBuffer object, bool lockPool = true) {
+ c_VkCommandBuffer.FinishWrite(object);
+ if (lockPool) {
+ std::unique_lock<std::mutex> lock(command_pool_lock);
+ VkCommandPool pool = command_pool_map[object];
+ lock.unlock();
+ FinishWriteObject(pool);
+ }
+ }
+ void StartReadObject(VkCommandBuffer object) {
+ std::unique_lock<std::mutex> lock(command_pool_lock);
+ VkCommandPool pool = command_pool_map[object];
+ lock.unlock();
+ // We set up a read guard against the "Contents" counter to catch conflict vs. vkResetCommandPool and vkDestroyCommandPool
+ // while *not* establishing a read guard against the command pool counter itself to avoid false postives for
+ // non-externally sync'd command buffers
+ c_VkCommandPoolContents.StartRead(pool);
+ c_VkCommandBuffer.StartRead(object);
+ }
+ void FinishReadObject(VkCommandBuffer object) {
+ c_VkCommandBuffer.FinishRead(object);
+ std::unique_lock<std::mutex> lock(command_pool_lock);
+ VkCommandPool pool = command_pool_map[object];
+ lock.unlock();
+ c_VkCommandPoolContents.FinishRead(pool);
+ }"""
+
+
inline_custom_source_preamble = """
void ThreadSafety::PreCallRecordAllocateCommandBuffers(VkDevice device, const VkCommandBufferAllocateInfo *pAllocateInfo,
VkCommandBuffer *pCommandBuffers) {
@@ -370,9 +777,7 @@
#
# TODO: LUGMAL -- remove this and add our copyright
# User-supplied prefix text, if any (list of strings)
- if (genOpts.prefixText):
- for s in genOpts.prefixText:
- write(s, file=self.outFile)
+ write(self.inline_copyright_message, file=self.outFile)
self.header_file = (genOpts.filename == 'thread_safety.h')
self.source_file = (genOpts.filename == 'thread_safety.cpp')
@@ -383,13 +788,20 @@
if self.source_file:
write('#include "chassis.h"', file=self.outFile)
- write('#include "thread_safety_validation.h"', file=self.outFile)
+ write('#include "thread_safety.h"', file=self.outFile)
self.newline()
write(self.inline_custom_source_preamble, file=self.outFile)
+ else:
+ write(self.inline_custom_header_preamble, file=self.outFile)
+
def endFile(self):
+ if self.header_file:
+ write('};', file=self.outFile)
+
# Finish processing in superclass
OutputGenerator.endFile(self)
+
def beginFeature(self, interface, emit):
#write('// starting beginFeature', file=self.outFile)
# Start processing in superclass