| // |
| // Copyright 2019 The ANGLE Project Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| // |
| // Wrapper for Khronos glslang compiler. |
| // |
| |
| #include "libANGLE/renderer/glslang_wrapper_utils.h" |
| |
| // glslang has issues with some specific warnings. |
| ANGLE_DISABLE_EXTRA_SEMI_WARNING |
| ANGLE_DISABLE_SHADOWING_WARNING |
| ANGLE_DISABLE_SUGGEST_OVERRIDE_WARNINGS |
| |
| // glslang's version of ShaderLang.h, not to be confused with ANGLE's. |
| #include <glslang/Public/ShaderLang.h> |
| |
| // Other glslang includes. |
| #include <SPIRV/GlslangToSpv.h> |
| #include <StandAlone/ResourceLimits.h> |
| |
| ANGLE_REENABLE_SUGGEST_OVERRIDE_WARNINGS |
| ANGLE_REENABLE_SHADOWING_WARNING |
| ANGLE_REENABLE_EXTRA_SEMI_WARNING |
| |
| #include <array> |
| #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/trace.h" |
| |
| #define ANGLE_GLSLANG_CHECK(CALLBACK, TEST, ERR) \ |
| do \ |
| { \ |
| if (ANGLE_UNLIKELY(!(TEST))) \ |
| { \ |
| return CALLBACK(ERR); \ |
| } \ |
| \ |
| } while (0) |
| |
| // Enable this for debug logging of pre-transform SPIR-V: |
| #if !defined(ANGLE_DEBUG_SPIRV_TRANSFORMER) |
| # define ANGLE_DEBUG_SPIRV_TRANSFORMER 0 |
| #endif // !defined(ANGLE_DEBUG_SPIRV_TRANSFORMER) |
| |
| 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; |
| } |
| |
| void GetBuiltInResourcesFromCaps(const gl::Caps &caps, TBuiltInResource *outBuiltInResources) |
| { |
| outBuiltInResources->maxDrawBuffers = caps.maxDrawBuffers; |
| outBuiltInResources->maxAtomicCounterBindings = caps.maxAtomicCounterBufferBindings; |
| outBuiltInResources->maxAtomicCounterBufferSize = caps.maxAtomicCounterBufferSize; |
| outBuiltInResources->maxClipPlanes = caps.maxClipPlanes; |
| outBuiltInResources->maxCombinedAtomicCounterBuffers = caps.maxCombinedAtomicCounterBuffers; |
| outBuiltInResources->maxCombinedAtomicCounters = caps.maxCombinedAtomicCounters; |
| outBuiltInResources->maxCombinedImageUniforms = caps.maxCombinedImageUniforms; |
| outBuiltInResources->maxCombinedTextureImageUnits = caps.maxCombinedTextureImageUnits; |
| outBuiltInResources->maxCombinedShaderOutputResources = caps.maxCombinedShaderOutputResources; |
| outBuiltInResources->maxComputeWorkGroupCountX = caps.maxComputeWorkGroupCount[0]; |
| outBuiltInResources->maxComputeWorkGroupCountY = caps.maxComputeWorkGroupCount[1]; |
| outBuiltInResources->maxComputeWorkGroupCountZ = caps.maxComputeWorkGroupCount[2]; |
| outBuiltInResources->maxComputeWorkGroupSizeX = caps.maxComputeWorkGroupSize[0]; |
| outBuiltInResources->maxComputeWorkGroupSizeY = caps.maxComputeWorkGroupSize[1]; |
| outBuiltInResources->maxComputeWorkGroupSizeZ = caps.maxComputeWorkGroupSize[2]; |
| outBuiltInResources->minProgramTexelOffset = caps.minProgramTexelOffset; |
| outBuiltInResources->maxFragmentUniformVectors = caps.maxFragmentUniformVectors; |
| outBuiltInResources->maxFragmentInputComponents = caps.maxFragmentInputComponents; |
| outBuiltInResources->maxGeometryInputComponents = caps.maxGeometryInputComponents; |
| outBuiltInResources->maxGeometryOutputComponents = caps.maxGeometryOutputComponents; |
| outBuiltInResources->maxGeometryOutputVertices = caps.maxGeometryOutputVertices; |
| outBuiltInResources->maxGeometryTotalOutputComponents = caps.maxGeometryTotalOutputComponents; |
| outBuiltInResources->maxPatchVertices = caps.maxPatchVertices; |
| outBuiltInResources->maxLights = caps.maxLights; |
| outBuiltInResources->maxProgramTexelOffset = caps.maxProgramTexelOffset; |
| outBuiltInResources->maxVaryingComponents = caps.maxVaryingComponents; |
| outBuiltInResources->maxVaryingVectors = caps.maxVaryingVectors; |
| outBuiltInResources->maxVertexAttribs = caps.maxVertexAttributes; |
| outBuiltInResources->maxVertexOutputComponents = caps.maxVertexOutputComponents; |
| outBuiltInResources->maxVertexUniformVectors = caps.maxVertexUniformVectors; |
| outBuiltInResources->maxClipDistances = caps.maxClipDistances; |
| outBuiltInResources->maxSamples = caps.maxSamples; |
| outBuiltInResources->maxCullDistances = caps.maxCullDistances; |
| outBuiltInResources->maxCombinedClipAndCullDistances = caps.maxCombinedClipAndCullDistances; |
| } |
| |
| // Run at startup to warm up glslang's internals to avoid hitches on first shader compile. |
| void GlslangWarmup() |
| { |
| ANGLE_TRACE_EVENT0("gpu.angle,startup", "GlslangWarmup"); |
| |
| EShMessages messages = static_cast<EShMessages>(EShMsgSpvRules | EShMsgVulkanRules); |
| // EShMessages messages = EShMsgDefault; |
| |
| const TBuiltInResource builtInResources(glslang::DefaultTBuiltInResource); |
| glslang::TShader warmUpShader(EShLangVertex); |
| |
| const char *kShaderString = R"(#version 450 core |
| void main(){} |
| )"; |
| const int kShaderLength = static_cast<int>(strlen(kShaderString)); |
| |
| warmUpShader.setStringsWithLengths(&kShaderString, &kShaderLength, 1); |
| warmUpShader.setEntryPoint("main"); |
| |
| bool result = warmUpShader.parse(&builtInResources, 450, ECoreProfile, false, false, messages); |
| ASSERT(result); |
| } |
| |
| bool IsRotationIdentity(SurfaceRotation rotation) |
| { |
| return rotation == SurfaceRotation::Identity || rotation == SurfaceRotation::FlippedIdentity; |
| } |
| |
| // 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; |
| } |
| |
| bool MappedSamplerNameNeedsUserDefinedPrefix(const std::string &originalName) |
| { |
| return originalName.find('.') == std::string::npos; |
| } |
| |
| template <typename OutputIter, typename ImplicitIter> |
| uint32_t CountExplicitOutputs(OutputIter outputsBegin, |
| OutputIter outputsEnd, |
| ImplicitIter implicitsBegin, |
| ImplicitIter implicitsEnd) |
| { |
| auto reduce = [implicitsBegin, implicitsEnd](uint32_t count, const sh::ShaderVariable &var) { |
| bool isExplicit = std::find(implicitsBegin, implicitsEnd, var.name) == implicitsEnd; |
| return count + isExplicit; |
| }; |
| |
| return std::accumulate(outputsBegin, outputsEnd, 0, reduce); |
| } |
| |
| ShaderInterfaceVariableInfo *AddResourceInfoToAllStages(ShaderInterfaceVariableInfoMap *infoMap, |
| gl::ShaderType shaderType, |
| const std::string &varName, |
| uint32_t descriptorSet, |
| uint32_t binding) |
| { |
| gl::ShaderBitSet allStages; |
| allStages.set(); |
| |
| ShaderInterfaceVariableInfo &info = infoMap->add(shaderType, varName); |
| info.descriptorSet = descriptorSet; |
| info.binding = binding; |
| info.activeStages = allStages; |
| return &info; |
| } |
| |
| ShaderInterfaceVariableInfo *AddResourceInfo(ShaderInterfaceVariableInfoMap *infoMap, |
| gl::ShaderType shaderType, |
| const std::string &varName, |
| uint32_t descriptorSet, |
| uint32_t binding) |
| { |
| gl::ShaderBitSet stages; |
| stages.set(shaderType); |
| |
| ShaderInterfaceVariableInfo &info = infoMap->add(shaderType, varName); |
| 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, |
| const std::string &varName, |
| uint32_t location, |
| uint32_t component, |
| uint8_t attributeComponentCount, |
| uint8_t attributeLocationCount) |
| { |
| // The info map for this name may or may not exist already. This function merges the |
| // location/component information. |
| ShaderInterfaceVariableInfo &info = infoMap->addOrGet(shaderType, varName); |
| |
| ASSERT(info.descriptorSet == ShaderInterfaceVariableInfo::kInvalid); |
| ASSERT(info.binding == ShaderInterfaceVariableInfo::kInvalid); |
| ASSERT(info.location == ShaderInterfaceVariableInfo::kInvalid); |
| ASSERT(info.component == ShaderInterfaceVariableInfo::kInvalid); |
| |
| info.location = location; |
| info.component = component; |
| info.activeStages.set(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 bool isStructField, |
| const uint32_t location, |
| const uint32_t component) |
| { |
| const std::string &name = isStructField ? ref.parentStructMappedName : ref.varying->mappedName; |
| AddLocationInfo(infoMap, ref.stage, name, location, component, 0, 0); |
| } |
| |
| // Modify an existing out variable and add transform feedback information. |
| ShaderInterfaceVariableInfo *SetXfbInfo(ShaderInterfaceVariableInfoMap *infoMap, |
| gl::ShaderType shaderType, |
| const std::string &varName, |
| 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) |
| { |
| ShaderInterfaceVariableInfo &info = infoMap->get(shaderType, varName); |
| 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->buffer == ShaderInterfaceVariableXfbInfo::kInvalid); |
| ASSERT(xfb->offset == ShaderInterfaceVariableXfbInfo::kInvalid); |
| ASSERT(xfb->stride == ShaderInterfaceVariableXfbInfo::kInvalid); |
| |
| if (arrayIndex != ShaderInterfaceVariableXfbInfo::kInvalid) |
| { |
| xfb->arrayElements.emplace_back(); |
| xfb = &xfb->arrayElements.back(); |
| } |
| |
| xfb->buffer = xfbBuffer; |
| xfb->offset = xfbOffset; |
| xfb->stride = xfbStride; |
| xfb->arraySize = arraySize; |
| xfb->columnCount = columnCount; |
| xfb->rowCount = rowCount; |
| xfb->arrayIndex = arrayIndex; |
| xfb->componentType = componentType; |
| |
| return &info; |
| } |
| |
| void AssignTransformFeedbackEmulationBindings(gl::ShaderType shaderType, |
| const gl::ProgramState &programState, |
| bool isTransformFeedbackStage, |
| GlslangProgramInterfaceInfo *programInterfaceInfo, |
| ShaderInterfaceVariableInfoMap *variableInfoMapOut) |
| { |
| size_t bufferCount = 0; |
| if (isTransformFeedbackStage) |
| { |
| ASSERT(!programState.getLinkedTransformFeedbackVaryings().empty()); |
| const bool isInterleaved = |
| programState.getTransformFeedbackBufferMode() == GL_INTERLEAVED_ATTRIBS; |
| bufferCount = isInterleaved ? 1 : programState.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, shaderType, GetXfbBufferName(bufferIndex), |
| programInterfaceInfo->uniformsAndXfbDescriptorSetIndex, |
| programInterfaceInfo->currentUniformBindingIndex); |
| ++programInterfaceInfo->currentUniformBindingIndex; |
| } |
| |
| // Remove inactive transform feedback buffers. |
| for (uint32_t bufferIndex = bufferCount; |
| bufferIndex < gl::IMPLEMENTATION_MAX_TRANSFORM_FEEDBACK_BUFFERS; ++bufferIndex) |
| { |
| variableInfoMapOut->add(shaderType, GetXfbBufferName(bufferIndex)); |
| } |
| } |
| |
| void AssignTransformFeedbackExtensionLocations(gl::ShaderType shaderType, |
| const gl::ProgramState &programState, |
| bool isTransformFeedbackStage, |
| GlslangProgramInterfaceInfo *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 = |
| programState.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.mappedName; |
| |
| if (tfVaryingName == "gl_Position") |
| { |
| ASSERT(tfVarying.isBuiltIn()); |
| capturesPosition = true; |
| break; |
| } |
| } |
| } |
| |
| if (capturesPosition) |
| { |
| AddLocationInfo(variableInfoMapOut, shaderType, sh::vk::kXfbExtensionPositionOutName, |
| 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::kXfbExtensionPositionOutName); |
| } |
| } |
| |
| bool IsFirstRegisterOfVarying(const gl::PackedVaryingRegister &varyingReg, bool allowFields) |
| { |
| 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. |
| if (varyingReg.varyingArrayIndex != 0 || |
| (varying.arrayIndex != GL_INVALID_INDEX && varying.arrayIndex != 0)) |
| { |
| return false; |
| } |
| |
| // Similarly, assign matrix varying locations to the assigned location of the first row. |
| if (varyingReg.varyingRowIndex != 0) |
| { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void AssignAttributeLocations(const gl::ProgramExecutable &programExecutable, |
| gl::ShaderType shaderType, |
| ShaderInterfaceVariableInfoMap *variableInfoMapOut) |
| { |
| // Assign attribute locations for the vertex shader. |
| for (const sh::ShaderVariable &attribute : programExecutable.getProgramInputs()) |
| { |
| ASSERT(attribute.active); |
| |
| const uint8_t colCount = static_cast<uint8_t>(gl::VariableColumnCount(attribute.type)); |
| const uint8_t rowCount = static_cast<uint8_t>(gl::VariableRowCount(attribute.type)); |
| 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.mappedName, attribute.location, |
| ShaderInterfaceVariableInfo::kInvalid, componentCount, locationCount); |
| } |
| } |
| |
| void AssignSecondaryOutputLocations(const gl::ProgramState &programState, |
| ShaderInterfaceVariableInfoMap *variableInfoMapOut) |
| { |
| const auto &secondaryOutputLocations = |
| programState.getExecutable().getSecondaryOutputLocations(); |
| const auto &outputVariables = programState.getExecutable().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 sh::ShaderVariable &outputVar = outputVariables[outputLocation.index]; |
| |
| uint32_t location = 0; |
| if (outputVar.location != -1) |
| { |
| location = outputVar.location; |
| } |
| |
| ShaderInterfaceVariableInfo *info = |
| AddLocationInfo(variableInfoMapOut, gl::ShaderType::Fragment, outputVar.mappedName, |
| location, ShaderInterfaceVariableInfo::kInvalid, 0, 0); |
| |
| // If the shader source has not specified the index, specify it here. |
| if (outputVar.index == -1) |
| { |
| // Index 1 is used to specify that the color be used as the second color input to |
| // the blend equation |
| info->index = 1; |
| } |
| } |
| } |
| // Handle secondary outputs for ESSL version less than 3.00 |
| gl::Shader *fragmentShader = programState.getAttachedShader(gl::ShaderType::Fragment); |
| if (fragmentShader && fragmentShader->getShaderVersion() == 100) |
| { |
| const auto &shaderOutputs = fragmentShader->getActiveOutputVariables(); |
| for (const auto &outputVar : shaderOutputs) |
| { |
| if (outputVar.name == "gl_SecondaryFragColorEXT") |
| { |
| AddLocationInfo(variableInfoMapOut, gl::ShaderType::Fragment, |
| "angle_SecondaryFragColor", 0, |
| ShaderInterfaceVariableInfo::kInvalid, 0, 0); |
| } |
| else if (outputVar.name == "gl_SecondaryFragDataEXT") |
| { |
| AddLocationInfo(variableInfoMapOut, gl::ShaderType::Fragment, |
| "angle_SecondaryFragData", 0, ShaderInterfaceVariableInfo::kInvalid, |
| 0, 0); |
| } |
| } |
| } |
| } |
| |
| void AssignOutputLocations(const gl::ProgramState &programState, |
| const gl::ShaderType shaderType, |
| ShaderInterfaceVariableInfoMap *variableInfoMapOut) |
| { |
| // Assign output locations for the fragment shader. |
| ASSERT(shaderType == gl::ShaderType::Fragment); |
| |
| const gl::ProgramExecutable &programExecutable = programState.getExecutable(); |
| 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 sh::ShaderVariable &outputVar = outputVariables[outputLocation.index]; |
| |
| uint32_t location = 0; |
| if (outputVar.location != -1) |
| { |
| location = outputVar.location; |
| } |
| else if (std::find(implicitOutputs.begin(), implicitOutputs.end(), outputVar.name) == |
| implicitOutputs.end()) |
| { |
| // If there is only one output, it is allowed not to have a location qualifier, in |
| // which case it defaults to 0. GLSL ES 3.00 spec, section 4.3.8.2. |
| ASSERT(CountExplicitOutputs(outputVariables.begin(), outputVariables.end(), |
| implicitOutputs.begin(), implicitOutputs.end()) == 1); |
| } |
| |
| AddLocationInfo(variableInfoMapOut, shaderType, outputVar.mappedName, location, |
| ShaderInterfaceVariableInfo::kInvalid, 0, 0); |
| } |
| } |
| |
| AssignSecondaryOutputLocations(programState, variableInfoMapOut); |
| |
| // When no fragment output is specified by the shader, the translator outputs webgl_FragColor or |
| // webgl_FragData. Add an entry for these. Even though the translator is already assigning |
| // location 0 to these entries, adding an entry for them here allows us to ASSERT that every |
| // shader interface variable is processed during the SPIR-V transformation. This is done when |
| // iterating the ids provided by OpEntryPoint. |
| AddLocationInfo(variableInfoMapOut, shaderType, "webgl_FragColor", 0, 0, 0, 0); |
| AddLocationInfo(variableInfoMapOut, shaderType, "webgl_FragData", 0, 0, 0, 0); |
| } |
| |
| void AssignVaryingLocations(const GlslangSourceOptions &options, |
| const gl::VaryingPacking &varyingPacking, |
| const gl::ShaderType shaderType, |
| const gl::ShaderType frontShaderType, |
| GlslangProgramInterfaceInfo *programInterfaceInfo, |
| ShaderInterfaceVariableInfoMap *variableInfoMapOut) |
| { |
| uint32_t locationsUsedForEmulation = programInterfaceInfo->locationsUsedForXfbExtension; |
| |
| // Substitute layout and qualifier strings for the position varying added for line raster |
| // emulation. |
| if (options.emulateBresenhamLines) |
| { |
| uint32_t lineRasterEmulationPositionLocation = locationsUsedForEmulation++; |
| |
| AddLocationInfo(variableInfoMapOut, shaderType, sh::vk::kLineRasterEmulationPosition, |
| lineRasterEmulationPositionLocation, ShaderInterfaceVariableInfo::kInvalid, |
| 0, 0); |
| } |
| |
| // Assign varying locations. |
| for (const gl::PackedVaryingRegister &varyingReg : varyingPacking.getRegisterList()) |
| { |
| if (!IsFirstRegisterOfVarying(varyingReg, false)) |
| { |
| continue; |
| } |
| |
| const gl::PackedVarying &varying = *varyingReg.packedVarying; |
| |
| uint32_t location = varyingReg.registerRow + locationsUsedForEmulation; |
| uint32_t component = ShaderInterfaceVariableInfo::kInvalid; |
| if (varyingReg.registerColumn > 0) |
| { |
| ASSERT(!varying.varying().isStruct()); |
| ASSERT(!gl::IsMatrixType(varying.varying().type)); |
| component = varyingReg.registerColumn; |
| } |
| |
| // In the following: |
| // |
| // struct S { vec4 field; }; |
| // out S varStruct; |
| // |
| // "_uvarStruct" is found through |parentStructMappedName|, with |varying->mappedName| |
| // being "_ufield". In such a case, use |parentStructMappedName|. |
| if (varying.frontVarying.varying && (varying.frontVarying.stage == shaderType)) |
| { |
| AddVaryingLocationInfo(variableInfoMapOut, varying.frontVarying, |
| varying.isStructField(), location, component); |
| } |
| |
| if (varying.backVarying.varying && (varying.backVarying.stage == shaderType)) |
| { |
| AddVaryingLocationInfo(variableInfoMapOut, varying.backVarying, varying.isStructField(), |
| location, component); |
| } |
| } |
| |
| // Add an entry for inactive varyings. |
| const gl::ShaderMap<std::vector<std::string>> &inactiveVaryingMappedNames = |
| varyingPacking.getInactiveVaryingMappedNames(); |
| for (const std::string &varyingName : inactiveVaryingMappedNames[shaderType]) |
| { |
| ASSERT(!gl::IsBuiltInName(varyingName)); |
| |
| // If name is already in the map, it will automatically have marked all other stages |
| // inactive. |
| if (variableInfoMapOut->contains(shaderType, varyingName)) |
| { |
| continue; |
| } |
| |
| // Otherwise, add an entry for it with all locations inactive. |
| ShaderInterfaceVariableInfo &info = variableInfoMapOut->addOrGet(shaderType, varyingName); |
| ASSERT(info.location == ShaderInterfaceVariableInfo::kInvalid); |
| } |
| |
| // Add an entry for active builtins varyings. This will allow inactive builtins, such as |
| // gl_PointSize, gl_ClipDistance etc to be removed. |
| const gl::ShaderMap<std::vector<std::string>> &activeOutputBuiltIns = |
| varyingPacking.getActiveOutputBuiltInNames(); |
| for (const std::string &builtInName : activeOutputBuiltIns[shaderType]) |
| { |
| ASSERT(gl::IsBuiltInName(builtInName)); |
| |
| ShaderInterfaceVariableInfo &info = variableInfoMapOut->addOrGet(shaderType, builtInName); |
| info.activeStages.set(shaderType); |
| info.varyingIsOutput = true; |
| } |
| |
| // If an output builtin is active in the previous stage, assume it's active in the input of the |
| // current stage as well. |
| if (frontShaderType != gl::ShaderType::InvalidEnum) |
| { |
| for (const std::string &builtInName : activeOutputBuiltIns[frontShaderType]) |
| { |
| ASSERT(gl::IsBuiltInName(builtInName)); |
| |
| ShaderInterfaceVariableInfo &info = |
| variableInfoMapOut->addOrGet(shaderType, builtInName); |
| info.activeStages.set(shaderType); |
| info.varyingIsInput = true; |
| } |
| } |
| |
| // Add an entry for gl_PerVertex, for use with transform feedback capture of built-ins. |
| ShaderInterfaceVariableInfo &info = variableInfoMapOut->addOrGet(shaderType, "gl_PerVertex"); |
| info.activeStages.set(shaderType); |
| } |
| |
| // Calculates XFB layout qualifier arguments for each tranform 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::kXfbExtensionPositionOutName, -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, "gl_PerVertex", 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, and the name of the varying is found through parentStructMappedName. This should |
| // only be done for the first field of the struct. 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()) |
| { |
| if (!IsFirstRegisterOfVarying(varyingReg, tfVarying.isShaderIOBlock)) |
| { |
| 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 std::string &mappedName = |
| originalVarying->isStructField() |
| ? originalVarying->frontVarying.parentStructMappedName |
| : originalVarying->frontVarying.varying->mappedName; |
| |
| 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, mappedName, fieldIndex, bufferIndex, |
| currentOffset, currentStride, varyingSize, uniformInfo.columnCount, |
| uniformInfo.rowCount, arrayIndex, uniformInfo.componentType); |
| } |
| } |
| } |
| |
| void AssignUniformBindings(const GlslangSourceOptions &options, |
| const gl::ProgramExecutable &programExecutable, |
| const gl::ShaderType shaderType, |
| GlslangProgramInterfaceInfo *programInterfaceInfo, |
| ShaderInterfaceVariableInfoMap *variableInfoMapOut) |
| { |
| if (programExecutable.hasLinkedShaderStage(shaderType)) |
| { |
| AddResourceInfo(variableInfoMapOut, shaderType, kDefaultUniformNames[shaderType], |
| programInterfaceInfo->uniformsAndXfbDescriptorSetIndex, |
| programInterfaceInfo->currentUniformBindingIndex); |
| ++programInterfaceInfo->currentUniformBindingIndex; |
| |
| // Assign binding to the driver uniforms block |
| AddResourceInfoToAllStages(variableInfoMapOut, shaderType, sh::vk::kDriverUniformsBlockName, |
| programInterfaceInfo->driverUniformsDescriptorSetIndex, 0); |
| } |
| } |
| |
| // TODO: http://anglebug.com/4512: Need to combine descriptor set bindings across |
| // shader stages. |
| void AssignInputAttachmentBindings(const GlslangSourceOptions &options, |
| const gl::ProgramExecutable &programExecutable, |
| const std::vector<gl::LinkedUniform> &uniforms, |
| const gl::RangeUI &inputAttachmentUniformRange, |
| const gl::ShaderType shaderType, |
| GlslangProgramInterfaceInfo *programInterfaceInfo, |
| ShaderInterfaceVariableInfoMap *variableInfoMapOut) |
| { |
| const uint32_t baseInputAttachmentBindingIndex = |
| programInterfaceInfo->currentShaderResourceBindingIndex; |
| |
| bool hasFragmentInOutVars = false; |
| |
| for (unsigned int uniformIndex : inputAttachmentUniformRange) |
| { |
| std::string mappedInputAttachmentName; |
| const gl::LinkedUniform &inputAttachmentUniform = uniforms[uniformIndex]; |
| mappedInputAttachmentName = inputAttachmentUniform.mappedName; |
| |
| if (programExecutable.hasLinkedShaderStage(shaderType) && |
| inputAttachmentUniform.isActive(shaderType)) |
| { |
| const uint32_t inputAttachmentBindingIndex = |
| baseInputAttachmentBindingIndex + inputAttachmentUniform.location; |
| |
| AddResourceInfo(variableInfoMapOut, shaderType, mappedInputAttachmentName, |
| programInterfaceInfo->shaderResourceDescriptorSetIndex, |
| inputAttachmentBindingIndex); |
| |
| hasFragmentInOutVars = true; |
| } |
| } |
| |
| if (hasFragmentInOutVars) |
| { |
| // 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; |
| } |
| } |
| |
| // TODO: http://anglebug.com/4512: Need to combine descriptor set bindings across |
| // shader stages. |
| void AssignInterfaceBlockBindings(const GlslangSourceOptions &options, |
| const gl::ProgramExecutable &programExecutable, |
| const std::vector<gl::InterfaceBlock> &blocks, |
| const gl::ShaderType shaderType, |
| GlslangProgramInterfaceInfo *programInterfaceInfo, |
| ShaderInterfaceVariableInfoMap *variableInfoMapOut) |
| { |
| for (const gl::InterfaceBlock &block : blocks) |
| { |
| if (!block.isArray || block.arrayElement == 0) |
| { |
| // TODO: http://anglebug.com/4523: All blocks should be active |
| if (programExecutable.hasLinkedShaderStage(shaderType) && block.isActive(shaderType)) |
| { |
| AddResourceInfo(variableInfoMapOut, shaderType, block.mappedName, |
| programInterfaceInfo->shaderResourceDescriptorSetIndex, |
| programInterfaceInfo->currentShaderResourceBindingIndex); |
| ++programInterfaceInfo->currentShaderResourceBindingIndex; |
| } |
| } |
| } |
| } |
| |
| // TODO: http://anglebug.com/4512: Need to combine descriptor set bindings across |
| // shader stages. |
| void AssignAtomicCounterBufferBindings(const GlslangSourceOptions &options, |
| const gl::ProgramExecutable &programExecutable, |
| const std::vector<gl::AtomicCounterBuffer> &buffers, |
| const gl::ShaderType shaderType, |
| GlslangProgramInterfaceInfo *programInterfaceInfo, |
| ShaderInterfaceVariableInfoMap *variableInfoMapOut) |
| { |
| if (buffers.size() == 0) |
| { |
| return; |
| } |
| |
| if (programExecutable.hasLinkedShaderStage(shaderType)) |
| { |
| AddResourceInfo(variableInfoMapOut, shaderType, sh::vk::kAtomicCountersBlockName, |
| programInterfaceInfo->shaderResourceDescriptorSetIndex, |
| programInterfaceInfo->currentShaderResourceBindingIndex); |
| ++programInterfaceInfo->currentShaderResourceBindingIndex; |
| } |
| } |
| |
| // TODO: http://anglebug.com/4512: Need to combine descriptor set bindings across |
| // shader stages. |
| void AssignImageBindings(const GlslangSourceOptions &options, |
| const gl::ProgramExecutable &programExecutable, |
| const std::vector<gl::LinkedUniform> &uniforms, |
| const gl::RangeUI &imageUniformRange, |
| const gl::ShaderType shaderType, |
| GlslangProgramInterfaceInfo *programInterfaceInfo, |
| ShaderInterfaceVariableInfoMap *variableInfoMapOut) |
| { |
| for (unsigned int uniformIndex : imageUniformRange) |
| { |
| const gl::LinkedUniform &imageUniform = uniforms[uniformIndex]; |
| |
| std::string name = imageUniform.mappedName; |
| if (GetImageNameWithoutIndices(&name)) |
| { |
| if (programExecutable.hasLinkedShaderStage(shaderType)) |
| { |
| AddResourceInfo(variableInfoMapOut, shaderType, name, |
| programInterfaceInfo->shaderResourceDescriptorSetIndex, |
| programInterfaceInfo->currentShaderResourceBindingIndex); |
| ++programInterfaceInfo->currentShaderResourceBindingIndex; |
| } |
| } |
| } |
| } |
| |
| void AssignNonTextureBindings(const GlslangSourceOptions &options, |
| const gl::ProgramExecutable &programExecutable, |
| const gl::ShaderType shaderType, |
| GlslangProgramInterfaceInfo *programInterfaceInfo, |
| ShaderInterfaceVariableInfoMap *variableInfoMapOut) |
| { |
| const std::vector<gl::LinkedUniform> &uniforms = programExecutable.getUniforms(); |
| const gl::RangeUI &inputAttachmentUniformRange = programExecutable.getFragmentInoutRange(); |
| AssignInputAttachmentBindings(options, programExecutable, uniforms, inputAttachmentUniformRange, |
| shaderType, programInterfaceInfo, variableInfoMapOut); |
| |
| const std::vector<gl::InterfaceBlock> &uniformBlocks = programExecutable.getUniformBlocks(); |
| AssignInterfaceBlockBindings(options, programExecutable, uniformBlocks, shaderType, |
| programInterfaceInfo, variableInfoMapOut); |
| |
| const std::vector<gl::InterfaceBlock> &storageBlocks = |
| programExecutable.getShaderStorageBlocks(); |
| AssignInterfaceBlockBindings(options, programExecutable, storageBlocks, shaderType, |
| programInterfaceInfo, variableInfoMapOut); |
| |
| const std::vector<gl::AtomicCounterBuffer> &atomicCounterBuffers = |
| programExecutable.getAtomicCounterBuffers(); |
| AssignAtomicCounterBufferBindings(options, programExecutable, atomicCounterBuffers, shaderType, |
| programInterfaceInfo, variableInfoMapOut); |
| |
| const gl::RangeUI &imageUniformRange = programExecutable.getImageUniformRange(); |
| AssignImageBindings(options, programExecutable, uniforms, imageUniformRange, shaderType, |
| programInterfaceInfo, variableInfoMapOut); |
| } |
| |
| // TODO: http://anglebug.com/4512: Need to combine descriptor set bindings across |
| // shader stages. |
| void AssignTextureBindings(const GlslangSourceOptions &options, |
| const gl::ProgramExecutable &programExecutable, |
| const gl::ShaderType shaderType, |
| GlslangProgramInterfaceInfo *programInterfaceInfo, |
| ShaderInterfaceVariableInfoMap *variableInfoMapOut) |
| { |
| // Assign textures to a descriptor set and binding. |
| const std::vector<gl::LinkedUniform> &uniforms = programExecutable.getUniforms(); |
| |
| for (unsigned int uniformIndex : programExecutable.getSamplerUniformRange()) |
| { |
| const gl::LinkedUniform &samplerUniform = uniforms[uniformIndex]; |
| |
| if (gl::SamplerNameContainsNonZeroArrayElement(samplerUniform.name)) |
| { |
| continue; |
| } |
| |
| if (UniformNameIsIndexZero(samplerUniform.name)) |
| { |
| // Samplers in structs are extracted and renamed. |
| const std::string samplerName = GlslangGetMappedSamplerName(samplerUniform.name); |
| |
| // TODO: http://anglebug.com/4523: All uniforms should be active |
| if (programExecutable.hasLinkedShaderStage(shaderType) && |
| samplerUniform.isActive(shaderType)) |
| { |
| AddResourceInfo(variableInfoMapOut, shaderType, samplerName, |
| programInterfaceInfo->textureDescriptorSetIndex, |
| programInterfaceInfo->currentTextureBindingIndex); |
| ++programInterfaceInfo->currentTextureBindingIndex; |
| } |
| } |
| } |
| } |
| |
| constexpr gl::ShaderMap<EShLanguage> kShLanguageMap = { |
| {gl::ShaderType::Vertex, EShLangVertex}, |
| {gl::ShaderType::TessControl, EShLangTessControl}, |
| {gl::ShaderType::TessEvaluation, EShLangTessEvaluation}, |
| {gl::ShaderType::Geometry, EShLangGeometry}, |
| {gl::ShaderType::Fragment, EShLangFragment}, |
| {gl::ShaderType::Compute, EShLangCompute}, |
| }; |
| |
| angle::Result CompileShader(const GlslangErrorCallback &callback, |
| const TBuiltInResource &builtInResources, |
| gl::ShaderType shaderType, |
| const std::string &shaderSource, |
| glslang::TShader *shader, |
| glslang::TProgram *program) |
| { |
| // Enable SPIR-V and Vulkan rules when parsing GLSL |
| constexpr EShMessages messages = static_cast<EShMessages>(EShMsgSpvRules | EShMsgVulkanRules); |
| |
| ANGLE_TRACE_EVENT0("gpu.angle", "Glslang CompileShader TShader::parse"); |
| |
| const char *shaderString = shaderSource.c_str(); |
| int shaderLength = static_cast<int>(shaderSource.size()); |
| |
| shader->setStringsWithLengths(&shaderString, &shaderLength, 1); |
| shader->setEntryPoint("main"); |
| |
| bool result = shader->parse(&builtInResources, 450, ECoreProfile, false, false, messages); |
| if (!result) |
| { |
| ERR() << "Internal error parsing Vulkan shader corresponding to " << shaderType << ":\n" |
| << shader->getInfoLog() << "\n" |
| << shader->getInfoDebugLog() << "\n"; |
| ANGLE_GLSLANG_CHECK(callback, false, GlslangError::InvalidShader); |
| } |
| |
| program->addShader(shader); |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result LinkProgram(const GlslangErrorCallback &callback, glslang::TProgram *program) |
| { |
| // Enable SPIR-V and Vulkan rules |
| constexpr EShMessages messages = static_cast<EShMessages>(EShMsgSpvRules | EShMsgVulkanRules); |
| |
| bool linkResult = program->link(messages); |
| if (!linkResult) |
| { |
| ERR() << "Internal error linking Vulkan shaders:\n" << program->getInfoLog() << "\n"; |
| ANGLE_GLSLANG_CHECK(callback, false, GlslangError::InvalidShader); |
| } |
| return angle::Result::Continue; |
| } |
| |
| // 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: |
| // SPIR-V 1.0 Table 1: First Words of Physical Layout |
| enum HeaderIndex |
| { |
| kHeaderIndexMagic = 0, |
| kHeaderIndexVersion = 1, |
| kHeaderIndexGenerator = 2, |
| kHeaderIndexIndexBound = 3, |
| kHeaderIndexSchema = 4, |
| kHeaderIndexInstructions = 5, |
| }; |
| |
| // 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() |
| { |
| // Glslang succeeded in outputting SPIR-V, so we assume it's valid. |
| ASSERT(mSpirvBlobIn.size() >= kHeaderIndexInstructions); |
| // Since SPIR-V comes from a local call to glslang, it necessarily has the same endianness as |
| // the running architecture, so no byte-swapping is necessary. |
| ASSERT(mSpirvBlobIn[kHeaderIndexMagic] == spv::MagicNumber); |
| |
| // Make sure the transformer is not reused to avoid having to reinitialize it here. |
| ASSERT(mCurrentWord == 0); |
| ASSERT(mIsInFunctionSection == false); |
| |
| // Make sure the 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() + kHeaderIndexInstructions); |
| |
| mCurrentWord = 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); |
| |
| // Since glslang succeeded in producing SPIR-V, we assume it to be 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)[kHeaderIndexIndexBound]++); |
| } |
| |
| spirv::IdRef SpirvTransformerBase::getNewId() |
| { |
| return GetNewId(mSpirvBlobOut); |
| } |
| |
| enum class SpirvVariableType |
| { |
| InterfaceVariable, |
| BuiltIn, |
| Other, |
| }; |
| |
| enum class TransformationState |
| { |
| Transformed, |
| Unchanged, |
| }; |
| |
| // Helper class that gathers IDs of interest. This class would be largely unnecessary when the |
| // translator generates SPIR-V directly, as it could communicate these IDs directly. |
| class SpirvIDDiscoverer final : angle::NonCopyable |
| { |
| public: |
| SpirvIDDiscoverer() : mOutputPerVertex{}, mInputPerVertex{} {} |
| |
| void init(size_t indexBound); |
| |
| // Instructions: |
| void visitDecorate(spirv::IdRef id, spv::Decoration decoration); |
| void visitName(spirv::IdRef id, const spirv::LiteralString &name); |
| void visitMemberName(const ShaderInterfaceVariableInfo &info, |
| spirv::IdRef id, |
| spirv::LiteralInteger member, |
| const spirv::LiteralString &name); |
| void visitTypeArray(spirv::IdResult id, spirv::IdRef elementType, spirv::IdRef length); |
| void visitTypeFloat(spirv::IdResult id, spirv::LiteralInteger width); |
| void visitTypeInt(spirv::IdResult id, |
| spirv::LiteralInteger width, |
| spirv::LiteralInteger signedness); |
| void visitTypePointer(spirv::IdResult id, spv::StorageClass storageClass, spirv::IdRef typeId); |
| void visitTypeVector(spirv::IdResult id, |
| spirv::IdRef componentId, |
| spirv::LiteralInteger componentCount); |
| SpirvVariableType visitVariable(spirv::IdResultType typeId, |
| spirv::IdResult id, |
| spv::StorageClass storageClass, |
| spirv::LiteralString *nameOut); |
| |
| // Helpers: |
| void visitTypeHelper(spirv::IdResult id, spirv::IdRef typeId); |
| void writePendingDeclarations(spirv::Blob *blobOut); |
| |
| // Getters: |
| const spirv::LiteralString &getName(spirv::IdRef id) const { return mNamesById[id]; } |
| bool isIOBlock(spirv::IdRef id) const { return mIsIOBlockById[id]; } |
| bool isPerVertex(spirv::IdRef typeId) const |
| { |
| return typeId == mOutputPerVertex.typeId || typeId == mInputPerVertex.typeId; |
| } |
| uint32_t getPerVertexMaxActiveMember(spirv::IdRef typeId) const |
| { |
| ASSERT(isPerVertex(typeId)); |
| return typeId == mOutputPerVertex.typeId ? mOutputPerVertex.maxActiveMember |
| : mInputPerVertex.maxActiveMember; |
| } |
| |
| spirv::IdRef floatId() const { return mFloatId; } |
| spirv::IdRef vec4Id() const { return mVec4Id; } |
| spirv::IdRef vec4OutTypePointerId() const { return mVec4OutTypePointerId; } |
| spirv::IdRef intId() const { return mIntId; } |
| spirv::IdRef uintId() const { return mUintId; } |
| spirv::IdRef int0Id() const { return mInt0Id; } |
| spirv::IdRef floatHalfId() const { return mFloatHalfId; } |
| spirv::IdRef outputPerVertexTypePointerId() const { return mOutputPerVertexTypePointerId; } |
| spirv::IdRef outputPerVertexId() const { return mOutputPerVertexId; } |
| |
| private: |
| // Names associated with ids through OpName. The same name may be assigned to multiple ids, but |
| // not all names are interesting (for example function arguments). When the variable |
| // declaration is met (OpVariable), the variable info is matched with the corresponding id's |
| // name based on the Storage Class. |
| std::vector<spirv::LiteralString> mNamesById; |
| |
| // Tracks whether a given type is an I/O block. I/O blocks are identified by their type name |
| // instead of variable name, but otherwise look like varyings of struct type (which are |
| // identified by their instance name). To disambiguate them, the `OpDecorate %N Block` |
| // instruction is used which decorates I/O block types. |
| std::vector<bool> mIsIOBlockById; |
| |
| // gl_PerVertex is unique in that it's the only builtin of struct type. This struct is pruned |
| // by removing trailing inactive members. We therefore need to keep track of what's its type id |
| // as well as which is the last active member. Note that intermediate stages, i.e. geometry and |
| // tessellation have two gl_PerVertex declarations, one for input and one for output. |
| struct PerVertexData |
| { |
| spirv::IdRef typeId; |
| uint32_t maxActiveMember; |
| }; |
| PerVertexData mOutputPerVertex; |
| PerVertexData mInputPerVertex; |
| |
| // A handful of ids that are used to generate gl_Position transformation code (for pre-rotation |
| // or depth correction). These IDs are used to load/store gl_Position and apply modifications |
| // and swizzles. |
| // |
| // - mFloatId: id of OpTypeFloat 32 |
| // - mVec4Id: id of OpTypeVector %mFloatID 4 |
| // - mVec4OutTypePointerId: id of OpTypePointer Output %mVec4ID |
| // - mIntId: id of OpTypeInt 32 1 |
| // - mUintId: id of OpTypeInt 32 0 |
| // - mInt0Id: id of OpConstant %mIntID 0 |
| // - mFloatHalfId: id of OpConstant %mFloatId 0.5f |
| // - mOutputPerVertexTypePointerId: id of OpTypePointer Output %mOutputPerVertex.typeId |
| // - mOutputPerVertexId: id of OpVariable %mOutputPerVertexTypePointerId Output |
| // |
| spirv::IdRef mFloatId; |
| spirv::IdRef mVec4Id; |
| spirv::IdRef mVec4OutTypePointerId; |
| spirv::IdRef mIntId; |
| spirv::IdRef mUintId; |
| spirv::IdRef mInt0Id; |
| spirv::IdRef mFloatHalfId; |
| spirv::IdRef mOutputPerVertexTypePointerId; |
| spirv::IdRef mOutputPerVertexId; |
| }; |
| |
| void SpirvIDDiscoverer::init(size_t indexBound) |
| { |
| // Allocate storage for id-to-name map. Used to associate ShaderInterfaceVariableInfo with ids |
| // based on name, but only when it's determined that the name corresponds to a shader interface |
| // variable. |
| mNamesById.resize(indexBound, nullptr); |
| |
| // Allocate storage for id-to-flag map. Used to disambiguate I/O blocks instances from varyings |
| // of struct type. |
| mIsIOBlockById.resize(indexBound, false); |
| } |
| |
| void SpirvIDDiscoverer::visitDecorate(spirv::IdRef id, spv::Decoration decoration) |
| { |
| mIsIOBlockById[id] = decoration == spv::DecorationBlock; |
| } |
| |
| void SpirvIDDiscoverer::visitName(spirv::IdRef id, const spirv::LiteralString &name) |
| { |
| // The names and ids are unique |
| ASSERT(id < mNamesById.size()); |
| ASSERT(mNamesById[id] == nullptr); |
| |
| mNamesById[id] = name; |
| } |
| |
| void SpirvIDDiscoverer::visitMemberName(const ShaderInterfaceVariableInfo &info, |
| spirv::IdRef id, |
| spirv::LiteralInteger member, |
| const spirv::LiteralString &name) |
| { |
| // The names and ids are unique |
| ASSERT(id < mNamesById.size()); |
| ASSERT(mNamesById[id] != nullptr); |
| |
| if (strcmp(mNamesById[id], "gl_PerVertex") != 0) |
| { |
| return; |
| } |
| |
| // Assume output gl_PerVertex is encountered first. When the storage class of these types are |
| // determined, the variables can be swapped if this assumption was incorrect. |
| if (!mOutputPerVertex.typeId.valid() || id == mOutputPerVertex.typeId) |
| { |
| mOutputPerVertex.typeId = id; |
| |
| // Keep track of the range of members that are active. |
| if (info.varyingIsOutput && member > mOutputPerVertex.maxActiveMember) |
| { |
| mOutputPerVertex.maxActiveMember = member; |
| } |
| } |
| else if (!mInputPerVertex.typeId.valid() || id == mInputPerVertex.typeId) |
| { |
| mInputPerVertex.typeId = id; |
| |
| // Keep track of the range of members that are active. |
| if (info.varyingIsInput && member > mInputPerVertex.maxActiveMember) |
| { |
| mInputPerVertex.maxActiveMember = member; |
| } |
| } |
| else |
| { |
| UNREACHABLE(); |
| } |
| } |
| |
| void SpirvIDDiscoverer::visitTypeHelper(spirv::IdResult id, spirv::IdRef typeId) |
| { |
| // Every type id is declared only once. |
| ASSERT(id < mNamesById.size()); |
| ASSERT(mNamesById[id] == nullptr); |
| ASSERT(id < mIsIOBlockById.size()); |
| ASSERT(!mIsIOBlockById[id]); |
| |
| // Carry the name forward from the base type. This is only necessary for interface blocks, |
| // as the variable info is associated with the block name instead of the variable name (to |
| // support nameless interface blocks). When the variable declaration is met, either the |
| // type name or the variable name is used to associate with info based on the variable's |
| // storage class. |
| ASSERT(typeId < mNamesById.size()); |
| mNamesById[id] = mNamesById[typeId]; |
| |
| // Similarly, carry forward the information regarding whether this type is an I/O block. |
| ASSERT(typeId < mIsIOBlockById.size()); |
| mIsIOBlockById[id] = mIsIOBlockById[typeId]; |
| } |
| |
| void SpirvIDDiscoverer::visitTypeArray(spirv::IdResult id, |
| spirv::IdRef elementType, |
| spirv::IdRef length) |
| { |
| visitTypeHelper(id, elementType); |
| } |
| |
| void SpirvIDDiscoverer::visitTypeFloat(spirv::IdResult id, spirv::LiteralInteger width) |
| { |
| // Only interested in OpTypeFloat 32. |
| if (width == 32) |
| { |
| ASSERT(!mFloatId.valid()); |
| mFloatId = id; |
| } |
| } |
| |
| void SpirvIDDiscoverer::visitTypeInt(spirv::IdResult id, |
| spirv::LiteralInteger width, |
| spirv::LiteralInteger signedness) |
| { |
| // Only interested in OpTypeInt 32 *. |
| if (width != 32) |
| { |
| return; |
| } |
| |
| if (signedness == 0) |
| { |
| ASSERT(!mUintId.valid()); |
| mUintId = id; |
| } |
| else |
| { |
| ASSERT(!mIntId.valid()); |
| mIntId = id; |
| } |
| } |
| |
| void SpirvIDDiscoverer::visitTypePointer(spirv::IdResult id, |
| spv::StorageClass storageClass, |
| spirv::IdRef typeId) |
| { |
| visitTypeHelper(id, typeId); |
| |
| // Verify that the ids associated with input and output gl_PerVertex are correct. |
| if (typeId == mOutputPerVertex.typeId || typeId == mInputPerVertex.typeId) |
| { |
| // If assumption about the first gl_PerVertex encountered being Output is wrong, swap the |
| // two ids. |
| if ((typeId == mOutputPerVertex.typeId && storageClass == spv::StorageClassInput) || |
| (typeId == mInputPerVertex.typeId && storageClass == spv::StorageClassOutput)) |
| { |
| std::swap(mOutputPerVertex.typeId, mInputPerVertex.typeId); |
| } |
| |
| // Remember type pointer of output gl_PerVertex for gl_Position transformations. |
| if (storageClass == spv::StorageClassOutput) |
| { |
| mOutputPerVertexTypePointerId = id; |
| } |
| } |
| |
| // If OpTypePointer Output %mVec4ID was encountered, remember that. Otherwise we'll have to |
| // generate one. |
| if (typeId == mVec4Id && storageClass == spv::StorageClassOutput) |
| { |
| mVec4OutTypePointerId = id; |
| } |
| } |
| |
| void SpirvIDDiscoverer::visitTypeVector(spirv::IdResult id, |
| spirv::IdRef componentId, |
| spirv::LiteralInteger componentCount) |
| { |
| // Only interested in OpTypeVector %mFloatId 4 |
| if (componentId == mFloatId && componentCount == 4) |
| { |
| ASSERT(!mVec4Id.valid()); |
| mVec4Id = id; |
| } |
| } |
| |
| SpirvVariableType SpirvIDDiscoverer::visitVariable(spirv::IdResultType typeId, |
| spirv::IdResult id, |
| spv::StorageClass storageClass, |
| spirv::LiteralString *nameOut) |
| { |
| ASSERT(typeId < mNamesById.size()); |
| ASSERT(id < mNamesById.size()); |
| ASSERT(typeId < mIsIOBlockById.size()); |
| |
| // If storage class indicates that this is not a shader interface variable, ignore it. |
| const bool isInterfaceBlockVariable = |
| storageClass == spv::StorageClassUniform || storageClass == spv::StorageClassStorageBuffer; |
| const bool isOpaqueUniform = storageClass == spv::StorageClassUniformConstant; |
| const bool isInOut = |
| storageClass == spv::StorageClassInput || storageClass == spv::StorageClassOutput; |
| |
| if (!isInterfaceBlockVariable && !isOpaqueUniform && !isInOut) |
| { |
| return SpirvVariableType::Other; |
| } |
| |
| // For interface block variables, the name that's used to associate info is the block name |
| // rather than the variable name. |
| const bool isIOBlock = mIsIOBlockById[typeId]; |
| *nameOut = mNamesById[isInterfaceBlockVariable || isIOBlock ? typeId : id]; |
| |
| ASSERT(*nameOut != nullptr); |
| |
| // Handle builtins, which all start with "gl_". The variable name could be an indication of a |
| // builtin variable (such as with gl_FragCoord). gl_PerVertex is the only builtin whose "type" |
| // name starts with gl_. However, gl_PerVertex has its own entry in the info map for its |
| // potential use with transform feedback. |
| const bool isNameBuiltin = isInOut && !isIOBlock && gl::IsBuiltInName(*nameOut); |
| if (isNameBuiltin) |
| { |
| return SpirvVariableType::BuiltIn; |
| } |
| |
| if (typeId == mOutputPerVertexTypePointerId) |
| { |
| // If this is the output gl_PerVertex variable, remember its id for gl_Position |
| // transformations. |
| ASSERT(storageClass == spv::StorageClassOutput && isIOBlock && |
| strcmp(*nameOut, "gl_PerVertex") == 0); |
| mOutputPerVertexId = id; |
| } |
| |
| return SpirvVariableType::InterfaceVariable; |
| } |
| |
| void SpirvIDDiscoverer::writePendingDeclarations(spirv::Blob *blobOut) |
| { |
| if (!mFloatId.valid()) |
| { |
| mFloatId = SpirvTransformerBase::GetNewId(blobOut); |
| spirv::WriteTypeFloat(blobOut, mFloatId, spirv::LiteralInteger(32)); |
| } |
| |
| if (!mVec4Id.valid()) |
| { |
| mVec4Id = SpirvTransformerBase::GetNewId(blobOut); |
| spirv::WriteTypeVector(blobOut, mVec4Id, mFloatId, spirv::LiteralInteger(4)); |
| } |
| |
| if (!mVec4OutTypePointerId.valid()) |
| { |
| mVec4OutTypePointerId = SpirvTransformerBase::GetNewId(blobOut); |
| spirv::WriteTypePointer(blobOut, mVec4OutTypePointerId, spv::StorageClassOutput, mVec4Id); |
| } |
| |
| if (!mIntId.valid()) |
| { |
| mIntId = SpirvTransformerBase::GetNewId(blobOut); |
| spirv::WriteTypeInt(blobOut, mIntId, spirv::LiteralInteger(32), spirv::LiteralInteger(1)); |
| } |
| |
| ASSERT(!mInt0Id.valid()); |
| mInt0Id = SpirvTransformerBase::GetNewId(blobOut); |
| spirv::WriteConstant(blobOut, mIntId, mInt0Id, spirv::LiteralContextDependentNumber(0)); |
| |
| constexpr uint32_t kFloatHalfAsUint = 0x3F00'0000; |
| |
| ASSERT(!mFloatHalfId.valid()); |
| mFloatHalfId = SpirvTransformerBase::GetNewId(blobOut); |
| spirv::WriteConstant(blobOut, mFloatId, mFloatHalfId, |
| spirv::LiteralContextDependentNumber(kFloatHalfAsUint)); |
| } |
| |
| // Helper class that trims input and output gl_PerVertex declarations to remove inactive builtins. |
| class SpirvPerVertexTrimmer final : angle::NonCopyable |
| { |
| public: |
| SpirvPerVertexTrimmer() {} |
| |
| TransformationState transformMemberDecorate(const SpirvIDDiscoverer &ids, |
| spirv::IdRef typeId, |
| spirv::LiteralInteger member, |
| spv::Decoration decoration); |
| TransformationState transformMemberName(const SpirvIDDiscoverer &ids, |
| spirv::IdRef id, |
| spirv::LiteralInteger member, |
| const spirv::LiteralString &name); |
| TransformationState transformTypeStruct(const SpirvIDDiscoverer &ids, |
| spirv::IdResult id, |
| spirv::IdRefList *memberList, |
| spirv::Blob *blobOut); |
| }; |
| |
| TransformationState SpirvPerVertexTrimmer::transformMemberDecorate(const SpirvIDDiscoverer &ids, |
| spirv::IdRef typeId, |
| spirv::LiteralInteger member, |
| spv::Decoration decoration) |
| { |
| // Transform only OpMemberDecorate %gl_PerVertex N BuiltIn B |
| if (!ids.isPerVertex(typeId) || decoration != spv::DecorationBuiltIn) |
| { |
| return TransformationState::Unchanged; |
| } |
| |
| // Drop stripped fields. |
| return member > ids.getPerVertexMaxActiveMember(typeId) ? TransformationState::Transformed |
| : TransformationState::Unchanged; |
| } |
| |
| TransformationState SpirvPerVertexTrimmer::transformMemberName(const SpirvIDDiscoverer &ids, |
| spirv::IdRef id, |
| spirv::LiteralInteger member, |
| const spirv::LiteralString &name) |
| { |
| // Remove the instruction if it's a stripped member of gl_PerVertex. |
| return ids.isPerVertex(id) && member > ids.getPerVertexMaxActiveMember(id) |
| ? TransformationState::Transformed |
| : TransformationState::Unchanged; |
| } |
| |
| TransformationState SpirvPerVertexTrimmer::transformTypeStruct(const SpirvIDDiscoverer &ids, |
| spirv::IdResult id, |
| spirv::IdRefList *memberList, |
| spirv::Blob *blobOut) |
| { |
| if (!ids.isPerVertex(id)) |
| { |
| return TransformationState::Unchanged; |
| } |
| |
| const uint32_t maxMembers = ids.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(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(const SpirvIDDiscoverer &ids, |
| 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); |
| |
| 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; |
| }; |
| |
| 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); |
| } |
| |
| 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(writeIndex); |
| } |
| |
| TransformationState SpirvInactiveVaryingRemover::transformTypePointer( |
| const SpirvIDDiscoverer &ids, |
| 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 (ids.getName(typeId) != nullptr && gl::IsBuiltInName(ids.getName(typeId))) |
| { |
| 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); |
| |
| 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) |
| {} |
| |
| void visitName(spirv::IdRef id, const spirv::LiteralString &name); |
| void visitTypeVector(const SpirvIDDiscoverer &ids, |
| spirv::IdResult id, |
| spirv::IdRef componentId, |
| spirv::LiteralInteger componentCount); |
| void visitTypePointer(spirv::IdResult id, spv::StorageClass storageClass, spirv::IdRef typeId); |
| void visitVariable(const ShaderInterfaceVariableInfo &info, |
| gl::ShaderType shaderType, |
| const spirv::LiteralString &name, |
| spirv::IdResultType typeId, |
| spirv::IdResult id, |
| spv::StorageClass storageClass); |
| |
| TransformationState transformCapability(spv::Capability capability, spirv::Blob *blobOut); |
| TransformationState transformName(spirv::IdRef id, spirv::LiteralString name); |
| 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, |
| const SpirvIDDiscoverer &ids, |
| spirv::Blob *blobOut); |
| void writeTransformFeedbackExtensionOutput(const SpirvIDDiscoverer &ids, |
| spirv::IdRef positionId, |
| spirv::Blob *blobOut); |
| void writeTransformFeedbackEmulationOutput( |
| const SpirvIDDiscoverer &ids, |
| const SpirvVaryingPrecisionFixer &varyingPrecisionFixer, |
| spirv::IdRef currentFunctionId, |
| spirv::Blob *blobOut); |
| void addExecutionMode(spirv::IdRef entryPointId, spirv::Blob *blobOut); |
| void addMemberDecorate(const ShaderInterfaceVariableInfo &info, |
| spirv::IdRef id, |
| spirv::Blob *blobOut); |
| void addDecorate(const ShaderInterfaceVariableInfo &info, |
| spirv::IdRef id, |
| spirv::Blob *blobOut); |
| |
| private: |
| void gatherXfbVaryings(const ShaderInterfaceVariableInfo &info, spirv::IdRef id); |
| void visitXfbVarying(const ShaderInterfaceVariableXfbInfo &xfb, |
| spirv::IdRef baseId, |
| uint32_t fieldIndex); |
| void writeIntConstant(const SpirvIDDiscoverer &ids, |
| uint32_t value, |
| spirv::IdRef intId, |
| spirv::Blob *blobOut); |
| void getVaryingTypeIds(const SpirvIDDiscoverer &ids, |
| GLenum componentType, |
| bool isPrivate, |
| spirv::IdRef *typeIdOut, |
| spirv::IdRef *typePtrOut); |
| void writeGetOffsetsCall(spirv::IdRef xfbOffsets, spirv::Blob *blobOut); |
| void writeComponentCapture(const SpirvIDDiscoverer &ids, |
| 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. |
| spirv::IdRef mTransformFeedbackExtensionPositionId; |
| spirv::IdRef mGetXfbOffsetsFuncId; |
| spirv::IdRef mXfbCaptureFuncId; |
| gl::TransformFeedbackBuffersArray<spirv::IdRef> mXfbBuffers; |
| gl::TransformFeedbackBuffersArray<spirv::IdRef> mBufferStrides; |
| spirv::IdRef mBufferStridesCompositeId; |
| |
| // Type and constant ids: |
| // |
| // - mIVec4Id: id of OpTypeVector %mIntId 4 |
| // |
| // - mFloatOutputPointerId: id of OpTypePointer Output %mFloatId |
| // - mIntOutputPointerId: id of OpTypePointer Output %mIntId |
| // - mUintOutputPointerId: id of OpTypePointer Output %mUintId |
| // - 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 %mFloatId |
| // - mIVec4FuncPointerId: id of OpTypePointer Function %mIVec4Id |
| // |
| // - mIntNIds[n]: id of OpConstant %mIntId n |
| spirv::IdRef mIVec4Id; |
| spirv::IdRef mFloatOutputPointerId; |
| spirv::IdRef mIntOutputPointerId; |
| spirv::IdRef mUintOutputPointerId; |
| spirv::IdRef mFloatPrivatePointerId; |
| spirv::IdRef mIntPrivatePointerId; |
| spirv::IdRef mUintPrivatePointerId; |
| spirv::IdRef mFloatUniformPointerId; |
| spirv::IdRef mIVec4FuncPointerId; |
| // Id of constants such as row, column and array index. Integers 0, 1, 2 and 3 are always |
| // defined due to the ubiquity of usage. |
| 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::visitName(spirv::IdRef id, |
| const spirv::LiteralString &name) |
| { |
| if (!mIsEmulated) |
| { |
| return; |
| } |
| |
| const size_t bufferNameBaseLength = strlen(sh::vk::kXfbEmulationBufferName); |
| |
| if (angle::BeginsWith(name, sh::vk::kXfbEmulationGetOffsetsFunctionName)) |
| { |
| ASSERT(!mGetXfbOffsetsFuncId.valid()); |
| mGetXfbOffsetsFuncId = id; |
| } |
| else if (angle::BeginsWith(name, sh::vk::kXfbEmulationCaptureFunctionName)) |
| { |
| ASSERT(!mXfbCaptureFuncId.valid()); |
| mXfbCaptureFuncId = id; |
| } |
| else if (angle::BeginsWith(name, sh::vk::kXfbEmulationBufferName) && |
| std::isdigit(name[bufferNameBaseLength])) |
| { |
| static_assert(gl::IMPLEMENTATION_MAX_TRANSFORM_FEEDBACK_BUFFERS < 10, |
| "Parsing the xfb buffer index below must be adjusted"); |
| uint32_t xfbBuffer = name[bufferNameBaseLength] - '0'; |
| mXfbBuffers[xfbBuffer] = id; |
| } |
| } |
| |
| void SpirvTransformFeedbackCodeGenerator::visitTypeVector(const SpirvIDDiscoverer &ids, |
| spirv::IdResult id, |
| spirv::IdRef componentId, |
| spirv::LiteralInteger componentCount) |
| { |
| // Only interested in OpTypeVector %mIntId 4 |
| if (componentId == ids.intId() && componentCount == 4) |
| { |
| ASSERT(!mIVec4Id.valid()); |
| mIVec4Id = id; |
| } |
| } |
| |
| void SpirvTransformFeedbackCodeGenerator::visitTypePointer(spirv::IdResult id, |
| spv::StorageClass storageClass, |
| spirv::IdRef typeId) |
| { |
| if (typeId == mIVec4Id && storageClass == spv::StorageClassFunction) |
| { |
| ASSERT(!mIVec4FuncPointerId.valid()); |
| mIVec4FuncPointerId = id; |
| } |
| } |
| |
| void SpirvTransformFeedbackCodeGenerator::visitVariable(const ShaderInterfaceVariableInfo &info, |
| gl::ShaderType shaderType, |
| const spirv::LiteralString &name, |
| spirv::IdResultType typeId, |
| spirv::IdResult id, |
| spv::StorageClass storageClass) |
| { |
| if (mIsEmulated) |
| { |
| gatherXfbVaryings(info, id); |
| return; |
| } |
| |
| // Note if the variable is captured by transform feedback. In that case, the TransformFeedback |
| // capability needs to be added. |
| if ((info.xfb.buffer != ShaderInterfaceVariableInfo::kInvalid || !info.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 (strcmp(name, sh::vk::kXfbExtensionPositionOutName) == 0) |
| { |
| mTransformFeedbackExtensionPositionId = id; |
| } |
| } |
| } |
| |
| 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. |
| return angle::BeginsWith(name, sh::vk::kXfbEmulationBufferName) |
| ? TransformationState::Transformed |
| : TransformationState::Unchanged; |
| } |
| |
| 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.get(shaderType, GetXfbBufferName(0)) || |
| &info == &variableInfoMap.get(shaderType, GetXfbBufferName(1)) || |
| &info == &variableInfoMap.get(shaderType, GetXfbBufferName(2)) || |
| &info == &variableInfoMap.get(shaderType, GetXfbBufferName(3))); |
| |
| // Drop the declaration. |
| return TransformationState::Transformed; |
| } |
| |
| return TransformationState::Unchanged; |
| } |
| |
| void SpirvTransformFeedbackCodeGenerator::gatherXfbVaryings(const ShaderInterfaceVariableInfo &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.buffer == ShaderInterfaceVariableXfbInfo::kInvalid) |
| { |
| return; |
| } |
| |
| // Varyings captured to the same buffer have the same stride. |
| ASSERT(mXfbVaryings[xfb.buffer].empty() || |
| mXfbVaryings[xfb.buffer][0].info->stride == xfb.stride); |
| |
| mXfbVaryings[xfb.buffer].push_back({&xfb, baseId, fieldIndex}); |
| } |
| |
| void SpirvTransformFeedbackCodeGenerator::writeIntConstant(const SpirvIDDiscoverer &ids, |
| uint32_t value, |
| spirv::IdRef intId, |
| spirv::Blob *blobOut) |
| { |
| if (value == ShaderInterfaceVariableXfbInfo::kInvalid) |
| { |
| return; |
| } |
| |
| if (mIntNIds.size() <= value) |
| { |
| mIntNIds.resize(value + 1); |
| } |
| else if (mIntNIds[value].valid()) |
| { |
| return; |
| } |
| |
| mIntNIds[value] = SpirvTransformerBase::GetNewId(blobOut); |
| spirv::WriteConstant(blobOut, ids.intId(), mIntNIds[value], |
| spirv::LiteralContextDependentNumber(value)); |
| } |
| |
| void SpirvTransformFeedbackCodeGenerator::writePendingDeclarations( |
| const std::vector<const ShaderInterfaceVariableInfo *> &variableInfoById, |
| const SpirvIDDiscoverer &ids, |
| spirv::Blob *blobOut) |
| { |
| if (!mIsEmulated) |
| { |
| return; |
| } |
| |
| ASSERT(mIVec4Id.valid()); |
| |
| mFloatOutputPointerId = SpirvTransformerBase::GetNewId(blobOut); |
| mFloatPrivatePointerId = SpirvTransformerBase::GetNewId(blobOut); |
| spirv::WriteTypePointer(blobOut, mFloatOutputPointerId, spv::StorageClassOutput, ids.floatId()); |
| spirv::WriteTypePointer(blobOut, mFloatPrivatePointerId, spv::StorageClassPrivate, |
| ids.floatId()); |
| |
| if (ids.intId().valid()) |
| { |
| mIntOutputPointerId = SpirvTransformerBase::GetNewId(blobOut); |
| mIntPrivatePointerId = SpirvTransformerBase::GetNewId(blobOut); |
| spirv::WriteTypePointer(blobOut, mIntOutputPointerId, spv::StorageClassOutput, ids.intId()); |
| spirv::WriteTypePointer(blobOut, mIntPrivatePointerId, spv::StorageClassPrivate, |
| ids.intId()); |
| } |
| |
| if (ids.uintId().valid()) |
| { |
| mUintOutputPointerId = SpirvTransformerBase::GetNewId(blobOut); |
| mUintPrivatePointerId = SpirvTransformerBase::GetNewId(blobOut); |
| spirv::WriteTypePointer(blobOut, mUintOutputPointerId, spv::StorageClassOutput, |
| ids.uintId()); |
| spirv::WriteTypePointer(blobOut, mUintPrivatePointerId, spv::StorageClassPrivate, |
| ids.uintId()); |
| } |
| |
| mFloatUniformPointerId = SpirvTransformerBase::GetNewId(blobOut); |
| spirv::WriteTypePointer(blobOut, mFloatUniformPointerId, spv::StorageClassUniform, |
| ids.floatId()); |
| |
| mIntNIds.resize(4); |
| mIntNIds[0] = ids.int0Id(); |
| for (int n = 1; n < 4; ++n) |
| { |
| writeIntConstant(ids, n, ids.intId(), blobOut); |
| } |
| |
| spirv::IdRefList compositeIds; |
| for (const std::vector<XfbVarying> &varyings : mXfbVaryings) |
| { |
| if (varyings.empty()) |
| { |
| compositeIds.push_back(ids.int0Id()); |
| continue; |
| } |
| |
| const ShaderInterfaceVariableXfbInfo *info0 = varyings[0].info; |
| |
| // Define the buffer stride constant |
| ASSERT(info0->stride % sizeof(float) == 0); |
| uint32_t stride = info0->stride / sizeof(float); |
| |
| mBufferStrides[info0->buffer] = SpirvTransformerBase::GetNewId(blobOut); |
| spirv::WriteConstant(blobOut, ids.intId(), mBufferStrides[info0->buffer], |
| spirv::LiteralContextDependentNumber(stride)); |
| |
| compositeIds.push_back(mBufferStrides[info0->buffer]); |
| |
| // Define all the constants that would be necessary to load the components of the varying. |
| for (const XfbVarying &varying : varyings) |
| { |
| writeIntConstant(ids, varying.fieldIndex, ids.intId(), blobOut); |
| const ShaderInterfaceVariableXfbInfo *info = varying.info; |
| if (info->arraySize == ShaderInterfaceVariableXfbInfo::kInvalid) |
| { |
| continue; |
| } |
| |
| uint32_t arrayIndexStart = |
| varying.info->arrayIndex != ShaderInterfaceVariableXfbInfo::kInvalid |
| ? varying.info->arrayIndex |
| : 0; |
| uint32_t arrayIndexEnd = arrayIndexStart + info->arraySize; |
| |
| for (uint32_t arrayIndex = arrayIndexStart; arrayIndex < arrayIndexEnd; ++arrayIndex) |
| { |
| writeIntConstant(ids, arrayIndex, ids.intId(), blobOut); |
| } |
| } |
| } |
| |
| mBufferStridesCompositeId = SpirvTransformerBase::GetNewId(blobOut); |
| spirv::WriteConstantComposite(blobOut, mIVec4Id, mBufferStridesCompositeId, compositeIds); |
| } |
| |
| void SpirvTransformFeedbackCodeGenerator::writeTransformFeedbackExtensionOutput( |
| const SpirvIDDiscoverer &ids, |
| spirv::IdRef positionId, |
| spirv::Blob *blobOut) |
| { |
| if (mIsEmulated) |
| { |
| return; |
| } |
| |
| if (mTransformFeedbackExtensionPositionId.valid()) |
| { |
| spirv::WriteStore(blobOut, mTransformFeedbackExtensionPositionId, 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 SpirvIDDiscoverer &ids, |
| const SpirvVaryingPrecisionFixer &varyingPrecisionFixer, |
| spirv::IdRef currentFunctionId, |
| spirv::Blob *blobOut) |
| { |
| if (!mIsEmulated || !mXfbCaptureFuncId.valid() || currentFunctionId != mXfbCaptureFuncId) |
| { |
| 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->offset < second.info->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: |
| // |
| // %getOffsetsParam = OpVariable %mIVec4FuncPointerId Function %stridesComposite |
| // %xfbOffsetsResult = OpFunctionCall %ivec4 %ANGLEGetXfbOffsets %stridesComposite |
| // %xfbOffsetsVar = OpVariable %mIVec4FuncPointerId 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 |
| // |
| |
| const spirv::IdRef 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, ids.intId(), 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->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->arraySize; |
| const bool isArray = info->arraySize != ShaderInterfaceVariableXfbInfo::kInvalid; |
| if (varying.info->arrayIndex != ShaderInterfaceVariableXfbInfo::kInvalid) |
| { |
| // Capturing a single element. |
| arrayIndexStart = varying.info->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->offset == offsetForVerification); |
| offsetForVerification += (arrayIndexEnd - arrayIndexStart) * info->rowCount * |
| info->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 = varyingPrecisionFixer.isReplaced(varying.baseId); |
| getVaryingTypeIds(ids, info->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->columnCount; ++col) |
| { |
| AccessChainIndexListAppend appendColumn(info->columnCount > 1, mIntNIds, col, |
| &indexList); |
| for (uint32_t row = 0; row < info->rowCount; ++row) |
| { |
| AccessChainIndexListAppend appendRow(info->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(ids, bufferIndex, xfbOffset, varyingTypeId, |
| varyingTypePtr, varying.baseId, indexList, |
| info->componentType, blobOut); |
| |
| // Increment the offset: |
| // |
| // xfbOffset += 1; |
| // |
| // which translates to: |
| // |
| // %newOffsetId = OpIAdd %int %currentOffsetId %int1 |
| spirv::IdRef nextOffset(SpirvTransformerBase::GetNewId(blobOut)); |
| spirv::WriteIAdd(blobOut, ids.intId(), nextOffset, xfbOffset, mIntNIds[1]); |
| xfbOffset = nextOffset; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| void SpirvTransformFeedbackCodeGenerator::getVaryingTypeIds(const SpirvIDDiscoverer &ids, |
| GLenum componentType, |
| bool isPrivate, |
| spirv::IdRef *typeIdOut, |
| spirv::IdRef *typePtrOut) |
| { |
| switch (componentType) |
| { |
| case GL_INT: |
| *typeIdOut = ids.intId(); |
| *typePtrOut = isPrivate ? mIntPrivatePointerId : mIntOutputPointerId; |
| break; |
| case GL_UNSIGNED_INT: |
| *typeIdOut = ids.uintId(); |
| *typePtrOut = isPrivate ? mUintPrivatePointerId : mUintOutputPointerId; |
| break; |
| case GL_FLOAT: |
| *typeIdOut = ids.floatId(); |
| *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 xfbGetOffsetsParam(SpirvTransformerBase::GetNewId(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 parameter, initialized with the constant ivec4 containing the |
| // strides. |
| spirv::WriteVariable(blobOut, mIVec4FuncPointerId, xfbGetOffsetsParam, |
| spv::StorageClassFunction, &mBufferStridesCompositeId); |
| // Create a variable to hold the result. |
| spirv::WriteVariable(blobOut, mIVec4FuncPointerId, xfbOffsetsVar, spv::StorageClassFunction, |
| nullptr); |
| // Call a helper function generated by the translator to calculate the offsets for the current |
| // vertex. |
| spirv::WriteFunctionCall(blobOut, mIVec4Id, xfbOffsetsResult, mGetXfbOffsetsFuncId, |
| {xfbGetOffsetsParam}); |
| // Store the results. |
| spirv::WriteStore(blobOut, xfbOffsetsVar, xfbOffsetsResult, nullptr); |
| // Load from the variable for use in expressions. |
| spirv::WriteLoad(blobOut, mIVec4Id, xfbOffsets, xfbOffsetsVar, nullptr); |
| } |
| |
| void SpirvTransformFeedbackCodeGenerator::writeComponentCapture( |
| const SpirvIDDiscoverer &ids, |
| 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, ids.floatId(), 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}. |
| spirv::WriteAccessChain(blobOut, mFloatUniformPointerId, xfbOutPtr, mXfbBuffers[bufferIndex], |
| {mIntNIds[0], 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 ShaderInterfaceVariableInfo &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.buffer == ShaderInterfaceVariableXfbInfo::kInvalid) |
| { |
| continue; |
| } |
| |
| ASSERT(xfb.stride != ShaderInterfaceVariableXfbInfo::kInvalid); |
| ASSERT(xfb.offset != ShaderInterfaceVariableXfbInfo::kInvalid); |
| |
| const uint32_t xfbDecorationValues[kXfbDecorationCount] = { |
| xfb.buffer, |
| xfb.stride, |
| xfb.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 ShaderInterfaceVariableInfo &info, |
| spirv::IdRef id, |
| spirv::Blob *blobOut) |
| { |
| if (mIsEmulated || info.xfb.buffer == ShaderInterfaceVariableXfbInfo::kInvalid) |
| { |
| return; |
| } |
| |
| ASSERT(info.xfb.stride != ShaderInterfaceVariableXfbInfo::kInvalid); |
| ASSERT(info.xfb.offset != ShaderInterfaceVariableXfbInfo::kInvalid); |
| |
| const uint32_t xfbDecorationValues[kXfbDecorationCount] = { |
| info.xfb.buffer, |
| info.xfb.stride, |
| info.xfb.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 GlslangSpirvOptions &options) : mOptions(options) {} |
| |
| void writePositionTransformation(const SpirvIDDiscoverer &ids, |
| spirv::IdRef positionPointerId, |
| spirv::IdRef positionId, |
| spirv::Blob *blobOut); |
| |
| private: |
| void preRotateXY(const SpirvIDDiscoverer &ids, |
| spirv::IdRef xId, |
| spirv::IdRef yId, |
| spirv::IdRef *rotatedXIdOut, |
| spirv::IdRef *rotatedYIdOut, |
| spirv::Blob *blobOut); |
| void transformZToVulkanClipSpace(const SpirvIDDiscoverer &ids, |
| spirv::IdRef zId, |
| spirv::IdRef wId, |
| spirv::IdRef *correctedZIdOut, |
| spirv::Blob *blobOut); |
| |
| GlslangSpirvOptions mOptions; |
| }; |
| |
| void SpirvPositionTransformer::writePositionTransformation(const SpirvIDDiscoverer &ids, |
| spirv::IdRef positionPointerId, |
| spirv::IdRef positionId, |
| spirv::Blob *blobOut) |
| { |
| // In GL the viewport transformation is slightly different - see the GL 2.0 spec section "2.12.1 |
| // Controlling the Viewport". In Vulkan the corresponding spec section is currently "23.4. |
| // Coordinate Transformations". The following transformation needs to be done: |
| // |
| // z_vk = 0.5 * (w_gl + z_gl) |
| // |
| // where z_vk is the depth output of a Vulkan geometry-stage shader and z_gl is the same for GL. |
| |
| // Generate the following SPIR-V for prerotation and depth transformation: |
| // |
| // // Create gl_Position.x and gl_Position.y for transformation, as well as gl_Position.z |
| // // and gl_Position.w for later. |
| // %x = OpCompositeExtract %mFloatId %Position 0 |
| // %y = OpCompositeExtract %mFloatId %Position 1 |
| // %z = OpCompositeExtract %mFloatId %Position 2 |
| // %w = OpCompositeExtract %mFloatId %Position 3 |
| // |
| // // Transform %x and %y based on pre-rotation. This could include swapping the two ids |
| // // (in the transformer, no need to generate SPIR-V instructions for that), and/or |
| // // negating either component. To negate a component, the following instruction is used: |
| // (optional:) %negated = OpFNegate %mFloatId %component |
| // |
| // // Transform %z if necessary, based on the above formula. |
| // %zPlusW = OpFAdd %mFloatId %z %w |
| // %correctedZ = OpFMul %mFloatId %zPlusW %mFloatHalfId |
| // |
| // // Create the rotated gl_Position from the rotated x and y and corrected z components. |
| // %RotatedPosition = OpCompositeConstruct %mVec4Id %rotatedX %rotatedY %correctedZ %w |
| // // Store the results back in gl_Position |
| // OpStore %PositionPointer %RotatedPosition |
| // |
| const spirv::IdRef xId(SpirvTransformerBase::GetNewId(blobOut)); |
| const spirv::IdRef yId(SpirvTransformerBase::GetNewId(blobOut)); |
| const spirv::IdRef zId(SpirvTransformerBase::GetNewId(blobOut)); |
| const spirv::IdRef wId(SpirvTransformerBase::GetNewId(blobOut)); |
| const spirv::IdRef rotatedPositionId(SpirvTransformerBase::GetNewId(blobOut)); |
| |
| spirv::WriteCompositeExtract(blobOut, ids.floatId(), xId, positionId, |
| {spirv::LiteralInteger{0}}); |
| spirv::WriteCompositeExtract(blobOut, ids.floatId(), yId, positionId, |
| {spirv::LiteralInteger{1}}); |
| spirv::WriteCompositeExtract(blobOut, ids.floatId(), zId, positionId, |
| {spirv::LiteralInteger{2}}); |
| spirv::WriteCompositeExtract(blobOut, ids.floatId(), wId, positionId, |
| {spirv::LiteralInteger{3}}); |
| |
| spirv::IdRef rotatedXId; |
| spirv::IdRef rotatedYId; |
| preRotateXY(ids, xId, yId, &rotatedXId, &rotatedYId, blobOut); |
| |
| spirv::IdRef correctedZId; |
| transformZToVulkanClipSpace(ids, zId, wId, &correctedZId, blobOut); |
| |
| spirv::WriteCompositeConstruct(blobOut, ids.vec4Id(), rotatedPositionId, |
| {rotatedXId, rotatedYId, correctedZId, wId}); |
| spirv::WriteStore(blobOut, positionPointerId, rotatedPositionId, nullptr); |
| } |
| |
| void SpirvPositionTransformer::preRotateXY(const SpirvIDDiscoverer &ids, |
| spirv::IdRef xId, |
| spirv::IdRef yId, |
| spirv::IdRef *rotatedXIdOut, |
| spirv::IdRef *rotatedYIdOut, |
| spirv::Blob *blobOut) |
| { |
| switch (mOptions.preRotation) |
| { |
| case SurfaceRotation::Identity: |
| // [ 1 0] [x] |
| // [ 0 1] * [y] |
| *rotatedXIdOut = xId; |
| *rotatedYIdOut = yId; |
| break; |
| case SurfaceRotation::FlippedIdentity: |
| if (mOptions.negativeViewportSupported) |
| { |
| // [ 1 0] [x] |
| // [ 0 1] * [y] |
| *rotatedXIdOut = xId; |
| *rotatedYIdOut = yId; |
| } |
| else |
| { |
| // [ 1 0] [x] |
| // [ 0 -1] * [y] |
| *rotatedXIdOut = xId; |
| *rotatedYIdOut = SpirvTransformerBase::GetNewId(blobOut); |
| spirv::WriteFNegate(blobOut, ids.floatId(), *rotatedYIdOut, yId); |
| } |
| break; |
| case SurfaceRotation::Rotated90Degrees: |
| case SurfaceRotation::FlippedRotated90Degrees: |
| // [ 0 1] [x] |
| // [-1 0] * [y] |
| *rotatedXIdOut = yId; |
| *rotatedYIdOut = SpirvTransformerBase::GetNewId(blobOut); |
| spirv::WriteFNegate(blobOut, ids.floatId(), *rotatedYIdOut, xId); |
| break; |
| case SurfaceRotation::Rotated180Degrees: |
| case SurfaceRotation::FlippedRotated180Degrees: |
| // [-1 0] [x] |
| // [ 0 -1] * [y] |
| *rotatedXIdOut = SpirvTransformerBase::GetNewId(blobOut); |
| *rotatedYIdOut = SpirvTransformerBase::GetNewId(blobOut); |
| spirv::WriteFNegate(blobOut, ids.floatId(), *rotatedXIdOut, xId); |
| spirv::WriteFNegate(blobOut, ids.floatId(), *rotatedYIdOut, yId); |
| break; |
| case SurfaceRotation::Rotated270Degrees: |
| case SurfaceRotation::FlippedRotated270Degrees: |
| // [ 0 -1] [x] |
| // [ 1 0] * [y] |
| *rotatedXIdOut = SpirvTransformerBase::GetNewId(blobOut); |
| *rotatedYIdOut = xId; |
| spirv::WriteFNegate(blobOut, ids.floatId(), *rotatedXIdOut, yId); |
| break; |
| default: |
| UNREACHABLE(); |
| } |
| } |
| |
| void SpirvPositionTransformer::transformZToVulkanClipSpace(const SpirvIDDiscoverer &ids, |
| spirv::IdRef zId, |
| spirv::IdRef wId, |
| spirv::IdRef *correctedZIdOut, |
| spirv::Blob *blobOut) |
| { |
| if (!mOptions.transformPositionToVulkanClipSpace) |
| { |
| *correctedZIdOut = zId; |
| return; |
| } |
| |
| const spirv::IdRef zPlusWId(SpirvTransformerBase::GetNewId(blobOut)); |
| *correctedZIdOut = SpirvTransformerBase::GetNewId(blobOut); |
| |
| // %zPlusW = OpFAdd %mFloatId %z %w |
| spirv::WriteFAdd(blobOut, ids.floatId(), zPlusWId, zId, wId); |
| |
| // %correctedZ = OpFMul %mFloatId %zPlusW %mFloatHalfId |
| spirv::WriteFMul(blobOut, ids.floatId(), *correctedZIdOut, zPlusWId, ids.floatHalfId()); |
| } |
| |
| // 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 GlslangSpirvOptions &options, |
| const ShaderInterfaceVariableInfoMap &variableInfoMap, |
| spirv::Blob *spirvBlobOut) |
| : SpirvTransformerBase(spirvBlobIn, variableInfoMap, spirvBlobOut), |
| mOptions(options), |
| mXfbCodeGenerator(options.isTransformFeedbackEmulated), |
| mPositionTransformer(options) |
| {} |
| |
| bool 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 visitName(const uint32_t *instruction); |
| void visitMemberName(const uint32_t *instruction); |
| void visitTypeArray(const uint32_t *instruction); |
| void visitTypeFloat(const uint32_t *instruction); |
| void visitTypeInt(const uint32_t *instruction); |
| void visitTypePointer(const uint32_t *instruction); |
| void visitTypeVector(const uint32_t *instruction); |
| void visitVariable(const uint32_t *instruction); |
| |
| // Instructions that potentially need transformation. They return true if the instruction is |
| // transformed. If false is returned, the instruction should be copied as-is. |
| TransformationState transformAccessChain(const uint32_t *instruction); |
| TransformationState transformCapability(const uint32_t *instruction); |
| TransformationState transformDebugInfo(const uint32_t *instruction, spv::Op op); |
| TransformationState transformEmitVertex(const uint32_t *instruction); |
| TransformationState transformEntryPoint(const uint32_t *instruction); |
| TransformationState transformDecorate(const uint32_t *instruction); |
| TransformationState transformMemberDecorate(const uint32_t *instruction); |
| TransformationState transformTypePointer(const uint32_t *instruction); |
| TransformationState transformTypeStruct(const uint32_t *instruction); |
| TransformationState transformReturn(const uint32_t *instruction); |
| TransformationState transformVariable(const uint32_t *instruction); |
| TransformationState transformExecutionMode(const uint32_t *instruction); |
| |
| // Helpers: |
| void visitTypeHelper(spirv::IdResult id, spirv::IdRef typeId); |
| void writePendingDeclarations(); |
| void writeInputPreamble(); |
| void writeOutputPrologue(); |
| |
| // Special flags: |
| GlslangSpirvOptions mOptions; |
| |
| // Traversal state: |
| bool mInsertFunctionVariables = false; |
| spirv::IdRef mEntryPointId; |
| spirv::IdRef mCurrentFunctionId; |
| |
| SpirvIDDiscoverer mIds; |
| |
| // Transformation state: |
| |
| SpirvPerVertexTrimmer mPerVertexTrimmer; |
| SpirvInactiveVaryingRemover mInactiveVaryingRemover; |
| SpirvVaryingPrecisionFixer mVaryingPrecisionFixer; |
| SpirvTransformFeedbackCodeGenerator mXfbCodeGenerator; |
| SpirvPositionTransformer mPositionTransformer; |
| }; |
| |
| bool SpirvTransformer::transform() |
| { |
| onTransformBegin(); |
| |
| // First, find all necessary ids and associate them with the information required to transform |
| // their decorations. |
| resolveVariableIds(); |
| |
| while (mCurrentWord < mSpirvBlobIn.size()) |
| { |
| transformInstruction(); |
| } |
| |
| return true; |
| } |
| |
| void SpirvTransformer::resolveVariableIds() |
| { |
| const size_t indexBound = mSpirvBlobIn[kHeaderIndexIndexBound]; |
| |
| mIds.init(indexBound); |
| mInactiveVaryingRemover.init(indexBound); |
| mVaryingPrecisionFixer.init(indexBound); |
| |
| // Allocate storage for id-to-info map. If %i is the id of a name in mVariableInfoMap, index i |
| // in this vector will hold a pointer to the ShaderInterfaceVariableInfo object associated with |
| // that name in mVariableInfoMap. |
| mVariableInfoById.resize(indexBound, nullptr); |
| |
| size_t currentWord = 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::OpName: |
| visitName(instruction); |
| break; |
| case spv::OpMemberName: |
| visitMemberName(instruction); |
| break; |
| case spv::OpTypeArray: |
| visitTypeArray(instruction); |
| break; |
| case spv::OpTypeFloat: |
| visitTypeFloat(instruction); |
| break; |
| case spv::OpTypeInt: |
| visitTypeInt(instruction); |
| break; |
| case spv::OpTypePointer: |
| visitTypePointer(instruction); |
| break; |
| case spv::OpTypeVector: |
| visitTypeVector(instruction); |
| break; |
| case spv::OpVariable: |
| visitVariable(instruction); |
| break; |
| case spv::OpFunction: |
| // SPIR-V is structured in sections (SPIR-V 1.0 Section 2.4 Logical Layout of a |
| // Module). Names appear before decorations, which are followed by type+variables |
| // and finally functions. We are only interested in name and variable declarations |
| // (as well as type declarations for the sake of nameless interface blocks). Early |
| // out when the function declaration section is met. |
| return; |
| default: |
| break; |
| } |
| |
| currentWord += wordCount; |
| } |
| 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. |
| // |
| // If this is the first OpFunction instruction, this is also where the declaration section |
| // finishes, so we need to declare anything that we need but didn't find there already right |
| // now. |
| if (!mIsInFunctionSection) |
| { |
| writePendingDeclarations(); |
| } |
| mIsInFunctionSection = true; |
| |
| // Only write function variables for the EntryPoint function for non-compute shaders |
| mInsertFunctionVariables = |
| mCurrentFunctionId == mEntryPointId && mOptions.shaderType != gl::ShaderType::Compute; |
| } |
| |
| // Only look at interesting instructions. |
| TransformationState transformationState = TransformationState::Unchanged; |
| |
| if (mIsInFunctionSection) |
| { |
| // After we process an OpFunction instruction and any instructions that must come |
| // immediately after OpFunction we need to check if there are any precision mismatches that |
| // need to be handled. If so, output OpVariable for each variable that needed to change from |
| // a StorageClassOutput to a StorageClassFunction. |
| if (mInsertFunctionVariables && opCode != spv::OpFunction && |
| opCode != spv::OpFunctionParameter && opCode != spv::OpLabel && |
| opCode != spv::OpVariable) |
| { |
| writeInputPreamble(); |
| mInsertFunctionVariables = false; |
| } |
| |
| // Look at in-function opcodes. |
| switch (opCode) |
| { |
| case spv::OpAccessChain: |
| case spv::OpInBoundsAccessChain: |
| case spv::OpPtrAccessChain: |
| case spv::OpInBoundsPtrAccessChain: |
| transformationState = transformAccessChain(instruction); |
| break; |
| |
| case spv::OpEmitVertex: |
| transformationState = transformEmitVertex(instruction); |
| break; |
| case spv::OpReturn: |
| transformationState = transformReturn(instruction); |
| break; |
| default: |
| break; |
| } |
| } |
| else |
| { |
| // Look at global declaration opcodes. |
| switch (opCode) |
| { |
| case spv::OpName: |
| case spv::OpMemberName: |
| case spv::OpString: |
| case spv::OpLine: |
| case spv::OpNoLine: |
| case spv::OpModuleProcessed: |
| transformationState = transformDebugInfo(instruction, opCode); |
| 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::OpTypePointer: |
| transformationState = transformTypePointer(instruction); |
| break; |
| case spv::OpTypeStruct: |
| transformationState = transformTypeStruct(instruction); |
| break; |
| case spv::OpVariable: |
| transformationState = transformVariable(instruction); |
| break; |
| case spv::OpExecutionMode: |
| transformationState = transformExecutionMode(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() |
| { |
| // 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 (IsRotationIdentity(mOptions.preRotation) && !mOptions.transformPositionToVulkanClipSpace && |
| !mOptions.isTransformFeedbackStage) |
| { |
| return; |
| } |
| |
| mIds.writePendingDeclarations(mSpirvBlobOut); |
| mXfbCodeGenerator.writePendingDeclarations(mVariableInfoById, mIds, mSpirvBlobOut); |
| } |
| |
| // Called by transformInstruction to insert necessary instructions for casting varyings. |
| void SpirvTransformer::writeInputPreamble() |
| { |
| mVaryingPrecisionFixer.writeInputPreamble(mVariableInfoById, mOptions.shaderType, |
| mSpirvBlobOut); |
| } |
| |
| // Called by transformInstruction to insert necessary instructions for casting varyings and |
| // modifying gl_Position. |
| void SpirvTransformer::writeOutputPrologue() |
| { |
| mVaryingPrecisionFixer.writeOutputPrologue(mVariableInfoById, mOptions.shaderType, |
| mSpirvBlobOut); |
| |
| if (!mIds.outputPerVertexId().valid()) |
| { |
| return; |
| } |
| |
| // Whether gl_Position should be transformed to account for prerotation and Vulkan clip space. |
| const bool transformPosition = |
| !IsRotationIdentity(mOptions.preRotation) || mOptions.transformPositionToVulkanClipSpace; |
| 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 %mVec4OutTypePointerId %mOutputPerVertexId %mInt0Id |
| // // Load gl_Position |
| // %Position = OpLoad %mVec4Id %PositionPointer |
| // |
| const spirv::IdRef positionPointerId(getNewId()); |
| const spirv::IdRef positionId(getNewId()); |
| |
| spirv::WriteAccessChain(mSpirvBlobOut, mIds.vec4OutTypePointerId(), positionPointerId, |
| mIds.outputPerVertexId(), {mIds.int0Id()}); |
| spirv::WriteLoad(mSpirvBlobOut, mIds.vec4Id(), positionId, positionPointerId, nullptr); |
| |
| // Write transform feedback output before modifying gl_Position. |
| if (isXfbExtensionStage) |
| { |
| mXfbCodeGenerator.writeTransformFeedbackExtensionOutput(mIds, positionId, mSpirvBlobOut); |
| } |
| |
| if (transformPosition) |
| { |
| mPositionTransformer.writePositionTransformation(mIds, positionPointerId, positionId, |
| mSpirvBlobOut); |
| } |
| } |
| |
| void SpirvTransformer::visitDecorate(const uint32_t *instruction) |
| { |
| spirv::IdRef id; |
| spv::Decoration decoration; |
| spirv::ParseDecorate(instruction, &id, &decoration, nullptr); |
| |
| mIds.visitDecorate(id, decoration); |
| |
| if (mIds.isIOBlock(id)) |
| { |
| // For I/O blocks, associate the type with the info, which is used to decorate its members |
| // with transform feedback if any. |
| spirv::LiteralString name = mIds.getName(id); |
| ASSERT(name != nullptr); |
| |
| const ShaderInterfaceVariableInfo &info = mVariableInfoMap.get(mOptions.shaderType, name); |
| mVariableInfoById[id] = &info; |
| } |
| } |
| |
| void SpirvTransformer::visitName(const uint32_t *instruction) |
| { |
| spirv::IdRef id; |
| spirv::LiteralString name; |
| spirv::ParseName(instruction, &id, &name); |
| |
| mIds.visitName(id, name); |
| mXfbCodeGenerator.visitName(id, name); |
| } |
| |
| void SpirvTransformer::visitMemberName(const uint32_t *instruction) |
| { |
| spirv::IdRef id; |
| spirv::LiteralInteger member; |
| spirv::LiteralString name; |
| spirv::ParseMemberName(instruction, &id, &member, &name); |
| |
| if (!mVariableInfoMap.contains(mOptions.shaderType, name)) |
| { |
| return; |
| } |
| |
| const ShaderInterfaceVariableInfo &info = mVariableInfoMap.get(mOptions.shaderType, name); |
| |
| mIds.visitMemberName(info, id, member, name); |
| } |
| |
| void SpirvTransformer::visitTypeArray(const uint32_t *instruction) |
| { |
| spirv::IdResult id; |
| spirv::IdRef elementType; |
| spirv::IdRef length; |
| spirv::ParseTypeArray(instruction, &id, &elementType, &length); |
| |
| mIds.visitTypeArray(id, elementType, length); |
| } |
| |
| void SpirvTransformer::visitTypeFloat(const uint32_t *instruction) |
| { |
| spirv::IdResult id; |
| spirv::LiteralInteger width; |
| spirv::ParseTypeFloat(instruction, &id, &width); |
| |
| mIds.visitTypeFloat(id, width); |
| } |
| |
| void SpirvTransformer::visitTypeInt(const uint32_t *instruction) |
| { |
| spirv::IdResult id; |
| spirv::LiteralInteger width; |
| spirv::LiteralInteger signedness; |
| spirv::ParseTypeInt(instruction, &id, &width, &signedness); |
| |
| mIds.visitTypeInt(id, width, signedness); |
| } |
| |
| void SpirvTransformer::visitTypePointer(const uint32_t *instruction) |
| { |
| spirv::IdResult id; |
| spv::StorageClass storageClass; |
| spirv::IdRef typeId; |
| spirv::ParseTypePointer(instruction, &id, &storageClass, &typeId); |
| |
| mIds.visitTypePointer(id, storageClass, typeId); |
| mVaryingPrecisionFixer.visitTypePointer(id, storageClass, typeId); |
| mXfbCodeGenerator.visitTypePointer(id, storageClass, typeId); |
| } |
| |
| void SpirvTransformer::visitTypeVector(const uint32_t *instruction) |
| { |
| spirv::IdResult id; |
| spirv::IdRef componentId; |
| spirv::LiteralInteger componentCount; |
| spirv::ParseTypeVector(instruction, &id, &componentId, &componentCount); |
| |
| mIds.visitTypeVector(id, componentId, componentCount); |
| mXfbCodeGenerator.visitTypeVector(mIds, id, componentId, componentCount); |
| } |
| |
| void SpirvTransformer::visitVariable(const uint32_t *instruction) |
| { |
| spirv::IdResultType typeId; |
| spirv::IdResult id; |
| spv::StorageClass storageClass; |
| spirv::ParseVariable(instruction, &typeId, &id, &storageClass, nullptr); |
| |
| spirv::LiteralString name; |
| SpirvVariableType variableType = mIds.visitVariable(typeId, id, storageClass, &name); |
| |
| if (variableType == SpirvVariableType::Other) |
| { |
| return; |
| } |
| |
| // The ids are unique. |
| ASSERT(id < mVariableInfoById.size()); |
| ASSERT(mVariableInfoById[id] == nullptr); |
| |
| if (variableType == SpirvVariableType::BuiltIn) |
| { |
| // Make all builtins point to this no-op info. Adding this entry allows us to ASSERT that |
| // every shader interface variable is processed during the SPIR-V transformation. This is |
| // done when iterating the ids provided by OpEntryPoint. |
| mVariableInfoById[id] = &mBuiltinVariableInfo; |
| return; |
| } |
| |
| // Every shader interface variable should have an associated data. |
| const ShaderInterfaceVariableInfo &info = mVariableInfoMap.get(mOptions.shaderType, name); |
| |
| // Associate the id of this name with its info. |
| mVariableInfoById[id] = &info; |
| |
| mVaryingPrecisionFixer.visitVariable(info, mOptions.shaderType, typeId, id, storageClass, |
| mSpirvBlobOut); |
| if (mOptions.isTransformFeedbackStage) |
| { |
| mXfbCodeGenerator.visitVariable(info, mOptions.shaderType, name, typeId, id, storageClass); |
| } |
| } |
| |
| 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 (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. |
| 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: |
| if (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) |
| { |
| mXfbCodeGenerator.addMemberDecorate(*info, id, mSpirvBlobOut); |
| } |
| break; |
| 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 (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) |
| { |
| mXfbCodeGenerator.addDecorate(*info, 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); |
| |
| return mPerVertexTrimmer.transformMemberDecorate(mIds, typeId, member, decoration); |
| } |
| |
| TransformationState SpirvTransformer::transformCapability(const uint32_t *instruction) |
| { |
| spv::Capability capability; |
| spirv::ParseCapability(instruction, &capability); |
| |
| return mXfbCodeGenerator.transformCapability(capability, mSpirvBlobOut); |
| } |
| |
| TransformationState SpirvTransformer::transformDebugInfo(const uint32_t *instruction, spv::Op op) |
| { |
| if (mOptions.removeDebugInfo) |
| { |
| // Strip debug info to reduce binary size. |
| return TransformationState::Transformed; |
| } |
| |
| // In the case of OpMemberName, unconditionally remove stripped gl_PerVertex members. |
| if (op == spv::OpMemberName) |
| { |
| spirv::IdRef id; |
| spirv::LiteralInteger member; |
| spirv::LiteralString name; |
| spirv::ParseMemberName(instruction, &id, &member, &name); |
| |
| return mPerVertexTrimmer.transformMemberName(mIds, id, member, name); |
| } |
| |
| if (op == spv::OpName) |
| { |
| spirv::IdRef id; |
| spirv::LiteralString name; |
| spirv::ParseName(instruction, &id, &name); |
| |
| return mXfbCodeGenerator.transformName(id, name); |
| } |
| |
| return TransformationState::Unchanged; |
| } |
| |
| TransformationState SpirvTransformer::transformEmitVertex(const uint32_t *instruction) |
| { |
| // This is only possible in geometry shaders. |
| ASSERT(mOptions.shaderType == gl::ShaderType::Geometry); |
| |
| // Write the temporary variables that hold varyings data before EmitVertex(). |
| writeOutputPrologue(); |
| |
| return TransformationState::Unchanged; |
| } |
| |
| TransformationState SpirvTransformer::transformEntryPoint(const uint32_t *instruction) |
| { |
| // Should only have one EntryPoint |
| ASSERT(!mEntryPointId.valid()); |
| |
| spv::ExecutionModel executionModel; |
| spirv::LiteralString name; |
| spirv::IdRefList interfaceList; |
| spirv::ParseEntryPoint(instruction, &executionModel, &mEntryPointId, &name, &interfaceList); |
| |
| mInactiveVaryingRemover.modifyEntryPointInterfaceList(mVariableInfoById, mOptions.shaderType, |
| &interfaceList); |
| mVaryingPrecisionFixer.modifyEntryPointInterfaceList(&interfaceList); |
| |
| // Write the entry point with the inactive interface variables removed. |
| spirv::WriteEntryPoint(mSpirvBlobOut, executionModel, mEntryPointId, name, interfaceList); |
| |
| // Add an OpExecutionMode Xfb instruction if necessary. |
| mXfbCodeGenerator.addExecutionMode(mEntryPointId, 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); |
| |
| return mInactiveVaryingRemover.transformTypePointer(mIds, id, storageClass, typeId, |
| mSpirvBlobOut); |
| } |
| |
| TransformationState SpirvTransformer::transformTypeStruct(const uint32_t *instruction) |
| { |
| spirv::IdResult id; |
| spirv::IdRefList memberList; |
| ParseTypeStruct(instruction, &id, &memberList); |
| |
| return mPerVertexTrimmer.transformTypeStruct(mIds, id, &memberList, mSpirvBlobOut); |
| } |
| |
| TransformationState SpirvTransformer::transformReturn(const uint32_t *instruction) |
| { |
| if (mCurrentFunctionId != mEntryPointId) |
| { |
| if (mOptions.isTransformFeedbackStage) |
| { |
| // Transform feedback emulation is written to a designated function. Allow its code to |
| // be generated if this is the right function. |
| mXfbCodeGenerator.writeTransformFeedbackEmulationOutput( |
| mIds, mVaryingPrecisionFixer, mCurrentFunctionId, mSpirvBlobOut); |
| } |
| |
| // We only need to process the precision info when returning from the entry point function |
| return TransformationState::Unchanged; |
| } |
| |
| // For geometry shaders, this operations is done before every EmitVertex() instead. |
| // Additionally, this transformation (which affects output varyings) doesn't apply to fragment |
| // shaders. |
| if (mOptions.shaderType == gl::ShaderType::Geometry || |
| mOptions.shaderType == gl::ShaderType::Fragment) |
| { |
| return TransformationState::Unchanged; |
| } |
| |
| writeOutputPrologue(); |
| |
| return TransformationState::Unchanged; |
| } |
| |
| 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; |
| } |
| |
| // 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 (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::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 (info->activeStages[mOptions.shaderType] && !info->useRelaxedPrecision) |
| { |
| return TransformationState::Unchanged; |
| } |
| |
| return mInactiveVaryingRemover.transformAccessChain(typeId, id, baseId, indexList, |
| mSpirvBlobOut); |
| } |
| |
| TransformationState SpirvTransformer::transformExecutionMode(const uint32_t *instruction) |
| { |
| spirv::IdRef entryPoint; |
| spv::ExecutionMode mode; |
| spirv::ParseExecutionMode(instruction, &entryPoint, &mode); |
| |
| if (mode == spv::ExecutionModeEarlyFragmentTests && |
| mOptions.removeEarlyFragmentTestsOptimization) |
| { |
| // Drop the instruction. |
| return TransformationState::Transformed; |
| } |
| return TransformationState::Unchanged; |
| } |
| |
| 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) |
| { |
| mVariableInfoById = std::move(variableInfoById); |
| } |
| |
| bool 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 visitTypeFloat(const uint32_t *instruction); |
| void visitTypeVector(const uint32_t *instruction); |
| void visitTypeMatrix(const uint32_t *instruction); |
| 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 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; |
| // Whether the expanded matrix OpVariables are generated. |
| bool mHaveMatricesExpanded = false; |
| // Whether initialization of the matrix attributes should be written at the beginning of the |
| // current function. |
| bool mWriteExpandedMatrixInitialization = false; |
| spirv::IdRef mEntryPointId; |
| |
| // Id of attribute types; float and veci. This array is one-based, and [0] is unused. |
| // |
| // [1]: id of OpTypeFloat 32 |
| // [N]: id of OpTypeVector %[1] N, N = {2, 3, 4} |
| // |
| // In other words, index of the array corresponds to the number of components in the type. |
| std::array<spirv::IdRef, 5> mFloatTypes; |
| |
| // Corresponding to mFloatTypes, [i]: id of OpMatrix %mFloatTypes[i] i. Note that only square |
| // matrices are possible as attributes in GLSL ES 1.00. [0] and [1] are unused. |
| std::array<spirv::IdRef, 5> mMatrixTypes; |
| |
| // Corresponding to mFloatTypes, [i]: id of OpTypePointer Input %mFloatTypes[i]. [0] is unused. |
| std::array<spirv::IdRef, 5> mInputTypePointers; |
| |
| // Corresponding to mFloatTypes, [i]: id of OpTypePointer Private %mFloatTypes[i]. [0] is |
| // unused. |
| std::array<spirv::IdRef, 5> mPrivateFloatTypePointers; |
| |
| // Corresponding to mMatrixTypes, [i]: id of OpTypePointer Private %mMatrixTypes[i]. [0] and |
| // [1] are unused. |
| std::array<spirv::IdRef, 5> mPrivateMatrixTypePointers; |
| }; |
| |
| bool SpirvVertexAttributeAliasingTransformer::transform() |
| { |
| onTransformBegin(); |
| |
| preprocessAliasingAttributes(); |
| |
| while (mCurrentWord < mSpirvBlobIn.size()) |
| { |
| transformInstruction(); |
| } |
| |
| return true; |
| } |
| |
| void SpirvVertexAttributeAliasingTransformer::preprocessAliasingAttributes() |
| { |
| const size_t indexBound = mSpirvBlobIn[kHeaderIndexIndexBound]; |
| |
| mVariableInfoById.resize(indexBound, nullptr); |
| mIsAliasingAttributeById.resize(indexBound, false); |
| mExpandedMatrixFirstVectorIdById.resize(indexBound); |
| |
| // Go through attributes and find out which alias which. |
| for (size_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) |
| { |
| // Declare the expanded matrix variables right before the first function declaration. |
| if (!mHaveMatricesExpanded) |
| { |
| declareExpandedMatrixVectors(); |
| mHaveMatricesExpanded = true; |
| } |
| |
| // SPIR-V is structured in sections. Function declarations come last. |
| mIsInFunctionSection = true; |
| |
| // 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. |
| |
| spirv::IdResultType id; |
| spirv::IdResult functionId; |
| spv::FunctionControlMask functionControl; |
| spirv::IdRef functionType; |
| spirv::ParseFunction(instruction, &id, &functionId, &functionControl, &functionType); |
| |
| mWriteExpandedMatrixInitialization = functionId == mEntryPointId; |
| } |
| |
| // Only look at interesting instructions. |
| TransformationState transformationState = TransformationState::Unchanged; |
| |
| if (mIsInFunctionSection) |
| { |
| // Write expanded matrix initialization right after the entry point's OpFunction and any |
| // instruction that must come immediately after it. |
| if (mWriteExpandedMatrixInitialization && opCode != spv::OpFunction && |
| opCode != spv::OpFunctionParameter && opCode != spv::OpLabel && |
| opCode != spv::OpVariable) |
| { |
| writeExpandedMatrixInitialization(); |
| mWriteExpandedMatrixInitialization = false; |
| } |
| |
| // Look at in-function opcodes. |
| switch (opCode) |
| { |
| 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::OpTypeFloat: |
| visitTypeFloat(instruction); |
| break; |
| case spv::OpTypeVector: |
| visitTypeVector(instruction); |
| break; |
| case spv::OpTypeMatrix: |
| visitTypeMatrix(instruction); |
| break; |
| case spv::OpTypePointer: |
| visitTypePointer(instruction); |
| break; |
| // Instructions that may need transformation: |
| case spv::OpEntryPoint: |
| transformationState = transformEntryPoint(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::visitTypeFloat(const uint32_t *instruction) |
| { |
| spirv::IdResult id; |
| spirv::LiteralInteger width; |
| spirv::ParseTypeFloat(instruction, &id, &width); |
| |
| // Only interested in OpTypeFloat 32. |
| if (width == 32) |
| { |
| ASSERT(!mFloatTypes[1].valid()); |
| mFloatTypes[1] = id; |
| } |
| } |
| |
| void SpirvVertexAttributeAliasingTransformer::visitTypeVector(const uint32_t *instruction) |
| { |
| spirv::IdResult id; |
| spirv::IdRef componentId; |
| spirv::LiteralInteger componentCount; |
| spirv::ParseTypeVector(instruction, &id, &componentId, &componentCount); |
| |
| // Only interested in OpTypeVector %f32 N, where %f32 is the id of OpTypeFloat 32. |
| if (componentId == mFloatTypes[1]) |
| { |
| ASSERT(componentCount >= 2 && componentCount <= 4); |
| ASSERT(!mFloatTypes[componentCount].valid()); |
| mFloatTypes[componentCount] = id; |
| } |
| } |
| |
| void SpirvVertexAttributeAliasingTransformer::visitTypeMatrix(const uint32_t *instruction) |
| { |
| spirv::IdResult id; |
| spirv::IdRef columnType; |
| spirv::LiteralInteger columnCount; |
| spirv::ParseTypeMatrix(instruction, &id, &columnType, &columnCount); |
| |
| // Only interested in OpTypeMatrix %vecN, where %vecN is the id of OpTypeVector %f32 N. |
| // This is only for square matN types (as allowed by GLSL ES 1.00), so columnCount is the same |
| // as rowCount. |
| if (columnType == mFloatTypes[columnCount]) |
| { |
| ASSERT(!mMatrixTypes[columnCount].valid()); |
| mMatrixTypes[columnCount] = id; |
| } |
| } |
| |
| 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 (size_t n = 2; n < mFloatTypes.size(); ++n) |
| { |
| if (typeId == mFloatTypes[n]) |
| { |
| ASSERT(!mInputTypePointers[n].valid()); |
| mInputTypePointers[n] = id; |
| break; |
| } |
| } |
| } |
| else if (storageClass == spv::StorageClassPrivate) |
| { |
| ASSERT(mFloatTypes.size() == mMatrixTypes.size()); |
| for (size_t n = 2; n < mMatrixTypes.size(); ++n) |
| { |
| // Note that Private types may not be unique, as the previous transformation can |
| // generate duplicates. |
| if (typeId == mFloatTypes[n]) |
| { |
| mPrivateFloatTypePointers[n] = id; |
| break; |
| } |
| if (typeId == mMatrixTypes[n]) |
| { |
| mPrivateMatrixTypePointers[n] = id; |
| break; |
| } |
| } |
| } |
| } |
| |
| TransformationState SpirvVertexAttributeAliasingTransformer::transformEntryPoint( |
| const uint32_t *instruction) |
| { |
| // Should only have one EntryPoint |
| ASSERT(!mEntryPointId.valid()); |
| |
| // Remove aliasing attributes from the shader interface declaration. |
| spv::ExecutionModel executionModel; |
| spirv::LiteralString name; |
| spirv::IdRefList interfaceList; |
| spirv::ParseEntryPoint(instruction, &executionModel, &mEntryPointId, &name, &interfaceList); |
| |
| // 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(writeIndex); |
| |
| // Write the entry point with the aliasing attributes removed. |
| spirv::WriteEntryPoint(mSpirvBlobOut, executionModel, mEntryPointId, name, interfaceList); |
| |
| return TransformationState::Transformed; |
| } |
| |
| 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(mFloatTypes[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(mFloatTypes[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(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(mMatrixTypes[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); |
| ASSERT(mMatrixTypes[locationCount].valid()); |
| |
| // OpTypePointer Private %matrixType |
| spirv::IdRef privateType(mPrivateMatrixTypePointers[locationCount]); |
| if (!privateType.valid()) |
| { |
| privateType = getNewId(); |
| mPrivateMatrixTypePointers[locationCount] = privateType; |
| spirv::WriteTypePointer(mSpirvBlobOut, privateType, spv::StorageClassPrivate, |
| mMatrixTypes[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, |
| mFloatTypes[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 %mFloatTypes[i] in case needed (used in |
| // Op*AccessChain instructions, if any). |
| for (size_t n = 1; n < mFloatTypes.size(); ++n) |
| { |
| if (mFloatTypes[n].valid() && !mPrivateFloatTypePointers[n].valid()) |
| { |
| const spirv::IdRef privateType(getNewId()); |
| mPrivateFloatTypePointers[n] = privateType; |
| spirv::WriteTypePointer(mSpirvBlobOut, privateType, spv::StorageClassPrivate, |
| mFloatTypes[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, mFloatTypes[info->attributeComponentCount], replacementId, |
| vecLoadIds.back()); |
| } |
| |
| // Aggregate the vector loads into a matrix. |
| ASSERT(mMatrixTypes[locationCount].valid()); |
| const spirv::IdRef compositeId(getNewId()); |
| spirv::WriteCompositeConstruct(mSpirvBlobOut, mMatrixTypes[locationCount], compositeId, |
| vecLoadIds); |
| |
| // Store it in the private variable. |
| spirv::WriteStore(mSpirvBlobOut, matrixId, compositeId, nullptr); |
| } |
| } |
| |
| bool HasAliasingAttributes(const ShaderInterfaceVariableInfoMap &variableInfoMap) |
| { |
| gl::AttributesMask isLocationAssigned; |
| |
| for (const auto &infoIter : variableInfoMap.getIterator(gl::ShaderType::Vertex)) |
| { |
| const ShaderInterfaceVariableInfo &info = infoIter.second; |
| |
| // Ignore non attribute ids. |
| if (info.attributeComponentCount == 0) |
| { |
| continue; |
| } |
| |
| ASSERT(info.activeStages[gl::ShaderType::Vertex]); |
| ASSERT(info.location != ShaderInterfaceVariableInfo::kInvalid); |
| ASSERT(info.attributeLocationCount > 0); |
| |
| for (uint8_t offset = 0; offset < info.attributeLocationCount; ++offset) |
| { |
| uint32_t location = info.location + offset; |
| |
| // If there's aliasing, return immediately. |
| if (isLocationAssigned.test(location)) |
| { |
| return true; |
| } |
| |
| isLocationAssigned.set(location); |
| } |
| } |
| |
| return false; |
| } |
| } // anonymous namespace |
| |
| // ShaderInterfaceVariableInfo implementation. |
| const uint32_t ShaderInterfaceVariableInfo::kInvalid; |
| |
| ShaderInterfaceVariableInfo::ShaderInterfaceVariableInfo() {} |
| |
| // ShaderInterfaceVariableInfoMap implementation. |
| ShaderInterfaceVariableInfoMap::ShaderInterfaceVariableInfoMap() = default; |
| |
| ShaderInterfaceVariableInfoMap::~ShaderInterfaceVariableInfoMap() = default; |
| |
| void ShaderInterfaceVariableInfoMap::clear() |
| { |
| for (VariableNameToInfoMap &shaderMap : mData) |
| { |
| shaderMap.clear(); |
| } |
| } |
| |
| bool ShaderInterfaceVariableInfoMap::contains(gl::ShaderType shaderType, |
| const std::string &variableName) const |
| { |
| return mData[shaderType].find(variableName) != mData[shaderType].end(); |
| } |
| |
| const ShaderInterfaceVariableInfo &ShaderInterfaceVariableInfoMap::get( |
| gl::ShaderType shaderType, |
| const std::string &variableName) const |
| { |
| auto it = mData[shaderType].find(variableName); |
| ASSERT(it != mData[shaderType].end()); |
| return it->second; |
| } |
| |
| ShaderInterfaceVariableInfo &ShaderInterfaceVariableInfoMap::get(gl::ShaderType shaderType, |
| const std::string &variableName) |
| { |
| auto it = mData[shaderType].find(variableName); |
| ASSERT(it != mData[shaderType].end()); |
| return it->second; |
| } |
| |
| ShaderInterfaceVariableInfo &ShaderInterfaceVariableInfoMap::add(gl::ShaderType shaderType, |
| const std::string &variableName) |
| { |
| ASSERT(!contains(shaderType, variableName)); |
| return mData[shaderType][variableName]; |
| } |
| |
| ShaderInterfaceVariableInfo &ShaderInterfaceVariableInfoMap::addOrGet( |
| gl::ShaderType shaderType, |
| const std::string &variableName) |
| { |
| return mData[shaderType][variableName]; |
| } |
| |
| ShaderInterfaceVariableInfoMap::Iterator ShaderInterfaceVariableInfoMap::getIterator( |
| gl::ShaderType shaderType) const |
| { |
| return Iterator(mData[shaderType].begin(), mData[shaderType].end()); |
| } |
| |
| void GlslangInitialize() |
| { |
| int result = ShInitialize(); |
| ASSERT(result != 0); |
| GlslangWarmup(); |
| } |
| |
| void GlslangRelease() |
| { |
| int result = ShFinalize(); |
| ASSERT(result != 0); |
| } |
| |
| // Strip indices from the name. If there are non-zero indices, return false to indicate that this |
| // image uniform doesn't require set/binding. That is done on index 0. |
| bool GetImageNameWithoutIndices(std::string *name) |
| { |
| if (name->back() != ']') |
| { |
| return true; |
| } |
| |
| if (!UniformNameIsIndexZero(*name)) |
| { |
| return false; |
| } |
| |
| // Strip all indices |
| *name = name->substr(0, name->find('[')); |
| return true; |
| } |
| |
| std::string GlslangGetMappedSamplerName(const std::string &originalName) |
| { |
| std::string samplerName = originalName; |
| |
| // Samplers in structs are extracted. |
| std::replace(samplerName.begin(), samplerName.end(), '.', '_'); |
| |
| // Remove array elements |
| auto out = samplerName.begin(); |
| for (auto in = samplerName.begin(); in != samplerName.end(); in++) |
| { |
| if (*in == '[') |
| { |
| while (*in != ']') |
| { |
| in++; |
| ASSERT(in != samplerName.end()); |
| } |
| } |
| else |
| { |
| *out++ = *in; |
| } |
| } |
| |
| samplerName.erase(out, samplerName.end()); |
| |
| if (MappedSamplerNameNeedsUserDefinedPrefix(originalName)) |
| { |
| samplerName = sh::kUserDefinedNamePrefix + samplerName; |
| } |
| |
| return samplerName; |
| } |
| |
| std::string GetXfbBufferName(const uint32_t bufferIndex) |
| { |
| return sh::vk::kXfbEmulationBufferBlockName + Str(bufferIndex); |
| } |
| |
| void GlslangAssignLocations(const GlslangSourceOptions &options, |
| const gl::ProgramState &programState, |
| const gl::ProgramVaryingPacking &varyingPacking, |
| const gl::ShaderType shaderType, |
| const gl::ShaderType frontShaderType, |
| bool isTransformFeedbackStage, |
| GlslangProgramInterfaceInfo *programInterfaceInfo, |
| ShaderInterfaceVariableInfoMap *variableInfoMapOut) |
| { |
| const gl::ProgramExecutable &programExecutable = programState.getExecutable(); |
| |
| // Assign outputs to the fragment shader, if any. |
| if ((shaderType == gl::ShaderType::Fragment) && |
| programExecutable.hasLinkedShaderStage(gl::ShaderType::Fragment)) |
| { |
| AssignOutputLocations(programState, gl::ShaderType::Fragment, variableInfoMapOut); |
| } |
| |
| // Assign attributes to the vertex shader, if any. |
| if ((shaderType == gl::ShaderType::Vertex) && |
| programExecutable.hasLinkedShaderStage(gl::ShaderType::Vertex)) |
| { |
| AssignAttributeLocations(programExecutable, gl::ShaderType::Vertex, variableInfoMapOut); |
| } |
| |
| if (!programExecutable.hasLinkedShaderStage(gl::ShaderType::Compute)) |
| { |
| const gl::VaryingPacking &inputPacking = varyingPacking.getInputPacking(shaderType); |
| const gl::VaryingPacking &outputPacking = varyingPacking.getOutputPacking(shaderType); |
| |
| // Assign location to varyings generated for transform feedback capture |
| if (options.supportsTransformFeedbackExtension && |
| gl::ShaderTypeSupportsTransformFeedback(shaderType)) |
| { |
| AssignTransformFeedbackExtensionLocations(shaderType, programState, |
| isTransformFeedbackStage, |
| programInterfaceInfo, variableInfoMapOut); |
| } |
| |
| // Assign varying locations. |
| if (shaderType != gl::ShaderType::Vertex) |
| { |
| AssignVaryingLocations(options, inputPacking, shaderType, frontShaderType, |
| programInterfaceInfo, variableInfoMapOut); |
| } |
| if (shaderType != gl::ShaderType::Fragment) |
| { |
| AssignVaryingLocations(options, outputPacking, shaderType, frontShaderType, |
| programInterfaceInfo, variableInfoMapOut); |
| } |
| |
| // Assign qualifiers to all varyings captured by transform feedback |
| if (!programExecutable.getLinkedTransformFeedbackVaryings().empty() && |
| shaderType == programExecutable.getLinkedTransformFeedbackStage()) |
| { |
| AssignTransformFeedbackQualifiers(programExecutable, outputPacking, shaderType, |
| options.supportsTransformFeedbackExtension, |
| variableInfoMapOut); |
| } |
| } |
| |
| AssignUniformBindings(options, programExecutable, shaderType, programInterfaceInfo, |
| variableInfoMapOut); |
| AssignTextureBindings(options, programExecutable, shaderType, programInterfaceInfo, |
| variableInfoMapOut); |
| AssignNonTextureBindings(options, programExecutable, shaderType, programInterfaceInfo, |
| variableInfoMapOut); |
| |
| if (options.supportsTransformFeedbackEmulation && |
| gl::ShaderTypeSupportsTransformFeedback(shaderType)) |
| { |
| // If transform feedback emulation is not enabled, mark all transform feedback output |
| // buffers as inactive. |
| isTransformFeedbackStage = |
| isTransformFeedbackStage && options.enableTransformFeedbackEmulation; |
| |
| AssignTransformFeedbackEmulationBindings(shaderType, programState, isTransformFeedbackStage, |
| programInterfaceInfo, variableInfoMapOut); |
| } |
| } |
| |
| void GlslangGetShaderSource(const GlslangSourceOptions &options, |
| const gl::ProgramState &programState, |
| const gl::ProgramLinkedResources &resources, |
| GlslangProgramInterfaceInfo *programInterfaceInfo, |
| gl::ShaderMap<std::string> *shaderSourcesOut, |
| ShaderInterfaceVariableInfoMap *variableInfoMapOut) |
| { |
| for (const gl::ShaderType shaderType : gl::AllShaderTypes()) |
| { |
| gl::Shader *glShader = programState.getAttachedShader(shaderType); |
| (*shaderSourcesOut)[shaderType] = glShader ? glShader->getTranslatedSource() : ""; |
| } |
| |
| gl::ShaderType xfbStage = programState.getAttachedTransformFeedbackStage(); |
| gl::ShaderType frontShaderType = gl::ShaderType::InvalidEnum; |
| |
| for (const gl::ShaderType shaderType : programState.getExecutable().getLinkedShaderStages()) |
| { |
| const bool isXfbStage = |
| shaderType == xfbStage && !programState.getLinkedTransformFeedbackVaryings().empty(); |
| GlslangAssignLocations(options, programState, resources.varyingPacking, shaderType, |
| frontShaderType, isXfbStage, programInterfaceInfo, |
| variableInfoMapOut); |
| |
| frontShaderType = shaderType; |
| } |
| } |
| |
| angle::Result GlslangTransformSpirvCode(const GlslangErrorCallback &callback, |
| const GlslangSpirvOptions &options, |
| const ShaderInterfaceVariableInfoMap &variableInfoMap, |
| const spirv::Blob &initialSpirvBlob, |
| spirv::Blob *spirvBlobOut) |
| { |
| if (initialSpirvBlob.empty()) |
| { |
| return angle::Result::Continue; |
| } |
| |
| #if defined(ANGLE_DEBUG_SPIRV_TRANSFORMER) && ANGLE_DEBUG_SPIRV_TRANSFORMER |
| spvtools::SpirvTools spirvTools(SPV_ENV_VULKAN_1_1); |
| spirvTools.SetMessageConsumer(ValidateSpirvMessage); |
| std::string readableSpirv; |
| spirvTools.Disassemble(initialSpirvBlob, &readableSpirv, 0); |
| fprintf(stderr, "%s\n", readableSpirv.c_str()); |
| #endif // defined(ANGLE_DEBUG_SPIRV_TRANSFORMER) && ANGLE_DEBUG_SPIRV_TRANSFORMER |
| |
| // Transform the SPIR-V code by assigning location/set/binding values. |
| SpirvTransformer transformer(initialSpirvBlob, options, variableInfoMap, spirvBlobOut); |
| ANGLE_GLSLANG_CHECK(callback, transformer.transform(), GlslangError::InvalidSpirv); |
| |
| // If there are aliasing vertex attributes, transform the SPIR-V again to remove them. |
| if (options.shaderType == gl::ShaderType::Vertex && HasAliasingAttributes(variableInfoMap)) |
| { |
| spirv::Blob preTransformBlob = std::move(*spirvBlobOut); |
| SpirvVertexAttributeAliasingTransformer aliasingTransformer( |
| preTransformBlob, variableInfoMap, std::move(transformer.getVariableInfoByIdMap()), |
| spirvBlobOut); |
| ANGLE_GLSLANG_CHECK(callback, aliasingTransformer.transform(), GlslangError::InvalidSpirv); |
| } |
| |
| ASSERT(spirv::Validate(*spirvBlobOut)); |
| |
| return angle::Result::Continue; |
| } |
| |
| angle::Result GlslangGetShaderSpirvCode(const GlslangErrorCallback &callback, |
| const gl::ShaderBitSet &linkedShaderStages, |
| const gl::Caps &glCaps, |
| const gl::ShaderMap<std::string> &shaderSources, |
| gl::ShaderMap<spirv::Blob> *spirvBlobsOut) |
| { |
| TBuiltInResource builtInResources(glslang::DefaultTBuiltInResource); |
| GetBuiltInResourcesFromCaps(glCaps, &builtInResources); |
| |
| glslang::TShader vertexShader(EShLangVertex); |
| glslang::TShader fragmentShader(EShLangFragment); |
| glslang::TShader geometryShader(EShLangGeometry); |
| glslang::TShader tessControlShader(EShLangTessControl); |
| glslang::TShader tessEvaluationShader(EShLangTessEvaluation); |
| glslang::TShader computeShader(EShLangCompute); |
| |
| gl::ShaderMap<glslang::TShader *> shaders = { |
| {gl::ShaderType::Vertex, &vertexShader}, |
| {gl::ShaderType::Fragment, &fragmentShader}, |
| {gl::ShaderType::TessControl, &tessControlShader}, |
| {gl::ShaderType::TessEvaluation, &tessEvaluationShader}, |
| {gl::ShaderType::Geometry, &geometryShader}, |
| {gl::ShaderType::Compute, &computeShader}, |
| }; |
| for (const gl::ShaderType shaderType : linkedShaderStages) |
| { |
| if (shaderSources[shaderType].empty()) |
| { |
| continue; |
| } |
| |
| glslang::TProgram program; |
| |
| ANGLE_TRY(CompileShader(callback, builtInResources, shaderType, shaderSources[shaderType], |
| shaders[shaderType], &program)); |
| |
| ANGLE_TRY(LinkProgram(callback, &program)); |
| |
| glslang::TIntermediate *intermediate = program.getIntermediate(kShLanguageMap[shaderType]); |
| glslang::GlslangToSpv(*intermediate, (*spirvBlobsOut)[shaderType]); |
| } |
| |
| return angle::Result::Continue; |
| } |
| |
| } // namespace rx |