// // 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. // // mtl_msl_utils.h: Utilities to manipulate MSL. // #import #include #include "common/string_utils.h" #include "common/utilities.h" #include "compiler/translator/Name.h" #include "compiler/translator/msl/TranslatorMSL.h" #include "libANGLE/renderer/metal/ContextMtl.h" #include "libANGLE/renderer/metal/ShaderMtl.h" #include "libANGLE/renderer/metal/mtl_msl_utils.h" namespace rx { namespace { constexpr char kXfbBindingsMarker[] = "@@XFB-Bindings@@"; constexpr char kXfbOutMarker[] = "ANGLE_@@XFB-OUT@@"; constexpr char kUserDefinedNamePrefix[] = "_u"; // Defined in GLSLANG/ShaderLang.h constexpr char kAttribBindingsMarker[] = "@@Attrib-Bindings@@\n"; std::string GetXfbBufferNameMtl(const uint32_t bufferIndex) { return "xfbBuffer" + Str(bufferIndex); } // Name format needs to match sh::Name. struct UserDefinedNameExpr { std::string name; }; std::ostream &operator<<(std::ostream &stream, const UserDefinedNameExpr &expr) { return stream << kUserDefinedNamePrefix << expr.name; } struct UserDefinedNameComponentExpr { UserDefinedNameExpr name; const int component; }; std::ostream &operator<<(std::ostream &stream, const UserDefinedNameComponentExpr &expr) { return stream << expr.name << '[' << expr.component << ']'; } struct InternalNameExpr { std::string name; }; std::ostream &operator<<(std::ostream &stream, const InternalNameExpr &expr) { return stream << sh::kAngleInternalPrefix << '_' << expr.name; } struct InternalNameComponentExpr { InternalNameExpr name; const int component; }; std::ostream &operator<<(std::ostream &stream, const InternalNameComponentExpr &expr) { return stream << expr.name << '_' << expr.component; } // ModifyStructs phase forwarded a single-component user-defined name or created a new AngleInternal // field name to support multi-component fields as multiple single-component fields. std::variant ResolveModifiedAttributeName(const std::string &name, int registerIndex, int registerCount) { if (registerCount < 2) { return UserDefinedNameExpr{name}; } return InternalNameComponentExpr{InternalNameExpr{name}, registerIndex}; } std::variant ResolveModifiedOutputName(const std::string &name, int component, int componentCount) { if (componentCount == 0) { return UserDefinedNameExpr{name}; } return InternalNameComponentExpr{InternalNameExpr{name}, component}; } // Accessing unmodified structs uses user-defined name, business as usual. std::variant ResolveUserDefinedName(const std::string &name, int component, int componentCount) { if (componentCount == 0) { return UserDefinedNameExpr{name}; } return UserDefinedNameComponentExpr{{name}, component}; } template struct ApplyOStream { const T &value; }; template ApplyOStream(T) -> ApplyOStream; template std::ostream &operator<<(std::ostream &stream, ApplyOStream sv) { stream << sv.value; return stream; } template std::ostream &operator<<(std::ostream &stream, ApplyOStream> sv) { std::visit([&stream](auto &&v) { stream << ApplyOStream{v}; }, sv.value); return stream; } } // namespace namespace mtl { void TranslatedShaderInfo::reset() { metalShaderSource = nullptr; metalLibrary = nil; hasUBOArgumentBuffer = false; hasIsnanOrIsinf = false; hasInvariant = false; for (mtl::SamplerBinding &binding : actualSamplerBindings) { binding.textureBinding = mtl::kMaxShaderSamplers; binding.samplerBinding = 0; } for (int &rwTextureBinding : actualImageBindings) { rwTextureBinding = -1; } for (uint32_t &binding : actualUBOBindings) { binding = mtl::kMaxShaderBuffers; } for (uint32_t &binding : actualXFBBindings) { binding = mtl::kMaxShaderBuffers; } } // Original mapping of front end from sampler name to multiple sampler slots (in form of // slot:count pair) using OriginalSamplerBindingMap = std::unordered_map>>; bool MappedSamplerNameNeedsUserDefinedPrefix(const std::string &originalName) { return originalName.find('.') == std::string::npos; } static std::string MSLGetMappedSamplerName(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; } void MSLGetShaderSource(const gl::ProgramState &programState, const gl::ProgramLinkedResources &resources, gl::ShaderMap *shaderSourcesOut) { for (const gl::ShaderType shaderType : gl::AllShaderTypes()) { const gl::SharedCompiledShaderState &glShader = programState.getAttachedShader(shaderType); (*shaderSourcesOut)[shaderType] = glShader ? glShader->translatedSource : ""; } } void GetAssignedSamplerBindings(const sh::TranslatorMetalReflection *reflection, const OriginalSamplerBindingMap &originalBindings, std::unordered_set &structSamplers, std::array *bindings) { for (auto &sampler : reflection->getSamplerBindings()) { const std::string &name = sampler.first; const uint32_t actualSamplerSlot = (uint32_t)reflection->getSamplerBinding(name); const uint32_t actualTextureSlot = (uint32_t)reflection->getTextureBinding(name); // Assign sequential index for subsequent array elements const bool structSampler = structSamplers.find(name) != structSamplers.end(); const std::string mappedName = structSampler ? name : MSLGetMappedSamplerName(sh::kUserDefinedNamePrefix + name); auto original = originalBindings.find(mappedName); if (original != originalBindings.end()) { const std::vector> &resOrignalBindings = originalBindings.at(mappedName); uint32_t currentTextureSlot = actualTextureSlot; uint32_t currentSamplerSlot = actualSamplerSlot; for (const std::pair &originalBindingRange : resOrignalBindings) { SamplerBinding &actualBinding = bindings->at(originalBindingRange.first); actualBinding.textureBinding = currentTextureSlot; actualBinding.samplerBinding = currentSamplerSlot; currentTextureSlot += originalBindingRange.second; currentSamplerSlot += originalBindingRange.second; } } } } std::string UpdateAliasedShaderAttributes(std::string shaderSourceIn, const gl::ProgramExecutable &executable) { // Cache max number of components for each attribute location std::array maxComponents{}; for (auto &attribute : executable.getProgramInputs()) { const int location = attribute.getLocation(); const int registers = gl::VariableRegisterCount(attribute.getType()); const uint8_t components = gl::VariableColumnCount(attribute.getType()); for (int i = 0; i < registers; ++i) { ASSERT(location + i < static_cast(maxComponents.size())); maxComponents[location + i] = std::max(maxComponents[location + i], components); } } // Define aliased names pointing to real attributes with swizzles as needed std::ostringstream stream; for (auto &attribute : executable.getProgramInputs()) { const int location = attribute.getLocation(); const int registers = gl::VariableRegisterCount(attribute.getType()); const uint8_t components = gl::VariableColumnCount(attribute.getType()); for (int i = 0; i < registers; i++) { stream << "#define ANGLE_ALIASED_" << ApplyOStream{ResolveModifiedAttributeName(attribute.name, i, registers)} << " ANGLE_modified.ANGLE_ATTRIBUTE_" << (location + i); if (components != maxComponents[location + i]) { ASSERT(components < maxComponents[location + i]); switch (components) { case 1: stream << ".x"; break; case 2: stream << ".xy"; break; case 3: stream << ".xyz"; break; } } stream << "\n"; } } // Declare actual MSL attributes for (size_t i : executable.getActiveAttribLocationsMask()) { stream << " float"; if (maxComponents[i] > 1) { stream << static_cast(maxComponents[i]); } stream << " ANGLE_ATTRIBUTE_" << i << "[[attribute(" << i << ")]];\n"; } std::string outputSource = shaderSourceIn; size_t markerFound = outputSource.find(kAttribBindingsMarker); ASSERT(markerFound != std::string::npos); outputSource.replace(markerFound, angle::ConstStrLen(kAttribBindingsMarker), stream.str()); return outputSource; } std::string updateShaderAttributes(std::string shaderSourceIn, const gl::ProgramExecutable &executable) { // Build string to attrib map. const auto &programAttributes = executable.getProgramInputs(); std::ostringstream stream; std::unordered_map attributeBindings; for (auto &attribute : programAttributes) { const int registers = gl::VariableRegisterCount(attribute.getType()); for (int i = 0; i < registers; i++) { stream.str(""); stream << ' ' << ApplyOStream{ResolveModifiedAttributeName(attribute.name, i, registers)} << sh::kUnassignedAttributeString; attributeBindings.insert({stream.str(), i + attribute.getLocation()}); } } // Rewrite attributes std::string outputSource = shaderSourceIn; for (auto it = attributeBindings.begin(); it != attributeBindings.end(); ++it) { std::size_t attribFound = outputSource.find(it->first); if (attribFound != std::string::npos) { stream.str(""); stream << "[[attribute(" << it->second << ")]]"; outputSource = outputSource.replace( attribFound + it->first.length() - angle::ConstStrLen(sh::kUnassignedAttributeString), angle::ConstStrLen(sh::kUnassignedAttributeString), stream.str()); } } return outputSource; } std::string UpdateFragmentShaderOutputs(std::string shaderSourceIn, const gl::ProgramExecutable &executable, bool defineAlpha0) { std::ostringstream stream; std::string outputSource = shaderSourceIn; const auto &outputVariables = executable.getOutputVariables(); // For alpha-to-coverage emulation, a reference to the alpha channel // of color output 0 is needed. For ESSL 1.00, it is gl_FragColor or // gl_FragData[0]; for ESSL 3.xx, it is a user-defined output. std::string alphaOutputName; auto assignLocations = [&](const std::vector &locations, bool secondary) { for (auto &outputLocation : locations) { if (!outputLocation.used()) { continue; } const int index = outputLocation.arrayIndex; const gl::ProgramOutput &outputVar = outputVariables[outputLocation.index]; ASSERT(outputVar.pod.location >= 0); const int location = outputVar.pod.location + index; const int arraySize = outputVar.getOutermostArraySize(); stream.str(""); stream << ApplyOStream{ResolveModifiedOutputName(outputVar.name, index, arraySize)} << " [[" << sh::kUnassignedFragmentOutputString; const std::string placeholder(stream.str()); size_t outputFound = outputSource.find(placeholder); if (outputFound != std::string::npos) { stream.str(""); stream << "color(" << location << (secondary ? "), index(1)" : ")"); outputSource = outputSource.replace( outputFound + placeholder.length() - angle::ConstStrLen(sh::kUnassignedFragmentOutputString), angle::ConstStrLen(sh::kUnassignedFragmentOutputString), stream.str()); } if (defineAlpha0 && location == 0 && !secondary && outputVar.pod.type == GL_FLOAT_VEC4) { ASSERT(index == 0); ASSERT(alphaOutputName.empty()); std::ostringstream nameStream; nameStream << "ANGLE_fragmentOut." << ApplyOStream{ResolveUserDefinedName(outputVar.name, index, arraySize)} << ".a"; alphaOutputName = nameStream.str(); } } }; assignLocations(executable.getOutputLocations(), false); assignLocations(executable.getSecondaryOutputLocations(), true); if (defineAlpha0) { // Locations are empty for ESSL 1.00 shaders, try built-in outputs if (alphaOutputName.empty()) { for (auto &v : outputVariables) { if (v.name == "gl_FragColor") { alphaOutputName = "ANGLE_fragmentOut.gl_FragColor.a"; break; } else if (v.name == "gl_FragData") { alphaOutputName = "ANGLE_fragmentOut.ANGLE_gl_FragData_0.a"; break; } } } // Set a value used for alpha-to-coverage emulation const std::string alphaPlaceholder("#define ANGLE_ALPHA0"); size_t alphaFound = outputSource.find(alphaPlaceholder); ASSERT(alphaFound != std::string::npos); std::ostringstream alphaStream; alphaStream << alphaPlaceholder << " "; alphaStream << (alphaOutputName.empty() ? "1.0" : alphaOutputName); outputSource = outputSource.replace(alphaFound, alphaPlaceholder.length(), alphaStream.str()); } return outputSource; } std::string SubstituteTransformFeedbackMarkers(const std::string &originalSource, const std::string &xfbBindings, const std::string &xfbOut) { const size_t xfbBindingsMarkerStart = originalSource.find(kXfbBindingsMarker); bool hasBindingsMarker = xfbBindingsMarkerStart != std::string::npos; const size_t xfbBindingsMarkerEnd = xfbBindingsMarkerStart + angle::ConstStrLen(kXfbBindingsMarker); const size_t xfbOutMarkerStart = originalSource.find(kXfbOutMarker, xfbBindingsMarkerStart); bool hasOutMarker = xfbOutMarkerStart != std::string::npos; const size_t xfbOutMarkerEnd = xfbOutMarkerStart + angle::ConstStrLen(kXfbOutMarker); // The shader is the following form: // // ..part1.. // @@ XFB-BINDINGS @@ // ..part2.. // @@ XFB-OUT @@; // ..part3.. // // Construct the string by concatenating these five pieces, replacing the markers with the given // values. std::string result; if (hasBindingsMarker && hasOutMarker) { result.append(&originalSource[0], &originalSource[xfbBindingsMarkerStart]); result.append(xfbBindings); result.append(&originalSource[xfbBindingsMarkerEnd], &originalSource[xfbOutMarkerStart]); result.append(xfbOut); result.append(&originalSource[xfbOutMarkerEnd], &originalSource[originalSource.size()]); return result; } return originalSource; } 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 << " "; result << "ANGLE_" << "xfbBuffer" << bufferIndex << "[" << "ANGLE_" << std::string(sh::kUniformsVar) << ".ANGLE_xfbBufferOffsets[" << bufferIndex << "] + (ANGLE_vertexIDMetal + (ANGLE_instanceIdMod - ANGLE_baseInstance) * " << "ANGLE_" << std::string(sh::kUniformsVar) << ".ANGLE_xfbVerticesPerInstance) * " << stride << " + " << offset << "] = " << "as_type" << "(" << "ANGLE_vertexOut."; if (!varying.isBuiltIn()) { result << kUserDefinedNamePrefix; } result << varying.name; 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( const gl::ProgramExecutable &executable, std::string *vertexShader, std::array *xfbBindingRemapOut) { const std::vector &varyings = executable.getLinkedTransformFeedbackVaryings(); const std::vector &bufferStrides = executable.getTransformFeedbackStrides(); const bool isInterleaved = executable.getTransformFeedbackBufferMode() == GL_INTERLEAVED_ATTRIBS; const size_t bufferCount = isInterleaved ? 1 : varyings.size(); std::vector xfbIndices(bufferCount); std::string xfbBindings; for (uint32_t bufferIndex = 0; bufferIndex < bufferCount; ++bufferIndex) { const std::string xfbBinding = Str(0); xfbIndices[bufferIndex] = Str(bufferIndex); std::string bufferName = GetXfbBufferNameMtl(bufferIndex); xfbBindings += ", "; // TODO: offset from last used buffer binding from front end // XFB buffer is allocated slot starting from last discrete Metal buffer slot. uint32_t bindingPoint = kMaxShaderBuffers - 1 - bufferIndex; xfbBindingRemapOut->at(bufferIndex) = bindingPoint; xfbBindings += "device float* ANGLE_" + bufferName + " [[buffer(" + Str(bindingPoint) + ")]]"; } std::string xfbOut = "#if TRANSFORM_FEEDBACK_ENABLED\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#endif\n"; *vertexShader = SubstituteTransformFeedbackMarkers(*vertexShader, xfbBindings, xfbOut); } angle::Result MTLGetMSL(const angle::FeaturesMtl &features, const gl::ProgramExecutable &executable, const gl::ShaderMap &shaderSources, const gl::ShaderMap &shadersState, gl::ShaderMap *mslShaderInfoOut) { // Retrieve original uniform buffer bindings generated by front end. We will need to do a remap. std::unordered_map uboOriginalBindings; const std::vector &blocks = executable.getUniformBlocks(); for (uint32_t bufferIdx = 0; bufferIdx < blocks.size(); ++bufferIdx) { const gl::InterfaceBlock &block = blocks[bufferIdx]; if (!uboOriginalBindings.count(block.name)) { uboOriginalBindings[block.name] = bufferIdx; } } // Retrieve original sampler bindings produced by front end. OriginalSamplerBindingMap originalSamplerBindings; const std::vector &samplerBindings = executable.getSamplerBindings(); std::unordered_set structSamplers = {}; for (uint32_t textureIndex = 0; textureIndex < samplerBindings.size(); ++textureIndex) { const gl::SamplerBinding &samplerBinding = samplerBindings[textureIndex]; uint32_t uniformIndex = executable.getUniformIndexFromSamplerIndex(textureIndex); const std::string &uniformName = executable.getUniformNames()[uniformIndex]; const std::string &uniformMappedName = executable.getUniformMappedNames()[uniformIndex]; bool isSamplerInStruct = uniformName.find('.') != std::string::npos; std::string mappedSamplerName = isSamplerInStruct ? MSLGetMappedSamplerName(uniformName) : MSLGetMappedSamplerName(uniformMappedName); // These need to be prefixed later seperately if (isSamplerInStruct) structSamplers.insert(mappedSamplerName); originalSamplerBindings[mappedSamplerName].push_back( {textureIndex, static_cast(samplerBinding.textureUnitsCount)}); } for (gl::ShaderType type : {gl::ShaderType::Vertex, gl::ShaderType::Fragment}) { std::string source; if (type == gl::ShaderType::Vertex) { source = shadersState[gl::ShaderType::Vertex]->translatorMetalReflection.hasAttributeAliasing ? UpdateAliasedShaderAttributes(shaderSources[type], executable) : updateShaderAttributes(shaderSources[type], executable); // Write transform feedback output code. if (!source.empty()) { if (executable.getLinkedTransformFeedbackVaryings().empty()) { source = SubstituteTransformFeedbackMarkers(source, "", ""); } else { GenerateTransformFeedbackEmulationOutputs( executable, &source, &(*mslShaderInfoOut)[type].actualXFBBindings); } } } else { ASSERT(type == gl::ShaderType::Fragment); const bool defineAlpha0 = features.emulateAlphaToCoverage.enabled || features.generateShareableShaders.enabled; source = UpdateFragmentShaderOutputs(shaderSources[type], executable, defineAlpha0); } (*mslShaderInfoOut)[type].metalShaderSource = std::make_shared(std::move(source)); const sh::TranslatorMetalReflection *reflection = &shadersState[type]->translatorMetalReflection; if (reflection->hasUBOs) { (*mslShaderInfoOut)[type].hasUBOArgumentBuffer = true; for (auto &uboBinding : reflection->getUniformBufferBindings()) { const std::string &uboName = uboBinding.first; const sh::UBOBindingInfo &bindInfo = uboBinding.second; const uint32_t uboBindIndex = static_cast(bindInfo.bindIndex); const uint32_t uboArraySize = static_cast(bindInfo.arraySize); const uint32_t originalBinding = uboOriginalBindings.at(uboName); uint32_t currentSlot = static_cast(uboBindIndex); for (uint32_t i = 0; i < uboArraySize; ++i) { // Use consecutive slot for member in array (*mslShaderInfoOut)[type].actualUBOBindings[originalBinding + i] = currentSlot + i; } } } // Retrieve automatic texture slot assignments if (originalSamplerBindings.size() > 0) { GetAssignedSamplerBindings(reflection, originalSamplerBindings, structSamplers, &mslShaderInfoOut->at(type).actualSamplerBindings); } for (uint32_t i = 0; i < kMaxShaderImages; ++i) { mslShaderInfoOut->at(type).actualImageBindings[i] = reflection->getRWTextureBinding(i); } (*mslShaderInfoOut)[type].hasIsnanOrIsinf = reflection->hasIsnanOrIsinf; (*mslShaderInfoOut)[type].hasInvariant = reflection->hasInvariance; } return angle::Result::Continue; } uint MslGetShaderShadowCompareMode(GLenum mode, GLenum func) { // See SpirvToMslCompiler::emit_header() if (mode == GL_NONE) { return 0; } else { switch (func) { case GL_LESS: return 1; case GL_LEQUAL: return 2; case GL_GREATER: return 3; case GL_GEQUAL: return 4; case GL_NEVER: return 5; case GL_ALWAYS: return 6; case GL_EQUAL: return 7; case GL_NOTEQUAL: return 8; default: UNREACHABLE(); return 1; } } } } // namespace mtl } // namespace rx