| // |
| // 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 <array> |
| #include <cctype> |
| #include <numeric> |
| |
| #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 |
| { |
| template <size_t N> |
| constexpr size_t ConstStrLen(const char (&)[N]) |
| { |
| static_assert(N > 0, "C++ shouldn't allow N to be zero"); |
| |
| // The length of a string defined as a char array is the size of the array minus 1 (the |
| // terminating '\0'). |
| return N - 1; |
| } |
| |
| // 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; |
| } |
| |
| template <typename OutputIter, typename ImplicitIter> |
| 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<size_t>(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<uint32_t>(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<std::string, 2> 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<uint8_t>(gl::VariableColumnCount(attribute.getType())); |
| const uint8_t rowCount = static_cast<uint8_t>(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<std::string, 2> 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<std::string, 3> 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<std::vector<uint32_t>> &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<gl::TransformFeedbackVarying> &tfVaryings = |
| programExecutable.getLinkedTransformFeedbackVaryings(); |
| const std::vector<GLsizei> &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<const char *, kPerVertexMemberCount> 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/4140 |
| 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) || |
| !programExecutable.usesFramebufferFetch()) |
| { |
| return; |
| } |
| |
| const uint32_t baseInputAttachmentBindingIndex = |
| programInterfaceInfo->currentShaderResourceBindingIndex; |
| const gl::ShaderBitSet activeShaders{gl::ShaderType::Fragment}; |
| |
| static_assert(gl::IMPLEMENTATION_MAX_DRAW_BUFFERS <= 8, |
| "sh::vk::spirv::ReservedIds supports max 8 draw buffers"); |
| |
| for (size_t index : programExecutable.getFragmentInoutIndices()) |
| { |
| const uint32_t inputAttachmentBindingIndex = |
| baseInputAttachmentBindingIndex + static_cast<uint32_t>(index); |
| |
| AddResourceInfo(variableInfoMapOut, activeShaders, gl::ShaderType::Fragment, |
| sh::vk::spirv::kIdInputAttachment0 + static_cast<uint32_t>(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 += gl::IMPLEMENTATION_MAX_DRAW_BUFFERS; |
| } |
| |
| void AssignInterfaceBlockBindings(const SpvSourceOptions &options, |
| const gl::ProgramExecutable &programExecutable, |
| const std::vector<gl::InterfaceBlock> &blocks, |
| |
| SpvProgramInterfaceInfo *programInterfaceInfo, |
| ShaderInterfaceVariableInfoMap *variableInfoMapOut) |
| { |
| for (uint32_t blockIndex = 0; blockIndex < blocks.size(); ++blockIndex) |
| { |
| const gl::InterfaceBlock &block = blocks[blockIndex]; |
| |
| // TODO: http://anglebug.com/4523: 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<gl::AtomicCounterBuffer> &buffers = |
| programExecutable.getAtomicCounterBuffers(); |
| |
| if (buffers.size() == 0) |
| { |
| return; |
| } |
| |
| const gl::ShaderBitSet activeShaders = programExecutable.getLinkedShaderStages(); |
| ASSERT(activeShaders.any()); |
| |
| gl::ShaderMap<uint32_t> 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<gl::LinkedUniform> &uniforms = programExecutable.getUniforms(); |
| const gl::RangeUI &imageUniformRange = programExecutable.getImageUniformRange(); |
| for (unsigned int uniformIndex : imageUniformRange) |
| { |
| const gl::LinkedUniform &imageUniform = uniforms[uniformIndex]; |
| |
| // TODO: http://anglebug.com/4523: 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<gl::InterfaceBlock> &uniformBlocks = programExecutable.getUniformBlocks(); |
| AssignInterfaceBlockBindings(options, programExecutable, uniformBlocks, programInterfaceInfo, |
| variableInfoMapOut); |
| |
| const std::vector<gl::InterfaceBlock> &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<gl::LinkedUniform> &uniforms = programExecutable.getUniforms(); |
| const gl::RangeUI &samplerUniformRange = programExecutable.getSamplerUniformRange(); |
| |
| for (unsigned int uniformIndex : samplerUniformRange) |
| { |
| const gl::LinkedUniform &samplerUniform = uniforms[uniformIndex]; |
| |
| // TODO: http://anglebug.com/4523: 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; |
| } |
| |
| // 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<const ShaderInterfaceVariableInfo *> &getVariableInfoByIdMap() |
| { |
| return mVariableInfoById; |
| } |
| |
| static spirv::IdRef GetNewId(spirv::Blob *blob); |
| spirv::IdRef getNewId(); |
| |
| 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: |
| |
| // Shader variable info per id, if id is a shader variable. |
| std::vector<const ShaderInterfaceVariableInfo *> 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; |
| } |
| |
| 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<sh::vk::spirv::NonSemanticInstruction>(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 <Index> BuiltIn <Member>, the |
| // <Index> 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<const ShaderInterfaceVariableInfo *> &variableInfoById, |
| gl::ShaderType shaderType, |
| 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<spirv::IdRef> mTypePointerTransformedId; |
| |
| // Whether a variable has been marked inactive. |
| std::vector<bool> 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<const ShaderInterfaceVariableInfo *> &variableInfoById, |
| gl::ShaderType shaderType, |
| spirv::IdRefList *interfaceList) |
| { |
| // 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(spirv::IdRefList *interfaceList); |
| void addDecorate(spirv::IdRef replacedId, spirv::Blob *blobOut); |
| void writeInputPreamble( |
| const std::vector<const ShaderInterfaceVariableInfo *> &variableInfoById, |
| gl::ShaderType shaderType, |
| spirv::Blob *blobOut); |
| void writeOutputPrologue( |
| const std::vector<const ShaderInterfaceVariableInfo *> &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<spirv::IdRef> mTypePointerTypeId; |
| std::vector<spirv::IdRef> mFixedVaryingId; |
| std::vector<spirv::IdRef> 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<const ShaderInterfaceVariableInfo *> &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(spirv::IdRefList *interfaceList) |
| { |
| // Modify interface list if any ID was replaced due to varying precision mismatch. |
| for (size_t index = 0; index < interfaceList->size(); ++index) |
| { |
| (*interfaceList)[index] = getReplacementId((*interfaceList)[index]); |
| } |
| } |
| |
| void SpirvVaryingPrecisionFixer::addDecorate(spirv::IdRef replacedId, spirv::Blob *blobOut) |
| { |
| spirv::WriteDecorate(blobOut, replacedId, spv::DecorationRelaxedPrecision, {}); |
| } |
| |
| void SpirvVaryingPrecisionFixer::writeOutputPrologue( |
| const std::vector<const ShaderInterfaceVariableInfo *> &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(bool isEmulated) |
| : mIsEmulated(isEmulated), |
| 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, |
| spirv::IdResultType typeId, |
| spirv::IdResult id, |
| spv::StorageClass storageClass); |
| |
| void writePendingDeclarations( |
| const std::vector<const ShaderInterfaceVariableInfo *> &variableInfoById, |
| 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<spirv::IdRef> 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<spirv::IdRef, 4> 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<std::vector<XfbVarying>> 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, |
| 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 == spv::StorageClassUniform) |
| { |
| // 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<uint32_t>(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::writePendingDeclarations( |
| const std::vector<const ShaderInterfaceVariableInfo *> &variableInfoById, |
| 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, spv::StorageClassUniform, 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<XfbVarying> &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<spirv::IdRef, 4> 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<XfbVarying> &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<XfbVarying> &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, |
| spirv::IdRefList *interfaceList, |
| spirv::Blob *blobOut); |
| |
| void writePendingDeclarations( |
| const SpirvNonSemanticInstructions &nonSemantic, |
| const std::vector<const ShaderInterfaceVariableInfo *> &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<bool> skipMemberSampleDecoration; |
| }; |
| std::vector<VaryingInfo> 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<const ShaderInterfaceVariableInfo *> &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, |
| 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 && |
| decoration == spv::DecorationInputAttachmentIndex && !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<const ShaderInterfaceVariableInfo *> &variableInfoById, |
| spirv::IdRefList *interfaceList, |
| spirv::Blob *blobOut); |
| |
| void writeOutputPrologue( |
| const std::vector<const ShaderInterfaceVariableInfo *> &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<spirv::IdRef> 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<const ShaderInterfaceVariableInfo *> &variableInfoById, |
| 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]; |
| |
| ASSERT(info); |
| if (info->index != 1 || !info->isArray) |
| { |
| continue; |
| } |
| |
| mArrayVariableId = id; |
| mReplacementVariableId = SpirvTransformerBase::GetNewId(blobOut); |
| (*interfaceList)[index] = 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<const ShaderInterfaceVariableInfo *> &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.isTransformFeedbackEmulated), |
| 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, 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, |
| &interfaceList); |
| |
| if (mOptions.shaderType == gl::ShaderType::Fragment) |
| { |
| mSecondaryOutputTransformer.modifyEntryPointInterfaceList(mVariableInfoById, &interfaceList, |
| mSpirvBlobOut); |
| } |
| |
| if (mOptions.useSpirvVaryingPrecisionFixer) |
| { |
| mVaryingPrecisionFixer.modifyEntryPointInterfaceList(&interfaceList); |
| } |
| |
| mMultisampleTransformer.modifyEntryPointInterfaceList(mNonSemanticInstructions, &interfaceList, |
| mSpirvBlobOut); |
| |
| // 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, 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<spirv::IdRef> 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<const ShaderInterfaceVariableInfo *> &&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<AliasingAttributeMap> mAliasingAttributeMap; |
| |
| // For each id, this map indicates whether it refers to an aliasing attribute that needs to be |
| // removed. |
| std::vector<bool> 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<spirv::IdRef> 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<spirv::IdRef, 5> mInputTypePointers; |
| |
| // Corresponding to floatType(), [i]: id of OpTypePointer Private %floatType(i). [0] is |
| // unused. |
| std::array<spirv::IdRef, 5> mPrivateFloatTypePointers; |
| |
| // Corresponding to matrixType(), [i]: id of OpTypePointer Private %matrixType(i). [0] and |
| // [1] are unused. |
| std::array<spirv::IdRef, 5> 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); |
| } |
| } |
| |
| // 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) |
| { |
| SpvSourceOptions options; |
| |
| options.supportsTransformFeedbackExtension = |
| features.supportsTransformFeedbackExtension.enabled; |
| options.supportsTransformFeedbackEmulation = features.emulateTransformFeedback.enabled; |
| options.enableTransformFeedbackEmulation = options.supportsTransformFeedbackEmulation; |
| |
| 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<gl::PerVertexMemberBitSet> &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<gl::PerVertexMemberBitSet> &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<gl::TransformFeedbackVarying> &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<const spirv::Blob *> *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 |