| // |
| // 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. |
| // |
| // GlslangUtils: Wrapper for Khronos's glslang compiler. |
| // |
| |
| #include "libANGLE/renderer/metal/mtl_glslang_utils.h" |
| |
| #include <regex> |
| |
| #include <spirv_msl.hpp> |
| |
| #include "common/apple_platform_utils.h" |
| #include "libANGLE/renderer/glslang_wrapper_utils.h" |
| #include "libANGLE/renderer/metal/DisplayMtl.h" |
| |
| namespace rx |
| { |
| namespace mtl |
| { |
| namespace |
| { |
| |
| constexpr uint32_t kGlslangTextureDescSet = 0; |
| constexpr uint32_t kGlslangDefaultUniformAndXfbDescSet = 1; |
| constexpr uint32_t kGlslangDriverUniformsDescSet = 2; |
| constexpr uint32_t kGlslangShaderResourceDescSet = 3; |
| |
| // Original mapping of front end from sampler name to multiple sampler slots (in form of |
| // slot:count pair) |
| using OriginalSamplerBindingMap = |
| angle::HashMap<std::string, std::vector<std::pair<uint32_t, uint32_t>>>; |
| |
| void ResetGlslangProgramInterfaceInfo(GlslangProgramInterfaceInfo *programInterfaceInfo) |
| { |
| // These are binding options passed to glslang. The actual binding might be changed later |
| // by spirv-cross. |
| programInterfaceInfo->uniformsAndXfbDescriptorSetIndex = kGlslangDefaultUniformAndXfbDescSet; |
| programInterfaceInfo->currentUniformBindingIndex = 0; |
| programInterfaceInfo->textureDescriptorSetIndex = kGlslangTextureDescSet; |
| programInterfaceInfo->currentTextureBindingIndex = 0; |
| programInterfaceInfo->driverUniformsDescriptorSetIndex = kGlslangDriverUniformsDescSet; |
| programInterfaceInfo->shaderResourceDescriptorSetIndex = kGlslangShaderResourceDescSet; |
| programInterfaceInfo->currentShaderResourceBindingIndex = 0; |
| programInterfaceInfo->locationsUsedForXfbExtension = 0; |
| |
| static_assert(kDefaultUniformsBindingIndex != 0, "kDefaultUniformsBindingIndex must not be 0"); |
| static_assert(kDriverUniformsBindingIndex != 0, "kDriverUniformsBindingIndex must not be 0"); |
| } |
| |
| GlslangSourceOptions CreateSourceOptions() |
| { |
| GlslangSourceOptions options; |
| return options; |
| } |
| |
| spv::ExecutionModel ShaderTypeToSpvExecutionModel(gl::ShaderType shaderType) |
| { |
| switch (shaderType) |
| { |
| case gl::ShaderType::Vertex: |
| return spv::ExecutionModelVertex; |
| case gl::ShaderType::Fragment: |
| return spv::ExecutionModelFragment; |
| default: |
| UNREACHABLE(); |
| return spv::ExecutionModelMax; |
| } |
| } |
| |
| void BindBuffers(spirv_cross::CompilerMSL *compiler, |
| const spirv_cross::SmallVector<spirv_cross::Resource> &resources, |
| gl::ShaderType shaderType, |
| const angle::HashMap<std::string, uint32_t> &uboOriginalBindings, |
| const angle::HashMap<uint32_t, uint32_t> &xfbOriginalBindings, |
| std::array<uint32_t, kMaxGLUBOBindings> *uboBindingsRemapOut, |
| std::array<uint32_t, kMaxShaderXFBs> *xfbBindingRemapOut, |
| bool *uboArgumentBufferUsed) |
| { |
| auto &compilerMsl = *compiler; |
| |
| uint32_t totalUniformBufferSlots = 0; |
| uint32_t totalXfbSlots = 0; |
| struct UniformBufferVar |
| { |
| const char *name = nullptr; |
| spirv_cross::MSLResourceBinding resBinding; |
| uint32_t arraySize; |
| }; |
| std::vector<UniformBufferVar> uniformBufferBindings; |
| |
| for (const spirv_cross::Resource &resource : resources) |
| { |
| spirv_cross::MSLResourceBinding resBinding; |
| resBinding.stage = ShaderTypeToSpvExecutionModel(shaderType); |
| |
| if (compilerMsl.has_decoration(resource.id, spv::DecorationDescriptorSet)) |
| { |
| resBinding.desc_set = |
| compilerMsl.get_decoration(resource.id, spv::DecorationDescriptorSet); |
| } |
| |
| if (!compilerMsl.has_decoration(resource.id, spv::DecorationBinding)) |
| { |
| continue; |
| } |
| |
| resBinding.binding = compilerMsl.get_decoration(resource.id, spv::DecorationBinding); |
| |
| uint32_t bindingPoint = 0; |
| // NOTE(hqle): We use separate discrete binding point for now, in future, we should use |
| // one argument buffer for each descriptor set. |
| switch (resBinding.desc_set) |
| { |
| case kGlslangTextureDescSet: |
| // Texture binding point is ignored. We let spirv-cross automatically assign it and |
| // retrieve it later |
| continue; |
| case kGlslangDriverUniformsDescSet: |
| bindingPoint = mtl::kDriverUniformsBindingIndex; |
| break; |
| case kGlslangDefaultUniformAndXfbDescSet: |
| if (shaderType != gl::ShaderType::Vertex || |
| !xfbOriginalBindings.count(resBinding.binding)) |
| { |
| bindingPoint = mtl::kDefaultUniformsBindingIndex; |
| } |
| else |
| { |
| // XFB buffer |
| uint32_t xfbSlot = xfbOriginalBindings.at(resBinding.binding); |
| |
| totalXfbSlots++; |
| // XFB buffer is allocated slot starting from last discrete Metal buffer slot. |
| bindingPoint = kMaxShaderBuffers - 1 - xfbSlot; |
| |
| xfbBindingRemapOut->at(xfbSlot) = bindingPoint; |
| } |
| break; |
| case kGlslangShaderResourceDescSet: |
| { |
| UniformBufferVar uboVar; |
| uboVar.name = resource.name.c_str(); |
| uboVar.resBinding = resBinding; |
| const spirv_cross::SPIRType &type = compilerMsl.get_type_from_variable(resource.id); |
| if (!type.array.empty()) |
| { |
| uboVar.arraySize = type.array[0]; |
| } |
| else |
| { |
| uboVar.arraySize = 1; |
| } |
| totalUniformBufferSlots += uboVar.arraySize; |
| uniformBufferBindings.push_back(uboVar); |
| } |
| continue; |
| default: |
| // We don't support this descriptor set. |
| continue; |
| } |
| |
| resBinding.msl_buffer = bindingPoint; |
| |
| compilerMsl.add_msl_resource_binding(resBinding); |
| } // for (resources) |
| |
| if (totalUniformBufferSlots == 0) |
| { |
| return; |
| } |
| |
| // Remap the uniform buffers bindings. glslang allows uniform buffers array to use exactly |
| // one slot in the descriptor set. However, metal enforces that the uniform buffers array |
| // use (n) slots where n=array size. |
| uint32_t currentSlot = 0; |
| uint32_t maxUBODiscreteSlots = |
| kMaxShaderBuffers - totalXfbSlots - kUBOArgumentBufferBindingIndex; |
| |
| if (totalUniformBufferSlots > maxUBODiscreteSlots) |
| { |
| // If shader uses more than maxUBODiscreteSlots number of UBOs, encode them all into |
| // an argument buffer. Each buffer will be assigned [[id(n)]] attribute. |
| *uboArgumentBufferUsed = true; |
| } |
| else |
| { |
| // Use discrete buffer binding slot for UBOs which translates each slot to [[buffer(n)]] |
| *uboArgumentBufferUsed = false; |
| // Discrete buffer binding slot starts at kUBOArgumentBufferBindingIndex |
| currentSlot += kUBOArgumentBufferBindingIndex; |
| } |
| |
| for (UniformBufferVar &uboVar : uniformBufferBindings) |
| { |
| spirv_cross::MSLResourceBinding &resBinding = uboVar.resBinding; |
| resBinding.msl_buffer = currentSlot; |
| |
| uint32_t originalBinding = uboOriginalBindings.at(uboVar.name); |
| |
| for (uint32_t i = 0; i < uboVar.arraySize; ++i, ++currentSlot) |
| { |
| // Use consecutive slot for member in array |
| uboBindingsRemapOut->at(originalBinding + i) = currentSlot; |
| } |
| |
| compilerMsl.add_msl_resource_binding(resBinding); |
| } |
| } |
| |
| void GetAssignedSamplerBindings(const spirv_cross::CompilerMSL &compilerMsl, |
| const OriginalSamplerBindingMap &originalBindings, |
| std::array<SamplerBinding, mtl::kMaxGLSamplerBindings> *bindings) |
| { |
| for (const spirv_cross::Resource &resource : compilerMsl.get_shader_resources().sampled_images) |
| { |
| uint32_t descriptorSet = 0; |
| if (compilerMsl.has_decoration(resource.id, spv::DecorationDescriptorSet)) |
| { |
| descriptorSet = compilerMsl.get_decoration(resource.id, spv::DecorationDescriptorSet); |
| } |
| |
| // We already assigned descriptor set 0 to textures. Just to double check. |
| ASSERT(descriptorSet == kGlslangTextureDescSet); |
| ASSERT(compilerMsl.has_decoration(resource.id, spv::DecorationBinding)); |
| |
| uint32_t actualTextureSlot = compilerMsl.get_automatic_msl_resource_binding(resource.id); |
| uint32_t actualSamplerSlot = |
| compilerMsl.get_automatic_msl_resource_binding_secondary(resource.id); |
| |
| // Assign sequential index for subsequent array elements |
| const std::vector<std::pair<uint32_t, uint32_t>> &resOrignalBindings = |
| originalBindings.at(resource.name); |
| uint32_t currentTextureSlot = actualTextureSlot; |
| uint32_t currentSamplerSlot = actualSamplerSlot; |
| for (const std::pair<uint32_t, uint32_t> &originalBindingRange : resOrignalBindings) |
| { |
| SamplerBinding &actualBinding = bindings->at(originalBindingRange.first); |
| actualBinding.textureBinding = currentTextureSlot; |
| actualBinding.samplerBinding = currentSamplerSlot; |
| |
| currentTextureSlot += originalBindingRange.second; |
| currentSamplerSlot += originalBindingRange.second; |
| } |
| } |
| } |
| |
| std::string PostProcessTranslatedMsl(const std::string &translatedSource) |
| { |
| // Add function_constant attribute to gl_SampleMask. |
| // Even though this varying is only used when ANGLECoverageMaskEnabled is true, |
| // the spirv-cross doesn't assign function_constant attribute to it. Thus it won't be dead-code |
| // removed when ANGLECoverageMaskEnabled=false. |
| std::string sampleMaskReplaceStr = std::string("[[sample_mask, function_constant(") + |
| sh::mtl::kCoverageMaskEnabledConstName + ")]]"; |
| |
| // This replaces "gl_SampleMask [[sample_mask]]" |
| // with "gl_SampleMask [[sample_mask, function_constant(ANGLECoverageMaskEnabled)]]" |
| std::regex sampleMaskDeclareRegex(R"(\[\s*\[\s*sample_mask\s*\]\s*\])"); |
| return std::regex_replace(translatedSource, sampleMaskDeclareRegex, sampleMaskReplaceStr); |
| } |
| |
| // Customized spirv-cross compiler |
| class SpirvToMslCompiler : public spirv_cross::CompilerMSL |
| { |
| public: |
| SpirvToMslCompiler(angle::spirv::Blob &&spriv) : spirv_cross::CompilerMSL(spriv) {} |
| |
| void compileEx(gl::ShaderType shaderType, |
| const angle::HashMap<std::string, uint32_t> &uboOriginalBindings, |
| const angle::HashMap<uint32_t, uint32_t> &xfbOriginalBindings, |
| const OriginalSamplerBindingMap &originalSamplerBindings, |
| bool disableRasterization, |
| TranslatedShaderInfo *mslShaderInfoOut) |
| { |
| spirv_cross::CompilerMSL::Options compOpt; |
| |
| #if TARGET_OS_OSX || TARGET_OS_MACCATALYST |
| compOpt.platform = spirv_cross::CompilerMSL::Options::macOS; |
| #else |
| compOpt.platform = spirv_cross::CompilerMSL::Options::iOS; |
| #endif |
| |
| if (ANGLE_APPLE_AVAILABLE_XCI(10.14, 13.0, 12)) |
| { |
| // Use Metal 2.1 |
| compOpt.set_msl_version(2, 1); |
| } |
| else |
| { |
| // Always use at least Metal 2.0. |
| compOpt.set_msl_version(2); |
| } |
| |
| compOpt.pad_fragment_output_components = true; |
| compOpt.disable_rasterization = disableRasterization; |
| |
| // Tell spirv-cross to map default & driver uniform & storage blocks as we want |
| spirv_cross::ShaderResources mslRes = spirv_cross::CompilerMSL::get_shader_resources(); |
| |
| spirv_cross::SmallVector<spirv_cross::Resource> buffers = std::move(mslRes.uniform_buffers); |
| buffers.insert(buffers.end(), mslRes.storage_buffers.begin(), mslRes.storage_buffers.end()); |
| |
| BindBuffers(this, buffers, shaderType, uboOriginalBindings, xfbOriginalBindings, |
| &mslShaderInfoOut->actualUBOBindings, &mslShaderInfoOut->actualXFBBindings, |
| &mslShaderInfoOut->hasUBOArgumentBuffer); |
| |
| if (mslShaderInfoOut->hasUBOArgumentBuffer) |
| { |
| // Enable argument buffer. |
| compOpt.argument_buffers = true; |
| |
| // Force UBO argument buffer binding to start at kUBOArgumentBufferBindingIndex. |
| spirv_cross::MSLResourceBinding argBufferBinding = {}; |
| argBufferBinding.stage = ShaderTypeToSpvExecutionModel(shaderType); |
| argBufferBinding.desc_set = kGlslangShaderResourceDescSet; |
| argBufferBinding.binding = |
| spirv_cross::kArgumentBufferBinding; // spirv-cross built-in binding. |
| argBufferBinding.msl_buffer = kUBOArgumentBufferBindingIndex; // Actual binding. |
| spirv_cross::CompilerMSL::add_msl_resource_binding(argBufferBinding); |
| |
| // Force discrete slot bindings for textures, default uniforms & driver uniforms |
| // instead of using argument buffer. |
| spirv_cross::CompilerMSL::add_discrete_descriptor_set(kGlslangTextureDescSet); |
| spirv_cross::CompilerMSL::add_discrete_descriptor_set( |
| kGlslangDefaultUniformAndXfbDescSet); |
| spirv_cross::CompilerMSL::add_discrete_descriptor_set(kGlslangDriverUniformsDescSet); |
| } |
| else |
| { |
| // Disable argument buffer generation for uniform buffers |
| compOpt.argument_buffers = false; |
| } |
| |
| spirv_cross::CompilerMSL::set_msl_options(compOpt); |
| |
| // Actual compilation |
| mslShaderInfoOut->metalShaderSource = |
| PostProcessTranslatedMsl(spirv_cross::CompilerMSL::compile()); |
| |
| // Retrieve automatic texture slot assignments |
| GetAssignedSamplerBindings(*this, originalSamplerBindings, |
| &mslShaderInfoOut->actualSamplerBindings); |
| } |
| }; |
| |
| angle::Result ConvertSpirvToMsl(Context *context, |
| gl::ShaderType shaderType, |
| const angle::HashMap<std::string, uint32_t> &uboOriginalBindings, |
| const angle::HashMap<uint32_t, uint32_t> &xfbOriginalBindings, |
| const OriginalSamplerBindingMap &originalSamplerBindings, |
| bool disableRasterization, |
| angle::spirv::Blob *sprivCode, |
| TranslatedShaderInfo *translatedShaderInfoOut) |
| { |
| if (!sprivCode || sprivCode->empty()) |
| { |
| return angle::Result::Continue; |
| } |
| |
| SpirvToMslCompiler compilerMsl(std::move(*sprivCode)); |
| |
| // NOTE(hqle): spirv-cross uses exceptions to report error, what should we do here |
| // in case of error? |
| compilerMsl.compileEx(shaderType, uboOriginalBindings, xfbOriginalBindings, |
| originalSamplerBindings, disableRasterization, translatedShaderInfoOut); |
| if (translatedShaderInfoOut->metalShaderSource.size() == 0) |
| { |
| ANGLE_MTL_CHECK(context, false, GL_INVALID_OPERATION); |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| } // namespace |
| |
| void GlslangGetShaderSpirvCode(const gl::ProgramState &programState, |
| const gl::ProgramLinkedResources &resources, |
| gl::ShaderMap<const angle::spirv::Blob *> *spirvBlobsOut, |
| ShaderInterfaceVariableInfoMap *variableInfoMapOut, |
| ShaderInterfaceVariableInfoMap *xfbOnlyVSVariableInfoMapOut) |
| { |
| GlslangSourceOptions options = CreateSourceOptions(); |
| GlslangProgramInterfaceInfo programInterfaceInfo; |
| ResetGlslangProgramInterfaceInfo(&programInterfaceInfo); |
| |
| options.supportsTransformFeedbackEmulation = true; |
| |
| // Get shader sources and fill variable info map with transform feedback disabled. |
| rx::GlslangGetShaderSpirvCode(options, programState, resources, &programInterfaceInfo, |
| spirvBlobsOut, variableInfoMapOut); |
| |
| // Fill variable info map with transform feedback enabled. |
| if (!programState.getLinkedTransformFeedbackVaryings().empty()) |
| { |
| GlslangProgramInterfaceInfo xfbOnlyInterfaceInfo; |
| ResetGlslangProgramInterfaceInfo(&xfbOnlyInterfaceInfo); |
| |
| options.enableTransformFeedbackEmulation = true; |
| UniformBindingIndexMap uniformBindingIndexMap; |
| GlslangAssignLocations(options, programState, resources.varyingPacking, |
| gl::ShaderType::Vertex, gl::ShaderType::InvalidEnum, true, |
| &xfbOnlyInterfaceInfo, &uniformBindingIndexMap, |
| xfbOnlyVSVariableInfoMapOut); |
| } |
| } |
| |
| angle::Result GlslangTransformSpirvCode(const gl::ShaderBitSet &linkedShaderStages, |
| const gl::ShaderMap<const angle::spirv::Blob *> &spirvBlobs, |
| bool isTransformFeedbackEnabled, |
| const ShaderInterfaceVariableInfoMap &variableInfoMap, |
| gl::ShaderMap<angle::spirv::Blob> *shaderCodeOut) |
| { |
| for (const gl::ShaderType shaderType : linkedShaderStages) |
| { |
| GlslangSpirvOptions options; |
| options.shaderType = shaderType; |
| options.transformPositionToVulkanClipSpace = true; |
| options.isTransformFeedbackStage = |
| shaderType == gl::ShaderType::Vertex && isTransformFeedbackEnabled; |
| options.isTransformFeedbackEmulated = true; |
| |
| angle::Result status = GlslangTransformSpirvCode( |
| options, variableInfoMap, *spirvBlobs[shaderType], &(*shaderCodeOut)[shaderType]); |
| if (status != angle::Result::Continue) |
| { |
| return status; |
| } |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result SpirvCodeToMsl(Context *context, |
| const gl::ProgramState &programState, |
| const ShaderInterfaceVariableInfoMap &xfbVSVariableInfoMap, |
| gl::ShaderMap<angle::spirv::Blob> *spirvShaderCode, |
| angle::spirv::Blob *xfbOnlySpirvCode /** nullable */, |
| gl::ShaderMap<TranslatedShaderInfo> *mslShaderInfoOut, |
| TranslatedShaderInfo *mslXfbOnlyShaderInfoOut /** nullable */) |
| { |
| // Retrieve original uniform buffer bindings generated by front end. We will need to do a remap. |
| angle::HashMap<std::string, uint32_t> uboOriginalBindings; |
| const std::vector<gl::InterfaceBlock> &blocks = programState.getUniformBlocks(); |
| for (uint32_t bufferIdx = 0; bufferIdx < blocks.size(); ++bufferIdx) |
| { |
| const gl::InterfaceBlock &block = blocks[bufferIdx]; |
| if (!uboOriginalBindings.count(block.mappedName)) |
| { |
| uboOriginalBindings[block.mappedName] = bufferIdx; |
| } |
| } |
| // Retrieve original XFB buffers bindings produced by front end. |
| angle::HashMap<uint32_t, uint32_t> xfbOriginalBindings; |
| for (uint32_t bufferIdx = 0; bufferIdx < kMaxShaderXFBs; ++bufferIdx) |
| { |
| std::string bufferName = rx::GetXfbBufferName(bufferIdx); |
| if (xfbVSVariableInfoMap.contains(gl::ShaderType::Vertex, bufferName)) |
| { |
| const ShaderInterfaceVariableInfo &info = |
| xfbVSVariableInfoMap.get(gl::ShaderType::Vertex, bufferName); |
| xfbOriginalBindings[info.binding] = bufferIdx; |
| } |
| } |
| |
| // Retrieve original sampler bindings produced by front end. |
| OriginalSamplerBindingMap originalSamplerBindings; |
| const std::vector<gl::SamplerBinding> &samplerBindings = programState.getSamplerBindings(); |
| const std::vector<gl::LinkedUniform> &uniforms = programState.getUniforms(); |
| |
| for (uint32_t textureIndex = 0; textureIndex < samplerBindings.size(); ++textureIndex) |
| { |
| const gl::SamplerBinding &samplerBinding = samplerBindings[textureIndex]; |
| uint32_t uniformIndex = programState.getUniformIndexFromSamplerIndex(textureIndex); |
| const gl::LinkedUniform &samplerUniform = uniforms[uniformIndex]; |
| std::string mappedSamplerName = GlslangGetMappedSamplerName(samplerUniform.name); |
| originalSamplerBindings[mappedSamplerName].push_back( |
| {textureIndex, static_cast<uint32_t>(samplerBinding.boundTextureUnits.size())}); |
| } |
| |
| // Do the actual translation |
| for (gl::ShaderType shaderType : gl::kAllGLES2ShaderTypes) |
| { |
| angle::spirv::Blob &sprivCode = spirvShaderCode->at(shaderType); |
| ANGLE_TRY(ConvertSpirvToMsl(context, shaderType, uboOriginalBindings, xfbOriginalBindings, |
| originalSamplerBindings, /* disableRasterization */ false, |
| &sprivCode, &mslShaderInfoOut->at(shaderType))); |
| } // for (gl::ShaderType shaderType |
| |
| // Special version of XFB only |
| if (xfbOnlySpirvCode && !programState.getLinkedTransformFeedbackVaryings().empty()) |
| { |
| ANGLE_TRY(ConvertSpirvToMsl(context, gl::ShaderType::Vertex, uboOriginalBindings, |
| xfbOriginalBindings, originalSamplerBindings, |
| /* disableRasterization */ true, xfbOnlySpirvCode, |
| mslXfbOnlyShaderInfoOut)); |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| } // namespace mtl |
| } // namespace rx |