From f1f082e137fb7a5f3ae23f6dc65d253ac9f88d0f Mon Sep 17 00:00:00 2001 From: Shahbaz Youssefi Date: Fri, 17 Jan 2020 14:53:38 -0500 Subject: [PATCH] Vulkan: Set varying location & xfb decorations in SPIR-V The shader translator outputs arbitrary location indices. Once compiled by glslang, the SPIR-V transformer modifies these decorations. If the transform feedback extension is used, it will also add the relevant decorations to the varyings that are captured. Bug: angleproject:3394 Change-Id: I5ecafd0536408612a5d4b920dbabbfabe650657c Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2008468 Commit-Queue: Shahbaz Youssefi Reviewed-by: Jamie Madill --- include/GLSLANG/ShaderLang.h | 3 + src/compiler/translator/OutputGLSLBase.h | 2 +- src/compiler/translator/OutputVulkanGLSL.cpp | 77 +- src/compiler/translator/OutputVulkanGLSL.h | 1 - src/compiler/translator/ShaderLang.cpp | 2 + src/compiler/translator/TranslatorVulkan.cpp | 9 +- src/libANGLE/VaryingPacking.cpp | 15 +- src/libANGLE/VaryingPacking.h | 8 +- .../renderer/glslang_wrapper_utils.cpp | 987 +++++++++++++----- src/libANGLE/renderer/glslang_wrapper_utils.h | 6 +- .../renderer/vulkan/GlslangWrapperVk.cpp | 1 + src/libANGLE/renderer/vulkan/ProgramVk.cpp | 2 + src/tests/gl_tests/GLSLTest.cpp | 30 + 13 files changed, 778 insertions(+), 365 deletions(-) diff --git a/include/GLSLANG/ShaderLang.h b/include/GLSLANG/ShaderLang.h index 926e3327a..1b5c5817b 100644 --- a/include/GLSLANG/ShaderLang.h +++ b/include/GLSLANG/ShaderLang.h @@ -765,6 +765,9 @@ extern const char kDriverUniformsVarName[]; // Interface block array variable name used for atomic counter emulation extern const char kAtomicCountersVarName[]; +// Line raster emulation varying +extern const char kLineRasterEmulationPosition[]; + } // namespace vk } // namespace sh diff --git a/src/compiler/translator/OutputGLSLBase.h b/src/compiler/translator/OutputGLSLBase.h index 4e718215a..70777b4f9 100644 --- a/src/compiler/translator/OutputGLSLBase.h +++ b/src/compiler/translator/OutputGLSLBase.h @@ -88,7 +88,7 @@ class TOutputGLSLBase : public TIntermTraverser } void declareStruct(const TStructure *structure); - virtual void writeQualifier(TQualifier qualifier, const TType &type, const TSymbol *symbol); + void writeQualifier(TQualifier qualifier, const TType &type, const TSymbol *symbol); bool structDeclared(const TStructure *structure) const; const char *mapQualifierToString(TQualifier qualifier); diff --git a/src/compiler/translator/OutputVulkanGLSL.cpp b/src/compiler/translator/OutputVulkanGLSL.cpp index db8dbea65..279f8124e 100644 --- a/src/compiler/translator/OutputVulkanGLSL.cpp +++ b/src/compiler/translator/OutputVulkanGLSL.cpp @@ -46,15 +46,13 @@ void TOutputVulkanGLSL::writeLayoutQualifier(TIntermTyped *variable) { const TType &type = variable->getType(); - bool needsCustomLayout = IsVarying(type.getQualifier()); bool needsSetBinding = IsSampler(type.getBasicType()) || type.isInterfaceBlock() || IsImage(type.getBasicType()); bool needsLocation = type.getQualifier() == EvqAttribute || type.getQualifier() == EvqVertexIn || - type.getQualifier() == EvqFragmentOut; + type.getQualifier() == EvqFragmentOut || IsVarying(type.getQualifier()); - if (!NeedsToWriteLayoutQualifier(type) && !needsCustomLayout && !needsSetBinding && - !needsLocation) + if (!NeedsToWriteLayoutQualifier(type) && !needsSetBinding && !needsLocation) { return; } @@ -103,33 +101,25 @@ void TOutputVulkanGLSL::writeLayoutQualifier(TIntermTyped *variable) } const char *separator = ""; - if (needsCustomLayout) + out << "layout("; + + // If the resource declaration requires set & binding layout qualifiers, specify arbitrary + // ones. + if (needsSetBinding) { - out << "@@ LAYOUT-" << name << "("; + out << "set=0, binding=" << nextUnusedBinding(); + separator = ", "; } - else + + if (needsLocation) { - out << "layout("; + const unsigned int locationCount = CalculateVaryingLocationCount(symbol, getShaderType()); + uint32_t location = IsShaderIn(type.getQualifier()) + ? nextUnusedInputLocation(locationCount) + : nextUnusedOutputLocation(locationCount); - // If the resource declaration requires set & binding layout qualifiers, specify arbitrary - // ones. - if (needsSetBinding) - { - out << "set=0, binding=" << nextUnusedBinding(); - separator = ", "; - } - - if (needsLocation) - { - const unsigned int locationCount = - CalculateVaryingLocationCount(symbol, getShaderType()); - uint32_t location = IsShaderIn(type.getQualifier()) - ? nextUnusedInputLocation(locationCount) - : nextUnusedOutputLocation(locationCount); - - out << "location=" << location; - separator = ", "; - } + out << "location=" << location; + separator = ", "; } // Output the list of qualifiers already known at this stage, i.e. everything other than @@ -152,39 +142,6 @@ void TOutputVulkanGLSL::writeLayoutQualifier(TIntermTyped *variable) } out << ") "; - if (needsCustomLayout) - { - out << "@@"; - } -} - -void TOutputVulkanGLSL::writeQualifier(TQualifier qualifier, - const TType &type, - const TSymbol *symbol) -{ - // Only varyings need to output qualifiers through a @@ QUALIFIER macro. Glslang wrapper may - // decide to remove them if they are inactive and turn them into global variables. This is only - // necessary for varyings because they are the only shader interface variables that could be - // referenced in the shader source and still be inactive. - if (!sh::IsVarying(qualifier)) - { - TOutputGLSLBase::writeQualifier(qualifier, type, symbol); - return; - } - - if (symbol == nullptr) - { - return; - } - - ImmutableString name = symbol->name(); - - // The in/out qualifiers are calculated here so glslang wrapper doesn't need to guess them. - ASSERT(IsShaderIn(qualifier) || IsShaderOut(qualifier)); - const char *inOutQualifier = mapQualifierToString(qualifier); - - TInfoSinkBase &out = objSink(); - out << "@@ QUALIFIER-" << name.data() << "(" << inOutQualifier << ") @@ "; } void TOutputVulkanGLSL::writeVariableType(const TType &type, diff --git a/src/compiler/translator/OutputVulkanGLSL.h b/src/compiler/translator/OutputVulkanGLSL.h index 49bf402de..6ad6154dc 100644 --- a/src/compiler/translator/OutputVulkanGLSL.h +++ b/src/compiler/translator/OutputVulkanGLSL.h @@ -45,7 +45,6 @@ class TOutputVulkanGLSL : public TOutputGLSL protected: void writeLayoutQualifier(TIntermTyped *variable) override; - void writeQualifier(TQualifier qualifier, const TType &type, const TSymbol *symbol) override; void writeVariableType(const TType &type, const TSymbol *symbol, bool isFunctionArgument) override; diff --git a/src/compiler/translator/ShaderLang.cpp b/src/compiler/translator/ShaderLang.cpp index 15800f83f..ff38626d0 100644 --- a/src/compiler/translator/ShaderLang.cpp +++ b/src/compiler/translator/ShaderLang.cpp @@ -720,6 +720,8 @@ const char kDriverUniformsVarName[] = "ANGLEUniforms"; // Interface block array variable name used for atomic counter emulation const char kAtomicCountersVarName[] = "atomicCounters"; +const char kLineRasterEmulationPosition[] = "ANGLEPosition"; + } // namespace vk } // namespace sh diff --git a/src/compiler/translator/TranslatorVulkan.cpp b/src/compiler/translator/TranslatorVulkan.cpp index a717091cc..d65736f6f 100644 --- a/src/compiler/translator/TranslatorVulkan.cpp +++ b/src/compiler/translator/TranslatorVulkan.cpp @@ -443,10 +443,11 @@ TVariable *AddANGLEPositionVaryingDeclaration(TIntermBlock *root, TSymbolTable *symbolTable, TQualifier qualifier) { - // Define a driver varying vec2 "ANGLEPosition". - TType *varyingType = new TType(EbtFloat, EbpMedium, qualifier, 2); - TVariable *varyingVar = new TVariable(symbolTable, ImmutableString("ANGLEPosition"), - varyingType, SymbolType::AngleInternal); + // Define a vec2 driver varying to hold the line rasterization emulation position. + TType *varyingType = new TType(EbtFloat, EbpMedium, qualifier, 2); + TVariable *varyingVar = + new TVariable(symbolTable, ImmutableString(vk::kLineRasterEmulationPosition), varyingType, + SymbolType::AngleInternal); TIntermSymbol *varyingDeclarator = new TIntermSymbol(varyingVar); TIntermDeclaration *varyingDecl = new TIntermDeclaration; varyingDecl->appendDeclarator(varyingDeclarator); diff --git a/src/libANGLE/VaryingPacking.cpp b/src/libANGLE/VaryingPacking.cpp index 8bee3b923..4bd670638 100644 --- a/src/libANGLE/VaryingPacking.cpp +++ b/src/libANGLE/VaryingPacking.cpp @@ -58,15 +58,17 @@ bool ComparePackedVarying(const PackedVarying &x, const PackedVarying &y) // Implementation of PackedVarying PackedVarying::PackedVarying(const sh::ShaderVariable &varyingIn, sh::InterpolationType interpolationIn) - : PackedVarying(varyingIn, interpolationIn, "", false) + : PackedVarying(varyingIn, interpolationIn, "", "", false) {} PackedVarying::PackedVarying(const sh::ShaderVariable &varyingIn, sh::InterpolationType interpolationIn, const std::string &parentStructNameIn, + const std::string &parentStructMappedNameIn, GLuint fieldIndexIn) : varying(&varyingIn), interpolation(interpolationIn), parentStructName(parentStructNameIn), + parentStructMappedName(parentStructMappedNameIn), arrayIndex(GL_INVALID_INDEX), fieldIndex(fieldIndexIn) {} @@ -84,6 +86,7 @@ PackedVarying &PackedVarying::operator=(PackedVarying &&other) std::swap(shaderStages, other.shaderStages); std::swap(interpolation, other.interpolation); std::swap(parentStructName, other.parentStructName); + std::swap(parentStructMappedName, other.parentStructMappedName); std::swap(arrayIndex, other.arrayIndex); std::swap(fieldIndex, other.fieldIndex); @@ -364,10 +367,11 @@ bool VaryingPacking::collectAndPackUserVaryings(gl::InfoLog &infoLog, ASSERT(!field.isStruct() && !field.isArray()); mPackedVaryings.emplace_back(field, interpolation, varying->name, - fieldIndex); + varying->mappedName, fieldIndex); mPackedVaryings.back().shaderStages = shaderStages; uniqueFullNames.insert(mPackedVaryings.back().fullName()); } + uniqueFullNames.insert(varying->name); } else { @@ -382,7 +386,7 @@ bool VaryingPacking::collectAndPackUserVaryings(gl::InfoLog &infoLog, // If the varying is not used in the input, we know it is inactive. if (!input) { - mInactiveVaryingNames.push_back(ref.first); + mInactiveVaryingMappedNames.push_back(output->mappedName); continue; } @@ -410,11 +414,12 @@ bool VaryingPacking::collectAndPackUserVaryings(gl::InfoLog &infoLog, { ASSERT(!field->isStruct() && !field->isArray()); mPackedVaryings.emplace_back(*field, input->interpolation, input->name, - fieldIndex); + input->mappedName, fieldIndex); mPackedVaryings.back().shaderStages.set(ShaderType::Vertex); mPackedVaryings.back().arrayIndex = GL_INVALID_INDEX; uniqueFullNames.insert(tfVarying); } + uniqueFullNames.insert(input->name); } // Array as a whole and array element conflict has already been checked in // linkValidateTransformFeedback. @@ -439,7 +444,7 @@ bool VaryingPacking::collectAndPackUserVaryings(gl::InfoLog &infoLog, if (uniqueFullNames.count(ref.first) == 0) { - mInactiveVaryingNames.push_back(ref.first); + mInactiveVaryingMappedNames.push_back(input->mappedName); } } diff --git a/src/libANGLE/VaryingPacking.h b/src/libANGLE/VaryingPacking.h index d1a5f67c0..08e6dda25 100644 --- a/src/libANGLE/VaryingPacking.h +++ b/src/libANGLE/VaryingPacking.h @@ -33,6 +33,7 @@ struct PackedVarying : angle::NonCopyable PackedVarying(const sh::ShaderVariable &varyingIn, sh::InterpolationType interpolationIn, const std::string &parentStructNameIn, + const std::string &parentStructMappedNameIn, GLuint fieldIndexIn); PackedVarying(PackedVarying &&other); ~PackedVarying(); @@ -76,6 +77,7 @@ struct PackedVarying : angle::NonCopyable // Struct name std::string parentStructName; + std::string parentStructMappedName; GLuint arrayIndex; @@ -180,9 +182,9 @@ class VaryingPacking final : angle::NonCopyable return static_cast(mRegisterList.size()); } - const std::vector &getInactiveVaryingNames() const + const std::vector &getInactiveVaryingMappedNames() const { - return mInactiveVaryingNames; + return mInactiveVaryingMappedNames; } const std::vector &getInputVaryings() const { return mInputVaryings; } @@ -201,7 +203,7 @@ class VaryingPacking final : angle::NonCopyable std::vector mRegisterList; std::vector mInputVaryings; std::vector mPackedVaryings; - std::vector mInactiveVaryingNames; + std::vector mInactiveVaryingMappedNames; PackMode mPackMode; }; diff --git a/src/libANGLE/renderer/glslang_wrapper_utils.cpp b/src/libANGLE/renderer/glslang_wrapper_utils.cpp index e6b62353a..a096a300d 100644 --- a/src/libANGLE/renderer/glslang_wrapper_utils.cpp +++ b/src/libANGLE/renderer/glslang_wrapper_utils.cpp @@ -51,16 +51,15 @@ namespace rx { namespace { -constexpr char kMarkerStart[] = "@@ "; -constexpr char kQualifierMarkerBegin[] = "@@ QUALIFIER-"; -constexpr char kLayoutMarkerBegin[] = "@@ LAYOUT-"; -constexpr char kXfbDeclMarkerBegin[] = "@@ XFB-DECL"; -constexpr char kXfbOutMarkerBegin[] = "@@ XFB-OUT"; -constexpr char kMarkerEnd[] = " @@"; -constexpr char kParamsBegin = '('; -constexpr char kParamsEnd = ')'; -constexpr uint32_t kANGLEPositionLocationOffset = 1; -constexpr uint32_t kXfbANGLEPositionLocationOffset = 2; +constexpr char kMarkerStart[] = "@@ "; +constexpr char kQualifierMarkerBegin[] = "@@ QUALIFIER-"; +constexpr char kLayoutMarkerBegin[] = "@@ LAYOUT-"; +constexpr char kXfbDeclMarkerBegin[] = "@@ XFB-DECL"; +constexpr char kXfbOutMarkerBegin[] = "@@ XFB-OUT"; +constexpr char kMarkerEnd[] = " @@"; +constexpr char kParamsBegin = '('; +constexpr char kParamsEnd = ')'; +constexpr char kXfbBuiltInPrefix[] = "xfbANGLE"; constexpr gl::ShaderMap kDefaultUniformNames = { {gl::ShaderType::Vertex, sh::vk::kDefaultUniformsNameVS}, @@ -112,14 +111,6 @@ void GetBuiltInResourcesFromCaps(const gl::Caps &caps, TBuiltInResource *outBuil outBuiltInResources->maxVertexUniformVectors = caps.maxVertexUniformVectors; } -// Information used for Xfb layout qualifier -struct XFBBufferInfo -{ - GLuint index; - GLuint offset; - GLuint stride; -}; - struct VaryingNameEquals { VaryingNameEquals(const std::string &name_) : name(name_) {} @@ -128,8 +119,6 @@ struct VaryingNameEquals std::string name; }; -using XfbBufferMap = std::map; - class IntermediateShaderSource final : angle::NonCopyable { public: @@ -506,35 +495,70 @@ ShaderInterfaceVariableInfo *AddShaderInterfaceVariable(ShaderInterfaceVariableI return &(*infoMap)[varName]; } +ShaderInterfaceVariableInfo *GetShaderInterfaceVariable(ShaderInterfaceVariableInfoMap *infoMap, + const std::string &varName) +{ + ASSERT(infoMap->find(varName) != infoMap->end()); + return &(*infoMap)[varName]; +} + ShaderInterfaceVariableInfo *AddResourceInfo(ShaderInterfaceVariableInfoMap *infoMap, const std::string &varName, uint32_t descriptorSet, uint32_t binding) { + gl::ShaderBitSet allStages; + allStages.set(); + ShaderInterfaceVariableInfo *info = AddShaderInterfaceVariable(infoMap, varName); info->descriptorSet = descriptorSet; info->binding = binding; + info->activeStages = allStages; return info; } // Add location information for an in/out variable. ShaderInterfaceVariableInfo *AddLocationInfo(ShaderInterfaceVariableInfoMap *infoMap, const std::string &varName, - gl::ShaderType shaderType, uint32_t location, - uint32_t component) + uint32_t component, + gl::ShaderBitSet activeStages) { // The info map for this name may or may not exist already. This function merges the // location/component information. ShaderInterfaceVariableInfo *info = &(*infoMap)[varName]; - ASSERT(info->descriptorSet == ShaderInterfaceVariableInfo::kInvalid); - ASSERT(info->binding == ShaderInterfaceVariableInfo::kInvalid); - ASSERT(info->location[shaderType] == ShaderInterfaceVariableInfo::kInvalid); - ASSERT(info->component[shaderType] == ShaderInterfaceVariableInfo::kInvalid); + for (const gl::ShaderType shaderType : activeStages) + { + ASSERT(info->descriptorSet == ShaderInterfaceVariableInfo::kInvalid); + ASSERT(info->binding == ShaderInterfaceVariableInfo::kInvalid); + ASSERT(info->location[shaderType] == ShaderInterfaceVariableInfo::kInvalid); + ASSERT(info->component[shaderType] == ShaderInterfaceVariableInfo::kInvalid); - info->location[shaderType] = location; - info->component[shaderType] = component; + info->location[shaderType] = location; + info->component[shaderType] = component; + } + info->activeStages |= activeStages; + + return info; +} + +// Modify an existing out variable and add transform feedback information. +ShaderInterfaceVariableInfo *SetXfbInfo(ShaderInterfaceVariableInfoMap *infoMap, + const std::string &varName, + uint32_t xfbBuffer, + uint32_t xfbOffset, + uint32_t xfbStride) +{ + ShaderInterfaceVariableInfo *info = GetShaderInterfaceVariable(infoMap, varName); + + ASSERT(info->xfbBuffer == ShaderInterfaceVariableInfo::kInvalid); + ASSERT(info->xfbOffset == ShaderInterfaceVariableInfo::kInvalid); + ASSERT(info->xfbStride == ShaderInterfaceVariableInfo::kInvalid); + + info->xfbBuffer = xfbBuffer; + info->xfbOffset = xfbOffset; + info->xfbStride = xfbStride; return info; } @@ -647,152 +671,90 @@ void GenerateTransformFeedbackEmulationOutputs(const GlslangSourceOptions &optio vertexShader->insertTransformFeedbackOutput(std::move(xfbOut)); } -// Calculates XFB layout qualifier arguments for each tranform feedback varying, inserts -// layout quailifier for built-in varyings here and gathers calculated arguments for non built-in -// varyings for later use. +bool IsFirstRegisterOfVarying(const gl::PackedVaryingRegister &varyingReg) +{ + const gl::PackedVarying &varying = *varyingReg.packedVarying; + + // In Vulkan GLSL, struct fields are not allowed to have location assignments. The varying of a + // struct type is thus given a location equal to the one assigned to its first field. + if (varying.isStructField() && varying.fieldIndex > 0) + { + return false; + } + + // Similarly, assign array varying locations to the assigned location of the first element. + if (varyingReg.varyingArrayIndex != 0 || (varying.isArrayElement() && varying.arrayIndex != 0)) + { + return false; + } + + // Similarly, assign matrix varying locations to the assigned location of the first row. + if (varyingReg.varyingRowIndex != 0) + { + return false; + } + + return true; +} + +// Calculates XFB layout qualifier arguments for each tranform feedback varying. Stores calculated +// values for the SPIR-V transformation. void GenerateTransformFeedbackExtensionOutputs(const gl::ProgramState &programState, IntermediateShaderSource *vertexShader, - XfbBufferMap *xfbBufferMap, - const gl::ProgramLinkedResources &resources) + const gl::ProgramLinkedResources &resources, + ShaderInterfaceVariableInfoMap *variableInfoMapOut, + uint32_t *locationsUsedForXfbExtensionOut) { const std::vector &tfVaryings = programState.getLinkedTransformFeedbackVaryings(); - const std::vector &varyingStrides = programState.getTransformFeedbackStrides(); - const bool isInterleaved = - programState.getTransformFeedbackBufferMode() == GL_INTERLEAVED_ATTRIBS; std::string xfbDecl; - bool hasBuiltInVaryings = false; - bool replacePositionVarying = false; - uint32_t currentOffset = 0; - uint32_t currentStride = 0; - uint32_t bufferIndex = 0; - std::string varyingType; - std::string xfbIndices; - std::string xfbOffsets; - std::string xfbStrides; - std::string replacedPositionLayout; + std::string xfbOut; for (uint32_t varyingIndex = 0; varyingIndex < tfVaryings.size(); ++varyingIndex) { - if (isInterleaved) + const gl::TransformFeedbackVarying &tfVarying = tfVaryings[varyingIndex]; + const std::string &tfVaryingName = tfVarying.mappedName; + + if (tfVarying.isBuiltIn()) { - bufferIndex = 0; - if (varyingIndex > 0) - { - const gl::TransformFeedbackVarying &prev = tfVaryings[varyingIndex - 1]; - currentOffset += prev.size() * gl::VariableExternalSize(prev.type); - } - currentStride = varyingStrides[0]; - } - else - { - bufferIndex = varyingIndex; - currentOffset = 0; - currentStride = varyingStrides[varyingIndex]; - } + // For simplicity, create a copy of every builtin that's captured so xfb qualifiers + // could be added to that instead. This allows the SPIR-V transformation to ignore + // OpMemberName and OpMemberDecorate instructions. Note that capturing gl_Position + // already requires such a copy, since the translator modifies this value at the end of + // main. Capturing the rest of the built-ins are niche enough that the inefficiency + // involved in doing this is not a concern. - if (tfVaryings[varyingIndex].isBuiltIn()) - { - xfbIndices = Str(bufferIndex); - xfbOffsets = Str(currentOffset); - xfbStrides = Str(currentStride); - varyingType = gl::GetGLSLTypeString(tfVaryings[varyingIndex].type); + uint32_t xfbVaryingLocation = resources.varyingPacking.getMaxSemanticIndex() + + ++(*locationsUsedForXfbExtensionOut); - if (tfVaryings[varyingIndex].name.compare("gl_Position") == 0) - { - replacePositionVarying = true; + std::string xfbVaryingName = kXfbBuiltInPrefix + tfVaryingName; - std::string xfbReplacedPositionLocation = - Str(resources.varyingPacking.getMaxSemanticIndex() + - kXfbANGLEPositionLocationOffset); - - replacedPositionLayout = "layout(location = " + xfbReplacedPositionLocation + - ", xfb_buffer = " + xfbIndices + - ", xfb_offset = " + xfbOffsets + - ", xfb_stride = " + xfbStrides + ") out " + varyingType + - " xfbANGLEPosition;\n"; - } - else - { - // Since built-in varyings are not in RegisterList, we can add layout qualifier - // here. - if (!hasBuiltInVaryings) - { - hasBuiltInVaryings = true; - - xfbDecl += "out gl_PerVertex\n{\n"; - for (uint32_t index = 0; index < tfVaryings.size(); ++index) - { - // need to add gl_Position to gl_Pervertex because we declared layout for - // replaced xfbANGLEPosition instead - if (tfVaryings[index].name.compare("gl_Position") == 0) - { - xfbDecl += "vec4 gl_Position;\n"; - break; - } - } - } - xfbDecl += "layout(xfb_buffer = " + xfbIndices + ", xfb_offset = " + xfbOffsets + - ", xfb_stride = " + xfbStrides + ") " + varyingType + " " + - tfVaryings[varyingIndex].name + ";\n"; - } - } - else - { - // Layout qualifier for non built-in varying will be written later, so we just save - // Xfb layout qualifier information into the xfbBufferMap. - XFBBufferInfo bufferInfo; - bufferInfo.index = bufferIndex; - bufferInfo.offset = currentOffset; - bufferInfo.stride = currentStride; - xfbBufferMap->insert(make_pair(tfVaryings[varyingIndex].name, bufferInfo)); + // Add declaration and initialization code for the new varying. + std::string varyingType = gl::GetGLSLTypeString(tfVarying.type); + xfbDecl += "layout(location = " + Str(xfbVaryingLocation) + ") out " + varyingType + + " " + xfbVaryingName + ";\n"; + xfbOut += xfbVaryingName + " = " + tfVaryingName + ";\n"; } } - if (hasBuiltInVaryings) - { - // We should add non transform feedback built-in varyings to gl_PerVertex because once - // we declare gl_PerVertex, all built-in varyings used in shaders should be included - // in gl_PerVertex struct. - for (const sh::ShaderVariable &varying : resources.varyingPacking.getInputVaryings()) - { - if (varying.isBuiltIn()) - { - auto iter = std::find_if(tfVaryings.begin(), tfVaryings.end(), - VaryingNameEquals(varying.name)); - if (iter == tfVaryings.end()) - { - xfbDecl += gl::GetGLSLTypeString(varying.type) + " " + varying.name + ";\n"; - } - } - } - xfbDecl += "\n};\n"; - } - - xfbDecl += replacedPositionLayout; - vertexShader->insertTransformFeedbackDeclaration(std::move(xfbDecl)); - - std::string xfbOut; - if (replacePositionVarying) - { - xfbOut += "xfbANGLEPosition = gl_Position;\n"; - } - vertexShader->insertTransformFeedbackOutput(std::move(xfbOut)); } void AssignAttributeLocations(const gl::ProgramState &programState, ShaderInterfaceVariableInfoMap *variableInfoMapOut) { + gl::ShaderBitSet vertexOnly; + vertexOnly.set(gl::ShaderType::Vertex); + // Assign attribute locations for the vertex shader. for (const sh::ShaderVariable &attribute : programState.getProgramInputs()) { ASSERT(attribute.active); - AddLocationInfo(variableInfoMapOut, attribute.mappedName, gl::ShaderType::Vertex, - attribute.location, ShaderInterfaceVariableInfo::kInvalid); + AddLocationInfo(variableInfoMapOut, attribute.mappedName, attribute.location, + ShaderInterfaceVariableInfo::kInvalid, vertexOnly); } } @@ -805,6 +767,10 @@ void AssignOutputLocations(const gl::ProgramState &programState, const auto &outputVariables = programState.getOutputVariables(); const std::array implicitOutputs = {"gl_FragDepth", "gl_SampleMask", "gl_FragStencilRefARB"}; + + gl::ShaderBitSet fragmentOnly; + fragmentOnly.set(gl::ShaderType::Fragment); + for (const gl::VariableLocation &outputLocation : outputLocations) { if (outputLocation.arrayIndex == 0 && outputLocation.used() && !outputLocation.ignored) @@ -825,111 +791,193 @@ void AssignOutputLocations(const gl::ProgramState &programState, implicitOutputs.begin(), implicitOutputs.end()) == 1); } - AddLocationInfo(variableInfoMapOut, outputVar.mappedName, gl::ShaderType::Fragment, - location, ShaderInterfaceVariableInfo::kInvalid); + AddLocationInfo(variableInfoMapOut, outputVar.mappedName, location, + ShaderInterfaceVariableInfo::kInvalid, fragmentOnly); } } + + // When no fragment output is specified by the shader, the translator outputs webgl_FragColor or + // webgl_FragData. Add an entry for these. Even though the translator is already assigning + // location 0 to these entries, adding an entry for them here allows us to ASSERT that every + // shader interface variable is processed during the SPIR-V transformation. This is done when + // iterating the ids provided by OpEntryPoint. + AddLocationInfo(variableInfoMapOut, "webgl_FragColor", 0, 0, fragmentOnly); + AddLocationInfo(variableInfoMapOut, "webgl_FragData", 0, 0, fragmentOnly); } -void AssignVaryingLocations(const gl::ProgramState &programState, +void AssignVaryingLocations(const GlslangSourceOptions &options, + const gl::ProgramState &programState, const gl::ProgramLinkedResources &resources, - gl::ShaderMap *shaderSources, - XfbBufferMap *xfbBufferMap) + uint32_t locationsUsedForXfbExtension, + ShaderInterfaceVariableInfoMap *variableInfoMapOut) { + uint32_t locationsUsedForEmulation = locationsUsedForXfbExtension; + + // Substitute layout and qualifier strings for the position varying added for line raster + // emulation. + if (options.emulateBresenhamLines) + { + uint32_t lineRasterEmulationPositionLocation = locationsUsedForEmulation++; + + gl::ShaderBitSet allActiveStages; + allActiveStages.set(); + + AddLocationInfo(variableInfoMapOut, sh::vk::kLineRasterEmulationPosition, + lineRasterEmulationPositionLocation, ShaderInterfaceVariableInfo::kInvalid, + allActiveStages); + } // Assign varying locations. for (const gl::PackedVaryingRegister &varyingReg : resources.varyingPacking.getRegisterList()) { - const auto &varying = *varyingReg.packedVarying; - - // In Vulkan GLSL, struct fields are not allowed to have location assignments. The varying - // of a struct type is thus given a location equal to the one assigned to its first field. - if (varying.isStructField() && varying.fieldIndex > 0) + if (!IsFirstRegisterOfVarying(varyingReg)) { continue; } - // Similarly, assign array varying locations to the assigned location of the first element. - if (varying.isArrayElement() && varying.arrayIndex != 0) - { - continue; - } + const gl::PackedVarying &varying = *varyingReg.packedVarying; // In the following: // // struct S { vec4 field; }; // out S varStruct; // - // "varStruct" is found through |parentStructName|, with |varying->name| being "field". In - // such a case, use |parentStructName|. + // "_uvarStruct" is found through |parentStructMappedName|, with |varying->mappedName| + // being "_ufield". In such a case, use |parentStructMappedName|. const std::string &name = - varying.isStructField() ? varying.parentStructName : varying.varying->name; + varying.isStructField() ? varying.parentStructMappedName : varying.varying->mappedName; - std::string locationString = "location = " + Str(varyingReg.registerRow); + uint32_t location = varyingReg.registerRow + locationsUsedForEmulation; + uint32_t component = ShaderInterfaceVariableInfo::kInvalid; if (varyingReg.registerColumn > 0) { ASSERT(!varying.varying->isStruct()); ASSERT(!gl::IsMatrixType(varying.varying->type)); - locationString += ", component = " + Str(varyingReg.registerColumn); + component = varyingReg.registerColumn; } - std::string *layoutSpecifier = &locationString; - - std::string xfbSpecifier; - XfbBufferMap::iterator iter; - iter = xfbBufferMap->find(name); - if (iter != xfbBufferMap->end()) - { - XFBBufferInfo item = iter->second; - xfbSpecifier = "xfb_buffer = " + Str(item.index) + - ", xfb_offset = " + Str(item.offset) + - ", xfb_stride = " + Str(item.stride) + ", " + locationString; - layoutSpecifier = &xfbSpecifier; - } - - for (const gl::ShaderType stage : gl::kAllGraphicsShaderTypes) - { - IntermediateShaderSource *shaderSource = &(*shaderSources)[stage]; - if (shaderSource->empty()) - { - ASSERT(!varying.shaderStages[stage]); - continue; - } - - if (!varying.shaderStages[stage]) - { - // If not active in this stage, remove the varying declaration. Imagine the - // following scenario: - // - // - VS: declare out varying used for transform feedback - // - FS: declare corresponding in varying which is not active - // - // Then varying.shaderStages would only contain Vertex, but the varying is not - // present in the list of inactive varyings since it _is_ active in some stages. - // As a result, we remove the varying from any stage that's not active. - // CleanupUnusedEntities will remove the varyings that are inactive in all stages. - shaderSource->eraseLayoutAndQualifierSpecifiers(name, ""); - continue; - } - - shaderSource->insertLayoutSpecifier( - name, stage == gl::ShaderType::Fragment ? locationString : *layoutSpecifier); - shaderSource->insertQualifierSpecifier(name, ""); - } + AddLocationInfo(variableInfoMapOut, name, location, component, varying.shaderStages); } - // Substitute layout and qualifier strings for the position varying. Use the first free - // varying register after the packed varyings. - constexpr char kVaryingName[] = "ANGLEPosition"; - std::stringstream layoutStream; - layoutStream << "location = " - << (resources.varyingPacking.getMaxSemanticIndex() + kANGLEPositionLocationOffset); - const std::string layout = layoutStream.str(); - - for (IntermediateShaderSource &shaderSource : *shaderSources) + // Add an entry for inactive varyings. + for (const std::string &varyingName : resources.varyingPacking.getInactiveVaryingMappedNames()) { - shaderSource.insertLayoutSpecifier(kVaryingName, layout); - shaderSource.insertQualifierSpecifier(kVaryingName, ""); + bool isBuiltin = angle::BeginsWith(varyingName, "gl_"); + if (isBuiltin) + { + continue; + } + + // TODO(syoussefi): inactive varying names should be unique. However, due to mishandling of + // partially captured arrays, a varying name can end up in both the active and inactive + // lists. The test below should be removed once that issue is resolved. + // http://anglebug.com/4140 + if (variableInfoMapOut->find(varyingName) != variableInfoMapOut->end()) + { + continue; + } + + AddLocationInfo(variableInfoMapOut, varyingName, ShaderInterfaceVariableInfo::kInvalid, + ShaderInterfaceVariableInfo::kInvalid, gl::ShaderBitSet()); + } +} + +// Calculates XFB layout qualifier arguments for each tranform feedback varying. Stores calculated +// values for the SPIR-V transformation. +void AssignTransformFeedbackExtensionQualifiers(const gl::ProgramState &programState, + const gl::ProgramLinkedResources &resources, + uint32_t locationsUsedForXfbExtension, + ShaderInterfaceVariableInfoMap *variableInfoMapOut) +{ + const std::vector &tfVaryings = + programState.getLinkedTransformFeedbackVaryings(); + const std::vector &varyingStrides = programState.getTransformFeedbackStrides(); + const bool isInterleaved = + programState.getTransformFeedbackBufferMode() == GL_INTERLEAVED_ATTRIBS; + + std::string xfbDecl; + std::string xfbOut; + uint32_t currentOffset = 0; + uint32_t currentStride = 0; + uint32_t bufferIndex = 0; + uint32_t currentBuiltinLocation = 0; + + for (uint32_t varyingIndex = 0; varyingIndex < tfVaryings.size(); ++varyingIndex) + { + if (isInterleaved) + { + bufferIndex = 0; + if (varyingIndex > 0) + { + const gl::TransformFeedbackVarying &prev = tfVaryings[varyingIndex - 1]; + currentOffset += prev.size() * gl::VariableExternalSize(prev.type); + } + currentStride = varyingStrides[0]; + } + else + { + bufferIndex = varyingIndex; + currentOffset = 0; + currentStride = varyingStrides[varyingIndex]; + } + + const gl::TransformFeedbackVarying &tfVarying = tfVaryings[varyingIndex]; + const std::string &tfVaryingName = tfVarying.mappedName; + + if (tfVarying.isBuiltIn()) + { + uint32_t xfbVaryingLocation = currentBuiltinLocation++; + std::string xfbVaryingName = kXfbBuiltInPrefix + tfVaryingName; + + ASSERT(xfbVaryingLocation < locationsUsedForXfbExtension); + + gl::ShaderBitSet vertexOnly; + vertexOnly.set(gl::ShaderType::Vertex); + + AddLocationInfo(variableInfoMapOut, xfbVaryingName, xfbVaryingLocation, + ShaderInterfaceVariableInfo::kInvalid, vertexOnly); + SetXfbInfo(variableInfoMapOut, xfbVaryingName, bufferIndex, currentOffset, + currentStride); + } + else if (!tfVarying.isArray() || tfVarying.arrayIndex == 0) + { + // Note: capturing individual array elements using the Vulkan transform feedback + // extension is not supported, and it unlikely to be ever supported (on the contrary, it + // may be removed from the GLES spec). http://anglebug.com/4140 + + // Find the varying with this name. If a struct is captured, we would be iterating over + // its fields, and the name of the varying is found through parentStructMappedName. Not + // only that, but also we should only do this for the first field of the struct. + const gl::PackedVarying *originalVarying = nullptr; + for (const gl::PackedVaryingRegister &varyingReg : + resources.varyingPacking.getRegisterList()) + { + if (!IsFirstRegisterOfVarying(varyingReg)) + { + continue; + } + + const gl::PackedVarying *varying = varyingReg.packedVarying; + + if (varying->varying->name == tfVarying.name) + { + originalVarying = varying; + break; + } + } + + if (originalVarying) + { + const std::string &mappedName = originalVarying->isStructField() + ? originalVarying->parentStructMappedName + : originalVarying->varying->mappedName; + + // Set xfb info for this varying. AssignVaryingLocations should have already added + // location information for these varyings. + SetXfbInfo(variableInfoMapOut, mappedName, bufferIndex, currentOffset, + currentStride); + } + } } } @@ -1071,21 +1119,6 @@ void AssignTextureBindings(const GlslangSourceOptions &options, } } -void CleanupUnusedEntities(bool useOldRewriteStructSamplers, - const gl::ProgramState &programState, - const gl::ProgramLinkedResources &resources, - gl::ShaderMap *shaderSources) -{ - // Remove all the markers for unused varyings. - for (const std::string &varyingName : resources.varyingPacking.getInactiveVaryingNames()) - { - for (IntermediateShaderSource &shaderSource : *shaderSources) - { - shaderSource.eraseLayoutAndQualifierSpecifiers(varyingName, ""); - } - } -} - constexpr gl::ShaderMap kShLanguageMap = { {gl::ShaderType::Vertex, EShLangVertex}, {gl::ShaderType::Geometry, EShLangGeometry}, @@ -1201,9 +1234,15 @@ class SpirvTransformer final : angle::NonCopyable SpirvBlob *spirvBlobOut) : mSpirvBlobIn(spirvBlobIn), mShaderType(shaderType), + mHasTransformFeedbackOutput(false), mVariableInfoMap(variableInfoMap), mSpirvBlobOut(spirvBlobOut) - {} + { + gl::ShaderBitSet allStages; + allStages.set(); + + mBuiltinVariableInfo.activeStages = allStages; + } bool transform(); @@ -1234,29 +1273,43 @@ class SpirvTransformer final : angle::NonCopyable // Instructions that potentially need transformation. They return true if the instruction is // transformed. If false is returned, the instruction should be copied as-is. + bool transformAccessChain(const uint32_t *instruction, size_t wordCount); + bool transformCapability(const uint32_t *instruction, size_t wordCount); + bool transformEntryPoint(const uint32_t *instruction, size_t wordCount); bool transformDecorate(const uint32_t *instruction, size_t wordCount); + bool transformTypePointer(const uint32_t *instruction, size_t wordCount); + bool transformVariable(const uint32_t *instruction, size_t wordCount); // Any other instructions: - void copyInstruction(const uint32_t *instruction, size_t wordCount); - size_t getCurrentOutputOffset() const; + size_t copyInstruction(const uint32_t *instruction, size_t wordCount); + uint32_t getNewId(); // SPIR-V to transform: const std::vector &mSpirvBlobIn; const gl::ShaderType mShaderType; + bool mHasTransformFeedbackOutput; // Input shader variable info map: const ShaderInterfaceVariableInfoMap &mVariableInfoMap; + ShaderInterfaceVariableInfo mBuiltinVariableInfo; // Transformed SPIR-V: SpirvBlob *mSpirvBlobOut; // Traversal state: - size_t mCurrentWord = 0; + size_t mCurrentWord = 0; + bool mIsInFunctionSection = false; // Transformation state: // Shader variable info per id, if id is a shader variable. std::vector mVariableInfoById; + + // Each OpTypePointer instruction that defines a type with the Output storage class is + // duplicated with a similar instruction but which defines a type with the Private storage + // class. If inactive varyings are encountered, its type is changed to the Private one. The + // following vector maps the Output type id to the corresponding Private one. + std::vector mTypePointerTransformedId; }; bool SpirvTransformer::transform() @@ -1269,6 +1322,7 @@ bool SpirvTransformer::transform() // Make sure the transformer is not reused to avoid having to reinitialize it here. ASSERT(mCurrentWord == 0); + ASSERT(mIsInFunctionSection == false); // Make sure the SpirvBlob is not reused. ASSERT(mSpirvBlobOut->empty()); @@ -1301,12 +1355,34 @@ uint32_t GetSpirvInstructionOp(const uint32_t *instruction) return instruction[0] & kOpMask; } +void SetSpirvInstructionLength(uint32_t *instruction, size_t length) +{ + ASSERT(length < 0xFFFFu); + + constexpr uint32_t kLengthMask = 0xFFFF0000u; + instruction[0] &= ~kLengthMask; + instruction[0] |= length << 16; +} + +void SetSpirvInstructionOp(uint32_t *instruction, uint32_t op) +{ + constexpr uint32_t kOpMask = 0xFFFFu; + instruction[0] &= ~kOpMask; + instruction[0] |= op; +} + void SpirvTransformer::resolveVariableIds() { + size_t indexBound = mSpirvBlobIn[kHeaderIndexIndexBound]; + // Allocate storage for id-to-info map. If %i is the id of a name in mVariableInfoMap, index i // in this vector will hold a pointer to the ShaderInterfaceVariableInfo object associated with // that name in mVariableInfoMap. - mVariableInfoById.resize(mSpirvBlobIn[kHeaderIndexIndexBound] + 1, nullptr); + mVariableInfoById.resize(indexBound + 1, nullptr); + + // Allocate storage for Output type pointer map. At index i, this vector holds the identical + // type as %i except for its storage class turned to Private. + mTypePointerTransformedId.resize(indexBound + 1, 0); size_t currentWord = kHeaderIndexInstructions; @@ -1332,10 +1408,11 @@ void SpirvTransformer::resolveVariableIds() visitVariable(instruction); break; case spv::OpFunction: - // SPIR-V is structured in sections. Names appear before decorations, which are - // followed by type+variables and finally functions. We are only interested in name - // and variable declarations (as well as type declarations for the sake of nameless - // interface blocks). Early out when the function declaration section is met. + // SPIR-V is structured in sections (SPIR-V 1.0 Section 2.4 Logical Layout of a + // Module). Names appear before decorations, which are followed by type+variables + // and finally functions. We are only interested in name and variable declarations + // (as well as type declarations for the sake of nameless interface blocks). Early + // out when the function declaration section is met. return; default: break; @@ -1353,25 +1430,56 @@ void SpirvTransformer::transformInstruction() const uint32_t opCode = GetSpirvInstructionOp(instruction); // Since glslang succeeded in producing SPIR-V, we assume it to be valid. - const size_t spirvBlobInSize = mSpirvBlobIn.size(); - ASSERT(mCurrentWord + wordCount <= spirvBlobInSize); + ASSERT(mCurrentWord + wordCount <= mSpirvBlobIn.size()); + + if (opCode == spv::OpFunction) + { + // SPIR-V is structured in sections. Function declarations come last. Only Op*Access* + // opcodes inside functions need to be inspected. + mIsInFunctionSection = true; + } // Only look at interesting instructions. bool transformed = false; - switch (opCode) + + if (mIsInFunctionSection) { - case spv::OpDecorate: - transformed = transformDecorate(instruction, wordCount); - break; - case spv::OpFunction: - // SPIR-V is structured in sections. Function declarations come last. This - // transformation will not modify functions, so we can copy the rest of the spir-v - // as-is. - copyInstruction(instruction, spirvBlobInSize - mCurrentWord); - mCurrentWord = spirvBlobInSize; - return; - default: - break; + // Look at in-function opcodes. + switch (opCode) + { + case spv::OpAccessChain: + case spv::OpInBoundsAccessChain: + case spv::OpPtrAccessChain: + case spv::OpInBoundsPtrAccessChain: + transformed = transformAccessChain(instruction, wordCount); + break; + default: + break; + } + } + else + { + // Look at global declaration opcodes. + switch (opCode) + { + case spv::OpCapability: + transformed = transformCapability(instruction, wordCount); + break; + case spv::OpEntryPoint: + transformed = transformEntryPoint(instruction, wordCount); + break; + case spv::OpDecorate: + transformed = transformDecorate(instruction, wordCount); + break; + case spv::OpTypePointer: + transformed = transformTypePointer(instruction, wordCount); + break; + case spv::OpVariable: + transformed = transformVariable(instruction, wordCount); + break; + default: + break; + } } // If the instruction was not transformed, copy it to output as is. @@ -1398,18 +1506,38 @@ void SpirvTransformer::visitName(const uint32_t *instruction) const uint32_t id = instruction[kIdIndex]; const char *name = reinterpret_cast(&instruction[kNameIndex]); + // The names and ids are unique + ASSERT(id < mVariableInfoById.size()); + ASSERT(mVariableInfoById[id] == nullptr); + + bool isBuiltin = angle::BeginsWith(name, "gl_"); + if (isBuiltin) + { + // Make all builtins point to this no-op info. Adding this entry allows us to ASSERT that + // every shader interface variable is processed during the SPIR-V transformation. This is + // done when iterating the ids provided by OpEntryPoint. + mVariableInfoById[id] = &mBuiltinVariableInfo; + return; + } + auto infoIter = mVariableInfoMap.find(name); if (infoIter == mVariableInfoMap.end()) { return; } - // The names and ids are unique - ASSERT(id < mVariableInfoById.size()); - ASSERT(mVariableInfoById[id] == nullptr); + const ShaderInterfaceVariableInfo *info = &infoIter->second; // Associate the id of this name with its info. - mVariableInfoById[id] = &infoIter->second; + mVariableInfoById[id] = info; + + // Note if the variable is captured by transform feedback. In that case, the TransformFeedback + // capability needs to be added. + if (mShaderType != gl::ShaderType::Fragment && + info->xfbBuffer != ShaderInterfaceVariableInfo::kInvalid && info->activeStages[mShaderType]) + { + mHasTransformFeedbackOutput = true; + } } void SpirvTransformer::visitTypeHelper(const uint32_t *instruction, @@ -1490,6 +1618,12 @@ bool SpirvTransformer::transformDecorate(const uint32_t *instruction, size_t wor return false; } + // If it's an inactive varying, remove the decoration altogether. + if (!info->activeStages[mShaderType]) + { + return true; + } + uint32_t newDecorationValue = ShaderInterfaceVariableInfo::kInvalid; switch (decoration) @@ -1497,24 +1631,12 @@ bool SpirvTransformer::transformDecorate(const uint32_t *instruction, size_t wor case spv::DecorationLocation: newDecorationValue = info->location[mShaderType]; break; - case spv::DecorationComponent: - newDecorationValue = info->component[mShaderType]; - break; case spv::DecorationBinding: newDecorationValue = info->binding; break; case spv::DecorationDescriptorSet: newDecorationValue = info->descriptorSet; break; - case spv::DecorationOffset: - newDecorationValue = info->xfbOffset; - break; - case spv::DecorationXfbBuffer: - newDecorationValue = info->xfbBuffer; - break; - case spv::DecorationXfbStride: - newDecorationValue = info->xfbStride; - break; default: break; } @@ -1526,21 +1648,303 @@ bool SpirvTransformer::transformDecorate(const uint32_t *instruction, size_t wor } // Copy the decoration declaration and modify it. - const size_t currentOutputOffset = getCurrentOutputOffset(); - copyInstruction(instruction, wordCount); - (*mSpirvBlobOut)[currentOutputOffset + kDecorationValueIndex] = newDecorationValue; + const size_t instructionOffset = copyInstruction(instruction, wordCount); + (*mSpirvBlobOut)[instructionOffset + kDecorationValueIndex] = newDecorationValue; + + // If there are decorations to be added, add them right after the Location decoration is + // encountered. + if (decoration != spv::DecorationLocation) + { + return true; + } + + // Add component decoration, if any. + if (info->component[mShaderType] != ShaderInterfaceVariableInfo::kInvalid) + { + // Copy the location decoration declaration and modify it to contain the Component + // decoration. + const size_t instOffset = copyInstruction(instruction, wordCount); + (*mSpirvBlobOut)[instOffset + kDecorationIndex] = spv::DecorationComponent; + (*mSpirvBlobOut)[instOffset + kDecorationValueIndex] = info->component[mShaderType]; + } + + // Add Xfb decorations, if any. + if (mShaderType != gl::ShaderType::Fragment && + info->xfbBuffer != ShaderInterfaceVariableInfo::kInvalid) + { + ASSERT(info->xfbStride != ShaderInterfaceVariableInfo::kInvalid); + ASSERT(info->xfbOffset != ShaderInterfaceVariableInfo::kInvalid); + + constexpr size_t kXfbDecorationCount = 3; + constexpr uint32_t xfbDecorations[kXfbDecorationCount] = { + spv::DecorationXfbBuffer, + spv::DecorationXfbStride, + spv::DecorationOffset, + }; + const uint32_t xfbDecorationValues[kXfbDecorationCount] = { + info->xfbBuffer, + info->xfbStride, + info->xfbOffset, + }; + + // Copy the location decoration declaration three times, and modify them to contain the + // XfbBuffer, XfbStride and Offset decorations. + for (size_t i = 0; i < kXfbDecorationCount; ++i) + { + const size_t xfbInstructionOffset = copyInstruction(instruction, wordCount); + (*mSpirvBlobOut)[xfbInstructionOffset + kDecorationIndex] = xfbDecorations[i]; + (*mSpirvBlobOut)[xfbInstructionOffset + kDecorationValueIndex] = xfbDecorationValues[i]; + } + } return true; } -void SpirvTransformer::copyInstruction(const uint32_t *instruction, size_t wordCount) +bool SpirvTransformer::transformCapability(const uint32_t *instruction, size_t wordCount) { - mSpirvBlobOut->insert(mSpirvBlobOut->end(), instruction, instruction + wordCount); + if (!mHasTransformFeedbackOutput) + { + return false; + } + + // SPIR-V 1.0 Section 3.32 Instructions, OpCapability + constexpr size_t kCapabilityIndex = 1; + + uint32_t capability = instruction[kCapabilityIndex]; + + // Transform feedback capability shouldn't have already been specified. + ASSERT(capability != spv::CapabilityTransformFeedback); + + // Vulkan shaders have either Shader, Geometry or Tessellation capability. We find this + // capability, and add the TransformFeedback capability after it. + if (capability != spv::CapabilityShader && capability != spv::CapabilityGeometry && + capability != spv::CapabilityTessellation) + { + return false; + } + + // Copy the original capability declaration. + copyInstruction(instruction, wordCount); + + // Create the TransformFeedback capability declaration. + + // SPIR-V 1.0 Section 3.32 Instructions, OpCapability + constexpr size_t kCapabilityInstructionLength = 2; + + std::array newCapabilityDeclaration = { + instruction[0], // length+opcode is identical + }; + // Fill the fields. + newCapabilityDeclaration[kCapabilityIndex] = spv::CapabilityTransformFeedback; + + copyInstruction(newCapabilityDeclaration.data(), kCapabilityInstructionLength); + + return true; } -size_t SpirvTransformer::getCurrentOutputOffset() const +bool SpirvTransformer::transformEntryPoint(const uint32_t *instruction, size_t wordCount) { - return mSpirvBlobOut->size(); + // Remove inactive varyings from the shader interface declaration. + + // SPIR-V 1.0 Section 3.32 Instructions, OpEntryPoint + constexpr size_t kNameIndex = 3; + + // Calculate the length of entry point name in words. Note that endianness of the string + // doesn't matter, since we are looking for the '\0' character and rounding up to the word size. + // This calculates (strlen(name)+1+3) / 4, which is equal to strlen(name)/4+1. + const size_t nameLength = + strlen(reinterpret_cast(&instruction[kNameIndex])) / 4 + 1; + const uint32_t instructionLength = GetSpirvInstructionLength(instruction); + const size_t interfaceStart = kNameIndex + nameLength; + const size_t interfaceCount = instructionLength - interfaceStart; + + // Create a copy of the entry point for modification. + std::vector filteredEntryPoint(instruction, instruction + wordCount); + + // Filter out inactive varyings from entry point interface declaration. + size_t writeIndex = interfaceStart; + for (size_t index = 0; index < interfaceCount; ++index) + { + uint32_t id = instruction[interfaceStart + index]; + const ShaderInterfaceVariableInfo *info = mVariableInfoById[id]; + + ASSERT(info); + + if (!info->activeStages[mShaderType]) + { + continue; + } + + filteredEntryPoint[writeIndex] = id; + ++writeIndex; + } + + // Update the length of the instruction. + const size_t newLength = writeIndex; + SetSpirvInstructionLength(filteredEntryPoint.data(), newLength); + + // Copy to output. + copyInstruction(filteredEntryPoint.data(), newLength); + + // Add an OpExecutionMode Xfb instruction if necessary. + if (!mHasTransformFeedbackOutput) + { + return true; + } + + // SPIR-V 1.0 Section 3.32 Instructions, OpEntryPoint + constexpr size_t kEntryPointIdIndex = 2; + + // SPIR-V 1.0 Section 3.32 Instructions, OpExecutionMode + constexpr size_t kExecutionModeInstructionLength = 3; + constexpr size_t kExecutionModeIdIndex = 1; + constexpr size_t kExecutionModeExecutionModeIndex = 2; + + std::array newExecutionModeDeclaration = {}; + + // Fill the fields. + SetSpirvInstructionOp(newExecutionModeDeclaration.data(), spv::OpExecutionMode); + SetSpirvInstructionLength(newExecutionModeDeclaration.data(), kExecutionModeInstructionLength); + newExecutionModeDeclaration[kExecutionModeIdIndex] = instruction[kEntryPointIdIndex]; + newExecutionModeDeclaration[kExecutionModeExecutionModeIndex] = spv::ExecutionModeXfb; + + copyInstruction(newExecutionModeDeclaration.data(), kExecutionModeInstructionLength); + + return true; +} + +bool SpirvTransformer::transformTypePointer(const uint32_t *instruction, size_t wordCount) +{ + // SPIR-V 1.0 Section 3.32 Instructions, OpTypePointer + constexpr size_t kIdIndex = 1; + constexpr size_t kStorageClassIndex = 2; + + const uint32_t id = instruction[kIdIndex]; + const uint32_t storageClass = instruction[kStorageClassIndex]; + + // If the storage class is output, this may be used to create a variable corresponding to an + // inactive varying, or if that varying is a struct, an Op*AccessChain retrieving a field of + // that inactive varying. + // + // Unfortunately, SPIR-V specifies the storage class both on the type and the variable + // declaration. Otherwise it would have been sufficient to modify the OpVariable instruction. + // For simplicty, copy every "OpTypePointer Output" instruction except with the Private storage + // class, in case it may be necessary later. + + if (storageClass != spv::StorageClassOutput) + { + return false; + } + + // Cannot create a Private type declaration from the builtin gl_PerVertex. Note that + // mVariableInfoById is only ever set for variables, except for nameless interface blocks and + // the builtin gl_PerVertex. Since the storage class is Output, if mVariableInfoById for this + // type is not nullptr, this must be a builtin. + const ShaderInterfaceVariableInfo *info = mVariableInfoById[id]; + if (info) + { + ASSERT(info == &mBuiltinVariableInfo); + return false; + } + + // Copy the type declaration for modification. + const size_t instructionOffset = copyInstruction(instruction, wordCount); + + const uint32_t newTypeId = getNewId(); + (*mSpirvBlobOut)[instructionOffset + kIdIndex] = newTypeId; + (*mSpirvBlobOut)[instructionOffset + kStorageClassIndex] = spv::StorageClassPrivate; + + // Remember the id of the replacement. + ASSERT(id < mTypePointerTransformedId.size()); + mTypePointerTransformedId[id] = newTypeId; + + // The original instruction should still be present as well. At this point, we don't know + // whether we will need the Output or Private type. + return false; +} + +bool SpirvTransformer::transformVariable(const uint32_t *instruction, size_t wordCount) +{ + // SPIR-V 1.0 Section 3.32 Instructions, OpVariable + constexpr size_t kTypeIdIndex = 1; + constexpr size_t kIdIndex = 2; + constexpr size_t kStorageClassIndex = 3; + + const uint32_t id = instruction[kIdIndex]; + const uint32_t typeId = instruction[kTypeIdIndex]; + const uint32_t storageClass = instruction[kStorageClassIndex]; + + const ShaderInterfaceVariableInfo *info = mVariableInfoById[id]; + + // If variable is not a shader interface variable that needs modification, there's nothing to + // do. + if (info == nullptr) + { + return false; + } + + // Furthermore, if it's not an inactive varying output, there's nothing to do. Note that + // inactive varying inputs are already pruned by the translator. + ASSERT(storageClass != spv::StorageClassInput || info->activeStages[mShaderType]); + if (info->activeStages[mShaderType]) + { + return false; + } + + ASSERT(storageClass == spv::StorageClassOutput); + + // Copy the variable declaration for modification. Change its type to the corresponding type + // with the Private storage class, as well as changing the storage class respecified in this + // instruction. + const size_t instructionOffset = copyInstruction(instruction, wordCount); + + ASSERT(typeId < mTypePointerTransformedId.size()); + ASSERT(mTypePointerTransformedId[typeId] != 0); + + (*mSpirvBlobOut)[instructionOffset + kTypeIdIndex] = mTypePointerTransformedId[typeId]; + (*mSpirvBlobOut)[instructionOffset + kStorageClassIndex] = spv::StorageClassPrivate; + + return true; +} + +bool SpirvTransformer::transformAccessChain(const uint32_t *instruction, size_t wordCount) +{ + // SPIR-V 1.0 Section 3.32 Instructions, OpAccessChain, OpInBoundsAccessChain, OpPtrAccessChain, + // OpInBoundsPtrAccessChain + constexpr size_t kTypeIdIndex = 1; + constexpr size_t kBaseIdIndex = 3; + + const uint32_t typeId = instruction[kTypeIdIndex]; + const uint32_t baseId = instruction[kBaseIdIndex]; + + // If not accessing an inactive output varying, nothing to do. + const ShaderInterfaceVariableInfo *info = mVariableInfoById[baseId]; + if (info == nullptr || info->activeStages[mShaderType]) + { + return false; + } + + // Copy the instruction for modification. + const size_t instructionOffset = copyInstruction(instruction, wordCount); + + ASSERT(typeId < mTypePointerTransformedId.size()); + ASSERT(mTypePointerTransformedId[typeId] != 0); + + (*mSpirvBlobOut)[instructionOffset + kTypeIdIndex] = mTypePointerTransformedId[typeId]; + + return true; +} + +size_t SpirvTransformer::copyInstruction(const uint32_t *instruction, size_t wordCount) +{ + size_t instructionOffset = mSpirvBlobOut->size(); + mSpirvBlobOut->insert(mSpirvBlobOut->end(), instruction, instruction + wordCount); + return instructionOffset; +} + +uint32_t SpirvTransformer::getNewId() +{ + return (*mSpirvBlobOut)[kHeaderIndexIndexBound]++; } } // anonymous namespace @@ -1608,6 +2012,7 @@ void GlslangGetShaderSource(const GlslangSourceOptions &options, variableInfoMapOut->clear(); gl::ShaderMap intermediateSources; + uint32_t locationsUsedForXfbExtension = 0; for (const gl::ShaderType shaderType : gl::AllShaderTypes()) { @@ -1622,8 +2027,6 @@ void GlslangGetShaderSource(const GlslangSourceOptions &options, IntermediateShaderSource *fragmentSource = &intermediateSources[gl::ShaderType::Fragment]; IntermediateShaderSource *computeSource = &intermediateSources[gl::ShaderType::Compute]; - XfbBufferMap xfbBufferMap; - // Write transform feedback output code. if (!vertexSource->empty()) { @@ -1636,8 +2039,9 @@ void GlslangGetShaderSource(const GlslangSourceOptions &options, { if (options.supportsTransformFeedbackExtension) { - GenerateTransformFeedbackExtensionOutputs(programState, vertexSource, &xfbBufferMap, - resources); + GenerateTransformFeedbackExtensionOutputs(programState, vertexSource, resources, + variableInfoMapOut, + &locationsUsedForXfbExtension); } else if (options.emulateTransformFeedback) { @@ -1662,16 +2066,21 @@ void GlslangGetShaderSource(const GlslangSourceOptions &options, if (computeSource->empty()) { // Assign varying locations. - AssignVaryingLocations(programState, resources, &intermediateSources, &xfbBufferMap); + AssignVaryingLocations(options, programState, resources, locationsUsedForXfbExtension, + variableInfoMapOut); + + if (!programState.getLinkedTransformFeedbackVaryings().empty() && + options.supportsTransformFeedbackExtension) + { + AssignTransformFeedbackExtensionQualifiers( + programState, resources, locationsUsedForXfbExtension, variableInfoMapOut); + } } AssignUniformBindings(options, &intermediateSources, variableInfoMapOut); AssignTextureBindings(options, programState, variableInfoMapOut); AssignNonTextureBindings(options, programState, variableInfoMapOut); - CleanupUnusedEntities(options.useOldRewriteStructSamplers, programState, resources, - &intermediateSources); - for (const gl::ShaderType shaderType : gl::AllShaderTypes()) { (*shaderSourcesOut)[shaderType] = intermediateSources[shaderType].getShaderSource(); diff --git a/src/libANGLE/renderer/glslang_wrapper_utils.h b/src/libANGLE/renderer/glslang_wrapper_utils.h index 445ae1be2..3f817b022 100644 --- a/src/libANGLE/renderer/glslang_wrapper_utils.h +++ b/src/libANGLE/renderer/glslang_wrapper_utils.h @@ -38,6 +38,7 @@ struct GlslangSourceOptions bool useOldRewriteStructSamplers = false; bool supportsTransformFeedbackExtension = false; bool emulateTransformFeedback = false; + bool emulateBresenhamLines = false; }; using SpirvBlob = std::vector; @@ -59,10 +60,11 @@ struct ShaderInterfaceVariableInfo // Used for vertex attributes, fragment shader outputs and varyings. There could be different // variables that share the same name, such as a vertex attribute and a fragment output. They // will share this object since they have the same name, but will find possibly different - // locations in their respective slots. This is also used to indicate in which stages a varying - // is active, as the rest would contain kInvalid. + // locations in their respective slots. gl::ShaderMap location; gl::ShaderMap component; + // The stages this shader interface variable is active. + gl::ShaderBitSet activeStages; // Used for transform feedback extension to decorate vertex shader output. uint32_t xfbBuffer = kInvalid; uint32_t xfbOffset = kInvalid; diff --git a/src/libANGLE/renderer/vulkan/GlslangWrapperVk.cpp b/src/libANGLE/renderer/vulkan/GlslangWrapperVk.cpp index f30c76d18..9bc93dd24 100644 --- a/src/libANGLE/renderer/vulkan/GlslangWrapperVk.cpp +++ b/src/libANGLE/renderer/vulkan/GlslangWrapperVk.cpp @@ -33,6 +33,7 @@ GlslangSourceOptions CreateSourceOptions(const angle::FeaturesVk &features) options.supportsTransformFeedbackExtension = features.supportsTransformFeedbackExtension.enabled; options.emulateTransformFeedback = features.emulateTransformFeedback.enabled; + options.emulateBresenhamLines = features.basicGLLineRasterization.enabled; return options; } } // namespace diff --git a/src/libANGLE/renderer/vulkan/ProgramVk.cpp b/src/libANGLE/renderer/vulkan/ProgramVk.cpp index e1c3d1ddd..139562ea4 100644 --- a/src/libANGLE/renderer/vulkan/ProgramVk.cpp +++ b/src/libANGLE/renderer/vulkan/ProgramVk.cpp @@ -455,6 +455,7 @@ angle::Result ProgramVk::loadSpirvBlob(ContextVk *contextVk, gl::BinaryInputStre info.descriptorSet = stream->readInt(); info.binding = stream->readInt(); + info.activeStages = gl::ShaderBitSet(static_cast(stream->readInt())); info.xfbBuffer = stream->readInt(); info.xfbOffset = stream->readInt(); info.xfbStride = stream->readInt(); @@ -491,6 +492,7 @@ void ProgramVk::saveSpirvBlob(gl::BinaryOutputStream *stream) stream->writeString(nameInfo.first); stream->writeIntOrNegOne(nameInfo.second.descriptorSet); stream->writeIntOrNegOne(nameInfo.second.binding); + stream->writeIntOrNegOne(nameInfo.second.activeStages.bits()); stream->writeIntOrNegOne(nameInfo.second.xfbBuffer); stream->writeIntOrNegOne(nameInfo.second.xfbOffset); stream->writeIntOrNegOne(nameInfo.second.xfbStride); diff --git a/src/tests/gl_tests/GLSLTest.cpp b/src/tests/gl_tests/GLSLTest.cpp index a1b82e3ee..7e263bd8f 100644 --- a/src/tests/gl_tests/GLSLTest.cpp +++ b/src/tests/gl_tests/GLSLTest.cpp @@ -4712,6 +4712,36 @@ TEST_P(GLSLTest_ES3, ComplexVaryingStructsUsedInFragmentShader) EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green); } +// Test that an inactive varying array that doesn't get used in the fragment shader works. +TEST_P(GLSLTest_ES3, InactiveVaryingArrayUnusedInFragmentShader) +{ + constexpr char kVS[] = + "#version 300 es\n" + "in vec4 inputAttribute;\n" + "out vec4 varArray[4];\n" + "void main()\n" + "{\n" + " gl_Position = inputAttribute;\n" + " varArray[0] = vec4(1.0, 0.0, 0.0, 1.0);\n" + " varArray[1] = vec4(0.0, 1.0, 0.0, 1.0);\n" + " varArray[2] = vec4(0.0, 0.0, 1.0, 1.0);\n" + " varArray[3] = vec4(1.0, 1.0, 0.0, 1.0);\n" + "}\n"; + + constexpr char kFS[] = + "#version 300 es\n" + "precision mediump float;\n" + "out vec4 col;\n" + "void main()\n" + "{\n" + " col = vec4(0.0, 0.0, 0.0, 1.0);\n" + "}\n"; + + ANGLE_GL_PROGRAM(program, kVS, kFS); + drawQuad(program.get(), "inputAttribute", 0.5f); + EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black); +} + // Test that an inactive varying struct that doesn't get used in the fragment shader works. TEST_P(GLSLTest_ES3, InactiveVaryingStructUnusedInFragmentShader) {