MVKPipeline: Add builtins that are read but not written to tessellation pipelines.

It is always legal in Vulkan to read a builtin, particularly
`BuiltInPosition`, even if it weren't written by the previous stage. The
CTS tests that this scenario works in the driver.

Update SPIRV-Cross to pull in a change required for this.

Fixes 8 CTS tests under `dEQP-VK.pipeline.*.no_position`. (Eight other
tests worked solely by accident without this change.)
diff --git a/ExternalRevisions/SPIRV-Cross_repo_revision b/ExternalRevisions/SPIRV-Cross_repo_revision
index ed09298..2e77e6f 100644
--- a/ExternalRevisions/SPIRV-Cross_repo_revision
+++ b/ExternalRevisions/SPIRV-Cross_repo_revision
@@ -1 +1 @@
-61c603f3baa5270e04bcfb6acf83c654e3c57679
+f6ca6178251c3c886d99781c5437df919fc21734
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.h b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.h
index 88b71b1..4b84769 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.h
@@ -293,7 +293,8 @@
 	~MVKGraphicsPipeline() override;
 
 protected:
-	typedef MVKSmallVector<SPIRVShaderOutput, 32> SPIRVShaderOutputs;
+	typedef MVKSmallVector<SPIRVShaderInterfaceVariable, 32> SPIRVShaderOutputs;
+	typedef MVKSmallVector<SPIRVShaderInterfaceVariable, 32> SPIRVShaderInputs;
 
     id<MTLRenderPipelineState> getOrCompilePipeline(MTLRenderPipelineDescriptor* plDesc, id<MTLRenderPipelineState>& plState);
     id<MTLComputePipelineState> getOrCompilePipeline(MTLComputePipelineDescriptor* plDesc, id<MTLComputePipelineState>& plState, const char* compilerType);
@@ -302,14 +303,15 @@
     void initShaderConversionConfig(SPIRVToMSLConversionConfiguration& shaderConfig, const VkGraphicsPipelineCreateInfo* pCreateInfo, const SPIRVTessReflectionData& reflectData);
 	void initReservedVertexAttributeBufferCount(const VkGraphicsPipelineCreateInfo* pCreateInfo);
     void addVertexInputToShaderConversionConfig(SPIRVToMSLConversionConfiguration& shaderConfig, const VkGraphicsPipelineCreateInfo* pCreateInfo);
+    void addNextStageInputToShaderConversionConfig(SPIRVToMSLConversionConfiguration& shaderConfig, SPIRVShaderInputs& inputs);
     void addPrevStageOutputToShaderConversionConfig(SPIRVToMSLConversionConfiguration& shaderConfig, SPIRVShaderOutputs& outputs);
     MTLRenderPipelineDescriptor* newMTLRenderPipelineDescriptor(const VkGraphicsPipelineCreateInfo* pCreateInfo, const SPIRVTessReflectionData& reflectData);
     MTLComputePipelineDescriptor* newMTLTessVertexStageDescriptor(const VkGraphicsPipelineCreateInfo* pCreateInfo, const SPIRVTessReflectionData& reflectData, SPIRVToMSLConversionConfiguration& shaderConfig);
 	MTLComputePipelineDescriptor* newMTLTessControlStageDescriptor(const VkGraphicsPipelineCreateInfo* pCreateInfo, const SPIRVTessReflectionData& reflectData, SPIRVToMSLConversionConfiguration& shaderConfig);
 	MTLRenderPipelineDescriptor* newMTLTessRasterStageDescriptor(const VkGraphicsPipelineCreateInfo* pCreateInfo, const SPIRVTessReflectionData& reflectData, SPIRVToMSLConversionConfiguration& shaderConfig);
 	bool addVertexShaderToPipeline(MTLRenderPipelineDescriptor* plDesc, const VkGraphicsPipelineCreateInfo* pCreateInfo, SPIRVToMSLConversionConfiguration& shaderConfig);
-	bool addVertexShaderToPipeline(MTLComputePipelineDescriptor* plDesc, const VkGraphicsPipelineCreateInfo* pCreateInfo, SPIRVToMSLConversionConfiguration& shaderConfig);
-	bool addTessCtlShaderToPipeline(MTLComputePipelineDescriptor* plDesc, const VkGraphicsPipelineCreateInfo* pCreateInfo, SPIRVToMSLConversionConfiguration& shaderConfig, SPIRVShaderOutputs& prevOutput);
+	bool addVertexShaderToPipeline(MTLComputePipelineDescriptor* plDesc, const VkGraphicsPipelineCreateInfo* pCreateInfo, SPIRVToMSLConversionConfiguration& shaderConfig, SPIRVShaderInputs& nextInputs);
+	bool addTessCtlShaderToPipeline(MTLComputePipelineDescriptor* plDesc, const VkGraphicsPipelineCreateInfo* pCreateInfo, SPIRVToMSLConversionConfiguration& shaderConfig, SPIRVShaderOutputs& prevOutput, SPIRVShaderInputs& nextInputs);
 	bool addTessEvalShaderToPipeline(MTLRenderPipelineDescriptor* plDesc, const VkGraphicsPipelineCreateInfo* pCreateInfo, SPIRVToMSLConversionConfiguration& shaderConfig, SPIRVShaderOutputs& prevOutput);
     bool addFragmentShaderToPipeline(MTLRenderPipelineDescriptor* plDesc, const VkGraphicsPipelineCreateInfo* pCreateInfo, SPIRVToMSLConversionConfiguration& shaderConfig, SPIRVShaderOutputs& prevOutput);
 	template<class T>
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm
index 4f9bc4a..2048687 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm
@@ -669,8 +669,21 @@
 																				  SPIRVToMSLConversionConfiguration& shaderConfig) {
 	MTLComputePipelineDescriptor* plDesc = [MTLComputePipelineDescriptor new];	// retained
 
+	SPIRVShaderInputs tcInputs;
+	std::string errorLog;
+	if (!getShaderInputs(((MVKShaderModule*)_pTessCtlSS->module)->getSPIRV(), spv::ExecutionModelTessellationControl, _pTessCtlSS->pName, tcInputs, errorLog) ) {
+		setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED, "Failed to get tessellation control inputs: %s", errorLog.c_str()));
+		return nil;
+	}
+
+	// Filter out anything but builtins. We couldn't do this before because we needed to make sure
+	// locations were assigned correctly.
+	tcInputs.erase(std::remove_if(tcInputs.begin(), tcInputs.end(), [](const SPIRVShaderInterfaceVariable& var) {
+		return var.builtin != spv::BuiltInPosition && var.builtin != spv::BuiltInPointSize && var.builtin != spv::BuiltInClipDistance && var.builtin != spv::BuiltInCullDistance;
+	}), tcInputs.end());
+
 	// Add shader stages.
