// // 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. // // Utilities to map shader interface variables to Vulkan mappings, and transform the SPIR-V // accordingly. // #include "libANGLE/renderer/vulkan/spv_utils.h" #include #include #include #include "common/FixedVector.h" #include "common/spirv/spirv_instruction_builder_autogen.h" #include "common/spirv/spirv_instruction_parser_autogen.h" #include "common/string_utils.h" #include "common/utilities.h" #include "libANGLE/Caps.h" #include "libANGLE/ProgramLinkedResources.h" #include "libANGLE/renderer/vulkan/ShaderInterfaceVariableInfoMap.h" #include "libANGLE/renderer/vulkan/vk_cache_utils.h" #include "libANGLE/trace.h" namespace spirv = angle::spirv; namespace rx { namespace { // 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. bool UniformNameIsIndexZero(const std::string &name) { size_t lastBracketClose = 0; 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; } uint32_t SpvIsXfbBufferBlockId(spirv::IdRef id) { return id >= sh::vk::spirv::ReservedIds::kIdXfbEmulationBufferVarZero && id < sh::vk::spirv::ReservedIds::kIdXfbEmulationBufferVarZero + 4; } template uint32_t CountExplicitOutputs(OutputIter outputsBegin, OutputIter outputsEnd, ImplicitIter implicitsBegin, ImplicitIter implicitsEnd) { auto reduce = [implicitsBegin, implicitsEnd](uint32_t count, const gl::ProgramOutput &var) { bool isExplicit = std::find(implicitsBegin, implicitsEnd, var.name) == implicitsEnd; return count + isExplicit; }; return std::accumulate(outputsBegin, outputsEnd, 0, reduce); } ShaderInterfaceVariableInfo *AddResourceInfoToAllStages(ShaderInterfaceVariableInfoMap *infoMap, gl::ShaderType shaderType, uint32_t varId, uint32_t descriptorSet, uint32_t binding) { gl::ShaderBitSet allStages; allStages.set(); ShaderInterfaceVariableInfo &info = infoMap->add(shaderType, varId); info.descriptorSet = descriptorSet; info.binding = binding; info.activeStages = allStages; return &info; } ShaderInterfaceVariableInfo *AddResourceInfo(ShaderInterfaceVariableInfoMap *infoMap, gl::ShaderBitSet stages, gl::ShaderType shaderType, uint32_t varId, uint32_t descriptorSet, uint32_t binding) { ShaderInterfaceVariableInfo &info = infoMap->add(shaderType, varId); info.descriptorSet = descriptorSet; info.binding = binding; info.activeStages = stages; return &info; } // Add location information for an in/out variable. ShaderInterfaceVariableInfo *AddLocationInfo(ShaderInterfaceVariableInfoMap *infoMap, gl::ShaderType shaderType, uint32_t varId, uint32_t location, uint32_t component, uint8_t attributeComponentCount, uint8_t attributeLocationCount) { // The info map for this id may or may not exist already. This function merges the // location/component information. ShaderInterfaceVariableInfo &info = infoMap->addOrGet(shaderType, varId); ASSERT(info.descriptorSet == ShaderInterfaceVariableInfo::kInvalid); ASSERT(info.binding == ShaderInterfaceVariableInfo::kInvalid); if (info.location != ShaderInterfaceVariableInfo::kInvalid) { ASSERT(info.location == location); ASSERT(info.component == component); } ASSERT(info.component == ShaderInterfaceVariableInfo::kInvalid); info.location = location; info.component = component; info.activeStages.set(shaderType); info.attributeComponentCount = attributeComponentCount; info.attributeLocationCount = attributeLocationCount; return &info; } // Add location information for an in/out variable void AddVaryingLocationInfo(ShaderInterfaceVariableInfoMap *infoMap, const gl::VaryingInShaderRef &ref, const uint32_t location, const uint32_t component) { // Skip statically-unused varyings, they are already pruned by the translator if (ref.varying->id != 0) { AddLocationInfo(infoMap, ref.stage, ref.varying->id, location, component, 0, 0); } } // Modify an existing out variable and add transform feedback information. void SetXfbInfo(ShaderInterfaceVariableInfoMap *infoMap, gl::ShaderType shaderType, uint32_t varId, int fieldIndex, uint32_t xfbBuffer, uint32_t xfbOffset, uint32_t xfbStride, uint32_t arraySize, uint32_t columnCount, uint32_t rowCount, uint32_t arrayIndex, GLenum componentType) { XFBInterfaceVariableInfo *info = infoMap->getXFBMutable(shaderType, varId); ASSERT(info != nullptr); ShaderInterfaceVariableXfbInfo *xfb = &info->xfb; if (fieldIndex >= 0) { if (info->fieldXfb.size() <= static_cast(fieldIndex)) { info->fieldXfb.resize(fieldIndex + 1); } xfb = &info->fieldXfb[fieldIndex]; } ASSERT(xfb->pod.buffer == ShaderInterfaceVariableXfbInfo::kInvalid); ASSERT(xfb->pod.offset == ShaderInterfaceVariableXfbInfo::kInvalid); ASSERT(xfb->pod.stride == ShaderInterfaceVariableXfbInfo::kInvalid); if (arrayIndex != ShaderInterfaceVariableXfbInfo::kInvalid) { xfb->arrayElements.emplace_back(); xfb = &xfb->arrayElements.back(); } xfb->pod.buffer = xfbBuffer; xfb->pod.offset = xfbOffset; xfb->pod.stride = xfbStride; xfb->pod.arraySize = arraySize; xfb->pod.columnCount = columnCount; xfb->pod.rowCount = rowCount; xfb->pod.arrayIndex = arrayIndex; xfb->pod.componentType = componentType; } void AssignTransformFeedbackEmulationBindings(gl::ShaderType shaderType, const gl::ProgramExecutable &programExecutable, bool isTransformFeedbackStage, SpvProgramInterfaceInfo *programInterfaceInfo, ShaderInterfaceVariableInfoMap *variableInfoMapOut) { size_t bufferCount = 0; if (isTransformFeedbackStage) { ASSERT(!programExecutable.getLinkedTransformFeedbackVaryings().empty()); const bool isInterleaved = programExecutable.getTransformFeedbackBufferMode() == GL_INTERLEAVED_ATTRIBS; bufferCount = isInterleaved ? 1 : programExecutable.getLinkedTransformFeedbackVaryings().size(); } // Add entries for the transform feedback buffers to the info map, so they can have correct // set/binding. for (uint32_t bufferIndex = 0; bufferIndex < bufferCount; ++bufferIndex) { AddResourceInfo(variableInfoMapOut, gl::ShaderBitSet().set(shaderType), shaderType, SpvGetXfbBufferBlockId(bufferIndex), ToUnderlying(DescriptorSetIndex::UniformsAndXfb), programInterfaceInfo->currentUniformBindingIndex); ++programInterfaceInfo->currentUniformBindingIndex; } // Remove inactive transform feedback buffers. for (uint32_t bufferIndex = static_cast(bufferCount); bufferIndex < gl::IMPLEMENTATION_MAX_TRANSFORM_FEEDBACK_BUFFERS; ++bufferIndex) { variableInfoMapOut->add(shaderType, SpvGetXfbBufferBlockId(bufferIndex)); } } bool IsFirstRegisterOfVarying(const gl::PackedVaryingRegister &varyingReg, bool allowFields, uint32_t expectArrayIndex) { 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. With I/O // blocks, transform feedback can capture an arbitrary field. In that case, we need to look at // every field, not just the first one. if (!allowFields && varying.isStructField() && (varying.fieldIndex > 0 || varying.secondaryFieldIndex > 0)) { return false; } // Similarly, assign array varying locations to the assigned location of the first element. // Transform feedback may capture array elements, so if a specific non-zero element is // requested, accept that only. if (varyingReg.varyingArrayIndex != expectArrayIndex || (varying.arrayIndex != GL_INVALID_INDEX && varying.arrayIndex != expectArrayIndex)) { return false; } // Similarly, assign matrix varying locations to the assigned location of the first row. if (varyingReg.varyingRowIndex != 0) { return false; } return true; } void AssignAttributeLocations(const gl::ProgramExecutable &programExecutable, gl::ShaderType shaderType, ShaderInterfaceVariableInfoMap *variableInfoMapOut) { const std::array implicitInputs = {"gl_VertexID", "gl_InstanceID"}; gl::AttributesMask isLocationAssigned; bool hasAliasingAttributes = false; // Assign attribute locations for the vertex shader. for (const gl::ProgramInput &attribute : programExecutable.getProgramInputs()) { ASSERT(attribute.isActive()); if (std::find(implicitInputs.begin(), implicitInputs.end(), attribute.name) != implicitInputs.end()) { continue; } const uint8_t colCount = static_cast(gl::VariableColumnCount(attribute.getType())); const uint8_t rowCount = static_cast(gl::VariableRowCount(attribute.getType())); const bool isMatrix = colCount > 1 && rowCount > 1; const uint8_t componentCount = isMatrix ? rowCount : colCount; const uint8_t locationCount = isMatrix ? colCount : rowCount; AddLocationInfo(variableInfoMapOut, shaderType, attribute.getId(), attribute.getLocation(), ShaderInterfaceVariableInfo::kInvalid, componentCount, locationCount); // Detect if there are aliasing attributes. if (!hasAliasingAttributes && programExecutable.getLinkedShaderVersion(gl::ShaderType::Vertex) == 100) { for (uint8_t offset = 0; offset < locationCount; ++offset) { uint32_t location = attribute.getLocation() + offset; // If there's aliasing, no need for futher processing. if (isLocationAssigned.test(location)) { hasAliasingAttributes = true; break; } isLocationAssigned.set(location); } } } if (hasAliasingAttributes) { variableInfoMapOut->setHasAliasingAttributes(); } } void AssignSecondaryOutputLocations(const gl::ProgramExecutable &programExecutable, ShaderInterfaceVariableInfoMap *variableInfoMapOut) { const auto &secondaryOutputLocations = programExecutable.getSecondaryOutputLocations(); const auto &outputVariables = programExecutable.getOutputVariables(); // Handle EXT_blend_func_extended secondary outputs (ones with index=1) for (const gl::VariableLocation &outputLocation : secondaryOutputLocations) { if (outputLocation.arrayIndex == 0 && outputLocation.used() && !outputLocation.ignored) { const gl::ProgramOutput &outputVar = outputVariables[outputLocation.index]; uint32_t location = 0; if (outputVar.pod.location != -1) { location = outputVar.pod.location; } ShaderInterfaceVariableInfo *info = AddLocationInfo(variableInfoMapOut, gl::ShaderType::Fragment, outputVar.pod.id, location, ShaderInterfaceVariableInfo::kInvalid, 0, 0); // Index 1 is used to specify that the color be used as the second color input to // the blend equation info->index = 1; ASSERT(!outputVar.isArray() || outputVar.getOutermostArraySize() == 1); info->isArray = outputVar.isArray(); } } // Handle secondary outputs for ESSL version less than 3.00 if (programExecutable.hasLinkedShaderStage(gl::ShaderType::Fragment) && programExecutable.getLinkedShaderVersion(gl::ShaderType::Fragment) == 100) { const std::array secondaryFrag = {"gl_SecondaryFragColorEXT", "gl_SecondaryFragDataEXT"}; for (const gl::ProgramOutput &outputVar : outputVariables) { if (std::find(secondaryFrag.begin(), secondaryFrag.end(), outputVar.name) != secondaryFrag.end()) { ShaderInterfaceVariableInfo *info = AddLocationInfo(variableInfoMapOut, gl::ShaderType::Fragment, outputVar.pod.id, 0, ShaderInterfaceVariableInfo::kInvalid, 0, 0); info->index = 1; ASSERT(!outputVar.isArray() || outputVar.getOutermostArraySize() == 1); info->isArray = outputVar.isArray(); // SecondaryFragColor and SecondaryFragData cannot be present simultaneously. break; } } } } void AssignOutputLocations(const gl::ProgramExecutable &programExecutable, const gl::ShaderType shaderType, ShaderInterfaceVariableInfoMap *variableInfoMapOut) { // Assign output locations for the fragment shader. ASSERT(shaderType == gl::ShaderType::Fragment); 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 gl::ProgramOutput &outputVar = outputVariables[outputLocation.index]; uint32_t location = 0; if (outputVar.pod.location != -1) { location = outputVar.pod.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, shaderType, outputVar.pod.id, location, ShaderInterfaceVariableInfo::kInvalid, 0, 0); } } // Handle outputs for ESSL version less than 3.00 if (programExecutable.hasLinkedShaderStage(gl::ShaderType::Fragment) && programExecutable.getLinkedShaderVersion(gl::ShaderType::Fragment) == 100) { for (const gl::ProgramOutput &outputVar : outputVariables) { if (outputVar.name == "gl_FragColor" || outputVar.name == "gl_FragData") { AddLocationInfo(variableInfoMapOut, gl::ShaderType::Fragment, outputVar.pod.id, 0, ShaderInterfaceVariableInfo::kInvalid, 0, 0); } } } AssignSecondaryOutputLocations(programExecutable, variableInfoMapOut); } void AssignVaryingLocations(const SpvSourceOptions &options, const gl::VaryingPacking &varyingPacking, const gl::ShaderType shaderType, const gl::ShaderType frontShaderType, SpvProgramInterfaceInfo *programInterfaceInfo, ShaderInterfaceVariableInfoMap *variableInfoMapOut) { uint32_t locationsUsedForEmulation = programInterfaceInfo->locationsUsedForXfbExtension; // Assign varying locations. for (const gl::PackedVaryingRegister &varyingReg : varyingPacking.getRegisterList()) { if (!IsFirstRegisterOfVarying(varyingReg, false, 0)) { 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; } if (varying.frontVarying.varying && (varying.frontVarying.stage == shaderType)) { AddVaryingLocationInfo(variableInfoMapOut, varying.frontVarying, location, component); } if (varying.backVarying.varying && (varying.backVarying.stage == shaderType)) { AddVaryingLocationInfo(variableInfoMapOut, varying.backVarying, location, component); } } // Add an entry for inactive varyings. const gl::ShaderMap> &inactiveVaryingIds = varyingPacking.getInactiveVaryingIds(); for (const uint32_t varyingId : inactiveVaryingIds[shaderType]) { // If id is already in the map, it will automatically have marked all other stages inactive. if (variableInfoMapOut->hasVariable(shaderType, varyingId)) { continue; } // Otherwise, add an entry for it with all locations inactive. ShaderInterfaceVariableInfo &info = variableInfoMapOut->addOrGet(shaderType, varyingId); ASSERT(info.location == ShaderInterfaceVariableInfo::kInvalid); } // Add an entry for gl_PerVertex, for use with transform feedback capture of built-ins. ShaderInterfaceVariableInfo &info = variableInfoMapOut->addOrGet(shaderType, sh::vk::spirv::kIdOutputPerVertexBlock); info.activeStages.set(shaderType); } // Calculates XFB layout qualifier arguments for each transform feedback varying. Stores calculated // values for the SPIR-V transformation. void AssignTransformFeedbackQualifiers(const gl::ProgramExecutable &programExecutable, const gl::VaryingPacking &varyingPacking, const gl::ShaderType shaderType, bool usesExtension, ShaderInterfaceVariableInfoMap *variableInfoMapOut) { const std::vector &tfVaryings = programExecutable.getLinkedTransformFeedbackVaryings(); const std::vector &varyingStrides = programExecutable.getTransformFeedbackStrides(); const bool isInterleaved = programExecutable.getTransformFeedbackBufferMode() == GL_INTERLEAVED_ATTRIBS; uint32_t currentOffset = 0; uint32_t currentStride = 0; uint32_t bufferIndex = 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 gl::UniformTypeInfo &uniformInfo = gl::GetUniformTypeInfo(tfVarying.type); const uint32_t varyingSize = tfVarying.isArray() ? tfVarying.size() : ShaderInterfaceVariableXfbInfo::kInvalid; if (tfVarying.isBuiltIn()) { if (usesExtension && tfVarying.name == "gl_Position") { // With the extension, gl_Position is captured via a special varying. SetXfbInfo(variableInfoMapOut, shaderType, sh::vk::spirv::kIdXfbExtensionPosition, -1, bufferIndex, currentOffset, currentStride, varyingSize, uniformInfo.columnCount, uniformInfo.rowCount, ShaderInterfaceVariableXfbInfo::kInvalid, uniformInfo.componentType); } else { // gl_PerVertex is always defined as: // // Field 0: gl_Position // Field 1: gl_PointSize // Field 2: gl_ClipDistance // Field 3: gl_CullDistance // // With the extension, all fields except gl_Position can be captured directly by // decorating gl_PerVertex fields. int fieldIndex = -1; constexpr int kPerVertexMemberCount = 4; constexpr std::array kPerVertexMembers = { "gl_Position", "gl_PointSize", "gl_ClipDistance", "gl_CullDistance", }; for (int index = 0; index < kPerVertexMemberCount; ++index) { if (tfVarying.name == kPerVertexMembers[index]) { fieldIndex = index; break; } } ASSERT(fieldIndex != -1); ASSERT(!usesExtension || fieldIndex > 0); SetXfbInfo(variableInfoMapOut, shaderType, sh::vk::spirv::kIdOutputPerVertexBlock, fieldIndex, bufferIndex, currentOffset, currentStride, varyingSize, uniformInfo.columnCount, uniformInfo.rowCount, ShaderInterfaceVariableXfbInfo::kInvalid, uniformInfo.componentType); } continue; } // Note: capturing individual array elements using the Vulkan transform feedback extension // is currently not supported due to limitations in the extension. // ANGLE supports capturing the whole array. // http://anglebug.com/42262773 if (usesExtension && tfVarying.isArray() && tfVarying.arrayIndex != GL_INVALID_INDEX) { continue; } // Find the varying with this name. If a struct is captured, we would be iterating over its // fields. This is done when the first field of the struct is visited. For I/O blocks on // the other hand, we need to decorate the exact member that is captured (as whole-block // capture is not supported). const gl::PackedVarying *originalVarying = nullptr; for (const gl::PackedVaryingRegister &varyingReg : varyingPacking.getRegisterList()) { const uint32_t arrayIndex = tfVarying.arrayIndex == GL_INVALID_INDEX ? 0 : tfVarying.arrayIndex; if (!IsFirstRegisterOfVarying(varyingReg, tfVarying.isShaderIOBlock, arrayIndex)) { continue; } const gl::PackedVarying *varying = varyingReg.packedVarying; if (tfVarying.isShaderIOBlock) { if (varying->frontVarying.parentStructName == tfVarying.structOrBlockName) { size_t pos = tfVarying.name.find_first_of("."); std::string fieldName = pos == std::string::npos ? tfVarying.name : tfVarying.name.substr(pos + 1); if (fieldName == varying->frontVarying.varying->name.c_str()) { originalVarying = varying; break; } } } else if (varying->frontVarying.varying->name == tfVarying.name) { originalVarying = varying; break; } } if (originalVarying) { const int fieldIndex = tfVarying.isShaderIOBlock ? originalVarying->fieldIndex : -1; const uint32_t arrayIndex = tfVarying.arrayIndex == GL_INVALID_INDEX ? ShaderInterfaceVariableXfbInfo::kInvalid : tfVarying.arrayIndex; // Set xfb info for this varying. AssignVaryingLocations should have already added // location information for these varyings. SetXfbInfo(variableInfoMapOut, shaderType, originalVarying->frontVarying.varying->id, fieldIndex, bufferIndex, currentOffset, currentStride, varyingSize, uniformInfo.columnCount, uniformInfo.rowCount, arrayIndex, uniformInfo.componentType); } } } void AssignUniformBindings(const SpvSourceOptions &options, const gl::ProgramExecutable &programExecutable, SpvProgramInterfaceInfo *programInterfaceInfo, ShaderInterfaceVariableInfoMap *variableInfoMapOut) { for (const gl::ShaderType shaderType : programExecutable.getLinkedShaderStages()) { AddResourceInfo(variableInfoMapOut, gl::ShaderBitSet().set(shaderType), shaderType, sh::vk::spirv::kIdDefaultUniformsBlock, ToUnderlying(DescriptorSetIndex::UniformsAndXfb), programInterfaceInfo->currentUniformBindingIndex); ++programInterfaceInfo->currentUniformBindingIndex; // Assign binding to the driver uniforms block AddResourceInfoToAllStages(variableInfoMapOut, shaderType, sh::vk::spirv::kIdDriverUniformsBlock, ToUnderlying(DescriptorSetIndex::Internal), 0); } } void AssignInputAttachmentBindings(const SpvSourceOptions &options, const gl::ProgramExecutable &programExecutable, SpvProgramInterfaceInfo *programInterfaceInfo, ShaderInterfaceVariableInfoMap *variableInfoMapOut) { if (!programExecutable.hasLinkedShaderStage(gl::ShaderType::Fragment)) { return; } if (!programExecutable.usesColorFramebufferFetch() && !programExecutable.usesDepthFramebufferFetch() && !programExecutable.usesStencilFramebufferFetch()) { return; } uint32_t baseInputAttachmentBindingIndex = programInterfaceInfo->currentShaderResourceBindingIndex; const gl::ShaderBitSet activeShaders{gl::ShaderType::Fragment}; // If depth/stencil framebuffer fetch is enabled, place their bindings before the color // attachments. When binding descriptors, this results in a smaller gap that would need to be // filled with bogus bindings. if (options.supportsDepthStencilInputAttachments) { AddResourceInfo(variableInfoMapOut, activeShaders, gl::ShaderType::Fragment, sh::vk::spirv::kIdDepthInputAttachment, ToUnderlying(DescriptorSetIndex::ShaderResource), baseInputAttachmentBindingIndex++); AddResourceInfo(variableInfoMapOut, activeShaders, gl::ShaderType::Fragment, sh::vk::spirv::kIdStencilInputAttachment, ToUnderlying(DescriptorSetIndex::ShaderResource), baseInputAttachmentBindingIndex++); programInterfaceInfo->currentShaderResourceBindingIndex += 2; } if (programExecutable.usesColorFramebufferFetch()) { // sh::vk::spirv::ReservedIds supports max 8 draw buffers ASSERT(options.maxColorInputAttachmentCount <= 8); ASSERT(options.maxColorInputAttachmentCount >= 1); for (size_t index : programExecutable.getFragmentInoutIndices()) { const uint32_t inputAttachmentBindingIndex = baseInputAttachmentBindingIndex + static_cast(index); AddResourceInfo(variableInfoMapOut, activeShaders, gl::ShaderType::Fragment, sh::vk::spirv::kIdInputAttachment0 + static_cast(index), ToUnderlying(DescriptorSetIndex::ShaderResource), inputAttachmentBindingIndex); } } // For input attachment uniform, the descriptor set binding indices are allocated as much as // the maximum draw buffers. programInterfaceInfo->currentShaderResourceBindingIndex += options.maxColorInputAttachmentCount; } void AssignInterfaceBlockBindings(const SpvSourceOptions &options, const gl::ProgramExecutable &programExecutable, const std::vector &blocks, SpvProgramInterfaceInfo *programInterfaceInfo, ShaderInterfaceVariableInfoMap *variableInfoMapOut) { for (uint32_t blockIndex = 0; blockIndex < blocks.size(); ++blockIndex) { const gl::InterfaceBlock &block = blocks[blockIndex]; // TODO: http://anglebug.com/42263134: All blocks should be active const gl::ShaderBitSet activeShaders = programExecutable.getLinkedShaderStages() & block.activeShaders(); if (activeShaders.none()) { continue; } const bool isIndexZero = !block.pod.isArray || block.pod.arrayElement == 0; if (!isIndexZero) { continue; } variableInfoMapOut->addResource(activeShaders, block.getIds(), ToUnderlying(DescriptorSetIndex::ShaderResource), programInterfaceInfo->currentShaderResourceBindingIndex++); } } void AssignAtomicCounterBufferBindings(const SpvSourceOptions &options, const gl::ProgramExecutable &programExecutable, SpvProgramInterfaceInfo *programInterfaceInfo, ShaderInterfaceVariableInfoMap *variableInfoMapOut) { const std::vector &buffers = programExecutable.getAtomicCounterBuffers(); if (buffers.size() == 0) { return; } const gl::ShaderBitSet activeShaders = programExecutable.getLinkedShaderStages(); ASSERT(activeShaders.any()); gl::ShaderMap ids = {}; for (const gl::ShaderType shaderType : activeShaders) { ids[shaderType] = sh::vk::spirv::kIdAtomicCounterBlock; } variableInfoMapOut->addResource(activeShaders, ids, ToUnderlying(DescriptorSetIndex::ShaderResource), programInterfaceInfo->currentShaderResourceBindingIndex++); } void AssignImageBindings(const SpvSourceOptions &options, const gl::ProgramExecutable &programExecutable, SpvProgramInterfaceInfo *programInterfaceInfo, ShaderInterfaceVariableInfoMap *variableInfoMapOut) { const std::vector &uniforms = programExecutable.getUniforms(); const gl::RangeUI &imageUniformRange = programExecutable.getImageUniformRange(); for (unsigned int uniformIndex : imageUniformRange) { const gl::LinkedUniform &imageUniform = uniforms[uniformIndex]; // TODO: http://anglebug.com/42263134: All uniforms should be active const gl::ShaderBitSet activeShaders = programExecutable.getLinkedShaderStages() & imageUniform.activeShaders(); if (activeShaders.none()) { continue; } const bool isIndexZero = UniformNameIsIndexZero(programExecutable.getUniformNameByIndex(uniformIndex)); if (!isIndexZero) { continue; } variableInfoMapOut->addResource(activeShaders, imageUniform.getIds(), ToUnderlying(DescriptorSetIndex::ShaderResource), programInterfaceInfo->currentShaderResourceBindingIndex++); } } void AssignNonTextureBindings(const SpvSourceOptions &options, const gl::ProgramExecutable &programExecutable, SpvProgramInterfaceInfo *programInterfaceInfo, ShaderInterfaceVariableInfoMap *variableInfoMapOut) { AssignInputAttachmentBindings(options, programExecutable, programInterfaceInfo, variableInfoMapOut); const std::vector &uniformBlocks = programExecutable.getUniformBlocks(); AssignInterfaceBlockBindings(options, programExecutable, uniformBlocks, programInterfaceInfo, variableInfoMapOut); const std::vector &storageBlocks = programExecutable.getShaderStorageBlocks(); AssignInterfaceBlockBindings(options, programExecutable, storageBlocks, programInterfaceInfo, variableInfoMapOut); AssignAtomicCounterBufferBindings(options, programExecutable, programInterfaceInfo, variableInfoMapOut); AssignImageBindings(options, programExecutable, programInterfaceInfo, variableInfoMapOut); } void AssignTextureBindings(const SpvSourceOptions &options, const gl::ProgramExecutable &programExecutable, SpvProgramInterfaceInfo *programInterfaceInfo, ShaderInterfaceVariableInfoMap *variableInfoMapOut) { // Assign textures to a descriptor set and binding. const std::vector &uniforms = programExecutable.getUniforms(); const gl::RangeUI &samplerUniformRange = programExecutable.getSamplerUniformRange(); for (unsigned int uniformIndex : samplerUniformRange) { const gl::LinkedUniform &samplerUniform = uniforms[uniformIndex]; // TODO: http://anglebug.com/42263134: All uniforms should be active const gl::ShaderBitSet activeShaders = programExecutable.getLinkedShaderStages() & samplerUniform.activeShaders(); if (activeShaders.none()) { continue; } const bool isIndexZero = UniformNameIsIndexZero(programExecutable.getUniformNameByIndex(uniformIndex)); if (!isIndexZero) { continue; } variableInfoMapOut->addResource(activeShaders, samplerUniform.getIds(), ToUnderlying(DescriptorSetIndex::Texture), programInterfaceInfo->currentTextureBindingIndex++); } } bool IsNonSemanticInstruction(const uint32_t *instruction) { // To avoid parsing the numerous GLSL OpExtInst instructions, take a quick peek at the set and // skip instructions that aren't non-semantic. return instruction[3] == sh::vk::spirv::kIdNonSemanticInstructionSet; } enum class EntryPointList { // Prior to SPIR-V 1.4, only the Input and Output variables are listed in OpEntryPoint. InterfaceVariables, // Since SPIR-V 1.4, all global variables must be listed in OpEntryPoint. GlobalVariables, }; // Base class for SPIR-V transformations. class SpirvTransformerBase : angle::NonCopyable { public: SpirvTransformerBase(const spirv::Blob &spirvBlobIn, const ShaderInterfaceVariableInfoMap &variableInfoMap, spirv::Blob *spirvBlobOut) : mSpirvBlobIn(spirvBlobIn), mVariableInfoMap(variableInfoMap), mSpirvBlobOut(spirvBlobOut) { gl::ShaderBitSet allStages; allStages.set(); mBuiltinVariableInfo.activeStages = allStages; } std::vector &getVariableInfoByIdMap() { return mVariableInfoById; } static spirv::IdRef GetNewId(spirv::Blob *blob); spirv::IdRef getNewId(); EntryPointList entryPointList() const { return mEntryPointList; } spv::StorageClass storageBufferStorageClass() const { return mStorageBufferStorageClass; } protected: // Common utilities void onTransformBegin(); const uint32_t *getCurrentInstruction(spv::Op *opCodeOut, uint32_t *wordCountOut) const; void copyInstruction(const uint32_t *instruction, size_t wordCount); // SPIR-V to transform: const spirv::Blob &mSpirvBlobIn; // Input shader variable info map: const ShaderInterfaceVariableInfoMap &mVariableInfoMap; // Transformed SPIR-V: spirv::Blob *mSpirvBlobOut; // Traversal state: size_t mCurrentWord = 0; bool mIsInFunctionSection = false; // Transformation state: // Required behavior based on SPIR-V version. EntryPointList mEntryPointList = EntryPointList::InterfaceVariables; spv::StorageClass mStorageBufferStorageClass = spv::StorageClassUniform; // Shader variable info per id, if id is a shader variable. std::vector mVariableInfoById; ShaderInterfaceVariableInfo mBuiltinVariableInfo; }; void SpirvTransformerBase::onTransformBegin() { // The translator succeeded in outputting SPIR-V, so we assume it's valid. ASSERT(mSpirvBlobIn.size() >= spirv::kHeaderIndexInstructions); // Since SPIR-V comes from a local call to the translator, it necessarily has the same // endianness as the running architecture, so no byte-swapping is necessary. ASSERT(mSpirvBlobIn[spirv::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 spirv::Blob is not reused. ASSERT(mSpirvBlobOut->empty()); // Copy the header to SPIR-V blob, we need that to be defined for SpirvTransformerBase::getNewId // to work. mSpirvBlobOut->assign(mSpirvBlobIn.begin(), mSpirvBlobIn.begin() + spirv::kHeaderIndexInstructions); mCurrentWord = spirv::kHeaderIndexInstructions; if (mSpirvBlobIn[spirv::kHeaderIndexVersion] >= spirv::kVersion_1_4) { mEntryPointList = EntryPointList::GlobalVariables; mStorageBufferStorageClass = spv::StorageClassStorageBuffer; } } const uint32_t *SpirvTransformerBase::getCurrentInstruction(spv::Op *opCodeOut, uint32_t *wordCountOut) const { ASSERT(mCurrentWord < mSpirvBlobIn.size()); const uint32_t *instruction = &mSpirvBlobIn[mCurrentWord]; spirv::GetInstructionOpAndLength(instruction, opCodeOut, wordCountOut); // The translator succeeded in outputting SPIR-V, so we assume it's valid. ASSERT(mCurrentWord + *wordCountOut <= mSpirvBlobIn.size()); return instruction; } void SpirvTransformerBase::copyInstruction(const uint32_t *instruction, size_t wordCount) { mSpirvBlobOut->insert(mSpirvBlobOut->end(), instruction, instruction + wordCount); } spirv::IdRef SpirvTransformerBase::GetNewId(spirv::Blob *blob) { return spirv::IdRef((*blob)[spirv::kHeaderIndexIndexBound]++); } spirv::IdRef SpirvTransformerBase::getNewId() { return GetNewId(mSpirvBlobOut); } enum class TransformationState { Transformed, Unchanged, }; class SpirvNonSemanticInstructions final : angle::NonCopyable { public: SpirvNonSemanticInstructions(bool isLastPass) : mIsLastPass(isLastPass) {} // Returns whether this is a non-semantic instruction (as opposed to GLSL extended // instructions). If it is non-semantic, returns the instruction code. bool visitExtInst(const uint32_t *instruction, sh::vk::spirv::NonSemanticInstruction *instOut); // Cleans up non-semantic instructions in the last SPIR-V pass. TransformationState transformExtInst(const uint32_t *instruction); bool hasSampleRateShading() const { return (mOverviewFlags & sh::vk::spirv::kOverviewHasSampleRateShadingMask) != 0; } bool hasSampleID() const { return (mOverviewFlags & sh::vk::spirv::kOverviewHasSampleIDMask) != 0; } bool hasOutputPerVertex() const { return (mOverviewFlags & sh::vk::spirv::kOverviewHasOutputPerVertexMask) != 0; } private: // Whether this is the last SPIR-V pass. The non-semantics instructions are removed from the // SPIR-V in the last pass. const bool mIsLastPass; uint32_t mOverviewFlags; }; bool SpirvNonSemanticInstructions::visitExtInst(const uint32_t *instruction, sh::vk::spirv::NonSemanticInstruction *instOut) { if (!IsNonSemanticInstruction(instruction)) { return false; } spirv::IdResultType typeId; spirv::IdResult id; spirv::IdRef set; spirv::LiteralExtInstInteger extInst; spirv::ParseExtInst(instruction, &typeId, &id, &set, &extInst, nullptr); ASSERT(set == sh::vk::spirv::kIdNonSemanticInstructionSet); const uint32_t inst = extInst & sh::vk::spirv::kNonSemanticInstructionMask; // Recover the additional overview flags placed in the instruction id. if (inst == sh::vk::spirv::kNonSemanticOverview) { mOverviewFlags = extInst & ~sh::vk::spirv::kNonSemanticInstructionMask; } *instOut = static_cast(inst); return true; } TransformationState SpirvNonSemanticInstructions::transformExtInst(const uint32_t *instruction) { return IsNonSemanticInstruction(instruction) && mIsLastPass ? TransformationState::Transformed : TransformationState::Unchanged; } namespace ID { namespace { [[maybe_unused]] constexpr spirv::IdRef EntryPoint(sh::vk::spirv::kIdEntryPoint); [[maybe_unused]] constexpr spirv::IdRef Void(sh::vk::spirv::kIdVoid); [[maybe_unused]] constexpr spirv::IdRef Float(sh::vk::spirv::kIdFloat); [[maybe_unused]] constexpr spirv::IdRef Vec2(sh::vk::spirv::kIdVec2); [[maybe_unused]] constexpr spirv::IdRef Vec3(sh::vk::spirv::kIdVec3); [[maybe_unused]] constexpr spirv::IdRef Vec4(sh::vk::spirv::kIdVec4); [[maybe_unused]] constexpr spirv::IdRef Mat2(sh::vk::spirv::kIdMat2); [[maybe_unused]] constexpr spirv::IdRef Mat3(sh::vk::spirv::kIdMat3); [[maybe_unused]] constexpr spirv::IdRef Mat4(sh::vk::spirv::kIdMat4); [[maybe_unused]] constexpr spirv::IdRef Int(sh::vk::spirv::kIdInt); [[maybe_unused]] constexpr spirv::IdRef IVec4(sh::vk::spirv::kIdIVec4); [[maybe_unused]] constexpr spirv::IdRef Uint(sh::vk::spirv::kIdUint); [[maybe_unused]] constexpr spirv::IdRef IntZero(sh::vk::spirv::kIdIntZero); [[maybe_unused]] constexpr spirv::IdRef IntOne(sh::vk::spirv::kIdIntOne); [[maybe_unused]] constexpr spirv::IdRef IntTwo(sh::vk::spirv::kIdIntTwo); [[maybe_unused]] constexpr spirv::IdRef IntThree(sh::vk::spirv::kIdIntThree); [[maybe_unused]] constexpr spirv::IdRef IntInputTypePointer(sh::vk::spirv::kIdIntInputTypePointer); [[maybe_unused]] constexpr spirv::IdRef Vec4OutputTypePointer( sh::vk::spirv::kIdVec4OutputTypePointer); [[maybe_unused]] constexpr spirv::IdRef IVec4FunctionTypePointer( sh::vk::spirv::kIdIVec4FunctionTypePointer); [[maybe_unused]] constexpr spirv::IdRef OutputPerVertexTypePointer( sh::vk::spirv::kIdOutputPerVertexTypePointer); [[maybe_unused]] constexpr spirv::IdRef TransformPositionFunction( sh::vk::spirv::kIdTransformPositionFunction); [[maybe_unused]] constexpr spirv::IdRef XfbEmulationGetOffsetsFunction( sh::vk::spirv::kIdXfbEmulationGetOffsetsFunction); [[maybe_unused]] constexpr spirv::IdRef SampleID(sh::vk::spirv::kIdSampleID); [[maybe_unused]] constexpr spirv::IdRef InputPerVertexBlock(sh::vk::spirv::kIdInputPerVertexBlock); [[maybe_unused]] constexpr spirv::IdRef OutputPerVertexBlock( sh::vk::spirv::kIdOutputPerVertexBlock); [[maybe_unused]] constexpr spirv::IdRef OutputPerVertexVar(sh::vk::spirv::kIdOutputPerVertexVar); [[maybe_unused]] constexpr spirv::IdRef XfbExtensionPosition( sh::vk::spirv::kIdXfbExtensionPosition); [[maybe_unused]] constexpr spirv::IdRef XfbEmulationBufferBlockZero( sh::vk::spirv::kIdXfbEmulationBufferBlockZero); [[maybe_unused]] constexpr spirv::IdRef XfbEmulationBufferBlockOne( sh::vk::spirv::kIdXfbEmulationBufferBlockOne); [[maybe_unused]] constexpr spirv::IdRef XfbEmulationBufferBlockTwo( sh::vk::spirv::kIdXfbEmulationBufferBlockTwo); [[maybe_unused]] constexpr spirv::IdRef XfbEmulationBufferBlockThree( sh::vk::spirv::kIdXfbEmulationBufferBlockThree); } // anonymous namespace } // namespace ID // Helper class that trims input and output gl_PerVertex declarations to remove inactive builtins. // // gl_PerVertex is unique in that it's the only builtin of struct type. This struct is pruned // by removing trailing inactive members. Note that intermediate stages, i.e. geometry and // tessellation have two gl_PerVertex declarations, one for input and one for output. class SpirvPerVertexTrimmer final : angle::NonCopyable { public: SpirvPerVertexTrimmer(const SpvTransformOptions &options, const ShaderInterfaceVariableInfoMap &variableInfoMap) : mInputPerVertexMaxActiveMember{gl::PerVertexMember::Position}, mOutputPerVertexMaxActiveMember{gl::PerVertexMember::Position}, mInputPerVertexMaxActiveMemberIndex(0), mOutputPerVertexMaxActiveMemberIndex(0) { const gl::PerVertexMemberBitSet inputPerVertexActiveMembers = variableInfoMap.getInputPerVertexActiveMembers()[options.shaderType]; const gl::PerVertexMemberBitSet outputPerVertexActiveMembers = variableInfoMap.getOutputPerVertexActiveMembers()[options.shaderType]; // Currently, this transformation does not trim inactive members in between two active // members. if (inputPerVertexActiveMembers.any()) { mInputPerVertexMaxActiveMember = inputPerVertexActiveMembers.last(); } if (outputPerVertexActiveMembers.any()) { mOutputPerVertexMaxActiveMember = outputPerVertexActiveMembers.last(); } } void visitMemberDecorate(spirv::IdRef id, spirv::LiteralInteger member, spv::Decoration decoration, const spirv::LiteralIntegerList &valueList); TransformationState transformMemberDecorate(spirv::IdRef typeId, spirv::LiteralInteger member, spv::Decoration decoration); TransformationState transformMemberName(spirv::IdRef id, spirv::LiteralInteger member, const spirv::LiteralString &name); TransformationState transformTypeStruct(spirv::IdResult id, spirv::IdRefList *memberList, spirv::Blob *blobOut); private: bool isPerVertex(spirv::IdRef typeId) const { return typeId == ID::OutputPerVertexBlock || typeId == ID::InputPerVertexBlock; } uint32_t getPerVertexMaxActiveMember(spirv::IdRef typeId) const { ASSERT(isPerVertex(typeId)); return typeId == ID::OutputPerVertexBlock ? mOutputPerVertexMaxActiveMemberIndex : mInputPerVertexMaxActiveMemberIndex; } gl::PerVertexMember mInputPerVertexMaxActiveMember; gl::PerVertexMember mOutputPerVertexMaxActiveMember; // If gl_ClipDistance and gl_CullDistance are not used, they are missing from gl_PerVertex. So // the index of gl_CullDistance may not be the same as the value of // gl::PerVertexMember::CullDistance. // // By looking at OpMemberDecorate %kIdInput/OutputPerVertexBlock BuiltIn , the // corresponding to mInput/OutputPerVertexMaxActiveMember is discovered and kept in // mInput/OutputPerVertexMaxActiveMemberIndex uint32_t mInputPerVertexMaxActiveMemberIndex; uint32_t mOutputPerVertexMaxActiveMemberIndex; }; void SpirvPerVertexTrimmer::visitMemberDecorate(spirv::IdRef id, spirv::LiteralInteger member, spv::Decoration decoration, const spirv::LiteralIntegerList &valueList) { if (decoration != spv::DecorationBuiltIn || !isPerVertex(id)) { return; } // Map spv::BuiltIn to gl::PerVertexMember. ASSERT(!valueList.empty()); const uint32_t builtIn = valueList[0]; gl::PerVertexMember perVertexMember = gl::PerVertexMember::Position; switch (builtIn) { case spv::BuiltInPosition: perVertexMember = gl::PerVertexMember::Position; break; case spv::BuiltInPointSize: perVertexMember = gl::PerVertexMember::PointSize; break; case spv::BuiltInClipDistance: perVertexMember = gl::PerVertexMember::ClipDistance; break; case spv::BuiltInCullDistance: perVertexMember = gl::PerVertexMember::CullDistance; break; default: UNREACHABLE(); } if (id == ID::OutputPerVertexBlock && perVertexMember == mOutputPerVertexMaxActiveMember) { mOutputPerVertexMaxActiveMemberIndex = member; } else if (id == ID::InputPerVertexBlock && perVertexMember == mInputPerVertexMaxActiveMember) { mInputPerVertexMaxActiveMemberIndex = member; } } TransformationState SpirvPerVertexTrimmer::transformMemberDecorate(spirv::IdRef typeId, spirv::LiteralInteger member, spv::Decoration decoration) { // Transform the following: // // - OpMemberDecorate %gl_PerVertex N BuiltIn B // - OpMemberDecorate %gl_PerVertex N Invariant // - OpMemberDecorate %gl_PerVertex N RelaxedPrecision if (!isPerVertex(typeId) || (decoration != spv::DecorationBuiltIn && decoration != spv::DecorationInvariant && decoration != spv::DecorationRelaxedPrecision)) { return TransformationState::Unchanged; } // Drop stripped fields. return member > getPerVertexMaxActiveMember(typeId) ? TransformationState::Transformed : TransformationState::Unchanged; } TransformationState SpirvPerVertexTrimmer::transformMemberName(spirv::IdRef id, spirv::LiteralInteger member, const spirv::LiteralString &name) { // Remove the instruction if it's a stripped member of gl_PerVertex. return isPerVertex(id) && member > getPerVertexMaxActiveMember(id) ? TransformationState::Transformed : TransformationState::Unchanged; } TransformationState SpirvPerVertexTrimmer::transformTypeStruct(spirv::IdResult id, spirv::IdRefList *memberList, spirv::Blob *blobOut) { if (!isPerVertex(id)) { return TransformationState::Unchanged; } const uint32_t maxMembers = getPerVertexMaxActiveMember(id); // Change the definition of the gl_PerVertex struct by stripping unused fields at the end. const uint32_t memberCount = maxMembers + 1; memberList->resize_down(memberCount); spirv::WriteTypeStruct(blobOut, id, *memberList); return TransformationState::Transformed; } // Helper class that removes inactive varyings and replaces them with Private variables. class SpirvInactiveVaryingRemover final : angle::NonCopyable { public: SpirvInactiveVaryingRemover() {} void init(size_t indexCount); TransformationState transformAccessChain(spirv::IdResultType typeId, spirv::IdResult id, spirv::IdRef baseId, const spirv::IdRefList &indexList, spirv::Blob *blobOut); TransformationState transformDecorate(const ShaderInterfaceVariableInfo &info, gl::ShaderType shaderType, spirv::IdRef id, spv::Decoration decoration, const spirv::LiteralIntegerList &decorationValues, spirv::Blob *blobOut); TransformationState transformTypePointer(spirv::IdResult id, spv::StorageClass storageClass, spirv::IdRef typeId, spirv::Blob *blobOut); TransformationState transformVariable(spirv::IdResultType typeId, spirv::IdResult id, spv::StorageClass storageClass, spirv::Blob *blobOut); void modifyEntryPointInterfaceList( const std::vector &variableInfoById, gl::ShaderType shaderType, EntryPointList entryPointList, spirv::IdRefList *interfaceList); bool isInactive(spirv::IdRef id) const { return mIsInactiveById[id]; } spirv::IdRef getTransformedPrivateType(spirv::IdRef id) const { ASSERT(id < mTypePointerTransformedId.size()); return mTypePointerTransformedId[id]; } private: // 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; // Whether a variable has been marked inactive. std::vector mIsInactiveById; }; void SpirvInactiveVaryingRemover::init(size_t indexBound) { // 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); mIsInactiveById.resize(indexBound, false); } TransformationState SpirvInactiveVaryingRemover::transformAccessChain( spirv::IdResultType typeId, spirv::IdResult id, spirv::IdRef baseId, const spirv::IdRefList &indexList, spirv::Blob *blobOut) { // Modifiy the instruction to use the private type. ASSERT(typeId < mTypePointerTransformedId.size()); ASSERT(mTypePointerTransformedId[typeId].valid()); spirv::WriteAccessChain(blobOut, mTypePointerTransformedId[typeId], id, baseId, indexList); return TransformationState::Transformed; } TransformationState SpirvInactiveVaryingRemover::transformDecorate( const ShaderInterfaceVariableInfo &info, gl::ShaderType shaderType, spirv::IdRef id, spv::Decoration decoration, const spirv::LiteralIntegerList &decorationValues, spirv::Blob *blobOut) { // If it's an inactive varying, remove the decoration altogether. return info.activeStages[shaderType] ? TransformationState::Unchanged : TransformationState::Transformed; } void SpirvInactiveVaryingRemover::modifyEntryPointInterfaceList( const std::vector &variableInfoById, gl::ShaderType shaderType, EntryPointList entryPointList, spirv::IdRefList *interfaceList) { // Nothing to do if SPIR-V 1.4, each inactive variable is replaced with a Private varaible, but // its ID is retained and stays in the variable list. if (entryPointList == EntryPointList::GlobalVariables) { return; } // Filter out inactive varyings from entry point interface declaration. size_t writeIndex = 0; for (size_t index = 0; index < interfaceList->size(); ++index) { spirv::IdRef id((*interfaceList)[index]); const ShaderInterfaceVariableInfo *info = variableInfoById[id]; ASSERT(info); if (!info->activeStages[shaderType]) { continue; } (*interfaceList)[writeIndex] = id; ++writeIndex; } // Update the number of interface variables. interfaceList->resize_down(writeIndex); } TransformationState SpirvInactiveVaryingRemover::transformTypePointer( spirv::IdResult id, spv::StorageClass storageClass, spirv::IdRef typeId, spirv::Blob *blobOut) { // 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. // // 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 simplicity, duplicate // every "OpTypePointer Output" and "OpTypePointer Input" instruction except with the Private // storage class, in case it may be necessary later. // Cannot create a Private type declaration from builtins such as gl_PerVertex. if (typeId == sh::vk::spirv::kIdInputPerVertexBlock || typeId == sh::vk::spirv::kIdOutputPerVertexBlock || typeId == sh::vk::spirv::kIdInputPerVertexBlockArray || typeId == sh::vk::spirv::kIdOutputPerVertexBlockArray) { return TransformationState::Unchanged; } if (storageClass != spv::StorageClassOutput && storageClass != spv::StorageClassInput) { return TransformationState::Unchanged; } const spirv::IdRef newPrivateTypeId(SpirvTransformerBase::GetNewId(blobOut)); // Write OpTypePointer for the new PrivateType. spirv::WriteTypePointer(blobOut, newPrivateTypeId, spv::StorageClassPrivate, typeId); // Remember the id of the replacement. ASSERT(id < mTypePointerTransformedId.size()); mTypePointerTransformedId[id] = newPrivateTypeId; // The original instruction should still be present as well. At this point, we don't know // whether we will need the original or Private type. return TransformationState::Unchanged; } TransformationState SpirvInactiveVaryingRemover::transformVariable(spirv::IdResultType typeId, spirv::IdResult id, spv::StorageClass storageClass, spirv::Blob *blobOut) { ASSERT(storageClass == spv::StorageClassOutput || storageClass == spv::StorageClassInput); ASSERT(typeId < mTypePointerTransformedId.size()); ASSERT(mTypePointerTransformedId[typeId].valid()); spirv::WriteVariable(blobOut, mTypePointerTransformedId[typeId], id, spv::StorageClassPrivate, nullptr); mIsInactiveById[id] = true; return TransformationState::Transformed; } // Helper class that fixes varying precisions so they match between shader stages. class SpirvVaryingPrecisionFixer final : angle::NonCopyable { public: SpirvVaryingPrecisionFixer() {} void init(size_t indexBound); void visitTypePointer(spirv::IdResult id, spv::StorageClass storageClass, spirv::IdRef typeId); void visitVariable(const ShaderInterfaceVariableInfo &info, gl::ShaderType shaderType, spirv::IdResultType typeId, spirv::IdResult id, spv::StorageClass storageClass, spirv::Blob *blobOut); TransformationState transformVariable(const ShaderInterfaceVariableInfo &info, spirv::IdResultType typeId, spirv::IdResult id, spv::StorageClass storageClass, spirv::Blob *blobOut); void modifyEntryPointInterfaceList(EntryPointList entryPointList, spirv::IdRefList *interfaceList); void addDecorate(spirv::IdRef replacedId, spirv::Blob *blobOut); void writeInputPreamble( const std::vector &variableInfoById, gl::ShaderType shaderType, spirv::Blob *blobOut); void writeOutputPrologue( const std::vector &variableInfoById, gl::ShaderType shaderType, spirv::Blob *blobOut); bool isReplaced(spirv::IdRef id) const { return mFixedVaryingId[id].valid(); } spirv::IdRef getReplacementId(spirv::IdRef id) const { return mFixedVaryingId[id].valid() ? mFixedVaryingId[id] : id; } private: std::vector mTypePointerTypeId; std::vector mFixedVaryingId; std::vector mFixedVaryingTypeId; }; void SpirvVaryingPrecisionFixer::init(size_t indexBound) { // Allocate storage for precision mismatch fix up. mTypePointerTypeId.resize(indexBound); mFixedVaryingId.resize(indexBound); mFixedVaryingTypeId.resize(indexBound); } void SpirvVaryingPrecisionFixer::visitTypePointer(spirv::IdResult id, spv::StorageClass storageClass, spirv::IdRef typeId) { mTypePointerTypeId[id] = typeId; } void SpirvVaryingPrecisionFixer::visitVariable(const ShaderInterfaceVariableInfo &info, gl::ShaderType shaderType, spirv::IdResultType typeId, spirv::IdResult id, spv::StorageClass storageClass, spirv::Blob *blobOut) { if (info.useRelaxedPrecision && info.activeStages[shaderType] && !mFixedVaryingId[id].valid()) { mFixedVaryingId[id] = SpirvTransformerBase::GetNewId(blobOut); mFixedVaryingTypeId[id] = typeId; } } TransformationState SpirvVaryingPrecisionFixer::transformVariable( const ShaderInterfaceVariableInfo &info, spirv::IdResultType typeId, spirv::IdResult id, spv::StorageClass storageClass, spirv::Blob *blobOut) { if (info.useRelaxedPrecision && (storageClass == spv::StorageClassOutput || storageClass == spv::StorageClassInput)) { // Change existing OpVariable to use fixedVaryingId ASSERT(mFixedVaryingId[id].valid()); spirv::WriteVariable(blobOut, typeId, mFixedVaryingId[id], storageClass, nullptr); return TransformationState::Transformed; } return TransformationState::Unchanged; } void SpirvVaryingPrecisionFixer::writeInputPreamble( const std::vector &variableInfoById, gl::ShaderType shaderType, spirv::Blob *blobOut) { if (shaderType == gl::ShaderType::Vertex || shaderType == gl::ShaderType::Compute) { return; } // Copy from corrected varyings to temp global variables with original precision. for (uint32_t idIndex = spirv::kMinValidId; idIndex < variableInfoById.size(); idIndex++) { const spirv::IdRef id(idIndex); const ShaderInterfaceVariableInfo *info = variableInfoById[id]; if (info && info->useRelaxedPrecision && info->activeStages[shaderType] && info->varyingIsInput) { // This is an input varying, need to cast the mediump value that came from // the previous stage into a highp value that the code wants to work with. ASSERT(mFixedVaryingTypeId[id].valid()); // Build OpLoad instruction to load the mediump value into a temporary const spirv::IdRef tempVar(SpirvTransformerBase::GetNewId(blobOut)); const spirv::IdRef tempVarType(mTypePointerTypeId[mFixedVaryingTypeId[id]]); ASSERT(tempVarType.valid()); spirv::WriteLoad(blobOut, tempVarType, tempVar, mFixedVaryingId[id], nullptr); // Build OpStore instruction to cast the mediump value to highp for use in // the function spirv::WriteStore(blobOut, id, tempVar, nullptr); } } } void SpirvVaryingPrecisionFixer::modifyEntryPointInterfaceList(EntryPointList entryPointList, spirv::IdRefList *interfaceList) { // With SPIR-V 1.3, modify interface list if any ID was replaced due to varying precision // mismatch. // // With SPIR-V 1.4, the original variables are changed to Private and should remain in the list. // The new variables should be added to the variable list. const size_t variableCount = interfaceList->size(); for (size_t index = 0; index < variableCount; ++index) { const spirv::IdRef id = (*interfaceList)[index]; const spirv::IdRef replacementId = getReplacementId(id); if (replacementId != id) { if (entryPointList == EntryPointList::InterfaceVariables) { (*interfaceList)[index] = replacementId; } else { interfaceList->push_back(replacementId); } } } } void SpirvVaryingPrecisionFixer::addDecorate(spirv::IdRef replacedId, spirv::Blob *blobOut) { spirv::WriteDecorate(blobOut, replacedId, spv::DecorationRelaxedPrecision, {}); } void SpirvVaryingPrecisionFixer::writeOutputPrologue( const std::vector &variableInfoById, gl::ShaderType shaderType, spirv::Blob *blobOut) { if (shaderType == gl::ShaderType::Fragment || shaderType == gl::ShaderType::Compute) { return; } // Copy from temp global variables with original precision to corrected varyings. for (uint32_t idIndex = spirv::kMinValidId; idIndex < variableInfoById.size(); idIndex++) { const spirv::IdRef id(idIndex); const ShaderInterfaceVariableInfo *info = variableInfoById[id]; if (info && info->useRelaxedPrecision && info->activeStages[shaderType] && info->varyingIsOutput) { ASSERT(mFixedVaryingTypeId[id].valid()); // Build OpLoad instruction to load the highp value into a temporary const spirv::IdRef tempVar(SpirvTransformerBase::GetNewId(blobOut)); const spirv::IdRef tempVarType(mTypePointerTypeId[mFixedVaryingTypeId[id]]); ASSERT(tempVarType.valid()); spirv::WriteLoad(blobOut, tempVarType, tempVar, id, nullptr); // Build OpStore instruction to cast the highp value to mediump for output spirv::WriteStore(blobOut, mFixedVaryingId[id], tempVar, nullptr); } } } // Helper class that generates code for transform feedback class SpirvTransformFeedbackCodeGenerator final : angle::NonCopyable { public: SpirvTransformFeedbackCodeGenerator(const SpvTransformOptions &options) : mIsEmulated(options.isTransformFeedbackEmulated), mHasTransformFeedbackOutput(false), mIsPositionCapturedByTransformFeedbackExtension(false) {} void visitVariable(const ShaderInterfaceVariableInfo &info, const XFBInterfaceVariableInfo &xfbInfo, gl::ShaderType shaderType, spirv::IdResultType typeId, spirv::IdResult id, spv::StorageClass storageClass); TransformationState transformCapability(spv::Capability capability, spirv::Blob *blobOut); TransformationState transformDecorate(const ShaderInterfaceVariableInfo *info, gl::ShaderType shaderType, spirv::IdRef id, spv::Decoration decoration, const spirv::LiteralIntegerList &decorationValues, spirv::Blob *blobOut); TransformationState transformMemberDecorate(const ShaderInterfaceVariableInfo *info, gl::ShaderType shaderType, spirv::IdRef id, spirv::LiteralInteger member, spv::Decoration decoration, spirv::Blob *blobOut); TransformationState transformName(spirv::IdRef id, spirv::LiteralString name); TransformationState transformMemberName(spirv::IdRef id, spirv::LiteralInteger member, spirv::LiteralString name); TransformationState transformTypeStruct(const ShaderInterfaceVariableInfo *info, gl::ShaderType shaderType, spirv::IdResult id, const spirv::IdRefList &memberList, spirv::Blob *blobOut); TransformationState transformTypePointer(const ShaderInterfaceVariableInfo *info, gl::ShaderType shaderType, spirv::IdResult id, spv::StorageClass storageClass, spirv::IdRef typeId, spirv::Blob *blobOut); TransformationState transformVariable(const ShaderInterfaceVariableInfo &info, const ShaderInterfaceVariableInfoMap &variableInfoMap, gl::ShaderType shaderType, spv::StorageClass storageBufferStorageClass, spirv::IdResultType typeId, spirv::IdResult id, spv::StorageClass storageClass); void modifyEntryPointInterfaceList( const std::vector &variableInfoById, gl::ShaderType shaderType, EntryPointList entryPointList, spirv::IdRefList *interfaceList); void writePendingDeclarations( const std::vector &variableInfoById, spv::StorageClass storageBufferStorageClass, spirv::Blob *blobOut); void writeTransformFeedbackExtensionOutput(spirv::IdRef positionId, spirv::Blob *blobOut); void writeTransformFeedbackEmulationOutput( const SpirvInactiveVaryingRemover &inactiveVaryingRemover, const SpirvVaryingPrecisionFixer &varyingPrecisionFixer, const bool usePrecisionFixer, spirv::Blob *blobOut); void addExecutionMode(spirv::IdRef entryPointId, spirv::Blob *blobOut); void addMemberDecorate(const XFBInterfaceVariableInfo &info, spirv::IdRef id, spirv::Blob *blobOut); void addDecorate(const XFBInterfaceVariableInfo &xfbInfo, spirv::IdRef id, spirv::Blob *blobOut); private: void gatherXfbVaryings(const XFBInterfaceVariableInfo &info, spirv::IdRef id); void visitXfbVarying(const ShaderInterfaceVariableXfbInfo &xfb, spirv::IdRef baseId, uint32_t fieldIndex); TransformationState transformTypeHelper(const ShaderInterfaceVariableInfo *info, gl::ShaderType shaderType, spirv::IdResult id); void writeIntConstant(uint32_t value, spirv::IdRef intId, spirv::Blob *blobOut); void getVaryingTypeIds(GLenum componentType, bool isPrivate, spirv::IdRef *typeIdOut, spirv::IdRef *typePtrOut); void writeGetOffsetsCall(spirv::IdRef xfbOffsets, spirv::Blob *blobOut); void writeComponentCapture(uint32_t bufferIndex, spirv::IdRef xfbOffset, spirv::IdRef varyingTypeId, spirv::IdRef varyingTypePtr, spirv::IdRef varyingBaseId, const spirv::IdRefList &accessChainIndices, GLenum componentType, spirv::Blob *blobOut); static constexpr size_t kXfbDecorationCount = 3; static constexpr spv::Decoration kXfbDecorations[kXfbDecorationCount] = { spv::DecorationXfbBuffer, spv::DecorationXfbStride, spv::DecorationOffset, }; bool mIsEmulated; bool mHasTransformFeedbackOutput; // Ids needed to generate transform feedback support code. bool mIsPositionCapturedByTransformFeedbackExtension; gl::TransformFeedbackBuffersArray mBufferStrides; spirv::IdRef mBufferStridesCompositeId; // Type and constant ids: // // - mFloatOutputPointerId: id of OpTypePointer Output %kIdFloat // - mIntOutputPointerId: id of OpTypePointer Output %kIdInt // - mUintOutputPointerId: id of OpTypePointer Output %kIdUint // - mFloatPrivatePointerId, mIntPrivatePointerId, mUintPrivatePointerId: identical to the // above, but with the Private storage class. Used to load from varyings that have been // replaced as part of precision mismatch fixup. // - mFloatUniformPointerId: id of OpTypePointer Uniform %kIdFloat // // - mIntNIds[n]: id of OpConstant %kIdInt n spirv::IdRef mFloatOutputPointerId; spirv::IdRef mIntOutputPointerId; spirv::IdRef mUintOutputPointerId; spirv::IdRef mFloatPrivatePointerId; spirv::IdRef mIntPrivatePointerId; spirv::IdRef mUintPrivatePointerId; spirv::IdRef mFloatUniformPointerId; // Id of constants such as row, column and array index. Integers 0, 1, 2 and 3 are always // defined by the compiler. angle::FastVector mIntNIds; // For transform feedback emulation, the captured elements are gathered in a list and sorted. // This allows the output generation code to always use offset += 1, thus relying on only one // constant (1). struct XfbVarying { // The varyings are sorted by info.offset. const ShaderInterfaceVariableXfbInfo *info; // Id of the base variable. spirv::IdRef baseId; // The field index, if a member of an I/O blocks uint32_t fieldIndex; }; gl::TransformFeedbackBuffersArray> mXfbVaryings; }; constexpr size_t SpirvTransformFeedbackCodeGenerator::kXfbDecorationCount; constexpr spv::Decoration SpirvTransformFeedbackCodeGenerator::kXfbDecorations[kXfbDecorationCount]; void SpirvTransformFeedbackCodeGenerator::visitVariable(const ShaderInterfaceVariableInfo &info, const XFBInterfaceVariableInfo &xfbInfo, gl::ShaderType shaderType, spirv::IdResultType typeId, spirv::IdResult id, spv::StorageClass storageClass) { if (mIsEmulated) { gatherXfbVaryings(xfbInfo, id); return; } // Note if the variable is captured by transform feedback. In that case, the TransformFeedback // capability needs to be added. if ((xfbInfo.xfb.pod.buffer != ShaderInterfaceVariableInfo::kInvalid || !xfbInfo.fieldXfb.empty()) && info.activeStages[shaderType]) { mHasTransformFeedbackOutput = true; // If this is the special ANGLEXfbPosition variable, remember its id to be used for the // ANGLEXfbPosition = gl_Position; assignment code generation. if (id == ID::XfbExtensionPosition) { mIsPositionCapturedByTransformFeedbackExtension = true; } } } TransformationState SpirvTransformFeedbackCodeGenerator::transformCapability( spv::Capability capability, spirv::Blob *blobOut) { if (!mHasTransformFeedbackOutput || mIsEmulated) { return TransformationState::Unchanged; } // 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 right before it. if (capability != spv::CapabilityShader && capability != spv::CapabilityGeometry && capability != spv::CapabilityTessellation) { return TransformationState::Unchanged; } // Write the TransformFeedback capability declaration. spirv::WriteCapability(blobOut, spv::CapabilityTransformFeedback); // The original capability is retained. return TransformationState::Unchanged; } TransformationState SpirvTransformFeedbackCodeGenerator::transformName(spirv::IdRef id, spirv::LiteralString name) { // In the case of ANGLEXfbN, unconditionally remove the variable names. If transform // feedback is not active, the corresponding variables will be removed. switch (id) { case sh::vk::spirv::kIdXfbEmulationBufferBlockZero: case sh::vk::spirv::kIdXfbEmulationBufferBlockOne: case sh::vk::spirv::kIdXfbEmulationBufferBlockTwo: case sh::vk::spirv::kIdXfbEmulationBufferBlockThree: case sh::vk::spirv::kIdXfbEmulationBufferVarZero: case sh::vk::spirv::kIdXfbEmulationBufferVarOne: case sh::vk::spirv::kIdXfbEmulationBufferVarTwo: case sh::vk::spirv::kIdXfbEmulationBufferVarThree: return TransformationState::Transformed; default: return TransformationState::Unchanged; } } TransformationState SpirvTransformFeedbackCodeGenerator::transformMemberName( spirv::IdRef id, spirv::LiteralInteger member, spirv::LiteralString name) { switch (id) { case sh::vk::spirv::kIdXfbEmulationBufferBlockZero: case sh::vk::spirv::kIdXfbEmulationBufferBlockOne: case sh::vk::spirv::kIdXfbEmulationBufferBlockTwo: case sh::vk::spirv::kIdXfbEmulationBufferBlockThree: return TransformationState::Transformed; default: return TransformationState::Unchanged; } } TransformationState SpirvTransformFeedbackCodeGenerator::transformTypeHelper( const ShaderInterfaceVariableInfo *info, gl::ShaderType shaderType, spirv::IdResult id) { switch (id) { case sh::vk::spirv::kIdXfbEmulationBufferBlockZero: case sh::vk::spirv::kIdXfbEmulationBufferBlockOne: case sh::vk::spirv::kIdXfbEmulationBufferBlockTwo: case sh::vk::spirv::kIdXfbEmulationBufferBlockThree: ASSERT(info); return info->activeStages[shaderType] ? TransformationState::Unchanged : TransformationState::Transformed; default: return TransformationState::Unchanged; } } TransformationState SpirvTransformFeedbackCodeGenerator::transformDecorate( const ShaderInterfaceVariableInfo *info, gl::ShaderType shaderType, spirv::IdRef id, spv::Decoration decoration, const spirv::LiteralIntegerList &decorationValues, spirv::Blob *blobOut) { return transformTypeHelper(info, shaderType, id); } TransformationState SpirvTransformFeedbackCodeGenerator::transformMemberDecorate( const ShaderInterfaceVariableInfo *info, gl::ShaderType shaderType, spirv::IdRef id, spirv::LiteralInteger member, spv::Decoration decoration, spirv::Blob *blobOut) { return transformTypeHelper(info, shaderType, id); } TransformationState SpirvTransformFeedbackCodeGenerator::transformTypeStruct( const ShaderInterfaceVariableInfo *info, gl::ShaderType shaderType, spirv::IdResult id, const spirv::IdRefList &memberList, spirv::Blob *blobOut) { return transformTypeHelper(info, shaderType, id); } TransformationState SpirvTransformFeedbackCodeGenerator::transformTypePointer( const ShaderInterfaceVariableInfo *info, gl::ShaderType shaderType, spirv::IdResult id, spv::StorageClass storageClass, spirv::IdRef typeId, spirv::Blob *blobOut) { return transformTypeHelper(info, shaderType, typeId); } TransformationState SpirvTransformFeedbackCodeGenerator::transformVariable( const ShaderInterfaceVariableInfo &info, const ShaderInterfaceVariableInfoMap &variableInfoMap, gl::ShaderType shaderType, spv::StorageClass storageBufferStorageClass, spirv::IdResultType typeId, spirv::IdResult id, spv::StorageClass storageClass) { // This function is currently called for inactive variables. ASSERT(!info.activeStages[shaderType]); if (shaderType == gl::ShaderType::Vertex && storageClass == storageBufferStorageClass) { // The ANGLEXfbN variables are unconditionally generated and may be inactive. Remove these // variables in that case. ASSERT(&info == &variableInfoMap.getVariableById(shaderType, SpvGetXfbBufferBlockId(0)) || &info == &variableInfoMap.getVariableById(shaderType, SpvGetXfbBufferBlockId(1)) || &info == &variableInfoMap.getVariableById(shaderType, SpvGetXfbBufferBlockId(2)) || &info == &variableInfoMap.getVariableById(shaderType, SpvGetXfbBufferBlockId(3))); // Drop the declaration. return TransformationState::Transformed; } return TransformationState::Unchanged; } void SpirvTransformFeedbackCodeGenerator::gatherXfbVaryings(const XFBInterfaceVariableInfo &info, spirv::IdRef id) { visitXfbVarying(info.xfb, id, ShaderInterfaceVariableXfbInfo::kInvalid); for (size_t fieldIndex = 0; fieldIndex < info.fieldXfb.size(); ++fieldIndex) { visitXfbVarying(info.fieldXfb[fieldIndex], id, static_cast(fieldIndex)); } } void SpirvTransformFeedbackCodeGenerator::visitXfbVarying(const ShaderInterfaceVariableXfbInfo &xfb, spirv::IdRef baseId, uint32_t fieldIndex) { for (const ShaderInterfaceVariableXfbInfo &arrayElement : xfb.arrayElements) { visitXfbVarying(arrayElement, baseId, fieldIndex); } if (xfb.pod.buffer == ShaderInterfaceVariableXfbInfo::kInvalid) { return; } // Varyings captured to the same buffer have the same stride. ASSERT(mXfbVaryings[xfb.pod.buffer].empty() || mXfbVaryings[xfb.pod.buffer][0].info->pod.stride == xfb.pod.stride); mXfbVaryings[xfb.pod.buffer].push_back({&xfb, baseId, fieldIndex}); } void SpirvTransformFeedbackCodeGenerator::writeIntConstant(uint32_t value, spirv::IdRef intId, spirv::Blob *blobOut) { if (value == ShaderInterfaceVariableXfbInfo::kInvalid) { return; } if (mIntNIds.size() <= value) { // This member never resized down, so new elements can't have previous values. mIntNIds.resize_maybe_value_reuse(value + 1); } else if (mIntNIds[value].valid()) { return; } mIntNIds[value] = SpirvTransformerBase::GetNewId(blobOut); spirv::WriteConstant(blobOut, ID::Int, mIntNIds[value], spirv::LiteralContextDependentNumber(value)); } void SpirvTransformFeedbackCodeGenerator::modifyEntryPointInterfaceList( const std::vector &variableInfoById, gl::ShaderType shaderType, EntryPointList entryPointList, spirv::IdRefList *interfaceList) { if (entryPointList == EntryPointList::GlobalVariables) { // Filter out unused xfb blocks from entry point interface declaration. size_t writeIndex = 0; for (size_t index = 0; index < interfaceList->size(); ++index) { spirv::IdRef id((*interfaceList)[index]); if (SpvIsXfbBufferBlockId(id)) { const ShaderInterfaceVariableInfo *info = variableInfoById[id]; ASSERT(info); if (!info->activeStages[shaderType]) { continue; } } (*interfaceList)[writeIndex] = id; ++writeIndex; } // Update the number of interface variables. interfaceList->resize_down(writeIndex); } } void SpirvTransformFeedbackCodeGenerator::writePendingDeclarations( const std::vector &variableInfoById, spv::StorageClass storageBufferStorageClass, spirv::Blob *blobOut) { if (!mIsEmulated) { return; } mFloatOutputPointerId = SpirvTransformerBase::GetNewId(blobOut); mFloatPrivatePointerId = SpirvTransformerBase::GetNewId(blobOut); spirv::WriteTypePointer(blobOut, mFloatOutputPointerId, spv::StorageClassOutput, ID::Float); spirv::WriteTypePointer(blobOut, mFloatPrivatePointerId, spv::StorageClassPrivate, ID::Float); mIntOutputPointerId = SpirvTransformerBase::GetNewId(blobOut); mIntPrivatePointerId = SpirvTransformerBase::GetNewId(blobOut); spirv::WriteTypePointer(blobOut, mIntOutputPointerId, spv::StorageClassOutput, ID::Int); spirv::WriteTypePointer(blobOut, mIntPrivatePointerId, spv::StorageClassPrivate, ID::Int); mUintOutputPointerId = SpirvTransformerBase::GetNewId(blobOut); mUintPrivatePointerId = SpirvTransformerBase::GetNewId(blobOut); spirv::WriteTypePointer(blobOut, mUintOutputPointerId, spv::StorageClassOutput, ID::Uint); spirv::WriteTypePointer(blobOut, mUintPrivatePointerId, spv::StorageClassPrivate, ID::Uint); mFloatUniformPointerId = SpirvTransformerBase::GetNewId(blobOut); spirv::WriteTypePointer(blobOut, mFloatUniformPointerId, storageBufferStorageClass, ID::Float); ASSERT(mIntNIds.empty()); // All new elements initialized later after the resize. Additionally mIntNIds was always empty // before this resize, so previous value reuse is not possible. mIntNIds.resize_maybe_value_reuse(4); mIntNIds[0] = ID::IntZero; mIntNIds[1] = ID::IntOne; mIntNIds[2] = ID::IntTwo; mIntNIds[3] = ID::IntThree; spirv::IdRefList compositeIds; for (const std::vector &varyings : mXfbVaryings) { if (varyings.empty()) { compositeIds.push_back(ID::IntZero); continue; } const ShaderInterfaceVariableXfbInfo *info0 = varyings[0].info; // Define the buffer stride constant ASSERT(info0->pod.stride % sizeof(float) == 0); uint32_t stride = info0->pod.stride / sizeof(float); mBufferStrides[info0->pod.buffer] = SpirvTransformerBase::GetNewId(blobOut); spirv::WriteConstant(blobOut, ID::Int, mBufferStrides[info0->pod.buffer], spirv::LiteralContextDependentNumber(stride)); compositeIds.push_back(mBufferStrides[info0->pod.buffer]); // Define all the constants that would be necessary to load the components of the varying. for (const XfbVarying &varying : varyings) { writeIntConstant(varying.fieldIndex, ID::Int, blobOut); const ShaderInterfaceVariableXfbInfo *info = varying.info; if (info->pod.arraySize == ShaderInterfaceVariableXfbInfo::kInvalid) { continue; } uint32_t arrayIndexStart = varying.info->pod.arrayIndex != ShaderInterfaceVariableXfbInfo::kInvalid ? varying.info->pod.arrayIndex : 0; uint32_t arrayIndexEnd = arrayIndexStart + info->pod.arraySize; for (uint32_t arrayIndex = arrayIndexStart; arrayIndex < arrayIndexEnd; ++arrayIndex) { writeIntConstant(arrayIndex, ID::Int, blobOut); } } } mBufferStridesCompositeId = SpirvTransformerBase::GetNewId(blobOut); spirv::WriteConstantComposite(blobOut, ID::IVec4, mBufferStridesCompositeId, compositeIds); } void SpirvTransformFeedbackCodeGenerator::writeTransformFeedbackExtensionOutput( spirv::IdRef positionId, spirv::Blob *blobOut) { if (mIsEmulated) { return; } if (mIsPositionCapturedByTransformFeedbackExtension) { spirv::WriteStore(blobOut, ID::XfbExtensionPosition, positionId, nullptr); } } class AccessChainIndexListAppend final : angle::NonCopyable { public: AccessChainIndexListAppend(bool condition, angle::FastVector intNIds, uint32_t index, spirv::IdRefList *indexList) : mCondition(condition), mIndexList(indexList) { if (mCondition) { mIndexList->push_back(intNIds[index]); } } ~AccessChainIndexListAppend() { if (mCondition) { mIndexList->pop_back(); } } private: bool mCondition; spirv::IdRefList *mIndexList; }; void SpirvTransformFeedbackCodeGenerator::writeTransformFeedbackEmulationOutput( const SpirvInactiveVaryingRemover &inactiveVaryingRemover, const SpirvVaryingPrecisionFixer &varyingPrecisionFixer, const bool usePrecisionFixer, spirv::Blob *blobOut) { if (!mIsEmulated) { return; } // First, sort the varyings by offset, to simplify calculation of the output offset. for (std::vector &varyings : mXfbVaryings) { std::sort(varyings.begin(), varyings.end(), [](const XfbVarying &first, const XfbVarying &second) { return first.info->pod.offset < second.info->pod.offset; }); } // The following code is generated for transform feedback emulation: // // ivec4 xfbOffsets = ANGLEGetXfbOffsets(ivec4(stride0, stride1, stride2, stride3)); // // For buffer N: // int xfbOffset = xfbOffsets[N] // ANGLEXfbN.xfbOut[xfbOffset] = tfVarying0.field[index][row][col] // xfbOffset += 1; // ANGLEXfbN.xfbOut[xfbOffset] = tfVarying0.field[index][row][col + 1] // xfbOffset += 1; // ... // // The following pieces of SPIR-V code are generated according to the above: // // - For the initial offsets calculation: // // %xfbOffsetsResult = OpFunctionCall %ivec4 %ANGLEGetXfbOffsets %stridesComposite // %xfbOffsetsVar = OpVariable %kIdIVec4FunctionTypePointer Function // OpStore %xfbOffsetsVar %xfbOffsetsResult // %xfbOffsets = OpLoad %ivec4 %xfbOffsetsVar // // - Initial code for each buffer N: // // %xfbOffset = OpCompositeExtract %int %xfbOffsets N // // - For each varying being captured: // // // Load the component // %componentPtr = OpAccessChain %floatOutputPtr %baseId %field %arrayIndex %row %col // %component = OpLoad %float %componentPtr // // Store in xfb output // %xfbOutPtr = OpAccessChain %floatUniformPtr %xfbBufferN %int0 %xfbOffset // OpStore %xfbOutPtr %component // // Increment offset // %xfbOffset = OpIAdd %int %xfbOffset %int1 // // Note that if the varying being captured is integer, the first two instructions above would // use the intger equivalent types, and the following instruction would bitcast it to float // for storage: // // %asFloat = OpBitcast %float %component // spirv::IdRef xfbOffsets; if (!mXfbVaryings.empty()) { xfbOffsets = SpirvTransformerBase::GetNewId(blobOut); // ivec4 xfbOffsets = ANGLEGetXfbOffsets(ivec4(stride0, stride1, stride2, stride3)); writeGetOffsetsCall(xfbOffsets, blobOut); } // Go over the buffers one by one and capture the varyings. for (uint32_t bufferIndex = 0; bufferIndex < mXfbVaryings.size(); ++bufferIndex) { spirv::IdRef xfbOffset(SpirvTransformerBase::GetNewId(blobOut)); // Get the offset corresponding to this buffer: // // int xfbOffset = xfbOffsets[N] spirv::WriteCompositeExtract(blobOut, ID::Int, xfbOffset, xfbOffsets, {spirv::LiteralInteger(bufferIndex)}); // Track offsets for verification. uint32_t offsetForVerification = 0; // Go over the varyings of this buffer in order. const std::vector &varyings = mXfbVaryings[bufferIndex]; for (size_t varyingIndex = 0; varyingIndex < varyings.size(); ++varyingIndex) { const XfbVarying &varying = varyings[varyingIndex]; const ShaderInterfaceVariableXfbInfo *info = varying.info; ASSERT(info->pod.buffer == bufferIndex); // Each component of the varying being captured is loaded one by one. This uses the // OpAccessChain instruction that takes a chain of "indices" to end up with the // component starting from the base variable. For example: // // var.member[3][2][0] // // where member is field number 4 in var and is a mat4, the access chain would be: // // 4 3 2 0 // ^ ^ ^ ^ // | | | | // | | | row 0 // | | column 2 // | array element 3 // field 4 // // The following tracks the access chain as the field, array elements, columns and rows // are looped over. spirv::IdRefList indexList; AccessChainIndexListAppend appendField( varying.fieldIndex != ShaderInterfaceVariableXfbInfo::kInvalid, mIntNIds, varying.fieldIndex, &indexList); // The varying being captured is either: // // - Not an array: In this case, no entry is added in the access chain // - An element of the array // - The whole array // uint32_t arrayIndexStart = 0; uint32_t arrayIndexEnd = info->pod.arraySize; const bool isArray = info->pod.arraySize != ShaderInterfaceVariableXfbInfo::kInvalid; if (varying.info->pod.arrayIndex != ShaderInterfaceVariableXfbInfo::kInvalid) { // Capturing a single element. arrayIndexStart = varying.info->pod.arrayIndex; arrayIndexEnd = arrayIndexStart + 1; } else if (!isArray) { // Not an array. arrayIndexEnd = 1; } // Sorting the varyings should have ensured that offsets are in order and that writing // to the output buffer sequentially ends up using the correct offsets. ASSERT(info->pod.offset == offsetForVerification); offsetForVerification += (arrayIndexEnd - arrayIndexStart) * info->pod.rowCount * info->pod.columnCount * sizeof(float); // Determine the type of the component being captured. OpBitcast is used (the // implementation of intBitsToFloat() and uintBitsToFloat() for non-float types). spirv::IdRef varyingTypeId; spirv::IdRef varyingTypePtr; const bool isPrivate = inactiveVaryingRemover.isInactive(varying.baseId) || (usePrecisionFixer && varyingPrecisionFixer.isReplaced(varying.baseId)); getVaryingTypeIds(info->pod.componentType, isPrivate, &varyingTypeId, &varyingTypePtr); for (uint32_t arrayIndex = arrayIndexStart; arrayIndex < arrayIndexEnd; ++arrayIndex) { AccessChainIndexListAppend appendArrayIndex(isArray, mIntNIds, arrayIndex, &indexList); for (uint32_t col = 0; col < info->pod.columnCount; ++col) { AccessChainIndexListAppend appendColumn(info->pod.columnCount > 1, mIntNIds, col, &indexList); for (uint32_t row = 0; row < info->pod.rowCount; ++row) { AccessChainIndexListAppend appendRow(info->pod.rowCount > 1, mIntNIds, row, &indexList); // Generate the code to capture a single component of the varying: // // ANGLEXfbN.xfbOut[xfbOffset] = tfVarying0.field[index][row][col] writeComponentCapture(bufferIndex, xfbOffset, varyingTypeId, varyingTypePtr, varying.baseId, indexList, info->pod.componentType, blobOut); // Increment the offset: // // xfbOffset += 1; // // which translates to: // // %newOffsetId = OpIAdd %int %currentOffsetId %int1 spirv::IdRef nextOffset(SpirvTransformerBase::GetNewId(blobOut)); spirv::WriteIAdd(blobOut, ID::Int, nextOffset, xfbOffset, ID::IntOne); xfbOffset = nextOffset; } } } } } } void SpirvTransformFeedbackCodeGenerator::getVaryingTypeIds(GLenum componentType, bool isPrivate, spirv::IdRef *typeIdOut, spirv::IdRef *typePtrOut) { switch (componentType) { case GL_INT: *typeIdOut = ID::Int; *typePtrOut = isPrivate ? mIntPrivatePointerId : mIntOutputPointerId; break; case GL_UNSIGNED_INT: *typeIdOut = ID::Uint; *typePtrOut = isPrivate ? mUintPrivatePointerId : mUintOutputPointerId; break; case GL_FLOAT: *typeIdOut = ID::Float; *typePtrOut = isPrivate ? mFloatPrivatePointerId : mFloatOutputPointerId; break; default: UNREACHABLE(); } ASSERT(typeIdOut->valid()); ASSERT(typePtrOut->valid()); } void SpirvTransformFeedbackCodeGenerator::writeGetOffsetsCall(spirv::IdRef xfbOffsets, spirv::Blob *blobOut) { const spirv::IdRef xfbOffsetsResult(SpirvTransformerBase::GetNewId(blobOut)); const spirv::IdRef xfbOffsetsVar(SpirvTransformerBase::GetNewId(blobOut)); // Generate code for the following: // // ivec4 xfbOffsets = ANGLEGetXfbOffsets(ivec4(stride0, stride1, stride2, stride3)); // Create a variable to hold the result. spirv::WriteVariable(blobOut, ID::IVec4FunctionTypePointer, xfbOffsetsVar, spv::StorageClassFunction, nullptr); // Call a helper function generated by the translator to calculate the offsets for the current // vertex. spirv::WriteFunctionCall(blobOut, ID::IVec4, xfbOffsetsResult, ID::XfbEmulationGetOffsetsFunction, {mBufferStridesCompositeId}); // Store the results. spirv::WriteStore(blobOut, xfbOffsetsVar, xfbOffsetsResult, nullptr); // Load from the variable for use in expressions. spirv::WriteLoad(blobOut, ID::IVec4, xfbOffsets, xfbOffsetsVar, nullptr); } void SpirvTransformFeedbackCodeGenerator::writeComponentCapture( uint32_t bufferIndex, spirv::IdRef xfbOffset, spirv::IdRef varyingTypeId, spirv::IdRef varyingTypePtr, spirv::IdRef varyingBaseId, const spirv::IdRefList &accessChainIndices, GLenum componentType, spirv::Blob *blobOut) { spirv::IdRef component(SpirvTransformerBase::GetNewId(blobOut)); spirv::IdRef xfbOutPtr(SpirvTransformerBase::GetNewId(blobOut)); // Generate code for the following: // // ANGLEXfbN.xfbOut[xfbOffset] = tfVarying0.field[index][row][col] // Load from the component traversing the base variable with the given indices. If there are no // indices, the variable can be loaded directly. spirv::IdRef loadPtr = varyingBaseId; if (!accessChainIndices.empty()) { loadPtr = SpirvTransformerBase::GetNewId(blobOut); spirv::WriteAccessChain(blobOut, varyingTypePtr, loadPtr, varyingBaseId, accessChainIndices); } spirv::WriteLoad(blobOut, varyingTypeId, component, loadPtr, nullptr); // If the varying is int or uint, bitcast it to float to store in the float[] array used to // capture transform feedback output. spirv::IdRef asFloat = component; if (componentType != GL_FLOAT) { asFloat = SpirvTransformerBase::GetNewId(blobOut); spirv::WriteBitcast(blobOut, ID::Float, asFloat, component); } // Store into the transform feedback capture buffer at the current offset. Note that this // buffer has only one field (xfbOut), hence ANGLEXfbN.xfbOut[xfbOffset] translates to ANGLEXfbN // with access chain {0, xfbOffset}. static_assert(gl::IMPLEMENTATION_MAX_TRANSFORM_FEEDBACK_BUFFERS == 4); static_assert(sh::vk::spirv::kIdXfbEmulationBufferVarOne == sh::vk::spirv::kIdXfbEmulationBufferVarZero + 1); static_assert(sh::vk::spirv::kIdXfbEmulationBufferVarTwo == sh::vk::spirv::kIdXfbEmulationBufferVarZero + 2); static_assert(sh::vk::spirv::kIdXfbEmulationBufferVarThree == sh::vk::spirv::kIdXfbEmulationBufferVarZero + 3); spirv::WriteAccessChain(blobOut, mFloatUniformPointerId, xfbOutPtr, spirv::IdRef(sh::vk::spirv::kIdXfbEmulationBufferVarZero + bufferIndex), {ID::IntZero, xfbOffset}); spirv::WriteStore(blobOut, xfbOutPtr, asFloat, nullptr); } void SpirvTransformFeedbackCodeGenerator::addExecutionMode(spirv::IdRef entryPointId, spirv::Blob *blobOut) { if (mIsEmulated) { return; } if (mHasTransformFeedbackOutput) { spirv::WriteExecutionMode(blobOut, entryPointId, spv::ExecutionModeXfb, {}); } } void SpirvTransformFeedbackCodeGenerator::addMemberDecorate(const XFBInterfaceVariableInfo &info, spirv::IdRef id, spirv::Blob *blobOut) { if (mIsEmulated || info.fieldXfb.empty()) { return; } for (uint32_t fieldIndex = 0; fieldIndex < info.fieldXfb.size(); ++fieldIndex) { const ShaderInterfaceVariableXfbInfo &xfb = info.fieldXfb[fieldIndex]; if (xfb.pod.buffer == ShaderInterfaceVariableXfbInfo::kInvalid) { continue; } ASSERT(xfb.pod.stride != ShaderInterfaceVariableXfbInfo::kInvalid); ASSERT(xfb.pod.offset != ShaderInterfaceVariableXfbInfo::kInvalid); const uint32_t xfbDecorationValues[kXfbDecorationCount] = { xfb.pod.buffer, xfb.pod.stride, xfb.pod.offset, }; // Generate the following three instructions: // // OpMemberDecorate %id fieldIndex XfbBuffer xfb.buffer // OpMemberDecorate %id fieldIndex XfbStride xfb.stride // OpMemberDecorate %id fieldIndex Offset xfb.offset for (size_t i = 0; i < kXfbDecorationCount; ++i) { spirv::WriteMemberDecorate(blobOut, id, spirv::LiteralInteger(fieldIndex), kXfbDecorations[i], {spirv::LiteralInteger(xfbDecorationValues[i])}); } } } void SpirvTransformFeedbackCodeGenerator::addDecorate(const XFBInterfaceVariableInfo &info, spirv::IdRef id, spirv::Blob *blobOut) { if (mIsEmulated || info.xfb.pod.buffer == ShaderInterfaceVariableXfbInfo::kInvalid) { return; } ASSERT(info.xfb.pod.stride != ShaderInterfaceVariableXfbInfo::kInvalid); ASSERT(info.xfb.pod.offset != ShaderInterfaceVariableXfbInfo::kInvalid); const uint32_t xfbDecorationValues[kXfbDecorationCount] = { info.xfb.pod.buffer, info.xfb.pod.stride, info.xfb.pod.offset, }; // Generate the following three instructions: // // OpDecorate %id XfbBuffer xfb.buffer // OpDecorate %id XfbStride xfb.stride // OpDecorate %id Offset xfb.offset for (size_t i = 0; i < kXfbDecorationCount; ++i) { spirv::WriteDecorate(blobOut, id, kXfbDecorations[i], {spirv::LiteralInteger(xfbDecorationValues[i])}); } } // Helper class that generates code for gl_Position transformations class SpirvPositionTransformer final : angle::NonCopyable { public: SpirvPositionTransformer(const SpvTransformOptions &options) : mOptions(options) {} void writePositionTransformation(spirv::IdRef positionPointerId, spirv::IdRef positionId, spirv::Blob *blobOut); private: SpvTransformOptions mOptions; }; void SpirvPositionTransformer::writePositionTransformation(spirv::IdRef positionPointerId, spirv::IdRef positionId, spirv::Blob *blobOut) { // Generate the following SPIR-V for prerotation and depth transformation: // // // Transform position based on uniforms by making a call to the ANGLETransformPosition // // function that the translator has already provided. // %transformed = OpFunctionCall %kIdVec4 %kIdTransformPositionFunction %position // // // Store the results back in gl_Position // OpStore %PositionPointer %transformedPosition // const spirv::IdRef transformedPositionId(SpirvTransformerBase::GetNewId(blobOut)); spirv::WriteFunctionCall(blobOut, ID::Vec4, transformedPositionId, ID::TransformPositionFunction, {positionId}); spirv::WriteStore(blobOut, positionPointerId, transformedPositionId, nullptr); } // A transformation to handle both the isMultisampledFramebufferFetch and enableSampleShading // options. The common transformation between these two options is the addition of the // SampleRateShading capability. class SpirvMultisampleTransformer final : angle::NonCopyable { public: SpirvMultisampleTransformer(const SpvTransformOptions &options) : mOptions(options), mSampleIDDecorationsAdded(false), mAnyImageTypesModified(false) {} ~SpirvMultisampleTransformer() { ASSERT(!mOptions.isMultisampledFramebufferFetch || mAnyImageTypesModified); } void init(size_t indexBound); void visitDecorate(spirv::IdRef id, spv::Decoration decoration, const spirv::LiteralIntegerList &valueList); void visitMemberDecorate(spirv::IdRef id, spirv::LiteralInteger member, spv::Decoration decoration); void visitTypeStruct(spirv::IdResult id, const spirv::IdRefList &memberList); void visitTypePointer(gl::ShaderType shaderType, spirv::IdResult id, spv::StorageClass storageClass, spirv::IdRef typeId); void visitVariable(gl::ShaderType shaderType, spirv::IdResultType typeId, spirv::IdResult id, spv::StorageClass storageClass); TransformationState transformCapability(const SpirvNonSemanticInstructions &nonSemantic, spv::Capability capability, spirv::Blob *blobOut); TransformationState transformTypeImage(const uint32_t *instruction, spirv::Blob *blobOut); void modifyEntryPointInterfaceList(const SpirvNonSemanticInstructions &nonSemantic, EntryPointList entryPointList, spirv::IdRefList *interfaceList, spirv::Blob *blobOut); void writePendingDeclarations( const SpirvNonSemanticInstructions &nonSemantic, const std::vector &variableInfoById, spirv::Blob *blobOut); TransformationState transformDecorate(const SpirvNonSemanticInstructions &nonSemantic, const ShaderInterfaceVariableInfo &info, gl::ShaderType shaderType, spirv::IdRef id, spv::Decoration &decoration, spirv::Blob *blobOut); TransformationState transformImageRead(const uint32_t *instruction, spirv::Blob *blobOut); private: void visitVarying(gl::ShaderType shaderType, spirv::IdRef id, spv::StorageClass storageClass); bool skipSampleDecoration(spv::Decoration decoration); SpvTransformOptions mOptions; // Used to assert that the transformation is not unnecessarily run. bool mSampleIDDecorationsAdded; bool mAnyImageTypesModified; struct VaryingInfo { // Whether any variable is a varying bool isVarying = false; // Whether any variable or its members are already sample-, centroid- or flat-qualified. bool skipSampleDecoration = false; std::vector skipMemberSampleDecoration; }; std::vector mVaryingInfoById; }; void SpirvMultisampleTransformer::init(size_t indexBound) { mVaryingInfoById.resize(indexBound); } TransformationState SpirvMultisampleTransformer::transformImageRead(const uint32_t *instruction, spirv::Blob *blobOut) { // Transform the following: // %21 = OpImageRead %v4float %13 %20 // to // %21 = OpImageRead %v4float %13 %20 Sample %17 // where // %17 = OpLoad %int %gl_SampleID if (!mOptions.isMultisampledFramebufferFetch) { return TransformationState::Unchanged; } spirv::IdResultType idResultType; spirv::IdResult idResult; spirv::IdRef image; spirv::IdRef coordinate; spv::ImageOperandsMask imageOperands; spirv::IdRefList imageOperandIdsList; spirv::ParseImageRead(instruction, &idResultType, &idResult, &image, &coordinate, &imageOperands, &imageOperandIdsList); ASSERT(ID::Int.valid()); spirv::IdRef builtInSampleIDOpLoad = SpirvTransformerBase::GetNewId(blobOut); spirv::WriteLoad(blobOut, ID::Int, builtInSampleIDOpLoad, ID::SampleID, nullptr); imageOperands = spv::ImageOperandsMask::ImageOperandsSampleMask; imageOperandIdsList.push_back(builtInSampleIDOpLoad); spirv::WriteImageRead(blobOut, idResultType, idResult, image, coordinate, &imageOperands, imageOperandIdsList); return TransformationState::Transformed; } void SpirvMultisampleTransformer::writePendingDeclarations( const SpirvNonSemanticInstructions &nonSemantic, const std::vector &variableInfoById, spirv::Blob *blobOut) { // Add following declarations if they are not available yet // %int = OpTypeInt 32 1 // %_ptr_Input_int = OpTypePointer Input %int // %gl_SampleID = OpVariable %_ptr_Input_int Input if (!mOptions.isMultisampledFramebufferFetch) { return; } if (nonSemantic.hasSampleID()) { return; } spirv::WriteVariable(blobOut, ID::IntInputTypePointer, ID::SampleID, spv::StorageClassInput, nullptr); } TransformationState SpirvMultisampleTransformer::transformTypeImage(const uint32_t *instruction, spirv::Blob *blobOut) { // Transform the following // %10 = OpTypeImage %float SubpassData 0 0 0 2 // To // %10 = OpTypeImage %float SubpassData 0 0 1 2 if (!mOptions.isMultisampledFramebufferFetch) { return TransformationState::Unchanged; } spirv::IdResult idResult; spirv::IdRef sampledType; spv::Dim dim; spirv::LiteralInteger depth; spirv::LiteralInteger arrayed; spirv::LiteralInteger ms; spirv::LiteralInteger sampled; spv::ImageFormat imageFormat; spv::AccessQualifier accessQualifier; spirv::ParseTypeImage(instruction, &idResult, &sampledType, &dim, &depth, &arrayed, &ms, &sampled, &imageFormat, &accessQualifier); // Only transform input attachment image types. if (dim != spv::DimSubpassData) { return TransformationState::Unchanged; } ms = spirv::LiteralInteger(1); spirv::WriteTypeImage(blobOut, idResult, sampledType, dim, depth, arrayed, ms, sampled, imageFormat, nullptr); mAnyImageTypesModified = true; return TransformationState::Transformed; } namespace { bool verifyEntryPointsContainsID(const spirv::IdRefList &interfaceList) { for (spirv::IdRef interfaceId : interfaceList) { if (interfaceId == ID::SampleID) { return true; } } return false; } } // namespace void SpirvMultisampleTransformer::modifyEntryPointInterfaceList( const SpirvNonSemanticInstructions &nonSemantic, EntryPointList entryPointList, spirv::IdRefList *interfaceList, spirv::Blob *blobOut) { // Append %gl_sampleID to OpEntryPoint // Transform the following // // OpEntryPoint Fragment %main "main" %_uo_color // // To // // OpEntryPoint Fragment %main "main" %_uo_color %gl_SampleID if (!mOptions.isMultisampledFramebufferFetch) { return; } // Nothing to do if the shader had already declared SampleID if (nonSemantic.hasSampleID()) { ASSERT(verifyEntryPointsContainsID(*interfaceList)); return; } // Add the SampleID id to the interfaceList. The variable will later be decalred in // writePendingDeclarations. interfaceList->push_back(ID::SampleID); return; } TransformationState SpirvMultisampleTransformer::transformCapability( const SpirvNonSemanticInstructions &nonSemantic, const spv::Capability capability, spirv::Blob *blobOut) { // Add a new OpCapability line: // // OpCapability SampleRateShading // // right before the following instruction // // OpCapability InputAttachment if (!mOptions.isMultisampledFramebufferFetch && !mOptions.enableSampleShading) { return TransformationState::Unchanged; } // Do not add the capability if the SPIR-V already has it if (nonSemantic.hasSampleRateShading()) { return TransformationState::Unchanged; } // Make sure no duplicates ASSERT(capability != spv::CapabilitySampleRateShading); // Make sure we only add the new line on top of "OpCapability Shader" if (capability != spv::CapabilityShader) { return TransformationState::Unchanged; } spirv::WriteCapability(blobOut, spv::CapabilitySampleRateShading); // Leave the original OpCapability untouched return TransformationState::Unchanged; } TransformationState SpirvMultisampleTransformer::transformDecorate( const SpirvNonSemanticInstructions &nonSemantic, const ShaderInterfaceVariableInfo &info, gl::ShaderType shaderType, spirv::IdRef id, spv::Decoration &decoration, spirv::Blob *blobOut) { if (mOptions.isMultisampledFramebufferFetch && !nonSemantic.hasSampleID() && !mSampleIDDecorationsAdded) { // Add the following instructions if they are not available yet: // // OpDecorate %gl_SampleID RelaxedPrecision // OpDecorate %gl_SampleID Flat // OpDecorate %gl_SampleID BuiltIn SampleId spirv::WriteDecorate(blobOut, ID::SampleID, spv::DecorationRelaxedPrecision, {}); spirv::WriteDecorate(blobOut, ID::SampleID, spv::DecorationFlat, {}); spirv::WriteDecorate(blobOut, ID::SampleID, spv::DecorationBuiltIn, {spirv::LiteralInteger(spv::BuiltIn::BuiltInSampleId)}); mSampleIDDecorationsAdded = true; } if (mOptions.enableSampleShading && mVaryingInfoById[id].isVarying && !mVaryingInfoById[id].skipSampleDecoration) { if (decoration == spv::DecorationLocation && info.activeStages[shaderType]) { // Add the following instructions when the Location decoration is met, if the varying is // not already decorated with Sample: // // OpDecorate %id Sample spirv::WriteDecorate(blobOut, id, spv::DecorationSample, {}); } else if (decoration == spv::DecorationBlock) { // Add the following instructions when the Block decoration is met, for any member that // is not already decorated with Sample: // // OpMemberDecorate %id member Sample for (uint32_t member = 0; member < mVaryingInfoById[id].skipMemberSampleDecoration.size(); ++member) { if (!mVaryingInfoById[id].skipMemberSampleDecoration[member]) { spirv::WriteMemberDecorate(blobOut, id, spirv::LiteralInteger(member), spv::DecorationSample, {}); } } } } return TransformationState::Unchanged; } bool SpirvMultisampleTransformer::skipSampleDecoration(spv::Decoration decoration) { // If a variable is already decorated with Sample, Patch or Centroid, it shouldn't be decorated // with Sample. BuiltIns are also excluded. return decoration == spv::DecorationPatch || decoration == spv::DecorationCentroid || decoration == spv::DecorationSample || decoration == spv::DecorationBuiltIn; } void SpirvMultisampleTransformer::visitDecorate(spirv::IdRef id, spv::Decoration decoration, const spirv::LiteralIntegerList &valueList) { if (mOptions.enableSampleShading) { // Determine whether the id is already decorated with Sample. if (skipSampleDecoration(decoration)) { mVaryingInfoById[id].skipSampleDecoration = true; } } return; } void SpirvMultisampleTransformer::visitMemberDecorate(spirv::IdRef id, spirv::LiteralInteger member, spv::Decoration decoration) { if (!mOptions.enableSampleShading) { return; } if (mVaryingInfoById[id].skipMemberSampleDecoration.size() <= member) { mVaryingInfoById[id].skipMemberSampleDecoration.resize(member + 1, false); } // Determine whether the member is already decorated with Sample. if (skipSampleDecoration(decoration)) { mVaryingInfoById[id].skipMemberSampleDecoration[member] = true; } } void SpirvMultisampleTransformer::visitVarying(gl::ShaderType shaderType, spirv::IdRef id, spv::StorageClass storageClass) { if (!mOptions.enableSampleShading) { return; } // Vertex input and fragment output variables are not varyings and don't need to be decorated // with Sample. if ((shaderType == gl::ShaderType::Fragment && storageClass == spv::StorageClassOutput) || (shaderType == gl::ShaderType::Vertex && storageClass == spv::StorageClassInput)) { return; } const bool isVarying = storageClass == spv::StorageClassInput || storageClass == spv::StorageClassOutput; mVaryingInfoById[id].isVarying = isVarying; } void SpirvMultisampleTransformer::visitTypeStruct(spirv::IdResult id, const spirv::IdRefList &memberList) { if (mOptions.enableSampleShading) { mVaryingInfoById[id].skipMemberSampleDecoration.resize(memberList.size(), false); } } void SpirvMultisampleTransformer::visitTypePointer(gl::ShaderType shaderType, spirv::IdResult id, spv::StorageClass storageClass, spirv::IdRef typeId) { // For I/O blocks, the Sample decoration should be specified on the members of the struct type. // For that purpose, we consider the struct type as the varying instead. visitVarying(shaderType, typeId, storageClass); } void SpirvMultisampleTransformer::visitVariable(gl::ShaderType shaderType, spirv::IdResultType typeId, spirv::IdResult id, spv::StorageClass storageClass) { visitVarying(shaderType, id, storageClass); } // Helper class that flattens secondary fragment output arrays. class SpirvSecondaryOutputTransformer final : angle::NonCopyable { public: SpirvSecondaryOutputTransformer() {} void init(size_t indexBound); void visitTypeArray(spirv::IdResult id, spirv::IdRef typeId); void visitTypePointer(spirv::IdResult id, spirv::IdRef typeId); TransformationState transformAccessChain(spirv::IdResultType typeId, spirv::IdResult id, spirv::IdRef baseId, const spirv::IdRefList &indexList, spirv::Blob *blobOut); TransformationState transformDecorate(spirv::IdRef id, spv::Decoration decoration, const spirv::LiteralIntegerList &decorationValues, spirv::Blob *blobOut); TransformationState transformVariable(spirv::IdResultType typeId, spirv::IdResultType privateTypeId, spirv::IdResult id, spirv::Blob *blobOut); void modifyEntryPointInterfaceList( const std::vector &variableInfoById, EntryPointList entryPointList, spirv::IdRefList *interfaceList, spirv::Blob *blobOut); void writeOutputPrologue( const std::vector &variableInfoById, spirv::Blob *blobOut); static_assert(gl::IMPLEMENTATION_MAX_DUAL_SOURCE_DRAW_BUFFERS == 1, "This transformer is incompatible with two or more dual-source draw buffers"); private: void visitTypeHelper(spirv::IdResult id, spirv::IdRef typeId) { mTypeCache[id] = typeId; } // This list is filled during visitTypePointer and visitTypeArray steps, // to resolve the element type ID of the original output array variable. std::vector mTypeCache; spirv::IdRef mElementTypeId; spirv::IdRef mArrayVariableId; spirv::IdRef mReplacementVariableId; spirv::IdRef mElementPointerTypeId; }; void SpirvSecondaryOutputTransformer::init(size_t indexBound) { mTypeCache.resize(indexBound); } void SpirvSecondaryOutputTransformer::visitTypeArray(spirv::IdResult id, spirv::IdRef typeId) { visitTypeHelper(id, typeId); } void SpirvSecondaryOutputTransformer::visitTypePointer(spirv::IdResult id, spirv::IdRef typeId) { visitTypeHelper(id, typeId); } void SpirvSecondaryOutputTransformer::modifyEntryPointInterfaceList( const std::vector &variableInfoById, EntryPointList entryPointList, spirv::IdRefList *interfaceList, spirv::Blob *blobOut) { // Flatten a secondary output array (if any). for (size_t index = 0; index < interfaceList->size(); ++index) { const spirv::IdRef id((*interfaceList)[index]); const ShaderInterfaceVariableInfo *info = variableInfoById[id]; if (info == nullptr || info->index != 1 || !info->isArray) { continue; } mArrayVariableId = id; mReplacementVariableId = SpirvTransformerBase::GetNewId(blobOut); // With SPIR-V 1.3, modify interface list with the replacement ID. // // With SPIR-V 1.4, the original variable is changed to Private and should remain in the // list. The new variable should be added to the variable list. if (entryPointList == EntryPointList::InterfaceVariables) { (*interfaceList)[index] = mReplacementVariableId; } else { interfaceList->push_back(mReplacementVariableId); } break; } } TransformationState SpirvSecondaryOutputTransformer::transformAccessChain( spirv::IdResultType typeId, spirv::IdResult id, spirv::IdRef baseId, const spirv::IdRefList &indexList, spirv::Blob *blobOut) { if (baseId != mArrayVariableId) { return TransformationState::Unchanged; } ASSERT(typeId.valid()); spirv::WriteAccessChain(blobOut, typeId, id, baseId, indexList); return TransformationState::Transformed; } TransformationState SpirvSecondaryOutputTransformer::transformDecorate( spirv::IdRef id, spv::Decoration decoration, const spirv::LiteralIntegerList &decorationValues, spirv::Blob *blobOut) { if (id != mArrayVariableId) { return TransformationState::Unchanged; } ASSERT(mReplacementVariableId.valid()); if (decoration == spv::DecorationLocation) { // Drop the Location decoration from the original variable and add // it together with an Index decoration to the replacement variable. spirv::WriteDecorate(blobOut, mReplacementVariableId, spv::DecorationLocation, {spirv::LiteralInteger(0)}); spirv::WriteDecorate(blobOut, mReplacementVariableId, spv::DecorationIndex, {spirv::LiteralInteger(1)}); } else { // Apply other decorations, such as RelaxedPrecision, to both variables. spirv::WriteDecorate(blobOut, id, decoration, decorationValues); spirv::WriteDecorate(blobOut, mReplacementVariableId, decoration, decorationValues); } return TransformationState::Transformed; } TransformationState SpirvSecondaryOutputTransformer::transformVariable( spirv::IdResultType typeId, spirv::IdResultType privateTypeId, spirv::IdResult id, spirv::Blob *blobOut) { if (id != mArrayVariableId) { return TransformationState::Unchanged; } // Change the original variable to use private storage. ASSERT(privateTypeId.valid()); spirv::WriteVariable(blobOut, privateTypeId, id, spv::StorageClassPrivate, nullptr); ASSERT(!mElementTypeId.valid()); mElementTypeId = mTypeCache[mTypeCache[typeId]]; ASSERT(mElementTypeId.valid()); // Pointer type for accessing the array element value. mElementPointerTypeId = SpirvTransformerBase::GetNewId(blobOut); spirv::WriteTypePointer(blobOut, mElementPointerTypeId, spv::StorageClassPrivate, mElementTypeId); // Pointer type for the replacement output variable. const spirv::IdRef outputPointerTypeId(SpirvTransformerBase::GetNewId(blobOut)); spirv::WriteTypePointer(blobOut, outputPointerTypeId, spv::StorageClassOutput, mElementTypeId); ASSERT(mReplacementVariableId.valid()); spirv::WriteVariable(blobOut, outputPointerTypeId, mReplacementVariableId, spv::StorageClassOutput, nullptr); return TransformationState::Transformed; } void SpirvSecondaryOutputTransformer::writeOutputPrologue( const std::vector &variableInfoById, spirv::Blob *blobOut) { if (mArrayVariableId.valid()) { const spirv::IdRef accessChainId(SpirvTransformerBase::GetNewId(blobOut)); spirv::WriteAccessChain(blobOut, mElementPointerTypeId, accessChainId, mArrayVariableId, {ID::IntZero}); ASSERT(mElementTypeId.valid()); const spirv::IdRef loadId(SpirvTransformerBase::GetNewId(blobOut)); spirv::WriteLoad(blobOut, mElementTypeId, loadId, accessChainId, nullptr); ASSERT(mReplacementVariableId.valid()); spirv::WriteStore(blobOut, mReplacementVariableId, loadId, nullptr); } } // A SPIR-V transformer. It walks the instructions and modifies them as necessary, for example to // assign bindings or locations. class SpirvTransformer final : public SpirvTransformerBase { public: SpirvTransformer(const spirv::Blob &spirvBlobIn, const SpvTransformOptions &options, bool isLastPass, const ShaderInterfaceVariableInfoMap &variableInfoMap, spirv::Blob *spirvBlobOut) : SpirvTransformerBase(spirvBlobIn, variableInfoMap, spirvBlobOut), mOptions(options), mOverviewFlags(0), mNonSemanticInstructions(isLastPass), mPerVertexTrimmer(options, variableInfoMap), mXfbCodeGenerator(options), mPositionTransformer(options), mMultisampleTransformer(options) {} void transform(); private: // A prepass to resolve interesting ids: void resolveVariableIds(); // Transform instructions: void transformInstruction(); // Instructions that are purely informational: void visitDecorate(const uint32_t *instruction); void visitMemberDecorate(const uint32_t *instruction); void visitTypeArray(const uint32_t *instruction); void visitTypePointer(const uint32_t *instruction); void visitTypeStruct(const uint32_t *instruction); void visitVariable(const uint32_t *instruction); void visitCapability(const uint32_t *instruction); bool visitExtInst(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. TransformationState transformAccessChain(const uint32_t *instruction); TransformationState transformCapability(const uint32_t *instruction); TransformationState transformEntryPoint(const uint32_t *instruction); TransformationState transformExtension(const uint32_t *instruction); TransformationState transformExtInstImport(const uint32_t *instruction); TransformationState transformExtInst(const uint32_t *instruction); TransformationState transformDecorate(const uint32_t *instruction); TransformationState transformMemberDecorate(const uint32_t *instruction); TransformationState transformName(const uint32_t *instruction); TransformationState transformMemberName(const uint32_t *instruction); TransformationState transformTypePointer(const uint32_t *instruction); TransformationState transformTypeStruct(const uint32_t *instruction); TransformationState transformVariable(const uint32_t *instruction); TransformationState transformTypeImage(const uint32_t *instruction); TransformationState transformImageRead(const uint32_t *instruction); // Helpers: void visitTypeHelper(spirv::IdResult id, spirv::IdRef typeId); void writePendingDeclarations(); void writeInputPreamble(); void writeOutputPrologue(); // Special flags: SpvTransformOptions mOptions; // Traversal state: spirv::IdRef mCurrentFunctionId; // Transformation state: uint32_t mOverviewFlags; SpirvNonSemanticInstructions mNonSemanticInstructions; SpirvPerVertexTrimmer mPerVertexTrimmer; SpirvInactiveVaryingRemover mInactiveVaryingRemover; SpirvVaryingPrecisionFixer mVaryingPrecisionFixer; SpirvTransformFeedbackCodeGenerator mXfbCodeGenerator; SpirvPositionTransformer mPositionTransformer; SpirvMultisampleTransformer mMultisampleTransformer; SpirvSecondaryOutputTransformer mSecondaryOutputTransformer; }; void SpirvTransformer::transform() { onTransformBegin(); // First, find all necessary ids and associate them with the information required to transform // their decorations. This is mostly derived from |mVariableInfoMap|, but may have additional // mappings; for example |mVariableInfoMap| maps an interface block's type ID to info, but the // transformer needs to discover the variable associated with that block and map it to the same // info. resolveVariableIds(); while (mCurrentWord < mSpirvBlobIn.size()) { transformInstruction(); } } void SpirvTransformer::resolveVariableIds() { const size_t indexBound = mSpirvBlobIn[spirv::kHeaderIndexIndexBound]; mInactiveVaryingRemover.init(indexBound); if (mOptions.useSpirvVaryingPrecisionFixer) { mVaryingPrecisionFixer.init(indexBound); } if (mOptions.isMultisampledFramebufferFetch || mOptions.enableSampleShading) { mMultisampleTransformer.init(indexBound); } if (mOptions.shaderType == gl::ShaderType::Fragment) { mSecondaryOutputTransformer.init(indexBound); } // Allocate storage for id-to-info map. If %i is an id in mVariableInfoMap, index i in this // vector will hold a pointer to the ShaderInterfaceVariableInfo object associated with that // name in mVariableInfoMap. mVariableInfoById.resize(indexBound, nullptr); // Pre-populate from mVariableInfoMap. { const ShaderInterfaceVariableInfoMap::VariableInfoArray &data = mVariableInfoMap.getData(); const ShaderInterfaceVariableInfoMap::IdToIndexMap &idToIndexMap = mVariableInfoMap.getIdToIndexMap()[mOptions.shaderType]; for (uint32_t hashedId = 0; hashedId < idToIndexMap.size(); ++hashedId) { const uint32_t id = hashedId + sh::vk::spirv::kIdShaderVariablesBegin; const VariableIndex &variableIndex = idToIndexMap.at(hashedId); if (variableIndex.index == VariableIndex::kInvalid) { continue; } const ShaderInterfaceVariableInfo &info = data[variableIndex.index]; ASSERT(id < mVariableInfoById.size()); mVariableInfoById[id] = &info; } } size_t currentWord = spirv::kHeaderIndexInstructions; while (currentWord < mSpirvBlobIn.size()) { const uint32_t *instruction = &mSpirvBlobIn[currentWord]; uint32_t wordCount; spv::Op opCode; spirv::GetInstructionOpAndLength(instruction, &opCode, &wordCount); switch (opCode) { case spv::OpDecorate: visitDecorate(instruction); break; case spv::OpMemberDecorate: visitMemberDecorate(instruction); break; case spv::OpTypeArray: visitTypeArray(instruction); break; case spv::OpTypePointer: visitTypePointer(instruction); break; case spv::OpTypeStruct: visitTypeStruct(instruction); break; case spv::OpVariable: visitVariable(instruction); break; case spv::OpExtInst: if (visitExtInst(instruction)) { return; } break; default: break; } currentWord += wordCount; } UNREACHABLE(); } void SpirvTransformer::transformInstruction() { uint32_t wordCount; spv::Op opCode; const uint32_t *instruction = getCurrentInstruction(&opCode, &wordCount); if (opCode == spv::OpFunction) { spirv::IdResultType id; spv::FunctionControlMask functionControl; spirv::IdRef functionType; spirv::ParseFunction(instruction, &id, &mCurrentFunctionId, &functionControl, &functionType); // SPIR-V is structured in sections. Function declarations come last. Only a few // instructions such as Op*Access* or OpEmitVertex opcodes inside functions need to be // inspected. mIsInFunctionSection = true; } // Only look at interesting instructions. TransformationState transformationState = TransformationState::Unchanged; if (mIsInFunctionSection) { // Look at in-function opcodes. switch (opCode) { case spv::OpExtInst: transformationState = transformExtInst(instruction); break; case spv::OpAccessChain: case spv::OpInBoundsAccessChain: case spv::OpPtrAccessChain: case spv::OpInBoundsPtrAccessChain: transformationState = transformAccessChain(instruction); break; case spv::OpImageRead: transformationState = transformImageRead(instruction); break; default: break; } } else { // Look at global declaration opcodes. switch (opCode) { case spv::OpExtension: transformationState = transformExtension(instruction); break; case spv::OpExtInstImport: transformationState = transformExtInstImport(instruction); break; case spv::OpExtInst: transformationState = transformExtInst(instruction); break; case spv::OpName: transformationState = transformName(instruction); break; case spv::OpMemberName: transformationState = transformMemberName(instruction); break; case spv::OpCapability: transformationState = transformCapability(instruction); break; case spv::OpEntryPoint: transformationState = transformEntryPoint(instruction); break; case spv::OpDecorate: transformationState = transformDecorate(instruction); break; case spv::OpMemberDecorate: transformationState = transformMemberDecorate(instruction); break; case spv::OpTypeImage: transformationState = transformTypeImage(instruction); break; case spv::OpTypePointer: transformationState = transformTypePointer(instruction); break; case spv::OpTypeStruct: transformationState = transformTypeStruct(instruction); break; case spv::OpVariable: transformationState = transformVariable(instruction); break; default: break; } } // If the instruction was not transformed, copy it to output as is. if (transformationState == TransformationState::Unchanged) { copyInstruction(instruction, wordCount); } // Advance to next instruction. mCurrentWord += wordCount; } // Called at the end of the declarations section. Any declarations that are necessary but weren't // present in the original shader need to be done here. void SpirvTransformer::writePendingDeclarations() { mMultisampleTransformer.writePendingDeclarations(mNonSemanticInstructions, mVariableInfoById, mSpirvBlobOut); // Pre-rotation and transformation of depth to Vulkan clip space require declarations that may // not necessarily be in the shader. Transform feedback emulation additionally requires a few // overlapping ids. if (!mOptions.isLastPreFragmentStage) { return; } if (mOptions.isTransformFeedbackStage) { mXfbCodeGenerator.writePendingDeclarations(mVariableInfoById, storageBufferStorageClass(), mSpirvBlobOut); } } // Called by transformInstruction to insert necessary instructions for casting varyings. void SpirvTransformer::writeInputPreamble() { if (mOptions.useSpirvVaryingPrecisionFixer) { mVaryingPrecisionFixer.writeInputPreamble(mVariableInfoById, mOptions.shaderType, mSpirvBlobOut); } } // Called by transformInstruction to insert necessary instructions for casting varyings and // modifying gl_Position. void SpirvTransformer::writeOutputPrologue() { if (mOptions.useSpirvVaryingPrecisionFixer) { mVaryingPrecisionFixer.writeOutputPrologue(mVariableInfoById, mOptions.shaderType, mSpirvBlobOut); } if (mOptions.shaderType == gl::ShaderType::Fragment) { mSecondaryOutputTransformer.writeOutputPrologue(mVariableInfoById, mSpirvBlobOut); } if (!mNonSemanticInstructions.hasOutputPerVertex()) { return; } // Whether gl_Position should be transformed to account for pre-rotation and Vulkan clip space. const bool transformPosition = mOptions.isLastPreFragmentStage; const bool isXfbExtensionStage = mOptions.isTransformFeedbackStage && !mOptions.isTransformFeedbackEmulated; if (!transformPosition && !isXfbExtensionStage) { return; } // Load gl_Position with the following SPIR-V: // // // Create an access chain to gl_PerVertex.gl_Position, which is always at index 0. // %PositionPointer = OpAccessChain %kIdVec4OutputTypePointer %kIdOutputPerVertexVar // %kIdIntZero // // Load gl_Position // %Position = OpLoad %kIdVec4 %PositionPointer // const spirv::IdRef positionPointerId(getNewId()); const spirv::IdRef positionId(getNewId()); spirv::WriteAccessChain(mSpirvBlobOut, ID::Vec4OutputTypePointer, positionPointerId, ID::OutputPerVertexVar, {ID::IntZero}); spirv::WriteLoad(mSpirvBlobOut, ID::Vec4, positionId, positionPointerId, nullptr); // Write transform feedback output before modifying gl_Position. if (isXfbExtensionStage) { mXfbCodeGenerator.writeTransformFeedbackExtensionOutput(positionId, mSpirvBlobOut); } if (transformPosition) { mPositionTransformer.writePositionTransformation(positionPointerId, positionId, mSpirvBlobOut); } } void SpirvTransformer::visitDecorate(const uint32_t *instruction) { spirv::IdRef id; spv::Decoration decoration; spirv::LiteralIntegerList valueList; spirv::ParseDecorate(instruction, &id, &decoration, &valueList); mMultisampleTransformer.visitDecorate(id, decoration, valueList); } void SpirvTransformer::visitMemberDecorate(const uint32_t *instruction) { spirv::IdRef typeId; spirv::LiteralInteger member; spv::Decoration decoration; spirv::LiteralIntegerList valueList; spirv::ParseMemberDecorate(instruction, &typeId, &member, &decoration, &valueList); mPerVertexTrimmer.visitMemberDecorate(typeId, member, decoration, valueList); mMultisampleTransformer.visitMemberDecorate(typeId, member, decoration); } void SpirvTransformer::visitTypeHelper(spirv::IdResult id, spirv::IdRef typeId) { // Carry forward the mapping of typeId->info to id->info. For interface block, it's the block // id that is mapped to the info, so this is necessary to eventually be able to map the variable // itself to the info. mVariableInfoById[id] = mVariableInfoById[typeId]; } void SpirvTransformer::visitTypeArray(const uint32_t *instruction) { spirv::IdResult id; spirv::IdRef elementType; spirv::IdRef length; spirv::ParseTypeArray(instruction, &id, &elementType, &length); visitTypeHelper(id, elementType); if (mOptions.shaderType == gl::ShaderType::Fragment) { mSecondaryOutputTransformer.visitTypeArray(id, elementType); } } void SpirvTransformer::visitTypePointer(const uint32_t *instruction) { spirv::IdResult id; spv::StorageClass storageClass; spirv::IdRef typeId; spirv::ParseTypePointer(instruction, &id, &storageClass, &typeId); visitTypeHelper(id, typeId); if (mOptions.useSpirvVaryingPrecisionFixer) { mVaryingPrecisionFixer.visitTypePointer(id, storageClass, typeId); } mMultisampleTransformer.visitTypePointer(mOptions.shaderType, id, storageClass, typeId); if (mOptions.shaderType == gl::ShaderType::Fragment) { mSecondaryOutputTransformer.visitTypePointer(id, typeId); } } void SpirvTransformer::visitTypeStruct(const uint32_t *instruction) { spirv::IdResult id; spirv::IdRefList memberList; ParseTypeStruct(instruction, &id, &memberList); mMultisampleTransformer.visitTypeStruct(id, memberList); } void SpirvTransformer::visitVariable(const uint32_t *instruction) { spirv::IdResultType typeId; spirv::IdResult id; spv::StorageClass storageClass; spirv::ParseVariable(instruction, &typeId, &id, &storageClass, nullptr); // 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; } // If no info is already associated with this id, carry that forward from the type. This // happens for interface blocks, where the id->info association is done on the type id. ASSERT(mVariableInfoById[id] == nullptr || mVariableInfoById[typeId] == nullptr); if (mVariableInfoById[id] == nullptr) { mVariableInfoById[id] = mVariableInfoById[typeId]; } const ShaderInterfaceVariableInfo *info = mVariableInfoById[id]; // If this is an interface variable but no info is associated with it, it must be a built-in. if (info == nullptr) { // 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; } if (mOptions.useSpirvVaryingPrecisionFixer) { mVaryingPrecisionFixer.visitVariable(*info, mOptions.shaderType, typeId, id, storageClass, mSpirvBlobOut); } if (mOptions.isTransformFeedbackStage && mVariableInfoById[id]->hasTransformFeedback) { const XFBInterfaceVariableInfo &xfbInfo = mVariableInfoMap.getXFBDataForVariableInfo(mVariableInfoById[id]); mXfbCodeGenerator.visitVariable(*info, xfbInfo, mOptions.shaderType, typeId, id, storageClass); } mMultisampleTransformer.visitVariable(mOptions.shaderType, typeId, id, storageClass); } bool SpirvTransformer::visitExtInst(const uint32_t *instruction) { sh::vk::spirv::NonSemanticInstruction inst; if (!mNonSemanticInstructions.visitExtInst(instruction, &inst)) { return false; } switch (inst) { case sh::vk::spirv::kNonSemanticOverview: // 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. // // This non-semantic instruction marks the beginning of the functions section. return true; default: UNREACHABLE(); } return false; } TransformationState SpirvTransformer::transformDecorate(const uint32_t *instruction) { spirv::IdRef id; spv::Decoration decoration; spirv::LiteralIntegerList decorationValues; spirv::ParseDecorate(instruction, &id, &decoration, &decorationValues); ASSERT(id < mVariableInfoById.size()); 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 TransformationState::Unchanged; } if (mOptions.shaderType == gl::ShaderType::Fragment) { // Handle decorations for the secondary fragment output array. if (mSecondaryOutputTransformer.transformDecorate(id, decoration, decorationValues, mSpirvBlobOut) == TransformationState::Transformed) { return TransformationState::Transformed; } } mMultisampleTransformer.transformDecorate(mNonSemanticInstructions, *info, mOptions.shaderType, id, decoration, mSpirvBlobOut); if (mXfbCodeGenerator.transformDecorate(info, mOptions.shaderType, id, decoration, decorationValues, mSpirvBlobOut) == TransformationState::Transformed) { return TransformationState::Transformed; } if (mInactiveVaryingRemover.transformDecorate(*info, mOptions.shaderType, id, decoration, decorationValues, mSpirvBlobOut) == TransformationState::Transformed) { return TransformationState::Transformed; } // If using relaxed precision, generate instructions for the replacement id instead. if (mOptions.useSpirvVaryingPrecisionFixer) { id = mVaryingPrecisionFixer.getReplacementId(id); } 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; case spv::DecorationFlat: case spv::DecorationNoPerspective: case spv::DecorationCentroid: case spv::DecorationSample: if (mOptions.useSpirvVaryingPrecisionFixer && info->useRelaxedPrecision) { // Change the id to replacement variable spirv::WriteDecorate(mSpirvBlobOut, id, decoration, decorationValues); return TransformationState::Transformed; } break; case spv::DecorationBlock: // If this is the Block decoration of a shader I/O block, add the transform feedback // decorations to its members right away. if (mOptions.isTransformFeedbackStage && info->hasTransformFeedback) { const XFBInterfaceVariableInfo &xfbInfo = mVariableInfoMap.getXFBDataForVariableInfo(info); mXfbCodeGenerator.addMemberDecorate(xfbInfo, id, mSpirvBlobOut); } break; case spv::DecorationInvariant: spirv::WriteDecorate(mSpirvBlobOut, id, spv::DecorationInvariant, {}); return TransformationState::Transformed; default: break; } // If the decoration is not something we care about modifying, there's nothing to do. if (newDecorationValue == ShaderInterfaceVariableInfo::kInvalid) { return TransformationState::Unchanged; } // Modify the decoration value. ASSERT(decorationValues.size() == 1); spirv::WriteDecorate(mSpirvBlobOut, id, decoration, {spirv::LiteralInteger(newDecorationValue)}); // If there are decorations to be added, add them right after the Location decoration is // encountered. if (decoration != spv::DecorationLocation) { return TransformationState::Transformed; } // If any, the replacement variable is always reduced precision so add that decoration to // fixedVaryingId. if (mOptions.useSpirvVaryingPrecisionFixer && info->useRelaxedPrecision) { mVaryingPrecisionFixer.addDecorate(id, mSpirvBlobOut); } // Add component decoration, if any. if (info->component != ShaderInterfaceVariableInfo::kInvalid) { spirv::WriteDecorate(mSpirvBlobOut, id, spv::DecorationComponent, {spirv::LiteralInteger(info->component)}); } // Add index decoration, if any. if (info->index != ShaderInterfaceVariableInfo::kInvalid) { spirv::WriteDecorate(mSpirvBlobOut, id, spv::DecorationIndex, {spirv::LiteralInteger(info->index)}); } // Add Xfb decorations, if any. if (mOptions.isTransformFeedbackStage && info->hasTransformFeedback) { const XFBInterfaceVariableInfo &xfbInfo = mVariableInfoMap.getXFBDataForVariableInfo(info); mXfbCodeGenerator.addDecorate(xfbInfo, id, mSpirvBlobOut); } return TransformationState::Transformed; } TransformationState SpirvTransformer::transformMemberDecorate(const uint32_t *instruction) { spirv::IdRef typeId; spirv::LiteralInteger member; spv::Decoration decoration; spirv::ParseMemberDecorate(instruction, &typeId, &member, &decoration, nullptr); if (mPerVertexTrimmer.transformMemberDecorate(typeId, member, decoration) == TransformationState::Transformed) { return TransformationState::Transformed; } ASSERT(typeId < mVariableInfoById.size()); const ShaderInterfaceVariableInfo *info = mVariableInfoById[typeId]; return mXfbCodeGenerator.transformMemberDecorate(info, mOptions.shaderType, typeId, member, decoration, mSpirvBlobOut); } TransformationState SpirvTransformer::transformCapability(const uint32_t *instruction) { spv::Capability capability; spirv::ParseCapability(instruction, &capability); TransformationState xfbTransformState = mXfbCodeGenerator.transformCapability(capability, mSpirvBlobOut); ASSERT(xfbTransformState == TransformationState::Unchanged); TransformationState multiSampleTransformState = mMultisampleTransformer.transformCapability( mNonSemanticInstructions, capability, mSpirvBlobOut); ASSERT(multiSampleTransformState == TransformationState::Unchanged); return TransformationState::Unchanged; } TransformationState SpirvTransformer::transformName(const uint32_t *instruction) { spirv::IdRef id; spirv::LiteralString name; spirv::ParseName(instruction, &id, &name); return mXfbCodeGenerator.transformName(id, name); } TransformationState SpirvTransformer::transformMemberName(const uint32_t *instruction) { spirv::IdRef id; spirv::LiteralInteger member; spirv::LiteralString name; spirv::ParseMemberName(instruction, &id, &member, &name); if (mXfbCodeGenerator.transformMemberName(id, member, name) == TransformationState::Transformed) { return TransformationState::Transformed; } return mPerVertexTrimmer.transformMemberName(id, member, name); } TransformationState SpirvTransformer::transformEntryPoint(const uint32_t *instruction) { spv::ExecutionModel executionModel; spirv::IdRef entryPointId; spirv::LiteralString name; spirv::IdRefList interfaceList; spirv::ParseEntryPoint(instruction, &executionModel, &entryPointId, &name, &interfaceList); // Should only have one EntryPoint ASSERT(entryPointId == ID::EntryPoint); mInactiveVaryingRemover.modifyEntryPointInterfaceList(mVariableInfoById, mOptions.shaderType, entryPointList(), &interfaceList); if (mOptions.shaderType == gl::ShaderType::Fragment) { mSecondaryOutputTransformer.modifyEntryPointInterfaceList( mVariableInfoById, entryPointList(), &interfaceList, mSpirvBlobOut); } if (mOptions.useSpirvVaryingPrecisionFixer) { mVaryingPrecisionFixer.modifyEntryPointInterfaceList(entryPointList(), &interfaceList); } mMultisampleTransformer.modifyEntryPointInterfaceList( mNonSemanticInstructions, entryPointList(), &interfaceList, mSpirvBlobOut); mXfbCodeGenerator.modifyEntryPointInterfaceList(mVariableInfoById, mOptions.shaderType, entryPointList(), &interfaceList); // Write the entry point with the inactive interface variables removed. spirv::WriteEntryPoint(mSpirvBlobOut, executionModel, ID::EntryPoint, name, interfaceList); // Add an OpExecutionMode Xfb instruction if necessary. mXfbCodeGenerator.addExecutionMode(ID::EntryPoint, mSpirvBlobOut); return TransformationState::Transformed; } TransformationState SpirvTransformer::transformTypePointer(const uint32_t *instruction) { spirv::IdResult id; spv::StorageClass storageClass; spirv::IdRef typeId; spirv::ParseTypePointer(instruction, &id, &storageClass, &typeId); if (mInactiveVaryingRemover.transformTypePointer(id, storageClass, typeId, mSpirvBlobOut) == TransformationState::Transformed) { return TransformationState::Transformed; } ASSERT(id < mVariableInfoById.size()); const ShaderInterfaceVariableInfo *info = mVariableInfoById[id]; return mXfbCodeGenerator.transformTypePointer(info, mOptions.shaderType, id, storageClass, typeId, mSpirvBlobOut); } TransformationState SpirvTransformer::transformExtension(const uint32_t *instruction) { // Drop the OpExtension "SPV_KHR_non_semantic_info" extension instruction. // SPV_KHR_non_semantic_info is used purely as a means of communication between the compiler and // the SPIR-V transformer, and is stripped away before the SPIR-V is handed off to the driver. spirv::LiteralString name; spirv::ParseExtension(instruction, &name); return strcmp(name, "SPV_KHR_non_semantic_info") == 0 ? TransformationState::Transformed : TransformationState::Unchanged; } TransformationState SpirvTransformer::transformExtInstImport(const uint32_t *instruction) { // Drop the OpExtInstImport "NonSemantic.ANGLE" instruction. spirv::IdResult id; spirv::LiteralString name; ParseExtInstImport(instruction, &id, &name); return id == sh::vk::spirv::kIdNonSemanticInstructionSet ? TransformationState::Transformed : TransformationState::Unchanged; } TransformationState SpirvTransformer::transformExtInst(const uint32_t *instruction) { sh::vk::spirv::NonSemanticInstruction inst; if (!mNonSemanticInstructions.visitExtInst(instruction, &inst)) { return TransformationState::Unchanged; } switch (inst) { case sh::vk::spirv::kNonSemanticOverview: // Declare anything that we need but didn't find there already. writePendingDeclarations(); break; case sh::vk::spirv::kNonSemanticEnter: // If there are any precision mismatches that need to be handled, temporary global // variables are created with the original precision. Initialize those variables from // the varyings at the beginning of the shader. writeInputPreamble(); break; case sh::vk::spirv::kNonSemanticOutput: // Generate gl_Position transformations and transform feedback capture (through // extension) before return or EmitVertex(). Additionally, if there are any precision // mismatches that need to be ahendled, write the temporary variables that hold varyings // data. Copy a secondary fragment output value if it was declared as an array. writeOutputPrologue(); break; case sh::vk::spirv::kNonSemanticTransformFeedbackEmulation: // Transform feedback emulation is written to a designated function. Allow its code to // be generated if this is the right function. if (mOptions.isTransformFeedbackStage) { mXfbCodeGenerator.writeTransformFeedbackEmulationOutput( mInactiveVaryingRemover, mVaryingPrecisionFixer, mOptions.useSpirvVaryingPrecisionFixer, mSpirvBlobOut); } break; default: UNREACHABLE(); break; } // Drop the instruction if this is the last pass return mNonSemanticInstructions.transformExtInst(instruction); } TransformationState SpirvTransformer::transformTypeStruct(const uint32_t *instruction) { spirv::IdResult id; spirv::IdRefList memberList; ParseTypeStruct(instruction, &id, &memberList); if (mPerVertexTrimmer.transformTypeStruct(id, &memberList, mSpirvBlobOut) == TransformationState::Transformed) { return TransformationState::Transformed; } ASSERT(id < mVariableInfoById.size()); const ShaderInterfaceVariableInfo *info = mVariableInfoById[id]; return mXfbCodeGenerator.transformTypeStruct(info, mOptions.shaderType, id, memberList, mSpirvBlobOut); } TransformationState SpirvTransformer::transformVariable(const uint32_t *instruction) { spirv::IdResultType typeId; spirv::IdResult id; spv::StorageClass storageClass; spirv::ParseVariable(instruction, &typeId, &id, &storageClass, nullptr); 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 TransformationState::Unchanged; } if (mOptions.shaderType == gl::ShaderType::Fragment && storageClass == spv::StorageClassOutput) { // If present, make the secondary fragment output array // private and declare a non-array output instead. if (mSecondaryOutputTransformer.transformVariable( typeId, mInactiveVaryingRemover.getTransformedPrivateType(typeId), id, mSpirvBlobOut) == TransformationState::Transformed) { return TransformationState::Transformed; } } // 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. // However, input or output storage class for interface block will not be pruned when a shader // is compiled separately. if (info->activeStages[mOptions.shaderType]) { if (mOptions.useSpirvVaryingPrecisionFixer && mVaryingPrecisionFixer.transformVariable( *info, typeId, id, storageClass, mSpirvBlobOut) == TransformationState::Transformed) { // Make original variable a private global return mInactiveVaryingRemover.transformVariable(typeId, id, storageClass, mSpirvBlobOut); } return TransformationState::Unchanged; } if (mXfbCodeGenerator.transformVariable(*info, mVariableInfoMap, mOptions.shaderType, storageBufferStorageClass(), typeId, id, storageClass) == TransformationState::Transformed) { return TransformationState::Transformed; } // The variable is inactive. Output a modified variable declaration, where the type is the // corresponding type with the Private storage class. return mInactiveVaryingRemover.transformVariable(typeId, id, storageClass, mSpirvBlobOut); } TransformationState SpirvTransformer::transformTypeImage(const uint32_t *instruction) { return mMultisampleTransformer.transformTypeImage(instruction, mSpirvBlobOut); } TransformationState SpirvTransformer::transformImageRead(const uint32_t *instruction) { return mMultisampleTransformer.transformImageRead(instruction, mSpirvBlobOut); } TransformationState SpirvTransformer::transformAccessChain(const uint32_t *instruction) { spirv::IdResultType typeId; spirv::IdResult id; spirv::IdRef baseId; spirv::IdRefList indexList; spirv::ParseAccessChain(instruction, &typeId, &id, &baseId, &indexList); // If not accessing an inactive output varying, nothing to do. const ShaderInterfaceVariableInfo *info = mVariableInfoById[baseId]; if (info == nullptr) { return TransformationState::Unchanged; } if (mOptions.shaderType == gl::ShaderType::Fragment) { // Update the type used for accessing the secondary fragment output array. if (mSecondaryOutputTransformer.transformAccessChain( mInactiveVaryingRemover.getTransformedPrivateType(typeId), id, baseId, indexList, mSpirvBlobOut) == TransformationState::Transformed) { return TransformationState::Transformed; } } if (mOptions.useSpirvVaryingPrecisionFixer) { if (info->activeStages[mOptions.shaderType] && !info->useRelaxedPrecision) { return TransformationState::Unchanged; } } else { if (info->activeStages[mOptions.shaderType]) { return TransformationState::Unchanged; } } return mInactiveVaryingRemover.transformAccessChain(typeId, id, baseId, indexList, mSpirvBlobOut); } struct AliasingAttributeMap { // The SPIR-V id of the aliasing attribute with the most components. This attribute will be // used to read from this location instead of every aliasing one. spirv::IdRef attribute; // SPIR-V ids of aliasing attributes. std::vector aliasingAttributes; }; void ValidateShaderInterfaceVariableIsAttribute(const ShaderInterfaceVariableInfo *info) { ASSERT(info); ASSERT(info->activeStages[gl::ShaderType::Vertex]); ASSERT(info->attributeComponentCount > 0); ASSERT(info->attributeLocationCount > 0); ASSERT(info->location != ShaderInterfaceVariableInfo::kInvalid); } void ValidateIsAliasingAttribute(const AliasingAttributeMap *aliasingMap, uint32_t id) { ASSERT(id != aliasingMap->attribute); ASSERT(std::find(aliasingMap->aliasingAttributes.begin(), aliasingMap->aliasingAttributes.end(), id) != aliasingMap->aliasingAttributes.end()); } // A transformation that resolves vertex attribute aliases. Note that vertex attribute aliasing is // only allowed in GLSL ES 100, where the attribute types can only be one of float, vec2, vec3, // vec4, mat2, mat3, and mat4. Matrix attributes are handled by expanding them to multiple vector // attributes, each occupying one location. class SpirvVertexAttributeAliasingTransformer final : public SpirvTransformerBase { public: SpirvVertexAttributeAliasingTransformer( const spirv::Blob &spirvBlobIn, const ShaderInterfaceVariableInfoMap &variableInfoMap, std::vector &&variableInfoById, spirv::Blob *spirvBlobOut) : SpirvTransformerBase(spirvBlobIn, variableInfoMap, spirvBlobOut), mNonSemanticInstructions(true) { mVariableInfoById = std::move(variableInfoById); } void transform(); private: // Preprocess aliasing attributes in preparation for their removal. void preprocessAliasingAttributes(); // Transform instructions: void transformInstruction(); // Helpers: spirv::IdRef getAliasingAttributeReplacementId(spirv::IdRef aliasingId, uint32_t offset) const; bool isMatrixAttribute(spirv::IdRef id) const; // Instructions that are purely informational: void visitTypePointer(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. TransformationState transformEntryPoint(const uint32_t *instruction); TransformationState transformExtInst(const uint32_t *instruction); TransformationState transformName(const uint32_t *instruction); TransformationState transformDecorate(const uint32_t *instruction); TransformationState transformVariable(const uint32_t *instruction); TransformationState transformAccessChain(const uint32_t *instruction); void transformLoadHelper(spirv::IdRef pointerId, spirv::IdRef typeId, spirv::IdRef replacementId, spirv::IdRef resultId); TransformationState transformLoad(const uint32_t *instruction); void declareExpandedMatrixVectors(); void writeExpandedMatrixInitialization(); // Transformation state: // Map of aliasing attributes per location. gl::AttribArray mAliasingAttributeMap; // For each id, this map indicates whether it refers to an aliasing attribute that needs to be // removed. std::vector mIsAliasingAttributeById; // Matrix attributes are split into vectors, each occupying one location. The SPIR-V // declaration would need to change from: // // %type = OpTypeMatrix %vectorType N // %matrixType = OpTypePointer Input %type // %matrix = OpVariable %matrixType Input // // to: // // %matrixType = OpTypePointer Private %type // %matrix = OpVariable %matrixType Private // // %vecType = OpTypePointer Input %vectorType // // %vec0 = OpVariable %vecType Input // ... // %vecN-1 = OpVariable %vecType Input // // For each id %matrix (which corresponds to a matrix attribute), this map contains %vec0. The // ids of the split vectors are consecutive, so %veci == %vec0 + i. %veciType is taken from // mInputTypePointers. std::vector mExpandedMatrixFirstVectorIdById; // Id of attribute types; float and veci. spirv::IdRef floatType(uint32_t componentCount) { static_assert(sh::vk::spirv::kIdVec2 == sh::vk::spirv::kIdFloat + 1); static_assert(sh::vk::spirv::kIdVec3 == sh::vk::spirv::kIdFloat + 2); static_assert(sh::vk::spirv::kIdVec4 == sh::vk::spirv::kIdFloat + 3); ASSERT(componentCount <= 4); return spirv::IdRef(sh::vk::spirv::kIdFloat + (componentCount - 1)); } // Id of matrix attribute types. Note that only square matrices are possible as attributes in // GLSL ES 1.00. spirv::IdRef matrixType(uint32_t dimension) { static_assert(sh::vk::spirv::kIdMat3 == sh::vk::spirv::kIdMat2 + 1); static_assert(sh::vk::spirv::kIdMat4 == sh::vk::spirv::kIdMat2 + 2); ASSERT(dimension >= 2 && dimension <= 4); return spirv::IdRef(sh::vk::spirv::kIdMat2 + (dimension - 2)); } // Corresponding to floatType(), [i]: id of OpTypePointer Input %floatType(i). [0] is unused. std::array mInputTypePointers; // Corresponding to floatType(), [i]: id of OpTypePointer Private %floatType(i). [0] is // unused. std::array mPrivateFloatTypePointers; // Corresponding to matrixType(), [i]: id of OpTypePointer Private %matrixType(i). [0] and // [1] are unused. std::array mPrivateMatrixTypePointers; SpirvNonSemanticInstructions mNonSemanticInstructions; }; void SpirvVertexAttributeAliasingTransformer::transform() { onTransformBegin(); preprocessAliasingAttributes(); while (mCurrentWord < mSpirvBlobIn.size()) { transformInstruction(); } } void SpirvVertexAttributeAliasingTransformer::preprocessAliasingAttributes() { const uint32_t indexBound = mSpirvBlobIn[spirv::kHeaderIndexIndexBound]; mVariableInfoById.resize(indexBound, nullptr); mIsAliasingAttributeById.resize(indexBound, false); mExpandedMatrixFirstVectorIdById.resize(indexBound); // Go through attributes and find out which alias which. for (uint32_t idIndex = spirv::kMinValidId; idIndex < indexBound; ++idIndex) { const spirv::IdRef id(idIndex); const ShaderInterfaceVariableInfo *info = mVariableInfoById[id]; // Ignore non attribute ids. if (info == nullptr || info->attributeComponentCount == 0) { continue; } ASSERT(info->activeStages[gl::ShaderType::Vertex]); ASSERT(info->location != ShaderInterfaceVariableInfo::kInvalid); const bool isMatrixAttribute = info->attributeLocationCount > 1; for (uint32_t offset = 0; offset < info->attributeLocationCount; ++offset) { uint32_t location = info->location + offset; ASSERT(location < mAliasingAttributeMap.size()); spirv::IdRef attributeId(id); // If this is a matrix attribute, expand it to vectors. if (isMatrixAttribute) { const spirv::IdRef matrixId(id); // Get a new id for this location and associate it with the matrix. attributeId = getNewId(); if (offset == 0) { mExpandedMatrixFirstVectorIdById[matrixId] = attributeId; } // The ids are consecutive. ASSERT(attributeId == mExpandedMatrixFirstVectorIdById[matrixId] + offset); mIsAliasingAttributeById.resize(attributeId + 1, false); mVariableInfoById.resize(attributeId + 1, nullptr); mVariableInfoById[attributeId] = info; } AliasingAttributeMap *aliasingMap = &mAliasingAttributeMap[location]; // If this is the first attribute in this location, remember it. if (!aliasingMap->attribute.valid()) { aliasingMap->attribute = attributeId; continue; } // Otherwise, either add it to the list of aliasing attributes, or replace the main // attribute (and add that to the list of aliasing attributes). The one with the // largest number of components is used as the main attribute. const ShaderInterfaceVariableInfo *curMainAttribute = mVariableInfoById[aliasingMap->attribute]; ASSERT(curMainAttribute != nullptr && curMainAttribute->attributeComponentCount > 0); spirv::IdRef aliasingId; if (info->attributeComponentCount > curMainAttribute->attributeComponentCount) { aliasingId = aliasingMap->attribute; aliasingMap->attribute = attributeId; } else { aliasingId = attributeId; } aliasingMap->aliasingAttributes.push_back(aliasingId); ASSERT(!mIsAliasingAttributeById[aliasingId]); mIsAliasingAttributeById[aliasingId] = true; } } } void SpirvVertexAttributeAliasingTransformer::transformInstruction() { uint32_t wordCount; spv::Op opCode; const uint32_t *instruction = getCurrentInstruction(&opCode, &wordCount); if (opCode == spv::OpFunction) { // SPIR-V is structured in sections. Function declarations come last. mIsInFunctionSection = true; } // Only look at interesting instructions. TransformationState transformationState = TransformationState::Unchanged; if (mIsInFunctionSection) { // Look at in-function opcodes. switch (opCode) { case spv::OpExtInst: transformationState = transformExtInst(instruction); break; case spv::OpAccessChain: case spv::OpInBoundsAccessChain: transformationState = transformAccessChain(instruction); break; case spv::OpLoad: transformationState = transformLoad(instruction); break; default: break; } } else { // Look at global declaration opcodes. switch (opCode) { // Informational instructions: case spv::OpTypePointer: visitTypePointer(instruction); break; // Instructions that may need transformation: case spv::OpEntryPoint: transformationState = transformEntryPoint(instruction); break; case spv::OpExtInst: transformationState = transformExtInst(instruction); break; case spv::OpName: transformationState = transformName(instruction); break; case spv::OpDecorate: transformationState = transformDecorate(instruction); break; case spv::OpVariable: transformationState = transformVariable(instruction); break; default: break; } } // If the instruction was not transformed, copy it to output as is. if (transformationState == TransformationState::Unchanged) { copyInstruction(instruction, wordCount); } // Advance to next instruction. mCurrentWord += wordCount; } spirv::IdRef SpirvVertexAttributeAliasingTransformer::getAliasingAttributeReplacementId( spirv::IdRef aliasingId, uint32_t offset) const { // Get variable info corresponding to the aliasing attribute. const ShaderInterfaceVariableInfo *aliasingInfo = mVariableInfoById[aliasingId]; ValidateShaderInterfaceVariableIsAttribute(aliasingInfo); // Find the replacement attribute. const AliasingAttributeMap *aliasingMap = &mAliasingAttributeMap[aliasingInfo->location + offset]; ValidateIsAliasingAttribute(aliasingMap, aliasingId); const spirv::IdRef replacementId(aliasingMap->attribute); ASSERT(replacementId.valid() && replacementId < mIsAliasingAttributeById.size()); ASSERT(!mIsAliasingAttributeById[replacementId]); return replacementId; } bool SpirvVertexAttributeAliasingTransformer::isMatrixAttribute(spirv::IdRef id) const { return mExpandedMatrixFirstVectorIdById[id].valid(); } void SpirvVertexAttributeAliasingTransformer::visitTypePointer(const uint32_t *instruction) { spirv::IdResult id; spv::StorageClass storageClass; spirv::IdRef typeId; spirv::ParseTypePointer(instruction, &id, &storageClass, &typeId); // Only interested in OpTypePointer Input %vecN, where %vecN is the id of OpTypeVector %f32 N, // as well as OpTypePointer Private %matN, where %matN is the id of OpTypeMatrix %vecN N. // This is only for matN types (as allowed by GLSL ES 1.00), so N >= 2. if (storageClass == spv::StorageClassInput) { for (uint32_t n = 2; n <= 4; ++n) { if (typeId == floatType(n)) { ASSERT(!mInputTypePointers[n].valid()); mInputTypePointers[n] = id; break; } } } else if (storageClass == spv::StorageClassPrivate) { for (uint32_t n = 2; n <= 4; ++n) { // Note that Private types may not be unique, as the previous transformation can // generate duplicates. if (typeId == floatType(n)) { mPrivateFloatTypePointers[n] = id; break; } if (typeId == matrixType(n)) { mPrivateMatrixTypePointers[n] = id; break; } } } } TransformationState SpirvVertexAttributeAliasingTransformer::transformEntryPoint( const uint32_t *instruction) { // Remove aliasing attributes from the shader interface declaration. spv::ExecutionModel executionModel; spirv::IdRef entryPointId; spirv::LiteralString name; spirv::IdRefList interfaceList; spirv::ParseEntryPoint(instruction, &executionModel, &entryPointId, &name, &interfaceList); // Should only have one EntryPoint ASSERT(entryPointId == ID::EntryPoint); // As a first pass, filter out matrix attributes and append their replacement vectors. size_t originalInterfaceListSize = interfaceList.size(); for (size_t index = 0; index < originalInterfaceListSize; ++index) { const spirv::IdRef matrixId(interfaceList[index]); if (!mExpandedMatrixFirstVectorIdById[matrixId].valid()) { continue; } const ShaderInterfaceVariableInfo *info = mVariableInfoById[matrixId]; ValidateShaderInterfaceVariableIsAttribute(info); // Replace the matrix id with its first vector id. const spirv::IdRef vec0Id(mExpandedMatrixFirstVectorIdById[matrixId]); interfaceList[index] = vec0Id; // Append the rest of the vectors to the entry point. for (uint32_t offset = 1; offset < info->attributeLocationCount; ++offset) { const spirv::IdRef vecId(vec0Id + offset); interfaceList.push_back(vecId); } // With SPIR-V 1.4, keep the Private variable in the interface list. if (entryPointList() == EntryPointList::GlobalVariables) { interfaceList.push_back(matrixId); } } // Filter out aliasing attributes from entry point interface declaration. size_t writeIndex = 0; for (size_t index = 0; index < interfaceList.size(); ++index) { const spirv::IdRef id(interfaceList[index]); // If this is an attribute that's aliasing another one in the same location, remove it. if (mIsAliasingAttributeById[id]) { const ShaderInterfaceVariableInfo *info = mVariableInfoById[id]; ValidateShaderInterfaceVariableIsAttribute(info); // The following assertion is only valid for non-matrix attributes. if (info->attributeLocationCount == 1) { const AliasingAttributeMap *aliasingMap = &mAliasingAttributeMap[info->location]; ValidateIsAliasingAttribute(aliasingMap, id); } continue; } interfaceList[writeIndex] = id; ++writeIndex; } // Update the number of interface variables. interfaceList.resize_down(writeIndex); // Write the entry point with the aliasing attributes removed. spirv::WriteEntryPoint(mSpirvBlobOut, executionModel, ID::EntryPoint, name, interfaceList); return TransformationState::Transformed; } TransformationState SpirvVertexAttributeAliasingTransformer::transformExtInst( const uint32_t *instruction) { sh::vk::spirv::NonSemanticInstruction inst; if (!mNonSemanticInstructions.visitExtInst(instruction, &inst)) { return TransformationState::Unchanged; } switch (inst) { case sh::vk::spirv::kNonSemanticOverview: // Declare the expanded matrix variables declareExpandedMatrixVectors(); break; case sh::vk::spirv::kNonSemanticEnter: // The matrix attribute declarations have been changed to have Private storage class, // and they are initialized from the expanded (and potentially aliased) Input vectors. // This is done at the beginning of the entry point. writeExpandedMatrixInitialization(); break; case sh::vk::spirv::kNonSemanticOutput: case sh::vk::spirv::kNonSemanticTransformFeedbackEmulation: // Unused by this transformation break; default: UNREACHABLE(); break; } // Drop the instruction if this is the last pass return mNonSemanticInstructions.transformExtInst(instruction); } TransformationState SpirvVertexAttributeAliasingTransformer::transformName( const uint32_t *instruction) { spirv::IdRef id; spirv::LiteralString name; spirv::ParseName(instruction, &id, &name); // If id is not that of an aliasing attribute, there's nothing to do. ASSERT(id < mIsAliasingAttributeById.size()); if (!mIsAliasingAttributeById[id]) { return TransformationState::Unchanged; } // Drop debug annotations for this id. return TransformationState::Transformed; } TransformationState SpirvVertexAttributeAliasingTransformer::transformDecorate( const uint32_t *instruction) { spirv::IdRef id; spv::Decoration decoration; spirv::ParseDecorate(instruction, &id, &decoration, nullptr); if (isMatrixAttribute(id)) { // If it's a matrix attribute, it's expanded to multiple vectors. Insert the Location // decorations for these vectors here. // Keep all decorations except for Location. if (decoration != spv::DecorationLocation) { return TransformationState::Unchanged; } const ShaderInterfaceVariableInfo *info = mVariableInfoById[id]; ValidateShaderInterfaceVariableIsAttribute(info); const spirv::IdRef vec0Id(mExpandedMatrixFirstVectorIdById[id]); ASSERT(vec0Id.valid()); for (uint32_t offset = 0; offset < info->attributeLocationCount; ++offset) { const spirv::IdRef vecId(vec0Id + offset); if (mIsAliasingAttributeById[vecId]) { continue; } spirv::WriteDecorate(mSpirvBlobOut, vecId, decoration, {spirv::LiteralInteger(info->location + offset)}); } } else { // If id is not that of an active attribute, there's nothing to do. const ShaderInterfaceVariableInfo *info = mVariableInfoById[id]; if (info == nullptr || info->attributeComponentCount == 0 || !info->activeStages[gl::ShaderType::Vertex]) { return TransformationState::Unchanged; } // Always drop RelaxedPrecision from input attributes. The temporary variable the attribute // is loaded into has RelaxedPrecision and will implicitly convert. if (decoration == spv::DecorationRelaxedPrecision) { return TransformationState::Transformed; } // If id is not that of an aliasing attribute, there's nothing else to do. ASSERT(id < mIsAliasingAttributeById.size()); if (!mIsAliasingAttributeById[id]) { return TransformationState::Unchanged; } } // Drop every decoration for this id. return TransformationState::Transformed; } TransformationState SpirvVertexAttributeAliasingTransformer::transformVariable( const uint32_t *instruction) { spirv::IdResultType typeId; spirv::IdResult id; spv::StorageClass storageClass; spirv::ParseVariable(instruction, &typeId, &id, &storageClass, nullptr); if (!isMatrixAttribute(id)) { // If id is not that of an aliasing attribute, there's nothing to do. Note that matrix // declarations are always replaced. ASSERT(id < mIsAliasingAttributeById.size()); if (!mIsAliasingAttributeById[id]) { return TransformationState::Unchanged; } } ASSERT(storageClass == spv::StorageClassInput); // Drop the declaration. return TransformationState::Transformed; } TransformationState SpirvVertexAttributeAliasingTransformer::transformAccessChain( const uint32_t *instruction) { spirv::IdResultType typeId; spirv::IdResult id; spirv::IdRef baseId; spirv::IdRefList indexList; spirv::ParseAccessChain(instruction, &typeId, &id, &baseId, &indexList); if (isMatrixAttribute(baseId)) { // Write a modified OpAccessChain instruction. Only modification is that the %type is // replaced with the Private version of it. If there is one %index, that would be a vector // type, and if there are two %index'es, it's a float type. spirv::IdRef replacementTypeId; if (indexList.size() == 1) { // If indexed once, it uses a vector type. const ShaderInterfaceVariableInfo *info = mVariableInfoById[baseId]; ValidateShaderInterfaceVariableIsAttribute(info); const uint32_t componentCount = info->attributeComponentCount; // %type must have been the Input vector type with the matrice's component size. ASSERT(typeId == mInputTypePointers[componentCount]); // Replace the type with the corresponding Private one. replacementTypeId = mPrivateFloatTypePointers[componentCount]; } else { // If indexed twice, it uses the float type. ASSERT(indexList.size() == 2); // Replace the type with the Private pointer to float32. replacementTypeId = mPrivateFloatTypePointers[1]; } spirv::WriteAccessChain(mSpirvBlobOut, replacementTypeId, id, baseId, indexList); } else { // If base id is not that of an aliasing attribute, there's nothing to do. ASSERT(baseId < mIsAliasingAttributeById.size()); if (!mIsAliasingAttributeById[baseId]) { return TransformationState::Unchanged; } // Find the replacement attribute for the aliasing one. const spirv::IdRef replacementId(getAliasingAttributeReplacementId(baseId, 0)); // Get variable info corresponding to the replacement attribute. const ShaderInterfaceVariableInfo *replacementInfo = mVariableInfoById[replacementId]; ValidateShaderInterfaceVariableIsAttribute(replacementInfo); // Write a modified OpAccessChain instruction. Currently, the instruction is: // // %id = OpAccessChain %type %base %index // // This is modified to: // // %id = OpAccessChain %type %replacement %index // // Note that the replacement has at least as many components as the aliasing attribute, // and both attributes start at component 0 (GLSL ES restriction). So, indexing the // replacement attribute with the same index yields the same result and type. spirv::WriteAccessChain(mSpirvBlobOut, typeId, id, replacementId, indexList); } return TransformationState::Transformed; } void SpirvVertexAttributeAliasingTransformer::transformLoadHelper(spirv::IdRef pointerId, spirv::IdRef typeId, spirv::IdRef replacementId, spirv::IdRef resultId) { // Get variable info corresponding to the replacement attribute. const ShaderInterfaceVariableInfo *replacementInfo = mVariableInfoById[replacementId]; ValidateShaderInterfaceVariableIsAttribute(replacementInfo); // Currently, the instruction is: // // %id = OpLoad %type %pointer // // This is modified to: // // %newId = OpLoad %replacementType %replacement // const spirv::IdRef loadResultId(getNewId()); const spirv::IdRef replacementTypeId(floatType(replacementInfo->attributeComponentCount)); ASSERT(replacementTypeId.valid()); spirv::WriteLoad(mSpirvBlobOut, replacementTypeId, loadResultId, replacementId, nullptr); // If swizzle is not necessary, assign %newId to %resultId. const ShaderInterfaceVariableInfo *aliasingInfo = mVariableInfoById[pointerId]; if (aliasingInfo->attributeComponentCount == replacementInfo->attributeComponentCount) { spirv::WriteCopyObject(mSpirvBlobOut, typeId, resultId, loadResultId); return; } // Take as many components from the replacement as the aliasing attribute wanted. This is done // by either of the following instructions: // // - If aliasing attribute has only one component: // // %resultId = OpCompositeExtract %floatType %newId 0 // // - If aliasing attribute has more than one component: // // %resultId = OpVectorShuffle %vecType %newId %newId 0 1 ... // ASSERT(aliasingInfo->attributeComponentCount < replacementInfo->attributeComponentCount); ASSERT(floatType(aliasingInfo->attributeComponentCount) == typeId); if (aliasingInfo->attributeComponentCount == 1) { spirv::WriteCompositeExtract(mSpirvBlobOut, typeId, resultId, loadResultId, {spirv::LiteralInteger(0)}); } else { spirv::LiteralIntegerList swizzle = {spirv::LiteralInteger(0), spirv::LiteralInteger(1), spirv::LiteralInteger(2), spirv::LiteralInteger(3)}; swizzle.resize_down(aliasingInfo->attributeComponentCount); spirv::WriteVectorShuffle(mSpirvBlobOut, typeId, resultId, loadResultId, loadResultId, swizzle); } } TransformationState SpirvVertexAttributeAliasingTransformer::transformLoad( const uint32_t *instruction) { spirv::IdResultType typeId; spirv::IdResult id; spirv::IdRef pointerId; ParseLoad(instruction, &typeId, &id, &pointerId, nullptr); // Currently, the instruction is: // // %id = OpLoad %type %pointer // // If non-matrix, this is modifed to load from the aliasing vector instead if aliasing. // // If matrix, this is modified such that %type points to the Private version of it. // if (isMatrixAttribute(pointerId)) { const ShaderInterfaceVariableInfo *info = mVariableInfoById[pointerId]; ValidateShaderInterfaceVariableIsAttribute(info); const spirv::IdRef replacementTypeId(matrixType(info->attributeLocationCount)); spirv::WriteLoad(mSpirvBlobOut, replacementTypeId, id, pointerId, nullptr); } else { // If pointer id is not that of an aliasing attribute, there's nothing to do. ASSERT(pointerId < mIsAliasingAttributeById.size()); if (!mIsAliasingAttributeById[pointerId]) { return TransformationState::Unchanged; } // Find the replacement attribute for the aliasing one. const spirv::IdRef replacementId(getAliasingAttributeReplacementId(pointerId, 0)); // Replace the load instruction by a load from the replacement id. transformLoadHelper(pointerId, typeId, replacementId, id); } return TransformationState::Transformed; } void SpirvVertexAttributeAliasingTransformer::declareExpandedMatrixVectors() { // Go through matrix attributes and expand them. for (uint32_t matrixIdIndex = spirv::kMinValidId; matrixIdIndex < mExpandedMatrixFirstVectorIdById.size(); ++matrixIdIndex) { const spirv::IdRef matrixId(matrixIdIndex); if (!mExpandedMatrixFirstVectorIdById[matrixId].valid()) { continue; } const spirv::IdRef vec0Id(mExpandedMatrixFirstVectorIdById[matrixId]); const ShaderInterfaceVariableInfo *info = mVariableInfoById[matrixId]; ValidateShaderInterfaceVariableIsAttribute(info); // Need to generate the following: // // %privateType = OpTypePointer Private %matrixType // %id = OpVariable %privateType Private // %vecType = OpTypePointer %vecType Input // %vec0 = OpVariable %vecType Input // ... // %vecN-1 = OpVariable %vecType Input const uint32_t componentCount = info->attributeComponentCount; const uint32_t locationCount = info->attributeLocationCount; ASSERT(componentCount == locationCount); // OpTypePointer Private %matrixType spirv::IdRef privateType(mPrivateMatrixTypePointers[locationCount]); if (!privateType.valid()) { privateType = getNewId(); mPrivateMatrixTypePointers[locationCount] = privateType; spirv::WriteTypePointer(mSpirvBlobOut, privateType, spv::StorageClassPrivate, matrixType(locationCount)); } // OpVariable %privateType Private spirv::WriteVariable(mSpirvBlobOut, privateType, matrixId, spv::StorageClassPrivate, nullptr); // If the OpTypePointer is not declared for the vector type corresponding to each location, // declare it now. // // %vecType = OpTypePointer %vecType Input spirv::IdRef inputType(mInputTypePointers[componentCount]); if (!inputType.valid()) { inputType = getNewId(); mInputTypePointers[componentCount] = inputType; spirv::WriteTypePointer(mSpirvBlobOut, inputType, spv::StorageClassInput, floatType(componentCount)); } // Declare a vector for each column of the matrix. for (uint32_t offset = 0; offset < info->attributeLocationCount; ++offset) { const spirv::IdRef vecId(vec0Id + offset); if (!mIsAliasingAttributeById[vecId]) { spirv::WriteVariable(mSpirvBlobOut, inputType, vecId, spv::StorageClassInput, nullptr); } } } // Additionally, declare OpTypePointer Private %floatType(i) in case needed (used in // Op*AccessChain instructions, if any). for (uint32_t n = 1; n <= 4; ++n) { if (!mPrivateFloatTypePointers[n].valid()) { const spirv::IdRef privateType(getNewId()); mPrivateFloatTypePointers[n] = privateType; spirv::WriteTypePointer(mSpirvBlobOut, privateType, spv::StorageClassPrivate, floatType(n)); } } } void SpirvVertexAttributeAliasingTransformer::writeExpandedMatrixInitialization() { // Go through matrix attributes and initialize them. Note that their declaration is replaced // with a Private storage class, but otherwise has the same id. for (uint32_t matrixIdIndex = spirv::kMinValidId; matrixIdIndex < mExpandedMatrixFirstVectorIdById.size(); ++matrixIdIndex) { const spirv::IdRef matrixId(matrixIdIndex); if (!mExpandedMatrixFirstVectorIdById[matrixId].valid()) { continue; } const spirv::IdRef vec0Id(mExpandedMatrixFirstVectorIdById[matrixId]); // For every matrix, need to generate the following: // // %vec0Id = OpLoad %vecType %vec0Pointer // ... // %vecN-1Id = OpLoad %vecType %vecN-1Pointer // %mat = OpCompositeConstruct %matrixType %vec0 ... %vecN-1 // OpStore %matrixId %mat const ShaderInterfaceVariableInfo *info = mVariableInfoById[matrixId]; ValidateShaderInterfaceVariableIsAttribute(info); spirv::IdRefList vecLoadIds; const uint32_t locationCount = info->attributeLocationCount; for (uint32_t offset = 0; offset < locationCount; ++offset) { const spirv::IdRef vecId(vec0Id + offset); // Load into temporary, potentially through an aliasing vector. spirv::IdRef replacementId(vecId); ASSERT(vecId < mIsAliasingAttributeById.size()); if (mIsAliasingAttributeById[vecId]) { replacementId = getAliasingAttributeReplacementId(vecId, offset); } // Write a load instruction from the replacement id. vecLoadIds.push_back(getNewId()); transformLoadHelper(matrixId, floatType(info->attributeComponentCount), replacementId, vecLoadIds.back()); } // Aggregate the vector loads into a matrix. const spirv::IdRef compositeId(getNewId()); spirv::WriteCompositeConstruct(mSpirvBlobOut, matrixType(locationCount), compositeId, vecLoadIds); // Store it in the private variable. spirv::WriteStore(mSpirvBlobOut, matrixId, compositeId, nullptr); } } } // anonymous namespace SpvSourceOptions SpvCreateSourceOptions(const angle::FeaturesVk &features, uint32_t maxColorInputAttachmentCount) { SpvSourceOptions options; options.maxColorInputAttachmentCount = maxColorInputAttachmentCount; options.supportsTransformFeedbackExtension = features.supportsTransformFeedbackExtension.enabled; options.supportsTransformFeedbackEmulation = features.emulateTransformFeedback.enabled; options.enableTransformFeedbackEmulation = options.supportsTransformFeedbackEmulation; options.supportsDepthStencilInputAttachments = features.supportsShaderFramebufferFetchDepthStencil.enabled; return options; } uint32_t SpvGetXfbBufferBlockId(const uint32_t bufferIndex) { ASSERT(bufferIndex < 4); static_assert(sh::vk::spirv::ReservedIds::kIdXfbEmulationBufferBlockOne == sh::vk::spirv::ReservedIds::kIdXfbEmulationBufferBlockZero + 1); static_assert(sh::vk::spirv::ReservedIds::kIdXfbEmulationBufferBlockTwo == sh::vk::spirv::ReservedIds::kIdXfbEmulationBufferBlockZero + 2); static_assert(sh::vk::spirv::ReservedIds::kIdXfbEmulationBufferBlockThree == sh::vk::spirv::ReservedIds::kIdXfbEmulationBufferBlockZero + 3); return sh::vk::spirv::ReservedIds::kIdXfbEmulationBufferBlockZero + bufferIndex; } void SpvAssignLocations(const SpvSourceOptions &options, const gl::ProgramExecutable &programExecutable, const gl::ProgramVaryingPacking &varyingPacking, const gl::ShaderType transformFeedbackStage, SpvProgramInterfaceInfo *programInterfaceInfo, ShaderInterfaceVariableInfoMap *variableInfoMapOut) { const gl::ShaderBitSet shaderStages = programExecutable.getLinkedShaderStages(); // Assign outputs to the fragment shader, if any. if (shaderStages[gl::ShaderType::Fragment] && programExecutable.hasLinkedShaderStage(gl::ShaderType::Fragment)) { AssignOutputLocations(programExecutable, gl::ShaderType::Fragment, variableInfoMapOut); } // Assign attributes to the vertex shader, if any. if (shaderStages[gl::ShaderType::Vertex] && programExecutable.hasLinkedShaderStage(gl::ShaderType::Vertex)) { AssignAttributeLocations(programExecutable, gl::ShaderType::Vertex, variableInfoMapOut); if (options.supportsTransformFeedbackEmulation) { // If transform feedback emulation is not enabled, mark all transform feedback output // buffers as inactive. const bool isTransformFeedbackStage = transformFeedbackStage == gl::ShaderType::Vertex && options.enableTransformFeedbackEmulation && !programExecutable.getLinkedTransformFeedbackVaryings().empty(); AssignTransformFeedbackEmulationBindings(gl::ShaderType::Vertex, programExecutable, isTransformFeedbackStage, programInterfaceInfo, variableInfoMapOut); } } gl::ShaderType frontShaderType = gl::ShaderType::InvalidEnum; for (const gl::ShaderType shaderType : shaderStages) { if (programExecutable.hasLinkedGraphicsShader()) { const gl::VaryingPacking &inputPacking = varyingPacking.getInputPacking(shaderType); const gl::VaryingPacking &outputPacking = varyingPacking.getOutputPacking(shaderType); // Assign varying locations. if (shaderType != gl::ShaderType::Vertex) { AssignVaryingLocations(options, inputPacking, shaderType, frontShaderType, programInterfaceInfo, variableInfoMapOut); // Record active members of in gl_PerVertex. if (shaderType != gl::ShaderType::Fragment && frontShaderType != gl::ShaderType::InvalidEnum) { // If an output builtin is active in the previous stage, assume it's active in // the input of the current stage as well. const gl::ShaderMap &outputPerVertexActiveMembers = inputPacking.getOutputPerVertexActiveMembers(); variableInfoMapOut->setInputPerVertexActiveMembers( shaderType, outputPerVertexActiveMembers[frontShaderType]); } } if (shaderType != gl::ShaderType::Fragment) { AssignVaryingLocations(options, outputPacking, shaderType, frontShaderType, programInterfaceInfo, variableInfoMapOut); // Record active members of out gl_PerVertex. const gl::ShaderMap &outputPerVertexActiveMembers = outputPacking.getOutputPerVertexActiveMembers(); variableInfoMapOut->setOutputPerVertexActiveMembers( shaderType, outputPerVertexActiveMembers[shaderType]); } // Assign qualifiers to all varyings captured by transform feedback if (!programExecutable.getLinkedTransformFeedbackVaryings().empty() && shaderType == programExecutable.getLinkedTransformFeedbackStage()) { AssignTransformFeedbackQualifiers(programExecutable, outputPacking, shaderType, options.supportsTransformFeedbackExtension, variableInfoMapOut); } } frontShaderType = shaderType; } AssignUniformBindings(options, programExecutable, programInterfaceInfo, variableInfoMapOut); AssignTextureBindings(options, programExecutable, programInterfaceInfo, variableInfoMapOut); AssignNonTextureBindings(options, programExecutable, programInterfaceInfo, variableInfoMapOut); } void SpvAssignTransformFeedbackLocations(gl::ShaderType shaderType, const gl::ProgramExecutable &programExecutable, bool isTransformFeedbackStage, SpvProgramInterfaceInfo *programInterfaceInfo, ShaderInterfaceVariableInfoMap *variableInfoMapOut) { // The only varying that requires additional resources is gl_Position, as it's indirectly // captured through ANGLEXfbPosition. const std::vector &tfVaryings = programExecutable.getLinkedTransformFeedbackVaryings(); bool capturesPosition = false; if (isTransformFeedbackStage) { for (uint32_t varyingIndex = 0; varyingIndex < tfVaryings.size(); ++varyingIndex) { const gl::TransformFeedbackVarying &tfVarying = tfVaryings[varyingIndex]; const std::string &tfVaryingName = tfVarying.name; if (tfVaryingName == "gl_Position") { ASSERT(tfVarying.isBuiltIn()); capturesPosition = true; break; } } } if (capturesPosition) { AddLocationInfo(variableInfoMapOut, shaderType, sh::vk::spirv::kIdXfbExtensionPosition, programInterfaceInfo->locationsUsedForXfbExtension, 0, 0, 0); ++programInterfaceInfo->locationsUsedForXfbExtension; } else { // Make sure this varying is removed from the other stages, or if position is not captured // at all. variableInfoMapOut->add(shaderType, sh::vk::spirv::kIdXfbExtensionPosition); } } void SpvGetShaderSpirvCode(const gl::ProgramState &programState, gl::ShaderMap *spirvBlobsOut) { for (const gl::ShaderType shaderType : gl::AllShaderTypes()) { const gl::SharedCompiledShaderState &glShader = programState.getAttachedShader(shaderType); (*spirvBlobsOut)[shaderType] = glShader ? &glShader->compiledBinary : nullptr; } } void SpvAssignAllLocations(const SpvSourceOptions &options, const gl::ProgramState &programState, const gl::ProgramLinkedResources &resources, ShaderInterfaceVariableInfoMap *variableInfoMapOut) { SpvProgramInterfaceInfo spvProgramInterfaceInfo = {}; const gl::ProgramExecutable &programExecutable = programState.getExecutable(); gl::ShaderType xfbStage = programState.getAttachedTransformFeedbackStage(); // This should be done before assigning varying location. Otherwise, We can encounter shader // interface mismatching problem in case the transformFeedback stage is not Vertex stage. for (const gl::ShaderType shaderType : programExecutable.getLinkedShaderStages()) { // Assign location to varyings generated for transform feedback capture const bool isXfbStage = shaderType == xfbStage && !programExecutable.getLinkedTransformFeedbackVaryings().empty(); if (options.supportsTransformFeedbackExtension && gl::ShaderTypeSupportsTransformFeedback(shaderType)) { SpvAssignTransformFeedbackLocations(shaderType, programExecutable, isXfbStage, &spvProgramInterfaceInfo, variableInfoMapOut); } } SpvAssignLocations(options, programExecutable, resources.varyingPacking, xfbStage, &spvProgramInterfaceInfo, variableInfoMapOut); } angle::Result SpvTransformSpirvCode(const SpvTransformOptions &options, const ShaderInterfaceVariableInfoMap &variableInfoMap, const spirv::Blob &initialSpirvBlob, spirv::Blob *spirvBlobOut) { if (initialSpirvBlob.empty()) { return angle::Result::Continue; } const bool hasAliasingAttributes = options.shaderType == gl::ShaderType::Vertex && variableInfoMap.hasAliasingAttributes(); // Transform the SPIR-V code by assigning location/set/binding values. SpirvTransformer transformer(initialSpirvBlob, options, !hasAliasingAttributes, variableInfoMap, spirvBlobOut); transformer.transform(); // If there are aliasing vertex attributes, transform the SPIR-V again to remove them. if (hasAliasingAttributes) { spirv::Blob preTransformBlob = std::move(*spirvBlobOut); SpirvVertexAttributeAliasingTransformer aliasingTransformer( preTransformBlob, variableInfoMap, std::move(transformer.getVariableInfoByIdMap()), spirvBlobOut); aliasingTransformer.transform(); } spirvBlobOut->shrink_to_fit(); if (options.validate) { ASSERT(spirv::Validate(*spirvBlobOut)); } return angle::Result::Continue; } } // namespace rx