SpirvShader: Move spec ops to new cpp file

Bug: b/145336353
Change-Id: I202a8c4a126b2de25703bdde6409e878d4f33d31
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/38815
Kokoro-Presubmit: kokoro <noreply+kokoro@google.com>
Reviewed-by: Alexis Hétu <sugoi@google.com>
Tested-by: Ben Clayton <bclayton@google.com>
diff --git a/src/Pipeline/BUILD.gn b/src/Pipeline/BUILD.gn
index 7f158fb..0422f97 100644
--- a/src/Pipeline/BUILD.gn
+++ b/src/Pipeline/BUILD.gn
@@ -45,6 +45,7 @@
     "SpirvShaderGroup.cpp",
     "SpirvShaderMemory.cpp",
     "SpirvShaderSampling.cpp",
+    "SpirvShaderSpec.cpp",
     "VertexProgram.cpp",
     "VertexRoutine.cpp",
   ]
diff --git a/src/Pipeline/SpirvShader.cpp b/src/Pipeline/SpirvShader.cpp
index b8632d3..f8fc6d0 100644
--- a/src/Pipeline/SpirvShader.cpp
+++ b/src/Pipeline/SpirvShader.cpp
@@ -3809,299 +3809,6 @@
 		return scopeObj.constantValue[0];
 	}
 
