Use of MVKSmallVector between MVKPipeline and SPIRVReflection.

Make SPIRVReflection global functions inline and templated.
Remove SPIRVReflection.cpp.
Allow MVKSmallVector to be sorted by supporting
random access from MVKSmallVector::iterator.
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.h b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.h
index 78fff51..8dd5e2c 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.h
@@ -239,19 +239,21 @@
 	~MVKGraphicsPipeline() override;
 
 protected:
+	typedef MVKSmallVector<SPIRVShaderOutput, 32> SPIRVShaderOutputs;
+
     id<MTLRenderPipelineState> getOrCompilePipeline(MTLRenderPipelineDescriptor* plDesc, id<MTLRenderPipelineState>& plState);
     id<MTLComputePipelineState> getOrCompilePipeline(MTLComputePipelineDescriptor* plDesc, id<MTLComputePipelineState>& plState, const char* compilerType);
     void initMTLRenderPipelineState(const VkGraphicsPipelineCreateInfo* pCreateInfo, const SPIRVTessReflectionData& reflectData);
     void initMVKShaderConverterContext(SPIRVToMSLConversionConfiguration& _shaderContext, const VkGraphicsPipelineCreateInfo* pCreateInfo, const SPIRVTessReflectionData& reflectData);
     void addVertexInputToShaderConverterContext(SPIRVToMSLConversionConfiguration& shaderContext, const VkGraphicsPipelineCreateInfo* pCreateInfo);
-    void addPrevStageOutputToShaderConverterContext(SPIRVToMSLConversionConfiguration& shaderContext, std::vector<SPIRVShaderOutput>& outputs);
+    void addPrevStageOutputToShaderConverterContext(SPIRVToMSLConversionConfiguration& shaderContext, SPIRVShaderOutputs& outputs);
     MTLRenderPipelineDescriptor* newMTLRenderPipelineDescriptor(const VkGraphicsPipelineCreateInfo* pCreateInfo, const SPIRVTessReflectionData& reflectData);
     MTLRenderPipelineDescriptor* newMTLTessVertexStageDescriptor(const VkGraphicsPipelineCreateInfo* pCreateInfo, const SPIRVTessReflectionData& reflectData, SPIRVToMSLConversionConfiguration& shaderContext);
 	MTLComputePipelineDescriptor* newMTLTessControlStageDescriptor(const VkGraphicsPipelineCreateInfo* pCreateInfo, const SPIRVTessReflectionData& reflectData, SPIRVToMSLConversionConfiguration& shaderContext);
 	MTLRenderPipelineDescriptor* newMTLTessRasterStageDescriptor(const VkGraphicsPipelineCreateInfo* pCreateInfo, const SPIRVTessReflectionData& reflectData, SPIRVToMSLConversionConfiguration& shaderContext);
 	bool addVertexShaderToPipeline(MTLRenderPipelineDescriptor* plDesc, const VkGraphicsPipelineCreateInfo* pCreateInfo, SPIRVToMSLConversionConfiguration& shaderContext);
-	bool addTessCtlShaderToPipeline(MTLComputePipelineDescriptor* plDesc, const VkGraphicsPipelineCreateInfo* pCreateInfo, SPIRVToMSLConversionConfiguration& shaderContext, std::vector<SPIRVShaderOutput>& prevOutput);
-	bool addTessEvalShaderToPipeline(MTLRenderPipelineDescriptor* plDesc, const VkGraphicsPipelineCreateInfo* pCreateInfo, SPIRVToMSLConversionConfiguration& shaderContext, std::vector<SPIRVShaderOutput>& prevOutput);
+	bool addTessCtlShaderToPipeline(MTLComputePipelineDescriptor* plDesc, const VkGraphicsPipelineCreateInfo* pCreateInfo, SPIRVToMSLConversionConfiguration& shaderContext, SPIRVShaderOutputs& prevOutput);
+	bool addTessEvalShaderToPipeline(MTLRenderPipelineDescriptor* plDesc, const VkGraphicsPipelineCreateInfo* pCreateInfo, SPIRVToMSLConversionConfiguration& shaderContext, SPIRVShaderOutputs& prevOutput);
     bool addFragmentShaderToPipeline(MTLRenderPipelineDescriptor* plDesc, const VkGraphicsPipelineCreateInfo* pCreateInfo, SPIRVToMSLConversionConfiguration& shaderContext);
 	bool addVertexInputToPipeline(MTLRenderPipelineDescriptor* plDesc, const VkPipelineVertexInputStateCreateInfo* pVI, const SPIRVToMSLConversionConfiguration& shaderContext);
     void addTessellationToPipeline(MTLRenderPipelineDescriptor* plDesc, const SPIRVTessReflectionData& reflectData, const VkPipelineTessellationStateCreateInfo* pTS);
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm
index 89d30be..781681a 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm
@@ -606,9 +606,8 @@
 																					SPIRVToMSLConversionConfiguration& shaderContext) {
 	MTLComputePipelineDescriptor* plDesc = [MTLComputePipelineDescriptor new];		// retained
 
-	std::vector<SPIRVShaderOutput> vtxOutputs;
+	SPIRVShaderOutputs vtxOutputs;
 	std::string errorLog;
-	// Unfortunately, MoltenVKShaderConverter doesn't know about MVKSmallVector, so we can't use that here.
 	if (!getShaderOutputs(((MVKShaderModule*)_pVertexSS->module)->getSPIRV(), spv::ExecutionModelVertex, _pVertexSS->pName, vtxOutputs, errorLog) ) {
 		setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED, "Failed to get vertex outputs: %s", errorLog.c_str()));
 		return nil;
