From f9d613b7953eecdede8682608f857bc09540dd1a Mon Sep 17 00:00:00 2001 From: Lei Zhang Date: Mon, 8 Jan 2018 11:42:47 -0500 Subject: [PATCH] [spirv] Add support for -fvk-invert-y (#967) This is to accommodate Vulkan's coordinate system, which is different from DX's. --- docs/SPIR-V.rst | 3 ++ include/dxc/Support/HLSLOptions.h | 1 + include/dxc/Support/HLSLOptions.td | 2 ++ lib/DxcSupport/HLSLOptions.cpp | 2 ++ .../include/clang/SPIRV/EmitSPIRVOptions.h | 1 + .../clang/include/clang/SPIRV/ModuleBuilder.h | 13 ++++++-- tools/clang/lib/SPIRV/DeclResultIdMapper.cpp | 12 +++++++ tools/clang/lib/SPIRV/DeclResultIdMapper.h | 2 +- tools/clang/lib/SPIRV/GlPerVertex.cpp | 27 +++++++++++---- tools/clang/lib/SPIRV/GlPerVertex.h | 8 +++-- tools/clang/lib/SPIRV/ModuleBuilder.cpp | 27 ++++++++++----- tools/clang/lib/SPIRV/SPIRVEmitter.cpp | 3 ++ .../CodeGenSPIRV/vk.cloption.invert-y.ds.hlsl | 33 +++++++++++++++++++ .../CodeGenSPIRV/vk.cloption.invert-y.gs.hlsl | 29 ++++++++++++++++ .../CodeGenSPIRV/vk.cloption.invert-y.vs.hlsl | 12 +++++++ tools/clang/test/vk.cloption.invert-y.vs.hlsl | 0 .../clang/tools/dxcompiler/dxcompilerobj.cpp | 1 + .../unittests/SPIRV/CodeGenSPIRVTest.cpp | 10 ++++++ 18 files changed, 165 insertions(+), 21 deletions(-) create mode 100644 tools/clang/test/CodeGenSPIRV/vk.cloption.invert-y.ds.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/vk.cloption.invert-y.gs.hlsl create mode 100644 tools/clang/test/CodeGenSPIRV/vk.cloption.invert-y.vs.hlsl create mode 100644 tools/clang/test/vk.cloption.invert-y.vs.hlsl diff --git a/docs/SPIR-V.rst b/docs/SPIR-V.rst index b940a8225..7b27b62c1 100644 --- a/docs/SPIR-V.rst +++ b/docs/SPIR-V.rst @@ -2316,6 +2316,9 @@ codegen for Vulkan: - ``-fvk-ignore-unused-resources``: Avoids emitting SPIR-V code for resources defined but not statically referenced by the call tree of the entry point in question. +- ``-fvk-invert-y``: Inverts SV_Position.y before writing to stage output. + Used to accommodate the difference between Vulkan's coordinate system and + DirectX's. Only allowed in VS/DS/GS. - ``-fvk-stage-io-order={alpha|decl}``: Assigns the stage input/output variable location number according to alphabetical order or declaration order. See `HLSL semantic and Vulkan Location`_ for more details. diff --git a/include/dxc/Support/HLSLOptions.h b/include/dxc/Support/HLSLOptions.h index 01bfbf4d9..0b0db7574 100644 --- a/include/dxc/Support/HLSLOptions.h +++ b/include/dxc/Support/HLSLOptions.h @@ -159,6 +159,7 @@ public: #ifdef ENABLE_SPIRV_CODEGEN bool GenSPIRV; // OPT_spirv bool VkIgnoreUnusedResources; // OPT_fvk_ignore_used_resources + bool VkInvertY; // OPT_fvk_invert_y llvm::StringRef VkStageIoOrder; // OPT_fvk_stage_io_order llvm::SmallVector VkBShift; // OPT_fvk_b_shift llvm::SmallVector VkTShift; // OPT_fvk_t_shift diff --git a/include/dxc/Support/HLSLOptions.td b/include/dxc/Support/HLSLOptions.td index 0bae3b7d8..c22fa8ce3 100644 --- a/include/dxc/Support/HLSLOptions.td +++ b/include/dxc/Support/HLSLOptions.td @@ -248,6 +248,8 @@ def fvk_s_shift : MultiArg<["-"], "fvk-s-shift", 2>, MetaVarName<" ; def fvk_u_shift : MultiArg<["-"], "fvk-u-shift", 2>, MetaVarName<" ">, Group, Flags<[CoreOption, DriverOption]>, HelpText<"Specify Vulkan binding number shift for u-type register">; +def fvk_invert_y: Flag<["-"], "fvk-invert-y">, Group, Flags<[CoreOption, DriverOption]>, + HelpText<"Invert SV_Position.y in VS/DS/GS to accommodate Vulkan's coordinate system">; // SPIRV Change Ends ////////////////////////////////////////////////////////////////////////////// diff --git a/lib/DxcSupport/HLSLOptions.cpp b/lib/DxcSupport/HLSLOptions.cpp index 827f118bb..1dc99eb99 100644 --- a/lib/DxcSupport/HLSLOptions.cpp +++ b/lib/DxcSupport/HLSLOptions.cpp @@ -481,6 +481,7 @@ int ReadDxcOpts(const OptTable *optionTable, unsigned flagsToInclude, // SPIRV Change Starts #ifdef ENABLE_SPIRV_CODEGEN const bool genSpirv = opts.GenSPIRV = Args.hasFlag(OPT_spirv, OPT_INVALID, false); + opts.VkInvertY = Args.hasFlag(OPT_fvk_invert_y, OPT_INVALID, false); opts.VkIgnoreUnusedResources = Args.hasFlag(OPT_fvk_ignore_unused_resources, OPT_INVALID, false); // Collects the arguments for -fvk-{b|s|t|u}-shift. @@ -519,6 +520,7 @@ int ReadDxcOpts(const OptTable *optionTable, unsigned flagsToInclude, } #else if (Args.hasFlag(OPT_spirv, OPT_INVALID, false) || + Args.hasFlag(OPT_fvk_invert_y, OPT_INVALID, false) || Args.hasFlag(OPT_fvk_ignore_unused_resources, OPT_INVALID, false) || !Args.getLastArgValue(OPT_fvk_stage_io_order_EQ).empty() || !Args.getLastArgValue(OPT_fvk_b_shift).empty() || diff --git a/tools/clang/include/clang/SPIRV/EmitSPIRVOptions.h b/tools/clang/include/clang/SPIRV/EmitSPIRVOptions.h index e31061413..0d482f458 100644 --- a/tools/clang/include/clang/SPIRV/EmitSPIRVOptions.h +++ b/tools/clang/include/clang/SPIRV/EmitSPIRVOptions.h @@ -19,6 +19,7 @@ struct EmitSPIRVOptions { bool codeGenHighLevel; bool defaultRowMajor; bool disableValidation; + bool invertY; bool ignoreUnusedResources; bool enable16BitTypes; llvm::StringRef stageIoOrder; diff --git a/tools/clang/include/clang/SPIRV/ModuleBuilder.h b/tools/clang/include/clang/SPIRV/ModuleBuilder.h index d11325c3e..8bcce27b9 100644 --- a/tools/clang/include/clang/SPIRV/ModuleBuilder.h +++ b/tools/clang/include/clang/SPIRV/ModuleBuilder.h @@ -111,6 +111,13 @@ public: uint32_t createCompositeExtract(uint32_t resultType, uint32_t composite, llvm::ArrayRef indexes); + /// \brief Creates a composite insert instruction. The given object will + /// replace the component in the composite at the given indices. Returns the + /// for the new composite. + uint32_t createCompositeInsert(uint32_t resultType, uint32_t composite, + llvm::ArrayRef indices, + uint32_t object); + /// \brief Creates a vector shuffle instruction of selecting from the two /// vectors using selectors and returns the of the result vector. uint32_t createVectorShuffle(uint32_t resultType, uint32_t vector1, @@ -211,9 +218,9 @@ public: /// If compareVal is given a non-zero value, OpImageDrefGather or /// OpImageSparseDrefGather will be generated; otherwise, OpImageGather or /// OpImageSparseGather will be generated. - /// If residencyCodeId is not zero, the sparse version of the instructions will - /// be used, and the SPIR-V instruction for storing the resulting residency - /// code will also be emitted. + /// If residencyCodeId is not zero, the sparse version of the instructions + /// will be used, and the SPIR-V instruction for storing the resulting + /// residency code will also be emitted. uint32_t createImageGather(uint32_t texelType, uint32_t imageType, uint32_t image, uint32_t sampler, uint32_t coordinate, uint32_t component, diff --git a/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp b/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp index b817a2479..3c60592a4 100644 --- a/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp +++ b/tools/clang/lib/SPIRV/DeclResultIdMapper.cpp @@ -1343,6 +1343,18 @@ bool DeclResultIdMapper::writeBackOutputStream(const ValueDecl *decl, // We should have recorded its stage output variable previously. assert(found != stageVarIds.end()); + // Negate SV_Position.y if requested + if (spirvOptions.invertY && + semanticInfo.semantic->GetKind() == hlsl::Semantic::Kind::Position) { + + const auto f32Type = theBuilder.getFloat32Type(); + const auto v4f32Type = theBuilder.getVecType(f32Type, 4); + const auto oldY = theBuilder.createCompositeExtract(f32Type, value, {1}); + const auto newY = + theBuilder.createUnaryOp(spv::Op::OpFNegate, f32Type, oldY); + value = theBuilder.createCompositeInsert(v4f32Type, value, {1}, newY); + } + theBuilder.createStore(found->second, value); return true; } diff --git a/tools/clang/lib/SPIRV/DeclResultIdMapper.h b/tools/clang/lib/SPIRV/DeclResultIdMapper.h index c351b13e2..979c5729c 100644 --- a/tools/clang/lib/SPIRV/DeclResultIdMapper.h +++ b/tools/clang/lib/SPIRV/DeclResultIdMapper.h @@ -599,7 +599,7 @@ DeclResultIdMapper::DeclResultIdMapper(const hlsl::ShaderModel &model, astContext(context), diags(context.getDiagnostics()), typeTranslator(context, builder, diags, options), entryFunctionId(0), needsLegalization(false), - glPerVertex(model, context, builder, typeTranslator) {} + glPerVertex(model, context, builder, typeTranslator, options.invertY) {} bool DeclResultIdMapper::decorateStageIOLocations() { // Try both input and output even if input location assignment failed diff --git a/tools/clang/lib/SPIRV/GlPerVertex.cpp b/tools/clang/lib/SPIRV/GlPerVertex.cpp index 01de07ed1..7b6cb06ca 100644 --- a/tools/clang/lib/SPIRV/GlPerVertex.cpp +++ b/tools/clang/lib/SPIRV/GlPerVertex.cpp @@ -57,12 +57,14 @@ inline bool hasGSPrimitiveTypeQualifier(const DeclaratorDecl *decl) { } // anonymous namespace GlPerVertex::GlPerVertex(const hlsl::ShaderModel &sm, ASTContext &context, - ModuleBuilder &builder, TypeTranslator &translator) + ModuleBuilder &builder, TypeTranslator &translator, + bool negateY) : shaderModel(sm), astContext(context), theBuilder(builder), - typeTranslator(translator), inIsGrouped(true), outIsGrouped(true), - inBlockVar(0), outBlockVar(0), inClipVar(0), inCullVar(0), outClipVar(0), - outCullVar(0), inArraySize(0), outArraySize(0), inClipArraySize(1), - outClipArraySize(1), inCullArraySize(1), outCullArraySize(1) {} + typeTranslator(translator), invertY(negateY), inIsGrouped(true), + outIsGrouped(true), inBlockVar(0), outBlockVar(0), inClipVar(0), + inCullVar(0), outClipVar(0), outCullVar(0), inArraySize(0), + outArraySize(0), inClipArraySize(1), outClipArraySize(1), + inCullArraySize(1), outCullArraySize(1) {} void GlPerVertex::generateVars(uint32_t inArrayLen, uint32_t outArrayLen) { // Calling this method twice is an internal error. @@ -624,8 +626,7 @@ bool GlPerVertex::readField(hlsl::Semantic::Kind semanticKind, } void GlPerVertex::writePositionOrPointSize( - bool isPosition, llvm::Optional invocationId, - uint32_t value) const { + bool isPosition, llvm::Optional invocationId, uint32_t value) { // We do not handle stand-alone Position/PointSize builtin here. assert(outIsGrouped); @@ -643,6 +644,17 @@ void GlPerVertex::writePositionOrPointSize( // locate the Position/PointSize builtin. const uint32_t ptr = theBuilder.createAccessChain(ptrType, outBlockVar, {fieldIndex}); + + if (isPosition && invertY) { + if (shaderModel.IsVS() || shaderModel.IsDS()) { + const auto oldY = + theBuilder.createCompositeExtract(f32Type, value, {1}); + const auto newY = + theBuilder.createUnaryOp(spv::Op::OpFNegate, f32Type, oldY); + value = theBuilder.createCompositeInsert(fieldType, value, {1}, newY); + } + } + theBuilder.createStore(ptr, value); return; } @@ -661,6 +673,7 @@ void GlPerVertex::writePositionOrPointSize( // and the second one is the struct index. const uint32_t ptr = theBuilder.createAccessChain(ptrType, outBlockVar, {arrayIndex, fieldIndex}); + theBuilder.createStore(ptr, value); } diff --git a/tools/clang/lib/SPIRV/GlPerVertex.h b/tools/clang/lib/SPIRV/GlPerVertex.h index 32a456f52..c7f720d18 100644 --- a/tools/clang/lib/SPIRV/GlPerVertex.h +++ b/tools/clang/lib/SPIRV/GlPerVertex.h @@ -57,7 +57,7 @@ namespace spirv { class GlPerVertex { public: GlPerVertex(const hlsl::ShaderModel &sm, ASTContext &context, - ModuleBuilder &builder, TypeTranslator &translator); + ModuleBuilder &builder, TypeTranslator &translator, bool negateY); /// Records a declaration of SV_ClipDistance/SV_CullDistance so later /// we can caculate the ClipDistance/CullDistance array layout. @@ -140,7 +140,7 @@ private: /// Emits SPIR-V instructions for writing the Position/PointSize builtin. void writePositionOrPointSize(bool isPosition, llvm::Optional invocationId, - uint32_t value) const; + uint32_t value); /// Emits SPIR-V instructions for writing data into the ClipDistance/ /// CullDistance builtin starting from offset. The value to be written is /// fromValue, whose type is fromType. Necessary transformations will be @@ -166,6 +166,10 @@ private: ModuleBuilder &theBuilder; TypeTranslator &typeTranslator; + /// Indicates whether to invert SV_Position.y to accommodate Vulkan's + /// coordinate system + bool invertY; + /// We can have Position, ClipDistance, and CullDistance either grouped (G) /// into the gl_PerVertex struct, or separated (S) as stand-alone variables. /// The following table shows for each shader stage, which one is used: diff --git a/tools/clang/lib/SPIRV/ModuleBuilder.cpp b/tools/clang/lib/SPIRV/ModuleBuilder.cpp index 73dc4d051..a5b9c00fb 100644 --- a/tools/clang/lib/SPIRV/ModuleBuilder.cpp +++ b/tools/clang/lib/SPIRV/ModuleBuilder.cpp @@ -148,6 +148,19 @@ ModuleBuilder::createCompositeExtract(uint32_t resultType, uint32_t composite, return resultId; } +uint32_t ModuleBuilder::createCompositeInsert(uint32_t resultType, + uint32_t composite, + llvm::ArrayRef indices, + uint32_t object) { + assert(insertPoint && "null insert point"); + const uint32_t resultId = theContext.takeNextId(); + instBuilder + .opCompositeInsert(resultType, resultId, object, composite, indices) + .x(); + insertPoint->appendInstruction(std::move(constructSite)); + return resultId; +} + uint32_t ModuleBuilder::createVectorShuffle(uint32_t resultType, uint32_t vector1, uint32_t vector2, @@ -773,14 +786,13 @@ void ModuleBuilder::decorate(uint32_t targetId, spv::Decoration decoration) { } #define IMPL_GET_PRIMITIVE_TYPE(ty) \ - \ -uint32_t ModuleBuilder::get##ty##Type() { \ + \ + uint32_t ModuleBuilder::get##ty##Type() { \ const Type *type = Type::get##ty(theContext); \ const uint32_t typeId = theContext.getResultIdForType(type); \ theModule.addType(type, typeId); \ return typeId; \ - \ -} + } IMPL_GET_PRIMITIVE_TYPE(Void) IMPL_GET_PRIMITIVE_TYPE(Bool) @@ -1049,16 +1061,15 @@ uint32_t ModuleBuilder::getConstantBool(bool value) { } #define IMPL_GET_PRIMITIVE_CONST(builderTy, cppTy) \ - \ -uint32_t ModuleBuilder::getConstant##builderTy(cppTy value) { \ + \ + uint32_t ModuleBuilder::getConstant##builderTy(cppTy value) { \ const uint32_t typeId = get##builderTy##Type(); \ const Constant *constant = \ Constant::get##builderTy(theContext, typeId, value); \ const uint32_t constId = theContext.getResultIdForConstant(constant); \ theModule.addConstant(constant, constId); \ return constId; \ - \ -} + } IMPL_GET_PRIMITIVE_CONST(Int16, int16_t) IMPL_GET_PRIMITIVE_CONST(Int32, int32_t) diff --git a/tools/clang/lib/SPIRV/SPIRVEmitter.cpp b/tools/clang/lib/SPIRV/SPIRVEmitter.cpp index 6fd433996..651f59b74 100644 --- a/tools/clang/lib/SPIRV/SPIRVEmitter.cpp +++ b/tools/clang/lib/SPIRV/SPIRVEmitter.cpp @@ -395,6 +395,9 @@ SPIRVEmitter::SPIRVEmitter(CompilerInstance &ci, seenPushConstantAt(), needsLegalization(false) { if (shaderModel.GetKind() == hlsl::ShaderModel::Kind::Invalid) emitError("unknown shader module: %0", {}) << shaderModel.GetName(); + if (options.invertY && + !(shaderModel.IsVS() || shaderModel.IsDS() || shaderModel.IsGS())) + emitError("-fvk-invert-y can only be used in VS/DS/GS", {}); } void SPIRVEmitter::HandleTranslationUnit(ASTContext &context) { diff --git a/tools/clang/test/CodeGenSPIRV/vk.cloption.invert-y.ds.hlsl b/tools/clang/test/CodeGenSPIRV/vk.cloption.invert-y.ds.hlsl new file mode 100644 index 000000000..33a9fb4b1 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/vk.cloption.invert-y.ds.hlsl @@ -0,0 +1,33 @@ +// Run: %dxc -T ds_6_0 -E main -fvk-invert-y + +// HS PCF output +struct HsPcfOut { + float outTessFactor[4] : SV_TessFactor; + float inTessFactor[2] : SV_InsideTessFactor; +}; + +// Per-vertex input structs +struct DsCpIn { + float4 pos : SV_Position; +}; + +// Per-vertex output structs +struct DsCpOut { + float4 pos : SV_Position; +}; + +[domain("quad")] +DsCpOut main(OutputPatch patch, + HsPcfOut pcfData) { + DsCpOut dsOut; + dsOut = (DsCpOut)0; + return dsOut; +} + +// CHECK: [[call:%\d+]] = OpFunctionCall %DsCpIn %src_main %param_var_patch %param_var_pcfData +// CHECK-NEXT: [[val:%\d+]] = OpCompositeExtract %v4float [[call]] 0 +// CHECK-NEXT: [[ptr:%\d+]] = OpAccessChain %_ptr_Output_v4float %gl_PerVertexOut %uint_0 +// CHECK-NEXT: [[oldY:%\d+]] = OpCompositeExtract %float [[val]] 1 +// CHECK-NEXT: [[newY:%\d+]] = OpFNegate %float [[oldY]] +// CHECK-NEXT: [[pos:%\d+]] = OpCompositeInsert %v4float [[newY]] [[val]] 1 +// CHECK-NEXT: OpStore [[ptr]] [[pos]] \ No newline at end of file diff --git a/tools/clang/test/CodeGenSPIRV/vk.cloption.invert-y.gs.hlsl b/tools/clang/test/CodeGenSPIRV/vk.cloption.invert-y.gs.hlsl new file mode 100644 index 000000000..16e442d22 --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/vk.cloption.invert-y.gs.hlsl @@ -0,0 +1,29 @@ +// Run: %dxc -T gs_6_0 -E main -fvk-invert-y + +// GS per-vertex input +struct GsVIn { + float4 pos : SV_Position; +}; + +// GS per-vertex output +struct GsVOut { + float4 pos : SV_Position; +}; + +[maxvertexcount(2)] +void main(in line GsVIn inData[2], + inout LineStream outData) { + + GsVOut vertex; + vertex = (GsVOut)0; +// CHECK: [[vert:%\d+]] = OpLoad %GsVIn %vertex +// CHECK-NEXT: [[val:%\d+]] = OpCompositeExtract %v4float [[vert]] 0 +// CHECK-NEXT: [[oldY:%\d+]] = OpCompositeExtract %float [[val]] 1 +// CHECK-NEXT: [[newY:%\d+]] = OpFNegate %float [[oldY]] +// CHECK-NEXT: [[pos:%\d+]] = OpCompositeInsert %v4float [[newY]] [[val]] 1 +// CHECK-NEXT: OpStore %gl_Position [[pos]] + outData.Append(vertex); + + outData.RestartStrip(); +} + diff --git a/tools/clang/test/CodeGenSPIRV/vk.cloption.invert-y.vs.hlsl b/tools/clang/test/CodeGenSPIRV/vk.cloption.invert-y.vs.hlsl new file mode 100644 index 000000000..80198a7ad --- /dev/null +++ b/tools/clang/test/CodeGenSPIRV/vk.cloption.invert-y.vs.hlsl @@ -0,0 +1,12 @@ +// Run: %dxc -T vs_6_0 -E main -fvk-invert-y + +float4 main(float4 a : A) : SV_Position { + return a; +} + +// CHECK: [[a:%\d+]] = OpFunctionCall %v4float %src_main %param_var_a +// CHECK-NEXT: [[ptr:%\d+]] = OpAccessChain %_ptr_Output_v4float %gl_PerVertexOut %uint_0 +// CHECK-NEXT: [[oldY:%\d+]] = OpCompositeExtract %float [[a]] 1 +// CHECK-NEXT: [[newY:%\d+]] = OpFNegate %float [[oldY]] +// CHECK-NEXT: [[pos:%\d+]] = OpCompositeInsert %v4float [[newY]] [[a]] 1 +// CHECK-NEXT: OpStore [[ptr]] [[pos]] diff --git a/tools/clang/test/vk.cloption.invert-y.vs.hlsl b/tools/clang/test/vk.cloption.invert-y.vs.hlsl new file mode 100644 index 000000000..e69de29bb diff --git a/tools/clang/tools/dxcompiler/dxcompilerobj.cpp b/tools/clang/tools/dxcompiler/dxcompilerobj.cpp index c5ca130bc..6cb9d9933 100644 --- a/tools/clang/tools/dxcompiler/dxcompilerobj.cpp +++ b/tools/clang/tools/dxcompiler/dxcompilerobj.cpp @@ -467,6 +467,7 @@ public: spirvOpts.codeGenHighLevel = opts.CodeGenHighLevel; spirvOpts.disableValidation = opts.DisableValidation; + spirvOpts.invertY = opts.VkInvertY; spirvOpts.ignoreUnusedResources = opts.VkIgnoreUnusedResources; spirvOpts.defaultRowMajor = opts.DefaultRowMajor; spirvOpts.stageIoOrder = opts.VkStageIoOrder; diff --git a/tools/clang/unittests/SPIRV/CodeGenSPIRVTest.cpp b/tools/clang/unittests/SPIRV/CodeGenSPIRVTest.cpp index 938c94025..a83b4575f 100644 --- a/tools/clang/unittests/SPIRV/CodeGenSPIRVTest.cpp +++ b/tools/clang/unittests/SPIRV/CodeGenSPIRVTest.cpp @@ -1023,6 +1023,16 @@ TEST_F(FileTest, VulkanCLOptionIgnoreUnusedResources) { runFileTest("vk.cloption.ignore-unused-resources.hlsl"); } +TEST_F(FileTest, VulkanCLOptionInvertYVS) { + runFileTest("vk.cloption.invert-y.vs.hlsl"); +} +TEST_F(FileTest, VulkanCLOptionInvertYDS) { + runFileTest("vk.cloption.invert-y.ds.hlsl"); +} +TEST_F(FileTest, VulkanCLOptionInvertYGS) { + runFileTest("vk.cloption.invert-y.gs.hlsl"); +} + // Vulkan specific TEST_F(FileTest, VulkanLocation) { runFileTest("vk.location.hlsl"); } TEST_F(FileTest, VulkanLocationInputExplicitOutputImplicit) {