// // Copyright 2019 The ANGLE Project Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // Wrapper for Khronos glslang compiler. // #include "libANGLE/renderer/glslang_wrapper_utils.h" // glslang has issues with some specific warnings. ANGLE_DISABLE_EXTRA_SEMI_WARNING ANGLE_DISABLE_SHADOWING_WARNING // glslang's version of ShaderLang.h, not to be confused with ANGLE's. #include // Other glslang includes. #include #include ANGLE_REENABLE_SHADOWING_WARNING ANGLE_REENABLE_EXTRA_SEMI_WARNING // SPIR-V headers include for AST transformation. #include // SPIR-V tools include for AST validation. #include #include #include #include "common/FixedVector.h" #include "common/string_utils.h" #include "common/utilities.h" #include "libANGLE/Caps.h" #include "libANGLE/ProgramLinkedResources.h" #define ANGLE_GLSLANG_CHECK(CALLBACK, TEST, ERR) \ do \ { \ if (ANGLE_UNLIKELY(!(TEST))) \ { \ return CALLBACK(ERR); \ } \ \ } while (0) namespace rx { namespace { constexpr char kXfbDeclMarker[] = "@@ XFB-DECL @@"; constexpr char kXfbOutMarker[] = "@@ XFB-OUT @@;"; constexpr char kXfbBuiltInPrefix[] = "xfbANGLE"; template constexpr size_t ConstStrLen(const char (&)[N]) { static_assert(N > 0, "C++ shouldn't allow N to be zero"); // The length of a string defined as a char array is the size of the array minus 1 (the // terminating '\0'). return N - 1; } void GetBuiltInResourcesFromCaps(const gl::Caps &caps, TBuiltInResource *outBuiltInResources) { outBuiltInResources->maxDrawBuffers = caps.maxDrawBuffers; outBuiltInResources->maxAtomicCounterBindings = caps.maxAtomicCounterBufferBindings; outBuiltInResources->maxAtomicCounterBufferSize = caps.maxAtomicCounterBufferSize; outBuiltInResources->maxClipPlanes = caps.maxClipPlanes; outBuiltInResources->maxCombinedAtomicCounterBuffers = caps.maxCombinedAtomicCounterBuffers; outBuiltInResources->maxCombinedAtomicCounters = caps.maxCombinedAtomicCounters; outBuiltInResources->maxCombinedImageUniforms = caps.maxCombinedImageUniforms; outBuiltInResources->maxCombinedTextureImageUnits = caps.maxCombinedTextureImageUnits; outBuiltInResources->maxCombinedShaderOutputResources = caps.maxCombinedShaderOutputResources; outBuiltInResources->maxComputeWorkGroupCountX = caps.maxComputeWorkGroupCount[0]; outBuiltInResources->maxComputeWorkGroupCountY = caps.maxComputeWorkGroupCount[1]; outBuiltInResources->maxComputeWorkGroupCountZ = caps.maxComputeWorkGroupCount[2]; outBuiltInResources->maxComputeWorkGroupSizeX = caps.maxComputeWorkGroupSize[0]; outBuiltInResources->maxComputeWorkGroupSizeY = caps.maxComputeWorkGroupSize[1]; outBuiltInResources->maxComputeWorkGroupSizeZ = caps.maxComputeWorkGroupSize[2]; outBuiltInResources->minProgramTexelOffset = caps.minProgramTexelOffset; outBuiltInResources->maxFragmentUniformVectors = caps.maxFragmentUniformVectors; outBuiltInResources->maxFragmentInputComponents = caps.maxFragmentInputComponents; outBuiltInResources->maxGeometryInputComponents = caps.maxGeometryInputComponents; outBuiltInResources->maxGeometryOutputComponents = caps.maxGeometryOutputComponents; outBuiltInResources->maxGeometryOutputVertices = caps.maxGeometryOutputVertices; outBuiltInResources->maxGeometryTotalOutputComponents = caps.maxGeometryTotalOutputComponents; outBuiltInResources->maxLights = caps.maxLights; outBuiltInResources->maxProgramTexelOffset = caps.maxProgramTexelOffset; outBuiltInResources->maxVaryingComponents = caps.maxVaryingComponents; outBuiltInResources->maxVaryingVectors = caps.maxVaryingVectors; outBuiltInResources->maxVertexAttribs = caps.maxVertexAttributes; outBuiltInResources->maxVertexOutputComponents = caps.maxVertexOutputComponents; outBuiltInResources->maxVertexUniformVectors = caps.maxVertexUniformVectors; outBuiltInResources->maxClipDistances = caps.maxClipDistances; } // Test if there are non-zero indices in the uniform name, returning false in that case. This // happens for multi-dimensional arrays, where a uniform is created for every possible index of the // array (except for the innermost dimension). When assigning decorations (set/binding/etc), only // the indices corresponding to the first element of the array should be specified. This function // is used to skip the other indices. // // If useOldRewriteStructSamplers, there are multiple samplers extracted out of struct arrays // though, so the above only applies to the sampler array defined in the struct. bool UniformNameIsIndexZero(const std::string &name, bool excludeCheckForOwningStructArrays) { size_t lastBracketClose = 0; if (excludeCheckForOwningStructArrays) { size_t lastDot = name.find_last_of('.'); if (lastDot != std::string::npos) { lastBracketClose = lastDot; } } while (true) { size_t openBracket = name.find('[', lastBracketClose); if (openBracket == std::string::npos) { break; } size_t closeBracket = name.find(']', openBracket); // If the index between the brackets is not zero, ignore this uniform. if (name.substr(openBracket + 1, closeBracket - openBracket - 1) != "0") { return false; } lastBracketClose = closeBracket; } return true; } bool MappedSamplerNameNeedsUserDefinedPrefix(const std::string &originalName) { return originalName.find('.') == std::string::npos; } template uint32_t CountExplicitOutputs(OutputIter outputsBegin, OutputIter outputsEnd, ImplicitIter implicitsBegin, ImplicitIter implicitsEnd) { auto reduce = [implicitsBegin, implicitsEnd](uint32_t count, const sh::ShaderVariable &var) { bool isExplicit = std::find(implicitsBegin, implicitsEnd, var.name) == implicitsEnd; return count + isExplicit; }; return std::accumulate(outputsBegin, outputsEnd, 0, reduce); } ShaderInterfaceVariableInfo *AddShaderInterfaceVariable(ShaderInterfaceVariableInfoMap *infoMap, const std::string &varName) { ASSERT(infoMap->find(varName) == infoMap->end()); return &(*infoMap)[varName]; } ShaderInterfaceVariableInfo *GetShaderInterfaceVariable(ShaderInterfaceVariableInfoMap *infoMap, const std::string &varName) { ASSERT(infoMap->find(varName) != infoMap->end()); return &(*infoMap)[varName]; } ShaderInterfaceVariableInfo *AddResourceInfoToAllStages(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; } ShaderInterfaceVariableInfo *AddResourceInfo(ShaderInterfaceVariableInfoMap *infoMap, const std::string &varName, uint32_t descriptorSet, uint32_t binding, const gl::ShaderType shaderType) { gl::ShaderBitSet stages; stages.set(shaderType); ShaderInterfaceVariableInfo *info = AddShaderInterfaceVariable(infoMap, varName); info->descriptorSet = descriptorSet; info->binding = binding; info->activeStages = stages; return info; } // Add location information for an in/out variable. ShaderInterfaceVariableInfo *AddLocationInfo(ShaderInterfaceVariableInfoMap *infoMap, const std::string &varName, uint32_t location, uint32_t component, gl::ShaderType stage) { // 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 == ShaderInterfaceVariableInfo::kInvalid); ASSERT(info->component == ShaderInterfaceVariableInfo::kInvalid); info->location = location; info->component = component; info->activeStages.set(stage); 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; } std::string SubstituteTransformFeedbackMarkers(const std::string &originalSource, const std::string &xfbDecl, const std::string &xfbOut) { const size_t xfbDeclMarkerStart = originalSource.find(kXfbDeclMarker); const size_t xfbDeclMarkerEnd = xfbDeclMarkerStart + ConstStrLen(kXfbDeclMarker); const size_t xfbOutMarkerStart = originalSource.find(kXfbOutMarker, xfbDeclMarkerStart); const size_t xfbOutMarkerEnd = xfbOutMarkerStart + ConstStrLen(kXfbOutMarker); // The shader is the following form: // // ..part1.. // @@ XFB-DECL @@ // ..part2.. // @@ XFB-OUT @@; // ..part3.. // // Construct the string by concatenating these five pieces, replacing the markers with the given // values. std::string result; result.append(&originalSource[0], &originalSource[xfbDeclMarkerStart]); result.append(xfbDecl); result.append(&originalSource[xfbDeclMarkerEnd], &originalSource[xfbOutMarkerStart]); result.append(xfbOut); result.append(&originalSource[xfbOutMarkerEnd], &originalSource[originalSource.size()]); return result; } std::string GenerateTransformFeedbackVaryingOutput(const gl::TransformFeedbackVarying &varying, const gl::UniformTypeInfo &info, size_t strideBytes, size_t offset, const std::string &bufferIndex) { std::ostringstream result; ASSERT(strideBytes % 4 == 0); size_t stride = strideBytes / 4; const size_t arrayIndexStart = varying.arrayIndex == GL_INVALID_INDEX ? 0 : varying.arrayIndex; const size_t arrayIndexEnd = arrayIndexStart + varying.size(); for (size_t arrayIndex = arrayIndexStart; arrayIndex < arrayIndexEnd; ++arrayIndex) { for (int col = 0; col < info.columnCount; ++col) { for (int row = 0; row < info.rowCount; ++row) { result << "xfbOut" << bufferIndex << "[" << sh::vk::kDriverUniformsVarName << ".xfbBufferOffsets[" << bufferIndex << "] + (gl_VertexIndex + gl_InstanceIndex * " << sh::vk::kDriverUniformsVarName << ".xfbVerticesPerDraw) * " << stride << " + " << offset << "] = " << info.glslAsFloat << "(" << varying.mappedName; if (varying.isArray()) { result << "[" << arrayIndex << "]"; } if (info.columnCount > 1) { result << "[" << col << "]"; } if (info.rowCount > 1) { result << "[" << row << "]"; } result << ");\n"; ++offset; } } } return result.str(); } void GenerateTransformFeedbackEmulationOutputs(GlslangSourceOptions &options, const gl::ProgramState &programState, GlslangProgramInterfaceInfo *programInterfaceInfo, std::string *vertexShader, ShaderInterfaceVariableInfoMap *variableInfoMapOut) { const std::vector &varyings = programState.getLinkedTransformFeedbackVaryings(); const std::vector &bufferStrides = programState.getTransformFeedbackStrides(); const bool isInterleaved = programState.getTransformFeedbackBufferMode() == GL_INTERLEAVED_ATTRIBS; const size_t bufferCount = isInterleaved ? 1 : varyings.size(); const std::string xfbSet = Str(programInterfaceInfo->uniformsAndXfbDescriptorSetIndex); std::vector xfbIndices(bufferCount); std::string xfbDecl; for (uint32_t bufferIndex = 0; bufferIndex < bufferCount; ++bufferIndex) { const std::string xfbBinding = Str(programInterfaceInfo->currentUniformBindingIndex); xfbIndices[bufferIndex] = Str(bufferIndex); std::string bufferName = GetXfbBufferName(bufferIndex); xfbDecl += "layout(set = " + xfbSet + ", binding = " + xfbBinding + ") buffer " + bufferName + " { float xfbOut" + Str(bufferIndex) + "[]; };\n"; // Add this entry to the info map, so we can easily assert that every resource has an entry // in this map. AddResourceInfo(variableInfoMapOut, bufferName, programInterfaceInfo->uniformsAndXfbDescriptorSetIndex, programInterfaceInfo->currentUniformBindingIndex, gl::ShaderType::Vertex); ++programInterfaceInfo->currentUniformBindingIndex; } std::string xfbOut = "if (" + std::string(sh::vk::kDriverUniformsVarName) + ".xfbActiveUnpaused != 0)\n{\n"; size_t outputOffset = 0; for (size_t varyingIndex = 0; varyingIndex < varyings.size(); ++varyingIndex) { const size_t bufferIndex = isInterleaved ? 0 : varyingIndex; const gl::TransformFeedbackVarying &varying = varyings[varyingIndex]; // For every varying, output to the respective buffer packed. If interleaved, the output is // always to the same buffer, but at different offsets. const gl::UniformTypeInfo &info = gl::GetUniformTypeInfo(varying.type); xfbOut += GenerateTransformFeedbackVaryingOutput(varying, info, bufferStrides[bufferIndex], outputOffset, xfbIndices[bufferIndex]); if (isInterleaved) { outputOffset += info.columnCount * info.rowCount * varying.size(); } } xfbOut += "}\n"; *vertexShader = SubstituteTransformFeedbackMarkers(*vertexShader, xfbDecl, xfbOut); } 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, const gl::ProgramLinkedResources &resources, std::string *vertexShader, uint32_t *locationsUsedForXfbExtensionOut) { const std::vector &tfVaryings = programState.getLinkedTransformFeedbackVaryings(); std::string xfbDecl; std::string xfbOut; for (uint32_t varyingIndex = 0; varyingIndex < tfVaryings.size(); ++varyingIndex) { const gl::TransformFeedbackVarying &tfVarying = tfVaryings[varyingIndex]; const std::string &tfVaryingName = tfVarying.mappedName; if (tfVarying.isBuiltIn()) { // 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. uint32_t xfbVaryingLocation = resources.varyingPacking.getMaxSemanticIndex() + ++(*locationsUsedForXfbExtensionOut); std::string xfbVaryingName = kXfbBuiltInPrefix + tfVaryingName; // 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"; } } *vertexShader = SubstituteTransformFeedbackMarkers(*vertexShader, xfbDecl, xfbOut); } void AssignAttributeLocations(const gl::ProgramExecutable &programExecutable, gl::ShaderType stage, ShaderInterfaceVariableInfoMap *variableInfoMapOut) { // Assign attribute locations for the vertex shader. for (const sh::ShaderVariable &attribute : programExecutable.getProgramInputs()) { ASSERT(attribute.active); AddLocationInfo(variableInfoMapOut, attribute.mappedName, attribute.location, ShaderInterfaceVariableInfo::kInvalid, stage); } } void AssignOutputLocations(const gl::ProgramExecutable &programExecutable, const gl::ShaderType shaderType, ShaderInterfaceVariableInfoMap *variableInfoMapOut) { // Assign output locations for the fragment shader. ASSERT(shaderType == gl::ShaderType::Fragment); // TODO(syoussefi): Add support for EXT_blend_func_extended. http://anglebug.com/3385 const auto &outputLocations = programExecutable.getOutputLocations(); const auto &outputVariables = programExecutable.getOutputVariables(); const std::array implicitOutputs = {"gl_FragDepth", "gl_SampleMask", "gl_FragStencilRefARB"}; for (const gl::VariableLocation &outputLocation : outputLocations) { if (outputLocation.arrayIndex == 0 && outputLocation.used() && !outputLocation.ignored) { const sh::ShaderVariable &outputVar = outputVariables[outputLocation.index]; uint32_t location = 0; if (outputVar.location != -1) { location = outputVar.location; } else if (std::find(implicitOutputs.begin(), implicitOutputs.end(), outputVar.name) == implicitOutputs.end()) { // If there is only one output, it is allowed not to have a location qualifier, in // which case it defaults to 0. GLSL ES 3.00 spec, section 4.3.8.2. ASSERT(CountExplicitOutputs(outputVariables.begin(), outputVariables.end(), implicitOutputs.begin(), implicitOutputs.end()) == 1); } AddLocationInfo(variableInfoMapOut, outputVar.mappedName, location, ShaderInterfaceVariableInfo::kInvalid, shaderType); } } // 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, shaderType); AddLocationInfo(variableInfoMapOut, "webgl_FragData", 0, 0, shaderType); } void AssignVaryingLocations(const GlslangSourceOptions &options, const gl::ProgramExecutable &programExecutable, const gl::ShaderType shaderType, GlslangProgramInterfaceInfo *programInterfaceInfo, ShaderMapInterfaceVariableInfoMap *variableInfoMapOut) { uint32_t locationsUsedForEmulation = programInterfaceInfo->locationsUsedForXfbExtension; // Substitute layout and qualifier strings for the position varying added for line raster // emulation. if (options.emulateBresenhamLines) { uint32_t lineRasterEmulationPositionLocation = locationsUsedForEmulation++; AddLocationInfo(&(*variableInfoMapOut)[shaderType], sh::vk::kLineRasterEmulationPosition, lineRasterEmulationPositionLocation, ShaderInterfaceVariableInfo::kInvalid, shaderType); } // Assign varying locations. for (const gl::PackedVaryingRegister &varyingReg : programExecutable.getResources().varyingPacking.getRegisterList()) { if (!IsFirstRegisterOfVarying(varyingReg)) { continue; } const gl::PackedVarying &varying = *varyingReg.packedVarying; 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)); component = varyingReg.registerColumn; } // In the following: // // struct S { vec4 field; }; // out S varStruct; // // "_uvarStruct" is found through |parentStructMappedName|, with |varying->mappedName| // being "_ufield". In such a case, use |parentStructMappedName|. if (varying.frontVarying.varying && (varying.frontVarying.stage == shaderType)) { const std::string &name = varying.isStructField() ? varying.frontVarying.parentStructMappedName : varying.frontVarying.varying->mappedName; AddLocationInfo(&(*variableInfoMapOut)[varying.frontVarying.stage], name, location, component, varying.frontVarying.stage); } if (varying.backVarying.varying && (varying.backVarying.stage == shaderType)) { const std::string &name = varying.isStructField() ? varying.backVarying.parentStructMappedName : varying.backVarying.varying->mappedName; AddLocationInfo(&(*variableInfoMapOut)[varying.backVarying.stage], name, location, component, varying.backVarying.stage); } } // Add an entry for inactive varyings. const gl::ShaderMap> &inactiveVaryingMappedNames = programExecutable.getResources().varyingPacking.getInactiveVaryingMappedNames(); for (const std::string &varyingName : inactiveVaryingMappedNames[shaderType]) { bool isBuiltin = angle::BeginsWith(varyingName, "gl_"); if (isBuiltin) { continue; } // If name is already in the map, it will automatically have marked all other stages // inactive. if ((*variableInfoMapOut)[shaderType].find(varyingName) != (*variableInfoMapOut)[shaderType].end()) { continue; } // Otherwise, add an entry for it with all locations inactive. ShaderInterfaceVariableInfo *info = &(*variableInfoMapOut)[shaderType][varyingName]; ASSERT(info->location == ShaderInterfaceVariableInfo::kInvalid); } } // Calculates XFB layout qualifier arguments for each tranform feedback varying. Stores calculated // values for the SPIR-V transformation. void AssignTransformFeedbackExtensionQualifiers(const gl::ProgramExecutable &programExecutable, uint32_t locationsUsedForXfbExtension, const gl::ShaderType shaderType, ShaderInterfaceVariableInfoMap *variableInfoMapOut) { const std::vector &tfVaryings = programExecutable.getLinkedTransformFeedbackVaryings(); const std::vector &varyingStrides = programExecutable.getTransformFeedbackStrides(); const bool isInterleaved = programExecutable.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); AddLocationInfo(variableInfoMapOut, xfbVaryingName, xfbVaryingLocation, ShaderInterfaceVariableInfo::kInvalid, shaderType); 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 : programExecutable.getResources().varyingPacking.getRegisterList()) { if (!IsFirstRegisterOfVarying(varyingReg)) { continue; } const gl::PackedVarying *varying = varyingReg.packedVarying; if (varying->frontVarying.varying->name == tfVarying.name) { originalVarying = varying; break; } } if (originalVarying) { const std::string &mappedName = originalVarying->isStructField() ? originalVarying->frontVarying.parentStructMappedName : originalVarying->frontVarying.varying->mappedName; // Set xfb info for this varying. AssignVaryingLocations should have already added // location information for these varyings. SetXfbInfo(variableInfoMapOut, mappedName, bufferIndex, currentOffset, currentStride); } } } } void AssignUniformBindings(GlslangSourceOptions &options, const gl::ProgramExecutable &programExecutable, const gl::ShaderType shaderType, GlslangProgramInterfaceInfo *programInterfaceInfo, ShaderMapInterfaceVariableInfoMap *variableInfoMapOut) { if (programExecutable.hasLinkedShaderStage(shaderType)) { AddResourceInfo(&(*variableInfoMapOut)[shaderType], kDefaultUniformNames[shaderType], programInterfaceInfo->uniformsAndXfbDescriptorSetIndex, programInterfaceInfo->currentUniformBindingIndex, shaderType); ++programInterfaceInfo->currentUniformBindingIndex; // Assign binding to the driver uniforms block AddResourceInfoToAllStages(&(*variableInfoMapOut)[shaderType], sh::vk::kDriverUniformsBlockName, programInterfaceInfo->driverUniformsDescriptorSetIndex, 0); } } // TODO: http://anglebug.com/4512: Need to combine descriptor set bindings across // shader stages. void AssignInterfaceBlockBindings(GlslangSourceOptions &options, const gl::ProgramExecutable &programExecutable, const std::vector &blocks, const gl::ShaderType shaderType, GlslangProgramInterfaceInfo *programInterfaceInfo, ShaderMapInterfaceVariableInfoMap *variableInfoMapOut) { for (const gl::InterfaceBlock &block : blocks) { if (!block.isArray || block.arrayElement == 0) { // TODO: http://anglebug.com/4523: All blocks should be active if (programExecutable.hasLinkedShaderStage(shaderType) && block.isActive(shaderType)) { AddResourceInfo(&(*variableInfoMapOut)[shaderType], block.mappedName, programInterfaceInfo->shaderResourceDescriptorSetIndex, programInterfaceInfo->currentShaderResourceBindingIndex, shaderType); ++programInterfaceInfo->currentShaderResourceBindingIndex; } } } } // TODO: http://anglebug.com/4512: Need to combine descriptor set bindings across // shader stages. void AssignAtomicCounterBufferBindings(GlslangSourceOptions &options, const gl::ProgramExecutable &programExecutable, const std::vector &buffers, const gl::ShaderType shaderType, GlslangProgramInterfaceInfo *programInterfaceInfo, ShaderMapInterfaceVariableInfoMap *variableInfoMapOut) { if (buffers.size() == 0) { return; } if (programExecutable.hasLinkedShaderStage(shaderType)) { AddResourceInfo(&(*variableInfoMapOut)[shaderType], sh::vk::kAtomicCountersBlockName, programInterfaceInfo->shaderResourceDescriptorSetIndex, programInterfaceInfo->currentShaderResourceBindingIndex, shaderType); ++programInterfaceInfo->currentShaderResourceBindingIndex; } } // TODO: http://anglebug.com/4512: Need to combine descriptor set bindings across // shader stages. void AssignImageBindings(GlslangSourceOptions &options, const gl::ProgramExecutable &programExecutable, const std::vector &uniforms, const gl::RangeUI &imageUniformRange, const gl::ShaderType shaderType, GlslangProgramInterfaceInfo *programInterfaceInfo, ShaderMapInterfaceVariableInfoMap *variableInfoMapOut) { for (unsigned int uniformIndex : imageUniformRange) { const gl::LinkedUniform &imageUniform = uniforms[uniformIndex]; std::string name = imageUniform.mappedName; if (GetImageNameWithoutIndices(&name)) { if (programExecutable.hasLinkedShaderStage(shaderType)) { AddResourceInfo(&(*variableInfoMapOut)[shaderType], name, programInterfaceInfo->shaderResourceDescriptorSetIndex, programInterfaceInfo->currentShaderResourceBindingIndex, shaderType); ++programInterfaceInfo->currentShaderResourceBindingIndex; } } } } void AssignNonTextureBindings(GlslangSourceOptions &options, const gl::ProgramExecutable &programExecutable, const gl::ShaderType shaderType, GlslangProgramInterfaceInfo *programInterfaceInfo, ShaderMapInterfaceVariableInfoMap *variableInfoMapOut) { const std::vector &uniformBlocks = programExecutable.getUniformBlocks(); AssignInterfaceBlockBindings(options, programExecutable, uniformBlocks, shaderType, programInterfaceInfo, variableInfoMapOut); const std::vector &storageBlocks = programExecutable.getShaderStorageBlocks(); AssignInterfaceBlockBindings(options, programExecutable, storageBlocks, shaderType, programInterfaceInfo, variableInfoMapOut); const std::vector &atomicCounterBuffers = programExecutable.getAtomicCounterBuffers(); AssignAtomicCounterBufferBindings(options, programExecutable, atomicCounterBuffers, shaderType, programInterfaceInfo, variableInfoMapOut); const std::vector &uniforms = programExecutable.getUniforms(); const gl::RangeUI &imageUniformRange = programExecutable.getImageUniformRange(); AssignImageBindings(options, programExecutable, uniforms, imageUniformRange, shaderType, programInterfaceInfo, variableInfoMapOut); } // TODO: http://anglebug.com/4512: Need to combine descriptor set bindings across // shader stages. void AssignTextureBindings(GlslangSourceOptions &options, const gl::ProgramExecutable &programExecutable, const gl::ShaderType shaderType, GlslangProgramInterfaceInfo *programInterfaceInfo, ShaderMapInterfaceVariableInfoMap *variableInfoMapOut) { // Assign textures to a descriptor set and binding. const std::vector &uniforms = programExecutable.getUniforms(); for (unsigned int uniformIndex : programExecutable.getSamplerUniformRange()) { const gl::LinkedUniform &samplerUniform = uniforms[uniformIndex]; if (!options.useOldRewriteStructSamplers && gl::SamplerNameContainsNonZeroArrayElement(samplerUniform.name)) { continue; } if (UniformNameIsIndexZero(samplerUniform.name, options.useOldRewriteStructSamplers)) { // Samplers in structs are extracted and renamed. const std::string samplerName = options.useOldRewriteStructSamplers ? GetMappedSamplerNameOld(samplerUniform.name) : GlslangGetMappedSamplerName(samplerUniform.name); // TODO: http://anglebug.com/4523: All uniforms should be active if (programExecutable.hasLinkedShaderStage(shaderType) && samplerUniform.isActive(shaderType)) { AddResourceInfo(&(*variableInfoMapOut)[shaderType], samplerName, programInterfaceInfo->textureDescriptorSetIndex, programInterfaceInfo->currentTextureBindingIndex, shaderType); ++programInterfaceInfo->currentTextureBindingIndex; } } } } constexpr gl::ShaderMap kShLanguageMap = { {gl::ShaderType::Vertex, EShLangVertex}, {gl::ShaderType::Geometry, EShLangGeometry}, {gl::ShaderType::Fragment, EShLangFragment}, {gl::ShaderType::Compute, EShLangCompute}, }; angle::Result GetShaderSpirvCode(GlslangErrorCallback callback, const gl::Caps &glCaps, const gl::ShaderMap &shaderSources, gl::ShaderMap> *spirvBlobsOut) { // Enable SPIR-V and Vulkan rules when parsing GLSL EShMessages messages = static_cast(EShMsgSpvRules | EShMsgVulkanRules); TBuiltInResource builtInResources(glslang::DefaultTBuiltInResource); GetBuiltInResourcesFromCaps(glCaps, &builtInResources); glslang::TShader vertexShader(EShLangVertex); glslang::TShader fragmentShader(EShLangFragment); glslang::TShader geometryShader(EShLangGeometry); glslang::TShader computeShader(EShLangCompute); gl::ShaderMap shaders = { {gl::ShaderType::Vertex, &vertexShader}, {gl::ShaderType::Fragment, &fragmentShader}, {gl::ShaderType::Geometry, &geometryShader}, {gl::ShaderType::Compute, &computeShader}, }; glslang::TProgram program; for (const gl::ShaderType shaderType : gl::AllShaderTypes()) { if (shaderSources[shaderType].empty()) { continue; } const char *shaderString = shaderSources[shaderType].c_str(); int shaderLength = static_cast(shaderSources[shaderType].size()); glslang::TShader *shader = shaders[shaderType]; shader->setStringsWithLengths(&shaderString, &shaderLength, 1); shader->setEntryPoint("main"); bool result = shader->parse(&builtInResources, 450, ECoreProfile, false, false, messages); if (!result) { ERR() << "Internal error parsing Vulkan shader corresponding to " << shaderType << ":\n" << shader->getInfoLog() << "\n" << shader->getInfoDebugLog() << "\n"; ANGLE_GLSLANG_CHECK(callback, false, GlslangError::InvalidShader); } program.addShader(shader); } bool linkResult = program.link(messages); if (!linkResult) { ERR() << "Internal error linking Vulkan shaders:\n" << program.getInfoLog() << "\n"; ANGLE_GLSLANG_CHECK(callback, false, GlslangError::InvalidShader); } for (const gl::ShaderType shaderType : gl::AllShaderTypes()) { if (shaderSources[shaderType].empty()) { continue; } glslang::TIntermediate *intermediate = program.getIntermediate(kShLanguageMap[shaderType]); glslang::GlslangToSpv(*intermediate, (*spirvBlobsOut)[shaderType]); } return angle::Result::Continue; } void ValidateSpirvMessage(spv_message_level_t level, const char *source, const spv_position_t &position, const char *message) { WARN() << "Level" << level << ": " << message; } bool ValidateSpirv(const std::vector &spirvBlob) { spvtools::SpirvTools spirvTools(SPV_ENV_VULKAN_1_1); spirvTools.SetMessageConsumer(ValidateSpirvMessage); bool result = spirvTools.Validate(spirvBlob); if (!result) { std::string readableSpirv; spirvTools.Disassemble(spirvBlob, &readableSpirv, SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES); WARN() << "Invalid SPIR-V:\n" << readableSpirv; } return result; } // A SPIR-V transformer. It walks the instructions and modifies them as necessary, for example to // assign bindings or locations. class SpirvTransformer final : angle::NonCopyable { public: SpirvTransformer(const std::vector &spirvBlobIn, bool removeEarlyFragmentTestsOptimization, const ShaderInterfaceVariableInfoMap &variableInfoMap, gl::ShaderType shaderType, SpirvBlob *spirvBlobOut) : mSpirvBlobIn(spirvBlobIn), mShaderType(shaderType), mHasTransformFeedbackOutput(false), mVariableInfoMap(variableInfoMap), mSpirvBlobOut(spirvBlobOut) { gl::ShaderBitSet allStages; allStages.set(); mRemoveEarlyFragmentTestsOptimization = removeEarlyFragmentTestsOptimization; mBuiltinVariableInfo.activeStages = allStages; } bool transform(); private: // SPIR-V 1.0 Table 1: First Words of Physical Layout enum HeaderIndex { kHeaderIndexMagic = 0, kHeaderIndexVersion = 1, kHeaderIndexGenerator = 2, kHeaderIndexIndexBound = 3, kHeaderIndexSchema = 4, kHeaderIndexInstructions = 5, }; // A prepass to resolve interesting ids: void resolveVariableIds(); // Transform instructions: void transformInstruction(); // Instructions that are purely informational: void visitName(const uint32_t *instruction); void visitTypeHelper(const uint32_t *instruction, size_t idIndex, size_t typeIdIndex); void visitTypeArray(const uint32_t *instruction); void visitTypePointer(const uint32_t *instruction); void visitVariable(const uint32_t *instruction); // 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); bool transformExecutionMode(const uint32_t *instruction, size_t wordCount); // Any other instructions: 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; bool mRemoveEarlyFragmentTestsOptimization; // Input shader variable info map: const ShaderInterfaceVariableInfoMap &mVariableInfoMap; ShaderInterfaceVariableInfo mBuiltinVariableInfo; // Transformed SPIR-V: SpirvBlob *mSpirvBlobOut; // Traversal state: size_t mCurrentWord = 0; bool mIsInFunctionSection = false; // Transformation state: // Names associated with ids through OpName. The same name may be assigned to multiple ids, but // not all names are interesting (for example function arguments). When the variable // declaration is met (OpVariable), the variable info is matched with the corresponding id's // name based on the Storage Class. std::vector mNamesById; // 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() { // Glslang succeeded in outputting SPIR-V, so we assume it's valid. ASSERT(mSpirvBlobIn.size() >= kHeaderIndexInstructions); // Since SPIR-V comes from a local call to glslang, it necessarily has the same endianness as // the running architecture, so no byte-swapping is necessary. ASSERT(mSpirvBlobIn[kHeaderIndexMagic] == spv::MagicNumber); // 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()); // First, find all necessary ids and associate them with the information required to transform // their decorations. resolveVariableIds(); // Copy the header to SpirvBlob mSpirvBlobOut->assign(mSpirvBlobIn.begin(), mSpirvBlobIn.begin() + kHeaderIndexInstructions); mCurrentWord = kHeaderIndexInstructions; while (mCurrentWord < mSpirvBlobIn.size()) { transformInstruction(); } return true; } // SPIR-V 1.0 Table 2: Instruction Physical Layout uint32_t GetSpirvInstructionLength(const uint32_t *instruction) { return instruction[0] >> 16; } uint32_t GetSpirvInstructionOp(const uint32_t *instruction) { constexpr uint32_t kOpMask = 0xFFFFu; 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-name map. Used to associate ShaderInterfaceVariableInfo with ids // based on name, but only when it's determined that the name corresponds to a shader interface // variable. mNamesById.resize(indexBound + 1, nullptr); // 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(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; while (currentWord < mSpirvBlobIn.size()) { const uint32_t *instruction = &mSpirvBlobIn[currentWord]; const uint32_t wordCount = GetSpirvInstructionLength(instruction); const uint32_t opCode = GetSpirvInstructionOp(instruction); switch (opCode) { case spv::OpName: visitName(instruction); break; case spv::OpTypeArray: visitTypeArray(instruction); break; case spv::OpTypePointer: visitTypePointer(instruction); break; case spv::OpVariable: visitVariable(instruction); break; case spv::OpFunction: // 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; } currentWord += wordCount; } } void SpirvTransformer::transformInstruction() { const uint32_t *instruction = &mSpirvBlobIn[mCurrentWord]; const uint32_t wordCount = GetSpirvInstructionLength(instruction); const uint32_t opCode = GetSpirvInstructionOp(instruction); // Since glslang succeeded in producing SPIR-V, we assume it to be valid. 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; if (mIsInFunctionSection) { // 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; case spv::OpExecutionMode: transformed = transformExecutionMode(instruction, wordCount); break; default: break; } } // If the instruction was not transformed, copy it to output as is. if (!transformed) { copyInstruction(instruction, wordCount); } // Advance to next instruction. mCurrentWord += wordCount; } void SpirvTransformer::visitName(const uint32_t *instruction) { // We currently don't have any big-endian devices in the list of supported platforms. Literal // strings in SPIR-V are stored little-endian (SPIR-V 1.0 Section 2.2.1, Literal String), so if // a big-endian device is to be supported, the string matching here should be specialized. ASSERT(IsLittleEndian()); // SPIR-V 1.0 Section 3.32 Instructions, OpName constexpr size_t kIdIndex = 1; constexpr size_t kNameIndex = 2; const uint32_t id = instruction[kIdIndex]; const char *name = reinterpret_cast(&instruction[kNameIndex]); // The names and ids are unique ASSERT(id < mNamesById.size()); ASSERT(mNamesById[id] == nullptr); mNamesById[id] = name; } void SpirvTransformer::visitTypeHelper(const uint32_t *instruction, const size_t idIndex, const size_t typeIdIndex) { const uint32_t id = instruction[idIndex]; const uint32_t typeId = instruction[typeIdIndex]; // Every type id is declared only once. ASSERT(id < mNamesById.size()); ASSERT(mNamesById[id] == nullptr); // Carry the name forward from the base type. This is only necessary for interface blocks, // as the variable info is associated with the block name instead of the variable name (to // support nameless interface blocks). When the variable declaration is met, either the // type name or the variable name is used to associate with info based on the variable's // storage class. ASSERT(typeId < mNamesById.size()); mNamesById[id] = mNamesById[typeId]; } void SpirvTransformer::visitTypeArray(const uint32_t *instruction) { // SPIR-V 1.0 Section 3.32 Instructions, OpTypeArray constexpr size_t kIdIndex = 1; constexpr size_t kElementTypeIdIndex = 2; visitTypeHelper(instruction, kIdIndex, kElementTypeIdIndex); } void SpirvTransformer::visitTypePointer(const uint32_t *instruction) { // SPIR-V 1.0 Section 3.32 Instructions, OpTypePointer constexpr size_t kIdIndex = 1; constexpr size_t kTypeIdIndex = 3; visitTypeHelper(instruction, kIdIndex, kTypeIdIndex); } void SpirvTransformer::visitVariable(const uint32_t *instruction) { // SPIR-V 1.0 Section 3.32 Instructions, OpVariable constexpr size_t kTypeIdIndex = 1; constexpr size_t kIdIndex = 2; constexpr size_t kStorageClassIndex = 3; // All resources that take set/binding should be transformed. const uint32_t typeId = instruction[kTypeIdIndex]; const uint32_t id = instruction[kIdIndex]; const uint32_t storageClass = instruction[kStorageClassIndex]; ASSERT(typeId < mNamesById.size()); ASSERT(id < mNamesById.size()); // If storage class indicates that this is not a shader interface variable, ignore it. const bool isInterfaceBlockVariable = storageClass == spv::StorageClassUniform || storageClass == spv::StorageClassStorageBuffer; const bool isOpaqueUniform = storageClass == spv::StorageClassUniformConstant; const bool isInOut = storageClass == spv::StorageClassInput || storageClass == spv::StorageClassOutput; if (!isInterfaceBlockVariable && !isOpaqueUniform && !isInOut) { return; } // The ids are unique. ASSERT(id < mVariableInfoById.size()); ASSERT(mVariableInfoById[id] == nullptr); // For interface block variables, the name that's used to associate info is the block name // rather than the variable name. const char *name = mNamesById[isInterfaceBlockVariable ? typeId : id]; ASSERT(name != nullptr); // Handle builtins, which all start with "gl_". Either the variable name could be an indication // of a builtin variable (such as with gl_FragCoord) or the type name (such as with // gl_PerVertex). const bool isNameBuiltin = isInOut && angle::BeginsWith(name, "gl_"); const bool isTypeBuiltin = isInOut && mNamesById[typeId] != nullptr && angle::BeginsWith(mNamesById[typeId], "gl_"); if (isNameBuiltin || isTypeBuiltin) { // 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; } // Every shader interface variable should have an associated data. auto infoIter = mVariableInfoMap.find(name); ASSERT(infoIter != mVariableInfoMap.end()); const ShaderInterfaceVariableInfo *info = &infoIter->second; // Associate the id of this name with its info. 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; } } bool SpirvTransformer::transformDecorate(const uint32_t *instruction, size_t wordCount) { // SPIR-V 1.0 Section 3.32 Instructions, OpDecorate constexpr size_t kIdIndex = 1; constexpr size_t kDecorationIndex = 2; constexpr size_t kDecorationValueIndex = 3; uint32_t id = instruction[kIdIndex]; uint32_t decoration = instruction[kDecorationIndex]; 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; } // If it's an inactive varying, remove the decoration altogether. if (!info->activeStages[mShaderType]) { return true; } uint32_t newDecorationValue = ShaderInterfaceVariableInfo::kInvalid; switch (decoration) { case spv::DecorationLocation: newDecorationValue = info->location; break; case spv::DecorationBinding: newDecorationValue = info->binding; break; case spv::DecorationDescriptorSet: newDecorationValue = info->descriptorSet; break; default: break; } // If the decoration is not something we care about modifying, there's nothing to do. if (newDecorationValue == ShaderInterfaceVariableInfo::kInvalid) { return false; } // Copy the decoration declaration and modify it. 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 != 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; } // 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; } bool SpirvTransformer::transformCapability(const uint32_t *instruction, size_t 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; } bool SpirvTransformer::transformEntryPoint(const uint32_t *instruction, size_t wordCount) { // 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; constexpr size_t kTypeIdIndex = 3; const uint32_t id = instruction[kIdIndex]; const uint32_t storageClass = instruction[kStorageClassIndex]; const uint32_t typeId = instruction[kTypeIdIndex]; // 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 builtins such as gl_PerVertex. if (mNamesById[typeId] != nullptr && angle::BeginsWith(mNamesById[typeId], "gl_")) { 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; } bool SpirvTransformer::transformExecutionMode(const uint32_t *instruction, size_t wordCount) { // SPIR-V 1.0 Section 3.32 Instructions, OpAccessChain, OpInBoundsAccessChain, OpPtrAccessChain, // OpInBoundsPtrAccessChain constexpr size_t kModeIndex = 2; const uint32_t executionMode = instruction[kModeIndex]; if (executionMode == spv::ExecutionModeEarlyFragmentTests && mRemoveEarlyFragmentTestsOptimization) { // skip the copy return true; } return false; } 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 const uint32_t ShaderInterfaceVariableInfo::kInvalid; ShaderInterfaceVariableInfo::ShaderInterfaceVariableInfo() {} void GlslangInitialize() { int result = ShInitialize(); ASSERT(result != 0); } void GlslangRelease() { int result = ShFinalize(); ASSERT(result != 0); } // Strip indices from the name. If there are non-zero indices, return false to indicate that this // image uniform doesn't require set/binding. That is done on index 0. bool GetImageNameWithoutIndices(std::string *name) { if (name->back() != ']') { return true; } if (!UniformNameIsIndexZero(*name, false)) { return false; } // Strip all indices *name = name->substr(0, name->find('[')); return true; } std::string GetMappedSamplerNameOld(const std::string &originalName) { std::string samplerName = gl::ParseResourceName(originalName, nullptr); // Samplers in structs are extracted. std::replace(samplerName.begin(), samplerName.end(), '.', '_'); // Samplers in arrays of structs are also extracted. std::replace(samplerName.begin(), samplerName.end(), '[', '_'); samplerName.erase(std::remove(samplerName.begin(), samplerName.end(), ']'), samplerName.end()); if (MappedSamplerNameNeedsUserDefinedPrefix(originalName)) { samplerName = sh::kUserDefinedNamePrefix + samplerName; } return samplerName; } std::string GlslangGetMappedSamplerName(const std::string &originalName) { std::string samplerName = originalName; // Samplers in structs are extracted. std::replace(samplerName.begin(), samplerName.end(), '.', '_'); // Remove array elements auto out = samplerName.begin(); for (auto in = samplerName.begin(); in != samplerName.end(); in++) { if (*in == '[') { while (*in != ']') { in++; ASSERT(in != samplerName.end()); } } else { *out++ = *in; } } samplerName.erase(out, samplerName.end()); if (MappedSamplerNameNeedsUserDefinedPrefix(originalName)) { samplerName = sh::kUserDefinedNamePrefix + samplerName; } return samplerName; } std::string GetXfbBufferName(const uint32_t bufferIndex) { return "xfbBuffer" + Str(bufferIndex); } void GlslangAssignLocations(GlslangSourceOptions &options, const gl::ProgramExecutable &programExecutable, const gl::ShaderType shaderType, GlslangProgramInterfaceInfo *programInterfaceInfo, ShaderMapInterfaceVariableInfoMap *variableInfoMapOut) { // Assign outputs to the fragment shader, if any. if ((shaderType == gl::ShaderType::Fragment) && programExecutable.hasLinkedShaderStage(gl::ShaderType::Fragment)) { AssignOutputLocations(programExecutable, gl::ShaderType::Fragment, &(*variableInfoMapOut)[gl::ShaderType::Fragment]); } // Assign attributes to the vertex shader, if any. if ((shaderType == gl::ShaderType::Vertex) && programExecutable.hasLinkedShaderStage(gl::ShaderType::Vertex)) { AssignAttributeLocations(programExecutable, gl::ShaderType::Vertex, &(*variableInfoMapOut)[gl::ShaderType::Vertex]); } if (!programExecutable.hasLinkedShaderStage(gl::ShaderType::Compute)) { // Assign varying locations. AssignVaryingLocations(options, programExecutable, shaderType, programInterfaceInfo, variableInfoMapOut); if (!programExecutable.getLinkedTransformFeedbackVaryings().empty() && options.supportsTransformFeedbackExtension && (shaderType == gl::ShaderType::Vertex)) { AssignTransformFeedbackExtensionQualifiers( programExecutable, programInterfaceInfo->locationsUsedForXfbExtension, gl::ShaderType::Vertex, &(*variableInfoMapOut)[gl::ShaderType::Vertex]); } } AssignUniformBindings(options, programExecutable, shaderType, programInterfaceInfo, variableInfoMapOut); AssignTextureBindings(options, programExecutable, shaderType, programInterfaceInfo, variableInfoMapOut); AssignNonTextureBindings(options, programExecutable, shaderType, programInterfaceInfo, variableInfoMapOut); } void GlslangGetShaderSource(GlslangSourceOptions &options, const gl::ProgramState &programState, const gl::ProgramLinkedResources &resources, GlslangProgramInterfaceInfo *programInterfaceInfo, gl::ShaderMap *shaderSourcesOut, ShaderMapInterfaceVariableInfoMap *variableInfoMapOut) { for (const gl::ShaderType shaderType : gl::AllShaderTypes()) { gl::Shader *glShader = programState.getAttachedShader(shaderType); (*shaderSourcesOut)[shaderType] = glShader ? glShader->getTranslatedSource() : ""; } std::string *vertexSource = &(*shaderSourcesOut)[gl::ShaderType::Vertex]; // Write transform feedback output code. if (!vertexSource->empty()) { if (programState.getLinkedTransformFeedbackVaryings().empty()) { *vertexSource = SubstituteTransformFeedbackMarkers(*vertexSource, "", ""); } else { if (options.supportsTransformFeedbackExtension) { GenerateTransformFeedbackExtensionOutputs( programState, resources, vertexSource, &programInterfaceInfo->locationsUsedForXfbExtension); } else if (options.emulateTransformFeedback) { GenerateTransformFeedbackEmulationOutputs( options, programState, programInterfaceInfo, vertexSource, &(*variableInfoMapOut)[gl::ShaderType::Vertex]); } } } for (const gl::ShaderType shaderType : programState.getExecutable().getLinkedShaderStages()) { GlslangAssignLocations(options, programState.getExecutable(), shaderType, programInterfaceInfo, variableInfoMapOut); } } angle::Result GlslangTransformSpirvCode(const GlslangErrorCallback &callback, const gl::ShaderType shaderType, bool removeEarlyFragmentTestsOptimization, const ShaderInterfaceVariableInfoMap &variableInfoMap, const SpirvBlob &initialSpirvBlob, SpirvBlob *spirvBlobOut) { if (initialSpirvBlob.empty()) { return angle::Result::Continue; } // Transform the SPIR-V code by assigning location/set/binding values. SpirvTransformer transformer(initialSpirvBlob, removeEarlyFragmentTestsOptimization, variableInfoMap, shaderType, spirvBlobOut); ANGLE_GLSLANG_CHECK(callback, transformer.transform(), GlslangError::InvalidSpirv); ASSERT(ValidateSpirv(*spirvBlobOut)); return angle::Result::Continue; } angle::Result GlslangGetShaderSpirvCode(const GlslangErrorCallback &callback, const gl::ShaderBitSet &linkedShaderStages, const gl::Caps &glCaps, const gl::ShaderMap &shaderSources, const ShaderMapInterfaceVariableInfoMap &variableInfoMap, gl::ShaderMap *spirvBlobsOut) { gl::ShaderMap initialSpirvBlobs; ANGLE_TRY(GetShaderSpirvCode(callback, glCaps, shaderSources, &initialSpirvBlobs)); for (const gl::ShaderType shaderType : linkedShaderStages) { // we pass in false here to skip modifications related to early fragment tests // optimizations and line rasterization. These are done in the initProgram time since they // are related to context state. We must keep original untouched spriv blobs here because we // do not have ability to add back in at initProgram time. angle::Result status = GlslangTransformSpirvCode(callback, shaderType, false, variableInfoMap[shaderType], initialSpirvBlobs[shaderType], &(*spirvBlobsOut)[shaderType]); if (status != angle::Result::Continue) { return status; } } return angle::Result::Continue; } } // namespace rx