@@ -654,7 +653,7 @@
 																				  SPIRVToMSLConversionConfiguration& shaderContext) {
 	MTLRenderPipelineDescriptor* plDesc = [MTLRenderPipelineDescriptor new];	// retained
 
-	std::vector<SPIRVShaderOutput> tcOutputs;
+	SPIRVShaderOutputs tcOutputs;
 	std::string errorLog;
 	if (!getShaderOutputs(((MVKShaderModule*)_pTessCtlSS->module)->getSPIRV(), spv::ExecutionModelTessellationControl, _pTessCtlSS->pName, tcOutputs, errorLog) ) {
 		setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED, "Failed to get tessellation control outputs: %s", errorLog.c_str()));
@@ -823,7 +822,7 @@
 bool MVKGraphicsPipeline::addTessCtlShaderToPipeline(MTLComputePipelineDescriptor* plDesc,
 													 const VkGraphicsPipelineCreateInfo* pCreateInfo,
 													 SPIRVToMSLConversionConfiguration& shaderContext,
-													 std::vector<SPIRVShaderOutput>& vtxOutputs) {
+													 SPIRVShaderOutputs& vtxOutputs) {
 	shaderContext.options.entryPointStage = spv::ExecutionModelTessellationControl;
 	shaderContext.options.entryPointName = _pTessCtlSS->pName;
 	shaderContext.options.mslOptions.swizzle_buffer_index = _swizzleBufferIndex.stages[kMVKShaderStageTessCtl];
@@ -876,7 +875,7 @@
 bool MVKGraphicsPipeline::addTessEvalShaderToPipeline(MTLRenderPipelineDescriptor* plDesc,
 													  const VkGraphicsPipelineCreateInfo* pCreateInfo,
 													  SPIRVToMSLConversionConfiguration& shaderContext,
-													  std::vector<SPIRVShaderOutput>& tcOutputs) {
+													  SPIRVShaderOutputs& tcOutputs) {
 	shaderContext.options.entryPointStage = spv::ExecutionModelTessellationEvaluation;
 	shaderContext.options.entryPointName = _pTessEvalSS->pName;
 	shaderContext.options.mslOptions.swizzle_buffer_index = _swizzleBufferIndex.stages[kMVKShaderStageTessEval];
@@ -1241,7 +1240,7 @@
 
 // Initializes the vertex attributes in a shader converter context from the previous stage output.
 void MVKGraphicsPipeline::addPrevStageOutputToShaderConverterContext(SPIRVToMSLConversionConfiguration& shaderContext,
-                                                                     std::vector<SPIRVShaderOutput>& shaderOutputs) {
+                                                                     SPIRVShaderOutputs& shaderOutputs) {
     // Set the shader context vertex attribute information
     shaderContext.vertexAttributes.clear();
     uint32_t vaCnt = (uint32_t)shaderOutputs.size();
diff --git a/MoltenVK/MoltenVK/Utility/MVKSmallVector.h b/MoltenVK/MoltenVK/Utility/MVKSmallVector.h
index 39dd099..1d1612b 100755
--- a/MoltenVK/MoltenVK/Utility/MVKSmallVector.h
+++ b/MoltenVK/MoltenVK/Utility/MVKSmallVector.h
@@ -62,13 +62,15 @@
   Allocator  alc;

   

 public:

-  class iterator : public std::iterator<std::forward_iterator_tag, Type>

+  class iterator : public std::iterator<std::random_access_iterator_tag, Type>

   {

     const MVKSmallVectorImpl *vector;

     size_t               index;

 

   public:

-    iterator() = delete;

+    typedef typename std::iterator_traits<iterator>::difference_type diff_type;

+

+    iterator() : vector{ nullptr }, index{ 0 } { }

     iterator( const size_t _index, const MVKSmallVectorImpl &_vector ) : vector{ &_vector }, index{ _index } { }

 

     iterator &operator=( const iterator &it )

@@ -87,6 +89,23 @@
 

     iterator& operator++()      {                 ++index; return *this; }

     iterator  operator++( int ) { auto t = *this; ++index; return t; }

+    iterator& operator--()      {                 --index; return *this; }

+    iterator  operator--( int ) { auto t = *this; --index; return t; }

+

+    iterator operator+ (const diff_type n)   { return iterator( index + n, *vector ); }

+    iterator& operator+= (const diff_type n) { index += n; return *this; }

+    iterator operator- (const diff_type n)   { return iterator( index - n, *vector ); }

+    iterator& operator-= (const diff_type n) { index -= n; return *this; }

+

+    diff_type operator- (const iterator& it) { return index - it.index; }

+

+    bool operator< (const iterator& it)  { return index < it.index; }

+    bool operator<= (const iterator& it) { return index <= it.index; }

+    bool operator> (const iterator& it)  { return index > it.index; }

+    bool operator>= (const iterator& it) { return index >= it.index; }

+

+    const Type &operator[]( const diff_type i ) const { return vector->alc.ptr[index + i]; }

+    Type &operator[]( const diff_type i )             { return vector->alc.ptr[index + i]; }

 

     bool   is_valid()     const { return index < vector->alc.size(); }

     size_t get_position() const { return index; }

@@ -497,13 +516,15 @@
   Allocator  alc;

 

 public:

-  class iterator : public std::iterator<std::forward_iterator_tag, Type*>

+  class iterator : public std::iterator<std::random_access_iterator_tag, Type*>

   {

     MVKSmallVectorImpl *vector;

     size_t         index;

 

   public:

-    iterator() = delete;

+    typedef typename std::iterator_traits<iterator>::difference_type diff_type;

+

+    iterator() : vector{ nullptr }, index{ 0 } { }

     iterator( const size_t _index, MVKSmallVectorImpl &_vector ) : vector{ &_vector }, index{ _index } { }

 

     iterator &operator=( const iterator &it )

@@ -518,8 +539,25 @@
     bool operator==( const iterator &it ) const { return vector == it.vector && index == it.index; }

     bool operator!=( const iterator &it ) const { return vector != it.vector || index != it.index; }

 

-    iterator& operator++() { ++index; return *this; }

+    iterator& operator++()      { ++index; return *this; }

     iterator  operator++( int ) { auto t = *this; ++index; return t; }

+    iterator& operator--()      {                 --index; return *this; }

+    iterator  operator--( int ) { auto t = *this; --index; return t; }

+

+    iterator operator+ (const diff_type n)   { return iterator( index + n, *vector ); }

+    iterator& operator+= (const diff_type n) { index += n; return *this; }

+    iterator operator- (const diff_type n)   { return iterator( index - n, *vector ); }

+    iterator& operator-= (const diff_type n) { index -= n; return *this; }

+

+    diff_type operator- (const iterator& it) { return index - it.index; }

+

+    bool operator< (const iterator& it)  { return index < it.index; }

+    bool operator<= (const iterator& it) { return index <= it.index; }

+    bool operator> (const iterator& it)  { return index > it.index; }

+    bool operator>= (const iterator& it) { return index >= it.index; }

+

+    const Type &operator[]( const diff_type i ) const { return vector->alc.ptr[index + i]; }

+    Type &operator[]( const diff_type i )             { return vector->alc.ptr[index + i]; }

 

     bool   is_valid()     const { return index < vector->alc.size(); }

     size_t get_position() const { return index; }

diff --git a/MoltenVKShaderConverter/MoltenVKSPIRVToMSLConverter/SPIRVReflection.cpp b/MoltenVKShaderConverter/MoltenVKSPIRVToMSLConverter/SPIRVReflection.cpp
deleted file mode 100644
index 213b3ad..0000000
--- a/MoltenVKShaderConverter/MoltenVKSPIRVToMSLConverter/SPIRVReflection.cpp
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * SPIRVReflection.cpp
- *
- * Copyright (c) 2019-2020 Chip Davis for Codeweavers
- *
- * 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.
- */
-
-#include "SPIRVReflection.h"
-#include <SPIRV-Cross/spirv_parser.hpp>
-#include <SPIRV-Cross/spirv_reflect.hpp>
-
-namespace mvk {
-
-static const char missingPatchInputErr[] = "Neither tessellation shader specifies a patch input mode (Triangles, Quads, or Isolines).";
-static const char missingWindingErr[] = "Neither tessellation shader specifies a winding order mode (VertexOrderCw or VertexOrderCcw).";
-static const char missingPartitionErr[] = "Neither tessellation shader specifies a partition mode (SpacingEqual, SpacingFractionalOdd, or SpacingFractionalEven).";
-static const char missingOutputVerticesErr[] = "Neither tessellation shader specifies the number of output control points.";
-
-/** Given a tessellation control shader and a tessellation evaluation shader, both in SPIR-V format, returns tessellation reflection data. */
-bool getTessReflectionData(const std::vector<uint32_t>& tesc, const std::string& tescEntryName, const std::vector<uint32_t>& tese, const std::string& teseEntryName, SPIRVTessReflectionData& reflectData, std::string& errorLog) {
-#ifndef SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS
-	try {
-#endif
-		SPIRV_CROSS_NAMESPACE::CompilerReflection tescReflect(tesc);
-		SPIRV_CROSS_NAMESPACE::CompilerReflection teseReflect(tese);
-
-		if (!tescEntryName.empty()) {
-			tescReflect.set_entry_point(tescEntryName, spv::ExecutionModelTessellationControl);
-		}
-		if (!teseEntryName.empty()) {
-			teseReflect.set_entry_point(teseEntryName, spv::ExecutionModelTessellationEvaluation);
-		}
-
-		tescReflect.compile();
-		teseReflect.compile();
-
-		const SPIRV_CROSS_NAMESPACE::Bitset& tescModes = tescReflect.get_execution_mode_bitset();
-		const SPIRV_CROSS_NAMESPACE::Bitset& teseModes = teseReflect.get_execution_mode_bitset();
-
-		// Extract the parameters from the shaders.
-		if (tescModes.get(spv::ExecutionModeTriangles)) {
-			reflectData.patchKind = spv::ExecutionModeTriangles;
-		} else if (tescModes.get(spv::ExecutionModeQuads)) {
-			reflectData.patchKind = spv::ExecutionModeQuads;
-		} else if (tescModes.get(spv::ExecutionModeIsolines)) {
-			reflectData.patchKind = spv::ExecutionModeIsolines;
-		} else if (teseModes.get(spv::ExecutionModeTriangles)) {
-			reflectData.patchKind = spv::ExecutionModeTriangles;
-		} else if (teseModes.get(spv::ExecutionModeQuads)) {
-			reflectData.patchKind = spv::ExecutionModeQuads;
-		} else if (teseModes.get(spv::ExecutionModeIsolines)) {
-			reflectData.patchKind = spv::ExecutionModeIsolines;
-		} else {
-			errorLog = missingPatchInputErr;
-			return false;
-		}
-
-		if (tescModes.get(spv::ExecutionModeVertexOrderCw)) {
-			reflectData.windingOrder = spv::ExecutionModeVertexOrderCw;
-		} else if (tescModes.get(spv::ExecutionModeVertexOrderCcw)) {
-			reflectData.windingOrder = spv::ExecutionModeVertexOrderCcw;
-		} else if (teseModes.get(spv::ExecutionModeVertexOrderCw)) {
-			reflectData.windingOrder = spv::ExecutionModeVertexOrderCw;
-		} else if (teseModes.get(spv::ExecutionModeVertexOrderCcw)) {
-			reflectData.windingOrder = spv::ExecutionModeVertexOrderCcw;
-		} else {
-			errorLog = missingWindingErr;
-			return false;
-		}
-
-		reflectData.pointMode = tescModes.get(spv::ExecutionModePointMode) || teseModes.get(spv::ExecutionModePointMode);
-
-		if (tescModes.get(spv::ExecutionModeSpacingEqual)) {
-			reflectData.partitionMode = spv::ExecutionModeSpacingEqual;
-		} else if (tescModes.get(spv::ExecutionModeSpacingFractionalEven)) {
-			reflectData.partitionMode = spv::ExecutionModeSpacingFractionalEven;
-		} else if (tescModes.get(spv::ExecutionModeSpacingFractionalOdd)) {
-			reflectData.partitionMode = spv::ExecutionModeSpacingFractionalOdd;
-		} else if (teseModes.get(spv::ExecutionModeSpacingEqual)) {
-			reflectData.partitionMode = spv::ExecutionModeSpacingEqual;
-		} else if (teseModes.get(spv::ExecutionModeSpacingFractionalEven)) {
-			reflectData.partitionMode = spv::ExecutionModeSpacingFractionalEven;
-		} else if (teseModes.get(spv::ExecutionModeSpacingFractionalOdd)) {
-			reflectData.partitionMode = spv::ExecutionModeSpacingFractionalOdd;
-		} else {
-			errorLog = missingPartitionErr;
-			return false;
-		}
-
-		if (tescModes.get(spv::ExecutionModeOutputVertices)) {
-			reflectData.numControlPoints = tescReflect.get_execution_mode_argument(spv::ExecutionModeOutputVertices);
-		} else if (teseModes.get(spv::ExecutionModeOutputVertices)) {
-			reflectData.numControlPoints = teseReflect.get_execution_mode_argument(spv::ExecutionModeOutputVertices);
-		} else {
-			errorLog = missingOutputVerticesErr;
-			return false;
-		}
-
-		return true;
-
-#ifndef SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS
-	} catch (SPIRV_CROSS_NAMESPACE::CompilerError& ex) {
-		errorLog = ex.what();
-		return false;
-	}
-#endif
-}
-
-/** Given a shader in SPIR-V format, returns output reflection data. */
-bool getShaderOutputs(const std::vector<uint32_t>& spirv, spv::ExecutionModel model, const std::string& entryName, std::vector<SPIRVShaderOutput>& outputs, std::string& errorLog) {
-#ifndef SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS
-	try {
-#endif
-		SPIRV_CROSS_NAMESPACE::Parser parser(spirv);
-		parser.parse();
-		SPIRV_CROSS_NAMESPACE::CompilerReflection reflect(parser.get_parsed_ir());
-		if (!entryName.empty()) {
-			reflect.set_entry_point(entryName, model);
-		}
-		reflect.compile();
-		reflect.update_active_builtins();
-
-		outputs.clear();
-
-		auto addSat = [](uint32_t a, uint32_t b) { return a == uint32_t(-1) ? a : a + b; };
-		parser.get_parsed_ir().for_each_typed_id<SPIRV_CROSS_NAMESPACE::SPIRVariable>([&reflect, &outputs, model, addSat](uint32_t varID, const SPIRV_CROSS_NAMESPACE::SPIRVariable& var) {
-			if (var.storage != spv::StorageClassOutput) { return; }
-
-			bool isUsed = true;
-			const auto* type = &reflect.get_type(reflect.get_type_from_variable(varID).parent_type);
-			bool patch = reflect.has_decoration(varID, spv::DecorationPatch);
-			auto biType = spv::BuiltInMax;
-			if (reflect.has_decoration(varID, spv::DecorationBuiltIn)) {
-				biType = (spv::BuiltIn)reflect.get_decoration(varID, spv::DecorationBuiltIn);
-				isUsed = reflect.has_active_builtin(biType, var.storage);
-			}
-			uint32_t loc = -1;
-			if (reflect.has_decoration(varID, spv::DecorationLocation)) {
-				loc = reflect.get_decoration(varID, spv::DecorationLocation);
-			}
-			if (model == spv::ExecutionModelTessellationControl && !patch)
-				type = &reflect.get_type(type->parent_type);
-
-			if (type->basetype == SPIRV_CROSS_NAMESPACE::SPIRType::Struct) {
-				for (uint32_t i = 0; i < type->member_types.size(); i++) {
-					// Each member may have a location decoration. If not, each member
-					// gets an incrementing location.
-					uint32_t memberLoc = addSat(loc, i);
-					if (reflect.has_member_decoration(type->self, i, spv::DecorationLocation)) {
-						memberLoc = reflect.get_member_decoration(type->self, i, spv::DecorationLocation);
-					}
-					patch = reflect.has_member_decoration(type->self, i, spv::DecorationPatch);
-					if (reflect.has_member_decoration(type->self, i, spv::DecorationBuiltIn)) {
-						biType = (spv::BuiltIn)reflect.get_member_decoration(type->self, i, spv::DecorationBuiltIn);
-						isUsed = reflect.has_active_builtin(biType, var.storage);
-					}
-					const SPIRV_CROSS_NAMESPACE::SPIRType& memberType = reflect.get_type(type->member_types[i]);
-					if (memberType.columns > 1) {
-						for (uint32_t i = 0; i < memberType.columns; i++) {
-							outputs.push_back({memberType.basetype, memberType.vecsize, addSat(memberLoc, i), biType, patch, isUsed});
-						}
-					} else if (!memberType.array.empty()) {
-						for (uint32_t i = 0; i < memberType.array[0]; i++) {
-							outputs.push_back({memberType.basetype, memberType.vecsize, addSat(memberLoc, i), biType, patch, isUsed});
-						}
-					} else {
-						outputs.push_back({memberType.basetype, memberType.vecsize, memberLoc, biType, patch, isUsed});
-					}
-				}
-			} else if (type->columns > 1) {
-				for (uint32_t i = 0; i < type->columns; i++) {
-					outputs.push_back({type->basetype, type->vecsize, addSat(loc, i), biType, patch, isUsed});
-				}
-			} else if (!type->array.empty()) {
-				for (uint32_t i = 0; i < type->array[0]; i++) {
-					outputs.push_back({type->basetype, type->vecsize, addSat(loc, i), biType, patch, isUsed});
-				}
-			} else {
-				outputs.push_back({type->basetype, type->vecsize, loc, biType, patch, isUsed});
-			}
-		});
-		// Sort outputs by ascending location.
-		std::stable_sort(outputs.begin(), outputs.end(), [](const SPIRVShaderOutput& a, const SPIRVShaderOutput& b) {
-			return a.location < b.location;
-		});
-		// Assign locations to outputs that don't have one.
-		uint32_t loc = -1;
-		for (SPIRVShaderOutput& out : outputs) {
-			if (out.location == uint32_t(-1)) { out.location = loc + 1; }
-			loc = out.location;
-		}
-		return true;
-#ifndef SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS
-	} catch (SPIRV_CROSS_NAMESPACE::CompilerError& ex) {
-		errorLog = ex.what();
-		return false;
-	}
-#endif
-}
-
-}
diff --git a/MoltenVKShaderConverter/MoltenVKSPIRVToMSLConverter/SPIRVReflection.h b/MoltenVKShaderConverter/MoltenVKSPIRVToMSLConverter/SPIRVReflection.h
index 30d4dbf..b17fe79 100644
--- a/MoltenVKShaderConverter/MoltenVKSPIRVToMSLConverter/SPIRVReflection.h
+++ b/MoltenVKShaderConverter/MoltenVKSPIRVToMSLConverter/SPIRVReflection.h
@@ -21,6 +21,8 @@
 
 #include <SPIRV-Cross/spirv.hpp>
 #include <SPIRV-Cross/spirv_common.hpp>
+#include <SPIRV-Cross/spirv_parser.hpp>
+#include <SPIRV-Cross/spirv_reflect.hpp>
 #include <string>
 #include <vector>
 
@@ -75,10 +77,191 @@
 #pragma mark Functions
 
 	/** Given a tessellation control shader and a tessellation evaluation shader, both in SPIR-V format, returns tessellation reflection data. */
-	bool getTessReflectionData(const std::vector<uint32_t>& tesc, const std::string& tescEntryName, const std::vector<uint32_t>& tese, const std::string& teseEntryName, SPIRVTessReflectionData& reflectData, std::string& errorLog);
+	template<typename Vs>
+	static inline bool getTessReflectionData(const Vs& tesc, const std::string& tescEntryName,
+											 const Vs& tese, const std::string& teseEntryName,
+											 SPIRVTessReflectionData& reflectData, std::string& errorLog) {
+#ifndef SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS
+		try {
+#endif
+			SPIRV_CROSS_NAMESPACE::CompilerReflection tescReflect(tesc);
+			SPIRV_CROSS_NAMESPACE::CompilerReflection teseReflect(tese);
+
+			if (!tescEntryName.empty()) {
+				tescReflect.set_entry_point(tescEntryName, spv::ExecutionModelTessellationControl);
+			}
+			if (!teseEntryName.empty()) {
+				teseReflect.set_entry_point(teseEntryName, spv::ExecutionModelTessellationEvaluation);
+			}
+
+			tescReflect.compile();
+			teseReflect.compile();
+
+			const SPIRV_CROSS_NAMESPACE::Bitset& tescModes = tescReflect.get_execution_mode_bitset();
+			const SPIRV_CROSS_NAMESPACE::Bitset& teseModes = teseReflect.get_execution_mode_bitset();
+
+			// Extract the parameters from the shaders.
+			if (tescModes.get(spv::ExecutionModeTriangles)) {
+				reflectData.patchKind = spv::ExecutionModeTriangles;
+			} else if (tescModes.get(spv::ExecutionModeQuads)) {
+				reflectData.patchKind = spv::ExecutionModeQuads;
+			} else if (tescModes.get(spv::ExecutionModeIsolines)) {
+				reflectData.patchKind = spv::ExecutionModeIsolines;
+			} else if (teseModes.get(spv::ExecutionModeTriangles)) {
+				reflectData.patchKind = spv::ExecutionModeTriangles;
+			} else if (teseModes.get(spv::ExecutionModeQuads)) {
+				reflectData.patchKind = spv::ExecutionModeQuads;
+			} else if (teseModes.get(spv::ExecutionModeIsolines)) {
+				reflectData.patchKind = spv::ExecutionModeIsolines;
+			} else {
+				errorLog = "Neither tessellation shader specifies a patch input mode (Triangles, Quads, or Isolines).";
+				return false;
+			}
+
+			if (tescModes.get(spv::ExecutionModeVertexOrderCw)) {
+				reflectData.windingOrder = spv::ExecutionModeVertexOrderCw;
+			} else if (tescModes.get(spv::ExecutionModeVertexOrderCcw)) {
+				reflectData.windingOrder = spv::ExecutionModeVertexOrderCcw;
+			} else if (teseModes.get(spv::ExecutionModeVertexOrderCw)) {
+				reflectData.windingOrder = spv::ExecutionModeVertexOrderCw;
+			} else if (teseModes.get(spv::ExecutionModeVertexOrderCcw)) {
+				reflectData.windingOrder = spv::ExecutionModeVertexOrderCcw;
+			} else {
+				errorLog = "Neither tessellation shader specifies a winding order mode (VertexOrderCw or VertexOrderCcw).";
+				return false;
+			}
+
+			reflectData.pointMode = tescModes.get(spv::ExecutionModePointMode) || teseModes.get(spv::ExecutionModePointMode);
+
+			if (tescModes.get(spv::ExecutionModeSpacingEqual)) {
+				reflectData.partitionMode = spv::ExecutionModeSpacingEqual;
+			} else if (tescModes.get(spv::ExecutionModeSpacingFractionalEven)) {
+				reflectData.partitionMode = spv::ExecutionModeSpacingFractionalEven;
+			} else if (tescModes.get(spv::ExecutionModeSpacingFractionalOdd)) {
+				reflectData.partitionMode = spv::ExecutionModeSpacingFractionalOdd;
+			} else if (teseModes.get(spv::ExecutionModeSpacingEqual)) {
+				reflectData.partitionMode = spv::ExecutionModeSpacingEqual;
+			} else if (teseModes.get(spv::ExecutionModeSpacingFractionalEven)) {
+				reflectData.partitionMode = spv::ExecutionModeSpacingFractionalEven;
+			} else if (teseModes.get(spv::ExecutionModeSpacingFractionalOdd)) {
+				reflectData.partitionMode = spv::ExecutionModeSpacingFractionalOdd;
+			} else {
+				errorLog = "Neither tessellation shader specifies a partition mode (SpacingEqual, SpacingFractionalOdd, or SpacingFractionalEven).";
+				return false;
+			}
+
+			if (tescModes.get(spv::ExecutionModeOutputVertices)) {
+				reflectData.numControlPoints = tescReflect.get_execution_mode_argument(spv::ExecutionModeOutputVertices);
+			} else if (teseModes.get(spv::ExecutionModeOutputVertices)) {
+				reflectData.numControlPoints = teseReflect.get_execution_mode_argument(spv::ExecutionModeOutputVertices);
+			} else {
+				errorLog = "Neither tessellation shader specifies the number of output control points.";
+				return false;
+			}
+
+			return true;
+
+#ifndef SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS
+		} catch (SPIRV_CROSS_NAMESPACE::CompilerError& ex) {
+			errorLog = ex.what();
+			return false;
+		}
+#endif
+	}
 
 	/** Given a shader in SPIR-V format, returns output reflection data. */
-	bool getShaderOutputs(const std::vector<uint32_t>& spirv, spv::ExecutionModel model, const std::string& entryName, std::vector<SPIRVShaderOutput>& outputs, std::string& errorLog);
+	template<typename Vs, typename Vo>
+	static inline bool getShaderOutputs(const Vs& spirv, spv::ExecutionModel model, const std::string& entryName,
+										Vo& outputs, std::string& errorLog) {
+#ifndef SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS
+		try {
+#endif
+			SPIRV_CROSS_NAMESPACE::Parser parser(spirv);
+			parser.parse();
+			SPIRV_CROSS_NAMESPACE::CompilerReflection reflect(parser.get_parsed_ir());
+			if (!entryName.empty()) {
+				reflect.set_entry_point(entryName, model);
+			}
+			reflect.compile();
+			reflect.update_active_builtins();
+
+			outputs.clear();
+
+			auto addSat = [](uint32_t a, uint32_t b) { return a == uint32_t(-1) ? a : a + b; };
+			parser.get_parsed_ir().for_each_typed_id<SPIRV_CROSS_NAMESPACE::SPIRVariable>([&reflect, &outputs, model, addSat](uint32_t varID, const SPIRV_CROSS_NAMESPACE::SPIRVariable& var) {
+				if (var.storage != spv::StorageClassOutput) { return; }
+
+				bool isUsed = true;
+				const auto* type = &reflect.get_type(reflect.get_type_from_variable(varID).parent_type);
+				bool patch = reflect.has_decoration(varID, spv::DecorationPatch);
+				auto biType = spv::BuiltInMax;
+				if (reflect.has_decoration(varID, spv::DecorationBuiltIn)) {
+					biType = (spv::BuiltIn)reflect.get_decoration(varID, spv::DecorationBuiltIn);
+					isUsed = reflect.has_active_builtin(biType, var.storage);
+				}
+				uint32_t loc = -1;
+				if (reflect.has_decoration(varID, spv::DecorationLocation)) {
+					loc = reflect.get_decoration(varID, spv::DecorationLocation);
+				}
+				if (model == spv::ExecutionModelTessellationControl && !patch)
+					type = &reflect.get_type(type->parent_type);
+
+				if (type->basetype == SPIRV_CROSS_NAMESPACE::SPIRType::Struct) {
+					for (uint32_t idx = 0; idx < type->member_types.size(); idx++) {
+						// Each member may have a location decoration. If not, each member
+						// gets an incrementing location.
+						uint32_t memberLoc = addSat(loc, idx);
+						if (reflect.has_member_decoration(type->self, idx, spv::DecorationLocation)) {
+							memberLoc = reflect.get_member_decoration(type->self, idx, spv::DecorationLocation);
+						}
+						patch = reflect.has_member_decoration(type->self, idx, spv::DecorationPatch);
+						if (reflect.has_member_decoration(type->self, idx, spv::DecorationBuiltIn)) {
+							biType = (spv::BuiltIn)reflect.get_member_decoration(type->self, idx, spv::DecorationBuiltIn);
+							isUsed = reflect.has_active_builtin(biType, var.storage);
+						}
+						const SPIRV_CROSS_NAMESPACE::SPIRType& memberType = reflect.get_type(type->member_types[idx]);
+						if (memberType.columns > 1) {
+							for (uint32_t i = 0; i < memberType.columns; i++) {
+								outputs.push_back({memberType.basetype, memberType.vecsize, addSat(memberLoc, i), biType, patch, isUsed});
+							}
+						} else if (!memberType.array.empty()) {
+							for (uint32_t i = 0; i < memberType.array[0]; i++) {
+								outputs.push_back({memberType.basetype, memberType.vecsize, addSat(memberLoc, i), biType, patch, isUsed});
+							}
+						} else {
+							outputs.push_back({memberType.basetype, memberType.vecsize, memberLoc, biType, patch, isUsed});
+						}
+					}
+				} else if (type->columns > 1) {
+					for (uint32_t i = 0; i < type->columns; i++) {
+						outputs.push_back({type->basetype, type->vecsize, addSat(loc, i), biType, patch, isUsed});
+					}
+				} else if (!type->array.empty()) {
+					for (uint32_t i = 0; i < type->array[0]; i++) {
+						outputs.push_back({type->basetype, type->vecsize, addSat(loc, i), biType, patch, isUsed});
+					}
+				} else {
+					outputs.push_back({type->basetype, type->vecsize, loc, biType, patch, isUsed});
+				}
+			});
+			// Sort outputs by ascending location.
+			std::stable_sort(outputs.begin(), outputs.end(), [](const SPIRVShaderOutput& a, const SPIRVShaderOutput& b) {
+				return a.location < b.location;
+			});
+			// Assign locations to outputs that don't have one.
+			uint32_t loc = -1;
+			for (SPIRVShaderOutput& out : outputs) {
+				if (out.location == uint32_t(-1)) { out.location = loc + 1; }
+				loc = out.location;
+			}
+			return true;
+#ifndef SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS
+		} catch (SPIRV_CROSS_NAMESPACE::CompilerError& ex) {
+			errorLog = ex.what();
+			return false;
+		}
+#endif
+	}
 
 }
 #endif
diff --git a/MoltenVKShaderConverter/MoltenVKShaderConverter.xcodeproj/project.pbxproj b/MoltenVKShaderConverter/MoltenVKShaderConverter.xcodeproj/project.pbxproj
index e19c797..4de848f 100644
--- a/MoltenVKShaderConverter/MoltenVKShaderConverter.xcodeproj/project.pbxproj
+++ b/MoltenVKShaderConverter/MoltenVKShaderConverter.xcodeproj/project.pbxproj
@@ -7,8 +7,6 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
-		450A4F5F220CB180007203D7 /* SPIRVReflection.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 450A4F5D220CB180007203D7 /* SPIRVReflection.cpp */; };
-		450A4F60220CB180007203D7 /* SPIRVReflection.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 450A4F5D220CB180007203D7 /* SPIRVReflection.cpp */; };
 		450A4F61220CB180007203D7 /* SPIRVReflection.h in Headers */ = {isa = PBXBuildFile; fileRef = 450A4F5E220CB180007203D7 /* SPIRVReflection.h */; };
 		450A4F62220CB180007203D7 /* SPIRVReflection.h in Headers */ = {isa = PBXBuildFile; fileRef = 450A4F5E220CB180007203D7 /* SPIRVReflection.h */; };
 		A909408A1C58013E0094110D /* SPIRVToMSLConverter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A9093F5A1C58013E0094110D /* SPIRVToMSLConverter.cpp */; };
@@ -81,7 +79,6 @@
 /* End PBXContainerItemProxy section */
 
 /* Begin PBXFileReference section */
-		450A4F5D220CB180007203D7 /* SPIRVReflection.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SPIRVReflection.cpp; sourceTree = "<group>"; };
 		450A4F5E220CB180007203D7 /* SPIRVReflection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPIRVReflection.h; sourceTree = "<group>"; };
 		A9093F5A1C58013E0094110D /* SPIRVToMSLConverter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SPIRVToMSLConverter.cpp; sourceTree = "<group>"; };
 		A9093F5B1C58013E0094110D /* SPIRVToMSLConverter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPIRVToMSLConverter.h; sourceTree = "<group>"; };
@@ -175,7 +172,6 @@
 				A925B70A1C7754B2006E7ECD /* FileSupport.mm */,
 				A928C9171D0488DC00071B88 /* SPIRVConversion.h */,
 				A928C9181D0488DC00071B88 /* SPIRVConversion.mm */,
-				450A4F5D220CB180007203D7 /* SPIRVReflection.cpp */,
 				450A4F5E220CB180007203D7 /* SPIRVReflection.h */,
 				A9093F5A1C58013E0094110D /* SPIRVToMSLConverter.cpp */,
 				A9093F5B1C58013E0094110D /* SPIRVToMSLConverter.h */,
@@ -648,7 +644,6 @@
 				A909408A1C58013E0094110D /* SPIRVToMSLConverter.cpp in Sources */,
 				A9C70F66221B321700FBA31A /* SPIRVSupport.cpp in Sources */,
 				A928C91B1D0488DC00071B88 /* SPIRVConversion.mm in Sources */,
-				450A4F5F220CB180007203D7 /* SPIRVReflection.cpp in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -660,7 +655,6 @@
 				A909408B1C58013E0094110D /* SPIRVToMSLConverter.cpp in Sources */,
 				A9C70F67221B321700FBA31A /* SPIRVSupport.cpp in Sources */,
 				A928C91C1D0488DC00071B88 /* SPIRVConversion.mm in Sources */,
-				450A4F60220CB180007203D7 /* SPIRVReflection.cpp in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};