-	if (!addVertexShaderToPipeline(plDesc, pCreateInfo, shaderConfig)) { return nil; }
+	if (!addVertexShaderToPipeline(plDesc, pCreateInfo, shaderConfig, tcInputs)) { return nil; }
 
 	// Vertex input
 	plDesc.stageInputDescriptor = [MTLStageInputOutputDescriptor stageInputOutputDescriptor];
@@ -794,14 +807,25 @@
 	MTLComputePipelineDescriptor* plDesc = [MTLComputePipelineDescriptor new];		// retained
 
 	SPIRVShaderOutputs vtxOutputs;
+	SPIRVShaderInputs teInputs;
 	std::string errorLog;
 	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;
 	}
+	if (!getShaderInputs(((MVKShaderModule*)_pTessEvalSS->module)->getSPIRV(), spv::ExecutionModelTessellationEvaluation, _pTessEvalSS->pName, teInputs, errorLog) ) {
+		setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED, "Failed to get tessellation evaluation inputs: %s", errorLog.c_str()));
+		return nil;
+	}
+
+	// Filter out anything but builtins. We couldn't do this before because we needed to make sure
+	// locations were assigned correctly.
+	teInputs.erase(std::remove_if(teInputs.begin(), teInputs.end(), [](const SPIRVShaderInterfaceVariable& var) {
+		return var.builtin != spv::BuiltInPosition && var.builtin != spv::BuiltInPointSize && var.builtin != spv::BuiltInClipDistance && var.builtin != spv::BuiltInCullDistance;
+	}), teInputs.end());
 
 	// Add shader stages.
-	if (!addTessCtlShaderToPipeline(plDesc, pCreateInfo, shaderConfig, vtxOutputs)) {
+	if (!addTessCtlShaderToPipeline(plDesc, pCreateInfo, shaderConfig, vtxOutputs, teInputs)) {
 		[plDesc release];
 		return nil;
 	}
@@ -822,11 +846,16 @@
 	MTLRenderPipelineDescriptor* plDesc = [MTLRenderPipelineDescriptor new];	// retained
 
 	SPIRVShaderOutputs tcOutputs, teOutputs;
+	SPIRVShaderInputs teInputs;
 	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()));
 		return nil;
 	}
+	if (!getShaderInputs(((MVKShaderModule*)_pTessEvalSS->module)->getSPIRV(), spv::ExecutionModelTessellationEvaluation, _pTessEvalSS->pName, teInputs, errorLog) ) {
+		setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED, "Failed to get tessellation evaluation inputs: %s", errorLog.c_str()));
+		return nil;
+	}
 	if (!getShaderOutputs(((MVKShaderModule*)_pTessEvalSS->module)->getSPIRV(), spv::ExecutionModelTessellationEvaluation, _pTessEvalSS->pName, teOutputs, errorLog) ) {
 		setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED, "Failed to get tessellation evaluation outputs: %s", errorLog.c_str()));
 		return nil;
@@ -840,13 +869,38 @@
 
 	// Tessellation evaluation stage input
 	// This needs to happen before compiling the fragment shader, or we'll lose information on shader inputs.