-	void SpirvShader::EvalSpecConstantOp(InsnIterator insn)
-	{
-		auto opcode = static_cast<spv::Op>(insn.word(3));
-
-		switch (opcode)
-		{
-		case spv::OpIAdd:
-		case spv::OpISub:
-		case spv::OpIMul:
-		case spv::OpUDiv:
-		case spv::OpSDiv:
-		case spv::OpUMod:
-		case spv::OpSMod:
-		case spv::OpSRem:
-		case spv::OpShiftRightLogical:
-		case spv::OpShiftRightArithmetic:
-		case spv::OpShiftLeftLogical:
-		case spv::OpBitwiseOr:
-		case spv::OpLogicalOr:
-		case spv::OpBitwiseAnd:
-		case spv::OpLogicalAnd:
-		case spv::OpBitwiseXor:
-		case spv::OpLogicalEqual:
-		case spv::OpIEqual:
-		case spv::OpLogicalNotEqual:
-		case spv::OpINotEqual:
-		case spv::OpULessThan:
-		case spv::OpSLessThan:
-		case spv::OpUGreaterThan:
-		case spv::OpSGreaterThan:
-		case spv::OpULessThanEqual:
-		case spv::OpSLessThanEqual:
-		case spv::OpUGreaterThanEqual:
-		case spv::OpSGreaterThanEqual:
-			EvalSpecConstantBinaryOp(insn);
-			break;
-
-		case spv::OpSConvert:
-		case spv::OpFConvert:
-		case spv::OpUConvert:
-		case spv::OpSNegate:
-		case spv::OpNot:
-		case spv::OpLogicalNot:
-		case spv::OpQuantizeToF16:
-			EvalSpecConstantUnaryOp(insn);
-			break;
-
-		case spv::OpSelect:
-		{
-			auto &result = CreateConstant(insn);
-			auto const &cond = getObject(insn.word(4));
-			auto condIsScalar = (getType(cond.type).sizeInComponents == 1);
-			auto const &left = getObject(insn.word(5));
-			auto const &right = getObject(insn.word(6));
-
-			for (auto i = 0u; i < getType(result.type).sizeInComponents; i++)
-			{
-				auto sel = cond.constantValue[condIsScalar ? 0 : i];
-				result.constantValue[i] = sel ? left.constantValue[i] : right.constantValue[i];
-			}
-			break;
-		}
-
-		case spv::OpCompositeExtract:
-		{
-			auto &result = CreateConstant(insn);
-			auto const &compositeObject = getObject(insn.word(4));
-			auto firstComponent = WalkLiteralAccessChain(compositeObject.type, insn.wordCount() - 5, insn.wordPointer(5));
-
-			for (auto i = 0u; i < getType(result.type).sizeInComponents; i++)
-			{
-				result.constantValue[i] = compositeObject.constantValue[firstComponent + i];
-			}
-			break;
-		}
-
-		case spv::OpCompositeInsert:
-		{
-			auto &result = CreateConstant(insn);
-			auto const &newPart = getObject(insn.word(4));
-			auto const &oldObject = getObject(insn.word(5));
-			auto firstNewComponent = WalkLiteralAccessChain(result.type, insn.wordCount() - 6, insn.wordPointer(6));
-
-			// old components before
-			for (auto i = 0u; i < firstNewComponent; i++)
-			{
-				result.constantValue[i] = oldObject.constantValue[i];
-			}
-			// new part
-			for (auto i = 0u; i < getType(newPart.type).sizeInComponents; i++)
-			{
-				result.constantValue[firstNewComponent + i] = newPart.constantValue[i];
-			}
-			// old components after
-			for (auto i = firstNewComponent + getType(newPart.type).sizeInComponents; i < getType(result.type).sizeInComponents; i++)
-			{
-				result.constantValue[i] = oldObject.constantValue[i];
-			}
-			break;
-		}
-
-		case spv::OpVectorShuffle:
-		{
-			auto &result = CreateConstant(insn);
-			auto const &firstHalf = getObject(insn.word(4));
-			auto const &secondHalf = getObject(insn.word(5));
-
-			for (auto i = 0u; i < getType(result.type).sizeInComponents; i++)
-			{
-				auto selector = insn.word(6 + i);
-				if (selector == static_cast<uint32_t>(-1))
-				{
-					// Undefined value, we'll use zero
-					result.constantValue[i] = 0;
-				}
-				else if (selector < getType(firstHalf.type).sizeInComponents)
-				{
-					result.constantValue[i] = firstHalf.constantValue[selector];
-				}
-				else
-				{
-					result.constantValue[i] = secondHalf.constantValue[selector - getType(firstHalf.type).sizeInComponents];
-				}
-			}
-			break;
-		}
-
-		default:
-			// Other spec constant ops are possible, but require capabilities that are
-			// not exposed in our Vulkan implementation (eg Kernel), so we should never
-			// get here for correct shaders.
-			UNSUPPORTED("EvalSpecConstantOp op: %s", OpcodeName(opcode).c_str());
-		}
-	}
-
-	void SpirvShader::EvalSpecConstantUnaryOp(InsnIterator insn)
-	{
-		auto &result = CreateConstant(insn);
-
-		auto opcode = static_cast<spv::Op>(insn.word(3));
-		auto const &lhs = getObject(insn.word(4));
-		auto size = getType(lhs.type).sizeInComponents;
-
-		for (auto i = 0u; i < size; i++)
-		{
-			auto &v = result.constantValue[i];
-			auto l = lhs.constantValue[i];
-
-			switch (opcode)
-			{
-			case spv::OpSConvert:
-			case spv::OpFConvert:
-			case spv::OpUConvert:
-				UNREACHABLE("Not possible until we have multiple bit widths");
-				break;
-
-			case spv::OpSNegate:
-				v = -(int)l;
-				break;
-			case spv::OpNot:
-			case spv::OpLogicalNot:
-				v = ~l;
-				break;
-
-			case spv::OpQuantizeToF16:
-			{
-				// Can do this nicer with host code, but want to perfectly mirror the reactor code we emit.
-				auto abs = bit_cast<float>(l & 0x7FFFFFFF);
-				auto sign = l & 0x80000000;
-				auto isZero = abs < 0.000061035f ? ~0u : 0u;
-				auto isInf = abs > 65504.0f ? ~0u : 0u;
-				auto isNaN = (abs != abs) ? ~0u : 0u;
-				auto isInfOrNan = isInf | isNaN;
-				v = l & 0xFFFFE000;
-				v &= ~isZero | 0x80000000;
-				v = sign | (isInfOrNan & 0x7F800000) | (~isInfOrNan & v);
-				v |= isNaN & 0x400000;
-				break;
-			}
-			default:
-				UNREACHABLE("EvalSpecConstantUnaryOp op: %s", OpcodeName(opcode).c_str());
-			}
-		}
-	}
-
-	void SpirvShader::EvalSpecConstantBinaryOp(InsnIterator insn)
-	{
-		auto &result = CreateConstant(insn);
-
-		auto opcode = static_cast<spv::Op>(insn.word(3));
-		auto const &lhs = getObject(insn.word(4));
-		auto const &rhs = getObject(insn.word(5));
-		auto size = getType(lhs.type).sizeInComponents;
-
-		for (auto i = 0u; i < size; i++)
-		{
-			auto &v = result.constantValue[i];
-			auto l = lhs.constantValue[i];
-			auto r = rhs.constantValue[i];
-
-			switch (opcode)
-			{
-			case spv::OpIAdd:
-				v = l + r;
-				break;
-			case spv::OpISub:
-				v = l - r;
-				break;
-			case spv::OpIMul:
-				v = l * r;
-				break;
-			case spv::OpUDiv:
-				v = (r == 0) ? 0 : l / r;
-				break;
-			case spv::OpUMod:
-				v = (r == 0) ? 0 : l % r;
-				break;
-			case spv::OpSDiv:
-				if (r == 0) r = UINT32_MAX;
-				if (l == static_cast<uint32_t>(INT32_MIN)) l = UINT32_MAX;
-				v = static_cast<int32_t>(l) / static_cast<int32_t>(r);
-				break;
-			case spv::OpSRem:
-				if (r == 0) r = UINT32_MAX;
-				if (l == static_cast<uint32_t>(INT32_MIN)) l = UINT32_MAX;
-				v = static_cast<int32_t>(l) % static_cast<int32_t>(r);
-				break;
-			case spv::OpSMod:
-				if (r == 0) r = UINT32_MAX;
-				if (l == static_cast<uint32_t>(INT32_MIN)) l = UINT32_MAX;
-				// Test if a signed-multiply would be negative.
-				v = static_cast<int32_t>(l) % static_cast<int32_t>(r);
-				if ((v & 0x80000000) != (r & 0x80000000))
-					v += r;
-				break;
-			case spv::OpShiftRightLogical:
-				v = l >> r;
-				break;
-			case spv::OpShiftRightArithmetic:
-				v = static_cast<int32_t>(l) >> r;
-				break;
-			case spv::OpShiftLeftLogical:
-				v = l << r;
-				break;
-			case spv::OpBitwiseOr:
-			case spv::OpLogicalOr:
-				v = l | r;
-				break;
-			case spv::OpBitwiseAnd:
-			case spv::OpLogicalAnd:
-				v = l & r;
-				break;
-			case spv::OpBitwiseXor:
-				v = l ^ r;
-				break;
-			case spv::OpLogicalEqual:
-			case spv::OpIEqual:
-				v = (l == r) ? ~0u : 0u;
-				break;
-			case spv::OpLogicalNotEqual:
-			case spv::OpINotEqual:
-				v = (l != r) ? ~0u : 0u;
-				break;
-			case spv::OpULessThan:
-				v = l < r ? ~0u : 0u;
-				break;
-			case spv::OpSLessThan:
-				v = static_cast<int32_t>(l) < static_cast<int32_t>(r) ? ~0u : 0u;
-				break;
-			case spv::OpUGreaterThan:
-				v = l > r ? ~0u : 0u;
-				break;
-			case spv::OpSGreaterThan:
-				v = static_cast<int32_t>(l) > static_cast<int32_t>(r) ? ~0u : 0u;
-				break;
-			case spv::OpULessThanEqual:
-				v = l <= r ? ~0u : 0u;
-				break;
-			case spv::OpSLessThanEqual:
-				v = static_cast<int32_t>(l) <= static_cast<int32_t>(r) ? ~0u : 0u;
-				break;
-			case spv::OpUGreaterThanEqual:
-				v = l >= r ? ~0u : 0u;
-				break;
-			case spv::OpSGreaterThanEqual:
-				v = static_cast<int32_t>(l) >= static_cast<int32_t>(r) ? ~0u : 0u;
-				break;
-			default:
-				UNREACHABLE("EvalSpecConstantBinaryOp op: %s", OpcodeName(opcode).c_str());
-			}
-		}
-	}
-
 	void SpirvShader::emitEpilog(SpirvRoutine *routine) const
 	{
 		for (auto insn : *this)
diff --git a/src/Pipeline/SpirvShaderSpec.cpp b/src/Pipeline/SpirvShaderSpec.cpp
new file mode 100644
index 0000000..38e0267
--- /dev/null
+++ b/src/Pipeline/SpirvShaderSpec.cpp
@@ -0,0 +1,315 @@
+// Copyright 2019 The SwiftShader Authors. All Rights Reserved.
+//
+// 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 "SpirvShader.hpp"
+
+#include <spirv/unified1/spirv.hpp>
+#include <spirv/unified1/GLSL.std.450.h>
+
+namespace sw {
+
+void SpirvShader::EvalSpecConstantOp(InsnIterator insn)
+{
+	auto opcode = static_cast<spv::Op>(insn.word(3));
+
+	switch (opcode)
+	{
+	case spv::OpIAdd:
+	case spv::OpISub:
+	case spv::OpIMul:
+	case spv::OpUDiv:
+	case spv::OpSDiv:
+	case spv::OpUMod:
+	case spv::OpSMod:
+	case spv::OpSRem:
+	case spv::OpShiftRightLogical:
+	case spv::OpShiftRightArithmetic:
+	case spv::OpShiftLeftLogical:
+	case spv::OpBitwiseOr:
+	case spv::OpLogicalOr:
+	case spv::OpBitwiseAnd:
+	case spv::OpLogicalAnd:
+	case spv::OpBitwiseXor:
+	case spv::OpLogicalEqual:
+	case spv::OpIEqual:
+	case spv::OpLogicalNotEqual:
+	case spv::OpINotEqual:
+	case spv::OpULessThan:
+	case spv::OpSLessThan:
+	case spv::OpUGreaterThan:
+	case spv::OpSGreaterThan:
+	case spv::OpULessThanEqual:
+	case spv::OpSLessThanEqual:
+	case spv::OpUGreaterThanEqual:
+	case spv::OpSGreaterThanEqual:
+		EvalSpecConstantBinaryOp(insn);
+		break;
+
+	case spv::OpSConvert:
+	case spv::OpFConvert:
+	case spv::OpUConvert:
+	case spv::OpSNegate:
+	case spv::OpNot:
+	case spv::OpLogicalNot:
+	case spv::OpQuantizeToF16:
+		EvalSpecConstantUnaryOp(insn);
+		break;
+
+	case spv::OpSelect:
+	{
+		auto &result = CreateConstant(insn);
+		auto const &cond = getObject(insn.word(4));
+		auto condIsScalar = (getType(cond.type).sizeInComponents == 1);
+		auto const &left = getObject(insn.word(5));
+		auto const &right = getObject(insn.word(6));
+
+		for (auto i = 0u; i < getType(result.type).sizeInComponents; i++)
+		{
+			auto sel = cond.constantValue[condIsScalar ? 0 : i];
+			result.constantValue[i] = sel ? left.constantValue[i] : right.constantValue[i];
+		}
+		break;
+	}
+
+	case spv::OpCompositeExtract:
+	{
+		auto &result = CreateConstant(insn);
+		auto const &compositeObject = getObject(insn.word(4));
+		auto firstComponent = WalkLiteralAccessChain(compositeObject.type, insn.wordCount() - 5, insn.wordPointer(5));
+
+		for (auto i = 0u; i < getType(result.type).sizeInComponents; i++)
+		{
+			result.constantValue[i] = compositeObject.constantValue[firstComponent + i];
+		}
+		break;
+	}
+
+	case spv::OpCompositeInsert:
+	{
+		auto &result = CreateConstant(insn);
+		auto const &newPart = getObject(insn.word(4));
+		auto const &oldObject = getObject(insn.word(5));
+		auto firstNewComponent = WalkLiteralAccessChain(result.type, insn.wordCount() - 6, insn.wordPointer(6));
+
+		// old components before
+		for (auto i = 0u; i < firstNewComponent; i++)
+		{
+			result.constantValue[i] = oldObject.constantValue[i];
+		}
+		// new part
+		for (auto i = 0u; i < getType(newPart.type).sizeInComponents; i++)
+		{
+			result.constantValue[firstNewComponent + i] = newPart.constantValue[i];
+		}
+		// old components after
+		for (auto i = firstNewComponent + getType(newPart.type).sizeInComponents; i < getType(result.type).sizeInComponents; i++)
+		{
+			result.constantValue[i] = oldObject.constantValue[i];
+		}
+		break;
+	}
+
+	case spv::OpVectorShuffle:
+	{
+		auto &result = CreateConstant(insn);
+		auto const &firstHalf = getObject(insn.word(4));
+		auto const &secondHalf = getObject(insn.word(5));
+
+		for (auto i = 0u; i < getType(result.type).sizeInComponents; i++)
+		{
+			auto selector = insn.word(6 + i);
+			if (selector == static_cast<uint32_t>(-1))
+			{
+				// Undefined value, we'll use zero
+				result.constantValue[i] = 0;
+			}
+			else if (selector < getType(firstHalf.type).sizeInComponents)
+			{
+				result.constantValue[i] = firstHalf.constantValue[selector];
+			}
+			else
+			{
+				result.constantValue[i] = secondHalf.constantValue[selector - getType(firstHalf.type).sizeInComponents];
+			}
+		}
+		break;
+	}
+
+	default:
+		// Other spec constant ops are possible, but require capabilities that are
+		// not exposed in our Vulkan implementation (eg Kernel), so we should never
+		// get here for correct shaders.
+		UNSUPPORTED("EvalSpecConstantOp op: %s", OpcodeName(opcode).c_str());
+	}
+}
+
+void SpirvShader::EvalSpecConstantUnaryOp(InsnIterator insn)
+{
+	auto &result = CreateConstant(insn);
+
+	auto opcode = static_cast<spv::Op>(insn.word(3));
+	auto const &lhs = getObject(insn.word(4));
+	auto size = getType(lhs.type).sizeInComponents;
+
+	for (auto i = 0u; i < size; i++)
+	{
+		auto &v = result.constantValue[i];
+		auto l = lhs.constantValue[i];
+
+		switch (opcode)
+		{
+		case spv::OpSConvert:
+		case spv::OpFConvert:
+		case spv::OpUConvert:
+			UNREACHABLE("Not possible until we have multiple bit widths");
+			break;
+
+		case spv::OpSNegate:
+			v = -(int)l;
+			break;
+		case spv::OpNot:
+		case spv::OpLogicalNot:
+			v = ~l;
+			break;
+
+		case spv::OpQuantizeToF16:
+		{
+			// Can do this nicer with host code, but want to perfectly mirror the reactor code we emit.
+			auto abs = bit_cast<float>(l & 0x7FFFFFFF);
+			auto sign = l & 0x80000000;
+			auto isZero = abs < 0.000061035f ? ~0u : 0u;
+			auto isInf = abs > 65504.0f ? ~0u : 0u;
+			auto isNaN = (abs != abs) ? ~0u : 0u;
+			auto isInfOrNan = isInf | isNaN;
+			v = l & 0xFFFFE000;
+			v &= ~isZero | 0x80000000;
+			v = sign | (isInfOrNan & 0x7F800000) | (~isInfOrNan & v);
+			v |= isNaN & 0x400000;
+			break;
+		}
+		default:
+			UNREACHABLE("EvalSpecConstantUnaryOp op: %s", OpcodeName(opcode).c_str());
+		}
+	}
+}
+
+void SpirvShader::EvalSpecConstantBinaryOp(InsnIterator insn)
+{
+	auto &result = CreateConstant(insn);
+
+	auto opcode = static_cast<spv::Op>(insn.word(3));
+	auto const &lhs = getObject(insn.word(4));
+	auto const &rhs = getObject(insn.word(5));
+	auto size = getType(lhs.type).sizeInComponents;
+
+	for (auto i = 0u; i < size; i++)
+	{
+		auto &v = result.constantValue[i];
+		auto l = lhs.constantValue[i];
+		auto r = rhs.constantValue[i];
+
+		switch (opcode)
+		{
+		case spv::OpIAdd:
+			v = l + r;
+			break;
+		case spv::OpISub:
+			v = l - r;
+			break;
+		case spv::OpIMul:
+			v = l * r;
+			break;
+		case spv::OpUDiv:
+			v = (r == 0) ? 0 : l / r;
+			break;
+		case spv::OpUMod:
+			v = (r == 0) ? 0 : l % r;
+			break;
+		case spv::OpSDiv:
+			if (r == 0) r = UINT32_MAX;
+			if (l == static_cast<uint32_t>(INT32_MIN)) l = UINT32_MAX;
+			v = static_cast<int32_t>(l) / static_cast<int32_t>(r);
+			break;
+		case spv::OpSRem:
+			if (r == 0) r = UINT32_MAX;
+			if (l == static_cast<uint32_t>(INT32_MIN)) l = UINT32_MAX;
+			v = static_cast<int32_t>(l) % static_cast<int32_t>(r);
+			break;
+		case spv::OpSMod:
+			if (r == 0) r = UINT32_MAX;
+			if (l == static_cast<uint32_t>(INT32_MIN)) l = UINT32_MAX;
+			// Test if a signed-multiply would be negative.
+			v = static_cast<int32_t>(l) % static_cast<int32_t>(r);
+			if ((v & 0x80000000) != (r & 0x80000000))
+				v += r;
+			break;
+		case spv::OpShiftRightLogical:
+			v = l >> r;
+			break;
+		case spv::OpShiftRightArithmetic:
+			v = static_cast<int32_t>(l) >> r;
+			break;
+		case spv::OpShiftLeftLogical:
+			v = l << r;
+			break;
+		case spv::OpBitwiseOr:
+		case spv::OpLogicalOr:
+			v = l | r;
+			break;
+		case spv::OpBitwiseAnd:
+		case spv::OpLogicalAnd:
+			v = l & r;
+			break;
+		case spv::OpBitwiseXor:
+			v = l ^ r;
+			break;
+		case spv::OpLogicalEqual:
+		case spv::OpIEqual:
+			v = (l == r) ? ~0u : 0u;
+			break;
+		case spv::OpLogicalNotEqual:
+		case spv::OpINotEqual:
+			v = (l != r) ? ~0u : 0u;
+			break;
+		case spv::OpULessThan:
+			v = l < r ? ~0u : 0u;
+			break;
+		case spv::OpSLessThan:
+			v = static_cast<int32_t>(l) < static_cast<int32_t>(r) ? ~0u : 0u;
+			break;
+		case spv::OpUGreaterThan:
+			v = l > r ? ~0u : 0u;
+			break;
+		case spv::OpSGreaterThan:
+			v = static_cast<int32_t>(l) > static_cast<int32_t>(r) ? ~0u : 0u;
+			break;
+		case spv::OpULessThanEqual:
+			v = l <= r ? ~0u : 0u;
+			break;
+		case spv::OpSLessThanEqual:
+			v = static_cast<int32_t>(l) <= static_cast<int32_t>(r) ? ~0u : 0u;
+			break;
+		case spv::OpUGreaterThanEqual:
+			v = l >= r ? ~0u : 0u;
+			break;
+		case spv::OpSGreaterThanEqual:
+			v = static_cast<int32_t>(l) >= static_cast<int32_t>(r) ? ~0u : 0u;
+			break;
+		default:
+			UNREACHABLE("EvalSpecConstantBinaryOp op: %s", OpcodeName(opcode).c_str());
+		}
+	}
+}
+
+}  // namespace sw
\ No newline at end of file
diff --git a/src/Vulkan/vulkan.vcxproj b/src/Vulkan/vulkan.vcxproj
index 956dfa7..d575588 100644
--- a/src/Vulkan/vulkan.vcxproj
+++ b/src/Vulkan/vulkan.vcxproj
@@ -170,6 +170,7 @@
     <ClCompile Include="..\Pipeline\SpirvShaderGroup.cpp" />

     <ClCompile Include="..\Pipeline\SpirvShaderMemory.cpp" />

     <ClCompile Include="..\Pipeline\SpirvShaderSampling.cpp" />

+    <ClCompile Include="..\Pipeline\SpirvShaderSpec.cpp" />

     <ClCompile Include="..\Pipeline\SpirvShader_dbg.cpp" />

     <ClCompile Include="..\Pipeline\VertexProgram.cpp" />

     <ClCompile Include="..\Pipeline\VertexRoutine.cpp" />

diff --git a/src/Vulkan/vulkan.vcxproj.filters b/src/Vulkan/vulkan.vcxproj.filters
index cb566bb..1117b53 100644
--- a/src/Vulkan/vulkan.vcxproj.filters
+++ b/src/Vulkan/vulkan.vcxproj.filters
@@ -261,6 +261,9 @@
     <ClCompile Include="..\Pipeline\SpirvShaderSampling.cpp">

       <Filter>Source Files\Pipeline</Filter>

     </ClCompile>

+    <ClCompile Include="..\Pipeline\SpirvShaderSpec.cpp">

+      <Filter>Source Files\Pipeline</Filter>

+    </ClCompile>

     <ClCompile Include="..\Pipeline\SpirvShader_dbg.cpp">

       <Filter>Source Files\Pipeline</Filter>

     </ClCompile>