+	// First, add extra builtins that are in teInputs but not tcOutputs. They can be read
+	// even if not written.
+	teInputs.erase(std::remove_if(teInputs.begin(), teInputs.end(), [&tcOutputs](const SPIRVShaderInterfaceVariable& var) {
+		return var.builtin != spv::BuiltInPosition && var.builtin != spv::BuiltInPointSize && var.builtin != spv::BuiltInClipDistance && var.builtin != spv::BuiltInCullDistance;
+	}), teInputs.end());
+	std::remove_copy_if(teInputs.begin(), teInputs.end(), std::back_inserter(tcOutputs), [&tcOutputs](const SPIRVShaderInterfaceVariable& input) {
+		auto iter = std::find_if(tcOutputs.begin(), tcOutputs.end(), [input](const SPIRVShaderInterfaceVariable& oldVar) {
+			return oldVar.builtin == input.builtin;
+		});
+		if (iter != tcOutputs.end()) {
+			iter->isUsed = input.isUsed;
+		}
+		return iter != tcOutputs.end();
+	});
+
+	auto isBuiltInRead = [&teInputs](spv::BuiltIn builtin) {
+		for (const auto& input : teInputs) {
+			if (input.builtin == builtin) {
+				return input.isUsed;
+			}
+		}
+		return false;
+	};
+
 	plDesc.vertexDescriptor = [MTLVertexDescriptor vertexDescriptor];
 	uint32_t offset = 0, patchOffset = 0, outerLoc = -1, innerLoc = -1;
 	bool usedPerVertex = false, usedPerPatch = false;
 	const SPIRVShaderOutput* firstVertex = nullptr, * firstPatch = nullptr;
 	for (const SPIRVShaderOutput& output : tcOutputs) {
 		if (output.builtin == spv::BuiltInPointSize && !reflectData.pointMode) { continue; }
-		if (!shaderConfig.isShaderInputLocationUsed(output.location)) {
+		if ((output.builtin != spv::BuiltInMax && !isBuiltInRead(output.builtin)) &&
+			!shaderConfig.isShaderInputLocationUsed(output.location)) {
 			if (output.perPatch && !(output.builtin == spv::BuiltInTessLevelOuter || output.builtin == spv::BuiltInTessLevelInner) ) {
 				if (!firstPatch) { firstPatch = &output; }
 				patchOffset += getShaderOutputSize(output);
@@ -1014,7 +1068,8 @@
 // Adds a vertex shader compiled as a compute kernel to the pipeline description.
 bool MVKGraphicsPipeline::addVertexShaderToPipeline(MTLComputePipelineDescriptor* plDesc,
 													const VkGraphicsPipelineCreateInfo* pCreateInfo,
-													SPIRVToMSLConversionConfiguration& shaderConfig) {
+													SPIRVToMSLConversionConfiguration& shaderConfig,
+													SPIRVShaderInputs& tcInputs) {
 	shaderConfig.options.entryPointStage = spv::ExecutionModelVertex;
 	shaderConfig.options.entryPointName = _pVertexSS->pName;
 	shaderConfig.options.mslOptions.swizzle_buffer_index = _swizzleBufferIndex.stages[kMVKShaderStageVertex];
@@ -1026,6 +1081,7 @@
 	shaderConfig.options.mslOptions.vertex_for_tessellation = true;
 	shaderConfig.options.mslOptions.disable_rasterization = true;
     addVertexInputToShaderConversionConfig(shaderConfig, pCreateInfo);
+	addNextStageInputToShaderConversionConfig(shaderConfig, tcInputs);
 
 	static const CompilerMSL::Options::IndexType indexTypes[] = {
 		CompilerMSL::Options::IndexType::None,
@@ -1078,7 +1134,8 @@
 bool MVKGraphicsPipeline::addTessCtlShaderToPipeline(MTLComputePipelineDescriptor* plDesc,
 													 const VkGraphicsPipelineCreateInfo* pCreateInfo,
 													 SPIRVToMSLConversionConfiguration& shaderConfig,
-													 SPIRVShaderOutputs& vtxOutputs) {
+													 SPIRVShaderOutputs& vtxOutputs,
+													 SPIRVShaderInputs& teInputs) {
 	shaderConfig.options.entryPointStage = spv::ExecutionModelTessellationControl;
 	shaderConfig.options.entryPointName = _pTessCtlSS->pName;
 	shaderConfig.options.mslOptions.swizzle_buffer_index = _swizzleBufferIndex.stages[kMVKShaderStageTessCtl];
@@ -1093,6 +1150,7 @@
 	shaderConfig.options.mslOptions.multi_patch_workgroup = true;
 	shaderConfig.options.mslOptions.fixed_subgroup_size = mvkIsAnyFlagEnabled(_pTessCtlSS->flags, VK_PIPELINE_SHADER_STAGE_CREATE_ALLOW_VARYING_SUBGROUP_SIZE_BIT_EXT) ? 0 : _device->_pMetalFeatures->maxSubgroupSize;
 	addPrevStageOutputToShaderConversionConfig(shaderConfig, vtxOutputs);
+	addNextStageInputToShaderConversionConfig(shaderConfig, teInputs);
 
 	MVKMTLFunction func = ((MVKShaderModule*)_pTessCtlSS->module)->getMTLFunction(&shaderConfig, _pTessCtlSS->pSpecializationInfo, _pipelineCache);
 	id<MTLFunction> mtlFunc = func.getMTLFunction();
@@ -1695,7 +1753,7 @@
 
         // Set binding and offset from Vulkan vertex attribute
         mvk::MSLShaderInput si;
-        si.shaderInput.location = pVKVA->location;
+        si.shaderVar.location = pVKVA->location;
         si.binding = pVKVA->binding;
 
         // Metal can't do signedness conversions on vertex buffers (rdar://45922847). If the shader
@@ -1705,11 +1763,11 @@
         // declared type. Programs that try to invoke undefined behavior are on their own.
         switch (getPixelFormats()->getFormatType(pVKVA->format) ) {
         case kMVKFormatColorUInt8:
-            si.shaderInput.format = MSL_VERTEX_FORMAT_UINT8;
+            si.shaderVar.format = MSL_VERTEX_FORMAT_UINT8;
             break;
 
         case kMVKFormatColorUInt16:
-            si.shaderInput.format = MSL_VERTEX_FORMAT_UINT16;
+            si.shaderVar.format = MSL_VERTEX_FORMAT_UINT16;
             break;
 
         case kMVKFormatDepthStencil:
@@ -1719,7 +1777,7 @@
             case VK_FORMAT_D16_UNORM_S8_UINT:
             case VK_FORMAT_D24_UNORM_S8_UINT:
             case VK_FORMAT_D32_SFLOAT_S8_UINT:
-                si.shaderInput.format = MSL_VERTEX_FORMAT_UINT8;
+                si.shaderVar.format = MSL_VERTEX_FORMAT_UINT8;
                 break;
 
             default:
@@ -1736,6 +1794,49 @@
     }
 }
 
+// Initializes the shader outputs in a shader conversion config from the next stage input.
+void MVKGraphicsPipeline::addNextStageInputToShaderConversionConfig(SPIRVToMSLConversionConfiguration& shaderConfig,
+                                                                    SPIRVShaderInputs& shaderInputs) {
+    // Set the shader conversion configuration output variable information
+    shaderConfig.shaderOutputs.clear();
+    uint32_t soCnt = (uint32_t)shaderInputs.size();
+    for (uint32_t soIdx = 0; soIdx < soCnt; soIdx++) {
+		if (!shaderInputs[soIdx].isUsed) { continue; }
+
+        mvk::MSLShaderInterfaceVariable so;
+        so.shaderVar.location = shaderInputs[soIdx].location;
+		so.shaderVar.component = shaderInputs[soIdx].component;
+        so.shaderVar.builtin = shaderInputs[soIdx].builtin;
+        so.shaderVar.vecsize = shaderInputs[soIdx].vecWidth;
+
+        switch (getPixelFormats()->getFormatType(mvkFormatFromOutput(shaderInputs[soIdx]) ) ) {
+            case kMVKFormatColorUInt8:
+                so.shaderVar.format = MSL_SHADER_INPUT_FORMAT_UINT8;
+                break;
+
+            case kMVKFormatColorUInt16:
+                so.shaderVar.format = MSL_SHADER_INPUT_FORMAT_UINT16;
+                break;
+
+			case kMVKFormatColorHalf:
+			case kMVKFormatColorInt16:
+				so.shaderVar.format = MSL_SHADER_INPUT_FORMAT_ANY16;
+				break;
+
+			case kMVKFormatColorFloat:
+			case kMVKFormatColorInt32:
+			case kMVKFormatColorUInt32:
+				so.shaderVar.format = MSL_SHADER_INPUT_FORMAT_ANY32;
+				break;
+
+            default:
+                break;
+        }
+
+        shaderConfig.shaderOutputs.push_back(so);
+    }
+}
+
 // Initializes the shader inputs in a shader conversion config from the previous stage output.
 void MVKGraphicsPipeline::addPrevStageOutputToShaderConversionConfig(SPIRVToMSLConversionConfiguration& shaderConfig,
                                                                      SPIRVShaderOutputs& shaderOutputs) {
@@ -1746,29 +1847,29 @@
 		if (!shaderOutputs[siIdx].isUsed) { continue; }
 
         mvk::MSLShaderInput si;
-        si.shaderInput.location = shaderOutputs[siIdx].location;
-		si.shaderInput.component = shaderOutputs[siIdx].component;
-        si.shaderInput.builtin = shaderOutputs[siIdx].builtin;
-        si.shaderInput.vecsize = shaderOutputs[siIdx].vecWidth;
+        si.shaderVar.location = shaderOutputs[siIdx].location;
+		si.shaderVar.component = shaderOutputs[siIdx].component;
+        si.shaderVar.builtin = shaderOutputs[siIdx].builtin;
+        si.shaderVar.vecsize = shaderOutputs[siIdx].vecWidth;
 
         switch (getPixelFormats()->getFormatType(mvkFormatFromOutput(shaderOutputs[siIdx]) ) ) {
             case kMVKFormatColorUInt8:
-                si.shaderInput.format = MSL_SHADER_INPUT_FORMAT_UINT8;
+                si.shaderVar.format = MSL_SHADER_INPUT_FORMAT_UINT8;
                 break;
 
             case kMVKFormatColorUInt16:
-                si.shaderInput.format = MSL_SHADER_INPUT_FORMAT_UINT16;
+                si.shaderVar.format = MSL_SHADER_INPUT_FORMAT_UINT16;
                 break;
 
 			case kMVKFormatColorHalf:
 			case kMVKFormatColorInt16:
-				si.shaderInput.format = MSL_SHADER_INPUT_FORMAT_ANY16;
+				si.shaderVar.format = MSL_SHADER_INPUT_FORMAT_ANY16;
 				break;
 
 			case kMVKFormatColorFloat:
 			case kMVKFormatColorInt32:
 			case kMVKFormatColorUInt32:
-				si.shaderInput.format = MSL_SHADER_INPUT_FORMAT_ANY32;
+				si.shaderVar.format = MSL_SHADER_INPUT_FORMAT_ANY32;
 				break;
 
             default:
@@ -2251,7 +2352,7 @@
 	}
 
 	template<class Archive>
-	void serialize(Archive & archive, MSLShaderInput& si) {
+	void serialize(Archive & archive, MSLShaderInterfaceVariable& si) {
 		archive(si.location,
 				si.component,
 				si.format,
@@ -2331,8 +2432,8 @@
 	}
 
 	template<class Archive>
-	void serialize(Archive & archive, MSLShaderInput& si) {
-		archive(si.shaderInput,
+	void serialize(Archive & archive, MSLShaderInterfaceVariable& si) {
+		archive(si.shaderVar,
 				si.binding,
 				si.outIsUsedByShader);
 	}
@@ -2357,6 +2458,7 @@
 	void serialize(Archive & archive, SPIRVToMSLConversionConfiguration& ctx) {
 		archive(ctx.options,
 				ctx.shaderInputs,
+				ctx.shaderOutputs,
 				ctx.resourceBindings,
 				ctx.discreteDescriptorSets);
 	}
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKShaderModule.mm b/MoltenVK/MoltenVK/GPUObjects/MVKShaderModule.mm
index 40b484f..8170433 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKShaderModule.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKShaderModule.mm
@@ -288,7 +288,7 @@
 		_device->addActivityPerformance(_device->_performanceStatistics.shaderCompilation.shaderLibraryFromCache, startTime);
 	} else {
 		mvkLib->setEntryPointName(pShaderConfig->options.entryPointName);
-		pShaderConfig->markAllInputsAndResourcesUsed();
+		pShaderConfig->markAllInterfaceVarsAndResourcesUsed();
 	}
 
 	return mvkLib ? mvkLib->getMTLFunction(pSpecializationInfo, this) : MVKMTLFunctionNull;
diff --git a/MoltenVK/MoltenVK/Utility/MVKSmallVector.h b/MoltenVK/MoltenVK/Utility/MVKSmallVector.h
index ca19169..f262521 100755
--- a/MoltenVK/MoltenVK/Utility/MVKSmallVector.h
+++ b/MoltenVK/MoltenVK/Utility/MVKSmallVector.h
@@ -62,6 +62,7 @@
   Allocator  alc;

   

 public:

+  using value_type = Type;

   class iterator

   {

     const MVKSmallVectorImpl *vector;

@@ -115,6 +116,7 @@
     bool   is_valid()     const { return index < vector->alc.size(); }

     size_t get_position() const { return index; }

   };

+  using reverse_iterator = std::reverse_iterator<iterator>;

 

 private:

   // this is the growth strategy -> adjust to your needs

@@ -293,6 +295,9 @@
   iterator begin() const { return iterator( 0, *this ); }

   iterator end()   const { return iterator( alc.num_elements_used, *this ); }

 

+  reverse_iterator rbegin() const { return reverse_iterator( end() ); }

+  reverse_iterator rend()   const { return reverse_iterator( begin() ); }

+

   const MVKArrayRef<Type> contents() const { return MVKArrayRef<Type>(data(), size()); }

         MVKArrayRef<Type> contents()       { return MVKArrayRef<Type>(data(), size()); }

 

@@ -521,6 +526,7 @@
   Allocator  alc;

 

 public:

+  using value_type = Type*;

   class iterator

   {

     MVKSmallVectorImpl *vector;

@@ -572,6 +578,7 @@
     bool   is_valid()     const { return index < vector->alc.size(); }

     size_t get_position() const { return index; }

   };

+  using reverse_iterator = std::reverse_iterator<iterator>;

 

 private:

   // this is the growth strategy -> adjust to your needs

@@ -728,6 +735,9 @@
   iterator begin()        { return iterator( 0, *this ); }

   iterator end()          { return iterator( alc.num_elements_used, *this ); }

 

+  reverse_iterator rbegin()       { return reverse_iterator( end() ); }

+  reverse_iterator rend()         { return reverse_iterator( rbegin() ); }

+

   const MVKArrayRef<Type*> contents() const { return MVKArrayRef<Type*>(data(), size()); }

         MVKArrayRef<Type*> contents()       { return MVKArrayRef<Type*>(data(), size()); }

 

diff --git a/MoltenVKShaderConverter/MoltenVKShaderConverter/SPIRVReflection.h b/MoltenVKShaderConverter/MoltenVKShaderConverter/SPIRVReflection.h
index 06f78ec..b9e1e4a 100644
--- a/MoltenVKShaderConverter/MoltenVKShaderConverter/SPIRVReflection.h
+++ b/MoltenVKShaderConverter/MoltenVKShaderConverter/SPIRVReflection.h
@@ -54,41 +54,42 @@
 	};
 
 #pragma mark -
-#pragma mark SPIRVShaderOutputData
+#pragma mark SPIRVShaderInterfaceVariable
 
 	/**
-	 * Reflection data on a single output of a shader.
+	 * Reflection data on a single interface variable of a shader.
 	 * This contains the information needed to construct a
 	 * stage-input descriptor for the next stage of a pipeline.
 	 */
-	struct SPIRVShaderOutput {
-		/** The type of the output. */
+	struct SPIRVShaderInterfaceVariable {
+		/** The type of the variable. */
 		SPIRV_CROSS_NAMESPACE::SPIRType::BaseType baseType;
 
 		/** The vector size, if a vector. */
 		uint32_t vecWidth;
 
-		/** The location number of the output. */
+		/** The location number of the variable. */
 		uint32_t location;
 
-		/** The component index of the output. */
+		/** The component index of the variable. */
 		uint32_t component;
 
 		/**
 		 * If this is the first member of a struct, this will contain the alignment
-		 * of the struct containing this output, otherwise this will be zero.
+		 * of the struct containing this variable, otherwise this will be zero.
 		 */
 		uint32_t firstStructMemberAlignment;
 
 		/** If this is a builtin, the kind of builtin this is. */
 		spv::BuiltIn builtin;
 
-		/** Whether this is a per-patch or per-vertex output. Only meaningful for tessellation control shaders. */
+		/** Whether this is a per-patch or per-vertex variable. Only meaningful for tessellation shaders. */
 		bool perPatch;
 
-		/** Whether this output is actually used (populated) by the shader. */
+		/** Whether this variable is actually used (read or written) by the shader. */
 		bool isUsed;
 	};
+	typedef SPIRVShaderInterfaceVariable SPIRVShaderOutput;
 
 
 #pragma mark -
@@ -190,13 +191,13 @@
 #endif
 	}
 
-	/** Returns the size in bytes of the output. */
-	static inline uint32_t getShaderOutputSize(const SPIRVShaderOutput& output) {
-		if ( !output.isUsed ) { return 0; }		// Unused outputs consume no buffer space.
+	/** Returns the size in bytes of the interface variable. */
+	static inline uint32_t getShaderInterfaceVariableSize(const SPIRVShaderInterfaceVariable& var) {
+		if ( !var.isUsed ) { return 0; }		// Unused variables consume no buffer space.
 
-		uint32_t vecWidth = output.vecWidth;
+		uint32_t vecWidth = var.vecWidth;
 		if (vecWidth == 3) { vecWidth = 4; }	// Metal 3-vectors consume same as 4-vectors.
-		switch (output.baseType) {
+		switch (var.baseType) {
 			case SPIRV_CROSS_NAMESPACE::SPIRType::SByte:
 			case SPIRV_CROSS_NAMESPACE::SPIRType::UByte:
 				return 1 * vecWidth;
@@ -211,29 +212,35 @@
 				return 4 * vecWidth;
 		}
 	}
+	static inline uint32_t getShaderOutputSize(const SPIRVShaderOutput& output) {
+		return getShaderInterfaceVariableSize(output);
+	}
 
 	/**
-	 * Returns the alignment of the shader output, which typically matches the size of the output,
-	 * but the first member of a nested output struct may inherit special alignment from the struct.
+	 * Returns the alignment of the shader interface variable, which typically matches the size of the variable,
+	 * but the first member of a nested struct may inherit special alignment from the struct.
 	 */
-	static inline uint32_t getShaderOutputAlignment(const SPIRVShaderOutput& output) {
-		if(output.firstStructMemberAlignment && output.isUsed) {
-			return output.firstStructMemberAlignment;
+	static inline uint32_t getShaderInterfaceVariableAlignment(const SPIRVShaderInterfaceVariable& var) {
+		if(var.firstStructMemberAlignment && var.isUsed) {
+			return var.firstStructMemberAlignment;
 		} else {
-			return getShaderOutputSize(output);
+			return getShaderOutputSize(var);
 		}
 	}
+	static inline uint32_t getShaderOutputAlignment(const SPIRVShaderOutput& output) {
+		return getShaderInterfaceVariableAlignment(output);
+	}
 
 	auto addSat = [](uint32_t a, uint32_t b) { return a == uint32_t(-1) ? a : a + b; };
 
-	template<typename Vo>
-	static inline uint32_t getShaderOutputStructMembers(const SPIRV_CROSS_NAMESPACE::CompilerReflection& reflect,
-														Vo& outputs, SPIRVShaderOutput* pParentFirstMember,
-														const SPIRV_CROSS_NAMESPACE::SPIRType* structType, spv::StorageClass storage,
-														bool patch, uint32_t loc) {
+	template<typename Vi>
+	static inline uint32_t getShaderInterfaceStructMembers(const SPIRV_CROSS_NAMESPACE::CompilerReflection& reflect,
+														   Vi& vars, SPIRVShaderInterfaceVariable* pParentFirstMember,
+														   const SPIRV_CROSS_NAMESPACE::SPIRType* structType, spv::StorageClass storage,
+														   bool patch, uint32_t loc) {
 		bool isUsed = true;
 		auto biType = spv::BuiltInMax;
-		SPIRVShaderOutput* pFirstMember = nullptr;
+		SPIRVShaderInterfaceVariable* pFirstMember = nullptr;
 		size_t mbrCnt = structType->member_types.size();
 		for (uint32_t mbrIdx = 0; mbrIdx < mbrCnt; mbrIdx++) {
 			// Each member may have a location decoration. If not, each member
@@ -252,12 +259,12 @@
 			uint32_t elemCnt = (type->array.empty() ? 1 : type->array[0]) * type->columns;
 			for (uint32_t elemIdx = 0; elemIdx < elemCnt; elemIdx++) {
 				if (type->basetype == SPIRV_CROSS_NAMESPACE::SPIRType::Struct)
-					loc = getShaderOutputStructMembers(reflect, outputs, pFirstMember, type, storage, patch, loc);
+					loc = getShaderInterfaceStructMembers(reflect, vars, pFirstMember, type, storage, patch, loc);
 				else {
 					// The alignment of a structure is the same as the largest member of the structure.
 					// Consequently, the first flattened member of a structure should align with structure itself.
-					outputs.push_back({type->basetype, type->vecsize, loc, cmp, 0, biType, patch, isUsed});
-					auto& currOutput = outputs.back();
+					vars.push_back({type->basetype, type->vecsize, loc, cmp, 0, biType, patch, isUsed});
+					auto& currOutput = vars.back();
 					if ( !pFirstMember ) { pFirstMember = &currOutput; }
 					pFirstMember->firstStructMemberAlignment = std::max(pFirstMember->firstStructMemberAlignment, getShaderOutputSize(currOutput));
 					loc = addSat(loc, 1);
@@ -274,11 +281,18 @@
 
 		return loc;
 	}
+	template<typename Vo>
+	static inline uint32_t getShaderOutputStructMembers(const SPIRV_CROSS_NAMESPACE::CompilerReflection& reflect,
+														Vo& outputs, SPIRVShaderOutput* pParentFirstMember,
+														const SPIRV_CROSS_NAMESPACE::SPIRType* structType, spv::StorageClass storage,
+														bool patch, uint32_t loc) {
+		return getShaderInterfaceStructMembers(reflect, outputs, pParentFirstMember, structType, storage, patch, loc);
+	}
 
-	/** Given a shader in SPIR-V format, returns output reflection data. */
-	template<typename Vs, typename Vo>
-	static inline bool getShaderOutputs(const Vs& spirv, spv::ExecutionModel model, const std::string& entryName,
-										Vo& outputs, std::string& errorLog) {
+	/** Given a shader in SPIR-V format, returns interface reflection data. */
+	template<typename Vs, typename Vi>
+	static inline bool getShaderInterfaceVariables(const Vs& spirv, spv::StorageClass storage, spv::ExecutionModel model,
+												   const std::string& entryName, Vi& vars, std::string& errorLog) {
 #ifndef SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS
 		try {
 #endif
@@ -291,11 +305,10 @@
 			reflect.compile();
 			reflect.update_active_builtins();
 
-			outputs.clear();
+			vars.clear();
 
 			for (auto varID : reflect.get_active_interface_variables()) {
-				spv::StorageClass storage = reflect.get_storage_class(varID);
-				if (storage != spv::StorageClassOutput) { continue; }
+				if (storage != reflect.get_storage_class(varID)) { continue; }
 
 				bool isUsed = true;
 				const auto* type = &reflect.get_type(reflect.get_type_from_variable(varID).parent_type);
@@ -313,29 +326,33 @@
 				if (reflect.has_decoration(varID, spv::DecorationComponent)) {
 					cmp = reflect.get_decoration(varID, spv::DecorationComponent);
 				}
-				if (model == spv::ExecutionModelTessellationControl && !patch)
+				// For tessellation shaders, peel away the initial array type. SPIRV-Cross adds the array back automatically.
+				// Only some builtins will be arrayed here.
+				if ((model == spv::ExecutionModelTessellationControl || (model == spv::ExecutionModelTessellationEvaluation && storage == spv::StorageClassInput)) && !patch &&
+					(biType == spv::BuiltInMax || biType == spv::BuiltInPosition || biType == spv::BuiltInPointSize ||
+					 biType == spv::BuiltInClipDistance || biType == spv::BuiltInCullDistance))
 					type = &reflect.get_type(type->parent_type);
 
 				uint32_t elemCnt = (type->array.empty() ? 1 : type->array[0]) * type->columns;
 				for (uint32_t i = 0; i < elemCnt; i++) {
 					if (type->basetype == SPIRV_CROSS_NAMESPACE::SPIRType::Struct) {
-						SPIRVShaderOutput* pFirstMember = nullptr;
-						loc = getShaderOutputStructMembers(reflect, outputs, pFirstMember, type, storage, patch, loc);
+						SPIRVShaderInterfaceVariable* pFirstMember = nullptr;
+						loc = getShaderInterfaceStructMembers(reflect, vars, pFirstMember, type, storage, patch, loc);
 					} else {
-						outputs.push_back({type->basetype, type->vecsize, loc, cmp, 0, biType, patch, isUsed});
+						vars.push_back({type->basetype, type->vecsize, loc, cmp, 0, biType, patch, isUsed});
 						loc = addSat(loc, 1);
 					}
 				}
 			}
-			// Sort outputs by ascending location.
-			std::stable_sort(outputs.begin(), outputs.end(), [](const SPIRVShaderOutput& a, const SPIRVShaderOutput& b) {
+			// Sort variables by ascending location.
+			std::stable_sort(vars.begin(), vars.end(), [](const SPIRVShaderInterfaceVariable& a, const SPIRVShaderInterfaceVariable& b) {
 				return a.location < b.location;
 			});
-			// Assign locations to outputs that don't have one.
+			// Assign locations to variables 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;
+			for (SPIRVShaderInterfaceVariable& var : vars) {
+				if (var.location == uint32_t(-1)) { var.location = loc + 1; }
+				loc = var.location;
 			}
 			return true;
 #ifndef SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS
@@ -345,6 +362,16 @@
 		}
 #endif
 	}
+	template<typename Vs, typename Vo>
+	static inline bool getShaderOutputs(const Vs& spirv, spv::ExecutionModel model, const std::string& entryName,
+										Vo& outputs, std::string& errorLog) {
+		return getShaderInterfaceVariables(spirv, spv::StorageClassOutput, model, entryName, outputs, errorLog);
+	}
+	template<typename Vs, typename Vo>
+	static inline bool getShaderInputs(const Vs& spirv, spv::ExecutionModel model, const std::string& entryName,
+										Vo& outputs, std::string& errorLog) {
+		return getShaderInterfaceVariables(spirv, spv::StorageClassInput, model, entryName, outputs, errorLog);
+	}
 
 }
 #endif
diff --git a/MoltenVKShaderConverter/MoltenVKShaderConverter/SPIRVToMSLConverter.cpp b/MoltenVKShaderConverter/MoltenVKShaderConverter/SPIRVToMSLConverter.cpp
index 41d3601..6853b91 100644
--- a/MoltenVKShaderConverter/MoltenVKShaderConverter/SPIRVToMSLConverter.cpp
+++ b/MoltenVKShaderConverter/MoltenVKShaderConverter/SPIRVToMSLConverter.cpp
@@ -94,17 +94,17 @@
 	mslOptions.pad_fragment_output_components = true;
 }
 
-MVK_PUBLIC_SYMBOL bool mvk::MSLShaderInput::matches(const mvk::MSLShaderInput& other) const {
-	if (memcmp(&shaderInput, &other.shaderInput, sizeof(shaderInput)) != 0) { return false; }
+MVK_PUBLIC_SYMBOL bool mvk::MSLShaderInterfaceVariable::matches(const mvk::MSLShaderInterfaceVariable& other) const {
+	if (memcmp(&shaderVar, &other.shaderVar, sizeof(shaderVar)) != 0) { return false; }
 	if (binding != other.binding) { return false; }
 	return true;
 }
 
-MVK_PUBLIC_SYMBOL mvk::MSLShaderInput::MSLShaderInput() {
-	// Explicitly set shaderInput to defaults over cleared memory to ensure all instances
+MVK_PUBLIC_SYMBOL mvk::MSLShaderInterfaceVariable::MSLShaderInterfaceVariable() {
+	// Explicitly set shaderVar to defaults over cleared memory to ensure all instances
 	// have exactly the same memory layout when using memory comparison in matches().
-	memset(&shaderInput, 0, sizeof(shaderInput));
-	shaderInput = SPIRV_CROSS_NAMESPACE::MSLShaderInput();
+	memset(&shaderVar, 0, sizeof(shaderVar));
+	shaderVar = SPIRV_CROSS_NAMESPACE::MSLShaderInterfaceVariable();
 }
 
 // If requiresConstExprSampler is false, constExprSampler can be ignored
@@ -143,7 +143,21 @@
 // Check them all in case inactive VA's duplicate locations used by active VA's.
 MVK_PUBLIC_SYMBOL bool SPIRVToMSLConversionConfiguration::isShaderInputLocationUsed(uint32_t location) const {
     for (auto& si : shaderInputs) {
-        if ((si.shaderInput.location == location) && si.outIsUsedByShader) { return true; }
+        if ((si.shaderVar.location == location) && si.outIsUsedByShader) { return true; }
+    }
+    return false;
+}
+
+MVK_PUBLIC_SYMBOL bool SPIRVToMSLConversionConfiguration::isShaderInputBuiltInUsed(spv::BuiltIn builtin) const {
+    for (auto& si : shaderInputs) {
+        if ((si.shaderVar.builtin == builtin) && si.outIsUsedByShader) { return true; }
+    }
+    return false;
+}
+
+MVK_PUBLIC_SYMBOL bool SPIRVToMSLConversionConfiguration::isShaderOutputLocationUsed(uint32_t location) const {
+    for (auto& so : shaderOutputs) {
+        if ((so.shaderVar.location == location) && so.outIsUsedByShader) { return true; }
     }
     return false;
 }
@@ -166,8 +180,9 @@
 	return false;
 }
 
-MVK_PUBLIC_SYMBOL void SPIRVToMSLConversionConfiguration::markAllInputsAndResourcesUsed() {
+MVK_PUBLIC_SYMBOL void SPIRVToMSLConversionConfiguration::markAllInterfaceVarsAndResourcesUsed() {
 	for (auto& si : shaderInputs) { si.outIsUsedByShader = true; }
+	for (auto& so : shaderOutputs) { so.outIsUsedByShader = true; }
 	for (auto& rb : resourceBindings) { rb.outIsUsedByShader = true; }
 }
 
@@ -175,7 +190,7 @@
 // and the resources can be spread across these shader stages. To improve cache hits when using
 // this function to find a cached shader for a particular shader stage, only consider the resources
 // that are used in that shader stage. By contrast, discreteDescriptorSet apply across all stages,
-// and shaderInputs are populated before each stage, so neither needs to be filtered by stage here.
+// and shaderInputs and shaderOutputs are populated before each stage, so neither needs to be filtered by stage here.
 MVK_PUBLIC_SYMBOL bool SPIRVToMSLConversionConfiguration::matches(const SPIRVToMSLConversionConfiguration& other) const {
 
     if ( !options.matches(other.options) ) { return false; }
@@ -184,6 +199,10 @@
 		if (si.outIsUsedByShader && !containsMatching(other.shaderInputs, si)) { return false; }
 	}
 
+	for (const auto& so : shaderOutputs) {
+		if (so.outIsUsedByShader && !containsMatching(other.shaderOutputs, so)) { return false; }
+	}
+
     for (const auto& rb : resourceBindings) {
         if (rb.resourceBinding.stage == options.entryPointStage &&
 			rb.outIsUsedByShader &&
@@ -212,6 +231,13 @@
 		}
 	}
 
+	for (auto& so : shaderOutputs) {
+		so.outIsUsedByShader = false;
+		for (auto& srcSO : srcContext.shaderOutputs) {
+			if (so.matches(srcSO)) { so.outIsUsedByShader = srcSO.outIsUsedByShader; }
+		}
+	}
+
     for (auto& rb : resourceBindings) {
         rb.outIsUsedByShader = false;
         for (auto& srcRB : srcContext.resourceBindings) {
@@ -281,9 +307,13 @@
 		scOpts.vertex.flip_vert_y = shaderConfig.options.shouldFlipVertexY;
 		pMSLCompiler->set_common_options(scOpts);
 
-		// Add shader inputs
+		// Add shader inputs and outputs
 		for (auto& si : shaderConfig.shaderInputs) {
-			pMSLCompiler->add_msl_shader_input(si.shaderInput);
+			pMSLCompiler->add_msl_shader_input(si.shaderVar);
+		}
+
+		for (auto& so : shaderConfig.shaderOutputs) {
+			pMSLCompiler->add_msl_shader_output(so.shaderVar);
 		}
 
 		// Add resource bindings and hardcoded constexpr samplers
@@ -352,7 +382,18 @@
 	}
 
 	for (auto& ctxSI : shaderConfig.shaderInputs) {
-		ctxSI.outIsUsedByShader = pMSLCompiler->is_msl_shader_input_used(ctxSI.shaderInput.location);
+		if (ctxSI.shaderVar.builtin != spv::BuiltInMax) {
+			ctxSI.outIsUsedByShader = pMSLCompiler->has_active_builtin(ctxSI.shaderVar.builtin, spv::StorageClassInput);
+		} else {
+			ctxSI.outIsUsedByShader = pMSLCompiler->is_msl_shader_input_used(ctxSI.shaderVar.location);
+		}
+	}
+	for (auto& ctxSO : shaderConfig.shaderOutputs) {
+		if (ctxSO.shaderVar.builtin != spv::BuiltInMax) {
+			ctxSO.outIsUsedByShader = pMSLCompiler->has_active_builtin(ctxSO.shaderVar.builtin, spv::StorageClassOutput);
+		} else {
+			ctxSO.outIsUsedByShader = pMSLCompiler->is_msl_shader_output_used(ctxSO.shaderVar.location);
+		}
 	}
 	for (auto& ctxRB : shaderConfig.resourceBindings) {
 		if (ctxRB.resourceBinding.stage == shaderConfig.options.entryPointStage) {
diff --git a/MoltenVKShaderConverter/MoltenVKShaderConverter/SPIRVToMSLConverter.h b/MoltenVKShaderConverter/MoltenVKShaderConverter/SPIRVToMSLConverter.h
index bd0b297..5f00e50 100644
--- a/MoltenVKShaderConverter/MoltenVKShaderConverter/SPIRVToMSLConverter.h
+++ b/MoltenVKShaderConverter/MoltenVKShaderConverter/SPIRVToMSLConverter.h
@@ -61,30 +61,30 @@
 	} SPIRVToMSLConversionOptions;
 
 	/**
-	 * Defines MSL characteristics of a vertex attribute at a particular location.
+	 * Defines MSL characteristics of a shader interface variable at a particular location.
 	 *
 	 * The outIsUsedByShader flag is set to true during conversion of SPIR-V to MSL if the shader
-	 * makes use of this vertex attribute. This allows a pipeline to be optimized, and for two
+	 * makes use of this interface variable. This allows a pipeline to be optimized, and for two
 	 * shader conversion configurations to be compared only against the attributes that are
 	 * actually used by the shader.
 	 *
 	 * THIS STRUCT IS STREAMED OUT AS PART OF THE PIPELINE CACHE.
 	 * CHANGES TO THIS STRUCT SHOULD BE CAPTURED IN THE STREAMING LOGIC OF THE PIPELINE CACHE.
 	 */
-	typedef struct MSLShaderInput {
-		SPIRV_CROSS_NAMESPACE::MSLShaderInput shaderInput;
+	typedef struct MSLShaderInterfaceVariable {
+		SPIRV_CROSS_NAMESPACE::MSLShaderInterfaceVariable shaderVar;
 		uint32_t binding = 0;
 		bool outIsUsedByShader = false;
 
 		/**
-		 * Returns whether the specified vertex attribute match this one.
+		 * Returns whether the specified interface variable match this one.
 		 * It does if all corresponding elements except outIsUsedByShader are equal.
 		 */
-		bool matches(const MSLShaderInput& other) const;
+		bool matches(const MSLShaderInterfaceVariable& other) const;
 
-		MSLShaderInput();
+		MSLShaderInterfaceVariable();
 
-	} MSLShaderInput;
+	} MSLShaderInterfaceVariable, MSLShaderInput;
 
 	/**
 	 * Matches the binding index of a MSL resource for a binding within a descriptor set.
@@ -146,7 +146,8 @@
 	 */
 	typedef struct SPIRVToMSLConversionConfiguration {
 		SPIRVToMSLConversionOptions options;
-		std::vector<MSLShaderInput> shaderInputs;
+		std::vector<MSLShaderInterfaceVariable> shaderInputs;
+		std::vector<MSLShaderInterfaceVariable> shaderOutputs;
 		std::vector<MSLResourceBinding> resourceBindings;
 		std::vector<uint32_t> discreteDescriptorSets;
 		std::vector<DescriptorBinding> dynamicBufferDescriptors;
@@ -157,17 +158,23 @@
         /** Returns whether the shader input variable at the specified location is used by the shader. */
         bool isShaderInputLocationUsed(uint32_t location) const;
 
+		/** Returns whether the specified built-in shader input variable is used by the shader. */
+		bool isShaderInputBuiltInUsed(spv::BuiltIn builtin) const;
+
 		/** Returns the number of shader input variables bound to the specified Vulkan buffer binding, and used by the shader. */
 		uint32_t countShaderInputsAt(uint32_t binding) const;
 
+        /** Returns whether the shader output variable at the specified location is used by the shader. */
+        bool isShaderOutputLocationUsed(uint32_t location) const;
+
         /** Returns whether the vertex buffer at the specified Vulkan binding is used by the shader. */
 		bool isVertexBufferUsed(uint32_t binding) const { return countShaderInputsAt(binding) > 0; }
 
 		/** Returns whether the resource at the specified descriptor set binding is used by the shader. */
 		bool isResourceUsed(spv::ExecutionModel stage, uint32_t descSet, uint32_t binding) const;
 
-		/** Marks all input variables and resources as being used by the shader. */
-		void markAllInputsAndResourcesUsed();
+		/** Marks all interface variables and resources as being used by the shader. */
+		void markAllInterfaceVarsAndResourcesUsed();
 
         /**
          * Returns whether this configuration matches the other configuration. It does if